Compare commits

...

2449 Commits

Author SHA1 Message Date
6631e2cb6f Add Rin to homepage counter 2023-10-23 23:10:45 -07:00
James Shiffer
7040b1b8e5 Fembooru changes 2022-05-14 16:44:50 -07:00
Shish
c1068f1b2b bump 2020-06-24 16:14:49 +01:00
Shish
bb5614c5ef strip auth info from debug info dump 2020-06-24 15:09:53 +01:00
Shish
81417a5031 make info command match help text 2020-06-24 15:00:44 +01:00
Shish
2197b15012 Add core/sanitize_php.php
A small number of PHP-sanity things (eg don't silently ignore errors) to
be included right at the very start of index.php and tests/bootstrap.php
2020-06-24 14:54:46 +01:00
Shish
eecd35d175 turn 'Use of undefined constant' into an error 2020-06-24 13:00:46 +01:00
Shish
1c216e8d51 formatting 2020-06-24 12:44:35 +01:00
Shish
04987ea70e 'resize' support for static thumbnailer 2020-06-23 15:17:54 +01:00
Matthew Barbour
844ec8b53e Removed stump user config section 2020-06-23 15:15:55 +01:00
Matthew Barbour
72de50aa71 Added bulk download extension 2020-06-23 15:15:43 +01:00
Matthew Barbour
ac63992efa Added BulkActionException to allow clean error feedback from bulk actions 2020-06-23 15:15:10 +01:00
Matthew Barbour
7c32b1f7a8 Fixed issue where enabling bulk selection, then disabling it, resulted in subsequent bulk actions not applying to any items 2020-06-23 15:14:58 +01:00
Matthew Barbour
919a3039c4 Added user API key system 2020-06-23 15:14:04 +01:00
Matthew Barbour
ea34d9b756 Changes and bugfixes for bulk import extension 2020-06-23 15:12:26 +01:00
Matthew Barbour
30f62c2ff8 Fixed transaction issue between cron uploader and bulk import 2020-06-23 15:11:35 +01:00
Matthew Barbour
9b9f1d0341 Cleaned up some warnings in bulk import/export extension
Added transactions to bulk import
Renamed beginTransaction to begin_transaction for naming consistency
Updated cron uploader to handle bulk import transactions
2020-06-23 15:07:00 +01:00
Matthew Barbour
835c3b68a1 Added null return option 2020-06-23 15:06:26 +01:00
Matthew Barbour
b937ad6255 Added thumbnail scaling options
Changed ffmpeg thumbnailer to instead output a full-size png which is forwarded to the image thumbnailer, to allow it to take advantage of all available scaling options
2020-06-23 15:05:55 +01:00
Matthew Barbour
8e976fb812 Added "any" search option for private images 2020-06-23 15:03:44 +01:00
Matthew Barbour
dd08b936e3 Added skipped count to bulk import 2020-06-23 15:03:32 +01:00
Matthew Barbour
1fdd5bf575 New private image extension 2020-06-23 15:03:17 +01:00
Matthew Barbour
6d16c52367 New bulk import/export extension 2020-06-23 15:01:22 +01:00
Matthew Barbour
587735a866 Added terabyte support to the shorthand functions 2020-06-23 14:58:41 +01:00
Shish
a2fe0725f5 extra credit for Tag EditCloud 2020-06-22 18:14:07 +01:00
Shish
73660b376e
Merge pull request #732 from LaureeGrd/master
Tag EditCloud: Added category sorting and grouped tags.
2020-06-22 18:13:32 +01:00
Matthew Barbour
d243867b18 Removed errant colon 2020-06-22 18:07:45 +01:00
Matthew Barbour
fac2067069 Corrected issue with post title edit field width 2020-06-22 18:07:02 +01:00
Matthew Barbour
2f313b704a Making document ready events consistent 2020-06-22 18:06:35 +01:00
Matthew Barbour
8fe7038e73 Added option to BulkActionEvent to prevent redirect 2020-06-22 18:06:05 +01:00
Matthew Barbour
c171e98591 Changed int input field to type number 2020-06-22 18:03:40 +01:00
Matthew Barbour
edc8e5aa43 Added bulk import/export to gd suggestion 2020-06-22 18:03:13 +01:00
Matthew Barbour
f6923af8ab Added source logging to cron upload output when all sources are enabled 2020-06-22 18:03:03 +01:00
Matthew Barbour
18cd74f57d Changed a bunch of core extensions to be hidden since there's nothing to interact with 2020-06-22 18:02:47 +01:00
Matthew Barbour
4d69e7ce34 Added early 404 kill if requested image is not found 2020-06-22 18:02:21 +01:00
Matthew Barbour
04cde74226 Performance improvement for counting image category tags 2020-06-22 18:02:01 +01:00
Matthew Barbour
b2405166b3 Added git information to system info for telling what git commit a submission is for 2020-06-22 18:01:02 +01:00
Matthew Barbour
4d0b90921d Added missing AutoTaggerException 2020-06-22 18:00:23 +01:00
Shish
05d4a3a592
Merge pull request #734 from shish/pg-action
Fix postgres testing
2020-06-22 17:27:18 +01:00
Shish
b3fb923cd1 Fix postgres testing
Looks like github updated their default container, so now it contains
postgres and postgres-client by default - we just need to manually
launch the installed-but-disabled daemon
2020-06-22 16:19:11 +01:00
Shish
ad1e52bf05 DATABASE_TIMEOUT isn't set in the installer 2020-06-22 16:08:04 +01:00
LaureeGrd
549ec593bb Tag EditCloud: Added category sorting and grouped tags.
This change implements a simple category-based alphabetical sorting system that puts all tags containing ':' in front of general tabs. It also groups them together for easier styling into columns, grids, or even opening the door for drop-down categories in the edit menu. A much needed feature for me since I have hundreds of tags and I manage them all by my own.
2020-06-14 05:32:53 -03:00
Matthew Barbour
ed8a9fca52 Removed function stub 2020-06-03 20:02:55 +01:00
Matthew Barbour
ec290d8676 Added additional optional video formats to the video handler 2020-06-03 20:01:52 +01:00
Matthew Barbour
5446f29141 improved filetype error handling 2020-06-03 20:01:08 +01:00
Matthew Barbour
86f7a06ed0 New options for cron uploader:
Logging level
Including all logs in output
Stop on error instead of continuing
2020-06-03 20:00:54 +01:00
Matthew Barbour
e1aefb78ab Fix for cron uploader issue with root paths ending in a slash 2020-06-03 19:59:29 +01:00
Matthew Barbour
12c331cbd2 Removed extra tag set from cron uploader, no longer needed now that it runs as a user 2020-06-03 19:58:57 +01:00
Matthew Barbour
8b407e3df3 Changed video html element to use the image objects height and width, if available 2020-06-03 19:58:23 +01:00
Matthew Barbour
bccb206369 formatting pass 2020-06-03 19:58:13 +01:00
Matthew Barbour
2bb5f349f9 Added file type names
Formatted file
2020-06-03 19:57:47 +01:00
Matthew Barbour
007e07e507 Various changes to cron uploader:
Removed count limit, the cron job now checks the max PH execution time and auto-stops itself at 80% of that value.

Now skips os-specific image cache files like thumbs.db and the __macosx folder.

Changed failed folder re-deployment to allow re-deploying to populated queue, making it easier to re-process lots of failed batches all at once.

Changed page to output as a stream, allowing a long-running process to provide output as it runs rather than just at the very end.

Changed import loop to use the yield convention, allowing faster consumption of found files and lower memory use overall.
2020-06-03 19:57:27 +01:00
Matthew Barbour
63b2601e67 Mime type handling overhaul
Changed mime type map to deal with the reality that certain file types have multiple extensions and/or multiple mime types, as well as constants supporting all of the data. Created new functions using the updated mime type map to resolve mime types and extensions. Updated various items around the project that determine mime/extension to take advantage of the new functions.
2020-06-03 19:47:40 +01:00
Matthew Barbour
16c58e266b Added manual page mode to allow extensions to have direct control of the output 2020-06-03 19:40:43 +01:00
Matthew Barbour
6145ecc6f8 Updated post title extension to resolve set_title being removed from display image event
Resolves #724
2020-05-29 22:59:37 +01:00
Matthew Barbour
830915adf2 Fixed lite theme not showing image titles 2020-05-29 22:59:30 +01:00
Shish
06bd4589da option for admins to create new users 2020-05-19 19:33:51 +01:00
Shish
1e76fb239e s/fullrandom/dailyshuffle/, and run formatter 2020-05-13 13:03:49 +01:00
Shish
f7c6b662cd
Merge pull request #720 from MetallicAchu/master
Added a new search criteria "fullrandom"
2020-05-13 13:02:11 +01:00
MetallicAchu
00060c34c2
Merge pull request #1 from MetallicAchu/MetallicAchu-patch-1
Update main.php
2020-05-07 08:12:32 +03:00
MetallicAchu
10d46395d7
Update main.php
Added order[=|:]fullrandom so user doesn't need to choose a new seed every time.
Thus the list will change on a daily basis without user interaction, giving a more dynamic feel to the website
2020-05-07 08:10:15 +03:00
Shish
72645af9a4 refactor a bunch of weirdness in image replacement 2020-04-25 21:38:11 +01:00
Shish
2cae6cd273 format 2020-04-25 21:36:28 +01:00
Shish
0b2e36303d allow bypassing auth tokens in unit tests 2020-04-25 21:35:14 +01:00
Shish
b0cb46abca test a couple extra branches 2020-04-24 14:10:45 +01:00
Shish
78710166a1 add a extra escape, fixes #718 2020-04-20 09:53:44 +01:00
Shish
c146a9f53d don't double-escape 2020-04-12 12:45:19 +01:00
Shish
f6112d26a2 unify single and global history pages 2020-04-12 12:43:12 +01:00
Shish
b04b5af190
Merge pull request #717 from DanielOaks/add-logout-button
Add logout button to themes that use subnav bar
2020-04-06 16:54:48 +01:00
Daniel Oaks
02d42a01b4 Add logout button to themes that use subnav bar 2020-04-07 01:36:10 +10:00
Shish
0039aafe94 avoid excess ampersands 2020-04-02 22:38:50 +01:00
Shish
1d389f0156 make tests/router.php more like .htaccess 2020-04-02 22:31:57 +01:00
Shish
69cb67fe24 stagger thumbnail cache 2020-03-28 16:11:05 +00:00
Shish
5ea26a80cc nicetest without http vs https pain 2020-03-28 15:48:27 +00:00
Shish
126c629a1a test 2020-03-28 14:39:03 +00:00
Shish
ab4b745310 test 2020-03-28 14:33:48 +00:00
Shish
f47e35e4e5 make make_link more sane 2020-03-28 14:11:14 +00:00
Shish
fd359fb08c remove broken tagger extension 2020-03-28 00:56:54 +00:00
Shish
866b77ab19 set max-width / max-height for random and featured image blocks 2020-03-28 00:23:29 +00:00
Shish
b60e8ac5b4 make modify_url work better 2020-03-27 23:35:07 +00:00
Shish
70acc6015b drop support for ie6 2020-03-27 20:57:15 +00:00
Shish
a3a129df5f more niceurlness 2020-03-27 20:53:21 +00:00
Shish
02675609b4 more referer dedupe 2020-03-27 20:24:26 +00:00
Shish
85662575c5 stop warning for lack of referer / user-agent - not having those is now pretty normal 2020-03-27 19:42:46 +00:00
Shish
c16e3fd939 dedupe some referer handling 2020-03-27 19:41:34 +00:00
Shish
5ea7cc5b36 SCRIPT_NAME instead of PHP_SELF to find self for niceurl test 2020-03-27 19:03:46 +00:00
Shish
efde5e1edf bump 2020-03-27 14:56:59 +00:00
Shish
7b9c9dc208 Make SHM_FORM generate the correct targets 2020-03-27 14:46:55 +00:00
Shish
bc3de6a52a a bunch more testing and fixes for Pools 2020-03-27 14:41:24 +00:00
Shish
d85f4d3799 automatic version 2020-03-27 12:24:47 +00:00
Shish
880a702b42 dedupe page_number parsing 2020-03-27 00:23:29 +00:00
Shish
b5f0bc7621 a bunch of pools cleanup 2020-03-27 00:15:15 +00:00
Shish
599043baa5 whitespace 2020-03-27 00:06:55 +00:00
Shish
36a2125e90 basic tests for page nav 2020-03-26 21:28:36 +00:00
Shish
1f50f14672 basic tests for help pages 2020-03-26 21:28:23 +00:00
Shish
b0c5043892 test InitExt / DatabaseUpgrade 2020-03-26 19:06:30 +00:00
Shish
861ee946a9 typo 2020-03-26 19:03:34 +00:00
Shish
237f8148f3 a bunch of pools tests and fixes 2020-03-26 18:52:31 +00:00
Shish
17f3b44212 mention system_info in bug report form 2020-03-26 17:23:23 +00:00
Shish
511a82f2ba include database version in sys info 2020-03-26 16:57:08 +00:00
Shish
2d0b107adb convert pool IDs to ints 2020-03-26 16:50:16 +00:00
Shish
ecbf4f52a0 make sysinfo part of core, and use YAML for easier parsing 2020-03-26 16:46:09 +00:00
Shish
06e5b02874 streamline bug template 2020-03-26 15:20:46 +00:00
Shish
f819993685 remove nit 2020-03-26 15:16:30 +00:00
Shish
1b10d8583e missed a reference 2020-03-26 15:01:26 +00:00
Shish
e362f3bad2 stop depending on unmaintained tablesorter plugin 2020-03-26 14:57:38 +00:00
Shish
5f5b858175 allow images and thumbs to be cached when served in docker 2020-03-25 19:57:50 +00:00
Shish
168cf99188 shrink docker image from ~550MB to ~300MB 2020-03-25 19:43:28 +00:00
Shish
134fd7d919 xdebug only for unit-test image 2020-03-25 19:37:38 +00:00
Shish
ea637132bd named layers 2020-03-25 19:36:41 +00:00
Shish
566c92b780 make sure version is in core/sys_config 2020-03-25 15:31:28 +00:00
Shish
4e4deed889 bump 2020-03-25 15:27:50 +00:00
Shish
d4e05d947a bump 2020-03-25 15:20:55 +00:00
Shish
25248c089d inherit signup page from default on lite theme 2020-03-25 15:19:28 +00:00
Shish
0094d5c1a8 attempt 2 at auto-release 2020-03-25 13:53:21 +00:00
Shish
f0db4f9a02 first attempt at automated release 2020-03-25 13:40:54 +00:00
Shish
03806d0420 https a bunch of things 2020-03-25 11:47:00 +00:00
Shish
c3b67f346b Actually, let's have the docs in the wiki 2020-03-25 11:26:33 +00:00
Shish
edee8e7427 fix for EXIF with strict types 2020-03-25 10:50:32 +00:00
Shish
c794e457b1 + 2020-03-23 20:01:37 +00:00
Shish
1753cbd72b bump for cleaner install 2020-03-23 20:00:53 +00:00
Shish
8b1b4d257e stick a blank index.php in data/ just in case somebody left directory-indexing on 2020-03-23 19:56:05 +00:00
Shish
ea0e83abc9 clearer missing-vendor screen 2020-03-23 19:50:28 +00:00
Shish
bf4280461f upgrade docs 2020-03-23 19:01:37 +00:00
Shish
d0b00a72ce
Update INSTALL.md 2020-03-23 18:51:17 +00:00
Shish
d14819387e composer is a pain on windows 2020-03-23 18:50:45 +00:00
Shish
040bffa4f6 even the installer requires composer now, so check for that first 2020-03-23 18:47:18 +00:00
Shish
3f26013b28 the 'binary' releases are hugely out of date 2020-03-23 18:38:08 +00:00
Shish
a3f0c94ca2 version bump, and don't suggest that people should use old branches 2020-03-23 18:31:10 +00:00
Shish
9147b64625 more dev docs 2020-03-23 18:21:27 +00:00
Shish
9a21716f5e
Merge pull request #705 from DanielOaks/extend-wiki-tagcategories
Extend Wiki integration a bit
2020-03-23 14:22:41 +00:00
Daniel Oaks
0029aa5320 Add shortwiki entries when viewing a single tag 2020-03-23 22:48:38 +10:00
Daniel Oaks
deac369d26 Colour wiki titles using tag categories 2020-03-23 22:45:31 +10:00
Shish
4f04635cd8
Merge pull request #704 from DanielOaks/fix-futaba-strict
Fix Futaba theme with new strict types
2020-03-23 12:40:08 +00:00
Daniel Oaks
fc59fe6a3d Fix Futaba theme with new strict types 2020-03-23 22:29:34 +10:00
Shish
1d6ad6cb09
Merge pull request #703 from DanielOaks/more_tag_category_integration
More tag category integration
2020-03-23 12:14:13 +00:00
Daniel Oaks
a83c460b70 Fix double-escaping pointed out by Shish <3 2020-03-23 21:22:30 +10:00
Daniel Oaks
9484c9173c Colour tag categories in tag list+map 2020-03-23 15:02:09 +10:00
Daniel Oaks
b3e7d46351 Add tagcategories->getTagHtml helper 2020-03-23 15:01:24 +10:00
Shish
52a232d113 ... 2020-03-23 00:24:37 +00:00
Shish
92b000ed4e derp 2020-03-23 00:21:35 +00:00
Shish
d599ef4f22 hide redundant part 2020-03-23 00:15:20 +00:00
Shish
1fc75c0b18 Whenever we display an image for any reason, show the admin block, fixes #621 2020-03-23 00:06:43 +00:00
Shish
bfcefb97e7 docs 2020-03-22 15:52:03 +00:00
Shish
76b346b45d docs folder 2020-03-22 15:49:55 +00:00
Shish
fe874389ab change test name 2020-03-22 15:31:53 +00:00
Shish
ad9cca36b1 improvements to run inside docker 2020-03-22 15:23:23 +00:00
Shish
9d3939b87f derp 2020-03-22 14:01:27 +00:00
Shish
bbb8d8be4f docker docs 2020-03-21 22:47:34 +00:00
Shish
6308dfefac font size 2020-03-21 22:17:24 +00:00
Shish
718f72c42d case-insensitive username search 2020-03-21 22:17:24 +00:00
Shish
80354c99b5 cached docker builds, scheduled clean 2020-03-20 16:59:11 +00:00
Shish
f633f4abec order more 2020-03-19 15:10:50 +00:00
Shish
5c1925bc2d order regs 2020-03-19 15:02:47 +00:00
Shish
99c461e0e0 streamline docker build 2020-03-19 14:58:55 +00:00
Shish
6955053b87 fewer layers 2020-03-19 14:52:54 +00:00
Shish
4dda69df45 ... 2020-03-19 14:03:36 +00:00
Shish
345faeba8d ffffff yaml... 2020-03-19 14:02:49 +00:00
Shish
36f0024426 ffffff php... 2020-03-19 14:01:48 +00:00
Shish
15258657fc only publish after tests pass 2020-03-19 13:44:56 +00:00
Shish
adf4534816 base user all permissions disabled by default, and reg viewer 2020-03-19 13:37:14 +00:00
Shish
e672fbb343 security 2020-03-19 03:49:06 +00:00
Shish
fea8f90f68 backwards-compatible registry 2020-03-19 03:40:29 +00:00
Shish
c528cd9a42 extensions in stand-alone mode 2020-03-19 00:47:47 +00:00
Shish
2685ff150a
Merge pull request #701 from Mik-chan/master
Fixed Admin permissions
2020-03-18 23:18:56 +00:00
MikChan
cb275d3e3b
Fixed code style according to PSR-2 2020-03-19 01:15:25 +03:00
MikChan
a5f5b44798
Fixed Admin permissions 2020-03-19 01:06:55 +03:00
MikChan
e7c779796e
Merge pull request #2 from shish/master
Update
2020-03-19 01:02:11 +03:00
Shish
6fbdeb2b32 router working with niceurls 2020-03-18 20:27:52 +00:00
Shish
3206110cf4 InitExt / DatabaseUpgrade as part of the try/catch 2020-03-18 20:27:52 +00:00
Shish
df3660fbcf bbcode signup message 2020-03-18 17:29:08 +00:00
Shish
81c72b0a72 bumps 2020-03-18 17:24:06 +00:00
Shish
bd788582cf docker push 2020-03-17 20:15:36 +00:00
Shish
2da46ee484 start mysql 2020-03-16 15:22:22 +00:00
Shish
f67fcaf35d checkout v2 2020-03-16 15:06:12 +00:00
Shish
d261a7f76e show user list to anyone who can edit passwords, not the more-restricted classes 2020-03-13 10:08:23 +00:00
Shish
7fa9d11512 hard-code one bad case 2020-03-13 10:04:12 +00:00
Shish
5bebee7892 no need to update for mysql 2020-03-13 09:54:14 +00:00
Shish
8f3002f2d8 dot in special chars list 2020-03-13 09:47:43 +00:00
Shish
dede46374f avoid double-escape for upload collision error 2020-03-13 09:39:00 +00:00
Shish
1597eff082 lint fixing 2020-03-13 09:23:54 +00:00
Shish
591c21f3ce bumps, and add user ID columns 2020-03-09 23:54:00 +00:00
Shish
d18d25b3d1 most recent users first 2020-03-09 23:51:01 +00:00
Shish
7b59eebb0a
Merge pull request #698 from DanielOaks/um-whitespace-why
Weird... whitespace issue
2020-03-08 18:49:28 +00:00
Shish
07122f464b
Merge pull request #699 from DanielOaks/fix-danbooru-strict
Fix danbooru themes with new strict types
2020-03-08 18:46:52 +00:00
Shish
30a660cebb
Merge pull request #700 from DanielOaks/dockerfile-changes
Fix Dockerfile script not being marked as executable
2020-03-08 18:46:36 +00:00
Daniel Oaks
2c36dbef38 Mark the docker script as executable 2020-03-09 02:03:49 +10:00
Daniel Oaks
388a2545d1 Fix danbooru themes with new strict types 2020-03-09 01:49:36 +10:00
Daniel Oaks
373c6aca08 Fix... weird whitespace I guess? 2020-03-09 01:10:06 +10:00
Shish
135432b329 don't crash when docs are missing 2020-03-06 13:44:51 +00:00
Shish
f0f8242c3c allow extension documentation to contain raw HTML 2020-03-05 02:09:16 +00:00
Shish
3554d0e323 mark PHP as always using LF rather than CRLF, to be consistent with php-cs-fixer, fixes #694 2020-03-05 01:26:26 +00:00
Shish
04bfdf895f
Merge pull request #697 from sanmadjack/pull
Auto-tag extension and small changes
2020-03-05 01:19:41 +00:00
Shish
2e00b8c9ce scrollIntoView 2020-03-02 17:18:53 +00:00
Shish
5058e1f3fd standard jquery 2020-03-02 17:12:43 +00:00
Shish
ca462d86f1 initial cbz support 2020-03-02 16:04:29 +00:00
Matthew Barbour
8922966ddb Adjusted autotagger tests 2020-03-02 16:01:42 +00:00
Matthew Barbour
9ea2eeb831 Adjusted autotagger tests 2020-03-02 15:59:15 +00:00
Matthew Barbour
823c5d5610 Adjusted autotagger tests 2020-03-02 15:45:55 +00:00
Matthew Barbour
6947b726f0 Added exists function to database object 2020-03-02 15:42:28 +00:00
Matthew Barbour
ba599d5d1b Added count to alias editor import 2020-03-02 15:42:09 +00:00
Matthew Barbour
8ff52b9220 Removed rdundant bytes 2020-03-02 15:41:58 +00:00
Matthew Barbour
cff72263dc Added text to transcode message to indicate file size change 2020-03-02 15:41:43 +00:00
Shish
2f51c14afd fmt 2020-03-02 15:40:13 +00:00
Matthew Barbour
ec9244d553 Adjusted autotagger tests 2020-03-02 15:39:26 +00:00
Matthew Barbour
70560398e3 Adjusted autotagger tests 2020-03-02 15:34:25 +00:00
Matthew Barbour
b4bde94516 Added auto-tagger extension 2020-03-02 15:24:40 +00:00
Matthew Barbour
ac80ca8443 Added exists function to database object 2020-03-02 15:21:49 +00:00
Matthew Barbour
3fbbcdd473 Added count to alias editor import 2020-03-02 15:21:27 +00:00
Matthew Barbour
e159194737 Removed rdundant bytes 2020-03-02 14:24:08 +00:00
Matthew Barbour
546f0701a9 Added text to transcode message to indicate file size change 2020-03-02 14:24:08 +00:00
Shish
126412fb4b
Merge pull request #696 from shish/dependabot/composer/enshrined/svg-sanitize-0.13.1
Bump enshrined/svg-sanitize from 0.12.0 to 0.13.1
2020-03-02 10:08:40 +00:00
Shish
3844595bd1 modernish js 2020-03-02 09:33:56 +00:00
Shish
e50ff27510 === 2020-03-02 09:19:50 +00:00
Shish
05f0db73ff don't do prev/next on textarea 2020-03-02 09:19:17 +00:00
dependabot[bot]
22171806da
Bump enshrined/svg-sanitize from 0.12.0 to 0.13.1
Bumps [enshrined/svg-sanitize](https://github.com/darylldoyle/svg-sanitizer) from 0.12.0 to 0.13.1.
- [Release notes](https://github.com/darylldoyle/svg-sanitizer/releases)
- [Commits](https://github.com/darylldoyle/svg-sanitizer/compare/0.12.0...0.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-02-27 20:39:06 +00:00
Shish
ec7f63ee1f cache thumbs for 24h instead of forever 2020-02-26 10:42:08 +00:00
Shish
33731e8cb0 separate link formatting for URLs and for plain text 2020-02-25 12:26:56 +00:00
Shish
d97f492aaf format 2020-02-25 12:18:47 +00:00
Shish
b44a4de42c one return 2020-02-25 12:18:18 +00:00
Shish
89dd1a7658 create_scaled_image, for other uses 2020-02-25 12:04:37 +00:00
Shish
85731ebf65 file-accept 2020-02-25 12:04:37 +00:00
Shish
e2319769c6 Make ArchiveFileHandler inherit from DataHandlerExtension 2020-02-25 12:04:37 +00:00
Shish
d1374c021e theme-color 2020-02-24 22:47:23 +00:00
Shish
08acfd2ac4
Merge pull request #695 from sanmadjack/pull
Bugfixes
2020-02-24 15:01:04 +00:00
Matthew Barbour
ea96f415c5 Fixed replace creating a new image instead of replacing, fixed null source causing repalce error 2020-02-24 14:40:08 +00:00
Matthew Barbour
30761e6d1f Added mime check to svg check so that it doesn't try to load every upload into memory 2020-02-24 14:29:27 +00:00
Matthew Barbour
152e55b5db Changed Image::by_hash so that it isn't case-sensitive 2020-02-23 23:07:02 -06:00
Matthew Barbour
6d58fe9b32 Fix invalid type arg 2020-02-23 23:07:02 -06:00
Shish
e1e161759e rename handle_static to static_files - stop confusing it with file handlers 2020-02-23 18:48:25 +00:00
Shish
9dcc8b7da1 rename handle_404 to four_oh_four - stop confusing it with file handlers 2020-02-23 18:46:27 +00:00
Shish
174b87d0c4 info show types 2020-02-23 18:38:23 +00:00
Shish
b5e9daeab5 dedupe more data handling 2020-02-23 18:37:22 +00:00
Shish
674d3fc6fa dedupe create_image_from_data 2020-02-23 18:19:27 +00:00
Shish
394e57103c tidy 2020-02-23 18:14:35 +00:00
Shish
77fc510bb3 DataUploadEvent already asserts that file exist 2020-02-23 18:14:35 +00:00
Shish
c5d8585824 info command 2020-02-23 18:14:35 +00:00
Shish
4ade3452ee dedupe getSubclassesOf 2020-02-23 18:14:35 +00:00
Shish
9b6eb0e5e2 more bump 2020-02-23 11:23:58 +00:00
Shish
9b822e4132 bump 2020-02-23 11:22:15 +00:00
Shish
f53cd593c5 travis ci -> github actions in the README 2020-02-14 11:31:13 +00:00
Shish
43ab3088cf cache key 2020-02-13 20:54:59 +00:00
Shish
58346f8b49 stop fixing height / width of videos - that doesn't work well with max-width 2020-02-13 20:54:59 +00:00
Shish
9c47bdb100 users who can edit locks, can edit locked images 2020-02-13 20:54:45 +00:00
Shish
3a57817fc2 Spread ParseLinkTemplate work across relevant extensions 2020-02-09 19:22:25 +00:00
Shish
41a205d24a avoid having nice_urls as both system and admin setting 2020-02-09 16:36:22 +00:00
Shish
641fd5a16f remove CACHE_HTTP - client side page cache causes more problems than it solves 2020-02-09 16:25:17 +00:00
Shish
3c78b5685e remove runtime-coverage -- when we want coverage, we enable it at the PHP runtime level 2020-02-09 16:08:35 +00:00
Shish
d749784e95 remove redundant escaping and split load_balance_url into a separate function with testing 2020-02-09 16:02:37 +00:00
Shish
6087d31812 command to wipe thumb cache 2020-02-09 00:32:53 +00:00
Shish
9514075594 order=length 2020-02-09 00:32:38 +00:00
Shish
cc7a33b31f show lengths to 1/10th second 2020-02-09 00:32:20 +00:00
Shish
4b5becfb7f video length in thumb 2020-02-08 20:44:23 +00:00
Shish
fd7c774f5b handle_svg doesn't need to override ALL of onDataUpload 2020-02-08 20:44:23 +00:00
Shish
2f975eb6d4 don't crash if UA is empty 2020-02-08 11:55:06 +00:00
Shish
85cf801fb3 also msnbot 2020-02-08 11:43:04 +00:00
Shish
0b304bdf2e use Permissions for Favourites / Artist / Relationships, and also check image lock when sending ImageInfoSet instead of getting each receiver to check it 2020-02-08 00:24:42 +00:00
Shish
c6d50f417f note that caches are useful 2020-02-07 22:42:12 +00:00
Shish
5a8d2be90a some extra docs 2020-02-07 22:40:11 +00:00
Shish
45fc6758f0 didn't mean to commit the granular bootstrap tracing... 2020-02-07 22:10:26 +00:00
Shish
aac9cf1fe0 merge some self-contained bits from @sanmadjack's branch 2020-02-07 22:05:27 +00:00
Shish
45347279ce also bingbot and yandex don't need to see deep archives of weird searches 2020-02-06 12:49:11 +00:00
Shish
81ebc51257 actually, we don't really need weird combinations at all... 2020-02-06 03:11:21 +00:00
Shish
cdaecb3380 format 2020-02-06 03:10:30 +00:00
Shish
2b994d5c29 Merge branch 'master' of https://github.com/shish/shimmie2 2020-02-06 03:03:27 +00:00
Shish
c7a152df77 limit google a bit... 2020-02-06 02:59:44 +00:00
Shish
81880f7458 Make installer nice again 2020-02-06 02:19:51 +00:00
Shish
353f536698 UA in trace 2020-02-05 17:23:31 +00:00
Shish
adcd1b6b5e IP in slow log 2020-02-05 17:20:59 +00:00
Shish
03af4dd92f ??? 2020-02-05 09:01:22 +00:00
Shish
23943692ce formatting 2020-02-05 01:38:32 +00:00
Shish
342f30142b re-enable prefetch 2020-02-05 01:27:00 +00:00
Shish
ef82d5f1a1 account for missing tags 2020-02-05 01:26:18 +00:00
Shish
f7feb4075a order, order 2020-02-05 00:27:37 +00:00
Shish
1a07f84622 inline build_accurate_search_querylet 2020-02-05 00:16:47 +00:00
Shish
188d809ee7 trace all CLI commands 2020-02-05 00:16:30 +00:00
Shish
e971d10d41 we can also optimise one negative tag in the same way 2020-02-04 23:49:54 +00:00
Shish
b81a95129c faster search for getting deep into individual tag archives 2020-02-04 23:43:24 +00:00
Shish
c0bdb6b7f8 merge common stuff into build_search_querylet 2020-02-04 23:27:01 +00:00
Shish
aa5cf0e81b optimise counting number of results for one negative tag 2020-02-04 23:05:07 +00:00
Shish
9216be3c96 if we're past the searchable number of pages, don't bother counting the number of pages, just 404 2020-02-04 22:44:27 +00:00
Shish
7d4008bae8 remove email stuff that was never used 2020-02-04 21:09:58 +00:00
Shish
274f9fc7a8 typo 2020-02-04 02:00:26 +00:00
Shish
13f4de8c14 Have arrowkey nav use HTML next/prev links 2020-02-04 01:59:08 +00:00
Shish
015a597027 tighten up browser search 2020-02-04 01:45:45 +00:00
Shish
17c43ec7cc java in the browser is dead - RIP oekaki 2020-02-04 01:25:38 +00:00
Shish
35aca4fa9a separate calls for A and LINK?? 2020-02-04 01:22:26 +00:00
Shish
fdfae4f9c0 first/last links too 2020-02-04 01:18:04 +00:00
Shish
0f0cceae22 format 2020-02-04 01:15:25 +00:00
Shish
d13c91ff9a Merge branch 'master' of https://github.com/shish/shimmie2 2020-02-04 01:14:11 +00:00
Shish
ad905248e8 prev/next/preload links for any page with a paginator 2020-02-04 01:12:50 +00:00
Shish
c3088c57fe remove bulk_remove - it was never completed, and bulk_actions is better 2020-02-04 00:47:13 +00:00
Shish
e46b319295 set theme classes 2020-02-04 00:46:36 +00:00
Shish
116bd8d6e5 media logging 2020-02-02 17:01:17 +00:00
Shish
d880dc7997 don't fail to trace CLI 2020-02-02 17:00:55 +00:00
Shish
9d704183c7 actually do bulk actions from CLI 2020-02-02 17:00:33 +00:00
Shish
7cf5c2a28c bump size of index cli search 2020-02-02 15:53:20 +00:00
Shish
0452de1be9 flush stdout after each CLI logging call 2020-02-02 15:53:05 +00:00
Shish
81cd320928 don't trigger traces for slow uploads 2020-02-02 02:09:34 +00:00
Shish
ebea517c41 in speed mode, only support 10 pages of RSS for each query 2020-02-02 01:59:25 +00:00
Shish
84a4bb7f9a types 2020-02-01 23:50:42 +00:00
Shish
f70bce113d BASE_HREF is always defined, but sometimes defined as null 2020-02-01 23:42:40 +00:00
Shish
05b4cd96dc format 2020-02-01 23:40:05 +00:00
Shish
f5c402ad85 inline a one-use function 2020-02-01 23:37:20 +00:00
Shish
b6656e8141 more privacy 2020-02-01 23:30:19 +00:00
Shish
0bcbcb679e have navlinks as a utility methods that themes can call, if they want to 2020-02-01 23:23:23 +00:00
Shish
321eafa408 format 2020-02-01 23:04:40 +00:00
Shish
6d2c92575d tests for format_milliseconds 2020-02-01 23:03:23 +00:00
Shish
e91acbb2c2 remove unused scoreql parameters 2020-02-01 22:51:30 +00:00
Shish
40ab91f8ea remove redundant scoreql_to_sql 2020-02-01 22:44:50 +00:00
Shish
1589b42a10 test_truncate 2020-02-01 22:35:41 +00:00
Shish
ce8da04d3a dedupe BASE_URL / BASE_HREF 2020-02-01 22:26:08 +00:00
Shish
e9ab6aa802 drop redundant end-php tag 2020-02-01 21:42:47 +00:00
Shish
665d5db3f6 drop un-used, un-tested config variants 2020-02-01 21:40:35 +00:00
Shish
6f7e0e5b12 drop logging args that didn't get used in practice 2020-02-01 21:37:07 +00:00
Shish
3f689b68bc MockDatabase didn't get used 2020-02-01 21:32:38 +00:00
Shish
66bd27b0ee fix login for names with spaces 2020-02-01 21:21:27 +00:00
Shish
a5c7faeff7 move stream_file to its own function 2020-02-01 21:20:32 +00:00
Shish
1b4d06c8d2 explanation 2020-02-01 20:01:25 +00:00
Shish
43ea7fb70c replace array_{add,remove} with array_diff 2020-02-01 19:30:32 +00:00
Shish
deb26ff7d3 faster svg thumbs 2020-02-01 18:51:57 +00:00
Shish
89ca23a4fd cleanup 2020-02-01 18:44:54 +00:00
Shish
720470c948 updates 2020-02-01 18:36:30 +00:00
Shish
dcb1f862e6 types 2020-02-01 18:27:21 +00:00
Shish
7f2f5c342e r34 theme 2020-02-01 18:22:08 +00:00
Shish
f0f3cc7aa0 merge layout stuff into Page class 2020-02-01 18:11:11 +00:00
Shish
de0a7138d2 formatting 2020-02-01 11:59:39 +00:00
Shish
57cd550204 fix user deletion 2020-02-01 11:59:39 +00:00
Shish
ce22deea83 fucking php... 2020-02-01 11:59:39 +00:00
Shish
135b8db720 fix types in http_range support 2020-02-01 11:59:39 +00:00
Shish
fb4a07a3d6 name top level of trace after test class 2020-01-31 01:13:49 +00:00
Shish
e5cd9fa923 microcrud update 2020-01-30 22:55:28 +00:00
Shish
73310fa7d5 fix tag/source reverts 2020-01-30 22:33:43 +00:00
Shish
af733b53ca more encoding 2020-01-30 22:10:51 +00:00
Shish
da42b19d6b make it more explicit that caret/decaret are for encoding tags into URL paths 2020-01-30 21:50:30 +00:00
Shish
fb80509be9 also escape ? and & in tags 2020-01-30 21:24:16 +00:00
Shish
8651cc0d7a get_int for db_version 2020-01-30 21:11:56 +00:00
Shish
3ee05b21e2 cache CLI 2020-01-30 21:05:59 +00:00
Shish
81aef74715 config CLI 2020-01-30 21:05:43 +00:00
Shish
636e1da7d8 format 2020-01-30 14:50:38 +00:00
Shish
012f5d9da8 sqlite support for Approvals 2020-01-30 14:50:30 +00:00
Shish
5aa86963dd trash and rss comments work with sqlite 2020-01-30 14:45:32 +00:00
Shish
0ec305a886 stop using ? as a wildcard 2020-01-30 10:55:35 +00:00
Shish
c2231432f3 no make_link for forms 2020-01-30 10:31:11 +00:00
Shish
55b697c198 block type 2020-01-30 10:26:36 +00:00
Shish
02ec21cb16 weird tag test 2020-01-30 10:26:28 +00:00
Shish
831906681e fixes for mysql 2020-01-30 09:01:19 +00:00
Shish
f90c8cee3c SQL T_T 2020-01-29 20:36:25 +00:00
Shish
fb5bce69f8 SQL T_T 2020-01-29 20:34:02 +00:00
Shish
4e57e04ddf fix a lot of tests 2020-01-29 20:22:50 +00:00
Shish
86d93b2cc2 bool_escape('1') should be True 2020-01-29 20:20:17 +00:00
Shish
41ce16f1b8 ratings works with sqlite (unit tests pass, at least) 2020-01-29 11:30:52 +00:00
Shish
aa1637d128 format 2020-01-29 01:47:43 +00:00
Shish
6d3ca01424 format 2020-01-29 00:49:26 +00:00
Shish
e8a72b0291 clear in setUp 2020-01-29 00:37:31 +00:00
Shish
30bf856f98 test tracing 2020-01-28 23:57:43 +00:00
Shish
7472d6faf0 Add a secret 'static' media engine for fast unit testing 2020-01-28 22:23:03 +00:00
Shish
615da9e9d2 fix all the tests (for sqlite, php7.4, osx, at least) 2020-01-28 21:19:59 +00:00
Shish
ac1076b3f3 don't use string concatenation for sql 2020-01-28 00:49:51 +00:00
Shish
94635c0c00 add some tests 2020-01-28 00:47:30 +00:00
Shish
a887077ac8 remove redundant escapes 2020-01-28 00:16:22 +00:00
Shish
9ac8246fa2 fixes 2020-01-27 22:22:07 +00:00
Shish
95f4474b72 docket 2020-01-27 20:11:28 +00:00
Shish
d6c7857c6b syntax 2020-01-27 20:06:19 +00:00
Shish
9ea70b0055 separate VERSION for tests 2020-01-27 20:02:39 +00:00
Shish
af48aa504b stop ENABLED_MODS 2020-01-27 20:00:23 +00:00
Shish
59c89ee135 Stop failing to have a separate SCORE_VERSION 2020-01-27 19:57:07 +00:00
Shish
15d1e4ef17 Remove MIN_PHP_VERSION - that's not a thing that end users can change 2020-01-27 19:54:51 +00:00
Shish
22c7cab0cd Merge AUTO_DB_UPGRADE into SPEED_HAX 2020-01-27 19:52:54 +00:00
Shish
3c5e6f0746 delete search-accel stuff - built-in queries are faster now 2020-01-27 19:49:50 +00:00
Shish
87d1e21679 merge COMPILE_ELS into SPEED_HAX 2020-01-27 19:48:20 +00:00
Shish
88afc12c13 testing DSN from environment 2020-01-27 19:40:14 +00:00
Shish
d254b98780 more helpful installer exception 2020-01-27 19:37:28 +00:00
Shish
7080f8bc2a fix InstallerException 2020-01-27 19:31:38 +00:00
Shish
7e43e2e304 split www stuff to index.php and test things to tests/bootstrap.php 2020-01-27 19:28:58 +00:00
Shish
eb9d63c2a2 remove non-functional locking 2020-01-27 19:27:31 +00:00
Shish
fbe55ea531 remove non-functional locking 2020-01-27 19:27:20 +00:00
Shish
b0237ddd97 more stuff to util.php 2020-01-27 19:05:43 +00:00
Shish
b03eb861d4 AUTO_DB_UPGRADE define 2020-01-27 18:55:23 +00:00
Shish
fc6db3509a tracer_enabled is global 2020-01-27 18:36:29 +00:00
Shish
dba89e9d13 make all themes have a Page class, to simplify loading 2020-01-27 18:35:36 +00:00
Shish
903679dc53 more stuff into regular functions 2020-01-27 18:24:11 +00:00
Shish
9b50e98927 move some installer bits to util.php 2020-01-27 17:47:28 +00:00
Shish
2d20cf388e Merge branch 'master' of https://github.com/shish/shimmie2 2020-01-27 13:03:15 +00:00
Shish
5dab234630 create test users individually 2020-01-27 13:00:09 +00:00
Shish
88b592c6ef coverage as a separate step 2020-01-27 11:57:34 +00:00
Shish
10f99c45ee composer update, but with 7.3 2020-01-26 23:37:24 +00:00
Shish
50fb0da7bf composer update 2020-01-26 23:26:08 +00:00
Shish
4bd1d8b6ee forms 2020-01-26 23:23:15 +00:00
Shish
50f3d04f0c transload error log-ish 2020-01-26 23:23:01 +00:00
Shish
b98dd3dd76 typing 2020-01-26 22:58:59 +00:00
Shish
a83223f362 strict video 2020-01-26 21:14:50 +00:00
Shish
53d51b1cd1 another 2020-01-26 19:46:10 +00:00
Shish
cddf6e9d5f more types 2020-01-26 19:44:36 +00:00
Shish
f8499be286 plte 2020-01-26 18:53:04 +00:00
Shish
e0778f94f7 more types 2020-01-26 18:47:30 +00:00
Shish
235b976dbc databases... 2020-01-26 18:10:58 +00:00
Shish
f78edfcf99 warner 2020-01-26 17:50:35 +00:00
Shish
9d9532a215 warner 2020-01-26 17:47:41 +00:00
Shish
d3737c7a66 warner 2020-01-26 17:43:39 +00:00
Shish
06fffd6328 more 2020-01-26 17:39:55 +00:00
Shish
973a53c9bb fix 2020-01-26 16:43:41 +00:00
Shish
bb72edd15b tests 2020-01-26 16:40:52 +00:00
Shish
3631084afd format 2020-01-26 16:38:26 +00:00
Shish
60dda96fd2 nits 2020-01-26 16:38:13 +00:00
Shish
6cc7124069 ignore 2020-01-26 16:28:12 +00:00
Shish
9eb5acf2dc use strict types 2020-01-26 16:27:56 +00:00
Shish
f5ccffdaf4 shm_simple_form 2020-01-26 13:29:04 +00:00
Shish
1802b9c7f6 microhtml for blocks 2020-01-23 01:22:08 +00:00
Shish
d7a290b635 index prefetch 2020-01-19 19:13:05 +00:00
Shish
73c63e3477 microhtml for user page 2020-01-16 19:13:12 +00:00
Shish
2c2381d965 microhtml for user page 2020-01-12 16:28:59 +00:00
Shish
f85b43e17c autocomplete hints 2020-01-12 15:19:15 +00:00
Shish
485ad1e569 composer updates 2020-01-12 15:19:15 +00:00
Shish
ff7f456220
Merge pull request #692 from shish/dependabot/composer/enshrined/svg-sanitize-0.12.0
Bump enshrined/svg-sanitize from 0.8.2 to 0.12.0
2020-01-08 18:55:48 +00:00
dependabot[bot]
f27921e9ef
Bump enshrined/svg-sanitize from 0.8.2 to 0.12.0
Bumps [enshrined/svg-sanitize](https://github.com/darylldoyle/svg-sanitizer) from 0.8.2 to 0.12.0.
- [Release notes](https://github.com/darylldoyle/svg-sanitizer/releases)
- [Commits](https://github.com/darylldoyle/svg-sanitizer/compare/0.8.2...0.12.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-08 17:18:32 +00:00
Shish
9f4abdaf79 use the right keys for actions 2020-01-01 10:42:38 +00:00
Shish
5b5e9d8d7a fix #691 2019-12-26 17:08:29 +00:00
Shish
fb2fe58b57 fixes 2019-12-26 16:52:59 +00:00
Shish
057ccd65c3 Merge branch 'master' of https://github.com/shish/shimmie2 2019-12-26 16:37:37 +00:00
Shish
51628607d0 ...? 2019-12-26 16:33:08 +00:00
Shish
6516e5cc46 argh 2019-12-26 16:23:29 +00:00
Shish
8bc7d5d445 pg 2019-12-26 16:21:15 +00:00
Shish
f3cb70a06d Revert "inline some one-use vars"
This reverts commit 1c3d4ad5e3fe2a2f454109b8f00c5d200bc6e5b3.
2019-12-26 16:20:05 +00:00
Shish
bafdb1c769 crud update 2019-12-26 16:04:04 +00:00
Shish
67afe948bd format 2019-12-26 16:00:01 +00:00
Shish
b8dc0a880b if login fails with a space, try with underscore 2019-12-16 09:01:09 +00:00
Shish
daf43049a1 search user by join date 2019-12-16 09:00:41 +00:00
Shish
f6022e80fb Let's only have one Help link 2019-12-16 01:17:27 +00:00
Shish
50a3b23444 microcrud update 2019-12-16 00:12:05 +00:00
Shish
678b25d92b get flash from GET as early as possible 2019-12-16 00:06:04 +00:00
Shish
5d559dc654 fix typo 2019-12-16 00:02:34 +00:00
Shish
86d4f2eb82 permissions for sending & reading PMs, so that ghosts can have them revoked 2019-12-15 20:40:05 +00:00
Shish
70db0ce5bd flash_message -> page->flash, with no cookies 2019-12-15 19:47:18 +00:00
Shish
8740d83686 log ban type 2019-12-15 16:21:48 +00:00
Shish
0d6623c928 case-insensitive username search 2019-12-15 16:21:31 +00:00
Shish
1c3d4ad5e3 inline some one-use vars 2019-12-15 16:13:09 +00:00
Shish
090ff65109 replace ILIKE with the better-supported LOWER 2019-12-15 16:09:48 +00:00
Shish
d7a2ca9ddc remove SCORE_STRNORM - everyone supports LOWER now 2019-12-15 16:07:46 +00:00
Shish
7b7febea55 better validation 2019-12-15 16:01:32 +00:00
Shish
09b9901493 refer to users by name 2019-12-15 15:40:15 +00:00
Shish
d705578f79 formatting 2019-12-15 15:31:44 +00:00
Shish
f09d328b30 log_db search updates 2019-12-15 15:30:52 +00:00
Shish
a4376c9cf1 composer update 2019-12-15 15:29:37 +00:00
Shish
478e927019
Merge pull request #690 from GP32/patch-1
fixing "12 hours ago" error when using SQLite
2019-12-12 12:08:22 +00:00
GP32
e4400631a9
fixing "12 hours ago" error when using SQLite 2019-12-10 12:50:47 +09:00
Shish
cb29d07ecc get rid of exists(), because rowCount doesn't work consistently 2019-12-10 01:17:14 +00:00
Shish
431d6dd523 remove test for dead code 2019-12-09 18:31:57 +00:00
Shish
3c3e551db2 fix composer.lock 2019-12-09 18:18:38 +00:00
Shish
562a2c8fea use microhtml for ext_manager 2019-12-09 14:20:56 +00:00
Shish
5a7af0f083 remove delete-by-query - bulk actions does the same thing better 2019-12-09 14:19:07 +00:00
Shish
d1001b55de no sane defaults yet 2019-12-08 11:06:52 +00:00
Shish
3a15a679bc more custom stuff for log view 2019-12-08 10:52:00 +00:00
Shish
3e83e66d7c microcrud for log_db 2019-12-07 23:00:52 +00:00
Shish
151109ca0e allow adding URLs to get-page 2019-12-07 22:55:40 +00:00
Shish
7f041a9f93 have fatal_error print out a bunch more info when run from CLI 2019-12-07 22:53:59 +00:00
Shish
d6fe059b67 stringer() function, because php lacks a good repr() D: 2019-12-07 22:51:28 +00:00
Shish
0806b2e5f0 Give Event a default toString 2019-12-07 22:49:02 +00:00
Shish
7ca484972f remove Tag Categories page, because it is bad 2019-12-02 00:43:03 +00:00
Shish
3e408c0f28 allow dashes in block IDs 2019-12-01 23:41:10 +00:00
Shish
18be03997a composer update 2019-12-01 23:41:05 +00:00
Shish
ecb6266617 merge 2019-12-01 19:15:40 +00:00
Shish
0a330cd0ba merge 2019-12-01 18:58:13 +00:00
Shish
79e21cec7f microcrud for aliases 2019-12-01 01:02:18 +00:00
Shish
d2b50573c6 microcrud for user list 2019-12-01 00:46:54 +00:00
Shish
e926b15d5f only show current IP to self 2019-11-30 05:07:31 +00:00
Shish
a07220d29b typo fix 2019-11-29 18:16:31 +00:00
Shish
4e03d3cce3 Merge branch 'master' of https://github.com/shish/shimmie2 2019-11-29 18:13:54 +00:00
Shish
71941da552 custom primary keys 2019-11-29 02:21:00 +00:00
Shish
3ac3fcb711 fix whitespace 2019-11-29 02:20:48 +00:00
Shish
7d30aaf1ea microcrud for notatag 2019-11-29 02:07:12 +00:00
Shish
2deaeca133 use the right table 2019-11-29 02:04:14 +00:00
Shish
920bdd1884 microcrud for image hash bans 2019-11-29 01:52:33 +00:00
Shish
ae805be967 separate messages for ban types 2019-11-28 23:40:14 +00:00
Shish
5cc6a7cd68 show user's IP 2019-11-28 23:39:45 +00:00
Shish
a1e67e97b7 Don't cache ghost-ban announcement pages 2019-11-28 21:46:34 +00:00
Shish
efdc903263 fix typo 2019-11-28 21:35:43 +00:00
Shish
30b85f58db anon-ghost mode, and ghosts can't sign up for accounts 2019-11-28 21:32:18 +00:00
Shish
26e24c8988 ban message is not content 2019-11-28 18:10:58 +00:00
Shish
9cab604455 hook bans into UserLoginEvent instead of InitExtEvent 2019-11-28 18:01:21 +00:00
Shish
358f6d7abc ghost bans 2019-11-28 17:20:23 +00:00
Shish
3ed3ea7234 simplify ban fetching code 2019-11-28 16:49:21 +00:00
Shish
56b8d88ca4 install with composer.lock to make sure exact versions are installed 2019-11-28 16:42:59 +00:00
Shish
45351dd7d1 update microcrud 2019-11-28 15:27:36 +00:00
Shish
9e084cd615 support custom ban modes 2019-11-28 14:57:56 +00:00
Shish
7bf60542df script defer 2019-11-28 11:43:30 +00:00
Shish
33a32d2287 faster ip search in postgres 2019-11-28 11:43:30 +00:00
Shish
a9993b47a8 working microcrud for ipbans 2019-11-27 21:06:14 +00:00
Shish
1edc4a37bf remove sys_ip_bans 2019-11-27 19:55:25 +00:00
Shish
80a816de8c get-token and post-page 2019-11-27 16:10:12 +00:00
Shish
6b2304af93 format 2019-11-27 12:13:04 +00:00
Shish
21b983ac85 remove travis 2019-11-27 12:03:24 +00:00
Shish
33f564994d disambig 2019-11-27 11:51:23 +00:00
Shish
95ef5940fc consistently use colon parameters 2019-11-27 11:47:38 +00:00
Shish
861def1aa3 be explicit about using FFS-PHP's PDO not vanilla PDO 2019-11-26 10:26:38 +00:00
Shish
29994e9613 postgres now recommends IDENTITY over SERIAL 2019-11-25 00:24:45 +00:00
Shish
a175405210 replace ipban page with microcrud 2019-11-24 15:59:14 +00:00
Shish
56bb03f01a and tests 2019-11-24 13:25:41 +00:00
Shish
0de2f23ece be more CRUD 2019-11-24 13:24:42 +00:00
Shish
d8c4331c66 localhost 2019-11-23 12:15:16 +00:00
Shish
fb3a7c03e8 no host 2019-11-23 12:12:28 +00:00
Shish
bd1897ef9f auth 2019-11-23 12:09:18 +00:00
Shish
5ae8d02366 server 2019-11-23 12:00:45 +00:00
Shish
5ab54609b9 sudo 2019-11-23 11:54:54 +00:00
Shish
1fc0eb70e0 update 2019-11-23 11:51:01 +00:00
Shish
c638943eb8 install sqlite3 2019-11-23 00:09:15 +00:00
Shish
6f17f28194 mysql password 2019-11-22 23:03:34 +00:00
Shish
950d9c9fd3 set up db first 2019-11-21 17:42:31 +00:00
Shish
dde38ce403 ql 2019-11-21 17:35:41 +00:00
Shish
417f16079b more environment setup 2019-11-21 17:34:07 +00:00
Shish
17e2662d06 reduce CI CPU use while testing... 2019-11-21 17:30:19 +00:00
Shish
30df57eaf5 use php's database names 2019-11-21 17:28:13 +00:00
Shish
d00f0cdbe7 install pgsql driver 2019-11-21 17:25:05 +00:00
Shish
849d04bf7a more phpunit 2019-11-21 17:18:43 +00:00
Shish
1e4f08e9e9 updates for phpunit 8 2019-11-21 17:16:11 +00:00
Shish
4026181219 composer updates 2019-11-21 17:12:08 +00:00
Shish
d91e7fa136
testing github actions 2019-11-21 16:59:07 +00:00
Shish
659ef7dac9 update dependencies 2019-11-14 18:24:09 +00:00
Shish
962f6073ff sqlite requires limit/offset, offset/limit is treated as a syntax error 2019-11-11 16:53:11 +00:00
Shish
c94f289291 html_escape all exception messages - pass query out-of-band if we want it formatted 2019-11-11 16:43:42 +00:00
Shish
6486bb95da name_to_id 2019-11-11 16:43:42 +00:00
Shish
247cfcbd77 Avoid shadowing global variable
When we aren't referencing the current `global $user`, we should give it
a different name to avoid confusion
2019-11-11 16:43:42 +00:00
Shish
bde49c4f5e paged ip bans 2019-11-11 12:52:11 +00:00
Shish
418f5484ed show types 2019-11-08 18:54:32 +00:00
Shish
10b9e0ccbc fix page number logic 2019-11-08 17:34:06 +00:00
Shish
1a4a76c324 php... 2019-11-05 00:19:31 +00:00
Shish
15e61c5bf4 restore strtotime, for +4 months etc 2019-11-05 00:16:26 +00:00
Shish
c7d90c2df7 nits 2019-11-04 01:04:12 +00:00
Shish
7601140825 missed a spot 2019-11-04 01:03:47 +00:00
Shish
f79eafc91e format 2019-11-04 00:42:06 +00:00
Shish
d17e207984 Have get_arg never return null
90% of places assume it will never return null, and they will break in
weird ways if it does return null
2019-11-04 00:40:10 +00:00
Shish
fc7da5114f fixes 2019-11-03 23:43:35 +00:00
Shish
954158ad43 typo 2019-11-03 23:32:55 +00:00
Shish
503d93a28e syntax 2019-11-03 23:29:29 +00:00
Shish
bcf7947837 use timestamps for bans, consistency at last /o/ 2019-11-03 23:17:09 +00:00
Shish
f15a95b4de more version 2019-11-03 19:49:52 +00:00
Shish
539dd66fe8 ipban version 2019-11-03 19:43:39 +00:00
Shish
ee948352a5 defaults 2019-11-03 19:25:51 +00:00
Shish
0e660f5aba mysql... 2019-11-03 19:15:09 +00:00
Shish
c41378f0b9 text can't have default in mysql... 2019-11-03 19:11:48 +00:00
Shish
494ba15a70 log every ext version change 2019-11-03 19:04:57 +00:00
Shish
4f0ee38508 slightly more verbose docker install 2019-11-03 18:55:37 +00:00
Shish
0fa371c7b0 create tables before init 2019-11-03 18:32:50 +00:00
Shish
c58a13ae88 formatting 2019-11-03 18:28:38 +00:00
Shish
427acc55a0 Merge branch 'master' of https://github.com/shish/shimmie2 2019-11-03 18:28:16 +00:00
Shish
6bc33ee691 Drop SCORE_DATETIME/NOW - all the databases we care about now support TIMESTAMP/CURRENT_TIMESTAMP 2019-11-03 18:28:05 +00:00
Shish
e2cac352f5 start of ban types 2019-11-03 17:53:52 +00:00
Shish
0fab821d77 set DB timeout to a large number for upgrades 2019-11-03 17:53:41 +00:00
Shish
1210498e41 fix typo in image flag setting 2019-11-03 17:21:58 +00:00
Shish
2f23a11096 manual db upgrade command 2019-11-03 17:21:05 +00:00
Shish
031c441e47 split DatabaseUpgrade into a separate event from InitExt 2019-11-03 17:21:05 +00:00
Shish
ee3754ae79 Merge branch 'master' of https://github.com/shish/shimmie2 2019-11-03 16:28:00 +00:00
Shish
ac5546c6ef remove more old comments 2019-11-03 16:22:59 +00:00
Shish
b568933f45 remove extension metadata comments (we have metadata objects now) 2019-11-02 20:19:09 +00:00
Shish
55c6854003 formatting 2019-11-02 19:57:34 +00:00
Shish
aabc69033b
Merge pull request #687 from sanmadjack/pull
Various changes
2019-11-02 19:55:59 +00:00
Matthew Barbour
6b22f6da3f Cleanup 2019-11-01 23:55:23 -05:00
Matthew Barbour
6e320a090e Better help page nav system integration 2019-11-01 23:55:16 -05:00
Matthew Barbour
1565b8570b Added disapproving controls to approval extension 2019-11-01 23:55:08 -05:00
Matthew Barbour
4dfb2761ab More approval enable option consequences 2019-11-01 23:49:01 -05:00
Matthew Barbour
5f89420fab Approval enable option 2019-11-01 23:49:01 -05:00
Matthew Barbour
2b46ede098 approval permissions 2019-11-01 23:49:01 -05:00
Matthew Barbour
c2d6f1a5fa New "Approval" extension 2019-11-01 23:47:53 -05:00
Matthew Barbour
016fb6be65 Small fixes and corrections 2019-11-01 23:47:53 -05:00
Matthew Barbour
c17c84f15f Fixed searching issues on event log page 2019-10-18 16:36:06 +01:00
Matthew Barbour
ed8caa86bf Fix for random issue 2019-10-18 16:35:44 +01:00
Matthew Barbour
3efa76c6a2 Added set_timeout to database and engine 2019-10-18 16:34:12 +01:00
Matthew Barbour
702f098ea6 Added create_image permission check to upload menu code 2019-10-18 16:32:46 +01:00
Matthew Barbour
9907c02a11 Resolved transaction issue 2019-10-18 16:32:33 +01:00
Matthew Barbour
d1853ee1db Added scoreql option to database functions to make using scoreql less verbose
Added exists function to the database
2019-10-18 16:32:06 +01:00
Matthew Barbour
9139bbfd01 Added another lower() to a tag lookup 2019-10-18 16:31:38 +01:00
Matthew Barbour
0a30ec6cfa Added favorite bulk actions 2019-10-18 16:30:55 +01:00
Matthew Barbour
ed17a631d0 Added extension documentation link image 2019-10-18 16:30:30 +01:00
Matthew Barbour
51563017c8 Transcode config constants migrated to own file, added enabled option for future feature 2019-10-18 16:29:58 +01:00
Matthew Barbour
4897063adc Added trash link 2019-10-18 16:29:44 +01:00
Matthew Barbour
3a14857b40 Consolidated tag sanitization functions
Added more tag convenience functions
2019-10-18 16:29:04 +01:00
Matthew Barbour
04b1754893 Fixed bulk add's KEY 2019-10-18 16:27:32 +01:00
Matthew Barbour
aa5c8c81e0 Added lower() to some tag lookups
Removed a duplicate include line
2019-10-18 16:27:23 +01:00
Matthew Barbour
f594e9066e Added image flag 2019-10-18 16:27:04 +01:00
Matthew Barbour
6b030c00eb Constants for index config 2019-10-18 16:26:11 +01:00
Shish
de39f3e5d7
Merge pull request #686 from sanmadjack/cron_upload
Cron upload
2019-10-18 16:20:10 +01:00
Matthew Barbour
d605e0e572 Added cron_admin permission 2019-10-17 14:26:14 -05:00
Matthew Barbour
92a0afc15e Supporting function for cron uploader changes 2019-10-10 10:41:17 -05:00
matthew
40269a6f4a Cron uploader enhancements and bug fixes 2019-10-10 10:16:15 -05:00
Shish
b6b16b9804 media-rescan also accepts hash 2019-10-04 21:10:00 +01:00
Shish
5b2e9e44a2 note a TODO 2019-10-04 21:08:33 +01:00
Shish
134d2c029c limit results by default (ideally we'd have a --limit flag...) 2019-10-04 21:02:16 +01:00
Shish
32662af1ac start of bulk actions cli 2019-10-04 20:50:49 +01:00
Shish
fee0a845bb media-rescan CLI command 2019-10-04 20:50:36 +01:00
Shish
577d5c572a search from CLI for integration with other unix tools 2019-10-04 20:48:59 +01:00
Shish
4564fd4092 by_id_or_hash for more elegant CLI use 2019-10-04 20:48:21 +01:00
Shish
71c74e034b allow find_images without limit 2019-10-04 20:47:48 +01:00
Shish
cfa48deda1 consistent indent 2019-10-03 17:57:32 +01:00
Shish
30698fefdc remove a bunch of dead variables and things 2019-10-02 11:23:57 +01:00
Shish
e08cdb1638 make Cache its own thing, separate from Database 2019-10-02 10:49:32 +01:00
Shish
842df41951 make dbq html work 2019-10-02 10:23:35 +01:00
Shish
8f688fd2c6 lints 2019-10-02 10:10:47 +01:00
Shish
486c048950 typo 2019-10-02 09:06:00 +01:00
Shish
704cab4470 type fixes 2019-10-02 09:03:14 +01:00
Shish
785e5b67e6 assert for type checking 2019-10-02 08:35:54 +01:00
Shish
6ccf7b72e2 int for port numbers 2019-10-02 00:39:45 +01:00
Shish
c197d021bb Memcache hasn't been supported since php5 - we use Memcached now 2019-10-02 00:38:22 +01:00
Shish
f1c146b512 support set_int(foo, null) 2019-10-02 00:37:22 +01:00
Shish
14ca4f545d stub config so scrutinizer stops complaining about missing define()s 2019-10-01 13:28:38 +01:00
Shish
12d77da449 opt-in to new scrutinizer reports 2019-10-01 11:45:29 +01:00
Shish
c440682ae7 ignores 2019-10-01 11:40:57 +01:00
Shish
c98588d84c not using eclipse 2019-10-01 11:39:43 +01:00
Shish
08293bd32a remove hack for ancient php bug 2019-10-01 11:07:38 +01:00
Shish
d7b08d7b95 bump required versions based on Debian Stable 2019-10-01 11:06:40 +01:00
Shish
727fd921be remove references to develop 2019-10-01 10:51:13 +01:00
Shish
5183c52223 version bump 2019-10-01 10:45:07 +01:00
Shish
794e4ebb7d merge 2019-10-01 10:44:52 +01:00
Shish
7f8ee47eeb composer update 2019-10-01 10:31:31 +01:00
Shish
cea6e2b121 postgres has true / false 2019-09-30 18:05:35 +01:00
Shish
ce61b7dc40 truncate filenames to 64 chars at upload time 2019-09-30 18:05:35 +01:00
Shish
19c4fcaf34 even with EMULATE_PREPARES=false, sqlite still returns strings for int columns... 2019-09-30 10:40:15 +01:00
Shish
fd2d434c61 return native types (int, float) for mysql 2019-09-30 10:19:47 +01:00
Shish
a7bddb1dac get rid of is_admin, fixes #676 2019-09-29 19:01:09 +01:00
Shish
0f4a0275b5 use is_int instead of is_numeric if we want to reject numeric strings, should fix #681 2019-09-29 17:58:56 +01:00
Shish
160f673060 consistent class naming 2019-09-29 17:48:21 +01:00
Shish
1314c3990a
Merge pull request #684 from DanielOaks/develop+danbooru2-fixes
Minor danbooru2 theme fixes
2019-09-29 15:47:09 +01:00
Shish
54067f02a4 fix merge 2019-09-29 15:44:59 +01:00
Shish
ebe4075469 formatting 2019-09-29 14:43:47 +01:00
Shish
e6a402cd4e Merge branch 'custom_ratings' into develop 2019-09-29 14:41:14 +01:00
Shish
f5119b20a3 formatting 2019-09-29 14:32:51 +01:00
Shish
a6bb15d859 formatting 2019-09-29 14:30:55 +01:00
Shish
56e247faf5 remove duplicate import 2019-09-29 14:24:56 +01:00
Matthew Barbour
de68691fc3 ExtensionInfo for user config 2019-09-29 14:22:04 +01:00
Matthew Barbour
b43e425f95 Changed omitted related tags (like tagme) to be powered by a setting, allowing any performance-impacting (or just not useful in this context) tags to be omitted
Further tag list performance improvements
TagListConfig constants
2019-09-29 14:22:04 +01:00
matthew
cf8ed3b134 Added UserLoginEvent 2019-09-29 14:21:23 +01:00
Matthew Barbour
761834b6fa Updated tests to generate user config 2019-09-29 14:20:40 +01:00
Matthew Barbour
73b784266e Moved user config stuff into an extension 2019-09-29 14:20:40 +01:00
matthew
120cdb49a6 Added $user_config global based on existing config object for storing user-specific settings.
Added event to the user page so that extensions can hook into it, providing user-specific setting controls
2019-09-29 14:19:52 +01:00
Matthew Barbour
88e6e68d79 Changed omitted related tags (like tagme) to be powered by a setting, allowing any performance-impacting (or just not useful in this context) tags to be omitted
Further tag list performance improvements
TagListConfig constants
2019-09-26 15:46:28 +01:00
Matthew Barbour
8606c70437 Changed related tags queries to run more efficiently, filter out the starting tags, and filter out any tags starting with tagme, rather than just tagme. 2019-09-26 15:43:48 +01:00
Matthew Barbour
8f95d23828 Removed mass tagger extension 2019-09-26 15:41:51 +01:00
Shish
33fff87f39
Merge pull request #680 from sanmadjack/ext_info
Proposed extension info change to allow getting info for unloaded ext…
2019-09-26 15:37:16 +01:00
Daniel Oaks
0a8aa40a81 Fix header bar colours a bit 2019-09-16 11:08:33 +10:00
Daniel Oaks
dfd833d42d Update email addy 2019-09-16 11:08:33 +10:00
Daniel Oaks
d377fb1705 Fix minor link error, use more standard+simple method from the default theme 2019-09-16 11:08:21 +10:00
Shish
dbaf34f7c3
Merge pull request #682 from DanielOaks/develop+fix-sqlite-underscores
Fix SQLite underscore searching
2019-09-15 16:36:30 +01:00
Shish
c57cff7d5d
Merge pull request #683 from DanielOaks/develop+tiny_taglist_fix
Only show 'Tags' block when there's a tag to put in it
2019-09-15 16:34:55 +01:00
Daniel Oaks
05082c8f11 Only show 'Tags' block when there's a tag to put in it 2019-09-14 15:59:31 +10:00
Daniel Oaks
a1c276c840 Fix SQLite underscore searching (#619) 2019-09-14 13:46:39 +10:00
Matthew Barbour
651b1632b1
Update main.php 2019-09-09 08:13:25 -05:00
Shish
ba20d8d5af filter non-ascii domains 2019-09-08 17:13:20 +01:00
Shish
9341c408b9 allow randomness to be limited, because sql's OFFSET N is O(n) 2019-09-08 17:13:20 +01:00
Matthew Barbour
4dce3a2f07
Update theme.php 2019-08-14 09:07:45 -05:00
Matthew Barbour
d62386474d travis adjustment 2019-08-14 08:51:03 -05:00
Matthew Barbour
2fc0273d78 travis adjustment 2019-08-14 08:44:50 -05:00
Matthew Barbour
3b6ad05b6d Updated test extension test 2019-08-07 16:36:39 -05:00
Matthew Barbour
ac6ded877f Added dependency support for extensions
Separated a few extensions that had multiple extension classes in the same file
2019-08-07 16:32:44 -05:00
Matthew Barbour
744dcd63e1 EmoticonListInfo 2019-08-07 16:06:55 -05:00
Matthew Barbour
f4b647b6b8 Updated util for new extension methods 2019-08-07 15:59:06 -05:00
matthew
1e60c8720c Set unrated and unknown as reserved ratings 2019-08-07 15:50:37 -05:00
Matthew Barbour
0506adbf30 Adjusted rating setting migration 2019-08-07 15:50:37 -05:00
matthew
a019786895 Merge remote-tracking branch 'origin/bugfixes' into custom_ratings 2019-08-07 15:50:36 -05:00
Matthew Barbour
9bc5bb3374 Updated tests to generate user config 2019-08-07 15:50:35 -05:00
Matthew Barbour
85b883ed7a Moved user config stuff into an extension 2019-08-07 15:50:34 -05:00
matthew
0fa2adfdd5 Added $user_config global based on existing config object for storing user-specific settings.
Added event to the user page so that extensions can hook into it, providing user-specific setting controls
2019-08-07 15:50:02 -05:00
Matthew Barbour
ee3f53e108 Changed related tags queries to run more efficiently, filter out the starting tags, and filter out any tags starting with tagme, rather than just tagme. 2019-08-07 15:50:01 -05:00
Matthew Barbour
e065c8b789 Set column defaults 2019-08-07 15:50:00 -05:00
matthew
aa5a04fbd3 Further adjusting tests for user config
Renamed Rating to ImageRating to prevent test system from trying to make an extension of it
2019-08-07 15:49:59 -05:00
Matthew Barbour
3560a19f79 Updated tests to generate user config 2019-08-07 15:49:58 -05:00
Matthew Barbour
b2193cb6f1 Adjusted rating array usage 2019-08-07 15:49:57 -05:00
Matthew Barbour
5e87dff033 Adjustments to rating upgrade 2019-08-07 15:49:57 -05:00
Matthew Barbour
91b46d6598 Moved user config stuff into an extension 2019-08-07 15:49:56 -05:00
Matthew Barbour
40be8f045a Changed to use user_config extension 2019-08-07 15:49:27 -05:00
Matthew Barbour
68ee4d0e77 Custom rating support, user rating filter settings 2019-08-07 15:49:26 -05:00
Matthew Barbour
8f0aa8a4ca Added $user_config global based on existing config object for storing user-specific settings.
Added event to the user page so that extensions can hook into it, providing user-specific setting controls
2019-08-07 15:48:18 -05:00
matthew
8e3b8a7a1b Merge remote-tracking branch 'upstream/develop' into custom_ratings 2019-08-07 15:48:17 -05:00
Matthew Barbour
de98e86938 ExtensionInfo conversions what have I done 2019-08-07 15:40:01 -05:00
Matthew Barbour
3d1b964812 Proposed extension info change to allow getting info for unloaded extensions 2019-08-07 15:34:11 -05:00
Shish
e6411c32aa
Merge pull request #679 from sanmadjack/etc
Etc Commits
2019-08-06 17:45:24 -07:00
Matthew Barbour
a18589ee0a Help extension
Provides foundation for help pages that are generated from loaded extensions, starting with comprehensive search documentation. Addresses #522
2019-08-05 09:03:49 -05:00
Matthew Barbour
00464d2579 Implemented a nav link generating system so that extension power what shows up in the menus rather than being hard-coded in the themes. 2019-08-05 09:01:20 -05:00
Matthew Barbour
972b68bdd3 Setup constants 2019-08-05 09:00:23 -05:00
Matthew Barbour
5ceb6f4193
Update main.php 2019-08-01 08:40:15 -05:00
Matthew Barbour
fc294bfb3c add tracer_enabled check to the database class to prevent unnecessary memory build-up when tracer isn't outputting.
Globalized tracer_enabled to make it easier to access
2019-08-01 08:15:43 -05:00
Matthew Barbour
7d110f11b6 TagCategories config constant 2019-08-01 08:10:38 -05:00
Matthew Barbour
dd6c3b2321 Added window title option to post title extension 2019-08-01 08:10:30 -05:00
Matthew Barbour
c3f2d2e1bd New post titles extension, resolves #19 2019-08-01 08:10:24 -05:00
Matthew Barbour
45df025e7d Bulk action permission constant 2019-08-01 08:09:00 -05:00
Matthew Barbour
cb1e9c0075 Permissions to constants 2019-08-01 08:07:05 -05:00
matthew
d16dfe24f2 media extension adjustments 2019-07-31 15:08:56 +01:00
Shish
d57b624079 Merge commit '38cc05c' into develop 2019-07-31 14:58:24 +01:00
Shish
a0b1c82d0d function to clear event listeners 2019-07-19 10:25:07 +01:00
Shish
eb3cc73bce make pages >500 visible to logged-in users 2019-07-19 10:24:17 +01:00
Shish
c5aba18470 s/is_admin/can(perform_bulk_actions)/ 2019-07-08 20:35:10 +01:00
matthew
38cc05cf37 Fixed issue with merge's duplicate hash check 2019-07-08 08:07:18 -05:00
matthew
7991e981ca Revert "Update main.php"
This reverts commit 0eeede7977f69c1e3a60d778de771f08eed4fc38.
2019-07-08 08:07:17 -05:00
Matthew Barbour
f2496b99f6 fix for resize arg isssue 2019-07-08 08:07:17 -05:00
Matthew Barbour
9ce5a05840 Update main.php 2019-07-08 08:07:16 -05:00
Matthew Barbour
58948a90fb Fixed a constant reference 2019-07-08 08:07:15 -05:00
Matthew Barbour
d2d6c433e6 Make the media admin block look a little nicer 2019-07-08 08:07:14 -05:00
Matthew Barbour
8638a16694 Added table-building support to SetupBlock to allow easily building cleaner setup controls 2019-07-08 08:07:13 -05:00
Matthew Barbour
4065540f0e Added SCORE sql constants 2019-07-08 08:07:12 -05:00
Matthew Barbour
a0c0b6e3d1 Various fixes 2019-07-08 08:07:11 -05:00
Matthew Barbour
ae6126d388 Changed upgrade code to use SCORE stuff 2019-07-08 08:07:10 -05:00
Matthew Barbour
b1db833d51 Added additional media properties to the images table, video, audio, length, and lossless.
Added new event to handle fetching media properties like height, width, and the newly added fields, and admin controls to manually scan files for their properties.
Added a search terms content:video and content:audio to search for images that do (or do not) have those flags.
2019-07-08 08:07:09 -05:00
Matthew Barbour
a41e99d1af Renamed graphics extension to media extension 2019-07-08 08:07:08 -05:00
Matthew Barbour
0c16d3e78c Fixed some extension references 2019-07-08 08:07:08 -05:00
Matthew Barbour
7cc725fbc1 Moved graphics engine constants to their own class 2019-07-08 08:07:07 -05:00
Matthew Barbour
3753a1b6d6 Update main.php 2019-07-08 08:07:06 -05:00
Matthew Barbour
b0e12f6a6c Update main.php 2019-07-08 08:07:05 -05:00
Matthew Barbour
18656db7c8 Update main.php
Testing a CI issue
2019-07-08 08:07:04 -05:00
Matthew Barbour
e98e63f836 Adjusted graphic extension settings 2019-07-08 08:07:03 -05:00
Matthew Barbour
3859e27839 New Graphics extension
Added constants to several extensions
2019-07-08 08:07:02 -05:00
Shish
3dce134fe9 better error for >500 pages 2019-07-08 11:10:35 +01:00
Shish
bcf07946fb fix more pairs 2019-07-07 22:59:22 +01:00
Shish
38df37a9aa limit 500 pages of search results in speed hax mode 2019-07-07 19:23:48 +01:00
Shish
eb885c443c trim leading whitespace when logging queries 2019-07-07 19:22:44 +01:00
Shish
1bd7e1a547 better DISABLE_ACCEL 2019-07-07 17:40:24 +01:00
Shish
cf98e4bf43 remove one-positive-tag special case which is slower than the generic build_accurate_search_querylet 2019-07-07 17:13:56 +01:00
Shish
c22f0f6df8
Merge pull request #675 from shish/one-search
Remove build_ugly_search_querylet
2019-07-07 16:54:42 +01:00
Shish
ea802f4a24 Remove build_ugly_search_querylet
now that the accurate search doesn't use subqueries, perhaps mysql can
accept it
2019-07-07 16:50:55 +01:00
Shish
bd5ccd2800 remove ancient misleading description of the search code 2019-07-07 16:46:56 +01:00
Shish
e27ff02bb7 two columns 2019-07-07 16:02:33 +01:00
Shish
98f0375e94 make tracer not-null in installer 2019-07-07 15:57:06 +01:00
Shish
660ee2b249 make composer libs usable from the installer 2019-07-07 15:50:02 +01:00
Shish
b43ad07abe typo 2019-07-07 15:42:19 +01:00
Shish
1e1ede7db6 log which method was used to wrap db->execute 2019-07-07 15:12:51 +01:00
Shish
3c6b09110a Cache multiple flexihashes
otherwise get_thumb / get_image / get_thumb / get_image / ... will
have 100% cache miss rate
2019-07-07 14:26:45 +01:00
Shish
8ec3690f8e cache logging was getting hit/miss the wrong way round... forever? 2019-07-07 14:07:11 +01:00
Shish
c8563951ce tracing instead of cache debug log 2019-07-07 13:58:39 +01:00
Shish
bca74a0db5 drop DEBUG_SQL - all of that info is included in the trace file, fixes #674 2019-07-07 13:52:53 +01:00
Shish
4cadce1de0 merge slow-page-log into tracer, fixes #673 2019-07-07 13:51:19 +01:00
Shish
99646a4b00 bump tracer api 2019-07-07 13:30:50 +01:00
Matthew Barbour
f5a5352511 Correction to negative tag code 2019-07-07 11:17:21 +01:00
Shish
4136b1bb6b manually pull parts out of 57f2a50fb7 2019-07-07 11:16:47 +01:00
Shish
f8a08a7aae update tracer 2019-07-07 11:12:40 +01:00
matthew
c906df6956 Added iterator_map and iterator_map_to_array
Moved the path join/sanitize to pollyfills
2019-07-07 11:11:57 +01:00
matthew
490f1f97ed Updated pools bulk action for generator 2019-07-07 11:11:48 +01:00
Shish
3954f3d296 merge iterable queries with new count_time 2019-07-07 11:11:27 +01:00
matthew
d64603674e Added ability to use generators with database queries.
Adapted bulk actions to use generators.
2019-07-07 11:10:40 +01:00
Matthew Barbour
183f9bb897 Changed the image tag search query to run more efficiently on pgsql 2019-07-07 11:06:45 +01:00
Shish
b01f425a55 GET flag to skip cache 2019-07-07 10:29:00 +01:00
Shish
f4a98e7a9b GET flag to skip accelerator 2019-07-07 10:29:00 +01:00
Shish
68f3ebb2c6 Consistently say '<Type> Query' 2019-07-07 00:17:39 +01:00
Shish
b285acf70a tracer-complete needs start time 2019-07-06 23:01:22 +01:00
Shish
8ea78eed6a have database performance measured in three ways at once, instead of three separate ways 2019-07-06 21:41:48 +01:00
Shish
2935db9d6d take out category-autocomplete pending performance tweaks 2019-07-06 11:27:05 +01:00
Shish
c682670f64 derp 2019-07-06 10:38:18 +01:00
Shish
ccb9c493d5 s/Context/EventTracer/g 2019-07-05 20:49:47 +01:00
Shish
f0326dc3ab bootstrap as its own phase 2019-07-05 19:20:37 +01:00
Shish
b158901f53 add ban-reason to bulk actions delete 2019-07-05 19:20:37 +01:00
Shish
1ca5366ee2 ignore data dir 2019-07-05 19:20:10 +01:00
Shish
22f3532035 support for themes where .shm-thumb is not directly an A tag 2019-07-05 19:20:10 +01:00
matthew
dbfa995ba5 Changed create pool bulk action to default to the currently searched strings 2019-07-05 19:16:52 +01:00
Matthew Barbour
4116bda066 Adjusted admin delete by query to bypass trash 2019-07-05 19:02:18 +01:00
matthew
558d154e85 Fixed issue with trash item detection 2019-07-05 19:01:55 +01:00
Matthew Barbour
c4111cc948 Added shortcut-key support to bulk action extension 2019-07-05 18:37:25 +01:00
Matthew Barbour
a82fb56063 Added force flag to image deletion event to override trash extension 2019-07-05 18:31:31 +01:00
matthew
1bd9238b17 Additional trash stuff 2019-07-05 18:29:45 +01:00
Matthew Barbour
32d37254f7 New trash extension. For undelete-type stuff. 2019-07-05 18:29:44 +01:00
Matthew Barbour
92bb96049f Added SCORE sql constants 2019-07-05 17:41:29 +01:00
Matthew Barbour
02e2786cca Added missing constant 2019-07-05 17:39:25 +01:00
matthew
a7188a452b Fixed issue with setup block checkbox generator 2019-07-05 17:39:15 +01:00
Matthew Barbour
c16d55995b Added table-building support to SetupBlock to allow easily building cleaner setup controls 2019-07-05 17:38:12 +01:00
Matthew Barbour
9ca800d1c4 Added bulk action support to pools extension 2019-07-05 17:31:27 +01:00
Matthew Barbour
8794258072 pgsql automatically creates indexes on unique columns, so the manually created indexes on those columns are redundant on that database. This will remove tham. 2019-07-05 16:55:36 +01:00
Matthew Barbour
dfeb3bf5df Added a database upgrade that adds a tag_id,image_id index to image_tags, and lengthens the filename field to 255 characters. 64 was ridiculous.
Also added a substr to the filename for the merge code so it won't error when it's a long name
2019-07-05 16:55:25 +01:00
matthew
80e614b53e Added relationship set event.
Adjusted relationship set statements for betteer accuracy
2019-07-05 16:46:03 +01:00
matthew
639c896a16 Added join_path to cleanly join paths.
Added sanitize_path to normalize and deduplicate directory separators.
Changed warehouse_path to be able to scale up the number of octect pairs as much as desired.
2019-07-05 16:43:06 +01:00
Matthew Barbour
ac1196dee1 Added lower to tag_categories search 2019-07-05 16:25:03 +01:00
Matthew Barbour
8d567e9553 Changed autocomplete to escape _ and %, lowercase the tags, and also query for tag names that are preceded by categories to address #630 2019-07-05 16:25:03 +01:00
Matthew Barbour
aa9ce52f47 Adjustment to DATABASE_TIMEOUT 2019-07-05 16:25:03 +01:00
Matthew Barbour
3fc0ba3a63 Added DATBASE_TIMEOUT constant to allow customizing database wait time 2019-07-05 16:25:03 +01:00
Matthew Barbour
6a6d73168b Added function to database object for interpreting parameter values to ones that are database-appropriate. Specifically, to turn true/false into the correct values for a bool column. 2019-07-05 16:25:03 +01:00
Matthew Barbour
cb436cc182 Added transaction check before main rollback 2019-07-05 16:25:03 +01:00
Matthew Barbour
ae24b5c2e8 Moved transaction commit to above fastcgi_finish_request to prevent the page refreshing before the transaction actually commits. 2019-07-05 16:25:03 +01:00
Matthew Barbour
1a7fa4663e Added search_terms to the bulk action event 2019-07-05 16:25:03 +01:00
Matthew Barbour
af263bc2a5 Fix the flv mime type mapping 2019-07-05 16:25:03 +01:00
matthew
f12e2891e5 Added button element to input[button]-related css statements 2019-07-05 16:25:03 +01:00
Matthew Barbour
a7c978c8d2 Added poster attribute to video element so thumbnail can show until video is loaded 2019-07-05 16:25:03 +01:00
Matthew Barbour
de6d6a0515 Added new FILE page mode that allows sending files to the browser with these improvements:
Reads the file and outputs it in chunks rather than all at once, reducing the amount of memory needed to very little, even for very very large files.
Supports http request ranges so that only parts of the file will be returned if requested. This allows in-browser video players to seek to arbitrary points in the video without needing to download the whole file.
Makes use of flush during send to allow the browser to being receiving file data immediately, allowing streamable video formats to begin playing before the server has finished sending the data. This could also be used in the future to add a transmission rate limiter.
Has early-disconnect detection, to terminate sending file data if the client browser has disconnected or aborted (for instance, a user starts a video, then seeks to near the middle, the first request of data will be terminated rather than continuing to process the file).
2019-07-05 16:25:03 +01:00
Shish
ff28f34088
Merge pull request #671 from shish/phpunit-bump
bump phpunit to 7.x
2019-07-05 15:58:29 +01:00
Shish
97f8234778 bump phpunit to 7.x 2019-07-05 15:47:47 +01:00
Shish
c24a6e9b97 formatting pass 2019-06-21 09:12:44 +01:00
Shish
42a502953b
Merge pull request #663 from sanmadjack/bugfixes
Bugfixes and small changes
2019-06-21 09:11:52 +01:00
Shish
7e34a30a2f
Merge pull request #664 from sanmadjack/path_to_tags_enhancements
Path to tags enhancements
2019-06-21 08:45:52 +01:00
Matthew Barbour
1370afec72 Moved database driver constants to DatabaseDriver 2019-06-20 10:47:15 -05:00
Matthew Barbour
d128dfa78e Added lower indexes for postgresql to tags.tag and users.name to speed up queries for them using lower() 2019-06-20 10:07:43 -05:00
Matthew Barbour
a834d1f814 Resolved issue with bulk rater 2019-06-19 23:41:55 -05:00
Matthew Barbour
a2ac9776ff path tag corrections 2019-06-19 23:28:34 -05:00
Matthew Barbour
c951f7d13e Adjusted path-to-dir regex to prevent an error 2019-06-19 20:22:41 -05:00
matthew
27574cad76 Merge remote-tracking branch 'upstream/develop' into path_to_tags_enhancements 2019-06-19 20:21:47 -05:00
Matthew Barbour
921ec9a7bb Adjusted cron upload for new merged flag, and to make sure tags merge properly 2019-06-19 20:20:52 -05:00
Matthew Barbour
5eb4a66ab7 Added merged indicator to DataUploadEvent and ImageAddEvent
Changed merge process so that the ID of the merged image can make it back through the event chanin
2019-06-19 20:19:38 -05:00
Matthew Barbour
5a30ce1c83 Reverted removal of latter tag write 2019-06-19 18:59:51 -05:00
Matthew Barbour
826c623538 PageMode constants 2019-06-18 21:04:31 -05:00
matthew
123089bfbf Merge remote-tracking branch 'upstream/develop' into bugfixes
# Conflicts:
#	core/imageboard/image.php
2019-06-18 13:31:04 -05:00
Matthew Barbour
014a4c2cd2 Added extension constant lists to resize and rotate extensions so that they weren't rendering their controls ont he wrong image types 2019-06-18 13:25:45 -05:00
Shish
6313ebc339 LIMIT 1 when fetching a wiki page 2019-06-16 19:39:28 +01:00
Shish
1d10baa719 only sql-escape if we're going to the database, not the accelerator 2019-06-16 19:25:40 +01:00
Shish
e232811e8c silence errors from a broken client 2019-06-16 19:12:44 +01:00
Shish
6df1190501 Rename Tag/ImgQuerylet to Tag/ImgCondition
It was confusing because Tag/ImgQuerylet (an abstract condition to use
as part of image search filtering) were unrelated to Querylet (a
fragment of SQL)
2019-06-16 19:11:16 +01:00
Shish
6b9d18b52e Parse tags first, then check accelerator, then check database
Better than half-assed tag parsing in the accelerator then full parsing
in the database
2019-06-16 19:07:55 +01:00
Matthew Barbour
1fe18e7573 Missed a dir name 2019-06-15 12:52:22 -05:00
Matthew Barbour
8b531c04a2 removed SQLERROR escape from cron uploader, not necessary now that it is individualizing transactions.
Change cron uploader to use constants for dir and config names
2019-06-15 12:51:04 -05:00
Matthew Barbour
ab9389007f Changed key-generation process for cron upload so it doesn't endlessly generate new keys before the user first hits the same buttons in settings. 2019-06-15 11:51:03 -05:00
Matthew Barbour
ed9bd5e788 Fix in ExtensionAuthor 2019-06-15 11:29:35 -05:00
Matthew Barbour
37fe743f65 Changed "images" and "thumbs" usages to constants 2019-06-15 11:20:11 -05:00
Matthew Barbour
4ade0090cc Added float support to config 2019-06-15 11:20:01 -05:00
Matthew Barbour
0202597f88 Added lock file usage to cron uploader to prevent concurrent runs.
Changed extension manager to allow author to be a comma-separated list.
2019-06-15 11:19:51 -05:00
Matthew Barbour
e940d87c22 Added image_id null check to resize's data upload event, to prevent an error when merging is enabled 2019-06-15 11:19:38 -05:00
Matthew Barbour
6f501a6e74 Database driver constants 2019-06-14 13:38:47 -05:00
Matthew Barbour
444de26ce3 Added warning for webp thumbnails 2019-06-14 13:34:21 -05:00
Matthew Barbour
8950d27d64 Changed upload to detect unrecognized files so that it doesn't just blankly refresh when the type isn't handled 2019-06-14 13:01:49 -05:00
Matthew Barbour
58acb71282 Change imagemagick commands to return the error output
Added ico to transcode extension
2019-06-14 13:01:24 -05:00
Matthew Barbour
070429402b readme corrections 2019-06-14 13:01:11 -05:00
Matthew Barbour
ed4b6bc4a0 Updated handle_ico to use new common image thumbnailing and to inherit DataHandlerExtension 2019-06-14 13:00:59 -05:00
Matthew Barbour
85b6bba689 Changed path_to_tags to interpret ; as : and to allow inheriting categories from parent folders 2019-06-14 09:50:23 -05:00
Matthew Barbour
e854b6d884 Custom rating changes 2019-06-14 09:47:14 -05:00
Matthew Barbour
1b76366dd9 Cleaned up some of the new image processing code, added documentation 2019-06-14 09:41:55 -05:00
Matthew Barbour
b522d68736 Custom rating support 2019-06-14 08:05:30 -05:00
matthew
74965c383b Merge remote-tracking branch 'upstream/develop' into develop
# Conflicts:
#	ext/cron_uploader/main.php
2019-06-14 07:57:05 -05:00
Shish
44fcc3a1e9 rm some dead code 2019-06-14 13:52:27 +01:00
Shish
064b24ffc1 formatting pass 2019-06-14 13:47:50 +01:00
Shish
d1102cd635 Merge from sanmadjack:develop 2019-06-14 13:46:55 +01:00
Shish
f078b283bd pull a bunch of small fixes from #659 2019-06-14 13:16:58 +01:00
Matthew Barbour
5765978afd Changed to prevent writing duplicate image tag IDs 2019-06-14 12:52:58 +01:00
Matthew Barbour
edc05b2f72 Merge remote-tracking branch 'upstream/develop' into develop
# Conflicts:
#	ext/cron_uploader/main.php
2019-06-13 13:34:44 -05:00
Matthew Barbour
7c4356d788 Updated copyright notice 2019-06-13 18:19:18 +01:00
Matthew Barbour
5c48a5c6ee readme correction 2019-06-13 18:19:09 +01:00
Matthew Barbour
6006a83229 Added <label> to extension name in extension manager so the name can also be clicked to enable/disable an extension 2019-06-13 18:18:51 +01:00
Matthew Barbour
8cdab6623a Changed clamp function to allow null values 2019-06-13 18:17:38 +01:00
Matthew Barbour
68c3e5ea42 Changed cron upload so that an unrecognised file type results in an error instead of a success 2019-06-13 18:17:16 +01:00
Matthew Barbour
1aa0225652 Adjustments to transcoding to allow psd transcoding to actually work
Changed resize extension to run later in the extension stack
Little fixes
2019-06-13 11:45:34 -05:00
Shish
1d1536b1ee assert_options is deprecated for php7 2019-06-13 16:57:58 +01:00
Shish
10d8b352c1 allow tags with apostrophes to be accelerated 2019-06-13 16:57:23 +01:00
Matthew Barbour
3269d32378 Added transcode extension to allow admins to convert images to other types (for instance, converting PNG to more efficient lossless webps, not that I made this just so I could do that). It also allows uploading image formats that aren't compatible with the web, such as TIFF and PSD, by automatically transcoding them to a supported fele format. 2019-06-12 17:54:06 -05:00
Matthew Barbour
a1512975b6 This should have been checked in with the header bytes change, provides the actual type detection 2019-06-12 17:51:15 -05:00
Matthew Barbour
cb24ac69ab Changes to cron upload:
Added transaction handling so that subsequent errors don't result in images that have already moved to the uploaded folder from being wiped from the database.
Changed output folders to use subfolders based on the timestamp of the current run. This is to prevent writing over files in the error folder that happen to have the same name and path, effectively losing the data.
Added additional error and information logging, and a final count of imported/merged/failed.
2019-06-12 17:50:00 -05:00
Matthew Barbour
b27904a7e0 Changes to bulk actions, passing full ID arrays instead of chunked image arrays
Changed the bulk actions to have a separate identifier from the button name
2019-06-12 17:46:24 -05:00
Matthew Barbour
97f60b3ea5 Better error handling for GD code 2019-06-12 17:40:43 -05:00
Matthew Barbour
f9f4c3bd37 Updated copyright notice 2019-06-12 17:36:36 -05:00
Matthew Barbour
b1909ffed6 readme correction 2019-06-12 17:35:32 -05:00
Matthew Barbour
97abeb5254 Added option to detect file type based on header bytes 2019-06-12 17:35:11 -05:00
Matthew Barbour
8f73b35fbb Added OnTagTermParse to rating extension
Updated an install step to be pgsql compatible
2019-06-11 09:59:06 -05:00
Matthew Barbour
8f3c20134f Added <label> to extension name in extension manager so the name can also be clicked to enable/disable an extension 2019-06-11 09:08:16 -05:00
Matthew Barbour
f2fb040a5b Moved ImageResizeException to the core space so that the core space image resize code can use it 2019-06-11 09:06:47 -05:00
Matthew Barbour
b31a916477 Changed clamp function to allow null values 2019-06-11 09:05:54 -05:00
Matthew Barbour
e2d04ca58c Merge remote-tracking branch 'upstream/develop' into develop 2019-06-11 08:50:27 -05:00
Matthew Barbour
9d68c8e079
Merge pull request #2 from sanmadjack/custom
Custom
2019-06-09 14:24:54 -05:00
Matthew Barbour
b7945b098e Changed to prevent writing duplicate image tag IDs 2019-06-09 14:18:25 -05:00
Matthew Barbour
4410baeb9c Changed cron upload so that an unrecognised file type results in an error instead of a success 2019-06-09 14:17:13 -05:00
Matthew Barbour
eb4292316d Added webp upload and thumbnailing support
Bug fixes and consolidation of various thumbnail and resize functionality
Changed resize/rotate extensions to use replace image event
Added content-disposition header to image responses to provide a human-friendly filename when saving
Added more bulk thumbnail regeneration tools
Tweaks to bulk actions to correct totals when batching items
2019-06-09 14:14:04 -05:00
Shish
e77f7de7f9 Fixes for tag / source history 2019-06-09 08:32:01 +01:00
Matthew Barbour
49cb6f7233 Added thumb_scaling option for generating high-dpi thumbnails 2019-06-06 14:12:13 -05:00
Matthew Barbour
8612a07a5a cleanup 2019-06-05 19:37:07 -05:00
Matthew Barbour
66df295ec1 Bulk action extension 2019-06-05 18:16:15 -05:00
Shish
fad31ed38d
Merge pull request #655 from sanmadjack/develop
Enabled rating extension for pgsql
2019-06-05 15:14:54 +01:00
matthew
8741529590 Enabled rating extension for pgsql 2019-06-03 08:58:39 -05:00
Shish
abeb6299fc
Merge pull request #654 from sanmadjack/develop
Cron upload changes
2019-06-03 07:39:55 +01:00
matthew
aef455949b Added escape to cron upload to stop the process when a transaction-breaking error occurs. 2019-06-02 13:38:25 -05:00
matthew
38badf7e45 Changed cron import to output imported/failed files to subdirectories matching the imported file's original subdirectory 2019-06-02 13:34:24 -05:00
matthew
e651da03cc Changed path tag handling to merge path tags with filename tags
Added 0-9 to the filename tag regexp so that extensions like mp4 will be picked up as well.
2019-06-02 13:27:24 -05:00
Shish
a1297781d5
Merge pull request #653 from sanmadjack/develop
Fixes for various issues
2019-06-02 19:16:41 +01:00
matthew
5a2f893667 Changed cron upload new image tagging to work with tag event's requirement for tags to not be empty. 2019-06-01 12:17:38 -05:00
matthew
3e2a0ea3b5 Brought cron upload tag handling inline with everything else 2019-06-01 12:12:36 -05:00
matthew
e92ac10349 Removed unset line so it doesn't do it twice. 2019-06-01 12:08:07 -05:00
matthew
63a69e4258 Change to correct issue with my change to prevent cron uploader from throwing warnings. Now using array_pop so that position in the array doesn't matter. 2019-06-01 12:02:58 -05:00
matthew
99b51e65c1 Added array_unique to set_tags to prevent primary key violations when upload conflict is set to merge 2019-06-01 11:39:03 -05:00
matthew
42b39f20d7 Updated config interface as well 2019-06-01 10:50:53 -05:00
matthew
1eecf323f4 Changed set_int to accept a string, since it can accept shorthand strings like 1M. Casting it to an int was stripping out that information when settings would be submitted. 2019-06-01 10:47:11 -05:00
root
23392b6b91 Removed a test line 2019-06-01 10:07:01 -05:00
root
98bc7c7df1 Corrected issue preventing cron upload from generating key 2019-06-01 10:04:16 -05:00
root
76bd6d4238 Fixed an issue when null wueries were passed to some themes 2019-06-01 09:47:03 -05:00
Matthew Barbour
236904087b
Merge pull request #1 from shish/develop
Develop
2019-06-01 08:51:33 -05:00
Shish
d387469fdb Use RecursiveDirectoryIterator for cron_uploader consistently, should fix #652 2019-06-01 08:13:07 +01:00
Shish
8e90279c11 Fixes for cron uploader, fixes #650 2019-06-01 07:51:02 +01:00
Shish
da10859bb3 fixes 2019-05-29 19:50:12 +01:00
Shish
bf473f6d51 more lint fixes 2019-05-29 18:23:29 +01:00
Shish
8a49b1e80e remove unmaintained chatbox / amazon_s3 exts 2019-05-28 20:28:05 +01:00
Shish
2396ae2ef9 fix a bunch of lints 2019-05-28 20:27:23 +01:00
Shish
294f5845b1 defaults 2019-05-28 19:54:07 +01:00
Shish
1b114bfea8 url_escape handles null 2019-05-28 19:48:41 +01:00
Shish
f0aec21038 bump travis too 2019-05-28 19:20:21 +01:00
Shish
93cc732d39 fix tests 2019-05-28 19:00:23 +01:00
Shish
34b05cca7c PSR-2. I'm not a huge fan, but ugly consistency beats no consistency... 2019-05-28 17:59:38 +01:00
Shish
5ec3e89884 php7.1 all the things 2019-05-28 17:31:20 +01:00
Shish
189385ff61 forgot that php isn't python 2019-05-28 15:16:22 +01:00
Shish
4b4ff68729 decouple cache and db a little 2019-05-28 15:06:03 +01:00
Shish
445687111e Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2019-05-26 16:25:36 +01:00
Shish
b91f20875a put upload block on every page 2019-05-26 16:15:43 +01:00
Shish
f4c18930ce option to log slow pages 2019-05-26 10:42:58 +01:00
Shish
6175b36cc9 don't show uploader name in RSS feed, halve the number of queries 2019-05-26 10:37:26 +01:00
Shish
3d326344a9 don't show refine block for heavy queries 2019-05-26 10:35:26 +01:00
Shish
a0588bd8f8 empty list rather than 404 for invalid autocompletes 2019-05-21 23:12:52 +01:00
Shish
505877a330 support arbitrarily large accelerated search results 2019-04-28 09:55:28 +01:00
Shish
037b1f0f70 log mass deletion count in advance 2019-04-28 09:53:53 +01:00
Shish
bef1628b08 also block autocomplete for % / _ 2019-04-26 10:31:23 +01:00
Shish
bc45944ac9 flashier tnc 2019-04-26 10:15:32 +01:00
Shish
65dc3898c0 common tags / common source fields 2019-04-26 10:15:32 +01:00
Shish
b2b4317203 sync with python rss_images 2019-04-26 10:15:32 +01:00
Shish
80c84f3248 More detailed login logging 2019-04-26 10:15:32 +01:00
Shish
52dfa12df7 zend.assertions can't be set at runtime 2019-04-26 10:13:17 +01:00
Shish
629f155187 don't autocomplete searches with ==0 or >32 characters 2019-04-26 10:12:40 +01:00
Shish
4c70258352 typos 2019-04-16 20:41:13 +01:00
Shish
c2834aad96 regular implode() for shell commands 2019-02-24 08:29:33 +00:00
Shish
2acbba9d02 influxdb-friendly statsd format 2019-02-22 21:26:42 +00:00
Shish
ffd5fbb4af fully customisable IP ban 2019-02-22 21:24:53 +00:00
Shish
a588a0cfc5 show the right IPs 2019-02-22 21:05:53 +00:00
Shish
a8dfc9277b Show logged event IPs on user page 2019-02-22 21:04:09 +00:00
Shish
7abf1aa591 custom ipban message 2019-02-22 19:58:04 +00:00
Shish
d918f058bf core imageboard events 2019-02-22 19:57:55 +00:00
Shish
60a28af000 s/implode/Tag::implode/ 2019-02-02 12:07:33 +00:00
Shish
0aec16aa5b specify DB in docker env 2019-02-02 12:06:30 +00:00
Shish
eb24fa0b21 tweaks 2019-02-02 12:05:59 +00:00
Shish
bd7901eddf remove dead links 2019-01-06 11:56:31 +00:00
Shish
dd80363c61 remove dead links 2019-01-06 11:55:08 +00:00
Shish
ead3a5a588 php7 assertions, no strings 2019-01-06 10:40:39 +00:00
Shish
6f5cf4d865 jquery first 2018-11-11 17:41:28 +00:00
Shish
94af26fbf2 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-11-11 17:38:43 +00:00
Shish
c9ccb22951 make handle_static its own extension 2018-11-11 17:38:32 +00:00
Shish
983b4d5d42 clean out old lib/vendor 2018-11-11 17:13:17 +00:00
Shish
e8bfba2098 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-11-11 16:44:40 +00:00
Shish
b95cbe4666 skip r34 comic bits by default 2018-11-10 14:15:07 +00:00
Shish
8903d76e7e put style/script caches in their own dirs too 2018-11-10 13:32:10 +00:00
Shish
16d0abb546 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-11-10 13:10:22 +00:00
Shish
cc23528459 subdirs for tag_list caches, as those get huge... 2018-11-10 13:10:14 +00:00
Shish
b38ec11b64 is this syntax? 2018-11-10 12:43:53 +00:00
Shish
c74bd58207 sort image reports by id (newest first) 2018-11-10 12:03:05 +00:00
Shish
de2a688b5a php... 2018-11-10 12:02:48 +00:00
Shish
1fb7e7b823 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-11-10 12:01:55 +00:00
Shish
5c49b3631d un-bump php back to 7.0, because debian stable... 2018-11-10 12:01:26 +00:00
Shish
7a7dc86cfc Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-11-10 11:57:13 +00:00
Shish
379fcdfd20 docker instructions 2018-11-10 11:19:56 +00:00
Shish
bfa0c22b06 automatic sqlite name 2018-11-07 16:48:07 +00:00
Shish
65d2172ede move images and thumbs to data/ 2018-11-07 16:06:10 +00:00
Shish
f0c1baa3ed sqlite should pass 2018-11-07 15:24:15 +00:00
Shish
78258f7763 reduce diff between travis and docker 2018-11-07 15:12:13 +00:00
Shish
9e795f41a7 use vendor JS directly instead of copy-pasting 2018-11-07 15:12:13 +00:00
Shish
f772b30301 fix some tests 2018-11-07 15:12:09 +00:00
Shish
b01edb2aec copyright date update 2018-11-07 12:11:17 +00:00
Shish
a1aa0f9a62 A docker container for making testing easier 2018-11-07 00:43:01 +00:00
Shish
cefe1c0325 a bunch of installer tweaks 2018-11-07 00:25:43 +00:00
Shish
fc6fb3c6b8 use current protocol for niceurl test, see #632 2018-11-06 00:02:07 +00:00
Shish
9d3f4ea4b3 move ext-specific js into that ext 2018-11-05 23:12:18 +00:00
Shish
b2f10ea5ab split up files in core/ for saner management 2018-11-05 22:30:18 +00:00
Shish
3c5c44d75f PHP bump in travis and sys-config too 2018-11-05 20:55:50 +00:00
Shish
cdfc97d19b begin tests in core 2018-11-05 20:53:58 +00:00
Shish
5634ba6d97 fix a thing which doesn't seem like a syntax error but phpstorm flags it as a syntax error 2018-11-05 20:02:40 +00:00
Shish
c0699ce236 s/mime_content_type/getMimeType/, fixes #633 2018-11-05 19:52:55 +00:00
Shish
15f0847434 PHP bump, 7.2 is stable now, let's require at least 7.1 2018-11-05 19:47:22 +00:00
Shish
b93026ac1d dedupe 'og:' meta tags 2018-11-05 19:17:42 +00:00
Shish
0837b5f1ca Update issue templates 2018-11-05 19:10:06 +00:00
Shish
55e0e32395 Let the client choose the protocol for QR images (see #477) 2018-11-05 17:52:21 +00:00
Shish
96ed39c9e5 Check for mb_strlen during install (see #615) 2018-11-05 17:45:19 +00:00
Shish
6ae14e4921 https for theme links 2018-09-26 22:49:37 +01:00
Shish
4b37a38857 viewports argh 2018-09-09 10:58:18 +01:00
Shish
a7a7c0dd47 handle ffmpeg thumbnailing in a slightly more sane way 2018-09-09 10:57:28 +01:00
Shish
1ed888611a drop support for video without ffmpeg 2018-09-09 10:21:56 +01:00
Shish
38406ef33a block tags starting with minus 2018-08-22 21:56:27 +01:00
Shish
4da207106b add a .editorconfig 2018-08-15 21:50:54 +01:00
Shish
e2c46a4b00
Merge pull request #643 from ThePadawan/develop
Add missing escaping of ffmpeg shell command during video thumbnail generation
2018-08-14 10:29:58 +01:00
Rudolf M. Schreier
2ae760b62e Add missing escaping of ffmpeg shell command during video thumbnail generation 2018-08-14 11:23:09 +02:00
Shish
9f3bf7d2e1 force-desktop toggle 2018-07-26 00:28:08 +01:00
Shish
24276390b4 autocomplete only for search boxes again 2018-07-26 00:27:56 +01:00
Shish
bd6b2289b1 image-info box should avoid wrapping 2018-07-26 00:27:39 +01:00
Shish
dbc430e3d5 link to main and backup image 2018-07-26 00:26:46 +01:00
Shish
dd8a90414f leave it to the theme to link to the image 2018-07-26 00:26:23 +01:00
Shish
840915c9f0 support for picking n'th item from the consistent hash 2018-07-26 00:26:01 +01:00
Shish
97a03d8f83 paginated user list 2018-07-22 19:23:34 +01:00
Shish
413a742ca8 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-07-22 15:09:18 +01:00
Shish
d48e34030d time and message searching in the log 2018-07-22 15:08:53 +01:00
Shish
f31dabce20 show number of up/down votes on user page 2018-07-20 01:29:38 +01:00
Shish
e809a72155 log autocomplete errors instead of alerting 2018-07-20 00:48:03 +01:00
Shish
639a1bc3cd format text for image reports 2018-07-20 00:37:43 +01:00
Shish
8ea25a4e90 .autocomplete_tags as the class to indicate we want tag autocompletion 2018-07-20 00:32:49 +01:00
Shish
c75e7060e6 hide by default 2018-07-19 22:17:19 +01:00
Shish
1b372b2575 typo 2018-07-19 20:09:36 +01:00
Shish
8768284602 add r34 ext 2018-07-19 19:55:28 +01:00
Shish
d91b0ec218 regen thumbnail from cli 2018-07-19 19:53:20 +01:00
Shish
64e2f7fe53 query accelerator failures should silently fall back to non-accelerated mode 2018-07-19 19:31:37 +01:00
Shish
9b0edcf449 also don't even follow links from deep search pages 2018-07-19 08:51:19 +01:00
Shish
71445fdf96 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2018-07-17 01:15:27 +01:00
Shish
16a56f5e5b https for gravatars 2018-07-17 01:15:20 +01:00
Shish
d4b28d7c07 fixup alias non-recursion 2018-07-16 08:46:01 +01:00
Shish
4c73b27d1e tell google to stop indexing /post/list/-cake%20-pie/34342 2018-07-15 20:34:52 +01:00
Shish
b973705021 show source URL in query 2018-07-15 20:17:47 +01:00
Shish
8b2c580930 treat phpdbg the same as php-cli 2018-07-15 19:40:53 +01:00
Shish
2417b5b021 don't recursively expand aliaes, as that can create loops 2018-07-15 19:39:39 +01:00
Shish
9e3e37a209 accelerate counts as well as the actual results 2018-06-30 14:28:52 +01:00
Shish
c9d7bd1ae3 delete cached thumb blocks after replacing images 2018-06-20 03:08:40 +01:00
Shish
60c16a9139 dash in the middle of a tag isn't special, allow it to be accelerated 2018-06-20 02:40:52 +01:00
Shish
a6d84ad1d8 Merge branch 'master' of https://github.com/shish/shimmie2 2018-02-20 22:23:58 +00:00
Shish
419b53c2af version bump 2018-02-20 22:19:03 +00:00
Shish
da5d81dbb2 rebuild composer.lock with php7.0 instead of 7.1 2018-02-20 22:14:18 +00:00
Shish
60d693d323 use svg-sanitize to sanitize SVG files 2018-02-20 22:06:50 +00:00
Shish
bc68137797 use svg-sanitize to sanitize SVG files 2018-02-20 22:00:24 +00:00
Shish
18879ddc4c composer update 2018-02-20 21:44:21 +00:00
Shish
936ceac2ce composer update 2018-02-20 21:40:51 +00:00
Shish
09e3bd30a3 Merge branch 'master' of https://github.com/shish/shimmie2 2018-02-20 21:36:08 +00:00
Mik-chan
4721d1ee3a
Merge pull request #1 from shish/master
Keeping up to date
2018-02-20 00:33:12 +03:00
Shish
adaca87ca1 redis cache support 2017-10-28 20:28:31 +01:00
Shish
cf95e28144 firefox complains about invalid email in an email field 2017-10-28 20:28:23 +01:00
Shish
54da35f5db fix warning in local mode 2017-09-22 19:09:07 +01:00
Shish
4dead6837f Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-09-21 15:35:22 +01:00
Shish
7d478a809c this is PHP :( 2017-09-21 15:35:13 +01:00
Shish
c2b4210777 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-09-21 14:08:14 +01:00
Shish
4ea721f681 underp 2017-09-21 14:04:53 +01:00
Shish
6aa704d04c better image counting 2017-09-21 05:49:10 +01:00
Shish
2628c2c5d9 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-09-21 05:25:48 +01:00
Shish
6e914ff4e7 use just hash for flexihash lookup 2017-09-21 05:25:45 +01:00
Shish
977c3db1e3 PHP7 type annotations 2017-09-21 05:21:42 +01:00
Shish
c7ca2f4154 un-bundle context.php 2017-09-21 05:00:59 +01:00
Shish
df3f061533 PHPUnit 6 2017-09-21 04:16:36 +01:00
Shish
4dfad1dfbf also composer.json itself 2017-09-19 00:08:05 +01:00
Shish
8f577de08f travis too 2017-09-19 00:05:28 +01:00
Shish
117e018eb6 branch off 2.7 for php7 2017-09-19 00:04:51 +01:00
Shish
d8e75ddf7c bump 2017-09-18 23:57:03 +01:00
Shish
8a4616bc7d Merge branch 'develop' 2017-09-18 23:56:40 +01:00
Shish
4796ee9f00 rebuild composer.lock with php5.6 2017-09-18 23:51:34 +01:00
Shish
aef0d15783 Merge branch 'develop' 2017-09-18 23:40:46 +01:00
Shish
236b444ccd fix test 2017-09-17 19:38:44 +01:00
Shish
1566ff7eaa un-confuse phpunit 2017-09-17 19:37:30 +01:00
Shish
186ea55348 safety 2017-09-17 19:11:51 +01:00
Shish
5763b77e2b strnorm 2017-09-17 19:10:10 +01:00
Shish
2c0e49507e limit / offset 2017-09-17 19:06:10 +01:00
Shish
47ff7f185e Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-09-17 19:00:40 +01:00
Shish
860e828c3e Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-09-17 18:59:59 +01:00
Shish
a32bc6448c make user list slightly more useful 2017-09-17 18:59:48 +01:00
Shish
e1d6ff0f4e composer update 2017-09-17 18:16:07 +01:00
Shish
cdbb4e8c7b add resize/script.js 2017-09-17 15:09:25 +01:00
Shish
408b45e4cc Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-08-24 10:17:47 +01:00
Shish
abe473ffd6 count blank header as null 2017-08-24 10:17:39 +01:00
Shish
473c0f0bcb explicitly mark some block types as ignored when calculating 404ness 2017-08-24 10:17:24 +01:00
Shish
35bd51e513 use 'count()' + result->get_one() to count images, rather than 'select *' + result->rowcount()... 2017-08-23 00:42:19 +01:00
Shish
d875ab66a1 60 second post-list cache 2017-08-22 01:05:18 +01:00
Shish
5a6728209a improve cache logging 2017-08-22 01:04:33 +01:00
Shish
fe5b7cc760 Merge pull request #626 from im-mi/bookmarklet-fix
Fixed bookmarklets on imageboards running gelbooru
2017-08-11 21:37:08 +01:00
im-mi
08f1475007 Fixed bookmarklets on imageboards running gelbooru 2017-08-06 20:30:38 -04:00
Shish
3c3529a4cc don't respond to autocomplete requests for blank string 2017-07-20 23:29:17 +01:00
Shish
8fcf721045 use image link for video 2017-07-20 23:28:55 +01:00
Shish
2f083f7608 more useful memcached error messages 2017-06-08 09:37:38 +01:00
Shish
94ec37029a stub more mb_ functions 2017-06-08 09:37:21 +01:00
Shish
d105644d1b use php sorting for alphabetic tag list, as utf8 sort ignores punctuation 2017-06-08 09:36:59 +01:00
Shish
9f4caaddea remove some unused variables 2017-06-01 20:44:26 +01:00
Shish
53c6f6df30 bulk thumb regen 2017-06-01 20:44:17 +01:00
Shish
2666d83579 fix URL for video fallback flash player 2017-06-01 20:44:02 +01:00
Shish
2f557326df die if caching modules are missing, don't silently fall back to NoCache 2017-05-30 02:13:11 +01:00
Shish
4e5af70093 re-stub mb_strlen (see #615) 2017-05-29 11:09:28 +01:00
Shish
843d7fae24 merge 2017-05-29 10:19:55 +01:00
Shish
8828fdfd05 log SQL query times in DEBUG_SQL mode 2017-05-29 10:19:11 +01:00
Shish
51e165aecf Add separate memcached cache 2017-05-29 10:18:11 +01:00
Shish
684efedcfd Log what URL query caused invalid search queries 2017-05-29 10:16:32 +01:00
Shish
8440826f50 Merge pull request #616 from jgen/develop
Another location where $tags should be an array instead of a string.
2017-05-15 13:02:36 +01:00
jgen
fc7d96b530 Another location where $tags should be an array instead of a string. 2017-05-14 22:18:44 -07:00
Shish
511216f4e8 Merge pull request #614 from jgen/develop
Ensure that metadata['tags'] is always an array
2017-05-14 23:12:49 +01:00
jgen
cf5aacaddf Use empty array instead of array with empty string. (Thanks Shish!) 2017-05-14 14:00:20 -07:00
jgen
3ffb2da91c More checking to ensure tags is an array. 2017-05-13 23:18:47 -07:00
jgen
7ebe301ffd Check if already an array before exploding. 2017-05-13 18:01:31 -07:00
Jeff
dfc536807c Merge pull request #613 from jgen/develop
Fix issue with archive handler if there is no add_status() method for the theme.
2017-05-13 17:49:17 -07:00
jgen
f492c6c2c3 Ensure that the Image object tag_array and the metadata array tags are always arrays. 2017-05-12 00:57:50 -07:00
jgen
c0e87ae2ae Fix issue with archive handler if no add_status method. 2017-05-11 23:43:10 -07:00
Jeff
3183ef59b3 Merge pull request #611 from thomas-hori/develop
Fix error upon bulk add.
2017-05-11 23:23:17 -07:00
Thomas Hori
d1306cfb2d Move Tag::explode call into add_image call so that $result is unaffected. 2017-04-26 14:08:06 +01:00
Thomas Hori
1625bd68e3 Fix error upon bulk add.
Fixes "PHP Fatal error:  Uncaught TypeError: Argument 2 passed to
TagSetEvent::__construct() must be of the type array, string given"
upon bulk add.
2017-04-25 17:48:34 +01:00
Shish
d608b68387 Merge pull request #610 from Zimmedon/develop
bulk_add_csv: Run Tag::explode() on the tags
2017-04-23 10:33:12 -07:00
John Brooks
f934baa207 bulk_add_csv: Run Tag::explode() on the tags before passing them to handlers
Fixes #575
2017-04-23 02:21:39 +00:00
Jeff
dc3508560a Merge pull request #609 from jgen/develop
Develop
2017-03-29 21:42:54 -07:00
Shish
8ef13db69c bump develop 2017-03-27 18:15:16 +01:00
Shish
807d44d644 Version bump 2017-03-27 18:14:11 +01:00
Shish
6345db174b Merge pull request #608 from shish/develop
Merge from develop into master
2017-03-27 18:13:13 +01:00
Jeff
827cae6221 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2017-03-25 01:08:23 -07:00
Jeff
015c7d6728 Merge pull request #607 from im-mi/gelbooru-bookmarklet-fix
Fixed bookmarklet when logged in on imageboards running gelbooru
2017-03-25 01:07:10 -07:00
Jeff
8ff24eb1c6 Revert "Bump the version number"
This reverts commit 277d80c4df41918a4b664ecdd88fa1a0b4f0be35.
2017-03-24 23:21:48 -07:00
im-mi
495776cac1 Fixed bookmarklet when logged in on imageboards running gelbooru 2017-03-23 20:46:30 -04:00
jgen
277d80c4df Bump the version number 2017-03-23 00:35:18 -07:00
Jeff
997f321741 Merge pull request #605 from DakuTree/develop
Fix SVG XSS
2017-03-22 23:54:10 -07:00
Daku
acba60e7aa remove extra newline 2017-03-18 00:49:08 +00:00
Daku
2b62cc8171 loading SVG via <img> instead of <object> to stop inline JS execution 2017-03-18 00:13:16 +00:00
Daku
e2272e7786 linting 2017-03-17 23:56:26 +00:00
Daku
cd7352ad9b only need to disable these on PHP5.3 and lower 2017-03-17 23:12:54 +00:00
Shish
ce5bb30113 Merge pull request #604 from jgen/scrutinizer-fixes
Some more linting
2017-03-15 09:42:15 +00:00
jgen
204ea1d239 And more linting. 2017-03-12 17:13:32 -07:00
Jeff
9a68d7ac98 Merge pull request #2 from jgen/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2017-03-12 16:47:42 -07:00
Scrutinizer Auto-Fixer
1caef6d633 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2017-03-12 23:45:38 +00:00
jgen
b053be5d88 More linting. 2017-03-12 14:53:49 -07:00
jgen
a5a89dc08d mb_strlen and mb_internal_encoding have been in core PHP since version 4.0.6
mb_strtolower has been in core PHP since version 4.3.0
2017-03-12 14:43:19 -07:00
jgen
7bd581b530 Scrutinizer commets. 2017-03-12 14:29:35 -07:00
Shish
44bead8b92 Merge pull request #603 from jgen/develop
PHP Version check in the code should use a configurable value.
2017-03-12 16:13:02 +00:00
Shish
7968f336fd Merge pull request #601 from DakuTree/develop
Updated/removed various libs + tweaks
2017-03-12 16:12:01 +00:00
Shish
df6a91540c Merge pull request #602 from jgen/merge-from-master
Merge fixes from master into develop
2017-03-12 16:09:35 +00:00
jgen
ec4111430d More linting. 2017-03-12 00:29:10 -08:00
jgen
8de816d7dd Some more linting via PHPDoc comments. 2017-03-12 00:18:26 -08:00
jgen
fb3cc1832f Use triple equals, because PHP. 2017-03-11 23:19:37 -08:00
jgen
2691a6bbdc Change PHP version check to use a configuration constant, rather than a hardcoded version in the code.
(As it seems this hardcoded version number is easy to forget about, and then it doesn't actually get updated..)
2017-03-11 18:34:36 -08:00
jgen
ebab9eddeb Merge branch 'master' into merge-from-master
Conflicts:
	ext/tag_list/theme.php
2017-03-11 00:52:16 -08:00
Daku
f9d0c83d5b .audio > .audio_image (.audio is used for modernizr) 2017-03-10 19:18:35 +00:00
Daku
6fc3d51014 use HTML5 audio player, jsmediatags lib, default volume for audio/video 2017-03-10 19:16:54 +00:00
Daku
e07556c62d make sure video isn't bigger than container + linting 2017-03-10 18:26:07 +00:00
Daku
12d73c28a8 remove getID3 lib 2017-03-10 18:16:56 +00:00
Daku
84e86c4930 use mime_content_type instead of getID3 + use proper MP4 mimetype 2017-03-10 18:03:59 +00:00
Daku
256d09a3ec use multiline statements rather than multiple if statements 2017-03-10 16:39:12 +00:00
Daku
68d9ec50c3 specify composer phpunit 2017-03-10 16:21:07 +00:00
Shish
10863d4c4b import imageboard to avoid warnings 2017-03-10 16:15:31 +00:00
Shish
c208a3715c mark static functions as static 2017-03-10 16:14:56 +00:00
Daku
07d57f20e8 this is no longer needed 2017-03-10 16:10:48 +00:00
Daku
6006530b3d password_compat is not required on PHP 5.5+ 2017-03-10 16:10:28 +00:00
Daku
915acda6c7 update PHP-Akismet to PHP7 Compatable version 2017-03-10 15:56:09 +00:00
Daku
810ad041b7 update composer.lock 2017-03-10 15:55:09 +00:00
Daku
df38c7978c remove composer dependency on composer-asset-plugin & use asset-packagist instead
This works the exact same, but without the requirement of a globally installed plugin
This _may_ require a "composer global remove composer-asset-plugin remove" to work correctly.
2017-03-10 15:54:49 +00:00
Shish
55ee93fd27 Merge pull request #600 from jgen/develop
Bump PHP version up to 5.6 & Linting/fixes
2017-03-10 11:11:38 +00:00
jgen
ab9dc0c511 Move phpunit to require-dev in composer.json 2017-03-10 00:28:47 -08:00
jgen
9e7787de0c From Scrutinizer: The case 'r' would never be reached due to the default appearing before it. 2017-03-09 22:55:32 -08:00
jgen
d62304cf70 more linting 2017-03-09 22:28:48 -08:00
jgen
d0b0c7f93d More linting with PHPDoc comments 2017-03-09 21:58:14 -08:00
jgen
bd0310a699 Merge branch 'develop' of https://github.com/jgen/shimmie2 into develop 2017-03-09 21:23:05 -08:00
Jeff
8e7c6ee44e Merge pull request #1 from jgen/scrutinizer-patch-1
Scrutinizer Auto-Fixes for comments.
2017-03-09 20:43:19 -08:00
jgen
fef219c853 Add explicit phpunit dependency back 2017-03-09 10:32:00 -08:00
jgen
9cf325d195 Try global namespace 2017-03-09 01:31:41 -08:00
Scrutinizer Auto-Fixer
9a906d4ecf Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2017-03-09 09:27:52 +00:00
jgen
c1e115e075 Test removing explict phpunit dependency from composer 2017-03-09 01:22:45 -08:00
jgen
ab27e2d2a5 Bump PHP version up to 5.6 2017-03-09 01:12:27 -08:00
jgen
9b7627dede PHP versions 5.5 is EOL now. 2017-03-09 01:09:34 -08:00
jgen
f21648b46d Add phpunit to composer dependencies & update composer 2017-03-09 00:54:48 -08:00
jgen
20455aa2fb Test using PHP 7.1 on Travis as well 2017-03-09 00:13:11 -08:00
jgen
ede2fcf4eb More linting and fixing 2017-03-08 23:52:31 -08:00
jgen
fc0945354d Merge remote-tracking branch 'shish/develop' into develop 2017-03-08 23:05:52 -08:00
jgen
17307820e8 Add some PHPdoc comments as suggested by Scrutinizier 2017-03-08 23:02:26 -08:00
Shish
72de620b42 Merge pull request #598 from jgen/develop
#597: Fix for XSS issue in chatbox extension.
2017-03-08 10:30:54 +00:00
jgen
c379420a1f #597: Fix for XSS issue in chatbox extension. 2017-03-08 01:14:11 -08:00
Shish
fefc922070 Create LICENSE.txt 2016-11-27 20:28:16 +00:00
Shish
4ed28e2db7 Merge branch 'im-mi-tag-tables' into develop 2016-10-10 14:20:10 +01:00
Shish
e43b1b3aed Merge branch 'tag-tables' of git://github.com/im-mi/shimmie2 into im-mi-tag-tables 2016-10-10 14:19:57 +01:00
Shish
4b9278d09b Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2016-10-09 22:33:25 +01:00
Shish
623d34f20b Merge pull request #589 from im-mi/searchable-random-list
Searchable random list
2016-10-09 22:26:28 +01:00
Shish
7548c66a0b Merge pull request #578 from im-mi/enhance-zoom
Enhance zoom
2016-10-09 22:21:39 +01:00
Shish
579cbf3467 Merge pull request #591 from im-mi/url-escape-tag-info-link-tag
url_escape tag info link tag
2016-10-09 14:45:00 +01:00
im-mi
d5fd6f6821 url_escape tag info link tag 2016-09-28 10:26:13 -04:00
Shish
3daa83223e Merge pull request #588 from im-mi/arrow-key-nav-ignore-modifiers
Ignore arrow key navigation when modifier keys held
2016-09-26 15:22:06 -05:00
Shish
af0aa98649 Merge pull request #586 from im-mi/image-info-tweaks
Image info tweaks
2016-09-26 15:21:46 -05:00
Shish
7e9cc0fb56 Merge pull request #590 from im-mi/fix-ico-size
Misc. Bugfixes
2016-09-26 15:20:07 -05:00
im-mi
8805f0dd18 Remove get_ico page
It's no longer needed because ICO now uses Image->get_image_link()
2016-09-26 11:18:06 -04:00
im-mi
e6dd1b492c Sanitize ratings 2016-09-25 15:26:36 -04:00
im-mi
d4fda00dd9 Check for existence of POST var tag_edit__owner before using it 2016-09-25 15:26:36 -04:00
im-mi
05da5d0b4f Use Image->get_image_link() on ICO files 2016-09-25 15:26:36 -04:00
im-mi
1dd0dfc591 Read ICO header with proper sign
Fixes width/height being read incorrectly when >= 128
2016-09-25 10:17:46 -04:00
Shish
9c062f3385 case-insensitive wiki pages 2016-09-24 16:17:14 +01:00
im-mi
f763fc1356 React appropriately when there are no random list search results 2016-09-23 12:31:02 -04:00
im-mi
3bebe77add Made the random list searchable 2016-09-23 12:21:40 -04:00
im-mi
891e52f4b9 Ignore arrow key navigation when modifier keys held 2016-09-23 12:09:40 -04:00
Shish
d9485bbb40 Merge pull request #587 from im-mi/fix-comment-delete-injection
Fix comment-delete injection
2016-09-14 17:12:01 -07:00
im-mi
a49c5745b0 Use html_escape instead of htmlspecialchars 2016-09-14 18:08:12 -04:00
im-mi
c1083bbea1 Fixed comment-delete code-injection vulnerability 2016-09-14 17:42:32 -04:00
im-mi
3e52e332de Only show image rater if user can rate 2016-09-13 02:10:48 -04:00
im-mi
4dcee0eede Removed trailing period from "Parent: None." for consistency 2016-09-11 23:42:34 -04:00
im-mi
001a1176cc Show image rating in image info box even when not logged in 2016-09-11 23:42:34 -04:00
im-mi
e110b558b8 Hide the rating editor outside of edit mode 2016-09-11 23:42:34 -04:00
Shish
7efff25dcc Merge pull request #585 from im-mi/pool-placeholder-text
Don't use javascript for pool search placeholder
2016-09-07 23:49:19 +01:00
im-mi
e8c6f655b0 Don't use javascript for pool search placeholder 2016-09-07 08:43:34 -04:00
Shish
d36338d4c4 Merge pull request #582 from im-mi/minor-html-fixes
Minor html fixes
2016-09-07 10:31:25 +01:00
Shish
0ef74bdd15 Merge pull request #584 from im-mi/remove-pool-index-link
Use generic NavBlock on pool pages (fixes a JS error)
2016-09-07 10:28:43 +01:00
im-mi
58096e10c8 Fix tag list clipboard spacing
This brings back the original behavior of having spaces between the columns when the tag list gets copied to the clipboard (tested in Chrome, Edge, and Firefox).

One caveat: Edge now adds multiple spaces.
2016-09-06 22:10:09 -04:00
im-mi
ab33652d70 Made tag list spacing more like it was before
It's still a few pixels off in some cases, but the difference is negligible now.
2016-09-06 22:05:34 -04:00
im-mi
73ff0e669d Use generic NavBlock instead of "Index" block on pool pages 2016-09-06 09:25:08 -04:00
im-mi
5919112477 Use tables for tag lists 2016-09-06 06:08:33 -04:00
im-mi
e5cfea028a Fixed mass source set's broken dialog 2016-09-03 14:43:56 -04:00
im-mi
91fea63319 Removed stray end tag 2016-09-03 14:26:24 -04:00
im-mi
ac53fe52de Removed obsolete language specification from <script> 2016-09-03 14:26:24 -04:00
im-mi
10d47409ad Added doctype to home page 2016-09-03 14:26:24 -04:00
im-mi
d39b084537 Properly end attribute list of tag on upload form 2016-09-03 14:26:24 -04:00
im-mi
d9903a969d Added zoom support to svg 2016-09-03 05:36:15 -04:00
im-mi
17f71094f2 Use the size of the outermost svg element
... in case there are nested svg elements.
2016-09-03 05:36:14 -04:00
Shish
3051334d8f be explicit that variables are variables 2016-09-02 16:19:02 +01:00
Shish
e96a5e1109 Merge pull request #579 from im-mi/fix-ico-256-256
Fix .ico when size is 256 x 256
2016-09-02 15:50:46 +01:00
Shish
bc0b37a21f Merge pull request #580 from im-mi/mass-tagger-fixes
Mass tagger fixes
2016-09-02 15:49:18 +01:00
im-mi
15ffdff387 Fix .ico when size is 256 x 256 2016-09-02 05:33:16 -04:00
im-mi
d2540a9619 Use CSS for mass tagger button styling 2016-09-02 05:30:00 -04:00
im-mi
49d6fa99da Clear the mass tagger's selection on load
.. in case it was autocompleted by the browser.
2016-09-02 05:29:59 -04:00
im-mi
35d2f8682c Don't save zoom mode cookie upon window resize 2016-09-02 01:14:53 -04:00
im-mi
84dbc3abff Re-apply zoom upon window resize 2016-09-02 01:12:35 -04:00
im-mi
f26fc5925b Added zoom support to ico 2016-09-02 01:12:34 -04:00
im-mi
eeb0c2b974 Only allow click-to-zoom on img 2016-09-02 01:12:33 -04:00
im-mi
b554e7505b Added zoom support to webm 2016-09-02 00:38:31 -04:00
Shish
29bdc5da22 Merge pull request #576 from im-mi/fix-pool-description-code-injection
Update pool description formatter (code injection vulnerability)
2016-09-01 11:18:37 +01:00
Shish
2258116a31 Merge pull request #577 from im-mi/fix-tag-list-starts-with-headings
Fix "starts-with" header in tag list when escaping required
2016-09-01 11:16:10 +01:00
im-mi
10e8fc50d3 Fix "starts-with" header in tag list when escaping required
This fixes the "starts-with" headers* on the tags/alphabetic page. Before, the headers would be wrong if they started with an escaped character.

This also escapes the resulting header so that it no longer generates invalid HTML in such cases.

* Note that these headers are only visible when paged tag lists is disabled.
2016-09-01 03:33:17 -04:00
im-mi
bb64d12d9d Use TextFormattingEvent instead of raw BBCode formatter 2016-09-01 00:35:52 -04:00
im-mi
834bc740a4 html_escape data-tags for tags that contain single quotes 2016-08-29 10:05:56 +01:00
Shish
bcef3fbc8f have tag sanitisation process ignore tags which are too long, fixes #565 2016-08-29 09:21:23 +01:00
im-mi
1bab0051f1 html_escape tag info link 2016-08-29 09:11:13 +01:00
Shish
5691d1c3ad Merge pull request #574 from im-mi/single-quotes-in-tags-fix
Fix tags not being escaped for HTML in some cases (code injection vulnerability)
2016-08-29 09:09:55 +01:00
im-mi
84b4ac3893 html_escape tag info link 2016-08-29 01:19:11 -04:00
im-mi
36b66f4c23 html_escape data-tags for tags that contain single quotes 2016-08-29 00:26:55 -04:00
Shish
a68407e12e Merge pull request #573 from im-mi/patch-2
Fix log info section name
2016-08-20 14:57:35 +01:00
Shish
98254ef5bd Merge pull request #572 from im-mi/video-playback-options
Added video playback options for autoplay and loop
2016-08-20 14:57:16 +01:00
Shish
61b86da5fe Merge pull request #571 from im-mi/develop
Clean up a few superficial errors
2016-08-20 14:56:18 +01:00
im-mi
980e3b686b Fix log info section name 2016-08-19 21:52:48 -04:00
im-mi
cce24f9e80 Added video playback options for autoplay and loop 2016-08-19 21:25:58 -04:00
im-mi
8fe9212882 Fixed assertion failure upon upload due to incorrect type check 2016-08-19 15:55:18 -04:00
im-mi
d8e1346b78 Replace deprecated DefaultType directive 2016-08-19 15:55:18 -04:00
Shish
e7403819a7 Merge pull request #570 from im-mi/master
Fix bookmarklets
2016-08-16 23:45:07 +01:00
im-mi
85a3cc0a7e Fall back to file extension from path 2016-08-16 11:34:01 -04:00
im-mi
3323a50ea5 Made a few parts less site-specific 2016-08-16 11:32:34 -04:00
im-mi
adf723ad23 Fixed bookmarklet on Danbooru2 2016-08-16 11:30:34 -04:00
im-mi
91d609a4c6 Make back button go to page bookmarklet was executed on 2016-08-16 11:29:26 -04:00
im-mi
b10144492a Added supported video file extensions to bookmarklet 2016-08-16 11:28:16 -04:00
im-mi
9ba6e3f7db Remove trailing variables from image address 2016-08-16 11:27:22 -04:00
im-mi
8f974fba73 Fixed uploading scaled images from Gelbooru when logged in 2016-08-16 11:26:32 -04:00
im-mi
b24977e110 Fixed tag corruption bug
If there was no newline at the end of the input, then the last tag would get corrupted.
2016-08-16 11:25:04 -04:00
Shish
89dffd569a and mass tagger 2016-07-30 23:41:42 +01:00
Shish
28c10d1748 unit tests are input too 2016-07-30 23:08:08 +01:00
Shish
26b2cd5c16 derp 2016-07-30 23:06:42 +01:00
Shish
bed04a1230 assert that metadata['tags'] is an array 2016-07-30 23:02:14 +01:00
Shish
ffce1a4683 more hinting 2016-07-30 22:54:42 +01:00
Shish
b81e8a2db8 also explode tags on transload input 2016-07-30 22:45:44 +01:00
Shish
7be951b271 Convert tags from user-supplied string to array once, on input
This results in a fuckton of refactoring and code cancelling out
other code -- we no longer have a whole bunch of places trying
to support string params and array params, and doing their own
esaping and unescaping, never being quite sure if the data they've
been passed is escaped or not.

Also adds a bunch of type hinting, since we can now know what
data we're dealing with better.
2016-07-30 22:11:49 +01:00
Shish
bc3e482247 more image report display options 2016-07-30 15:04:34 +01:00
Shish
b533a43428 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2016-07-11 13:01:05 +01:00
Shish
c14a36079c add option to force a specific base url 2016-07-11 13:00:54 +01:00
Shish
b6ad316d20 linting 2016-07-03 12:07:54 +01:00
Shish
68d58a6f11 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2016-07-03 11:48:16 +01:00
Shish
8c8106c6a9 fix index var access 2016-07-03 11:48:08 +01:00
Shish
17696f842c tiny readme tweak 2016-06-27 20:49:07 +01:00
Shish
bb35421b05 Load AutoComplete before Home, so that the headers show up 2016-06-26 18:57:53 +01:00
Shish
d2eb248538 extra cookie updates 2016-06-20 00:24:50 +01:00
Shish
cb73a0caa0 let's stop being php5.2 compatible 2016-06-19 23:23:34 +01:00
Shish
6febdec7b5 more explicit variable types 2016-06-19 23:05:57 +01:00
Shish
f8b7909426 ExtensionInfo isn't private 2016-06-19 22:15:46 +01:00
Shish
333c74ba35 typo 2016-06-19 17:48:10 +01:00
Shish
dc10a18086 lots of minor doc tweaks, type hints, etc 2016-06-19 17:41:40 +01:00
Shish
edd3e49a2f mark wildcard test incomplete on pgsql / sqlite 2016-06-19 14:06:26 +01:00
Shish
68f69dc1fe Merge pull request #559 from shish/re-fix-wildcards
re-fix wildcards for mysql
2016-06-19 14:00:00 +01:00
Shish
6d47eb1d91 Merge pull request #558 from DakuTree/cleanup
Few small fixes/changes
2016-06-19 13:59:31 +01:00
Shish
d185fd354b fix wildcard search for mysql 2016-06-19 12:14:44 +01:00
Shish
e0d3d53479 stub mb_ functions 2016-06-19 12:12:48 +01:00
Daku
a8d9f8ce5b make sure prev/next links aren't clipped outside the box 2016-06-19 11:56:41 +01:00
Daku
f973fcc9fa basic autocomplete support for metatags 2016-06-19 10:07:56 +01:00
Daku
03240220d4 make sure to get proper tag value + use selected tag if possible 2016-06-19 09:49:55 +01:00
Daku
ce10831137 this shouldn't be indented under matrix.. 2016-06-19 05:25:40 +01:00
Daku
862a84677f add docs column, showing what extensions have documentation
previously it made every ext name into a link to /ext_doc, regardless if it had docs or not
2016-06-19 05:05:12 +01:00
Daku
c0601640bf only show link if extension actually has documentation 2016-06-19 05:04:59 +01:00
Daku
5ae10473e3 closing tag is unneeded 2016-06-19 05:01:13 +01:00
Daku
6486a4757f js scrutinizer fixes 2016-06-19 05:00:09 +01:00
Daku
b75638cace get_last_insert_id requires param for pgsql support 2016-06-19 04:57:14 +01:00
Daku
66226b5ccf include code quality, code coverage imgs in readme
would be nice if this changed depending on the branch..
2016-06-19 04:56:27 +01:00
Daku
d4793db6a5 improve instructions 2016-06-19 04:55:32 +01:00
Daku
b70e24acde github apparently auto-numbers rows and ignores ours 2016-06-19 04:51:48 +01:00
Daku
311dd91a09 disable travis logs since they don't work 2016-06-19 04:45:33 +01:00
Daku
43f108d095 SQLite is currently experimental, move it to allowed failures 2016-06-19 04:26:43 +01:00
Daku
90cbd9fff8 Merge pull request #557 from shish/composer-lock
add composer.lock for scrutinizer-ci
2016-06-19 04:11:03 +01:00
Shish
730c2659ae add composer.lock for scrutinizer-ci 2016-06-18 19:43:04 +01:00
Shish
4577ff70ef more linty bits 2016-06-18 19:26:56 +01:00
Shish
cfd3a9d248 lint fixes 2016-06-18 19:00:56 +01:00
Shish
24973ada17 don't run linter on ext/*/lib/* 2016-06-18 19:00:53 +01:00
Shish
0fd96fcdae remove incomplete and unmaintained bookmarks ext, see #532 2016-06-18 18:26:11 +01:00
Shish
06ee5347c7 https support for gravatars 2016-06-18 15:56:24 +01:00
Shish
0ff6da6d3c Merge #555 2016-06-18 15:16:47 +01:00
Shish
15076f4981 Merge branch 'composer-new' of git://github.com/DakuTree/shimmie2 into DakuTree-composer-new 2016-06-18 15:16:10 +01:00
Daku
b5d56214cd fix case-insensitive autocomplete on postgres 2016-06-18 14:45:21 +01:00
Shish
20d0edb332 Merge pull request #556 from shish/html-headers
fix and use get_all_html_headers()
2016-06-18 14:05:44 +01:00
Daku
5d5b1d7059 this should be set to $res 2016-06-18 14:03:37 +01:00
Daku
56e5348470 autocomplete caching
see 7dce8da850d2b266ffc196a9e9c0aa9ee9314fe3
2016-06-18 13:20:07 +01:00
Daku
2546621c59 sort autocomplete by score + show score 2016-06-18 13:20:04 +01:00
Shish
cfc1268354 update non-layout.class.php uses of html_headers too 2016-06-18 12:49:46 +01:00
Shish
b76ee95c76 fix and use get_all_html_headers() 2016-06-18 12:25:54 +01:00
Daku
4bd9ee1c7f fix autocomplete search not looking correct on home page 2016-06-18 12:25:39 +01:00
Daku
6b6e4f04b6 html_headers should be properly sorted 2016-06-18 12:25:22 +01:00
Daku
2a747c8f2b move home css to style.css 2016-06-18 12:10:37 +01:00
Daku
559a4c7e40 move autocomplete js to script.js so it's cached 2016-06-18 11:58:41 +01:00
Daku
c59995c807 search button can sometimes be on same line as input 2016-06-18 11:47:04 +01:00
Daku
b6e181efb1 excess whitespace 2016-06-18 11:24:18 +01:00
Shish
029e6def94 remove excess whitespace 2016-06-18 11:21:54 +01:00
Daku
a9e3ef26be space should always create new tag 2016-06-18 08:06:59 +01:00
Daku
1db62901be disallow spaces in tags 2016-06-18 07:41:59 +01:00
Daku
09aaf72e5a vendor/swf should have a .gitkeep 2016-06-18 06:46:00 +01:00
Daku
7f4e96240b throw error if vendor/ doesn't exist 2016-06-18 06:42:52 +01:00
Daku
9ed1079942 asset-plugin needs to be installed.. 2016-06-18 06:29:06 +01:00
Daku
f3d56f5810 fix README 2016-06-18 06:29:04 +01:00
Daku
7673769621 composer install + moving some stuff about 2016-06-18 06:29:02 +01:00
Daku
4aa6278a58 indent 2016-06-18 06:29:00 +01:00
Daku
4e163a3027 check against 7.0 rather than nightly (since 7.0 is out now) 2016-06-18 06:28:58 +01:00
Daku
60dd9eeb90 temp solution to avoid removing modernizr lib 2016-06-18 06:28:53 +01:00
Daku
d73b6275eb leftover gif from old lib 2016-06-18 06:28:52 +01:00
Daku
861862a3e1 password compat lib is now autoloaded with composer 2016-06-18 06:28:48 +01:00
Daku
4c9bddd496 modernizr lib
sadly this can't installed via composer since we'd end up getting a bunch of functions that will never be used
2016-06-18 06:28:46 +01:00
Daku
78c2731a12 move notes libs to ext folder, fix a few bugs
it would be nice to take this out of beta, but it still has major issues with image resizing / unable to edit or delete notes
2016-06-18 06:28:33 +01:00
Daku
543600dc0e make sure main css/js files are always loaded after libs 2016-06-18 06:28:31 +01:00
Daku
acfed13634 <font> is deprecated 2016-06-18 06:28:28 +01:00
Daku
d42a792e8b safe_mode was removed in php 5.4 2016-06-18 06:28:25 +01:00
Daku
7eca12b495 replace remove_trailing_slash with a simple rtrim 2016-06-18 06:28:23 +01:00
Daku
f28e78b6f0 show install error if vendor folder does not exist
this should help deter non-dev users from installing shimmie from the master/dev branches
2016-06-18 06:28:21 +01:00
Daku
abababc57a use <pre> instead of <plaintext> since it's been deprecated since HTML2
SEE: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/plaintext
2016-06-18 06:28:17 +01:00
Daku
b161bbcf1b use a container div for padding + handle_db_errors function 2016-06-18 06:28:15 +01:00
Daku
d17de45965 don't use < or > to avoid showing broken comment if PHP isn't loaded 2016-06-18 06:27:55 +01:00
Daku
394acce635 use base64 favicon to avoid being cached
it's also unlike that ./favicon.ico will even exist yet during a new install
2016-06-18 06:27:53 +01:00
Daku
5c3236c0ef installer css is only used once, so let's just inline it instead 2016-06-18 06:27:51 +01:00
Daku
f6e1da349b added composer setup instructions
this assumes that all new releases will already have all vendor files downloaded
2016-06-18 06:27:49 +01:00
Daku
3ebf78e252 make sure lib/vendor folder is cleared on composer update
this stops old libs from still being cached by mistake
2016-06-18 06:27:47 +01:00
Daku
9e7c318df0 notes ext code cleanup
initial prep to fix & take the ext out of beta
2016-06-18 06:27:44 +01:00
Daku
b9893cbbda $_POST["tags"] isn't always set 2016-06-18 06:27:41 +01:00
Daku
caed53de6a jQuery.cookie is deprecated so use js-cookie instead (+ composer), some js tweaking too
Conflicts:
	ext/blotter/script.js
	ext/handle_pixel/script.js
	ext/index/script.js
	ext/pools/script.js
	lib/shimmie.js
2016-06-18 06:27:32 +01:00
Daku
951323abcf default handle_video thumbgen to ffmpeg if ffmpeg in path & is_executable 2016-06-18 06:20:52 +01:00
Daku
b0daab8766 move from Jaris > MediaElement for <video> fallback + use composer
also made it so the video element is no longer bigger than parent div
2016-06-18 06:20:49 +01:00
Daku
dd105e174e images aren't always jpg, so don't force jpg
all the handle_* exts use this as well, which can cause issues
2016-06-18 06:20:46 +01:00
Daku
2070034d0d move securimage to composer + update lib 2016-06-18 06:20:43 +01:00
Daku
428d1285e0 instead of including the entire jquery ui lib for a single function, just load the single function 2016-06-18 06:17:18 +01:00
Daku
ba6ab8fb16 move S3.lib to ext folder
toggleable exts should really keep any libs they use in their own dir
2016-06-18 06:17:15 +01:00
Daku
d5a58916f7 get recaptcha lib with composer + fixes #498 2016-06-18 06:17:07 +01:00
Daku
57fb4e0a34 grab tablesorter lib with composer 2016-06-18 06:17:04 +01:00
Daku
5f4ba46463 forgot to remove this 2016-06-18 06:17:01 +01:00
Daku
a2d9d14b6f there isn't any reason to grab both versions of the js lib 2016-06-18 06:16:37 +01:00
Daku
7b82ec3a00 grab jQuery.timeago lib with composer 2016-06-18 06:16:33 +01:00
Daku
516488a625 load akismet via composer
note: we should probably be using a more recent library for this, but there doesn't seem to be any general ones..
2016-06-18 06:16:31 +01:00
Daku
895df8c22b load flexihash via composer 2016-06-18 06:16:28 +01:00
Daku
d00d0ee4bb asset-plugin doesn't need to be required as it is installed globally instead 2016-06-18 06:16:26 +01:00
Daku
ef898394f0 autocomplete has moved to ext 2016-06-18 06:16:23 +01:00
Daku
f17812c64b working autocomplete 2016-06-18 06:16:21 +01:00
Daku
1bfec55690 tag lib for autocomplete
--not added autocomplete yet
2016-06-18 06:16:17 +01:00
Daku
baf8aa1b8c use composer to grab jquery
this requires composer-asset-plugin to be globally installed
2016-06-18 06:16:13 +01:00
Daku
36264d3f6e stop caching css/js from disabled exts 2016-06-18 06:16:10 +01:00
Daku
e740d03101 generate seperate css/js cache files for libs & core files 2016-06-18 06:16:08 +01:00
Daku
0b75b559a0 init composer 2016-06-18 06:16:02 +01:00
Daku
a7021e190e Merge pull request #554 from shish/js-cookie-update
migrate to a non-deprecated (and dependency-free) cookie library
2016-06-18 03:34:12 +01:00
Shish
7dce8da850 caching for autocomplete 2016-06-18 01:19:34 +01:00
Shish
79648b04d2 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2016-06-18 01:18:29 +01:00
Shish
f3a3c76988 better test router 2016-06-18 00:45:58 +01:00
Shish
7bc9a73357 migrate to a non-deprecated (and dependency-free) cookie library 2016-06-18 00:00:02 +01:00
Shish
dda634b068 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2016-06-17 23:42:20 +01:00
Shish
76c2815b2b unit test bettering 2016-06-17 23:42:12 +01:00
Shish
57fa354842 fnmatch has a string length limit :| 2016-06-17 22:51:30 +01:00
Shish
190d8bd7d7 log api stats seperately 2016-06-17 22:48:28 +01:00
Shish
1b53ecceb7 Merge pull request #553 from HungryFeline/patch-2
Set ffmpeg's "overwrite output files" switch (-y)
2016-06-16 17:40:01 +01:00
HungryFeline
94ba42fc70 Set ffmpeg's "overwrite output files" switch (-y)
Regenerate thumbnail doesn't work since the output file already exists and ffmpeg expects the user to decide wether to replace the file or not. With the -y switch set, ffmpeg does so without asking.

https://ffmpeg.org/ffmpeg.html#Main-options
2016-06-16 16:40:51 +02:00
Shish
3d05c2896a expand test cases 2016-06-07 01:56:05 +01:00
Shish
bfa1dc20c0 fix mysql 2016-06-07 01:50:56 +01:00
Shish
8e8a3da790 support single wildcard 2016-06-07 01:39:23 +01:00
Shish
dc83d4ace7 lots of deduping for searching 2016-06-07 01:34:38 +01:00
Shish
7f2609f727 dedupe searching a bit 2016-06-07 00:19:41 +01:00
Shish
525fd20540 Merge pull request #548 from DakuTree/fix-wildcards
Fix wildcard tag bugs
2016-06-06 23:18:51 +01:00
Shish
14c7743024 Merge pull request #552 from shish/search-test
Search tests
2016-06-06 23:07:22 +01:00
Shish
c41c9d680f search tests 2016-06-06 22:42:40 +01:00
Shish
43eed32958 Merge pull request #551 from shish/contact-link
Have contact_link support http: and mailto:
2016-06-06 12:37:27 +01:00
Shish
aeeaabb22e allow both http: and mailto: contact links 2016-06-06 12:12:25 +01:00
Daku
61acb24875 fixes #547 -> multi-tag searches not working with wildcard when wildcard matches more than 1 tag
is there a better way to do this?
2016-05-22 18:35:37 +01:00
Shish
8326ef631f Merge pull request #546 from DakuTree/fix-relationships
Fix relationships ext checking for TRUE/FALSE instead of Y/N
2016-05-22 18:11:57 +01:00
Daku
3b171ffc5a fix single tag wildcard searches returning duplicate results if image has more than one match 2016-05-22 17:58:09 +01:00
Daku
44876a58d9 we should be using bool_escape instead... 2016-05-22 16:17:31 +01:00
Shish
536cba980a update footer date 2016-05-22 16:13:38 +01:00
Shish
3e57f725d3 update ban message 2016-05-22 16:12:43 +01:00
Daku
a3296e18ae create index for has_children 2016-05-11 14:49:22 +01:00
Daku
32dbdccd00 has_children uses Y/N, not TRUE/FALSE 2016-05-10 21:15:50 +01:00
Shish
4609549a4b Merge pull request #538 from dali99/Material_-_Home
Material - home extension
2016-04-04 11:37:44 +01:00
Shish
dc8efcb680 Merge pull request #543 from sanmadjack/develop
Fixes for bulk add results
2016-04-01 10:47:13 +01:00
Matthew Barbour
c0fb36e4c8 Merge remote-tracking branch 'sanmadjack/develop' into develop 2016-03-21 18:17:53 -05:00
Matthew Barbour
898bcfae94 Fixes for bulk add results 2016-03-21 18:14:48 -05:00
Shish
cfad6087ab Merge pull request #541 from DakuTree/patch-constraints
Fix constraint violations when mass-editing an image that already contains the new tag
2016-01-25 11:16:25 +00:00
Daku
9235025165 fix integrity constraint violations when mass-editting an image that already contains the new tag 2016-01-24 09:56:47 +00:00
Daniel Løvbrøtte Olsen
ba66f653d6 Removes data-clink-sel="" in the sidebar links
It's unused as far as I can see
2016-01-08 10:22:58 +01:00
Daniel Løvbrøtte Olsen
2cd649b719 That's what I get for copiying and pasting 2016-01-07 13:13:50 +01:00
Daniel Løvbrøtte Olsen
ac264c4040 Stylesheet and js not from other hosts anymore 2016-01-06 23:21:55 +01:00
Daniel Løvbrøtte Olsen
a9a3af9a88 It works!!!!1!!!111!one111!!! 2016-01-06 22:58:27 +01:00
Daniel Løvbrøtte Olsen
8da4d73d82 Online tool says this should work 2016-01-06 15:27:53 +01:00
Daniel Løvbrøtte Olsen
f2af07e3d1 O.o why didn't that work? 2016-01-06 15:22:38 +01:00
Daniel Løvbrøtte Olsen
0b74b5630f How to regex 2016-01-06 15:14:33 +01:00
Daniel Løvbrøtte Olsen
7a0cb4bddc Oooops, that's kiiiinda important 2016-01-06 15:10:26 +01:00
Daniel Løvbrøtte Olsen
c36cc25cd5 This should add the correct class 2016-01-06 15:00:54 +01:00
Daniel Løvbrøtte Olsen
f8a7156371 derp 2015-12-30 13:29:14 +01:00
Daniel Løvbrøtte Olsen
05656618db Updated mdl and added license 2015-12-29 19:27:32 +01:00
Daniel Løvbrøtte Olsen
a267516b77 Made navgation links reside inside nav - "I identify as navbarkin" 2015-12-28 18:42:55 +01:00
Daniel Løvbrøtte Olsen
90b306184f weird &ndash;es 2015-12-28 18:12:37 +01:00
Daniel Løvbrøtte Olsen
ddc3b844d1 Olds CSS rules begone! 2015-12-28 18:09:06 +01:00
Daniel Løvbrøtte Olsen
eceef2a56f Added links 2015-12-28 03:22:42 +01:00
Daniel Løvbrøtte Olsen
5761148d0a fixed search - wow i was stoopid 2015-12-28 03:01:12 +01:00
Daniel Løvbrøtte Olsen
2afdbfc2d1 fixed search 2015-12-28 02:51:10 +01:00
Daniel Løvbrøtte Olsen
d8f18df3ec more testing 2015-12-28 02:45:24 +01:00
Daniel Løvbrøtte Olsen
d21e86aeb0 Cheated a bit to test it out ;) 2015-12-28 02:37:05 +01:00
Daniel Løvbrøtte Olsen
a8ca54db0b ported search, counter, foot and title to php 2015-12-27 16:10:45 +01:00
Daniel Løvbrøtte Olsen
de87bdbb0d Merge pull request #1 from shish/develop
MERGE ALL THE THINGS
2015-12-17 11:26:51 +01:00
Shish
b328196d94 Merge pull request #534 from DakuTree/patch-sqlspeed
Subquery optimization on MySQL < 5.6
2015-12-04 14:52:35 +00:00
Daku
6d1c7c414b mysql < 5.6 has terrible subquery optimization, using EXISTS / JOIN fixes this 2015-12-04 11:38:44 +00:00
Shish
57e5d8e538 Merge pull request #519 from terokorp/feature/apache2.4_htaccess
Adding support for apache 2.4
2015-10-26 19:23:28 +00:00
Shish
ac9dc1b8d8 Merge pull request #523 from DakuTree/patch-tagevent
Move tag sanitization, alias resolving & tag parsing from set_tags > TagSetEvent
2015-10-26 19:22:08 +00:00
Shish
c946daf152 Merge pull request #530 from CrandellWS/hotfix-chatbox-relative-urls
hotfix-chatbox-relative-urls
2015-10-26 19:19:11 +00:00
William Crandell
7b8d8c2a2d chatbox bugfix
might cause new bugs checking into this now
2015-10-26 11:22:57 -04:00
Shish
ffac680588 Merge pull request #529 from CrandellWS/hotfix-bad-blotter-id
Hotfix block id character stripping
2015-10-26 13:51:01 +00:00
Shish
28e1cb30ea Merge pull request #528 from CrandellWS/develop
merge start of material theme
2015-10-26 13:49:56 +00:00
William Crandell
5ebd11717f Hotfix block id character stripping
Example problem is ext/blotter uses:

  $page->add_block(
     new Block(
     "Welcome to the Blotter Editor!", <-- SEE --> !
      $html, "main", 10));
2015-10-26 09:44:01 -04:00
William Crandell
b7114a142e Hotfix for better block id character filtering 2015-10-26 09:20:09 -04:00
William Crandell
4af1199ff3 Hotfix flash message
maybe other issues not sure
2015-10-26 09:10:32 -04:00
William Crandell
6fb43eab19 On Develop branch
Sorry about that
2015-10-26 08:51:02 -04:00
Shish
b9f6b44301 bump 2015-10-26 12:14:36 +00:00
Shish
7b0933ea54 merge develop, fix conflicts, bump 2015-10-26 12:13:17 +00:00
Shish
f8cb2e96e4 only check min < val < max if min and max are defined 2015-10-19 07:19:53 +01:00
Shish
0855c7e9c4 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2015-10-19 07:11:47 +01:00
Shish
4971fa9337 empty bool = false 2015-10-19 07:11:41 +01:00
Shish
e9a1bebe76 how did this ever not break everything? x_x 2015-10-19 06:58:47 +01:00
Shish
3e0dc69766 Merge pull request #524 from DanielOaks/fix-notes-for-sqlite
Fix the notes extension to work properly on SQLite
2015-10-15 09:26:50 -07:00
Daniel Oaks
144ac6bd89 Fix the notes extension to work properly on dbs other than MySQL 2015-10-15 19:31:39 +10:00
Shish
3aed2abec7 don't complain if one image in a bulk revert doesn't exist 2015-10-12 19:17:16 +01:00
Daku
600011219f update docs 2015-10-08 19:25:38 +01:00
Daku
de761c11d7 speed: don't check tag for metatag if doesn't contain : or = 2015-10-08 19:20:40 +01:00
Daku
43d0a297b8 move metatag parsing to after set_tags is sent, rather than before 2015-10-08 18:58:52 +01:00
Daku
b11041898b Tag::explode instead of explode
this fixes the off chance that an empty tag could be passed
2015-10-08 18:28:00 +01:00
Daku
744989a983 Tag::explode should now remove duplicate tags 2015-10-08 18:25:03 +01:00
Daku
6ff80ab2c8 move tag sanitization, alias checking & tag parsing to TagSetEvent 2015-10-08 18:22:20 +01:00
Daku
23b9d7d8da improved metatag regex 2015-10-08 18:03:45 +01:00
Shish
d30665d274 more deduping 2015-09-27 21:09:27 +01:00
Thasan
993bedc10c Changing to use IfModule instead of IfVersion 2015-09-27 22:48:30 +03:00
Shish
1ac88e8923 more comments 2015-09-27 12:38:48 +01:00
Shish
80f5a016c2 or else 2015-09-27 11:01:59 +01:00
Shish
cdcd762a52 more comments 2015-09-27 02:17:44 +01:00
Shish
78c44c7067 more tidying shimmie api and user page 2015-09-27 02:00:02 +01:00
Shish
793bc3614b loooooooads of artists refactoring and tidying 2015-09-27 01:03:58 +01:00
Shish
5d188a94cf one sample? 2015-09-26 22:58:34 +01:00
Shish
81ce7082f4 require fewer samples, should still be ok 2015-09-26 22:54:51 +01:00
Shish
b9a0278f6e clean up danbooru api code 2015-09-26 22:50:05 +01:00
Shish
937106c0d7 merge coverages 2015-09-26 21:07:06 +01:00
Shish
ff13e58f8f clamp to a valid value 2015-09-26 20:03:30 +01:00
Shish
e482f97955 more clamping 2015-09-26 19:53:15 +01:00
Shish
5a8df90fd9 splitting up huge functions in ext/comment 2015-09-26 19:14:11 +01:00
Shish
6a94ae4aaf spacing 2015-09-26 17:53:02 +01:00
Shish
47b9a6966a no access needed for public repo 2015-09-26 17:45:58 +01:00
Shish
c93b085ca5 update secure 2015-09-26 17:41:59 +01:00
Shish
5947220930 code coverage for scrutinizer 2015-09-26 17:23:52 +01:00
Shish
8c1e3bc92a mark tests as public 2015-09-26 11:17:13 +01:00
Shish
85e5c7250b mark tests as public 2015-09-26 11:11:26 +01:00
Thasan
b92dfd6718 Adding support for apache 2.4
This requires mod_version installed, but many distributions ship it by default
2015-09-25 23:06:56 +03:00
Shish
7f20b0527e remove trailing commas from schema definitions in create_table() 2015-09-24 23:16:51 +01:00
Shish
c54e336695 mark tests as incomplete rather than commenting them out 2015-09-24 23:16:38 +01:00
Shish
c337abe280 skip tests for extensions who don't support the current db 2015-09-21 10:05:32 +01:00
Shish
98d71f527d test fix 2015-09-20 23:24:26 +01:00
Shish
6919431c49 performance 2015-09-20 23:20:51 +01:00
Shish
82ab2a8305 faster tests 2015-09-20 23:10:33 +01:00
Shish
fc2bbefcb9 test all the things 2015-09-20 22:40:04 +01:00
Shish
13dfb8861f more tests 2015-09-20 21:20:28 +01:00
Shish
2600ef042b setup tests 2015-09-20 20:44:34 +01:00
Shish
7bfc959547 upload tests 2015-09-20 20:28:27 +01:00
Shish
ff8da5be8e word filter tests 2015-09-20 20:18:55 +01:00
Shish
90cd823ece fix liveness testing, and comments tests 2015-09-20 20:05:06 +01:00
Shish
c9036a91d5 remove dead code 2015-09-20 20:04:25 +01:00
Shish
0617079afb test 2015-09-20 20:04:16 +01:00
Shish
9f10f923fe chdir() in phpunit bootstrap 2015-09-20 20:04:16 +01:00
Shish
49a6083b8b fix 2015-09-20 20:04:16 +01:00
Shish
83a1336b76 more test 2015-09-20 20:04:16 +01:00
Shish
ec484c1144 more phpunit-ing 2015-09-20 20:04:13 +01:00
Shish
2d622cf908 put test svg in a file 2015-09-20 20:03:29 +01:00
Shish
8fa2fadd44 add sqlite to travis 2015-09-20 20:03:29 +01:00
Shish
9af5995dcc PHPUnit proof of concept 2015-09-20 20:03:29 +01:00
Shish
e3959e5ec8 remove simpletest 2015-09-20 20:03:29 +01:00
Shish
5a466bfac3 include config modification data in JS/CSS caches 2015-09-20 20:02:39 +01:00
Shish
ba3a61052f timeout on varnish purger 2015-09-20 20:02:36 +01:00
Shish
ffc636515f remove dead code 2015-09-20 20:02:34 +01:00
Shish
147b47a78c add comment style 2015-09-20 20:02:30 +01:00
Shish
e4b4bf2c8f better sql logging 2015-09-20 20:02:24 +01:00
Shish
1fc5fb755a handle null in tag_editcloud 2015-09-20 13:20:26 +01:00
Shish
ca1782ff68 Merge pull request #512 from JarJak/develop
fixed declaration of CustomCommentListTheme::comment_to_html
2015-09-20 12:32:34 +01:00
JarJak
446397b5ad fix 2015-09-20 12:14:56 +02:00
Shish
35a4f385b3 phpstorm tidying 2015-09-12 11:43:28 +01:00
Shish
6c304420a6 Merge pull request #506 from DakuTree/bugfixes
Fixes #505 & other small fixes
2015-08-12 10:41:00 +01:00
Daku
a55306f4be don't throw no handler error if empty directory / directory doesn't exist 2015-08-12 06:58:44 +01:00
Daku
9490e4aae2 fix endless loop when checking for subdirectory
is there any reason why this was only checking for "." & ".." ?
2015-08-12 06:43:38 +01:00
Daku
b2d8b41388 spaces > tabs 2015-08-12 06:12:53 +01:00
Daku
ef6a7289bb if alias is negative and has multiple tags, make sure each tag becomes negative 2015-08-12 06:11:21 +01:00
Daku
ebfcf9389e using links as block title doesn't play nice with .js, so specify ID
blocks use title as ID, which will break if the title is a link
2015-08-10 00:45:06 +01:00
Shish
4e79cbdc18 installer exit codes
Conflicts:
	install.php
2015-08-09 15:42:05 +01:00
Shish
e5511ec801 wtf, php 2015-08-09 15:40:57 +01:00
Shish
9ff2ef390d make handle_404 handle blotter 2015-08-09 13:40:34 +01:00
Shish
d043852785 php7 compat 2015-08-09 13:40:34 +01:00
Shish
e415bd3fca more database support 2015-08-09 12:20:43 +01:00
Shish
0ef8db8371 not all requests have remote_addr 2015-08-09 12:20:37 +01:00
Shish
a70ca33f96 fix typo 2015-08-08 23:04:36 +01:00
Shish
df56213ec3 allow exts to mark themselves as not supporting sqlite 2015-08-08 22:23:27 +01:00
Shish
a7d0158848 more sqlite support 2015-08-08 22:19:10 +01:00
Shish
65289ed9c8 skip to latest db 2015-08-08 22:18:54 +01:00
Shish
cef7257ec8 abstract unique index creation 2015-08-08 22:18:38 +01:00
Shish
45d090054b set_default_bool should take a bool, always 2015-08-08 22:18:25 +01:00
Shish
fa2f17817e scrutinizing 2015-08-04 13:53:58 +01:00
Shish
d2d2a9b73a try checking for property of object rather than class? 2015-08-04 12:37:35 +01:00
Shish
654b282ab7 allow BASE_HREF to be defined explicitly 2015-08-03 15:49:38 +01:00
Shish
7762b2c703 do InitExtEvent in bootstrap, before user exists 2015-08-03 15:49:38 +01:00
Shish
83435e3266 set cookies on Page object 2015-08-03 15:49:36 +01:00
Shish
eb246ef1ee count_execs is legacy code from adodb anyway... remove another global 2015-08-02 21:31:55 +01:00
Shish
ec5d9bb6f4 more sensible naming 2015-08-02 21:23:45 +01:00
Shish
6e56224b8d rename final private global 2015-08-02 21:19:22 +01:00
Shish
669fd800d0 move all event stuff together 2015-08-02 20:54:41 +01:00
Shish
3ad8fe4a93 inline add_event_listener to save some layers and loops 2015-08-02 20:43:53 +01:00
Shish
18490ed488 more global removal 2015-08-02 20:39:41 +01:00
Shish
c17250b6b9 there is no more purge unused tags 2015-08-02 19:41:06 +01:00
Shish
e2b6f2c2dd global management 2015-08-02 19:41:06 +01:00
Shish
c6cf4fa339 hiphop is no more, hhvm can handle defines 2015-08-02 19:40:57 +01:00
Shish
90e86b99ae page->set_code() to set HTTP status 2015-08-02 19:40:57 +01:00
Shish
94244d762b include the right jquery in install.php 2015-08-02 19:40:57 +01:00
Shish
0b385d05af wibble init into a separate bootstrap file, for more unit-testability 2015-08-02 19:40:57 +01:00
Shish
f68102b267 also test php7 2015-08-01 18:40:28 +01:00
Shish
4f413e7386 using system php-fpm always gets us php5.3 ~_~ 2015-08-01 17:43:57 +01:00
Shish
21a1b176c6 separate router from index.php 2015-08-01 16:36:07 +01:00
Shish
3d5172e235 stand-alone router mode 2015-08-01 16:23:33 +01:00
Shish
e3cb33efb9 remove single-use var 2015-08-01 16:22:42 +01:00
Shish
aed08d3c12 revert a little 2015-08-01 16:15:36 +01:00
Shish
ab4c558126 php cli server uses its own request thing 2015-08-01 15:24:58 +01:00
Shish
53f19120d2 check for set vars in get_base_href 2015-08-01 14:55:53 +01:00
Shish
ac5f0f611c also install sqlite driver 2015-08-01 14:12:58 +01:00
Shish
cb9cc07d30 add test-info 2015-08-01 14:05:42 +01:00
Shish
9bba2a0f5b update travis too 2015-08-01 13:44:37 +01:00
Shish
35d762bc32 Also update programatic check 2015-08-01 13:42:40 +01:00
Shish
78bbaa04de PHP 5.3 has been EOL for a year now, Postgres 8 too 2015-08-01 13:41:47 +01:00
Shish
e5e7f891e2 scrutinizer suggestions 2015-07-21 01:20:53 +01:00
Shish
9508bec8d3 whyyyyy 2015-07-21 01:00:13 +01:00
Shish
7064e9ffcd merge 2015-07-20 23:49:33 +01:00
Shish
f3c9f57e62 Merge pull request #491 from shish/more_https
More https
2015-07-20 23:28:21 +01:00
Shish
2b6f3b7266 better asserting 2015-07-19 19:04:35 +01:00
Shish
90539a32bc validate_input() function 2015-07-19 14:46:28 +01:00
Shish
06d8c7a879 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2015-07-12 22:40:34 +01:00
Shish
feecdd4d13 support for changing usernames 2015-07-12 22:14:57 +01:00
Shish
1907dc29bc cache event log size for 600 seconds, not cache the value '600' forever... 2015-07-12 22:12:37 +01:00
Shish
88fead6ba6 connect to search accelerator for 2-100x speedup on heavy queries 2015-07-12 22:12:05 +01:00
Shish
9f9f2735f4 case-insensitive autocomplete 2015-07-04 12:26:29 +01:00
Shish
cabc600207 show all html headers on the front page, so autocomplete can work 2015-07-04 12:25:23 +01:00
Shish
10e7dd27d4 remove trailing commas from schema definitions in create_table() 2015-07-02 12:38:33 +01:00
Shish
8f7e4bab3c Merge pull request #492 from jgen/develop
Fix issue with "Fit to Width" and other "Fit to .." options not working.
2015-06-02 12:14:25 +01:00
jgen
0ebe510db1 Javascript IIFE (immediately invoked function expressions) need semicolon. 2015-06-01 18:27:00 -07:00
jgen
f73e0e6884 argh 2015-06-01 18:00:46 -07:00
jgen
4bf56364eb Missed one use of $headers. Was used in only one other place. Also, change the function so that if anyone else uses it in the future it doesn't throw warnings. 2015-06-01 17:57:34 -07:00
jgen
6a6480181d Move zoom function inside closure so it isn't potentially knocked out by other functions (with same name) in global scope. 2015-06-01 15:38:04 -07:00
jgen
8a8a26e23a Merge branch 'develop' of git://github.com/shish/shimmie2 into develop 2015-06-01 15:31:27 -07:00
Shish
1aaaeb69fb fix merge 2015-06-01 16:00:03 +01:00
Shish
4ebcf449e2 Merge pull request #488 from jgen/master
Only examine response headers if we actually have them & remove duplicate 'class' attribute.
2015-06-01 15:58:36 +01:00
Shish
4fd3d1a024 argh php 2015-06-01 11:23:04 +01:00
Shish
8d2b97b72f more https support, and dedupe get_image/thumb_link 2015-06-01 11:09:19 +01:00
Shish
21d96db771 remove unused var 2015-06-01 10:50:26 +01:00
jgen
1c43fd9dc4 Remove duplicate 'class' attribute on html element. Fixed tag edit box so that auto complete works again. 2015-06-01 02:39:08 -07:00
jgen
56b9117a20 Merge branch 'master' into develop 2015-05-31 22:43:49 -07:00
jgen
edc9e07919 Only examine the headers if we actually have them. 2015-05-31 21:39:22 -07:00
jgen
718cfa0bcf Scrutinizer-ci has fixed this now. 2015-05-31 21:28:11 -07:00
Shish
2aea79ac35 merge common parts of handle_archive and bulk_add 2015-05-24 16:08:46 +01:00
Shish
9a28f0f51a split vote recounting on user deletion into chunks 2015-05-18 23:40:21 +01:00
Shish
1779f97cac strip RTL from tags 2015-04-26 15:32:04 +01:00
Shish
bb490ac413 stop comment info from concealing comments 2015-04-26 12:54:54 +01:00
Shish
98c1c763aa show when tags/sources were set on the history pages 2015-04-26 12:45:32 +01:00
Shish
9a63322b2f manually merge Daku's get/post q thing, somehow automatic merge failed 2015-04-18 10:45:47 +01:00
Daku
598df41b1d set default in display, not build to avoid errors 2015-04-18 10:42:08 +01:00
Daku
be18140944 added option to hide random link on paginator (only post/list should show now) 2015-04-18 10:41:32 +01:00
Daku
a7de21523f user confirm on mass source set 2015-04-18 10:40:02 +01:00
Daku
83c0c284ac fix first setup block positioning 2015-04-18 10:39:56 +01:00
Daku
05cfbde372 .tables only works with the SQLite3 CLI program
http://www.sqlite.org/faq.html#q7
2015-04-18 10:39:48 +01:00
Daku
b1ec0c434e move tag category css to it's own file 2015-04-18 10:39:42 +01:00
Daku
8c2d8df81d use html5 input types
we would use type='url' for source but non-urls can be used as source too
2015-04-18 10:39:34 +01:00
Daku
7166259cc5 autocomplete='off' for tag input
this disables autocomplete/captilize on "most" mobile keyboards
autocompletejs still works with this off
2015-04-18 10:39:22 +01:00
Daku
845543b2ba fixes #478 - import now uses AddAliasEvent rather than manually adding itself 2015-04-18 10:39:09 +01:00
Daku
6289402f2e make sure aliases.csv is downloaded, and not opened in browser
SEE: http://stackoverflow.com/questions/6468517
2015-04-18 10:38:57 +01:00
Daku
b81d457bf7 DEBUG_SQL isn't defined during install, use quotes to avoid errors 2015-04-18 10:38:39 +01:00
Shish
55c3fd8f38 show PHP version (particularly useful when debugging php vs hhvm) 2015-04-18 10:31:24 +01:00
Shish
926e3ff5c0 turn off auto-select-first-item in autocomplete, apparently power users find it annoying? o.o 2015-04-18 10:31:24 +01:00
Shish
97a0aa7e4c give unread PM count a class, so custom themes can make it red if they wish 2015-04-18 10:31:24 +01:00
Shish
1703cd9caa bump 2015-03-13 09:49:25 +00:00
Shish
f5c4321322 bump 2015-03-13 09:48:54 +00:00
Shish
519749559f Merge pull request #481 from jgen/develop
Fix XSS in arrowkey extension
2015-03-13 08:37:08 +00:00
jgen
adec7e1763 Fix XSS vulnerability. 2015-03-12 23:12:27 -07:00
jgen
3511711008 Should be consistent about using the int_escape method. 2015-03-12 23:12:06 -07:00
Shish
bdbf95f2fa Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2015-02-08 04:28:03 +00:00
Shish
f92f286fa0 remove all reports from user 2015-02-08 04:27:54 +00:00
Shish
610b144fb2 minor CSS tweak 2015-02-07 22:29:07 +00:00
Shish
1c50616cdb Merge pull request #479 from jgen/develop
Change generated links to be aware of HTTPS.
2015-02-02 08:14:41 -08:00
jgen
8e3fc1da9f Change generated links to be aware of HTTPS. 2015-02-02 00:13:05 -08:00
Shish
d523781481 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2015-01-30 03:54:31 +00:00
Shish
52dc36cfa6 str_getcsv rather than explode, for handling quotes 2015-01-30 03:53:58 +00:00
Shish
99be9c2fdb Merge branch 'master' into develop 2015-01-25 23:00:07 +00:00
Shish
09d03d9036 allow dashes and underscores in static files, for apple-touch-icon 2015-01-25 22:58:25 +00:00
Shish
86716082e1 32x32 favicon 2015-01-25 22:57:49 +00:00
Shish
0d2d7eedd7 minor opt-in class to style 2015-01-25 06:55:37 +00:00
Shish
eae385f4c0 bump 2015-01-24 17:11:25 +00:00
Shish
cbbc0bab26 version bump 2015-01-24 17:10:23 +00:00
Shish
8eee2c0c92 logstash logger 2015-01-24 16:57:28 +00:00
Shish
67628816a4 Finally commit ext for purging varnish on-demand 2015-01-24 16:54:18 +00:00
Shish
b47e4aacb1 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2015-01-24 16:44:33 +00:00
Shish
edde7848b8 add mp3 to thumbless filetypes, see #454 2015-01-24 16:44:27 +00:00
Shish
67fc50e6c3 Merge pull request #475 from Diftraku/ouroboros_api
Fix duplicate handling
2015-01-24 08:41:06 -08:00
Shish
8dc27c6b97 home page mobile tweaks 2015-01-08 23:31:24 +00:00
Diftraku
3af95b5ebe Fix duplicate handling
Now with 17% less fat:
* Moved validation logic into OuroborosPost for sanity
* Added sanity checks for OuroborosPost values
* Changed defaults to be more sane (mainly tags)
* OuroborosPost now accepts an MD5 hash along the metadata (like it
should!)
* Fixed dupe handling logic
2015-01-05 13:47:53 +02:00
Shish
92bc8b0709 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2014-12-31 13:16:24 +00:00
Shish
217559dc22 set statement_timeout for postgres 2014-12-31 13:16:00 +00:00
Shish
1cb0f13820 Merge pull request #471 from ElementAB/develop
Fixed information box link generation
2014-12-29 12:35:33 +00:00
ElementAB
b555fab819 Fixed information box link generation
Fixed issue where the information box would generate an incorrect link for the image source if the source was given as https:// instead of http://.
2014-12-25 01:38:51 -06:00
ElementAB
3d17310581 Fixed information box link generation
Fixed issue where the information box would generate an incorrect link for the image source if the source was given as https:// instead of http://.
2014-12-25 01:36:18 -06:00
ElementAB
9ee2250e63 Fixed information box link generation
Fixed issue where the information box would generate an incorrect link for the image source if the source was given as https:// instead of http://.
2014-12-24 12:34:32 -06:00
Shish
0fcfa49e75 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2014-12-18 14:38:37 +00:00
Shish
be4ae9da32 fix missing global (how did this pass tests? o.o) 2014-12-18 14:38:22 +00:00
Shish
caa2691bb9 Merge pull request #468 from jgen/develop
Allow Shimmie to work with older versions of FFmpeg
2014-12-17 13:44:25 +00:00
Shish
7bcf2fc29e Merge pull request #465 from shish/min_space_config
Turn MIN_FREE_SPACE into a userspace config option
2014-12-17 13:41:55 +00:00
Shish
c0279565f4 Merge pull request #462 from shish/bypass_comment_checks
Allow admins to bypass comment checks (rate limit etc)
2014-12-17 13:40:06 +00:00
jgen
d878526487 Button should say "Regenerate Thumbnail" for clarity. 2014-12-14 16:07:00 -08:00
jgen
95b94bd115 Some minor linting. 2014-12-14 16:06:31 -08:00
jgen
433d67453c This fixes issue #466. You can now optionally generate thumbnails for videos with the newer versions of FFmpeg that take into account the aspect ratio. 2014-12-14 15:22:44 -08:00
jgen
5012b15ce9 Comment as to why the default is to ignore the aspect ratio. 2014-12-14 15:20:47 -08:00
jgen
2ff036d250 Remove unused variables. 2014-12-14 15:19:59 -08:00
jgen
02efa180de Add a default option to ignore aspect ratio when creating a thumbnail with FFmpeg. 2014-12-13 16:54:21 -08:00
Shish
e1a7986239 Merge remote-tracking branch 'origin/master' into develop 2014-12-13 01:52:34 +00:00
Shish
b71c96a130 Merge pull request #467 from terokorp/hotfix/ratingsearchfix
Fixing "rating=safe" search bug
2014-12-13 01:51:08 +00:00
Thasan
f2fc69cef4 Fixing "rating=safe" search bug 2014-12-13 03:23:44 +02:00
Shish
40734ef711 turn MIN_FREE_SPACE into a userspace config option 2014-12-07 13:54:47 +00:00
Shish
9abcadffb6 throw Ex() -> throw new Ex(); 2014-12-07 13:20:36 +00:00
Shish
316f7ff858 Give a better error if User::class has been screwed up somehow 2014-12-07 03:44:17 +00:00
Shish
88f1df9ca6 HIPHOP is no longer supported (it's HHVM now, and HHVM supports PDO params anyway) 2014-12-07 01:10:12 +00:00
Shish
5d099c06f9 remove references to unused cache values 2014-12-06 23:51:58 +00:00
Shish
5ab8f9e5e2 log sets and deletes in CACHE_DEBUG mode 2014-12-06 23:50:56 +00:00
Shish
4d6e6ade1c version bump 2014-12-06 23:48:03 +00:00
Shish
b3931dd403 version bump 2014-12-06 23:47:17 +00:00
Shish
1bf3fba756 Merge branch 'develop' 2014-12-06 23:46:14 +00:00
Shish
8c1eb60367 Remove twitter_soc; any twitter ext will need to be different, fixes #395 2014-12-01 14:16:53 +00:00
Shish
a3880e2b49 Merge pull request #463 from shish/user_list_link
add a link to the user list, fixes #460
2014-12-01 14:04:04 +00:00
Shish
fcb7166b0c Merge pull request #461 from shish/dbtime
Log time spent in database (and some other minor performance debugging tweaks)
2014-12-01 14:03:28 +00:00
Shish
7f22f99aaa add a link to the user list, fixes #460 2014-11-30 15:42:43 +00:00
Shish
8511399076 bypass ban_words too 2014-11-30 13:52:29 +00:00
Shish
010b0620df add a user flag for bypassing comment checks 2014-11-30 13:52:29 +00:00
Shish
e4be2c24c6 update lib/context.php 2014-11-30 12:49:08 +00:00
Shish
c0dfd9adc0 a little extra consistency in cache variable names 2014-11-26 13:09:49 +00:00
Shish
4721d666cd log time spent waiting for database queries 2014-11-26 13:09:22 +00:00
Shish
1c60942730 log hit/miss when DEBUG_CACHE is on 2014-11-26 13:07:30 +00:00
Shish
dfba656355 die more explicitly when memcache is missing (PHP is terrible) 2014-11-24 02:21:45 +00:00
Shish
a846c16bd7 Merge pull request #457 from shish/show_bad_word_bans
Show when a banned regex is invalid
2014-11-10 23:57:17 +00:00
Shish
565b466721 Merge pull request #450 from DakuTree/patch-zipcheck
Hide "Download All Images" if ZipArchive doesn't exist/not enabled - Fixes #441
2014-10-15 18:33:30 +01:00
Shish
824d078ece Show when a banned regex is invalid 2014-10-15 18:31:06 +01:00
Shish
63a619d295 Merge branch 'develop' of https://github.com/shish/shimmie2 into develop 2014-10-10 22:20:29 +01:00
Shish
ccfc22aa8b make block placement more explicit 2014-10-10 22:19:54 +01:00
Shish
a066c5ac0e regex for word filtering 2014-10-10 22:19:20 +01:00
Shish
8c7257eb54 Merge pull request #452 from DakuTree/patch-fix451
Fix "Image Only" not working with nice_urls disabled - Fixes #451
2014-09-04 22:05:39 +01:00
Daku
693b18f50c fix "Image Only" not working with nice_urls disabled 2014-09-02 17:06:39 +01:00
Daku
1cb59572f9 Merge pull request #445 from shish/bbcode_anchor
[anchor] bbcode tag, to make linking to parts of a page easier
2014-08-30 13:37:01 +01:00
Shish
c651845401 make anchors visible in the page, not just in the markup 2014-08-30 13:18:14 +01:00
Daku
ab3366e518 Hide "Download All Images" if ZipArchive doesn't exist/not enabled 2014-08-26 16:16:15 +01:00
Daku
9719464c8b Merge pull request #448 from shish/theme_breakdown
Make IndexTheme a little easier to customise
2014-08-26 15:51:41 +01:00
Daku
0744b50a71 Merge pull request #446 from shish/simplify_uceu
simplify user_can_edit_user -- many nested if/else's to one if
2014-08-26 15:19:27 +01:00
Daku
5efe1ddb36 Merge pull request #444 from shish/check_handler_exists
Check handler exists
2014-08-26 13:47:32 +01:00
Shish
fc4bd27529 simplify user_can_edit_user -- many nested if/else's to one if 2014-08-24 20:43:12 +01:00
Shish
9c71f59061 [anchor] bbcode tag, to make linking to parts of a page easier 2014-08-24 16:55:00 +01:00
Shish
50d40c1c81 give IndexTheme:display_page some sub-functions to make them easier to customise 2014-08-24 16:29:32 +01:00
Shish
f873d2304d Check event handler exists before calling it, in case the cache is out of date. Fixes #442 2014-08-24 11:33:05 +01:00
Shish
d6eb3892e2 Merge pull request #435 from geosohh/master
Fixes for mass_tagger extension
2014-08-02 17:48:21 +01:00
Shish
b1d634a8de Merge pull request #434 from DakuTree/patch-tagfix
Fix for tags exceeding char limit returning error + Bumped tag char limit to 255.
2014-08-02 17:47:14 +01:00
Geosohh
3185af3280 Remove tags from images using mass_tagger
Using negative tags removes them from the selected images.
2014-07-29 19:11:29 -03:00
Geosohh
3d2c55a9d5 Fixed mass_tagger bug when selecting images with similar ids
The mass_tagger extension behaves incorrectly if the id of an image is
the same as the ending of the id of another image.

Example:
Select image 143: var string == "143:"
Select image 113: var string == "143:113:"
Select image 43: var string == "1113:"
So either the wrong image will be tagged, or it will cause an error if
that image doesn't exist.
2014-07-29 18:02:50 -03:00
Daku
14f682da75 avoid errors if tag exceeds limit + flash message showing the problem tag 2014-06-20 00:39:29 +01:00
Daku
b89dd52b47 bumped the tag char limit to 255
there wasn't really much reason to have this so low, and this puts it to the same limit as other booru software
2014-06-19 23:14:11 +01:00
Shish
9069b0c1fb Merge pull request #429 from DakuTree/patch-misc
Pool updates + other misc tweaks.
2014-06-09 21:05:19 +01:00
Shish
d067545969 Merge pull request #432 from DakuTree/patch-sqlfix
Fix for #431 - Fix negative tags not working if they don't exist.
2014-06-09 20:59:11 +01:00
Matthew Barbour
d9d4137735 Merge pull request #2 from DakuTree/patch-sqlfix
fixes negative tags not working if they don't exist
2014-06-09 12:55:15 -05:00
Matthew Barbour
92f3ba480a Merge pull request #1 from shish/develop
Develop
2014-06-09 12:52:50 -05:00
Shish
196e4d5300 Merge pull request #433 from muhkuh2005/master
Added check to initialize tags, when only categorized tags are available...
2014-06-09 16:32:34 +01:00
Pascal 'muhkuh2005' Borkenhagen
3468480864 Added check to initialize tags, when only categorized tags are available for image 2014-06-09 14:07:24 +00:00
Daku
5bd7369d7c Merge pull request #427 from shish/bcrypt
Use bcrypt for password storage
2014-06-05 18:00:19 +01:00
Daku
72fbafc271 fixes negative tags not working if they don't exist
fixes #431
2014-06-04 00:52:01 +01:00
Daku
24f2741a50 rating_to_name no longer exists
was removed in commit eadf1d9e87419a3f570cc9e7e77082358a9af0db
2014-06-01 21:56:33 +01:00
Daku
90232b0a4c rename "index" to "pool index" to help differentiate between the two index links 2014-05-31 18:20:57 +01:00
Daku
183a2c7ae6 docs + added pool:lastcreated metatag which uses the ID of the last pool created by the user 2014-05-30 16:04:12 +01:00
Daku
df6b9245ec optional setting to set pool order with the pool metatag
example: pool:10:3 (pool #10 with order 3)
2014-05-30 15:21:42 +01:00
Daku
77769d2c17 allow setting of image_order via add_post() 2014-05-30 15:21:32 +01:00
Daku
bca4fdffa5 make sure pool nav & manage pool show when needed 2014-05-30 14:41:03 +01:00
Daku
6a14360e7a option to use autoincrementing pool order when post is added
rather than having everything default to 0 order
2014-05-30 13:12:34 +01:00
Daku
1dc668ca41 main.php shouldn't be generating HTML.. 2014-05-30 11:55:53 +01:00
Daku
f228a8eb4f pools css should be in it's own folder, not the themes 2014-05-30 00:23:12 +01:00
Daku
1dada97adc prev link for pools 2014-05-30 00:22:04 +01:00
Daku
77607e0c02 keep aspect ratio when created video thumbnail 2014-05-29 18:40:17 +01:00
Daku
aa0cf27e7e add theme name to cached css/js filename to avoid loading cached files of old theme after theme change 2014-05-29 18:37:49 +01:00
Shish
e259b8cd5a bcrypt had bugs before 5.3.7 2014-05-24 10:51:09 +01:00
Shish
a3f9e12ea0 octal for permissions 2014-05-24 10:18:44 +01:00
Shish
1b655704f3 Merge remote branch 'yaoifox/patch-1' into develop 2014-05-24 10:18:10 +01:00
Shish
d5c5cfd428 Merge pull request #426 from DakuTree/patch-notesfix
Fix for #422 + Fix Note Positioning
2014-05-24 10:12:58 +01:00
Shish
eb51a9b639 migrate to bcrypt for password storage 2014-05-24 09:47:42 +01:00
Daku
36713803e3 Merge pull request #418 from jgen/develop
Even more linting and cleaning!
2014-05-23 23:24:32 +01:00
Daku
ae0a4725a0 avoid loading imgnotes until image is fully loaded 2014-05-23 21:46:13 +01:00
Daku
3d11df44a9 fix various bugs with notes ext 2014-05-23 20:48:11 +01:00
Shish
8d75968ab0 Merge pull request #425 from DakuTree/patch-headerfix
Fix for #423 - Check for lowercase header if camelcase header doesn't exist
2014-05-20 07:48:12 +01:00
Daku
64e2565236 check for lowercase header if camelcase header doesn't exist
fixes #423
2014-05-19 07:32:16 +01:00
YaoiFox
349d7bfa4d curl --silent now
curl --silent now
2014-05-07 07:10:23 +02:00
YaoiFox
f5f48d8a0b Update main.php 2014-05-07 05:38:12 +02:00
YaoiFox
5f0642f349 some bug fixes to cron uploader 2014-05-07 05:30:44 +02:00
jgen
a58bdbdc62 More PHP Doc comments. 2014-04-29 17:45:13 -04:00
jgen
2bf4c667a7 Some more comments. 2014-04-29 01:51:13 -04:00
jgen
317028a63b More PHP Doc comments. 2014-04-29 01:33:03 -04:00
jgen
3b1513f791 Adding some more PHP Doc comments. 2014-04-28 20:37:31 -04:00
jgen
c682ccef69 Adding more PHP Doc comments to the config classes. 2014-04-28 20:12:31 -04:00
jgen
eb18790dc9 More comments. 2014-04-28 19:50:01 -04:00
jgen
b3f9925839 PHPDoc all the things! 2014-04-28 18:22:57 -04:00
jgen
2a9f76d2f0 More comments, removing dead code, fixing small bugs. 2014-04-28 17:36:52 -04:00
jgen
86612bb1ff Adding more PHP Doc comments. 2014-04-28 17:36:11 -04:00
jgen
eadf1d9e87 Remove dead code. 2014-04-28 17:18:51 -04:00
jgen
9f838c0c11 Clean all the things. 2014-04-28 03:32:43 -04:00
jgen
b56e390676 Lots of linting again. 2014-04-28 03:26:35 -04:00
jgen
158819cb28 Linting: These actually were bugs. 2014-04-28 03:16:09 -04:00
jgen
d4ac6ca451 Such linting, such wow. Much clean. 2014-04-28 02:56:58 -04:00
jgen
8fd532e5a8 More linting and removing dead code. 2014-04-28 02:43:49 -04:00
jgen
3cd8c33ed1 Convert Spaces -> Tabs 2014-04-28 02:24:19 -04:00
jgen
e37c5cb9d0 More linting! 2014-04-28 02:23:45 -04:00
jgen
022c162f40 More linting and fixing of comments and PHP Doc, types, etc. 2014-04-28 01:26:22 -04:00
jgen
a0a39784d4 Make webm videos auto-loop by default. 2014-04-28 00:57:49 -04:00
jgen
76fd27f87c Fixing/Cleaning more PHP Doc related issues. 2014-04-28 00:56:19 -04:00
jgen
8a2eb4b121 Fixing more PHP Doc related issues. 2014-04-27 19:29:36 -04:00
jgen
03b3cdcbd2 Updating/Fixing/Adding more comments with the PHP Doc style. 2014-04-27 18:59:01 -04:00
Jeff Genovy
07cd38d34c Merge pull request #415 from jgen/master
Bump the version number.
2014-04-27 15:46:35 -04:00
jgen
a703bb9854 Bump the version number. 2014-04-27 15:45:22 -04:00
jgen
4f51e942be Updating/Fixing/Adding more comments with the PHP Doc style. 2014-04-27 15:33:57 -04:00
jgen
024e7eac8d Fix underling. 2014-04-26 17:52:31 -04:00
jgen
b707081b33 Cleaning up the README. 2014-04-26 17:47:14 -04:00
jgen
c37ba07de0 Convert the README.txt file into Markdown format. 2014-04-26 17:43:13 -04:00
jgen
b1d123c960 Add more escaping to the shell command for ffmpeg. 2014-04-26 17:09:18 -04:00
jgen
26e8db1fdc Add the Travis-CI build status icon image to the Readme. ;) 2014-04-26 17:01:01 -04:00
jgen
2e4b53c3f4 Merge branch 'code_removal' of github.com:jgen/shimmie2 into develop 2014-04-26 16:55:11 -04:00
jgen
1ecf33e39d Merge branch 'video_thumb' of git://github.com/shish/shimmie2 into develop 2014-04-26 16:54:28 -04:00
jgen
86b6d12819 Merge branch 'linting' of github.com:jgen/shimmie2 into code_removal 2014-04-26 16:50:14 -04:00
Shish
e9d8ae7f5b Merge pull request #413 from jgen/linting
Lots o' Linting
2014-04-26 12:00:35 +01:00
jgen
95ba85ee43 This js file relies on functions defined in 'shimmie.js' which is all globed together when rendering a page. Adding an ignore line to scrutinizer to prevent false-positives. 2014-04-26 05:32:15 -04:00
jgen
4e9e5ca2be Fixing/adding/cleaning up the PHP Doc comments. 2014-04-26 05:01:49 -04:00
jgen
a8dd045586 Combine these if statements into one to reduce the cyclomatic complexity. 2014-04-26 03:56:06 -04:00
jgen
42407116a8 Removing this file. 2014-04-26 03:33:20 -04:00
jgen
013222e4ee Code Removal: PHP has had JSON support directly baked into the language since 5.2.0 - there is no need to rely on an external library from 2006 for json support. 2014-04-26 03:31:02 -04:00
jgen
efb5038b20 This file no longer exists. 2014-04-26 03:15:44 -04:00
jgen
298344048c Make these functions public to prevent scrutinizer from complaining. 2014-04-26 00:27:16 -04:00
jgen
e6b1d514d1 Some more type hinting via the comments. 2014-04-25 23:22:34 -04:00
jgen
24371d8c34 Fix the PHP Doc comments. 2014-04-25 23:19:18 -04:00
jgen
1ca3865a1c Removing the closing PHP "?>" tag as per style guidelines. (Also see: https://stackoverflow.com/questions/4410704/why-would-one-omit-the-close-tag ) 2014-04-25 22:54:51 -04:00
jgen
8f60467848 This code doesn't seem to actually be used by anything at all. 2014-04-25 22:33:57 -04:00
jgen
5f2be45f68 Fix the PHPDoc comments on these. 2014-04-25 21:42:52 -04:00
jgen
3e899710d7 Stop scrutinizer from complaining here. 2014-04-25 17:40:42 -04:00
jgen
c85630198f These variables where never defined depending on the execution path. 2014-04-25 17:40:14 -04:00
jgen
6f685ca227 Use the method from core/util to parse the bool from the DB instead. 2014-04-25 17:39:46 -04:00
jgen
3a1197256a Need to pull in the global $page variable for this method. 2014-04-25 17:39:06 -04:00
jgen
1e15c5e47a Explicitly declare this class variable 2014-04-25 17:38:25 -04:00
jgen
bcd3f991de Removing the closing PHP "?>" tag as per style guidelines. 2014-04-25 17:34:20 -04:00
jgen
b4e6c0b743 Explicitly check if we can actually find the image by Id. 2014-04-25 16:08:00 -04:00
jgen
5b5fd955b7 New exception type. 2014-04-25 16:07:30 -04:00
jgen
9acf6c7f19 Rename these class functions to prevent warnings from scrutinizer. 2014-04-25 15:57:23 -04:00
jgen
141ec53236 Rename these class functions to prevent warnings from scrutinizer. 2014-04-25 15:55:31 -04:00
jgen
478eb3a4cc Linting: Check for null return value, and throw an exception if null. 2014-04-25 15:39:00 -04:00
jgen
7ef68b15f9 Fix indentation. 2014-04-25 15:38:15 -04:00
jgen
6724f2b75a Make these functions public to prevent scrutinizer from complaining. 2014-04-25 14:17:44 -04:00
jgen
dcadfc395a Move this code back down. 2014-04-25 14:14:22 -04:00
jgen
a7faefc5e0 Spelling. 2014-04-24 22:35:01 -04:00
jgen
b241390fa4 Removing the closing PHP "?>" tag as per style guidelines. 2014-04-24 22:34:45 -04:00
jgen
50686ac61e More linting, fix PHPDoc style. 2014-04-24 22:29:29 -04:00
jgen
daee99c1bc Removing the closing PHP "?>" tag as per style guidelines. 2014-04-24 22:28:53 -04:00
jgen
860af25550 Need to pull in the global $user variable. 2014-04-24 22:22:45 -04:00
jgen
4ec5a7ce52 Removing the closing PHP "?>" tag as per style guidelines. 2014-04-24 22:22:16 -04:00
jgen
780d5f8709 More linting. Explicitly declare these class variables, as well as comment out dead code. 2014-04-24 22:13:00 -04:00
jgen
6e4ae229ef Explicitly declare this class variable 2014-04-24 19:13:41 -04:00
jgen
b3077d5bcd Removing the closing PHP "?>" tag as per style guidelines. 2014-04-24 19:13:20 -04:00
jgen
2706f72ce4 Update and fix the PHPDoc comments. 2014-04-24 19:08:23 -04:00
jgen
4cc31df737 Update the comments to PHPDoc style guidelines. 2014-04-24 19:02:43 -04:00
jgen
ab3f9850e4 Removing the closing PHP "?>" tag as per style guidelines. 2014-04-24 19:01:47 -04:00
jgen
286e75bef0 Scrutinizer complains that these functions aren't defined, yet they really are. 2014-04-24 18:45:54 -04:00
jgen
709366b70c Explicitly declare these class variables. 2014-04-24 18:44:46 -04:00
jgen
7b90331f02 Add missing semi-colon. 2014-04-24 16:56:04 -04:00
jgen
993aacaea5 Update comment to reflect that a bug has been filed with PHP-analyzer. 2014-04-24 16:55:34 -04:00
jgen
c4dcba1e91 Explicitly check for the existence of these properties before attempting to use them. 2014-04-24 05:37:26 -04:00
jgen
7e41194b7f Shish you are too clever for scrutinizer-ci. 2014-04-24 05:29:02 -04:00
jgen
ce524e8729 This variable does exist, it is actually pulled from the database. Making it explicit for the linter. 2014-04-24 05:25:12 -04:00
jgen
e2f5c7a6ec Check for null, just in case. 2014-04-24 05:09:38 -04:00
jgen
0501e05cff Comment out dead code/vars. 2014-04-24 05:09:16 -04:00
jgen
332c8538b7 Change the function syntax to quiet JSHint. 2014-04-24 04:46:23 -04:00
jgen
868f8c9225 Commenting out dead variables and dead code. 2014-04-24 04:44:28 -04:00
jgen
f9c8a1720e Comment out dead variables. 2014-04-24 04:41:13 -04:00
jgen
f67220f64b From stack-overflow: The 'var' keyword is for declaring class variables in PHP4. It works in PHP5 but can raise an E_STRICT warning in from version 5.0.0 up to version 5.1.2, as it has been deprecated.
Changing to public as that is really what these variables are anyways.
2014-04-24 04:36:05 -04:00
jgen
67c087c30f From stack-overflow: The 'var' keyword is for declaring class variables in PHP4. It works in PHP5 but can raise an E_STRICT warning in from version 5.0.0 up to version 5.1.2, as it has been deprecated.
Changing to public as that is really what these variables are anyways.
2014-04-24 04:30:58 -04:00
jgen
dbe30ad37d Rename variable to make it more clear. 2014-04-24 01:36:30 -04:00
jgen
afc01fda5f Prevent scrutinizer-ci from complaining about these fall-thoughs. 2014-04-24 01:36:04 -04:00
jgen
506e699f8a Not sure if we really need the triple equals. 2014-04-24 01:15:30 -04:00
jgen
866d39a68e The 'tags' box actually has a different ID. 2014-04-24 01:15:03 -04:00
jgen
51c145d5c9 Some more linting on the bookmarklet.js file. 2014-04-24 01:08:47 -04:00
jgen
4b90faf89d Even more linting of JS files. 2014-04-24 00:55:14 -04:00
jgen
a105090039 Some more JS linting. 2014-04-23 23:39:09 -04:00
jgen
887f963e17 Add the jshint comment to alter how scrutinzer-ci deals with the js. 2014-04-23 23:38:53 -04:00
jgen
31501e48fe More JS linting. 2014-04-23 23:31:20 -04:00
jgen
2dc6e054c8 These sorts are necessary on the header arrays, otherwise the relative priority of certain headers is not respected. 2014-04-23 23:28:05 -04:00
jgen
b775c007cd Give these HTML headers some slight priority over the default value of 50. 2014-04-23 23:07:10 -04:00
jgen
a0ab3864c0 Push the headers for the chatbox even further down to make sure that jQuery is already loaded. 2014-04-23 23:01:51 -04:00
jgen
8cdf919827 More linting. This time of the JavaScript for the chat-box extension. 2014-04-23 22:43:42 -04:00
jgen
0bd031a269 Merge branch 'develop' of github.com:jgen/shimmie2 into linting
Manually resolved conflicts:
	ext/chatbox/js/yshout.js
2014-04-23 22:42:30 -04:00
jgen
5eaa637bbb Merge branch 'develop' of git://github.com/shish/shimmie2 into develop 2014-04-23 22:23:53 -04:00
Shish
44d0e56c97 Merge pull request #406 from jgen/video_thumb
Video thumbnails
2014-04-20 11:25:24 +01:00
Shish
ef3daaf388 Merge pull request #411 from jgen/develop
Chatbox is broken on develop branch
2014-04-20 11:21:43 +01:00
jgen
21e1359b86 Avoid short open tag for PHP. 2014-04-20 05:07:26 -04:00
jgen
60980c8839 One more jQuery reference. 2014-04-20 05:04:45 -04:00
jgen
a7ba0f887b Again, the chatbox needs help from the jQuery migrate plugin. 2014-04-20 05:00:59 -04:00
jgen
2df54b911a Don't use short open tags for PHP as they are not turned on by default in production environments. 2014-04-20 05:00:34 -04:00
jgen
e2516fa5cf Chatbox should be fixed now.
The chatbox extension actually needs some functionality that was removed from jQuery versions 1.9 and up. When Shimmie's version of jQuery was bumped up to 1.11 this actually broke the chatbox.
2014-04-20 04:51:19 -04:00
jgen
880840623a Need to ensure that this html header is added last. 2014-04-20 04:36:25 -04:00
jgen
717401ccab Removing this as jQuery is already included in Shimmie now. 2014-04-20 04:35:33 -04:00
jgen
8b698e1b6f Revert this commit. 2014-04-20 04:29:11 -04:00
jgen
54362a49c6 Fix the broken chatbox issue. 2014-04-20 04:25:08 -04:00
jgen
57a390eaab Massive linting! 2014-04-19 23:38:42 -04:00
jgen
ff722906b7 Holy shit, this is really really nasty old JS code. I would love to dump this if possible. 2014-04-19 23:06:48 -04:00
Shish
dc6653d3be Merge pull request #407 from jgen/hotfix/downtime_theme_edits
Hotfix/downtime theme edits
2014-04-20 00:30:57 +01:00
Shish
75c3d8e453 Merge pull request #409 from jgen/linting
Linting
2014-04-20 00:29:53 +01:00
jgen
e838fc2a03 Comment this in case someone else wonders why its there. 2014-04-19 04:17:58 -04:00
jgen
c527ae91d6 More linting and cleanup. 2014-04-19 04:17:28 -04:00
jgen
be385deaab Clean up this js file as well. 2014-04-19 02:58:36 -04:00
jgen
28d83fda0d Even more linting and cleaning. 2014-04-19 02:33:34 -04:00
jgen
2da8c19a07 More linting! 2014-04-19 01:36:18 -04:00
jgen
bdf49b33cb Some more linting thanks to scrutinizer-ci 2014-04-19 01:18:49 -04:00
jgen
505a3e5233 Merge branch 'develop' of git://github.com/shish/shimmie2 into hotfix/downtime_theme_edits 2014-04-18 20:25:36 -04:00
jgen
71d7474476 Add <section> tags to the downtime page so that theme render correctly. 2014-04-18 20:21:50 -04:00
jgen
7fd655fc4e Added conditional message for IE users to download plugin from Google for webm. 2014-04-18 18:47:14 -04:00
jgen
b5d005710c Fix indentation (spaces -> tabs) 2014-04-18 02:10:10 -04:00
jgen
2f380f5d59 Video thumbs are working for webm files now. 2014-04-18 02:06:12 -04:00
jgen
273d386d22 Working on thumbnail support for video files. 2014-04-18 01:37:27 -04:00
Shish
b360914db7 Merge pull request #402 from justinbrewer/cloud-patch
Tag Edit Cloud patch
2014-04-12 11:08:00 +01:00
Shish
98b6d0a6ff Merge pull request #403 from jgen/gitignore
Update the gitignore file to exclude operating system detritus and such.
2014-04-12 10:50:27 +01:00
jgen
4deb7e1775 Update the gitignore file to exclude operating system detritus and such. 2014-04-12 03:25:18 -04:00
Justin Brewer
f243092634 count("") == 1 2014-04-11 23:55:30 -04:00
Shish
6c9a4a3b86 A start on video thumbnailing; see #400
Almost certainly not working yet; not tested, just committing so that
other people have something to work with and we aren't duplicating
effort
2014-04-10 08:15:00 +01:00
Jeff Genovy
3a0da5df5a Merge pull request #399 from Diftraku/ouroboros_api
Special chars in tags barf filter_var
2014-04-09 22:33:05 -04:00
Diftraku
7ac61f4234 Special chars in tags barf filter_var
Due to how FILTER_SANITIZE_STRING works, if you had special chars in
your tags (eg. <3 or !) the string would be cut before the offending
character(s) and in worst case, result in no tags passing to the API
2014-04-10 04:11:43 +03:00
Shish
d247713311 Merge pull request #397 from jgen/develop
Manual Merge of More Linting #391
2014-04-10 00:15:02 +01:00
jgen
3c6b8b6fe6 Merge branch 'linting' of git://github.com/shish/shimmie2 into develop
Conflicts:
	ext/resize/main.php
2014-04-09 15:04:18 -04:00
Shish
f72bfbb050 fix indentation 2014-04-06 20:47:01 +01:00
Daku
f88ba20319 Merge pull request #393 from shish/resize_tweak
pass already fetched image object into resize()
2014-04-04 17:38:08 +01:00
Daku
2d25d43ecf Merge pull request #389 from HungryFeline/patch-1
Redirect to default page if referer isn't set
2014-04-04 17:30:19 +01:00
Shish
a0d1948a4f pass already fetched image object into resize(), rather than passing the ID to be re-fetched 2014-04-02 08:49:13 +01:00
Shish
745f91e939 Merge pull request #392 from jgen/develop
Resizing images actually was broken, how nobody noticed I don't know.
2014-04-02 08:39:11 +01:00
jgen
1e49cc9122 More comments and fix undefined variables. 2014-04-01 22:02:36 -04:00
jgen
9b1c3db400 Slight re-word of the exception error message. 2014-04-01 21:43:12 -04:00
jgen
18b8e7912f Fix warning generated when an image doesn't have bits or channel info (and use sane defaults). 2014-04-01 21:42:40 -04:00
jgen
225d197790 Remove duplicate code. 2014-04-01 21:41:57 -04:00
jgen
749f846b79 Fix typo. 2014-04-01 20:33:36 -04:00
jgen
a442fbe409 Resizing images actually was broken, how nobody noticed I don't know. 2014-04-01 19:29:31 -04:00
jgen
737f96b8bb Merge remote-tracking branch 'shish/master' 2014-04-01 17:27:08 -04:00
Shish
c8aa3327a1 A bunch of small changes from scrutinizer-ci; some tidying, some actual bug fixes 2014-03-30 13:26:48 +01:00
Shish
db5aa56300 remove a bunch of dead code 2014-03-29 11:44:34 +00:00
HungryFeline
c215d6991a Redirect to default page if referer isn't set
Instead of displaying `You should be redirected to <a href=""></a>`
2014-03-27 09:57:43 +01:00
Shish
7a95325a5f Merge pull request #388 from DakuTree/patch
Fixes issues 233 & 234.
2014-03-26 10:41:31 +00:00
Daku
7837362711 make sure source is still used if provided 2014-03-26 08:26:21 +00:00
Daku
994652c3f7 make sure mass_tag_edit reads high > low id if default order is different 2014-03-26 08:05:05 +00:00
Daku
37b21e7fcb Merge pull request #387 from shish/linting
Use new-style constructors everywhere
2014-03-26 04:04:47 +00:00
Shish
7b68d8ebfd use new-style constructors everywhere 2014-03-22 09:00:59 +00:00
Daku
61764e7f9d Merge pull request #386 from shish/linting
A few fixes for things scrutinizer-ci found
2014-03-18 01:34:52 +00:00
Shish
da29912646 tidy up a bunch of lint errors 2014-03-17 22:05:37 +00:00
Shish
6f0d898c28 Merge pull request #385 from justinbrewer/scrutinizer-config
Scrutinizer-CI
2014-03-16 09:10:42 +00:00
Justin Brewer
6ad530fd49 Scrutinizer config file 2014-03-14 13:10:12 -05:00
Daku
76526f371c Add option to use transloaded URL as source
fixes #234
2014-03-14 16:54:14 +00:00
Daku
5f3ff8db86 don't add "tagme" when trying to mass-edit to null
fixes #233
2014-03-13 23:29:47 +00:00
Shish
208e8de7f0 Merge pull request #382 from DakuTree/patch
New metatags, fixes various PostgreSQL errors & misc tweaks.
2014-03-08 15:33:58 +00:00
Daku
4351dc53c5 make sure all existing exts are converted to lowercase 2014-03-08 15:27:39 +00:00
Daku
258fda188e use different selector rather than hardcoding url 2014-03-08 15:20:50 +00:00
Shish
cf7f79194e Merge branch 'master' into develop 2014-03-07 08:28:03 +00:00
Shish
fa5ad5b77d bump the minimum PHP version, the README was updated but not the version check in the code. Fixes #383 2014-03-07 08:27:34 +00:00
Shish
faec25226a 'x ?: y' is a new PHP thing? 2014-03-06 09:14:03 +00:00
Daku
ddb6d32e29 fix fourm errors under postgresql 2014-03-02 21:59:19 +00:00
Daku
87065d08f6 use elseif 2014-03-02 21:58:45 +00:00
Daku
524ead8344 PostgreSQL doesn't return trimmed results for CHAR columns, so use VARCHAR instead 2014-03-02 21:07:25 +00:00
Daku
a863072bfb DATETIME > SCORE_DATETIME
fixes #354
2014-03-02 18:50:46 +00:00
Daku
53a7e23b64 various autocomplete tweaks
* made limit for /api/internal/tag_list/complete optional (as this breaks the autocomplete on dbs with lots of tags)
* removed delay + minChars = 1 again (due to above change)
* first autocomplete tag is selected by default
* tab now works as a select button
* autocompleted tags will now be followed by a space (so the next tag can be instantly autocompleted)
2014-02-28 02:03:41 +00:00
Daku
e78ddc69f3 fix strict standard errors 2014-02-25 21:51:52 +00:00
Daku
34e90c1cfc updated date/links in footer 2014-02-25 17:56:51 +00:00
Shish
63a71c239c Merge pull request #381 from jgen/master
Automated Tests via Travis-CI
2014-02-25 00:17:02 +00:00
Daku
df29537e74 use default minchars again
the tag list only returns a max of 10 results, and is only called once per autocomplete, making it harder to autocomplete longer tags
2014-02-24 20:07:05 +00:00
Daku
d675827173 order[=|:]random_#### metatag
possible replacement for random_list ext?
2014-02-24 19:54:15 +00:00
Daku
320a92289b have query use EXISTS rather than IN
preferably we would have a table with image tag counts, but this works for now
2014-02-24 01:50:31 +00:00
Daku
790cb0d7a9 [.+]tags metatag for tag_categories
This is nowhere as near as fast as I'd like. Need to find a better query.
2014-02-24 01:40:35 +00:00
Daku
58c5746e9b speed: use isset rather than identical 2014-02-23 22:32:15 +00:00
Daku
bdb3255116 speed: lowercase ext on image insert
avoids having to lowercase every build_thumb_html call
most extensions tend to match against their lowercase versions anyway
2014-02-23 22:27:52 +00:00
jgen
cebc197fba Merge branch 'develop' of git://github.com/shish/shimmie2 2014-02-23 03:11:09 -05:00
jgen
55603a0acb Yay, the Travis tests are working now! Let's increase the number of PHP versions to test with. 2014-02-23 03:07:19 -05:00
jgen
6f59de4d45 Prevent an internal server error occurring when mincount is less than 1. 2014-02-23 02:51:33 -05:00
jgen
2b4ffb1e60 Argh. 2014-02-23 02:18:45 -05:00
jgen
38b494125c Postgres requires you to include the GROUP BY column in the SELECT columns. Should be okay since we don't care about other columns though. 2014-02-23 02:15:19 -05:00
jgen
e2fa6d316e Realpath isn't used anymore. 2014-02-22 23:36:08 -05:00
jgen
cf35bf82a8 Postgres doesn't support the DATETIME field. 2014-02-22 23:30:58 -05:00
jgen
e30520a5ef Fixing typo. 2014-02-22 23:23:23 -05:00
jgen
9892d1f7fd PostgreSQL does not support INDEX() inside the CREATE TABLE method. You must create the index as a separate query. Fortunately MySQL also support this way of doing things as well. 2014-02-22 23:02:11 -05:00
jgen
5effee4812 Remove these indexes as we are creating them manually. 2014-02-22 22:13:26 -05:00
jgen
72ea1acf1d Forgot to remove the comma 2014-02-22 22:06:33 -05:00
jgen
1c6b306ac6 Fixing the installer to work with Postgres. 2014-02-22 22:04:14 -05:00
jgen
668de39626 Remove the debugging. 2014-02-22 19:45:53 -05:00
jgen
6da5f27447 Make sure we install the php5 postgres drivers. 2014-02-22 19:44:50 -05:00
jgen
8bbaf85114 Need to figure out why the Post data differs from what is specified on the command line. 2014-02-22 19:10:18 -05:00
jgen
aca6caa91a More debugging. 2014-02-22 18:52:23 -05:00
jgen
13e844a2f7 Output the DSN. 2014-02-22 18:32:42 -05:00
jgen
c5b265b177 No need to drop the database as the VM is reset every time. 2014-02-22 16:42:12 -05:00
jgen
532557bdc8 Output the DATABASE_DSN when a PDO exception occurs during install. 2014-02-22 16:37:55 -05:00
jgen
ce21fcd17c Enable logging of all PostgreSQL queries. 2014-02-22 16:32:14 -05:00
jgen
8ade958dd1 Working on getting PostgreSQL to work on Travis. 2014-02-22 16:04:04 -05:00
jgen
affb44c153 Enable TravisCI for Postgres. 2014-02-22 15:51:17 -05:00
jgen
7eb2bd9112 Trimming trailing blank space. 2014-02-22 15:42:09 -05:00
jgen
3d1217a1be Trim trailing blank space. 2014-02-22 15:36:52 -05:00
jgen
96358f0bc0 Nevermind, it doesn't work. 2014-02-22 15:33:55 -05:00
jgen
2d0edde2b2 We can check that admin users see the "Add" button though. 2014-02-22 15:25:25 -05:00
jgen
d6f0213a9f Commenting out the alias tests due to consistent failing on TravisCI. 2014-02-22 15:19:06 -05:00
jgen
6efe56eddf Well, lets try this. 2014-02-22 03:10:59 -05:00
jgen
b229e114c2 Dump all the post data on failure. 2014-02-22 03:06:09 -05:00
jgen
f1aed15f32 die doesn't support commas 2014-02-22 02:59:17 -05:00
jgen
cbfc0580f6 Temporary for debugging. 2014-02-22 02:35:56 -05:00
jgen
e6057c656f Re-order the events to ensure InitExtEvent() is fired off both with and without users. 2014-02-22 01:22:14 -05:00
jgen
ba0aef4f30 The Database class should really throw an error here. This will likely break the existing tests. 2014-02-22 00:40:14 -05:00
jgen
74639cd6b2 Missing bracket. 2014-02-21 20:49:17 -05:00
jgen
43f59cc4c0 Fix un-necessary PHP Notices being generated in the error log files. 2014-02-21 20:36:19 -05:00
jgen
e0bf45788e Dump all the mysql related log files. 2014-02-21 20:04:12 -05:00
jgen
bdc89814a3 Changed due to the default log location for MySQL on Ubuntu. 2014-02-21 16:35:49 -05:00
jgen
3fd0142d00 This command line option doesn't work. 2014-02-21 15:37:33 -05:00
jgen
4b36ae2936 Enable logging of all MySQL queries for debugging. 2014-02-21 14:26:44 -05:00
jgen
bbfba90ad4 Merge pull request #380 from DakuTree/patch-3
Misc tweaks/fixes 2 (fixes #349)
2014-02-21 13:40:52 -05:00
jgen
f4f94e6273 Merge pull request #379 from DakuTree/patch-2
Misc tweaks/fixes (fixes/closes issue 17 & 323)
2014-02-21 13:40:15 -05:00
jgen
e8cba99111 Merge pull request #378 from DakuTree/patch
Post Relationships ext + TagTermParseEvent
2014-02-21 13:32:33 -05:00
jgen
9255c861f7 Cleaning up. 2014-02-19 20:50:00 -05:00
jgen
0dd9c19bca Need to escape the dollar signs to prevent BASH from thinking they are vars. 2014-02-19 18:49:56 -05:00
jgen
bbaa9fc6f1 Change the nginx config. 2014-02-19 18:45:28 -05:00
jgen
f3c379b8db Rename the file. 2014-02-19 18:36:38 -05:00
jgen
dd18377f93 First remove the quotes, then the whitespace, then the trailing slash. 2014-02-19 18:26:01 -05:00
jgen
4e0d7d14ae Forgot the brackets. 2014-02-19 18:19:56 -05:00
jgen
eea4d34978 We can actually push the trim functions to when we create the define, less overhead. 2014-02-19 18:17:26 -05:00
jgen
141be8768f Better detection of running on the command line. 2014-02-19 18:10:40 -05:00
jgen
5a49bb59e4 Check that regular users can't add aliases. 2014-02-19 17:03:35 -05:00
jgen
1a7cbc9b2a Check the actual http response code. 2014-02-19 16:40:28 -05:00
jgen
f1a516ef18 Extra debugging when Alias fails. 2014-02-19 16:24:47 -05:00
jgen
7d90c20297 Cleaning up. 2014-02-19 16:16:01 -05:00
jgen
bdbb280064 Fixing the alias test cases. 2014-02-19 04:27:29 -05:00
jgen
61bf9b9b3e Put the database into auto commit mode. 2014-02-19 04:19:17 -05:00
jgen
55f07d0cfd Fix the tag_edit tests. 2014-02-19 04:15:00 -05:00
jgen
a717f5a0bd Remove the auto-run. 2014-02-19 03:42:18 -05:00
jgen
8b0ac0e9c1 The location of the webserver should be configurable in the travis file. 2014-02-19 03:29:12 -05:00
jgen
cb135d4763 Fix the Notice being generated. 2014-02-19 03:11:46 -05:00
jgen
a90f5bed8e This would be a good idea. 2014-02-19 02:50:10 -05:00
jgen
5b9107339a Let's just do it directly. 2014-02-19 02:39:13 -05:00
jgen
b570ef1573 Forgot a bracket. 2014-02-19 02:17:23 -05:00
jgen
2ca74a3b33 Expose methods to allow users of the database class to control when transactions occur. 2014-02-19 02:12:56 -05:00
jgen
6bc9315955 Need to commit the new users to the database. 2014-02-19 01:32:51 -05:00
jgen
0013ef923e We don't want to auto run these tests as we need to load and setup things first. 2014-02-19 01:24:53 -05:00
Daku
fe6b83412f use jQuery instead of JS + change spacing to tabs
this should fix IE/browser problems (see: #349)
2014-02-19 06:23:08 +00:00
Daku
5df6c09da6 reset_image_ids should properly change id for all possible tables 2014-02-19 05:48:20 +00:00
jgen
a5907c5a79 Cleaning up a bit. 2014-02-19 00:40:16 -05:00
jgen
b4819e4b33 We need to create these users before running the tests. 2014-02-19 00:00:18 -05:00
jgen
57b9d418c9 No idea how that got there. 2014-02-18 23:26:42 -05:00
jgen
156b178b35 Let try multiple script lines. 2014-02-18 23:18:59 -05:00
jgen
e7918cc372 Re-work travis config file. 2014-02-18 23:02:10 -05:00
jgen
a210cc9e9e Looks like we are going to have to install, then run the tests. 2014-02-18 22:54:53 -05:00
jgen
4eab3daef4 Typo 2014-02-18 22:42:37 -05:00
jgen
39bec6b666 Wait for the config file to be written. 2014-02-18 22:35:25 -05:00
jgen
62f0be7b92 So, the config file appears to take longer to write out to the file system. 2014-02-18 22:21:14 -05:00
jgen
7d3650555f Remove the catch and just let the exception bring the test down. 2014-02-18 21:35:18 -05:00
jgen
ced3006915 At its finest. 2014-02-18 21:25:08 -05:00
jgen
892b323c4d 21st century debugging 2014-02-18 20:58:08 -05:00
jgen
741eb0f739 I too like to live dangerously. 2014-02-18 20:49:20 -05:00
jgen
7f6672a2d3 Dump out the exception. 2014-02-18 20:34:39 -05:00
jgen
491069540e Output the shimmie config on failure. 2014-02-18 20:08:23 -05:00
jgen
6c8d519859 The order of these includes matter. 2014-02-18 19:37:07 -05:00
jgen
4936432d83 Copy the setup from index.php for All tests. 2014-02-18 19:24:10 -05:00
jgen
0b06477fd0 The tests are set to Auto Run. 2014-02-18 17:36:21 -05:00
jgen
83d034653e Forgot the include. 2014-02-18 17:22:45 -05:00
jgen
2935a87073 Refactoring: Move the installer related test case into another file. 2014-02-18 17:18:25 -05:00
jgen
66f32e63cf These tests for Mass tagger only work with NiceURLs enabled. Commenting them out for now. 2014-02-18 17:02:41 -05:00
jgen
8193b82735 Explicitly call out MariaDB in the README. 2014-02-18 17:02:08 -05:00
jgen
a94006e3c5 Add link to Shish's site. 2014-02-18 17:01:35 -05:00
jgen
f2c26bde1a Update the copyright notices. 2014-02-18 16:59:07 -05:00
jgen
dc8d1b4f49 Check for the correct text if the install succeeds. 2014-02-18 16:58:31 -05:00
jgen
74748d55c0 Use a constant instead. 2014-02-18 16:28:43 -05:00
jgen
cc88a33088 PHP doesn't call it is_empty, but it does have is_null. Yay consistency. 2014-02-18 16:14:57 -05:00
jgen
f12b32b5d9 Fail if there is no database defined. 2014-02-18 15:41:42 -05:00
jgen
689621a7ed The username for the database doesn't seem to be working. 2014-02-18 13:10:19 -05:00
jgen
cfc663de44 Output the HTML when the install fails. 2014-02-18 12:57:32 -05:00
jgen
f1608d39f1 Dump more logs and change the database username. 2014-02-18 12:22:52 -05:00
Daku
daad8d00d5 fix download all images not working 2014-02-18 12:33:10 +00:00
jgen
bc98d284c7 Dump logs on failure. 2014-02-18 03:42:59 -05:00
jgen
222d82b3c3 Test the installer. 2014-02-18 03:32:05 -05:00
Daku
5a4b089a61 fix database_dump regex not working with no password 2014-02-18 08:20:52 +00:00
Daku
857f7e4041 don't force hellban ext + add [Beta] tags 2014-02-18 07:24:26 +00:00
jgen
3564784236 Let's just remove these. 2014-02-18 01:41:40 -05:00
jgen
104711a30e Well lets try this again. 2014-02-18 01:30:27 -05:00
jgen
dcff694c42 Bash arguments start at 1. 2014-02-18 01:11:39 -05:00
jgen
94991bd14a More logging on failure. 2014-02-18 01:02:11 -05:00
jgen
ad3c865716 Explicitly listen on localhost. 2014-02-18 00:50:57 -05:00
jgen
20ce8451f4 Default is a unix socket. 2014-02-18 00:37:07 -05:00
jgen
3e52a1df03 Errors 2014-02-18 00:29:26 -05:00
jgen
3097de938d Need to know the default config for nginx 2014-02-18 00:18:19 -05:00
jgen
ec3804ceb8 Fix the nginx config. 2014-02-18 00:06:58 -05:00
jgen
b56f006b28 Forgot the semicolon. :( 2014-02-17 23:23:20 -05:00
jgen
6a10d66e2d Move the chmod to the script file. 2014-02-17 23:18:00 -05:00
jgen
ac4c0da9a9 Removing unneeded file. 2014-02-17 22:56:32 -05:00
jgen
cc1ed20fc2 Come on Travis 2014-02-17 22:45:03 -05:00
Daku
0065852fc7 lower delay & minChars for autocomplete 2014-02-18 03:32:05 +00:00
jgen
d2349216b5 I can totally spell environment correctly. 2014-02-17 21:37:56 -05:00
jgen
09fe543a4b Push the test environment setup into a separate shell script. 2014-02-17 21:26:06 -05:00
jgen
173e8ef164 These commands need to be done as root. 2014-02-17 20:11:26 -05:00
jgen
5e13a01ffd The config file is in the test folder. 2014-02-17 17:42:40 -05:00
jgen
8974c2cbc7 Change the apt-get commands. 2014-02-17 17:12:01 -05:00
jgen
dbb90465df Derp, forgot a dash. 2014-02-17 16:59:54 -05:00
jgen
58474d4bd3 We actually need a webserver in order to use SimpleTest. 2014-02-17 16:57:46 -05:00
jgen
6f057028b7 Test cases should start with 'test'. 2014-02-17 15:53:22 -05:00
jgen
48932af7c5 Working on testing the installer. 2014-02-17 15:45:42 -05:00
jgen
630f5c2c77 This include is necessary. 2014-02-17 15:03:27 -05:00
jgen
4b6b283f02 Can we assume that the server is 127.0.0.1? 2014-02-17 15:00:04 -05:00
jgen
8a700a34fc No idea why it was set to use the directory "2.Xm" 2014-02-17 14:46:00 -05:00
jgen
fe83e3d343 Adding comments to help explain the simpletest setup. 2014-02-17 13:59:41 -05:00
jgen
c05b4719d6 Fix the tests for the Alias extension. 2014-02-17 01:52:30 -05:00
jgen
807b0c7037 Commenting this out as it assumes Nice URLs, and fails if they are not enabled. 2014-02-17 01:51:40 -05:00
jgen
073813d0ea Fix the warning that these variables aren't defined. 2014-02-17 01:49:16 -05:00
jgen
5f0b7e989e PHP's getopt function on allows single character arguments. 2014-02-17 01:44:54 -05:00
Daku
7c36902537 updated js libs
jQuery 1.7.1 > 1.11.0
jQuery UI custom (highlight effect) 1.10.3 > 1.10.4
jQuery autocomplete 1.1 > 2.4.4
jQuery imgareaselect 0.4 > 1.0.0
jQuery imgnotes 0.2 > 1.0
jQuery tablesorter ? > 2.0.5
jQuery timeago 1.1.1 > 1.3.1
Modernizer 2.5.3 > 2.7.1
2014-02-17 03:02:14 +00:00
jgen
3cb79f0e8a SimpleTest works now. 2014-02-16 19:36:52 -05:00
Daku
c51d1388c0 remove lazyload lib + make random list ext more readable
lazyload is no longer used, see: 7c959813191a8fb4d17ec7b21b33d88c834dd0e6
2014-02-16 23:24:05 +00:00
Daku
1909f9072e use mt_rand() instead of rand()
rand() isn't so random on windows: http://tjl.co/blog/code/followup-php-rand-vs-mt_rand/
mt_rand() is also slightly faster
2014-02-16 04:43:38 +00:00
Daku
c065e38145 adds widget/thumb option to random_thumb ext (for #323)
this code was suggested in the comments, but never actually added
2014-02-16 04:38:58 +00:00
Daku
e8607ab6f2 cleaned up popular_by pages
my old code was so messy :<
2014-02-16 02:29:16 +00:00
Daku
eb16ab09ed order images sorted by score
this is solution for issue #17 (Method to sort by top scored images in numeric_score module)
2014-02-16 02:28:39 +00:00
Daku
16bb38f343 onSearchTermParse regex should be case insensitive 2014-02-15 21:26:31 +00:00
Daku
7b8395b341 have pool description use BBcode 2014-02-15 21:04:26 +00:00
Daku
50c2271ca6 complete rewrite of the update ext (again)
much cleaner than previous version
affixed the [beta] tag since this still has a few issues (mainly error checking/displaying etc.)
2014-02-15 03:44:53 +00:00
jgen
d069c7b50a Argh, PHP. 2014-02-10 00:54:27 -05:00
jgen
0a8abc54df Rename the class. 2014-02-10 00:46:43 -05:00
jgen
8d8ebddadc Change the include to the SimpleTest files. 2014-02-10 00:44:30 -05:00
jgen
c3835ceea9 Playing around with Travis-ci. 2014-02-10 00:33:57 -05:00
Shish
134f9082fd fix typo 2014-02-08 11:53:45 +00:00
Shish
88432215ce make image deletion message work, fixes #368 2014-02-08 11:53:35 +00:00
Daku
b7a4de90e4 updated docs with new metatags 2014-02-05 14:11:13 +00:00
Daku
c4150c15ce make sure source/parent isn't set twice if metatag is used 2014-02-05 14:11:12 +00:00
Daku
6e54580f56 check for permissions 2014-02-05 14:11:03 +00:00
Daku
629f9940c3 thumbnail border-color for post relationships
also removed build_thumb_html from themes (mostly old versions of the default base function)
2014-02-04 18:41:40 +00:00
Daku
13cdb9ba5f Post Relationship ext 2014-02-03 16:55:42 +00:00
Daku
d19e7fff7b vote[=|:](up|down|remove) metatag 2014-02-01 09:54:33 +00:00
Daku
2ffb9e9b8a option for add_post & remove_post to log history
(for use with TagTermParse)
also make navbox show on pool history page
2014-02-01 09:36:23 +00:00
Daku
6a1a57b1a2 make "source[=|:]none" null the source 2014-02-01 09:36:17 +00:00
Daku
b7778b54c9 add TagTermParseEvent for parsing tags during tagging 2014-02-01 09:36:10 +00:00
Shish
711ad775da Merge branch 'master' of https://github.com/shish/shimmie2 2014-01-29 21:38:07 +00:00
Shish
cbe5f27eb6 in-place tag renaming when possible, rather than mass editing thousands of images 2014-01-29 21:37:51 +00:00
jgen
5b0f0b4bef Merge branch 'master' of git://github.com/shish/shimmie2 2014-01-28 20:35:33 -05:00
Shish
d7ce075a43 Merge pull request #367 from DakuTree/patch
Metatag tweaks + Fix for issue 359 & 362.
2014-01-25 06:58:35 -08:00
Shish
851b33ec1d Merge pull request #369 from Diftraku/master
Fix EXIF data throwing a notice when showing an image
2014-01-25 06:54:56 -08:00
Daku
c07dc2e0ab use resolve_aliases rather than resolve_alias 2014-01-16 03:28:23 +00:00
Diftraku
85303d232e Fixing stuff with API output being output twice
Also some code formatting and a redirect from post/show for clients such
as CartonBox so you can actually view the image after opening it in the
browser on the client.
2014-01-15 23:28:40 +02:00
Diftraku
b856b13235 Fix EXIF data throwing a notice when showing an image 2014-01-15 23:06:27 +02:00
Daku
b5f70de496 change source metatag regex to allow searching for URLs 2014-01-14 07:52:45 +00:00
Daku
55ff224ac0 added any/none options to the source/pool metatags 2014-01-14 06:27:12 +00:00
Daku
ce256f5bf4 added pool & pool_by_name search metatags 2014-01-14 06:12:34 +00:00
Daku
ae4da2b410 add option for getMimeType to return list of extensions 2014-01-13 10:03:38 +00:00
Daku
325da11119 artist/comment/numeric_score metatags now work using :
also updated docs
2014-01-13 09:13:56 +00:00
Daku
7d49e21792 readability + moved stuff 2014-01-13 08:19:12 +00:00
Daku
152f5fbf26 add config option for default order 2014-01-13 08:19:11 +00:00
Daku
2c2f27ca64 add order metatag
not too happy with how this works...but it does work
2014-01-13 08:19:11 +00:00
Daku
9cae856df7 use the Content-Disposition header for filename & Content-Type for
extension
if either doesn't exist, it will fallback to using pathinfo
2014-01-13 08:19:01 +00:00
Daku
9f06a5c565 fix search not working properly for aliases to multiple tags
fix issue 359
2014-01-13 08:04:26 +00:00
Shish
952594cdce Merge pull request #365 from HungryFeline/patch-2
Don't silently ignore invalid URLs
2014-01-05 03:14:01 -08:00
Daku
14899e79ad added height & width metatags 2014-01-02 14:10:08 +00:00
Daku
25c286b71f add support for using : as a metatag seperator + updated docs 2014-01-02 14:00:24 +00:00
HungryFeline
7cf79171a8 Update extension.class.php
Fix my previous commit. Also put the results of the tests into variables so we don't need to check them again.
2014-01-01 18:25:28 +01:00
HungryFeline
fde6558a6f Don't silently ignore invalid URLs
Scenario: Providing an invalid url via $_GET (wrong/missing extension or file isn't an image (also happens on download errors))
Behavior before: Silently redirect to index
Behavior after: Display error message
2014-01-01 01:41:11 +01:00
Daku
85880804d2 have theme.php manage block creation
so themes can move/remove if they wish
2013-12-31 08:22:58 +00:00
Shish
2d40fcc92d Merge pull request #364 from DakuTree/patch
Pools ext tweaks/additions.
2013-12-30 02:49:06 -08:00
Daku
c892386bcb remove backticks 2013-12-30 10:36:32 +00:00
Shish
de6ad33e12 Merge pull request #356 from Diftraku/master
Bumping Ouroros API to v0.2: now with XML support and post creation!
2013-12-30 02:20:43 -08:00
Shish
bc2100940d Merge pull request #360 from DakuTree/master
bookmarklet.js support for danbooru2 + fixed most issues with other sites
2013-12-30 02:16:35 -08:00
Shish
7d3d331272 Merge pull request #358 from jgen/master
PHP Notice being generated in comment.php
2013-12-30 02:15:06 -08:00
Daku
fd6f2ddb43 show pool navigation box on pool/view pages 2013-12-30 08:42:29 +00:00
Daku
3dd3101995 added option to set pool & source via tag 2013-12-30 08:15:10 +00:00
Daku
9eaebfd1c2 if cookie doesn't exist, default to "created" 2013-12-30 06:52:17 +00:00
Daku
9511569ed4 added option to edit pool description to pool edit page 2013-12-30 06:51:47 +00:00
Daku
b79a042bdc added option to order pool list by created date/last updated/title/count 2013-12-30 06:47:29 +00:00
Daku
3e240fa78d return error when pool title exists + fix pool error reporting 2013-12-30 06:46:41 +00:00
Daku
59eb6d7ec2 pool title should be unique 2013-12-30 06:46:34 +00:00
jgen
f96a815288 Merge branch 'master' of git://github.com/shish/shimmie2 2013-12-08 13:48:33 -05:00
Daku
453d9a453b bookmarklet now supports danbooru2 + fixed issues with other sites 2013-12-08 15:05:27 +00:00
Shish
63aa4c6330 avoid possibility of infinite loop in bbcode parsing 2013-12-08 10:52:59 +00:00
Shish
caa6d59a87 log base was changed years ago, comment never got updated 2013-12-08 10:52:59 +00:00
jgen
4635f333ae This should have been moved earlier in the fix for Issue 346. 2013-12-07 23:16:20 -05:00
jgen
794cc648be Merge branch 'master' of git://github.com/shish/shimmie2 2013-12-05 19:51:20 -05:00
jgen
1c8d07983e Fix for PHP Notice being generated.
"PHP Notice:  Trying to get property of non-object in /comment/main.php on line 325"
Details: We don't need to try and get comments for images that don't exist. This might also help speed things up a bit as it should eliminate an unnecessary database query.
2013-12-05 19:50:21 -05:00
Shish
c0603d3b4b Merge pull request #357 from jgen/master
Fix for issue 346 - "Only first page of comments is shown"
2013-12-03 16:08:47 -08:00
jgen
a7ab12abdd Fix for issue 346 - "Only first page of comments is shown" 2013-12-03 14:39:12 -05:00
Shish
6dc345085f Merge pull request #353 from jgen/master
Fix for issue 330 - "Tag list does not handle non-english text correctly"
2013-12-03 11:05:27 -08:00
Diftraku
1a25014564 Derp, forgot I was actually giving the post[file] to OuroborosPost, making assert fail for null 2013-12-03 05:51:55 +02:00
Diftraku
095f743d57 Checking if the user can actually create new posts, seems the base DataHandlerExtension doesn't do this. Also forgot to update documentation! 2013-12-03 01:07:27 +02:00
Diftraku
6a4031dfd5 Bumping Ouroros API to v0.2: now with XML support and post creation! 2013-12-03 00:45:07 +02:00
jgen
049108e6ca Fix for issue 330 - "Tag list does not handle non-english text correctly". 2013-11-30 15:19:41 -05:00
Shish
9cdc529c13 Merge pull request #352 from Diftraku/master
Adding basic Ouroboros API
2013-11-30 07:39:35 -08:00
Shish
0a4c8a0931 Merge pull request #351 from jgen/master
Cleaning up the installer
2013-11-30 07:36:17 -08:00
Diftraku
9237c78f55 Adding basic Ouroboros API 2013-11-29 01:09:05 +02:00
jgen
13ea386b0e Fixed some spelling typos. 2013-11-28 00:34:31 -05:00
jgen
a5ea9ed743 Lets comment this rather tricky function. 2013-11-28 00:31:46 -05:00
jgen
bf4d434c86 Fixes for the installer. Display error messages on failures, as well as actually check for tables before installing. 2013-11-28 00:31:09 -05:00
Shish
9430ddc374 Merge pull request #350 from jgen/master
Fixed bookmarklet.js to allow uploading from Gelbooru. (was broken before)
2013-11-27 00:29:40 -08:00
Shish
0e90d58d85 Don't crash if no relevant tags are found, see #324 2013-11-27 08:28:07 +00:00
jgen
9d8bcbce37 Fixed bookmarklet.js to allow uploading from Gelbooru. (was broken before) 2013-11-24 14:54:10 -05:00
Shish
4fd58019e5 Merge pull request #324 from justinbrewer/editcloud_revamp
Tag Edit Cloud improvements
2013-11-24 09:07:43 -08:00
Shish
383fee2dab Merge pull request #345 from tkorpe/master
Fixing rating= search
2013-11-06 01:07:49 -08:00
Thasan
b16be279a8 ext\rating documentation 2013-11-06 00:32:27 +02:00
Thasan
fe84c8d9dd Also checking rating permissions 2013-11-06 00:11:17 +02:00
Thasan
5aa2f823d8 Fixing rating= search 2013-11-05 22:43:46 +02:00
Shish
81d44cbb4f lower tags before checking for mass replacement 2013-10-25 09:26:25 +01:00
Shish
5e556829d7 ignore vim undo files 2013-10-25 09:24:54 +01:00
Shish
ae3ee1fe79 Merge pull request #341 from jgen/master
More small tweaks
2013-10-14 05:29:47 -07:00
Shish
70a67afa5e Merge pull request #342 from NuckChorris/fix-rating-ext
Fix rating circumvention issue
2013-10-14 05:29:16 -07:00
Peter Lejeck
0e4717ecae Unify two regexes used in rating searches, intersect with user privs
Fixes issue with unpriveleged users being able to circumvent the ratings
they could see, just by searching for it.  Also makes code much much
prettier.
2013-10-06 04:38:34 -07:00
jgen
5b1a99d0c5 Fixing missing $. (Thanks Shish!) 2013-10-05 14:28:02 -04:00
jgen
b01ca89220 Some more small speed related tweaks. 2013-10-04 17:53:26 -04:00
jgen
0512858bd4 Some more small tweaks for speed. 2013-10-04 17:17:42 -04:00
Shish
dd58c35f8a Merge pull request #340 from jgen/master
Some small tweaks/changes
2013-10-04 01:17:05 -07:00
jgen
70428b2b5d Its better to pass to the right function. (Side note, this is why you always test your code) 2013-10-03 21:19:19 -04:00
jgen
393a9e60df No need to have HTML in the image thumb title. Derp. 2013-10-03 21:17:15 -04:00
jgen
2d041d468f Adding ability to specify the 'date' in the image thumbnail title (when you hover over the thumbnail). 2013-10-03 21:09:41 -04:00
jgen
234b8c4f51 Removing this line as this file does not exist and simply causes 404 errors. 2013-10-03 21:08:13 -04:00
jgen
1db371345e Merge branch 'master' of git://github.com/shish/shimmie2 2013-10-03 20:33:47 -04:00
Justin Brewer
8c5987a6b2 Fixed tags with quotes killing the Javascript
Of course not.
2013-09-26 10:47:37 -05:00
Justin Brewer
7be1f9e637 Properly escape tags while building query
Could it really be that easy?
2013-09-25 23:39:40 -05:00
Justin Brewer
55bfa4cfd7 Use API calls 2013-09-25 22:59:08 -05:00
Shish
827ce2e0fe merge cron_uploader changes manually 2013-09-23 13:43:39 +01:00
YaoiFox
a7c39105e7 Cron Uploader: Link to install guide & info from board config. And now i'm going to sleep before I want to change more... 2013-09-23 13:41:43 +01:00
YaoiFox
5afeeb0991 Cron Uploader: Fixed documentation & added page /cron_upload with info & install guide if user is admin. 2013-09-23 13:41:36 +01:00
DrudexSoftware
f4b9c8aca2 Cron Uploader: Board Config wasn't getting the right root directory. 2013-09-23 13:41:30 +01:00
DrudexSoftware
455b94c4fa Cron Uploader Extension added that allows automatic image uploading from an image queue using cron jobs. 2013-09-23 13:41:25 +01:00
Shish
835361b272 make tags in the image info box clickable (#336) 2013-09-23 13:09:47 +01:00
Shish
b0b0324ee7 image only button 2013-09-19 19:30:50 +01:00
Shish
ff330d542d Merge pull request #335 from vomitcuddle/image_hash_ban_patch
[image_hash_ban] Fix confusing button label
2013-09-18 08:13:00 -07:00
vomitcuddle
ea21e0657b [image_hash_ban] Fix confusing button label 2013-09-18 09:28:07 +01:00
Shish
67c026e8d8 unique request ID, for associating several log entries with one request 2013-09-12 16:29:38 +01:00
Shish
bf8b085de6 fix being logged in after changing password 2013-09-12 16:29:20 +01:00
Shish
76c7da0161 add hellban support to PMs 2013-09-09 13:47:42 +01:00
Shish
4584bf603a finally got that zero... 2013-09-09 13:46:47 +01:00
Shish
1b73dd4ddc add 'hellbanned' user class, with support from the comments extension 2013-09-09 13:41:08 +01:00
Shish
7c95981319 load thumbnails on demand is more trouble than it's worth 2013-09-08 23:21:06 +01:00
Shish
07304861f3 log IDs of things 2013-08-30 00:19:46 +01:00
Shish
7c140680b7 log args 2013-08-30 00:19:46 +01:00
Shish
12bc4a90f7 core-image -> core_image, for elasticsearch's sake 2013-08-30 00:19:46 +01:00
Justin Brewer
e93785339f Fix another corner case
If $counter == $def_count and the next tag in the list is a used tag, the
tagcloud_extra div would be printed twice, breaking the list.

This solution feels ugly, perhaps there's a better way to do this?
2013-08-26 23:32:44 -05:00
Shish
47abed28c3 Merge pull request #326 from Madoushi90/archive_fixes
handle_archive extension fixes
2013-08-22 04:47:08 -07:00
Justin Brewer
946dec9415 readdir is not guaranteed to be in order
readdir's ordering is filesystem-dependent. On many systems, that means it
might as well be random. People uploading archives will reasonably expect the
files to appear in alphabetical order, so we should make sure they do.
2013-08-11 17:52:38 -05:00
Justin Brewer
305e25f676 Fix incorrect DataUploadEvent creation, triggering an assertion failure
I guess this one slipped through the cracks when $user was made global.
2013-08-11 17:38:50 -05:00
Justin Brewer
8732f1a9ef Extra-switch fixes
Wrong variable name, and don't do the math unless we have to.
Fixed the diplayed number being wrong.
Also fixed a corner case where number of tags in the cloud equals number to
display. Would display "[show 0 more tags]".
2013-08-10 16:19:03 -05:00
Justin Brewer
fa1fb45c1e Polish 2013-08-07 16:08:05 -05:00
Justin Brewer
03e78bd19f Tag Category integration 2013-08-07 15:58:58 -05:00
Justin Brewer
e133138be2 Config option for which tags to ignore 2013-08-07 15:25:11 -05:00
Justin Brewer
905dc2df31 Relevance sorting method 2013-08-07 14:47:44 -05:00
Justin Brewer
d27079ca9f Cleanup build_tag_map
Millions of temp variables. Assuming there's only two sorting methods. Grammar.
[show 100 more problems]
2013-08-07 14:15:04 -05:00
Shish
194b68e046 show when a ban was added, fixes #250 2013-08-05 20:46:54 +01:00
Shish
44deff21a8 Merge branch 'master' of https://github.com/shish/shimmie2 2013-08-05 20:37:40 +01:00
Shish
390db4d6f4 have setup 'Help' link link to github wiki, fixes #303 2013-08-05 20:34:50 +01:00
Shish
4fc1f2f217 save cached config when saving database config, fixes #311 2013-08-05 20:21:57 +01:00
Shish
8bbf7e2171 hide https variable warning 2013-08-05 20:10:54 +01:00
Shish
e50d9c4dbb only sanitise when saving tags - when searching, '*' is important 2013-08-04 18:21:52 +01:00
Shish
8b22652aa0 resolve negative aliases 2013-08-04 18:19:23 +01:00
Shish
2b628a395f resolve_list -> resolve_aliases, to better describe what it actually does 2013-08-04 18:13:50 +01:00
Shish
7e89481105 have Tag::resolve_list always take an array 2013-08-04 18:11:02 +01:00
Shish
9c70d1bd3f ban from mass delete 2013-08-04 18:03:31 +01:00
Shish
f98e0d1927 remove debugging logs from js 2013-08-04 02:20:28 +01:00
Shish
2ef76708c5 untag table titles 2013-08-04 02:20:28 +01:00
Shish
097655e7f7 Merge pull request #322 from HungryFeline/patch-3
use upload_tmp_dir as temp place, see #297
2013-07-21 01:48:41 -07:00
HungryFeline
e9b0553876 use upload_tmp_dir as temp place, see #297
We changed a line like this some time ago in upload/main
2013-07-21 02:10:17 +02:00
Shish
1c5d717d27 put untags in the database 2013-07-13 09:35:34 +01:00
Shish
00e297c7d4 Merge pull request #317 from HungryFeline/patch-1
Let the admin define how much space should be free
2013-07-07 03:28:44 -07:00
Shish
39753fe7a9 Finish the process of removing $query from thumb links. (Forgot to remove $query from the thumb links) 2013-07-07 10:34:19 +01:00
HungryFeline
33712ee5c2 Merge pull request #1 from HungryFeline/patch-2
Update main.php
2013-07-07 00:17:30 -07:00
HungryFeline
17efb92b4e Update main.php 2013-07-07 09:13:14 +02:00
HungryFeline
9edbaf2ec7 Let the user define how much space should be free 2013-07-07 09:08:50 +02:00
Shish
b9a00e4c28 this doesn't work 2013-07-07 03:24:21 +01:00
Shish
bc253eef04 load list of voters in-place with ajax 2013-07-06 10:42:25 +01:00
Shish
48e40a6712 trim whitespace when adding aliases 2013-07-06 10:42:25 +01:00
Shish
589bda1b00 Merge pull request #316 from HungryFeline/patch-5
Fixing csv upload form
2013-07-06 01:53:29 -07:00
Shish
a212013d9d Merge pull request #314 from vomitcuddle/rm_dup_ext
Remove duplicate arrow key navigation extension
2013-07-06 01:50:38 -07:00
HungryFeline
03b0c82887 Better CSV validation
When uploading a CSV, check every entry before executing the INSERT. The checks are the same as with the normal add except that no errors are shown.
2013-07-06 00:33:31 +02:00
HungryFeline
db64370815 Fixing csv upload form
The 2nd argument of make_form is the method. Also, I don't see any point in defining the $multipart variable. Just true as argument is enough. C&P mistake?!
2013-07-06 00:12:25 +02:00
Shish
47c1b5d094 move query-string-passing to JS rather than embedding in the HTML, so that the HTML can be commonised and cached better 2013-07-05 22:32:16 +01:00
Shish
e4bfa7df70 further breakdown for page stats 2013-07-05 22:32:16 +01:00
Shish
30886b3a69 cache lookup debugging 2013-07-05 22:32:16 +01:00
Shish
47447ee982 generate rss thumbs in a different function, with cache 2013-07-05 22:32:16 +01:00
vomitcuddle
b8cf3562b0 Remove duplicate arrow key navigation extension 2013-07-04 01:30:48 +01:00
Shish
dbc0548bdc Merge pull request #310 from HungryFeline/patch-4
Checking if mod_headers is loaded before trying to set Cache-Control headers
2013-07-03 11:39:31 -07:00
HungryFeline
a87aedcef9 Checking if mod_headers is loaded before trying to set Cache-Control headers 2013-07-03 10:21:21 +02:00
Shish
3febf0e443 Merge pull request #309 from HungryFeline/patch-3
Don't check the certificate when transloading
2013-07-02 05:41:48 -07:00
Shish
79ff722d10 Merge pull request #307 from HungryFeline/patch-1
Allow transload from urls with query string
2013-07-02 05:36:38 -07:00
Shish
912b0b40d9 Merge pull request #308 from HungryFeline/patch-2
Look for the source in _GET and not in _POST when uploading via _GET
2013-07-02 05:34:56 -07:00
HungryFeline
65fcbcb9ba Actually set the cookie prefix
When COOKIE_PREFIX was not shm (the default) the script tried to read cookies prefixed with the new prefix but the script was setting the old default prefix from before the constants were implemented.
2013-07-02 06:46:54 +02:00
HungryFeline
616aa3300a Don't check the certificate when transloading
This allows the download of images via https even if the cert is self-signed.
2013-07-02 04:44:27 +02:00
HungryFeline
71117cf8fa Look for the source in _GET and not in _POST when uploading via _GET 2013-07-02 04:35:18 +02:00
HungryFeline
cbd927ffa2 Allow transload of image urls with querystrings
And remove them from the filename and fileext
2013-07-02 04:31:06 +02:00
Shish
e547b1362f Update theme.php 2013-07-01 09:45:32 +01:00
Shish
e6146d82ea Update theme.php 2013-07-01 08:05:30 +01:00
Shish
752d36476a Merge pull request #300 from DanielOaks/dan2
Danbooru2 theme, tag categories extension, tag list update/rework
2013-06-30 06:05:23 -07:00
Daniel Oaks
2a51a9d6ba Fixed things Shish pointed out 2013-06-23 16:57:25 +10:00
Daniel Oaks
bf0146cc41 Danbooru2 theme, initial tag categories extension, tag list update/rework 2013-06-23 09:42:15 +10:00
Shish
1264ec5b35 Merge pull request #298 from aki--aki/master
Add reset_image_ids from shish/shimmie2-utils to the admin extension
2013-06-22 06:31:55 -07:00
Aki Jenkinson
e5e12220b3 Made reset image IDs button only appear on MySQL 2013-06-22 18:30:10 +12:00
Shish
5c13fcac62 resolve aliases on input as well, so the target shows in the url 2013-06-20 22:59:42 +01:00
Shish
4a2c47459c Chatbox 2013-06-19 20:59:59 +01:00
Drudex Software
a3486f9b29 Bulk Remove 2013-06-19 20:59:37 +01:00
Drudex Software
668ebdcb2e Arrowkey Navigation 2013-06-19 20:56:19 +01:00
Daniel Oaks
06dd72fe66 Danbooru2 theme 2013-06-19 20:09:22 +01:00
Shish
c64a43eb2b Merge pull request #301 from vomitcuddle/dapi
Danbooru API changes
2013-06-19 11:37:50 -07:00
vomitcuddle
35f7ccb3ba Include thumbnail dimensions 2013-06-18 22:31:24 +01:00
vomitcuddle
911caadc84 Support Gelbooru API page offsets 2013-06-18 22:31:16 +01:00
vomitcuddle
619382d1b9 Include image count and offset in root tag 2013-06-18 22:31:09 +01:00
Shish
b6235695da unique IDs for 'posted' search params 2013-06-16 12:20:53 +01:00
Shish
c8b12a0459 update comment 2013-06-15 19:57:15 +01:00
Shish
e7ddd2960b use upload_tmp_dir as temp place for transloading, see #297 2013-06-15 19:54:00 +01:00
Zarek Jenkinson
f2d0bc6cfa Merge branch 'master' of https://github.com/shish/shimmie2 2013-06-13 23:47:47 +12:00
Shish
2859d58191 regex and trimming updates for danbooru and lite themes 2013-06-13 12:42:30 +01:00
Shish
df2f90016f trim leading slashes more efficiently 2013-06-13 12:20:27 +01:00
Shish
19c0868b2c sqlite got case sensitive by default at some point - lowercase strings for comparing them 2013-06-13 10:34:47 +01:00
Shish
e01b9b22c4 don't try to set tags / source if they aren't sent 2013-05-30 14:19:03 +01:00
Shish
51ab897655 need a tiny bit of jquery UI for flashing... 2013-05-30 10:18:23 +01:00
Shish
b562de3e94 redirect to referrer after adding image hash ban 2013-05-30 10:21:11 +01:00
Zarek Jenkinson
ac764d0e9c protect reset_image_ids & move protected checkbox 2013-05-29 23:00:01 +12:00
Zarek Jenkinson
344e8afd98 Add reset_image_ids from shish/shimmie2-utils to the admin extension 2013-05-29 19:59:41 +12:00
Shish
261dee5a7c hacky source history 2013-05-18 13:52:11 +01:00
Shish
c530e41335 new jquery.timeago with cutoff 2013-05-18 13:46:27 +01:00
Shish
5a9f69afbe Numeric type and aspect keeping - #281 2013-05-18 10:20:08 +01:00
Shish
f4a49b2fb1 Current size as default for resize, if default is otherwise 0 - #281 2013-05-18 10:20:08 +01:00
jgen
cc95eb5deb Some more small speed tweaks. 2013-05-13 20:40:01 -04:00
Shish
dcb51ba4ff Merge pull request #290 from jgen/master
Fix for Image View error
2013-05-12 07:29:08 -07:00
jgen
bf49c7ce19 Fix for Enabling Image View count extension causes fatal error. 2013-05-11 22:49:36 -04:00
Shish
18b94a801d allow custom themelets to call TagEditTheme::format_source() 2013-05-07 09:13:26 +01:00
Shish
7701758959 log old source in event log when changing source 2013-05-07 09:13:03 +01:00
Shish
44e9714d88 logging for delete user 2013-04-28 07:48:42 +01:00
Shish
bb0ebc5d42 first version of pm_trigger ext 2013-04-26 17:27:22 +01:00
Shish
a103672a6d Merge pull request #288 from opensorcerer/nicetest-over-https
Allow Nice URLs when served via HTTPS
2013-04-24 00:02:18 -07:00
Bill Garrett
861e04d85f Use ['HTTPS'] to construct the nicetest URL, allowing Shimmie to use nice URLs when served over HTTPS 2013-04-23 21:48:32 -07:00
Shish
8d77d251a5 Merge pull request #285 from kenshiros/fixvoteslistlinks
fix numeric_score to use make_link for user profile links
2013-04-07 05:33:51 -07:00
Shish
7c0666ad9a Merge pull request #284 from kenshiros/fixhomelinks
fix pools link on home page to point at site://pool and not site://pools
2013-04-07 05:33:31 -07:00
kenshiros
455bf795de fix numeric_score to use make_link for user profile links 2013-04-07 07:30:38 -04:00
kenshiros
519cff827f fix pools link on home page to point at site://pool and not site://pools 2013-04-07 07:06:20 -04:00
Shish
2e5a51bc8d Merge pull request #282 from DrudexSoftware/master
Random Images List & Modified Page Title
2013-03-16 12:50:03 -07:00
DrudexSoftware
014975884b - Custom page titles rewritten, can now only use website title as prefix or suffix. This is done so admins can use this feature without too much difficulty or without it being bugged. 2013-03-10 09:38:06 +01:00
DrudexSoftware
a89d417546 a few more minor changes 2013-03-10 08:56:58 +01:00
DrudexSoftware
a4b29f0254 - Setup block added for Random Images List 2013-03-10 08:43:50 +01:00
DrudexSoftware
8e6fe9f7e3 - Random list page now has a title<br>
- Custom Html Headers extension now allows admin to set title prefix & suffix for all pages
2013-03-10 08:24:15 +01:00
DrudexSoftware
e54c8439cb Random List now displays actual random images...
Also arrowkey navigation extension now supports Random List extension
2013-03-10 07:53:09 +01:00
DrudexSoftware
8251133270 Amount of randoms displayed = images per page / 2 2013-03-10 07:35:35 +01:00
DrudexSoftware
fccd5d249d Merge branch 'master' of https://github.com/shish/shimmie2.git 2013-03-10 07:01:28 +01:00
DrudexSoftware
41709e419b Added extension allowing users to view a random page at www.website.com/random 2013-03-10 07:00:24 +01:00
Shish
3175e605da Merge branch 'master' of https://github.com/shish/shimmie2 2013-03-07 03:25:23 +00:00
Shish
e49e90f9ab fff, php 2013-03-07 03:23:35 +00:00
Shish
7ff0ae51a1 fixup wildcards 2013-03-03 18:34:27 +00:00
Shish
e013d86b56 disallow aliases to other aliases 2013-03-03 18:32:56 +00:00
Shish
d5bc06835d reserve block slot #0 for site announcements 2013-03-03 18:27:06 +00:00
Shish
72a82c7487 saner wildcard handling, and only handle '*' 2013-03-03 18:26:47 +00:00
DrudexSoftware
7155804505 Cleaned up code in some of my extensions
Removed function testImage in ext/chatbox/test.php because it no longer applies (there is a cache now)
2013-03-01 17:29:28 +01:00
Shish
a55c5efc4f Merge pull request #280 from DrudexSoftware/master
Image View Counter, Arrow Navigation, and Custom HTML Headers
2013-02-25 05:50:27 -08:00
Drudex Software
a03e4d5102 Custom HTML Headers added
I may also expand it to allow headers specific pages later
2013-02-25 01:59:43 +01:00
DrudexSoftware
cf9e0eae8c oops, forgot something 2013-02-24 01:26:48 +01:00
DrudexSoftware
937eefcb50 /post/view now also supports things like /post/view/4#search=tag1 2013-02-24 01:05:50 +01:00
DrudexSoftware
c55c984cfc also correctly handle post/list without a page ID given 2013-02-23 23:34:32 +01:00
DrudexSoftware
069b7ee9ea arrowkey navigation on post/list now supports tags 2013-02-23 23:26:08 +01:00
DrudexSoftware
13d8b0831e Fixed problems from notes by shish
Added arrowkey navigator to post/list as well
arrowkey navigator also works with tags on post/list now
2013-02-23 22:17:20 +01:00
DrudexSoftware
dfd7157cd2 arrow key navigation through images
allows visitors to use left-right keyboard keys to navigate to different images using post/next/(id) and post/prev/(id)
2013-02-23 08:00:59 +01:00
DrudexSoftware
b88f7a4d6b image_view_counter: also saving with user ID with addview() for later use 2013-02-23 07:17:58 +01:00
DrudexSoftware
93a431dce7 image_view_counter:
- now displays views below image (only if it's okay with the admin settings)
- comment changed
2013-02-23 06:47:06 +01:00
DrudexSoftware
1a38a152e6 image_view_counter:
- added view counter
- only adds view every hour from same person
- no way to display to user/admin yet (todo)
2013-02-23 06:21:00 +01:00
Shish
636e540674 Merge pull request #278 from DrudexSoftware/master
Upgraded Sitemap & User extensions
2013-02-19 23:56:28 -08:00
DrudexSoftware
4c170110e8 Moved sitemap file to /data/cache/sitemap.xml
Sitemap creation time now determined by filemtime instead of via config
2013-02-20 00:31:52 +01:00
Drudex Software
4b4d08c854 Merge pull request #12 from shish/master
Merged shimmie2 changes
2013-02-14 20:03:39 -08:00
Drudex Software
e64f1f0bcb Update ext/sitemap/main.php
- Added setup block to choose between generating full sitemap or smaller sitemap
- Added sitemap caching, regenerating at most every day
2013-02-15 04:31:31 +01:00
Drudex Software
52893d0f04 Update ext/sitemap/main.php
- Added setup block to choose between generating full sitemap or smaller sitemap
2013-02-15 03:04:58 +01:00
Drudex Software
c90689a63e Update ext/user/main.php
- Added board config option for user login forwarding
- Set user_loginshowprofile default to 0
- now only applies when user is on a page containing post/ in its url (to avoid weird messages)
2013-02-15 02:22:09 +01:00
Drudex Software
55b0919465 Update ext/user/main.php
Also don't return to same page if page is "user_admin/login". 
Otherwise it can occur if user first failed to log in with the correct credentials
2013-02-14 23:54:23 +01:00
Drudex Software
62ad4c0ecb Update ext/user/main.php
Site owners can now choose in board config what page users will go to when logging in:
- Previous page (default)
- My Profile (old default)
2013-02-14 22:34:43 +01:00
Shish
9fbc31dbf2 Merge branch 'master' of https://github.com/shish/shimmie2 2013-02-11 08:16:06 +00:00
Shish
9d5fed4a94 sneaky update 2013-02-11 08:15:28 +00:00
DrudexSoftware
59ec209c4a Caching not working properly for various reasons, removed for now 2013-02-11 08:43:06 +01:00
Drudex Software
b7b8786f18 Remade the sitemap extension
- now makes full sitemap with specific with higher priorities for newer/more popular content
- Sitemap is cached for 6 hours
several things still need to be added
2013-02-10 17:41:19 +01:00
Shish
69cdece64a Merge pull request #277 from DrudexSoftware/master
Google Analytics
2013-02-09 07:47:54 -08:00
Shish
5056ac2789 make sortable tables slightly more obvious 2013-02-09 10:36:58 +00:00
Shish
ffdca16b30 read-ness column on PM table, for sorting 2013-02-09 10:35:59 +00:00
Shish
4366d3e3e9 Merge branch 'master' of https://github.com/shish/shimmie2 2013-02-09 10:28:14 +00:00
Shish
e69b72dfd3 only use >> quotes at the start of lines 2013-02-09 10:27:37 +00:00
Shish
4fe7855a70 nicer formatting for tag history log 2013-02-09 10:27:25 +00:00
Shish
279c3dd61a max-width 100% for images inside comments 2013-02-09 10:26:55 +00:00
Shish
ff39a1df91 explicit sorting for PM list 2013-02-09 10:26:30 +00:00
Drudex Software
c0e51b5f0c unnecessary code removed 2013-02-08 22:18:19 +01:00
DrudexSoftware
c709e12440 Google Analytics Integration
allows users to optionally use google analytics tracking in shimmie by
simply entring the analytics id in the board config.
2013-02-07 21:41:01 +01:00
Shish
dbfd747dd3 remove more slashes 2013-02-06 08:36:30 +00:00
Shish
6dc39fe39f show some extra help if there are no DB engines available 2013-02-06 08:24:01 +00:00
Shish
c9491ad714 Get SQLite:scoreql_to_sql to return something 2013-01-31 08:07:19 +00:00
Shish
06832bae48 Actually, that causes warnings in other versions; use the static version 2013-01-24 10:52:35 +00:00
Shish
7768e94c7c Merge pull request #275 from DrudexSoftware/patch-1
Invalid operator causing error
2013-01-24 02:47:04 -08:00
Stijn Raeymaekers
160bdb3553 Replaced invalid operator
Caused all images and some other pages to get the following error:
syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM
2013-01-24 11:08:34 +01:00
Shish
471bc1805a escape title too 2013-01-04 22:40:16 +00:00
Shish
1664f932fe Merge pull request #269 from pachuco/master
Comment extension
2013-01-04 14:43:52 -08:00
Shish
17cd89455d Merge pull request #270 from Diftraku/master
Fix PHP errors about Strict Standards
2012-12-31 13:08:58 -08:00
Shish
78442e96c3 add daily cache breakage to gravatars 2012-12-30 18:08:58 +00:00
Shish
6a5b92502b Log user creation with created user 2012-12-30 16:04:05 +00:00
Shish
da11cc5d05 better error handling for report image 2012-12-30 16:00:55 +00:00
Shish
35a3986c35 compatible declrations for danbooru theme 2012-12-30 15:46:25 +00:00
Diftraku
09a18b8710 Fix errors about PHP complaining of strict standards, Pools extension needs an overhaul due to display errors being different in BaseThemelet than it is in the ext/pools/main.php 2012-12-26 20:35:00 +02:00
pachuco
15de0f7732 Added page title. 2012-12-19 01:18:30 +02:00
Shish
9ef73748de Merge pull request #268 from pachuco/master
Comment extension fix
2012-12-18 11:03:17 -08:00
pachuco
ef8c60f032 Things were fetched from the currently logged user instead of the viewed one. My bad. 2012-12-18 17:29:07 +02:00
Shish
31cfb00b15 readme update 2012-12-11 00:10:27 +00:00
Shish
cebebcd975 Merge branch 'master' of https://github.com/shish/shimmie2 2012-12-10 21:28:44 +00:00
Shish
5a3b4dae49 normalise usernames 2012-12-10 21:28:41 +00:00
Shish
80bdc565dd truncate filenames on insert 2012-11-24 20:01:14 +00:00
Shish
acbbad2f80 Merge pull request #266 from pachuco/master
Comment extension fixes
2012-11-24 11:46:43 -08:00
pachuco
cfe3fabd6d *Index link fixed in uaser and site comment navigators
*No bloody idea why I added the assertions in display_all_user_comments();
*Commented out some doohickey thing that did something and borke other stuff
*Added index link and paginator in user comment list
2012-11-24 20:36:00 +02:00
pachuco
f6f2638003 *Total number of pages correctly calculated for user comments
*Page number sanity checks for both user comments and site comments
2012-11-24 20:28:49 +02:00
Shish
f30e34861d Merge pull request #264 from green-ponies/master
Fix for Issue 263 - Reversed ReCaptcha keys
2012-11-04 01:00:25 -08:00
green-ponies
ef44d6ccf3 Fix for issue 263 - ReCAPTCHA private and public keys reversed. 2012-11-03 13:49:10 -04:00
Shish
fff8318f1f offset after limit 2012-10-17 00:05:48 +01:00
Shish
d8c670eaa6 move yet more stuff out of index.php 2012-10-16 23:05:11 +01:00
Shish
b141632785 some mock objects, for testing (coming soon, maybe) 2012-10-16 22:58:47 +01:00
Shish
38fea60056 handle CLI_LOG_LEVEL not being set 2012-10-16 22:58:24 +01:00
Shish
073b21aaa1 split up event listener loading, so parts can be called individually 2012-10-16 22:31:50 +01:00
velocity37
44bcf6f322 Handle MP3: Use getID3() for validation, Fuzzy MP3 Detection + ID3 Reader 2012-10-16 21:42:21 +01:00
velocity37
afd75f1134 getID3: Boolean to force MP3 check
Apparently MP3 encoders can muck up an MP3 file pretty bad. getID3 can
read these files, but it takes extra work and thus is only done if the
file carries a relevant extension. Shimmie's files don't have an
extension, so a boolean was added to analyze() in order to force this
check.
2012-10-16 21:42:20 +01:00
velocity37
c9bacdf56d Handle Video: Flash/HTML5 video player
Requires getID3() and Jaris FLV Player
2012-10-16 21:42:20 +01:00
Shish
6cbf1b7865 requirement bump, 5.3 should be common by now 2012-10-16 21:24:39 +01:00
velocity37
1a55d997e3 Bulk_add_csv: add note about custom thumbnails 2012-10-16 19:52:08 +01:00
velocity37
c42cc57977 Bulk_add_csv: add custom thumbnail support 2012-10-16 19:36:55 +01:00
velocity37
52718b0590 util.inc: Default to static extension lookup
Magic can return invalid results for video files, breaking their playback. Default to the static extension lookup to avoid this problem but still return application/octet-stream if nothing works.
2012-10-16 18:56:39 +01:00
velocity37
0663bf54a0 Grr... 2012-10-16 18:55:43 +01:00
velocity37
1bd6f9f0ba Update core/util.inc.php 2012-10-16 18:55:38 +01:00
velocity37
fcdf784acc Update static thumb + bug fix
Add videos to static thumb and make Shimmie compare lowercase extensions.
2012-10-16 18:55:23 +01:00
velocity37
412dcd05af Add video MIME types 2012-10-16 18:55:00 +01:00
Shish
112130b8ad the start of full per-user comment history 2012-10-15 21:48:55 +01:00
Shish
0ae4ef4110 get source from POST[source], if set 2012-10-15 20:21:09 +01:00
Shish
14da124fdb this should be here, I presume 2012-10-15 19:05:25 +01:00
Shish
c6e5a4046a Merge branch 'master' of github.com:shish/shimmie2 2012-10-15 19:04:09 +01:00
Shish
3e516e143c cleaner image hash ban handling, allow specifying hash or ID 2012-10-15 19:03:59 +01:00
Shish
936c1a65ca fix off-by-ones 2012-10-06 15:59:13 +01:00
Shish
1acf839902 Merge pull request #260 from pachuco/master
Forum extension updates.
2012-10-06 07:58:11 -07:00
Shish
6abf65c062 option for making samefags public 2012-10-03 21:28:29 +01:00
Shish
e00299b376 autofocus for search box on front page 2012-09-25 23:20:52 +01:00
Shish
f59ea82e0e Merge pull request #259 from Agasa/patch-1
Update MassTagger redirect to reload page
2012-09-25 10:51:06 -07:00
pachuco
d333345a2c *Cosmetic changes to thread posts.
*Return link to forum index in thread page.
*Fixed pesky undefined $oe in theme.php.
*Reordered paginator for accessability
*Numbered posts.
2012-09-25 20:37:20 +03:00
pachuco
1db92e6f2a Some CSS love. 2012-09-25 20:34:50 +03:00
pachuco
9cebef5a50 *Inexistent threads are no longer accesible.
*Pages outside index cut to last one(thread index and threads).
*Return to last page in thread after posting reply.
2012-09-25 20:33:56 +03:00
Shish
e3d385aee5 Merge branch 'master' of github.com:shish/shimmie2 2012-09-24 22:36:28 +01:00
Shish
ac812b4a2a danbooru api updates 2012-09-24 22:36:09 +01:00
Agasa
af9d5ed185 Update MassTagger redirect to reload page
Modified Mass Tagger extension to make it so that instead of redirecting to post/list it reloads the page instead, unburdening the user from a click on Back and manually reloading.
2012-09-24 12:02:22 +03:00
Shish
65ce0ff4e4 check filename extension by lowercased version of itself 2012-09-22 23:24:19 +01:00
Shish
06426bdfe6 when checking mime types, use the filename.ext from the DB, not the (non-existent) .ext on disk 2012-09-22 23:15:25 +01:00
Shish
bbe2b1dc46 don't need to load the file into memory if we're responding with '304 not modified'... 2012-09-22 23:10:29 +01:00
Shish
5c30eab6ad merge Agasa's mass tagger work (replace tags mode, as opposed to the default of adding) 2012-09-22 22:58:56 +01:00
Shish
01aacdb19c admin function for fixing tag cases 2012-09-03 12:06:11 +01:00
Shish
c57801bce7 wildcards aren't regular tags either 2012-09-03 11:13:08 +01:00
Shish
da0bb2d65e Merge branch 'master' of github.com:shish/shimmie2 2012-09-03 10:49:35 +01:00
Shish
11340ca6da regen thumbs permission hack 2012-08-25 20:28:34 +01:00
Shish
35ad3057bd Merge pull request #257 from velocity37/master
merge bulk_add_csv extension
2012-08-25 09:56:05 -07:00
velocity37
86402f681c bulk_add_csv: bulk_add with metadata
Imports a CSV with:
path,tags,source,rating

It is basically bulk_add with an fgetcsv loop instead of a foreach glob.
I wanted an easy way to import tagged data from other sources.
2012-08-23 17:03:46 -07:00
Shish
902d0adbd7 Merge pull request #256 from velocity37/master
Resolution for Issue #254 (path to `convert` binary accidentally hardcoded)
2012-08-22 01:00:24 -07:00
velocity37
624468851e Only show thumb_convert_path if using ImageMagick 2012-08-21 19:00:25 -07:00
velocity37
a2e1edded5 Actually use thumb_convert_path
The check would always fail even on Windows, resulting in thumb_convert_path never being used. See:
https://github.com/shish/shimmie2/issues/254
2012-08-21 18:51:20 -07:00
velocity37
10fadb27d1 Make thumb_convert_path not Windows-specific 2012-08-21 18:46:34 -07:00
velocity37
d154b38680 Add thumb_convert_path to Board Config 2012-08-21 18:42:14 -07:00
Shish
99e431598c streamline rotation 2012-08-18 21:12:44 +01:00
Shish
7ec7ca623b rotate ext from Agasa 2012-08-18 21:10:35 +01:00
Shish
0d2c78939e streamline resize image process 2012-08-18 21:09:40 +01:00
Shish
216dada347 somehow ImageIOTheme wasn't extending Themelet, and things mostly worked... 2012-08-18 20:56:35 +01:00
Shish
b538fd4e27 make the forum not *completely* broken... 2012-08-18 20:43:55 +01:00
Shish
82d74b5719 report SCore errors as errors... 2012-08-18 20:36:54 +01:00
Shish
c1fbf128c9 hackily fix non-niceurl'ed tests 2012-08-18 20:31:13 +01:00
Shish
27c06f02ce remove debugging thing 2012-08-18 20:29:45 +01:00
Shish
0fd8157e5e part way towards running tests from the CLI 2012-08-18 20:27:12 +01:00
Shish
89a0308007 a terrible hack, because subclasses can't add optional extra params... 2012-08-18 19:55:52 +01:00
Shish
68e2ca798e make futaba:view:display_page make ext:view:display_page 2012-08-18 19:48:51 +01:00
Shish
5ddd358d5f don't user build_thumb_html statically 2012-08-18 19:47:57 +01:00
Shish
7dd929e5a8 remove a whole load of event->user variables that weren't used -- things should be using global user 2012-08-18 19:45:39 +01:00
Shish
f3aad43fa3 merge the two List Controls blocks 2012-08-18 19:23:11 +01:00
Shish
6fcdf4717e Merge branch 'js-classes' 2012-08-15 21:26:56 +01:00
Shish
5022d2b965 more tags, more opera glitch workarounds 2012-08-15 21:19:51 +01:00
Shish
887c746e35 mass tagger -> shm-*, specify what data tags shm-thumb has, now it works with all themes 2012-08-15 21:05:08 +01:00
Shish
6394cd8100 make shm-thumb use consistent across themes, with data-tags and data-post-id 2012-08-15 20:42:56 +01:00
Shish
8c39c29528 make tag hiding a little more consistent 2012-08-15 20:22:25 +01:00
Shish
ec16baecfe make some over-eager CSS more controlled 2012-08-15 20:20:26 +01:00
Shish
d22d8e033e playing with eclipse... 2012-08-15 20:11:10 +01:00
Shish
d6f15527e2 playing with eclipse... 2012-08-15 20:08:16 +01:00
Shish
4470d937a4 handle_pixel -> shm-* 2012-08-15 19:46:51 +01:00
Shish
f5d787efbf while we're here, fix the blotter in general... 2012-08-15 19:46:36 +01:00
Shish
61e9909327 hardcoded config too (unrelated) 2012-08-15 18:31:28 +01:00
Shish
fd4e801756 convert blotter to use ui-* and shm-* 2012-08-15 11:13:25 +01:00
Shish
6717639caa update readme to clarify ui-* and shm-* 2012-08-15 10:43:28 +01:00
Shish
fed253dc18 sort bans by add date 2012-07-28 12:07:55 +01:00
Shish
112422a2ca time and timestamp are incompatible 2012-07-28 12:07:46 +01:00
Shish
7e8cf48b08 and trigger the update... 2012-07-28 11:59:11 +01:00
Shish
acf285bd68 IPs are variable length 2012-07-28 11:57:22 +01:00
Shish
49a1ae94b6 ban IP as an inet type 2012-07-28 11:56:55 +01:00
Shish
442e1a0de1 stricter data for ban adding 2012-07-28 11:42:07 +01:00
Shish
0a73424679 Merge branch 'master' of github.com:shish/shimmie2 2012-07-28 11:12:40 +01:00
Shish
ee52d582c4 image replacing simplicity 2012-07-28 11:12:05 +01:00
Shish
a9bfd1bcdc break 2012-07-24 20:38:10 +01:00
Shish
8b83b90b53 make tags required in the HTML form 2012-06-27 11:13:36 +01:00
Shish
53ad7d4a2f more sqlite, and indexes 2012-06-27 00:08:51 +01:00
Shish
685c1248d9 use PDO to check PDO drivers 2012-06-26 23:51:05 +01:00
Shish
02a286cba0 sqlite updates 2012-06-26 23:46:38 +01:00
Shish
3a8269baf3 lots of installer improvements 2012-06-26 23:37:29 +01:00
Shish
b85387208e remove non-functional repair console 2012-06-26 22:21:45 +01:00
Shish
19ca8b7b9f Merge branch 'master' of github.com:shish/shimmie2 2012-06-26 20:24:58 +01:00
Shish
951da261e8 Use the same 'create new image' block for post/list and oekaki pages 2012-06-26 20:00:38 +01:00
Shish
7391dcf0b6 configuarable oekaki size, and more consistent creation 2012-06-26 19:58:35 +01:00
Shish
80673b73ec re-arrange oekaki bits for better permissions 2012-06-26 19:47:40 +01:00
Shish
756de7b023 Merge pull request #243 from pachuco/master
Oekaki extension tweaks
2012-06-26 11:43:18 -07:00
Shish
cd693630a3 width: 100% + shadows fails 2012-06-26 19:41:20 +01:00
Shish
b82fd133fc Merge branch 'master' of github.com:shish/shimmie2 2012-06-26 19:38:53 +01:00
Shish
a3d77c6f42 firefox ignores file upload width attribute :| 2012-06-26 19:38:22 +01:00
Shish
287cea7959 >> = quote... 2012-06-26 19:38:02 +01:00
pachuco
6453c767ef Added ability to input resolution both in post index and oekaki page, as well as a little warning about losing canvas contents. 2012-06-24 23:25:46 +03:00
pachuco
6b6f60faca Authors of updated iconset credited.
Icons taken from https://github.com/CatoTH/ChibiPaint-Animexx-Edition
2012-06-24 23:22:14 +03:00
pachuco
e1c04facdb Users with insufficient privileges are now unable to use oekaki. 2012-06-24 23:21:22 +03:00
pachuco
4535299b2a Replaced icons with something actually bearable.
Updated internal readme to credit authors.
Taken from https://github.com/CatoTH/ChibiPaint-Animexx-Edition
2012-06-24 23:19:06 +03:00
Shish
f9008a70a6 refactoring fail 2012-06-24 07:13:53 +01:00
Shish
0bc4ebafa5 somewhat excessive caching for index pages 2012-06-24 02:07:59 +01:00
Shish
8dfeb7cda3 longer cache timings, with better invalidation 2012-06-24 01:57:12 +01:00
Shish
6c4fd0d14d remove ancient non-comment 2012-06-24 01:49:53 +01:00
Shish
9ca07641d2 build_thumb_html can't be called statically 2012-06-24 01:13:24 +01:00
Shish
a640de5283 a little extra error checking 2012-06-24 01:11:07 +01:00
Shish
38f7555d98 get rid of direct references to Database's engine 2012-06-24 00:57:55 +01:00
Shish
d313cea8a7 create the DB connection on demand; some pages don't require one at all thanks to caching 2012-06-24 00:50:13 +01:00
Shish
98e7acc529 let database class handle transactions, index.php need not know about database->db 2012-06-24 00:27:53 +01:00
Shish
dd505e556e avoid using magic internal database->db value 2012-06-24 00:27:21 +01:00
Shish
bcfcca7cda checking the engine can trigger a database init... 2012-06-24 00:25:47 +01:00
Shish
008cc4253b wibble database / cache connection functions, make them repeatable, and set persistant DB connections as an option 2012-06-23 23:57:34 +01:00
Shish
63437d1e09 cache user sessions 2012-06-23 23:38:28 +01:00
Shish
1c5bf478d9 initialise global _execs, else we get 'page used queries' when we want 'page used 0 queries' 2012-06-23 23:37:41 +01:00
Shish
d87e53ab90 cache reported image count 2012-06-23 23:28:38 +01:00
Shish
5a93e650c1 make sure featured image object is fully populated before caching, else it still lazy-queries tags each time 2012-06-23 23:25:53 +01:00
Shish
ee1162309a block caching 2012-06-23 23:21:04 +01:00
Shish
d3e2653002 some cached values are often zero (eg unread PMs), 'zero' is still a cache hit though, only 'false' is miss 2012-06-23 23:17:17 +01:00
Shish
1daf0416bd optionally run jpegoptim on newly created thumbs 2012-06-23 23:02:02 +01:00
Shish
f61b4b3fcb proper setup bits for livefeed 2012-06-22 22:30:22 +01:00
Shish
16f1c06734 more livefeed tweaks - put the URL first for consistency 2012-06-22 20:04:12 +01:00
Shish
6181d59975 that slash needs to be there >_< 2012-06-22 20:02:53 +01:00
Shish
64e2b88b0a remove theme dependencies on private _get_internal_parts 2012-06-22 19:37:23 +01:00
Shish
2e897e097d live feed tweaks 2012-06-22 19:32:41 +01:00
Shish
8dcd56e0ec udp log_net, because it's faster and more elegant 2012-06-22 19:19:13 +01:00
Shish
7ed7f4bbb8 Merge branch 'master' of github.com:shish/shimmie2 2012-06-22 19:14:15 +01:00
Shish
9291601a30 livefeed extension 2012-06-22 19:13:57 +01:00
Shish
029719cafe statsd ext, for trivially making graphs of site activity 2012-06-22 18:34:23 +01:00
Shish
4b2cba4642 bulk add uses tags from filename if filename is '123 - tag1 tag2 tag3.ext' folder name otherwise 2012-06-21 09:11:26 +01:00
Shish
e7df4edbee slashes are removed already 2012-06-21 09:07:52 +01:00
Shish
3c710289b7 fff, javascript object model >_< 2012-06-21 09:06:58 +01:00
Shish
2a18da523c the define wibbling for hiphop seems to work now 2012-06-18 01:23:31 +01:00
Shish
fd5b4c5910 more re-organisation for create_monolith / hiphop friendliness 2012-06-18 01:06:36 +01:00
Shish
553aad8a36 cache vars do nothing 2012-06-18 00:51:09 +01:00
Shish
78d701dd53 move stuff about to make create_monolith simpler 2012-06-18 00:45:32 +01:00
Shish
8173a7cafd clear event listener cache when exts change 2012-06-18 00:29:41 +01:00
Shish
a9b3ffe0eb start of command-line ext manager 2012-06-18 00:27:58 +01:00
Shish
930de7fc8c remove built-in full-page caching; it doesn't do much any more, and everything it does do can be done better by Varnish 2012-06-18 00:07:12 +01:00
Shish
5519c3a320 lots of tidying and removal of duplicate code 2012-06-18 00:00:21 +01:00
Shish
082e6fa31b nice, event-based command line support; try 'php index.php' for info 2012-06-17 20:06:02 +01:00
Shish
427175983b don't pre-populate the ban lists with fake data, things get confused 2012-06-17 18:50:46 +01:00
Shish
1065a1915d bold for *unread* PMs 2012-06-10 18:40:30 +01:00
Shish
4a6f9f3245 say who a PM is from 2012-06-10 18:18:50 +01:00
Shish
f8cc785560 flashes for user changes 2012-06-10 18:13:22 +01:00
Shish
97d280501c image ban messages 2012-06-10 17:57:57 +01:00
Shish
8c1e495ab7 better IP Ban logging 2012-06-10 17:49:17 +01:00
Shish
b0c46b63ca make flash a common element 2012-06-10 17:46:47 +01:00
Shish
239e028662 flash messages everywhere~ 2012-06-10 04:21:03 +01:00
Shish
61754b8dfe add a type arg, even if it's not used yet 2012-06-10 01:05:03 +01:00
Shish
37b3dd60b6 flash_message(str) function and support in themes 2012-06-09 17:00:25 +01:00
Shish
a38aaba328 postgres has microseconds, which is overkill to display 2012-06-09 16:25:51 +01:00
Shish
82cc77817a delete comments by IP 2012-06-09 16:08:29 +01:00
Shish
5186601805 wording 2012-06-09 16:08:15 +01:00
Shish
b1e5ba794b update comment styles for new classes 2012-06-09 15:52:28 +01:00
Shish
2542051bc4 show user classes 2012-06-09 15:25:07 +01:00
Shish
16f557954d slow down crawlers to 1/3 hit/s; nginx will 503 if more than 1/2 hit/s 2012-06-05 13:12:58 +01:00
Shish
8de1bd0c56 different classes for different types of comment list 2012-06-05 13:11:15 +01:00
Shish
e89c14c72c Merge branch 'master' of github.com:shish/shimmie2 2012-06-05 13:09:31 +01:00
Shish
d167d25942 don't add navblock if there is already one 2012-05-28 10:03:17 +01:00
Shish
5d431b9b03 apparently 'false' is an object with length, and the length is 1. Fuck PHP. 2012-05-28 09:55:55 +01:00
Shish
a809e95ff1 anonymous functions are php 5.3, shimmie aims for 5.2.6 2012-05-28 09:46:16 +01:00
Shish
4f8332f3f7 use the global and well tested make_http() rather than local hostify() 2012-05-23 11:50:04 +01:00
Shish
545f5234bb less nesting, 25% speed gain 2012-05-16 07:36:20 +01:00
704 changed files with 47432 additions and 52242 deletions

8
.dockerignore Normal file
View File

@ -0,0 +1,8 @@
vendor
.git
*.phar
data
images
thumbs
*.sqlite
Dockerfile

21
.editorconfig Normal file
View File

@ -0,0 +1,21 @@
# In retrospect I'm less of a fan of tabs for indentation, because
# while they're better when they work, they're worse when they don't
# work, and so many people use terrible editors when they don't work
# that everything is inconsistent... but tabs are what Shimmie went
# with back in the 90's, so that's what we use now, and we deal with
# the pain of making sure everybody configures their editor properly
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,css,php}]
charset = utf-8
indent_style = space
indent_size = 4

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.php text eol=lf

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
---
**Server Software**
(You can get all these stats from `http://<your site>/system_info`)
- Shimmie version:
- Database: [mysql, postgres, ...]
- Web server: [apache, nginx, ...]
**Client Software (please complete the following information)**
- Device: [e.g. iphone, windows desktop]
- Browser: [e.g. chrome, safari]
**What steps trigger this bug**
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
**What did you expect to happen?**
A clear and concise description of what you expected to happen.
**What actually happened?**
If applicable, add screenshots to help explain your problem.

View File

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
---
**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.

62
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,62 @@
name: Create Release
on:
push:
tags:
- 'v*'
jobs:
build:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Get version from tag
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
- name: Test version in sys_config
run: grep ${{ steps.get_version.outputs.VERSION }} core/sys_config.php
- name: Build
run: |
composer install --no-dev
cd ..
tar cvzf shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz shimmie2
zip -r shimmie2-${{ steps.get_version.outputs.VERSION }}.zip shimmie2
- name: Create Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Shimmie ${{ steps.get_version.outputs.VERSION }}
body: Automated release from tags
draft: false
prerelease: false
- name: Upload Zip
id: upload-release-asset-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.zip
asset_content_type: application/zip
- name: Upload Tar
id: upload-release-asset-tar
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ../shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
asset_name: shimmie2-${{ steps.get_version.outputs.VERSION }}.tgz
asset_content_type: application/gzip

95
.github/workflows/test_and_publish.yml vendored Normal file
View File

@ -0,0 +1,95 @@
name: Test & Publish
on:
push:
pull_request:
schedule:
- cron: '0 2 * * 0' # Weekly on Sundays at 02:00
jobs:
test:
name: PHP ${{ matrix.php }} / DB ${{ matrix.database }}
strategy:
max-parallel: 3
fail-fast: false
matrix:
php: ['7.3']
database: ['pgsql', 'mysql', 'sqlite']
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up PHP
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php }}
coverage: pcov
extensions: mbstring
- name: Set up database
run: |
mkdir -p data/config
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
sudo systemctl start postgresql ;
psql --version ;
sudo -u postgres psql -c "SELECT set_config('log_statement', 'all', false);" -U postgres ;
sudo -u postgres psql -c "CREATE USER shimmie WITH PASSWORD 'shimmie';" -U postgres ;
sudo -u postgres psql -c "CREATE DATABASE shimmie WITH OWNER shimmie;" -U postgres ;
fi
if [[ "${{ matrix.database }}" == "mysql" ]]; then
sudo systemctl start mysql ;
mysql --version ;
mysql -e "SET GLOBAL general_log = 'ON';" -uroot -proot ;
mysql -e "CREATE DATABASE shimmie;" -uroot -proot ;
fi
if [[ "${{ matrix.database }}" == "sqlite" ]]; then
sudo apt update && sudo apt-get install -y sqlite3 ;
sqlite3 --version ;
fi
- name: Check versions
run: php -v && composer -V
- name: Validate composer.json and composer.lock
run: composer validate
- name: Install PHP dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Install shimmie
run: php index.php
- name: Run test suite
run: |
if [[ "${{ matrix.database }}" == "pgsql" ]]; then
export TEST_DSN="pgsql:user=shimmie;password=shimmie;host=127.0.0.1;dbname=shimmie"
fi
if [[ "${{ matrix.database }}" == "mysql" ]]; then
export TEST_DSN="mysql:user=root;password=root;host=127.0.0.1;dbname=shimmie"
fi
if [[ "${{ matrix.database }}" == "sqlite" ]]; then
export TEST_DSN="sqlite:data/shimmie.sqlite"
fi
vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover=data/coverage.clover
- name: Upload coverage
run: |
wget https://scrutinizer-ci.com/ocular.phar
php ocular.phar code-coverage:upload --format=php-clover data/coverage.clover
publish:
name: Publish
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- uses: actions/checkout@master
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: shish2k/shimmie2
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
cache: ${{ github.event_name != 'schedule' }}
buildoptions: "--build-arg RUN_TESTS=false"

86
.gitignore vendored
View File

@ -2,4 +2,88 @@ backup
data
images
thumbs
!lib/images
*.phar
*.sqlite
*.cache
.devcontainer
trace.json
#Composer
composer.phar
composer.lock
/vendor/
# Created by http://www.gitignore.io
### Windows ###
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must ends with two \r.
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
### Linux ###
*~
# KDE directory preferences
.directory
### vim ###
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
### PhpStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
## Directory-based project format
.idea/
# if you remove the above rule, at least ignore user-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# and these sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
## File-based project format
*.ipr
*.iws
*.iml
## Additional for IntelliJ
out/
# generated by mpeltonen/sbt-idea plugin
.idea_modules/
# generated by JIRA plugin
atlassian-ide-plugin.xml
# generated by Crashlytics plugin (for Android Studio and Intellij)
com_crashlytics_export_strings.xml

View File

@ -3,42 +3,65 @@
</IfModule>
<FilesMatch "\.(sqlite|sdb|s3db|db)$">
Deny from all
<IfModule mod_authz_host.c>
Require all denied
</IfModule>
<IfModule !mod_authz_host.c>
Deny from all
</IfModule>
</FilesMatch>
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteEngine On
# rather than link to images/ha/hash and have an ugly filename,
# we link to images/hash/tags.ext; mod_rewrite splits things so
# that shimmie sees hash and the user sees tags.ext
RewriteRule ^_images/([0-9a-f]{2})([0-9a-f]{30}).*$ images/$1/$1$2 [L]
RewriteRule ^_thumbs/([0-9a-f]{2})([0-9a-f]{30}).*$ thumbs/$1/$1$2 [L]
RewriteRule ^_(images|thumbs)/([0-9a-f]{2})([0-9a-f]{30}).*$ data/$1/$2/$2$3 [L]
# any requests for files which don't physically exist should be handled by index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L]
</IfModule>
<IfModule mod_php5.c>
php_flag register_globals 0
php_flag magic_quotes_gpc 0
php_flag magic_quotes_runtime 0
</IfModule>
DefaultType image/jpeg
<IfModule mod_expires.c>
ExpiresActive On
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|css|js))$">
Header set Cache-Control "public, max-age=2629743"
<FilesMatch "([0-9a-f]{32}|\.(gif|jpe?g|png|webp|css|js))$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=2629743"
</IfModule>
ExpiresDefault "access plus 1 month"
</FilesMatch>
#ExpiresByType text/html "now"
#ExpiresByType text/plain "now"
</IfModule>
<ifmodule mod_deflate.c>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/x-javascript application/javascript
</ifmodule>
</IfModule>
#EXT: handle_pixel
AddType image/jpeg jpg jpeg
AddType image/gif gif
AddType image/png png
AddType image/webp webp
#EXT: handle_ico
AddType image/x-icon ico ani cur
#EXT: handle_flash
AddType application/x-shockwave-flash swf
#EXT: handle_mp3
AddType audio/mpeg mp3
#EXT: handle_svg
AddType image/svg+xml svg svgz
#EXT: handle_video
AddType video/x-flv flv
AddType video/mp4 f4v f4p m4v mp4
AddType audio/mp4 f4a f4b m4a
AddType video/ogg ogv
AddType video/webm webm

19
.php_cs.dist Normal file
View File

@ -0,0 +1,19 @@
<?php
$finder = PhpCsFixer\Finder::create()
->exclude('ext/amazon_s3/lib')
->exclude('vendor')
->exclude('data')
->in(__DIR__)
;
return PhpCsFixer\Config::create()
->setRules([
'@PSR2' => true,
//'strict_param' => true,
'array_syntax' => ['syntax' => 'short'],
])
->setFinder($finder)
;
?>

19
.scrutinizer.yml Normal file
View File

@ -0,0 +1,19 @@
imports:
- javascript
- php
filter:
excluded_paths: [ext/*/lib/*,ext/tagger/script.js,tests/*]
build:
nodes:
analysis:
tests:
before:
- mkdir -p data/config
- cp tests/defines.php data/config/shimmie.conf.php
override:
- php-scrutinizer-run
tools:
external_code_coverage: true

49
Dockerfile Normal file
View File

@ -0,0 +1,49 @@
# "Build" shimmie (composer install - done in its own stage so that we don't
# need to include all the composer fluff in the final image)
FROM debian:stable-slim AS app
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
COPY composer.json composer.lock /app/
WORKDIR /app
RUN composer install --no-dev
COPY . /app/
# Tests in their own image. Really we should inherit from app and then
# `composer install` phpunit on top of that; but for some reason
# `composer install --no-dev && composer install` doesn't install dev
FROM debian:stable-slim AS tests
RUN apt update && apt install -y composer php7.3-gd php7.3-dom php7.3-sqlite3 php-xdebug imagemagick
COPY composer.json composer.lock /app/
WORKDIR /app
RUN composer install
COPY . /app/
ARG RUN_TESTS=true
RUN [ $RUN_TESTS = false ] || (\
echo '=== Installing ===' && mkdir -p data/config && INSTALL_DSN="sqlite:data/shimmie.sqlite" php index.php && \
echo '=== Smoke Test ===' && php index.php get-page /post/list && \
echo '=== Unit Tests ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml && \
echo '=== Coverage ===' && ./vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-text && \
echo '=== Cleaning ===' && rm -rf data)
# Build su-exec so that our final image can be nicer
FROM debian:stable-slim AS suexec
RUN apt-get update && apt-get install -y --no-install-recommends gcc libc-dev curl
RUN curl -k -o /usr/local/bin/su-exec.c https://raw.githubusercontent.com/ncopa/su-exec/master/su-exec.c; \
gcc -Wall /usr/local/bin/su-exec.c -o/usr/local/bin/su-exec; \
chown root:root /usr/local/bin/su-exec; \
chmod 0755 /usr/local/bin/su-exec;
# Actually run shimmie
FROM debian:stable-slim
EXPOSE 8000
HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1
ENV UID=1000 \
GID=1000
RUN apt update && apt install -y curl \
php7.3-cli php7.3-gd php7.3-pgsql php7.3-mysql php7.3-sqlite3 php7.3-zip php7.3-dom php7.3-mbstring \
imagemagick zip unzip && \
rm -rf /var/lib/apt/lists/*
COPY --from=app /app /app
COPY --from=suexec /usr/local/bin/su-exec /usr/local/bin/su-exec
WORKDIR /app
CMD ["/bin/sh", "/app/tests/docker-init.sh"]

339
LICENSE.txt Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
{signature of Ty Coon}, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

43
README.md Normal file
View File

@ -0,0 +1,43 @@
```
_________.__ .__ .__ ________
/ _____/| |__ |__| _____ _____ |__| ____ \_____ \
\_____ \ | | \ | | / \ / \ | |_/ __ \ / ____/
/ \| Y \| || Y Y \| Y Y \| |\ ___/ / \
/_______ /|___| /|__||__|_| /|__|_| /|__| \___ >\_______ \
\/ \/ \/ \/ \/ \/
```
# Shimmie
[![Test & Publish](https://github.com/shish/shimmie2/workflows/Test%20&%20Publish/badge.svg)](https://github.com/shish/shimmie2/actions)
[![Code Quality](https://scrutinizer-ci.com/g/shish/shimmie2/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/shish/shimmie2/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/shish/shimmie2/?branch=master)
# Documentation
* [Install straight on disk](https://github.com/shish/shimmie2/wiki/Install)
* [Install in docker container](https://github.com/shish/shimmie2/wiki/Docker)
* [Upgrade process](https://github.com/shish/shimmie2/wiki/Upgrade)
* [Basic settings](https://github.com/shish/shimmie2/wiki/Settings)
* [Advanced config](https://github.com/shish/shimmie2/wiki/Advanced-Config)
* [Developer notes](https://github.com/shish/shimmie2/wiki/Development-Info)
* [High-performance notes](https://github.com/shish/shimmie2/wiki/Performance)
# Contact
Email: webmaster at shishnet.org
Issue/Bug tracker: https://github.com/shish/shimmie2/issues
# Licence
All code is released under the [GNU GPL Version 2](https://www.gnu.org/licenses/gpl-2.0.html) unless mentioned otherwise.
If you give shimmie to someone else, you have to give them the source (which
should be easy, as PHP is an interpreted language...). If you want to add
customisations to your own site, then those customisations belong to you,
and you can do what you want with them.

View File

@ -1,123 +0,0 @@
_________.__ .__ .__ ________
/ _____/| |__ |__| _____ _____ |__| ____ \_____ \
\_____ \ | | \ | | / \ / \ | |_/ __ \ / ____/
/ \| Y \| || Y Y \| Y Y \| |\ ___/ / \
/_______ /|___| /|__||__|_| /|__|_| /|__| \___ >\_______ \
\/ \/ \/ \/ \/ \/
_________________________________________________________________________
Shimmie Alpha
~~~~~~~~~~~~~
If you're reading this on github and looking for the stable version, go
to the top of the page -> switch branches -> pick a stable branch. To do
similarly with a git clone, "git checkout -b my_2.X origin/branch_2.X"
This code is for people who want to write extensions compatible with the
next version of shimmie. You can run a production site with it if you're
feeling brave, but it's not recommended.
If there is a feature here, and not in the stable branch, that's probably
because the feature doesn't work yet :P
Requirements
~~~~~~~~~~~~
MySQL 5.1+ (with experimental support for PostgreSQL 8+ and SQLite 3)
PHP 5.2.6+
GD or ImageMagick
Installation
~~~~~~~~~~~~
1) Create a blank database
2) Unzip shimmie into a folder on the web host
3) Visit the folder with a web browser
4) Enter the location of the database
5) Click "install". Hopefully you'll end up at the welcome screen; if
not, you should be given instructions on how to fix any errors~
Upgrade from 2.3.X
~~~~~~~~~~~~~~~~~~
- Backup your current files and database!
- Unzip into a clean folder
- Copy across the images, thumbs, and data folders
- Move old/config.php to new/data/config/shimmie.conf.php
- Edit shimmie.conf.php to use the new database connection format:
OLD: $database_dsn = "<proto>://<username>:<password>@<host>/<database>";
NEW: define("DATABASE_DSN", "<proto>:user=<username>;password=<password>;host=<host>;dbname=<database>");
The rest should be automatic~
If there are any errors with the upgrade process, "in_upgrade=true" will
be left in the config table and the process will be paused for the admin
to investigate. Deleting this config entry and refreshing the page should
continue the upgrade from where it left off.
Upgrade from earlier versions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I very much recommend going via each major release in turn (eg, 2.0.6
-> 2.1.3 -> 2.2.4 -> 2.3.0 rather than 2.0.6 -> 2.3.0). While the basic
database and file formats haven't changed *completely*, it's different
enough to be a pain.
Custom Configuration
~~~~~~~~~~~~~~~~~~~~
Various aspects of Shimmie can be configured to suit your site specific needs
via the file "data/config/shimmie.conf.php" (created after installation).
Take a look at "core/sys_config.inc.php" for the available options that can
be used.
Custom User Classes
~~~~~~~~~~~~~~~~~~~
User classes can be added to or altered by placing them in
`data/config/user-classes.conf.php`. For example, one can override the
default anonymous "allow nothing" permissions like so:
new UserClass("anonymous", "base", array(
"create_comment" => True,
"edit_image_tag" => True,
"edit_image_source" => True,
"create_image_report" => True,
));
For a moderator class, being a regular user who can delete images and
comments:
new UserClass("moderator", "user", array(
"delete_image" => True,
"delete_comment" => True,
));
For a list of permissions, see core/userclass.class.php
Development Info
~~~~~~~~~~~~~~~~
http://shimmie.shishnet.org/doc/
Please tell me if those docs are lacking in any way, so that they can be
improved for the next person who uses them
Contact
~~~~~~~
#shimmie on Freenode -- IRC
webmaster at shishnet.org -- email
https://github.com/shish/shimmie2/issues -- bug tracker
Licence
~~~~~~~
All code is GPLv2 unless mentioned otherwise; ie, if you give shimmie to
someone else, you have to give them the source (which should be easy, as PHP
is an interpreted language...). If you want to add customisations to your own
site, then those customisations belong to you, and you can do what you want
with them.

67
composer.json Normal file
View File

@ -0,0 +1,67 @@
{
"name": "shish/shimmie2",
"description": "A tag-based image gallery",
"type" : "project",
"license" : "GPL-2.0-or-later",
"minimum-stability" : "dev",
"repositories" : [
{
"type" : "package",
"package" : {
"name" : "ifixit/php-akismet",
"version" : "1.1",
"source" : {
"url" : "https://github.com/iFixit/php-akismet.git",
"type" : "git",
"reference" : "fd4ff50eb577457c1b7b887401663e91e77625ae"
}
}
}
],
"require" : {
"php" : "^7.3",
"ext-pdo": "*",
"ext-json": "*",
"ext-fileinfo": "*",
"flexihash/flexihash" : "^2.0.0",
"ifixit/php-akismet" : "1.*",
"google/recaptcha" : "~1.1",
"dapphp/securimage" : "3.6.*",
"shish/eventtracer-php" : "^2.0.0",
"shish/ffsphp" : "^1.0.0",
"shish/microcrud" : "^2.0.0",
"shish/microhtml" : "^2.0.0",
"enshrined/svg-sanitize" : "0.13.*",
"bower-asset/jquery" : "1.12.*",
"bower-asset/jquery-timeago" : "1.5.*",
"bower-asset/mediaelement" : "2.21.*",
"bower-asset/js-cookie" : "2.1.*"
},
"require-dev" : {
},
"suggest": {
"ext-memcache": "memcache caching",
"ext-memcached": "memcached caching",
"ext-apc": "apc caching",
"ext-redis": "redis caching",
"ext-dom": "some extensions",
"ext-curl": "some extensions",
"ext-ctype": "some extensions",
"ext-json": "some extensions",
"ext-zip": "self-updater extension, bulk import/export",
"ext-zlib": "anti-spam",
"ext-xml": "some extensions",
"ext-gd": "GD-based thumbnailing"
},"replace": {
"bower-asset/jquery": ">=1.11.0",
"bower-asset/inputmask": ">=3.2.0",
"bower-asset/punycode": ">=1.3.0",
"bower-asset/yii2-pjax": ">=2.0.0"
}
}

461
composer.lock generated Normal file
View File

@ -0,0 +1,461 @@
{
"_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": "ca096500833679c5fa819eb5cfbd1d80",
"packages": [
{
"name": "bower-asset/jquery-timeago",
"version": "v1.5.4",
"source": {
"type": "git",
"url": "git@github.com:rmm5t/jquery-timeago.git",
"reference": "180864a9c544a49e43719b457250af216d5e4c3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rmm5t/jquery-timeago/zipball/180864a9c544a49e43719b457250af216d5e4c3a",
"reference": "180864a9c544a49e43719b457250af216d5e4c3a"
},
"require": {
"bower-asset/jquery": ">=1.4"
},
"type": "bower-asset",
"license": [
"MIT"
]
},
{
"name": "bower-asset/js-cookie",
"version": "v2.1.4",
"source": {
"type": "git",
"url": "git@github.com:js-cookie/js-cookie.git",
"reference": "8b70250875f7e07445b6a457f9c2474ead4cba44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/js-cookie/js-cookie/zipball/8b70250875f7e07445b6a457f9c2474ead4cba44",
"reference": "8b70250875f7e07445b6a457f9c2474ead4cba44"
},
"type": "bower-asset",
"license": [
"MIT"
]
},
{
"name": "bower-asset/mediaelement",
"version": "2.21.2",
"source": {
"type": "git",
"url": "git@github.com:johndyer/mediaelement.git",
"reference": "394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/johndyer/mediaelement/zipball/394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce",
"reference": "394db3b4a2e3f5f7988cacdefe62ed973bf4a3ce"
},
"type": "bower-asset",
"license": [
"MIT"
]
},
{
"name": "dapphp/securimage",
"version": "3.6.8",
"source": {
"type": "git",
"url": "https://github.com/dapphp/securimage.git",
"reference": "5fc5953c4ffba1eb214cc83100672f238c184ca4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dapphp/securimage/zipball/5fc5953c4ffba1eb214cc83100672f238c184ca4",
"reference": "5fc5953c4ffba1eb214cc83100672f238c184ca4",
"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": {
"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"
],
"time": "2020-05-30T09:43:22+00:00"
},
{
"name": "enshrined/svg-sanitize",
"version": "0.13.3",
"source": {
"type": "git",
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
"reference": "bc66593f255b7d2613d8f22041180036979b6403"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/bc66593f255b7d2613d8f22041180036979b6403",
"reference": "bc66593f255b7d2613d8f22041180036979b6403",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*"
},
"require-dev": {
"codeclimate/php-test-reporter": "^0.1.2",
"phpunit/phpunit": "^6"
},
"type": "library",
"autoload": {
"psr-4": {
"enshrined\\svgSanitize\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "Daryll Doyle",
"email": "daryll@enshrined.co.uk"
}
],
"description": "An SVG sanitizer for PHP",
"time": "2020-01-20T01:34:17+00:00"
},
{
"name": "flexihash/flexihash",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/pda/flexihash.git",
"reference": "497ba5782606d998f8ab0b4e5942e3a799bec018"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pda/flexihash/zipball/497ba5782606d998f8ab0b4e5942e3a799bec018",
"reference": "497ba5782606d998f8ab0b4e5942e3a799bec018",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"satooshi/php-coveralls": "~1.0",
"squizlabs/php_codesniffer": "^2.3",
"symfony/config": "^2.0.0",
"symfony/console": "^2.0.0",
"symfony/filesystem": "^2.0.0",
"symfony/stopwatch": "^2.0.0",
"symfony/yaml": "^2.0.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Flexihash\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paul Annesley",
"email": "paul@annesley.cc",
"homepage": "http://paul.annesley.cc"
},
{
"name": "Dom Morgan",
"email": "dom@d3r.com",
"homepage": "https://d3r.com"
}
],
"description": "Flexihash is a small PHP library which implements consistent hashing",
"homepage": "https://github.com/pda/flexihash",
"time": "2016-04-22T21:03:23+00:00"
},
{
"name": "google/recaptcha",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/google/recaptcha.git",
"reference": "79ccf652575e138d51742d04e78a5adbb9892f9d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/google/recaptcha/zipball/79ccf652575e138d51742d04e78a5adbb9892f9d",
"reference": "79ccf652575e138d51742d04e78a5adbb9892f9d",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.2.20|^2.15",
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^4.8.36|^5.7.27|^6.59|^7.5.11"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"psr-4": {
"ReCaptcha\\": "src/ReCaptcha"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "Client library for reCAPTCHA, a free service that protects websites from spam and abuse.",
"homepage": "https://www.google.com/recaptcha/",
"keywords": [
"Abuse",
"captcha",
"recaptcha",
"spam"
],
"time": "2020-05-21T08:56:25+00:00"
},
{
"name": "ifixit/php-akismet",
"version": "1.1",
"source": {
"type": "git",
"url": "https://github.com/iFixit/php-akismet.git",
"reference": "fd4ff50eb577457c1b7b887401663e91e77625ae"
},
"type": "library"
},
{
"name": "shish/eventtracer-php",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/shish/eventtracer-php.git",
"reference": "0328454c58d240667f004f3efd863b8ef521ba2a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shish/eventtracer-php/zipball/0328454c58d240667f004f3efd863b8ef521ba2a",
"reference": "0328454c58d240667f004f3efd863b8ef521ba2a",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Shish",
"email": "webmaster@shishnet.org",
"homepage": "http://shishnet.org",
"role": "Developer"
}
],
"description": "An API to write JSON traces as used by the Chrome Trace Viewer",
"homepage": "https://github.com/shish/eventtracer-php",
"time": "2020-09-20T11:46:44+00:00"
},
{
"name": "shish/ffsphp",
"version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/shish/ffsphp.git",
"reference": "82d45b2691da11c82a28f85b07752334a06b8a8e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shish/ffsphp/zipball/82d45b2691da11c82a28f85b07752334a06b8a8e",
"reference": "82d45b2691da11c82a28f85b07752334a06b8a8e",
"shasum": ""
},
"require": {
"php": "^7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"FFSPHP\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Shish",
"email": "webmaster@shishnet.org",
"homepage": "http://shishnet.org",
"role": "Developer"
}
],
"description": "A collection of workarounds for stupid PHP things",
"homepage": "https://github.com/shish/ffsphp",
"time": "2020-09-20T13:15:53+00:00"
},
{
"name": "shish/microcrud",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/shish/microcrud.git",
"reference": "1d55c02c405fc75ad6a0e952e9333552bef492f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shish/microcrud/zipball/1d55c02c405fc75ad6a0e952e9333552bef492f0",
"reference": "1d55c02c405fc75ad6a0e952e9333552bef492f0",
"shasum": ""
},
"require": {
"ext-pdo": "*",
"php": "^7.3",
"shish/ffsphp": "^1.0",
"shish/microhtml": "^2.0.2"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
"MicroCRUD\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Shish",
"email": "webmaster@shishnet.org",
"homepage": "http://shishnet.org",
"role": "Developer"
}
],
"description": "A minimal CRUD generating library",
"homepage": "https://github.com/shish/microcrud",
"keywords": [
"crud",
"generator"
],
"time": "2020-09-20T13:10:42+00:00"
},
{
"name": "shish/microhtml",
"version": "v2.0.2",
"source": {
"type": "git",
"url": "https://github.com/shish/microhtml.git",
"reference": "d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/shish/microhtml/zipball/d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947",
"reference": "d5c393d5a47bb3b3febdfde9ebf6e91cb9b41947",
"shasum": ""
},
"require": {
"php": "^7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"files": [
"src/microhtml.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Shish",
"email": "webmaster@shishnet.org",
"homepage": "http://shishnet.org",
"role": "Developer"
}
],
"description": "A minimal HTML generating library",
"homepage": "https://github.com/shish/microhtml",
"keywords": [
"generator",
"html"
],
"time": "2020-09-20T13:05:01+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.3",
"ext-pdo": "*",
"ext-json": "*",
"ext-fileinfo": "*"
},
"platform-dev": [],
"plugin-api-version": "1.1.0"
}

667
core/basepage.php Normal file
View File

@ -0,0 +1,667 @@
<?php declare(strict_types=1);
require_once "core/event.php";
abstract class PageMode
{
const REDIRECT = 'redirect';
const DATA = 'data';
const PAGE = 'page';
const FILE = 'file';
const MANUAL = 'manual';
}
/**
* Class Page
*
* A data structure for holding all the bits of data that make up a page.
*
* The various extensions all add whatever they want to this structure,
* then Layout turns it into HTML.
*/
class BasePage
{
/** @var string */
public $mode = PageMode::PAGE;
/** @var string */
private $type = "text/html; charset=utf-8";
/**
* Set what this page should do; "page", "data", or "redirect".
*/
public function set_mode(string $mode): void
{
$this->mode = $mode;
}
/**
* Set the page's MIME type.
*/
public function set_type(string $type): void
{
$this->type = $type;
}
public function __construct()
{
if (@$_GET["flash"]) {
$this->flash[] = $_GET['flash'];
unset($_GET["flash"]);
}
}
// ==============================================
/** @var string; public only for unit test */
public $data = "";
/** @var string */
private $file = null;
/** @var bool */
private $file_delete = false;
/** @var string */
private $filename = null;
private $disposition = null;
/**
* Set the raw data to be sent.
*/
public function set_data(string $data): void
{
$this->data = $data;
}
public function set_file(string $file, bool $delete = false): void
{
$this->file = $file;
$this->file_delete = $delete;
}
/**
* Set the recommended download filename.
*/
public function set_filename(string $filename, string $disposition = "attachment"): void
{
$this->filename = $filename;
$this->disposition = $disposition;
}
// ==============================================
/** @var string */
public $redirect = "";
/**
* Set the URL to redirect to (remember to use make_link() if linking
* to a page in the same site).
*/
public function set_redirect(string $redirect): void
{
$this->redirect = $redirect;
}
// ==============================================
/** @var int */
public $code = 200;
/** @var string */
public $title = "";
/** @var string */
public $heading = "";
/** @var string */
public $subheading = "";
/** @var string[] */
public $html_headers = [];
/** @var string[] */
public $http_headers = [];
/** @var string[][] */
public $cookies = [];
/** @var Block[] */
public $blocks = [];
/** @var string[] */
public $flash = [];
/**
* Set the HTTP status code
*/
public function set_code(int $code): void
{
$this->code = $code;
}
public function set_title(string $title): void
{
$this->title = $title;
}
public function set_heading(string $heading): void
{
$this->heading = $heading;
}
public function set_subheading(string $subheading): void
{
$this->subheading = $subheading;
}
public function flash(string $message): void
{
$this->flash[] = $message;
}
/**
* Add a line to the HTML head section.
*/
public function add_html_header(string $line, int $position = 50): void
{
while (isset($this->html_headers[$position])) {
$position++;
}
$this->html_headers[$position] = $line;
}
/**
* Add a http header to be sent to the client.
*/
public function add_http_header(string $line, int $position = 50): void
{
while (isset($this->http_headers[$position])) {
$position++;
}
$this->http_headers[$position] = $line;
}
/**
* The counterpart for get_cookie, this works like php's
* setcookie method, but prepends the site-wide cookie prefix to
* the $name argument before doing anything.
*/
public function add_cookie(string $name, string $value, int $time, string $path): void
{
$full_name = COOKIE_PREFIX . "_" . $name;
$this->cookies[] = [$full_name, $value, $time, $path];
}
public function get_cookie(string $name): ?string
{
$full_name = COOKIE_PREFIX . "_" . $name;
if (isset($_COOKIE[$full_name])) {
return $_COOKIE[$full_name];
} else {
return null;
}
}
/**
* Get all the HTML headers that are currently set and return as a string.
*/
public function get_all_html_headers(): string
{
$data = '';
ksort($this->html_headers);
foreach ($this->html_headers as $line) {
$data .= "\t\t" . $line . "\n";
}
return $data;
}
/**
* Add a Block of data to the page.
*/
public function add_block(Block $block): void
{
$this->blocks[] = $block;
}
/**
* Find a block which contains the given text
* (Useful for unit tests)
*/
public function find_block(string $text): ?Block
{
foreach ($this->blocks as $block) {
if ($block->header == $text) {
return $block;
}
}
return null;
}
// ==============================================
public function send_headers(): void
{
if (!headers_sent()) {
header("HTTP/1.0 {$this->code} Shimmie");
header("Content-type: " . $this->type);
header("X-Powered-By: Shimmie-" . VERSION);
foreach ($this->http_headers as $head) {
header($head);
}
foreach ($this->cookies as $c) {
setcookie($c[0], $c[1], $c[2], $c[3]);
}
} else {
print "Error: Headers have already been sent to the client.";
}
}
/**
* Display the page according to the mode and data given.
*/
public function display(): void
{
if ($this->mode!=PageMode::MANUAL) {
$this->send_headers();
}
switch ($this->mode) {
case PageMode::MANUAL:
break;
case PageMode::PAGE:
usort($this->blocks, "blockcmp");
$this->add_auto_html_headers();
$this->render();
break;
case PageMode::DATA:
header("Content-Length: " . strlen($this->data));
if (!is_null($this->filename)) {
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
}
print $this->data;
break;
case PageMode::FILE:
if (!is_null($this->filename)) {
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
}
// https://gist.github.com/codler/3906826
$size = filesize($this->file); // File size
$length = $size; // Content length
$start = 0; // Start byte
$end = $size - 1; // End byte
header("Content-Length: " . $size);
header('Accept-Ranges: bytes');
if (isset($_SERVER['HTTP_RANGE'])) {
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (strpos($range, ',') !== false) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
break;
}
if ($range == '-') {
$c_start = $size - (int)substr($range, 1);
$c_end = $end;
} else {
$range = explode('-', $range);
$c_start = (int)$range[0];
$c_end = (isset($range[1]) && is_numeric($range[1])) ? (int)$range[1] : $size;
}
$c_end = ($c_end > $end) ? $end : $c_end;
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
header('HTTP/1.1 416 Requested Range Not Satisfiable');
header("Content-Range: bytes $start-$end/$size");
break;
}
$start = $c_start;
$end = $c_end;
$length = $end - $start + 1;
header('HTTP/1.1 206 Partial Content');
}
header("Content-Range: bytes $start-$end/$size");
header("Content-Length: " . $length);
try {
stream_file($this->file, $start, $end);
} finally {
if ($this->file_delete === true) {
unlink($this->file);
}
}
break;
case PageMode::REDIRECT:
if ($this->flash) {
$this->redirect .= (strpos($this->redirect, "?") === false) ? "?" : "&";
$this->redirect .= "flash=" . url_escape(implode("\n", $this->flash));
}
header('Location: ' . $this->redirect);
print 'You should be redirected to <a href="' . $this->redirect . '">' . $this->redirect . '</a>';
break;
default:
print "Invalid page mode";
break;
}
}
/**
* This function grabs all the CSS and JavaScript files sprinkled throughout Shimmie's folders,
* concatenates them together into two large files (one for CSS and one for JS) and then stores
* them in the /cache/ directory for serving to the user.
*
* Why do this? Two reasons:
* 1. Reduces the number of files the user's browser needs to download.
* 2. Allows these cached files to be compressed/minified by the admin.
*
* TODO: This should really be configurable somehow...
*/
public function add_auto_html_headers(): void
{
global $config;
$data_href = get_base_href();
$theme_name = $config->get_string(SetupConfig::THEME, 'default');
$this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>", 40);
# static handler will map these to themes/foo/static/bar.ico or ext/static_files/static/bar.ico
$this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>", 41);
$this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>", 42);
//We use $config_latest to make sure cache is reset if config is ever updated.
$config_latest = 0;
foreach (zglob("data/config/*") as $conf) {
$config_latest = max($config_latest, filemtime($conf));
}
/*** Generate CSS cache files ***/
$css_latest = $config_latest;
$css_files = array_merge(
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/style.css"),
zglob("themes/$theme_name/style.css")
);
foreach ($css_files as $css) {
$css_latest = max($css_latest, filemtime($css));
}
$css_md5 = md5(serialize($css_files));
$css_cache_file = data_path("cache/style/{$theme_name}.{$css_latest}.{$css_md5}.css");
if (!file_exists($css_cache_file)) {
$css_data = "";
foreach ($css_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../../' . dirname($file) . '/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n";
}
file_put_contents($css_cache_file, $css_data);
}
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>", 43);
/*** Generate JS cache files ***/
$js_latest = $config_latest;
$js_files = array_merge(
[
"vendor/bower-asset/jquery-timeago/jquery.timeago.js",
"vendor/bower-asset/js-cookie/src/js.cookie.js",
"ext/static_files/modernizr-3.3.1.custom.js",
],
zglob("ext/{" . Extension::get_enabled_extensions_as_string() . "}/script.js"),
zglob("themes/$theme_name/script.js")
);
foreach ($js_files as $js) {
$js_latest = max($js_latest, filemtime($js));
}
$js_md5 = md5(serialize($js_files));
$js_cache_file = data_path("cache/script/{$theme_name}.{$js_latest}.{$js_md5}.js");
if (!file_exists($js_cache_file)) {
$js_data = "";
foreach ($js_files as $file) {
$js_data .= file_get_contents($file) . "\n";
}
file_put_contents($js_cache_file, $js_data);
}
$this->add_html_header("<script defer src='$data_href/$js_cache_file' type='text/javascript'></script>", 44);
}
protected function get_nav_links()
{
$pnbe = send_event(new PageNavBuildingEvent());
$nav_links = $pnbe->links;
$active_link = null;
// To save on event calls, we check if one of the top-level links has already been marked as active
foreach ($nav_links as $link) {
if ($link->active===true) {
$active_link = $link;
break;
}
}
$sub_links = null;
// If one is, we just query for sub-menu options under that one tab
if ($active_link!==null) {
$psnbe = send_event(new PageSubNavBuildingEvent($active_link->name));
$sub_links = $psnbe->links;
} else {
// Otherwise we query for the sub-items under each of the tabs
foreach ($nav_links as $link) {
$psnbe = send_event(new PageSubNavBuildingEvent($link->name));
// Now we check for a current link so we can identify the sub-links to show
foreach ($psnbe->links as $sub_link) {
if ($sub_link->active===true) {
$sub_links = $psnbe->links;
break;
}
}
// If the active link has been detected, we break out
if ($sub_links!==null) {
$link->active = true;
break;
}
}
}
$sub_links = $sub_links??[];
usort($nav_links, "sort_nav_links");
usort($sub_links, "sort_nav_links");
return [$nav_links, $sub_links];
}
/**
* turns the Page into HTML
*/
public function render()
{
$head_html = $this->head_html();
$body_html = $this->body_html();
print <<<EOD
<!doctype html>
<html class="no-js" lang="en">
$head_html
$body_html
</html>
EOD;
}
protected function head_html(): string
{
$html_header_html = $this->get_all_html_headers();
return "
<head>
<title>{$this->title}</title>
$html_header_html
</head>
";
}
protected function body_html(): string
{
$left_block_html = "";
$main_block_html = "";
$sub_block_html = "";
foreach ($this->blocks as $block) {
switch ($block->section) {
case "left":
$left_block_html .= $block->get_html(true);
break;
case "main":
$main_block_html .= $block->get_html(false);
break;
case "subheading":
$sub_block_html .= $block->get_html(false);
break;
default:
print "<p>error: {$block->header} using an unknown section ({$block->section})";
break;
}
}
$wrapper = "";
if (strlen($this->heading) > 100) {
$wrapper = ' style="height: 3em; overflow: auto;"';
}
$footer_html = $this->footer_html();
$flash_html = $this->flash ? "<b id='flash'>".nl2br(html_escape(implode("\n", $this->flash)))."</b>" : "";
return "
<body>
<header>
<h1$wrapper>{$this->heading}</h1>
$sub_block_html
</header>
<nav>
$left_block_html
</nav>
<article>
$flash_html
$main_block_html
</article>
<footer>
$footer_html
</footer>
</body>
";
}
protected function footer_html(): string
{
$debug = get_debug_info();
$contact_link = contact_link();
$contact = empty($contact_link) ? "" : "<br><a href='$contact_link'>Contact</a>";
return "
Images &copy; their respective owners,
<a href=\"https://code.shishnet.org/shimmie2/\">Shimmie</a> &copy;
<a href=\"https://www.shishnet.org/\">Shish</a> &amp;
<a href=\"https://github.com/shish/shimmie2/graphs/contributors\">The Team</a>
2007-2020,
based on the Danbooru concept.
$debug
$contact
";
}
}
class PageNavBuildingEvent extends Event
{
public $links = [];
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
{
$this->links[] = new NavLink($name, $link, $desc, $active, $order);
}
}
class PageSubNavBuildingEvent extends Event
{
public $parent;
public $links = [];
public function __construct(string $parent)
{
parent::__construct();
$this->parent= $parent;
}
public function add_nav_link(string $name, Link $link, string $desc, ?bool $active = null, int $order = 50)
{
$this->links[] = new NavLink($name, $link, $desc, $active, $order);
}
}
class NavLink
{
public $name;
public $link;
public $description;
public $order;
public $active = false;
public function __construct(String $name, Link $link, String $description, ?bool $active = null, int $order = 50)
{
global $config;
$this->name = $name;
$this->link = $link;
$this->description = $description;
$this->order = $order;
if ($active==null) {
$query = ltrim(_get_query(), "/");
if ($query === "") {
// This indicates the front page, so we check what's set as the front page
$front_page = trim($config->get_string(SetupConfig::FRONT_PAGE), "/");
if ($front_page === $link->page) {
$this->active = true;
} else {
$this->active = self::is_active([$link->page], $front_page);
}
} elseif ($query===$link->page) {
$this->active = true;
} else {
$this->active = self::is_active([$link->page]);
}
} else {
$this->active = $active;
}
}
public static function is_active(array $pages_matched, string $url = null): bool
{
/**
* Woo! We can actually SEE THE CURRENT PAGE!! (well... see it highlighted in the menu.)
*/
$url = $url??ltrim(_get_query(), "/");
$re1='.*?';
$re2='((?:[a-z][a-z_]+))';
if (preg_match_all("/".$re1.$re2."/is", $url, $matches)) {
$url=$matches[1][0];
}
$count_pages_matched = count($pages_matched);
for ($i=0; $i < $count_pages_matched; $i++) {
if ($url == $pages_matched[$i]) {
return true;
}
}
return false;
}
}
function sort_nav_links(NavLink $a, NavLink $b)
{
return $a->order - $b->order;
}

View File

@ -1,104 +0,0 @@
<?php
/**
* A collection of common functions for theme parts
*/
class BaseThemelet {
/**
* Generic error message display
*/
public function display_error(/*int*/ $code, /*string*/ $title, /*string*/ $message) {
global $page;
$page->add_http_header("HTTP/1.0 $code $title");
$page->set_title($title);
$page->set_heading($title);
$page->add_block(new NavBlock());
$page->add_block(new Block("Error", $message));
}
/**
* A specific, common error message
*/
public function display_permission_denied() {
$this->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
/**
* Generic thumbnail code; returns HTML rather than adding
* a block since thumbs tend to go inside blocks...
*/
public function build_thumb_html(Image $image, $query=null) {
global $config;
$i_id = (int) $image->id;
$h_view_link = make_link('post/view/'.$i_id, $query);
$h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip());
$h_tags = strtolower($image->get_tag_list());
$base = get_base_href();
// If file is flash or svg then sets thumbnail to max size.
if($image->ext === 'swf' || $image->ext === 'svg'){
$tsize = get_thumbnail_size($config->get_int('thumb_width'), $config->get_int('thumb_height'));
}
else{
$tsize = get_thumbnail_size($image->width, $image->height);
}
return '<a href="'.$h_view_link.'" class="thumb" data-tags="'.$h_tags.'">'.
'<img id="thumb_'.$i_id.'" title="'.$h_tip.'" alt="'.$h_tip.'" height="'.$tsize[1].'" width="'.$tsize[0].'" class="lazy" data-original="'.$h_thumb_link.'" src="'.$base.'/lib/static/grey.gif">'.
'<noscript><img id="thumb_'.$i_id.'" title="'.$h_tip.'" alt="'.$h_tip.'" height="'.$tsize[1].'" width="'.$tsize[0].'" src="'.$h_thumb_link.'"></noscript>'.
"</a>\n";
}
/**
* Add a generic paginator
*/
public function display_paginator(Page $page, $base, $query, $page_number, $total_pages) {
if($total_pages == 0) $total_pages = 1;
$body = $this->build_paginator($page_number, $total_pages, $base, $query);
$page->add_block(new Block(null, $body, "main", 90, "paginator"));
}
private function gen_page_link($base_url, $query, $page, $name) {
$link = make_link($base_url.'/'.$page, $query);
return '<a href="'.$link.'">'.$name.'</a>';
}
private function gen_page_link_block($base_url, $query, $page, $current_page, $name) {
$paginator = "";
if($page == $current_page) $paginator .= "<b>";
$paginator .= $this->gen_page_link($base_url, $query, $page, $name);
if($page == $current_page) $paginator .= "</b>";
return $paginator;
}
private function build_paginator($current_page, $total_pages, $base_url, $query) {
$next = $current_page + 1;
$prev = $current_page - 1;
$rand = rand(1, $total_pages);
$at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages);
$first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First");
$prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev");
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
$last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last");
$start = $current_page-5 > 1 ? $current_page-5 : 1;
$end = $start+10 < $total_pages ? $start+10 : $total_pages;
$pages = array();
foreach(range($start, $end) as $i) {
$pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, $i);
}
$pages_html = implode(" | ", $pages);
return $first_html.' | '.$prev_html.' | '.$random_html.' | '.$next_html.' | '.$last_html
.'<br>&lt;&lt; '.$pages_html.' &gt;&gt;';
}
}
?>

149
core/basethemelet.php Normal file
View File

@ -0,0 +1,149 @@
<?php declare(strict_types=1);
/**
* Class BaseThemelet
*
* A collection of common functions for theme parts
*/
class BaseThemelet
{
/**
* Generic error message display
*/
public function display_error(int $code, string $title, string $message): void
{
global $page;
$page->set_code($code);
$page->set_title($title);
$page->set_heading($title);
$has_nav = false;
foreach ($page->blocks as $block) {
if ($block->header == "Navigation") {
$has_nav = true;
break;
}
}
if (!$has_nav) {
$page->add_block(new NavBlock());
}
$page->add_block(new Block("Error", $message));
}
/**
* A specific, common error message
*/
public function display_permission_denied(): void
{
$this->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
/**
* Generic thumbnail code; returns HTML rather than adding
* a block since thumbs tend to go inside blocks...
*/
public function build_thumb_html(Image $image): string
{
global $config;
$i_id = (int) $image->id;
$h_view_link = make_link('post/view/'.$i_id);
$h_thumb_link = $image->get_thumb_link();
$h_tip = html_escape($image->get_tooltip());
$h_tags = html_escape(strtolower($image->get_tag_list()));
$extArr = array_flip([EXTENSION_FLASH, EXTENSION_SVG, EXTENSION_MP3]); //List of thumbless filetypes
if (!isset($extArr[$image->ext])) {
$tsize = get_thumbnail_size($image->width, $image->height);
} else {
//Use max thumbnail size if using thumbless filetype
$tsize = get_thumbnail_size($config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_WIDTH));
}
$custom_classes = "";
if (class_exists("Relationships")) {
if (property_exists($image, 'parent_id') && $image->parent_id !== null) {
$custom_classes .= "shm-thumb-has_parent ";
}
if (property_exists($image, 'has_children') && bool_escape($image->has_children)) {
$custom_classes .= "shm-thumb-has_child ";
}
}
return "<a href='$h_view_link' class='thumb shm-thumb shm-thumb-link {$custom_classes}' data-tags='$h_tags' data-post-id='$i_id'>".
"<img id='thumb_$i_id' title='$h_tip' alt='$h_tip' height='{$tsize[1]}' width='{$tsize[0]}' src='$h_thumb_link'>".
"</a>\n";
}
public function display_paginator(Page $page, string $base, ?string $query, int $page_number, int $total_pages, bool $show_random = false)
{
if ($total_pages == 0) {
$total_pages = 1;
}
$body = $this->build_paginator($page_number, $total_pages, $base, $query, $show_random);
$page->add_block(new Block(null, $body, "main", 90, "paginator"));
$page->add_html_header("<link rel='first' href='".make_http(make_link($base.'/1', $query))."'>");
if ($page_number < $total_pages) {
$page->add_html_header("<link rel='prefetch' href='".make_http(make_link($base.'/'.($page_number+1), $query))."'>");
$page->add_html_header("<link rel='next' href='".make_http(make_link($base.'/'.($page_number+1), $query))."'>");
}
if ($page_number > 1) {
$page->add_html_header("<link rel='previous' href='".make_http(make_link($base.'/'.($page_number-1), $query))."'>");
}
$page->add_html_header("<link rel='last' href='".make_http(make_link($base.'/'.$total_pages, $query))."'>");
}
private function gen_page_link(string $base_url, ?string $query, int $page, string $name): string
{
$link = make_link($base_url.'/'.$page, $query);
return '<a href="'.$link.'">'.$name.'</a>';
}
private function gen_page_link_block(string $base_url, ?string $query, int $page, int $current_page, string $name): string
{
$paginator = "";
if ($page == $current_page) {
$paginator .= "<b>";
}
$paginator .= $this->gen_page_link($base_url, $query, $page, $name);
if ($page == $current_page) {
$paginator .= "</b>";
}
return $paginator;
}
private function build_paginator(int $current_page, int $total_pages, string $base_url, ?string $query, bool $show_random): string
{
$next = $current_page + 1;
$prev = $current_page - 1;
$at_start = ($current_page <= 1 || $total_pages <= 1);
$at_end = ($current_page >= $total_pages);
$first_html = $at_start ? "First" : $this->gen_page_link($base_url, $query, 1, "First");
$prev_html = $at_start ? "Prev" : $this->gen_page_link($base_url, $query, $prev, "Prev");
$random_html = "-";
if ($show_random) {
$rand = mt_rand(1, $total_pages);
$random_html = $this->gen_page_link($base_url, $query, $rand, "Random");
}
$next_html = $at_end ? "Next" : $this->gen_page_link($base_url, $query, $next, "Next");
$last_html = $at_end ? "Last" : $this->gen_page_link($base_url, $query, $total_pages, "Last");
$start = $current_page-5 > 1 ? $current_page-5 : 1;
$end = $start+10 < $total_pages ? $start+10 : $total_pages;
$pages = [];
foreach (range($start, $end) as $i) {
$pages[] = $this->gen_page_link_block($base_url, $query, $i, $current_page, (string)$i);
}
$pages_html = implode(" | ", $pages);
return $first_html.' | '.$prev_html.' | '.$random_html.' | '.$next_html.' | '.$last_html
.'<br>&lt;&lt; '.$pages_html.' &gt;&gt;';
}
}

View File

@ -1,74 +0,0 @@
<?php
/**
* A basic chunk of a page
*/
class Block {
/**
* The block's title
*
* @retval string
*/
var $header;
/**
* The content
*
* @retval string
*/
var $body;
/**
* Where the block should be placed. The default theme supports
* "main" and "left", other themes can add their own areas
*
* @retval string
*/
var $section;
/**
* How far down the section the block should appear, higher
* numbers appear lower. The scale is 0-100 by convention,
* though any number or string will work.
*
* @retval int
*/
var $position;
/**
*
*/
var $id;
public function __construct($header, $body, /*string*/ $section="main", /*int*/ $position=50, $id=null) {
$this->header = $header;
$this->body = $body;
$this->section = $section;
$this->position = $position;
$this->id = str_replace(' ', '_', is_null($id) ? (is_null($header) ? md5($body) : $header) . $section : $id);
}
public function get_html($hidable=false) {
$h = $this->header;
$b = $this->body;
$i = $this->id;
$html = "<section id='$i'>";
$h_toggler = $hidable ? " shm-toggler" : "";
if(!empty($h)) $html .= "<h3 data-toggle-sel='#$i' class='$h_toggler'>$h</h3>";
if(!empty($b)) $html .= "<div class='blockbody'>$b</div>";
$html .= "</section>\n";
return $html;
}
}
/**
* A generic navigation block with a link to the main page.
*
* Used because "new NavBlock()" is easier than "new Block('Navigation', ..."
*/
class NavBlock extends Block {
public function __construct() {
parent::__construct("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0);
}
}
?>

105
core/block.php Normal file
View File

@ -0,0 +1,105 @@
<?php declare(strict_types=1);
/**
* Class Block
*
* A basic chunk of a page.
*/
class Block
{
/**
* The block's title.
*
* @var string
*/
public $header;
/**
* The content of the block.
*
* @var string
*/
public $body;
/**
* Where the block should be placed. The default theme supports
* "main" and "left", other themes can add their own areas.
*
* @var string
*/
public $section;
/**
* How far down the section the block should appear, higher
* numbers appear lower. The scale is 0-100 by convention,
* though any number will work.
*
* @var int
*/
public $position;
/**
* A unique ID for the block.
*
* @var string
*/
public $id;
/**
* Should this block count as content for the sake of
* the 404 handler
*
* @var boolean
*/
public $is_content = true;
public function __construct(string $header=null, string $body=null, string $section="main", int $position=50, string $id=null)
{
$this->header = $header;
$this->body = $body;
$this->section = $section;
$this->position = $position;
if (is_null($id)) {
$id = (empty($header) ? md5($body ?? '') : $header) . $section;
}
$this->id = preg_replace('/[^\w-]/', '', str_replace(' ', '_', $id));
}
/**
* Get the HTML for this block.
*/
public function get_html(bool $hidable=false): string
{
$h = $this->header;
$b = $this->body;
$i = $this->id;
$html = "<section id='$i'>";
$h_toggler = $hidable ? " shm-toggler" : "";
if (!empty($h)) {
$html .= "<h3 data-toggle-sel='#$i' class='$h_toggler'>$h</h3>";
}
if (!empty($b)) {
$html .= "<div class='blockbody'>$b</div>";
}
$html .= "</section>\n";
return $html;
}
}
/**
* Class NavBlock
*
* A generic navigation block with a link to the main page.
*
* Used because "new NavBlock()" is easier than "new Block('Navigation', ..."
*
*/
class NavBlock extends Block
{
public function __construct()
{
parent::__construct("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0);
}
}

199
core/cacheengine.php Normal file
View File

@ -0,0 +1,199 @@
<?php declare(strict_types=1);
interface CacheEngine
{
public function get(string $key);
public function set(string $key, $val, int $time=0);
public function delete(string $key);
}
class NoCache implements CacheEngine
{
public function get(string $key)
{
return false;
}
public function set(string $key, $val, int $time=0)
{
}
public function delete(string $key)
{
}
}
class MemcachedCache implements CacheEngine
{
/** @var ?Memcached */
public $memcache=null;
public function __construct(string $args)
{
$hp = explode(":", $args);
$this->memcache = new Memcached;
#$this->memcache->setOption(Memcached::OPT_COMPRESSION, False);
#$this->memcache->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP);
#$this->memcache->setOption(Memcached::OPT_PREFIX_KEY, phpversion());
$this->memcache->addServer($hp[0], (int)$hp[1]);
}
public function get(string $key)
{
$key = urlencode($key);
$val = $this->memcache->get($key);
$res = $this->memcache->getResultCode();
if ($res == Memcached::RES_SUCCESS) {
return $val;
} elseif ($res == Memcached::RES_NOTFOUND) {
return false;
} else {
error_log("Memcached error during get($key): $res");
return false;
}
}
public function set(string $key, $val, int $time=0)
{
$key = urlencode($key);
$this->memcache->set($key, $val, $time);
$res = $this->memcache->getResultCode();
if ($res != Memcached::RES_SUCCESS) {
error_log("Memcached error during set($key): $res");
}
}
public function delete(string $key)
{
$key = urlencode($key);
$this->memcache->delete($key);
$res = $this->memcache->getResultCode();
if ($res != Memcached::RES_SUCCESS && $res != Memcached::RES_NOTFOUND) {
error_log("Memcached error during delete($key): $res");
}
}
}
class APCCache implements CacheEngine
{
public function __construct(string $args)
{
// $args is not used, but is passed in when APC cache is created.
}
public function get(string $key)
{
return apc_fetch($key);
}
public function set(string $key, $val, int $time=0)
{
apc_store($key, $val, $time);
}
public function delete(string $key)
{
apc_delete($key);
}
}
class RedisCache implements CacheEngine
{
private $redis=null;
public function __construct(string $args)
{
$this->redis = new Redis();
$hp = explode(":", $args);
$this->redis->pconnect($hp[0], (int)$hp[1]);
$this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$this->redis->setOption(Redis::OPT_PREFIX, 'shm:');
}
public function get(string $key)
{
return $this->redis->get($key);
}
public function set(string $key, $val, int $time=0)
{
if ($time > 0) {
$this->redis->setEx($key, $time, $val);
} else {
$this->redis->set($key, $val);
}
}
public function delete(string $key)
{
$this->redis->del($key);
}
}
class Cache
{
public $engine;
public $hits=0;
public $misses=0;
public $time=0;
public function __construct(?string $dsn)
{
$matches = [];
$c = null;
if ($dsn && preg_match("#(.*)://(.*)#", $dsn, $matches) && !isset($_GET['DISABLE_CACHE'])) {
if ($matches[1] == "memcached") {
$c = new MemcachedCache($matches[2]);
} elseif ($matches[1] == "apc") {
$c = new APCCache($matches[2]);
} elseif ($matches[1] == "redis") {
$c = new RedisCache($matches[2]);
}
} else {
$c = new NoCache();
}
$this->engine = $c;
}
public function get(string $key)
{
global $_tracer;
$_tracer->begin("Cache Query", ["key"=>$key]);
$val = $this->engine->get($key);
if ($val !== false) {
$res = "hit";
$this->hits++;
} else {
$res = "miss";
$this->misses++;
}
$_tracer->end(null, ["result"=>$res]);
return $val;
}
public function set(string $key, $val, int $time=0)
{
global $_tracer;
$_tracer->begin("Cache Set", ["key"=>$key, "time"=>$time]);
$this->engine->set($key, $val, $time);
$_tracer->end();
}
public function delete(string $key)
{
global $_tracer;
$_tracer->begin("Cache Delete", ["key"=>$key]);
$this->engine->delete($key);
$_tracer->end();
}
public function get_hits(): int
{
return $this->hits;
}
public function get_misses(): int
{
return $this->misses;
}
}

60
core/captcha.php Normal file
View File

@ -0,0 +1,60 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* CAPTCHA abstraction *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
use ReCaptcha\ReCaptcha;
function captcha_get_html(): string
{
global $config, $user;
if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
return "";
}
$captcha = "";
if ($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_publickey = $config->get_string("api_recaptcha_pubkey");
if (!empty($r_publickey)) {
$captcha = "
<div class=\"g-recaptcha\" data-sitekey=\"{$r_publickey}\"></div>
<script type=\"text/javascript\" src=\"https://www.google.com/recaptcha/api.js\"></script>";
} else {
session_start();
$captcha = Securimage::getCaptchaHtml(['securimage_path' => './vendor/dapphp/securimage/']);
}
}
return $captcha;
}
function captcha_check(): bool
{
global $config, $user;
if (DEBUG && ip_in_range($_SERVER['REMOTE_ADDR'], "127.0.0.0/8")) {
return true;
}
if ($user->is_anonymous() && $config->get_bool("comment_captcha")) {
$r_privatekey = $config->get_string('api_recaptcha_privkey');
if (!empty($r_privatekey)) {
$recaptcha = new ReCaptcha($r_privatekey);
$resp = $recaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
if (!$resp->isSuccess()) {
log_info("core", "Captcha failed (ReCaptcha): " . implode("", $resp->getErrorCodes()));
return false;
}
} else {
session_start();
$securimg = new Securimage();
if ($securimg->check($_POST['captcha_code']) === false) {
log_info("core", "Captcha failed (Securimage)");
return false;
}
}
}
return true;
}

View File

@ -1,203 +0,0 @@
<?php
/**
* an abstract interface for altering a name:value pair list
*/
interface Config {
/**
* Save the list of name:value pairs to wherever they came from,
* so that the next time a page is loaded it will use the new
* configuration
*/
public function save(/*string*/ $name=null);
/** @name set_*
* Set a configuration option to a new value, regardless
* of what the value is at the moment
*/
//@{
public function set_int(/*string*/ $name, $value);
public function set_string(/*string*/ $name, $value);
public function set_bool(/*string*/ $name, $value);
public function set_array(/*string*/ $name, $value);
//@}
/** @name set_default_*
* Set a configuration option to a new value, if there is no
* value currently. Extensions should generally call these
* from their InitExtEvent handlers. This has the advantage
* that the values will show up in the "advanced" setup page
* where they can be modified, while calling get_* with a
* "default" paramater won't show up.
*/
//@{
public function set_default_int(/*string*/ $name, $value);
public function set_default_string(/*string*/ $name, $value);
public function set_default_bool(/*string*/ $name, $value);
public function set_default_array(/*string*/ $name, $value);
//@}
/** @name get_*
* pick a value out of the table by name, cast to the
* appropritate data type
*/
//@{
public function get_int(/*string*/ $name, $default=null);
public function get_string(/*string*/ $name, $default=null);
public function get_bool(/*string*/ $name, $default=null);
public function get_array(/*string*/ $name, $default=array());
//@}
}
/**
* Common methods for manipulating the list, loading and saving is
* left to the concrete implementation
*/
abstract class BaseConfig implements Config {
var $values = array();
public function set_int(/*string*/ $name, $value) {
$this->values[$name] = parse_shorthand_int($value);
$this->save($name);
}
public function set_string(/*string*/ $name, $value) {
$this->values[$name] = $value;
$this->save($name);
}
public function set_bool(/*string*/ $name, $value) {
$this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N');
$this->save($name);
}
public function set_array(/*string*/ $name, $value) {
assert(is_array($value));
$this->values[$name] = implode(",", $value);
$this->save($name);
}
public function set_default_int(/*string*/ $name, $value) {
if(is_null($this->get($name))) {
$this->values[$name] = parse_shorthand_int($value);
}
}
public function set_default_string(/*string*/ $name, $value) {
if(is_null($this->get($name))) {
$this->values[$name] = $value;
}
}
public function set_default_bool(/*string*/ $name, $value) {
if(is_null($this->get($name))) {
$this->values[$name] = (($value == 'on' || $value === true) ? 'Y' : 'N');
}
}
public function set_default_array(/*string*/ $name, $value) {
assert(is_array($value));
if(is_null($this->get($name))) {
$this->values[$name] = implode(",", $value);
}
}
public function get_int(/*string*/ $name, $default=null) {
return (int)($this->get($name, $default));
}
public function get_string(/*string*/ $name, $default=null) {
return $this->get($name, $default);
}
public function get_bool(/*string*/ $name, $default=null) {
return bool_escape($this->get($name, $default));
}
public function get_array(/*string*/ $name, $default=array()) {
return explode(",", $this->get($name, ""));
}
private function get(/*string*/ $name, $default=null) {
if(isset($this->values[$name])) {
return $this->values[$name];
}
else {
return $default;
}
}
}
/**
* Loads the config list from a PHP file; the file should be in the format:
*
* <?php
* $config['foo'] = "bar";
* $config['baz'] = "qux";
* ?>
*/
class StaticConfig extends BaseConfig {
public function __construct($filename) {
if(file_exists($filename)) {
require_once $filename;
if(isset($config)) {
$this->values = $config;
}
else {
throw new Exception("Config file '$filename' doesn't contain any config");
}
}
else {
throw new Exception("Config file '$filename' missing");
}
}
public function save(/*string*/ $name=null) {
// static config is static
}
}
/**
* Loads the config list from a table in a given database, the table should
* be called config and have the schema:
*
* \code
* CREATE TABLE config(
* name VARCHAR(255) NOT NULL,
* value TEXT
* );
* \endcode
*/
class DatabaseConfig extends BaseConfig {
var $database = null;
/*
* Load the config table from a database
*/
public function DatabaseConfig(Database $database) {
$this->database = $database;
$cached = $this->database->cache->get("config");
if($cached) {
$this->values = $cached;
}
else {
$this->values = array();
foreach($this->database->get_all("SELECT name, value FROM config") as $row) {
$this->values[$row["name"]] = $row["value"];
}
$this->database->cache->set("config", $this->values);
}
}
/*
* Save the current values as the new config table
*/
public function save(/*string*/ $name=null) {
if(is_null($name)) {
reset($this->values); // rewind the array to the first element
foreach($this->values as $name => $value) {
$this->save(/*string*/ $name);
}
}
else {
$this->database->Execute("DELETE FROM config WHERE name = :name", array("name"=>$name));
$this->database->Execute("INSERT INTO config VALUES (:name, :value)", array("name"=>$name, "value"=>$this->values[$name]));
}
$this->database->cache->delete("config");
}
}
?>

338
core/config.php Normal file
View File

@ -0,0 +1,338 @@
<?php declare(strict_types=1);
/**
* Interface Config
*
* An abstract interface for altering a name:value pair list.
*/
interface Config
{
/**
* Save the list of name:value pairs to wherever they came from,
* so that the next time a page is loaded it will use the new
* configuration.
*/
public function save(string $name=null): void;
//@{ /*--------------------------------- SET ------------------------------------------------------*/
/**
* Set a configuration option to a new value, regardless of what the value is at the moment.
*/
public function set_int(string $name, ?int $value): void;
/**
* Set a configuration option to a new value, regardless of what the value is at the moment.
*/
public function set_float(string $name, ?float $value): void;
/**
* Set a configuration option to a new value, regardless of what the value is at the moment.
*/
public function set_string(string $name, ?string $value): void;
/**
* Set a configuration option to a new value, regardless of what the value is at the moment.
*/
public function set_bool(string $name, ?bool $value): void;
/**
* Set a configuration option to a new value, regardless of what the value is at the moment.
*/
public function set_array(string $name, array $value): void;
//@} /*--------------------------------------------------------------------------------------------*/
//@{ /*-------------------------------- SET DEFAULT -----------------------------------------------*/
/**
* Set a configuration option to a new value, if there is no value currently.
*
* Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default"
* parameter won't show up.
*/
public function set_default_int(string $name, int $value): void;
/**
* Set a configuration option to a new value, if there is no value currently.
*
* Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default"
* parameter won't show up.
*/
public function set_default_float(string $name, float $value): void;
/**
* Set a configuration option to a new value, if there is no value currently.
*
* Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default"
* parameter won't show up.
*/
public function set_default_string(string $name, string $value): void;
/**
* Set a configuration option to a new value, if there is no value currently.
*
* Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default"
* parameter won't show up.
*/
public function set_default_bool(string $name, bool $value): void;
/**
* Set a configuration option to a new value, if there is no value currently.
*
* Extensions should generally call these from their InitExtEvent handlers.
* This has the advantage that the values will show up in the "advanced" setup
* page where they can be modified, while calling get_* with a "default"
* parameter won't show up.
*/
public function set_default_array(string $name, array $value): void;
//@} /*--------------------------------------------------------------------------------------------*/
//@{ /*--------------------------------- GET ------------------------------------------------------*/
/**
* Pick a value out of the table by name, cast to the appropriate data type.
*/
public function get_int(string $name, ?int $default=null): ?int;
/**
* Pick a value out of the table by name, cast to the appropriate data type.
*/
public function get_float(string $name, ?float $default=null): ?float;
/**
* Pick a value out of the table by name, cast to the appropriate data type.
*/
public function get_string(string $name, ?string $default=null): ?string;
/**
* Pick a value out of the table by name, cast to the appropriate data type.
*/
public function get_bool(string $name, ?bool $default=null): ?bool;
/**
* Pick a value out of the table by name, cast to the appropriate data type.
*/
public function get_array(string $name, ?array $default=[]): ?array;
//@} /*--------------------------------------------------------------------------------------------*/
}
/**
* Class BaseConfig
*
* Common methods for manipulating the list, loading and saving is
* left to the concrete implementation
*/
abstract class BaseConfig implements Config
{
public $values = [];
public function set_int(string $name, ?int $value): void
{
$this->values[$name] = is_null($value) ? null : $value;
$this->save($name);
}
public function set_float(string $name, ?float $value): void
{
$this->values[$name] = $value;
$this->save($name);
}
public function set_string(string $name, ?string $value): void
{
$this->values[$name] = $value;
$this->save($name);
}
public function set_bool(string $name, ?bool $value): void
{
$this->values[$name] = $value ? 'Y' : 'N';
$this->save($name);
}
public function set_array(string $name, ?array $value): void
{
if ($value!=null) {
$this->values[$name] = implode(",", $value);
} else {
$this->values[$name] = null;
}
$this->save($name);
}
public function set_default_int(string $name, int $value): void
{
if (is_null($this->get($name))) {
$this->values[$name] = $value;
}
}
public function set_default_float(string $name, float $value): void
{
if (is_null($this->get($name))) {
$this->values[$name] = $value;
}
}
public function set_default_string(string $name, string $value): void
{
if (is_null($this->get($name))) {
$this->values[$name] = $value;
}
}
public function set_default_bool(string $name, bool $value): void
{
if (is_null($this->get($name))) {
$this->values[$name] = $value ? 'Y' : 'N';
}
}
public function set_default_array(string $name, array $value): void
{
if (is_null($this->get($name))) {
$this->values[$name] = implode(",", $value);
}
}
public function get_int(string $name, ?int $default=null): ?int
{
return (int)($this->get($name, $default));
}
public function get_float(string $name, ?float $default=null): ?float
{
return (float)($this->get($name, $default));
}
public function get_string(string $name, ?string $default=null): ?string
{
$val = $this->get($name, $default);
if (!is_string($val) && !is_null($val)) {
throw new SCoreException("$name is not a string: $val");
}
return $val;
}
public function get_bool(string $name, ?bool $default=null): ?bool
{
return bool_escape($this->get($name, $default));
}
public function get_array(string $name, ?array $default=[]): ?array
{
return explode(",", $this->get($name, ""));
}
private function get(string $name, $default=null)
{
if (isset($this->values[$name])) {
return $this->values[$name];
} else {
return $default;
}
}
}
/**
* Class DatabaseConfig
*
* Loads the config list from a table in a given database, the table should
* be called config and have the schema:
*
* \code
* CREATE TABLE config(
* name VARCHAR(255) NOT NULL,
* value TEXT
* );
* \endcode
*/
class DatabaseConfig extends BaseConfig
{
/** @var Database */
private $database = null;
private $table_name;
private $sub_column;
private $sub_value;
public function __construct(
Database $database,
string $table_name = "config",
string $sub_column = null,
string $sub_value = null
) {
global $cache;
$this->database = $database;
$this->table_name = $table_name;
$this->sub_value = $sub_value;
$this->sub_column = $sub_column;
$cache_name = "config";
if (!empty($sub_value)) {
$cache_name .= "_".$sub_value;
}
$cached = $cache->get($cache_name);
if ($cached) {
$this->values = $cached;
} else {
$this->values = [];
$query = "SELECT name, value FROM {$this->table_name}";
$args = [];
if (!empty($sub_column)&&!empty($sub_value)) {
$query .= " WHERE $sub_column = :sub_value";
$args["sub_value"] = $sub_value;
}
foreach ($this->database->get_all($query, $args) as $row) {
$this->values[$row["name"]] = $row["value"];
}
$cache->set($cache_name, $this->values);
}
}
public function save(string $name=null): void
{
global $cache;
if (is_null($name)) {
reset($this->values); // rewind the array to the first element
foreach ($this->values as $name => $value) {
$this->save($name);
}
} else {
$query = "DELETE FROM {$this->table_name} WHERE name = :name";
$args = ["name"=>$name];
$cols = ["name","value"];
$params = [":name",":value"];
if (!empty($this->sub_column)&&!empty($this->sub_value)) {
$query .= " AND $this->sub_column = :sub_value";
$args["sub_value"] = $this->sub_value;
$cols[] = $this->sub_column;
$params[] = ":sub_value";
}
$this->database->Execute($query, $args);
$args["value"] =$this->values[$name];
$this->database->Execute(
"INSERT INTO {$this->table_name} (".join(",", $cols).") VALUES (".join(",", $params).")",
$args
);
}
// rather than deleting and having some other request(s) do a thundering
// herd of race-conditioned updates, just save the updated version once here
$cache->set("config", $this->values);
}
}

View File

@ -1,445 +0,0 @@
<?php
/** @privatesection */
// Querylet {{{
class Querylet {
var $sql;
var $variables;
public function Querylet($sql, $variables=array()) {
$this->sql = $sql;
$this->variables = $variables;
}
public function append($querylet) {
assert(!is_null($querylet));
$this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables);
}
public function append_sql($sql) {
$this->sql .= $sql;
}
public function add_variable($var) {
$this->variables[] = $var;
}
}
class TagQuerylet {
var $tag;
var $positive;
public function TagQuerylet($tag, $positive) {
$this->tag = $tag;
$this->positive = $positive;
}
}
class ImgQuerylet {
var $qlet;
var $positive;
public function ImgQuerylet($qlet, $positive) {
$this->qlet = $qlet;
$this->positive = $positive;
}
}
// }}}
// {{{ db engines
class DBEngine {
var $name = null;
public function init($db) {}
public function scoreql_to_sql($scoreql) {
return $scoreql;
}
public function create_table_sql($name, $data) {
return 'CREATE TABLE '.$name.' ('.$data.')';
}
}
class MySQL extends DBEngine {
var $name = "mysql";
public function init($db) {
$db->query("SET NAMES utf8;");
}
public function scoreql_to_sql($data) {
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY auto_increment", $data);
$data = str_replace("SCORE_INET", "CHAR(45)", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_BOOL", "ENUM('Y', 'N')", $data);
$data = str_replace("SCORE_DATETIME", "DATETIME", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_STRNORM", "", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
return $data;
}
public function create_table_sql($name, $data) {
$data = $this->scoreql_to_sql($data);
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
}
class PostgreSQL extends DBEngine {
var $name = "pgsql";
public function init($db) {
$db->query("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
}
public function scoreql_to_sql($data) {
$data = str_replace("SCORE_AIPK", "SERIAL PRIMARY KEY", $data);
$data = str_replace("SCORE_INET", "INET", $data);
$data = str_replace("SCORE_BOOL_Y", "'t'", $data);
$data = str_replace("SCORE_BOOL_N", "'f'", $data);
$data = str_replace("SCORE_BOOL", "BOOL", $data);
$data = str_replace("SCORE_DATETIME", "TIMESTAMP", $data);
$data = str_replace("SCORE_NOW", "current_time", $data);
$data = str_replace("SCORE_STRNORM", "lower", $data);
$data = str_replace("SCORE_ILIKE", "ILIKE", $data);
return $data;
}
public function create_table_sql($name, $data) {
$data = $this->scoreql_to_sql($data);
return 'CREATE TABLE '.$name.' ('.$data.')';
}
}
// shimmie functions for export to sqlite
function _unix_timestamp($date) { return strtotime($date); }
function _now() { return date("Y-m-d h:i:s"); }
function _floor($a) { return floor($a); }
function _log($a, $b=null) {
if(is_null($b)) return log($a);
else return log($a, $b);
}
function _isnull($a) { return is_null($a); }
function _md5($a) { return md5($a); }
function _concat($a, $b) { return $a . $b; }
function _lower($a) { return strtolower($a); }
class SQLite extends DBEngine {
var $name = "sqlite";
public function init($db) {
ini_set('sqlite.assoc_case', 0);
$db->execute("PRAGMA foreign_keys = ON;");
@sqlite_create_function($db->_connectionID, 'UNIX_TIMESTAMP', '_unix_timestamp', 1);
@sqlite_create_function($db->_connectionID, 'now', '_now', 0);
@sqlite_create_function($db->_connectionID, 'floor', '_floor', 1);
@sqlite_create_function($db->_connectionID, 'log', '_log');
@sqlite_create_function($db->_connectionID, 'isnull', '_isnull', 1);
@sqlite_create_function($db->_connectionID, 'md5', '_md5', 1);
@sqlite_create_function($db->_connectionID, 'concat', '_concat', 2);
@sqlite_create_function($db->_connectionID, 'lower', '_lower', 1);
}
public function create_table_sql($name, $data) {
$data = str_replace("SCORE_AIPK", "INTEGER PRIMARY KEY", $data);
$data = str_replace("SCORE_INET", "VARCHAR(45)", $data);
$data = str_replace("SCORE_BOOL_Y", "'Y'", $data);
$data = str_replace("SCORE_BOOL_N", "'N'", $data);
$data = str_replace("SCORE_BOOL", "CHAR(1)", $data);
$data = str_replace("SCORE_NOW", "\"1970-01-01\"", $data);
$data = str_replace("SCORE_STRNORM", "", $data);
$data = str_replace("SCORE_ILIKE", "LIKE", $data);
$cols = array();
$extras = "";
foreach(explode(",", $data) as $bit) {
$matches = array();
if(preg_match("/INDEX\s*\((.*)\)/", $bit, $matches)) {
$col = $matches[1];
$extras .= 'CREATE INDEX '.$name.'_'.$col.' on '.$name($col).';';
}
else {
$cols[] = $bit;
}
}
$cols_redone = implode(", ", $cols);
return 'CREATE TABLE '.$name.' ('.$cols_redone.'); '.$extras;
}
}
// }}}
// {{{ cache engines
interface CacheEngine {
public function get($key);
public function set($key, $val, $time=0);
public function delete($key);
public function get_hits();
public function get_misses();
}
class NoCache implements CacheEngine {
public function get($key) {return false;}
public function set($key, $val, $time=0) {}
public function delete($key) {}
public function get_hits() {return 0;}
public function get_misses() {return 0;}
}
class MemcacheCache implements CacheEngine {
var $memcache=null, $hits=0, $misses=0;
public function __construct($args) {
$hp = explode(":", $args);
if(class_exists("Memcache")) {
$this->memcache = new Memcache;
@$this->memcache->pconnect($hp[0], $hp[1]);
}
}
public function get($key) {
assert(!is_null($key));
$val = $this->memcache->get($key);
if($val) {
$this->hits++;
return $val;
}
else {
$this->misses++;
return false;
}
}
public function set($key, $val, $time=0) {
assert(!is_null($key));
$this->memcache->set($key, $val, false, $time);
}
public function delete($key) {
assert(!is_null($key));
$this->memcache->delete($key);
}
public function get_hits() {return $this->hits;}
public function get_misses() {return $this->misses;}
}
class APCCache implements CacheEngine {
var $hits=0, $misses=0;
public function __construct($args) {}
public function get($key) {
assert(!is_null($key));
$val = apc_fetch($key);
if($val) {
$this->hits++;
return $val;
}
else {
$this->misses++;
return false;
}
}
public function set($key, $val, $time=0) {
assert(!is_null($key));
apc_store($key, $val, $time);
}
public function delete($key) {
assert(!is_null($key));
apc_delete($key);
}
public function get_hits() {return $this->hits;}
public function get_misses() {return $this->misses;}
}
// }}}
/** @publicsection */
/**
* A class for controlled database access
*/
class Database {
/**
* The PDO database connection object, for anyone who wants direct access
*/
var $db;
/**
* Meta info about the database engine
*/
var $engine = null;
/**
* The currently active cache engine
*/
var $cache = null;
/**
* Create a new database object using connection info
* stored in the config file
*/
public function Database() {
# FIXME: detect ADODB URI, automatically translate PDO DSN
/*
* Why does the abstraction layer act differently depending on the
* back-end? Because PHP is deliberately retarded.
*
* http://stackoverflow.com/questions/237367
*/
$matches = array(); $db_user=null; $db_pass=null;
if(preg_match("/user=([^;]*)/", DATABASE_DSN, $matches)) $db_user=$matches[1];
if(preg_match("/password=([^;]*)/", DATABASE_DSN, $matches)) $db_pass=$matches[1];
$db_params = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
if(defined("HIPHOP")) $this->db = new PDO(DATABASE_DSN, $db_user, $db_pass);
else $this->db = new PDO(DATABASE_DSN, $db_user, $db_pass, $db_params);
$db_proto = $this->db->getAttribute(PDO::ATTR_DRIVER_NAME);
if($db_proto === "mysql") {
$this->engine = new MySQL();
}
else if($db_proto === "pgsql") {
$this->engine = new PostgreSQL();
}
else if($db_proto === "sqlite") {
$this->engine = new SQLite();
}
else {
die('Unknown PDO driver: '.$db_proto);
}
$matches = array();
if( defined("CACHE_DSN") && CACHE_DSN && preg_match("#(memcache|apc)://(.*)#", CACHE_DSN, $matches)) {
if($matches[1] == "memcache") {
$this->cache = new MemcacheCache($matches[2]);
}
else if($matches[1] == "apc") {
$this->cache = new APCCache($matches[2]);
}
}
else {
$this->cache = new NoCache();
}
$this->engine->init($this->db);
}
/**
* Execute an SQL query and return an PDO resultset
*/
public function execute($query, $args=array()) {
try {
_count_execs($this->db, $query, $args);
$stmt = $this->db->prepare($query);
if (!array_key_exists(0, $args)) {
foreach($args as $name=>$value) {
if(is_numeric($value)) {
$stmt->bindValue(':'.$name, $value, PDO::PARAM_INT);
}
else {
$stmt->bindValue(':'.$name, $value, PDO::PARAM_STR);
}
}
$stmt->execute();
}
else {
$stmt->execute($args);
}
return $stmt;
}
catch(PDOException $pdoe) {
throw new SCoreException($pdoe->getMessage()."<p><b>Query:</b> ".$query);
}
}
/**
* Execute an SQL query and return a 2D array
*/
public function get_all($query, $args=array()) {
return $this->execute($query, $args)->fetchAll();
}
/**
* Execute an SQL query and return a single row
*/
public function get_row($query, $args=array()) {
$row = $this->execute($query, $args)->fetch();
return $row ? $row : null;
}
/**
* Execute an SQL query and return the first column of each row
*/
public function get_col($query, $args=array()) {
$stmt = $this->execute($query, $args);
$res = array();
foreach($stmt as $row) {
$res[] = $row[0];
}
return $res;
}
/**
* Execute an SQL query and return the the first row => the second rown
*/
public function get_pairs($query, $args=array()) {
$stmt = $this->execute($query, $args);
$res = array();
foreach($stmt as $row) {
$res[$row[0]] = $row[1];
}
return $res;
}
/**
* Execute an SQL query and return a single value
*/
public function get_one($query, $args=array()) {
$row = $this->execute($query, $args)->fetch();
return $row[0];
}
/**
* get the ID of the last inserted row
*/
public function get_last_insert_id($seq) {
if($this->engine->name == "pgsql") {
return $this->db->lastInsertId($seq);
}
else {
return $this->db->lastInsertId();
}
}
/**
* Create a table from pseudo-SQL
*/
public function create_table($name, $data) {
$this->execute($this->engine->create_table_sql($name, $data));
}
/**
* Returns the number of tables present in the current database.
*/
public function count_tables() {
if($this->engine->name === "mysql") {
return count(
$this->get_all("SHOW TABLES")
);
} else if ($this->engine->name === "pgsql") {
return count(
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
);
} else if ($this->engine->name === "sqlite") {
return count(
$this->get_all(".tables")
);
} else {
// Hard to find a universal way to do this...
return NULL;
}
}
}
?>

339
core/database.php Normal file
View File

@ -0,0 +1,339 @@
<?php declare(strict_types=1);
use FFSPHP\PDO;
abstract class DatabaseDriver
{
public const MYSQL = "mysql";
public const PGSQL = "pgsql";
public const SQLITE = "sqlite";
}
/**
* A class for controlled database access
*/
class Database
{
/** @var string */
private $dsn;
/**
* The PDO database connection object, for anyone who wants direct access.
* @var null|PDO
*/
private $db = null;
/**
* @var float
*/
public $dbtime = 0.0;
/**
* Meta info about the database engine.
* @var DBEngine|null
*/
private $engine = null;
/**
* A boolean flag to track if we already have an active transaction.
* (ie: True if beginTransaction() already called)
*
* @var bool
*/
public $transaction = false;
/**
* How many queries this DB object has run
*/
public $query_count = 0;
public function __construct(string $dsn)
{
$this->dsn = $dsn;
}
private function connect_db(): void
{
$this->db = new PDO($this->dsn, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$this->connect_engine();
$this->engine->init($this->db);
$this->begin_transaction();
}
private function connect_engine(): void
{
if (preg_match("/^([^:]*)/", $this->dsn, $matches)) {
$db_proto=$matches[1];
} else {
throw new SCoreException("Can't figure out database engine");
}
if ($db_proto === DatabaseDriver::MYSQL) {
$this->engine = new MySQL();
} elseif ($db_proto === DatabaseDriver::PGSQL) {
$this->engine = new PostgreSQL();
} elseif ($db_proto === DatabaseDriver::SQLITE) {
$this->engine = new SQLite();
} else {
die_nicely(
'Unknown PDO driver: '.$db_proto,
"Please check that this is a valid driver, installing the PHP modules if needed"
);
}
}
public function begin_transaction(): void
{
if ($this->transaction === false) {
$this->db->beginTransaction();
$this->transaction = true;
}
}
public function is_transaction_open(): bool
{
return !is_null($this->db) && $this->transaction === true;
}
public function commit(): bool
{
if ($this->is_transaction_open()) {
$this->transaction = false;
return $this->db->commit();
} else {
throw new SCoreException("Unable to call commit() as there is no transaction currently open.");
}
}
public function rollback(): bool
{
if ($this->is_transaction_open()) {
$this->transaction = false;
return $this->db->rollback();
} else {
throw new SCoreException("Unable to call rollback() as there is no transaction currently open.");
}
}
public function scoreql_to_sql(string $input): string
{
if (is_null($this->engine)) {
$this->connect_engine();
}
return $this->engine->scoreql_to_sql($input);
}
public function scoresql_value_prepare($input)
{
if (is_null($this->engine)) {
$this->connect_engine();
}
if ($input===true) {
return $this->engine->BOOL_Y;
} elseif ($input===false) {
return $this->engine->BOOL_N;
}
return $input;
}
public function get_driver_name(): string
{
if (is_null($this->engine)) {
$this->connect_engine();
}
return $this->engine->name;
}
public function get_version(): string
{
return $this->engine->get_version($this->db);
}
private function count_time(string $method, float $start, string $query, ?array $args): void
{
global $_tracer, $tracer_enabled;
$dur = microtime(true) - $start;
if ($tracer_enabled) {
$query = trim(preg_replace('/^[\t ]+/m', '', $query)); // trim leading whitespace
$_tracer->complete($start * 1000000, $dur * 1000000, "DB Query", ["query"=>$query, "args"=>$args, "method"=>$method]);
}
$this->query_count++;
$this->dbtime += $dur;
}
public function set_timeout(int $time): void
{
$this->engine->set_timeout($this->db, $time);
}
public function execute(string $query, array $args = []): PDOStatement
{
try {
if (is_null($this->db)) {
$this->connect_db();
}
return $this->db->execute(
"-- " . str_replace("%2F", "/", urlencode($_GET['q'] ?? '')). "\n" .
$query,
$args
);
} catch (PDOException $pdoe) {
throw new SCoreException($pdoe->getMessage(), $query);
}
}
/**
* Execute an SQL query and return a 2D array.
*/
public function get_all(string $query, array $args = []): array
{
$_start = microtime(true);
$data = $this->execute($query, $args)->fetchAll();
$this->count_time("get_all", $_start, $query, $args);
return $data;
}
/**
* Execute an SQL query and return a iterable object for use with generators.
*/
public function get_all_iterable(string $query, array $args = []): PDOStatement
{
$_start = microtime(true);
$data = $this->execute($query, $args);
$this->count_time("get_all_iterable", $_start, $query, $args);
return $data;
}
/**
* Execute an SQL query and return a single row.
*/
public function get_row(string $query, array $args = []): ?array
{
$_start = microtime(true);
$row = $this->execute($query, $args)->fetch();
$this->count_time("get_row", $_start, $query, $args);
return $row ? $row : null;
}
/**
* Execute an SQL query and return the first column of each row.
*/
public function get_col(string $query, array $args = []): array
{
$_start = microtime(true);
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_COLUMN);
$this->count_time("get_col", $_start, $query, $args);
return $res;
}
/**
* Execute an SQL query and return the first column of each row as a single iterable object.
*/
public function get_col_iterable(string $query, array $args = []): Generator
{
$_start = microtime(true);
$stmt = $this->execute($query, $args);
$this->count_time("get_col_iterable", $_start, $query, $args);
foreach ($stmt as $row) {
yield $row[0];
}
}
/**
* Execute an SQL query and return the the first column => the second column.
*/
public function get_pairs(string $query, array $args = []): array
{
$_start = microtime(true);
$res = $this->execute($query, $args)->fetchAll(PDO::FETCH_KEY_PAIR);
$this->count_time("get_pairs", $_start, $query, $args);
return $res;
}
/**
* Execute an SQL query and return a single value, or null.
*/
public function get_one(string $query, array $args = [])
{
$_start = microtime(true);
$row = $this->execute($query, $args)->fetch();
$this->count_time("get_one", $_start, $query, $args);
return $row ? $row[0] : null;
}
/**
* Execute an SQL query and returns a bool indicating if any data was returned
*/
public function exists(string $query, array $args = []): bool
{
$_start = microtime(true);
$row = $this->execute($query, $args)->fetch();
$this->count_time("exists", $_start, $query, $args);
if ($row==null) {
return false;
}
return true;
}
/**
* Get the ID of the last inserted row.
*/
public function get_last_insert_id(string $seq): int
{
if ($this->engine->name == DatabaseDriver::PGSQL) {
$id = $this->db->lastInsertId($seq);
} else {
$id = $this->db->lastInsertId();
}
assert(is_numeric($id));
return (int)$id;
}
/**
* Create a table from pseudo-SQL.
*/
public function create_table(string $name, string $data): void
{
if (is_null($this->engine)) {
$this->connect_engine();
}
$data = trim($data, ", \t\n\r\0\x0B"); // mysql doesn't like trailing commas
$this->execute($this->engine->create_table_sql($name, $data));
}
/**
* Returns the number of tables present in the current database.
*
* @throws SCoreException
*/
public function count_tables(): int
{
if (is_null($this->db) || is_null($this->engine)) {
$this->connect_db();
}
if ($this->engine->name === DatabaseDriver::MYSQL) {
return count(
$this->get_all("SHOW TABLES")
);
} elseif ($this->engine->name === DatabaseDriver::PGSQL) {
return count(
$this->get_all("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'")
);
} elseif ($this->engine->name === DatabaseDriver::SQLITE) {
return count(
$this->get_all("SELECT name FROM sqlite_master WHERE type = 'table'")
);
} else {
throw new SCoreException("Can't count tables for database type {$this->engine->name}");
}
}
public function raw_db(): PDO
{
return $this->db;
}
}

236
core/dbengine.php Normal file
View File

@ -0,0 +1,236 @@
<?php declare(strict_types=1);
abstract class SCORE
{
const AIPK = "SCORE_AIPK";
const INET = "SCORE_INET";
const BOOL_Y = "SCORE_BOOL_Y";
const BOOL_N = "SCORE_BOOL_N";
const BOOL = "SCORE_BOOL";
}
abstract class DBEngine
{
/** @var null|string */
public $name = null;
public $BOOL_Y = null;
public $BOOL_N = null;
public function init(PDO $db)
{
}
public function scoreql_to_sql(string $scoreql): string
{
return $scoreql;
}
public function create_table_sql(string $name, string $data): string
{
return 'CREATE TABLE '.$name.' ('.$data.')';
}
abstract public function set_timeout(PDO $db, int $time);
abstract public function get_version(PDO $db): string;
}
class MySQL extends DBEngine
{
/** @var string */
public $name = DatabaseDriver::MYSQL;
public $BOOL_Y = 'Y';
public $BOOL_N = 'N';
public function init(PDO $db)
{
$db->exec("SET NAMES utf8;");
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY auto_increment", $data);
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
$data = str_replace(SCORE::BOOL, "ENUM('Y', 'N')", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
$ctes = "ENGINE=InnoDB DEFAULT CHARSET='utf8'";
return 'CREATE TABLE '.$name.' ('.$data.') '.$ctes;
}
public function set_timeout(PDO $db, int $time): void
{
// These only apply to read-only queries, which appears to be the best we can to mysql-wise
// $db->exec("SET SESSION MAX_EXECUTION_TIME=".$time.";");
}
public function get_version(PDO $db): string
{
return $db->query('select version()')->fetch()[0];
}
}
class PostgreSQL extends DBEngine
{
/** @var string */
public $name = DatabaseDriver::PGSQL;
public $BOOL_Y = "true";
public $BOOL_N = "false";
public function init(PDO $db)
{
if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
$db->exec("SET application_name TO 'shimmie [{$_SERVER['REMOTE_ADDR']}]';");
} else {
$db->exec("SET application_name TO 'shimmie [local]';");
}
if (defined("DATABASE_TIMEOUT")) {
$this->set_timeout($db, DATABASE_TIMEOUT);
}
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace(SCORE::AIPK, "INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY", $data);
$data = str_replace(SCORE::INET, "INET", $data);
$data = str_replace(SCORE::BOOL_Y, "true", $data);
$data = str_replace(SCORE::BOOL_N, "false", $data);
$data = str_replace(SCORE::BOOL, "BOOL", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
return "CREATE TABLE $name ($data)";
}
public function set_timeout(PDO $db, int $time): void
{
$db->exec("SET statement_timeout TO ".$time.";");
}
public function get_version(PDO $db): string
{
return $db->query('select version()')->fetch()[0];
}
}
// shimmie functions for export to sqlite
function _unix_timestamp($date)
{
return strtotime($date);
}
function _now()
{
return date("Y-m-d H:i:s");
}
function _floor($a)
{
return floor($a);
}
function _log($a, $b=null)
{
if (is_null($b)) {
return log($a);
} else {
return log($a, $b);
}
}
function _isnull($a)
{
return is_null($a);
}
function _md5($a)
{
return md5($a);
}
function _concat($a, $b)
{
return $a . $b;
}
function _lower($a)
{
return strtolower($a);
}
function _rand()
{
return rand();
}
function _ln($n)
{
return log($n);
}
class SQLite extends DBEngine
{
/** @var string */
public $name = DatabaseDriver::SQLITE;
public $BOOL_Y = 'Y';
public $BOOL_N = 'N';
public function init(PDO $db)
{
ini_set('sqlite.assoc_case', '0');
$db->exec("PRAGMA foreign_keys = ON;");
$db->sqliteCreateFunction('UNIX_TIMESTAMP', '_unix_timestamp', 1);
$db->sqliteCreateFunction('now', '_now', 0);
$db->sqliteCreateFunction('floor', '_floor', 1);
$db->sqliteCreateFunction('log', '_log');
$db->sqliteCreateFunction('isnull', '_isnull', 1);
$db->sqliteCreateFunction('md5', '_md5', 1);
$db->sqliteCreateFunction('concat', '_concat', 2);
$db->sqliteCreateFunction('lower', '_lower', 1);
$db->sqliteCreateFunction('rand', '_rand', 0);
$db->sqliteCreateFunction('ln', '_ln', 1);
}
public function scoreql_to_sql(string $data): string
{
$data = str_replace(SCORE::AIPK, "INTEGER PRIMARY KEY", $data);
$data = str_replace(SCORE::INET, "VARCHAR(45)", $data);
$data = str_replace(SCORE::BOOL_Y, "'$this->BOOL_Y'", $data);
$data = str_replace(SCORE::BOOL_N, "'$this->BOOL_N'", $data);
$data = str_replace(SCORE::BOOL, "CHAR(1)", $data);
return $data;
}
public function create_table_sql(string $name, string $data): string
{
$data = $this->scoreql_to_sql($data);
$cols = [];
$extras = "";
foreach (explode(",", $data) as $bit) {
$matches = [];
if (preg_match("/(UNIQUE)? ?INDEX\s*\((.*)\)/", $bit, $matches)) {
$uni = $matches[1];
$col = $matches[2];
$extras .= "CREATE $uni INDEX {$name}_{$col} ON {$name}({$col});";
} else {
$cols[] = $bit;
}
}
$cols_redone = implode(", ", $cols);
return "CREATE TABLE $name ($cols_redone); $extras";
}
public function set_timeout(PDO $db, int $time): void
{
// There doesn't seem to be such a thing for SQLite, so it does nothing
}
public function get_version(PDO $db): string
{
return $db->query('select sqlite_version()')->fetch()[0];
}
}

View File

@ -1,119 +0,0 @@
<?php
class Email {
/**
* A generic email.
*/
var $to;
var $subject;
var $header;
var $style;
var $header_img;
var $sitename;
var $sitedomain;
var $siteemail;
var $date;
var $body;
var $footer;
public function __construct($to, $subject, $header, $body) {
global $config;
$this->to = $to;
$sub_prefix = $config->get_string("mail_sub");
if(!isset($sub_prefix)){
$this->subject = $subject;
}
else{
$this->subject = $sub_prefix." ".$subject;
}
$this->style = $config->get_string("mail_style");
$this->header = html_escape($header);
$this->header_img = $config->get_string("mail_img");
$this->sitename = $config->get_string("site_title");
$this->sitedomain = make_http(make_link(""));
$this->siteemail = $config->get_string("site_email");
$this->date = date("F j, Y");
$this->body = $body;
$this->footer = $config->get_string("mail_fot");
}
public function send() {
$headers = "From: ".$this->sitename." <".$this->siteemail.">\r\n";
$headers .= "Reply-To: ".$this->siteemail."\r\n";
$headers .= "X-Mailer: PHP/" . phpversion(). "\r\n";
$headers .= "errors-to: ".$this->siteemail."\r\n";
$headers .= "Date: " . date(DATE_RFC2822);
$headers .= 'MIME-Version: 1.0' . "\r\n";
$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$message = '
<html>
<head>
<link rel="stylesheet" href="'.$this->style.'" type="text/css">
</head>
<body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0" bgcolor="#EEEEEE" >
<table width="100%" cellpadding="10" cellspacing="0" class="backgroundTable" bgcolor="#EEEEEE" >
<tr>
<td valign="top" align="center">
<table width="550" cellpadding="0" cellspacing="0">
<tr>
<td style="background-color:#FFFFFF;border-top:0px solid #333333;border-bottom:10px solid #FFFFFF;"><center><a href="'.$this->sitedomain.'"><IMG SRC="'.$this->header_img.'" alt="'.$this->sitename.'" name="Header" BORDER="0" align="center" title="'.$this->sitename.'"></a>
</center></td>
</tr>
</table>
<table width="550" cellpadding="20" cellspacing="0" bgcolor="#FFFFFF">
<tr>
<td bgcolor="#FFFFFF" valign="top" style="font-size:12px;color:#000000;line-height:150%;font-family:trebuchet ms;">
<p>
<span style="font-size:20px; font-weight:bold; color:#3399FF; font-family:arial; line-height:110%;">'.$this->header.'</span><br>
<span style="font-size:11px;font-weight:normal;color:#666666;font-style:italic;font-family:arial;">'.$this->date.'</span><br>
</p>
<p>'.$this->body.'</p>
<p>'.$this->footer.'</p>
</td>
</tr>
<tr>
<td style="background-color:#FFFFCC;border-top:10px solid #FFFFFF;" valign="top">
<span style="font-size:10px;color:#996600;line-height:100%;font-family:verdana;">
This email was sent to you since you are a member of <a href="'.$this->sitedomain.'">'.$this->sitename.'</a>. To change your email preferences, visit your <a href="'.make_http(make_link("preferences")).'">Account preferences</a>.<br />
<br />
Contact us:<br />
<a href="'.$this->siteemail.'">'.$this->siteemail.'</a><br /><br />
Copyright (C) <a href="'.$this->sitedomain.'">'.$this->sitename.'</a><br />
</span></td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
';
$sent = mail($this->to, $this->subject, $message, $headers);
if($sent){
log_info("mail", "Sent message '$this->subject' to '$this->to'");
}
else{
log_info("mail", "Error sending message '$this->subject' to '$this->to'");
}
return $sent;
}
}
?>

View File

@ -1,181 +0,0 @@
<?php
/**
* Generic parent class for all events.
*
* An event is anything that can be passed around via send_event($blah)
*/
abstract class Event {
public function __construct() {}
}
/**
* A wake-up call for extensions. Upon recieving an InitExtEvent an extension
* should check that it's database tables are there and install them if not,
* and set any defaults with Config::set_default_int() and such.
*/
class InitExtEvent extends Event {}
/**
* A signal that a page has been requested.
*
* User requests /view/42 -> an event is generated with $args = array("view",
* "42"); when an event handler asks $event->page_matches("view"), it returns
* true and ignores the matched part, such that $event->count_args() = 1 and
* $event->get_arg(0) = "42"
*/
class PageRequestEvent extends Event {
var $args;
var $arg_count;
var $part_count;
public function __construct($args) {
$this->args = $args;
$this->arg_count = count($args);
}
/**
* Test if the requested path matches a given pattern.
*
* If it matches, store the remaining path elements in $args
*
* @retval bool
*/
public function page_matches(/*string*/ $name) {
$parts = explode("/", $name);
$this->part_count = count($parts);
if($this->part_count > $this->arg_count) {
return false;
}
for($i=0; $i<$this->part_count; $i++) {
if($parts[$i] != $this->args[$i]) {
return false;
}
}
return true;
}
/**
* Get the n th argument of the page request (if it exists.)
* @param $n integer
* @retval The argmuent (string) or NULL
*/
public function get_arg(/*int*/ $n) {
$offset = $this->part_count + $n;
if($offset >= 0 && $offset < $this->arg_count) {
return $this->args[$offset];
}
else {
return null;
}
}
/**
* Returns the number of arguments the page request has.
* @retval int
*/
public function count_args() {
return (int)($this->arg_count - $this->part_count);
}
/*
* Many things use these functions
*/
public function get_search_terms() {
$search_terms = array();
if($this->count_args() === 2) {
$search_terms = explode(' ', $this->get_arg(0));
}
return $search_terms;
}
public function get_page_number() {
$page_number = 1;
if($this->count_args() === 1) {
$page_number = int_escape($this->get_arg(0));
}
else if($this->count_args() === 2) {
$page_number = int_escape($this->get_arg(1));
}
if($page_number === 0) $page_number = 1; // invalid -> 0
return $page_number;
}
public function get_page_size() {
global $config;
return $config->get_int('index_images');
}
}
/**
* A signal that some text needs formatting, the event carries
* both the text and the result
*/
class TextFormattingEvent extends Event {
/**
* For reference
*/
var $original;
/**
* with formatting applied
*/
var $formatted;
/**
* with formatting removed
*/
var $stripped;
public function __construct(/*string*/ $text) {
$h_text = html_escape(trim($text));
$this->original = $h_text;
$this->formatted = $h_text;
$this->stripped = $h_text;
}
}
/**
* A signal that something needs logging
*/
class LogEvent extends Event {
/**
* a category, normally the extension name
*
* @retval string
*/
var $section;
/**
* See python...
*
* @retval int
*/
var $priority = 0;
/**
* Free text to be logged
*
* @retval text
*/
var $message;
/**
* The time that the event was created
*
* @retval int
*/
var $time;
public function __construct($section, $priority, $message) {
$this->section = $section;
$this->priority = $priority;
$this->message = $message;
$this->time = time();
}
}
?>

344
core/event.php Normal file
View File

@ -0,0 +1,344 @@
<?php declare(strict_types=1);
/**
* Generic parent class for all events.
*
* An event is anything that can be passed around via send_event($blah)
*/
abstract class Event
{
public $stop_processing = false;
public function __construct()
{
}
public function __toString()
{
return var_export($this, true);
}
}
/**
* A wake-up call for extensions. Upon recieving an InitExtEvent an extension
* should check that it's database tables are there and install them if not,
* and set any defaults with Config::set_default_int() and such.
*
* This event is sent before $user is set to anything
*/
class InitExtEvent extends Event
{
}
/**
* A signal that a page has been requested.
*
* User requests /view/42 -> an event is generated with $args = array("view",
* "42"); when an event handler asks $event->page_matches("view"), it returns
* true and ignores the matched part, such that $event->count_args() = 1 and
* $event->get_arg(0) = "42"
*/
class PageRequestEvent extends Event
{
/**
* @var array
*/
public $args;
/**
* @var int
*/
public $arg_count;
/**
* @var int
*/
public $part_count;
public function __construct(string $path)
{
parent::__construct();
global $config;
// trim starting slashes
$path = ltrim($path, "/");
// if path is not specified, use the default front page
if (empty($path)) { /* empty is faster than strlen */
$path = $config->get_string(SetupConfig::FRONT_PAGE);
}
// break the path into parts
$args = explode('/', $path);
$this->args = $args;
$this->arg_count = count($args);
}
/**
* Test if the requested path matches a given pattern.
*
* If it matches, store the remaining path elements in $args
*/
public function page_matches(string $name): bool
{
$parts = explode("/", $name);
$this->part_count = count($parts);
if ($this->part_count > $this->arg_count) {
return false;
}
for ($i=0; $i<$this->part_count; $i++) {
if ($parts[$i] != $this->args[$i]) {
return false;
}
}
return true;
}
/**
* Get the n th argument of the page request (if it exists.)
*/
public function get_arg(int $n): string
{
$offset = $this->part_count + $n;
if ($offset >= 0 && $offset < $this->arg_count) {
return $this->args[$offset];
} else {
$nm1 = $this->arg_count - 1;
throw new SCoreException("Requested an invalid page argument {$offset} / {$nm1}");
}
}
/**
* If page arg $n is set, then treat that as a 1-indexed page number
* and return a 0-indexed page number less than $max; else return 0
*/
public function try_page_num(int $n, ?int $max=null): int
{
if ($this->count_args() > $n) {
$i = $this->get_arg($n);
if (is_numeric($i) && int_escape($i) > 0) {
return page_number($i, $max);
} else {
return 0;
}
} else {
return 0;
}
}
/**
* Returns the number of arguments the page request has.
*/
public function count_args(): int
{
return $this->arg_count - $this->part_count;
}
/*
* Many things use these functions
*/
public function get_search_terms(): array
{
$search_terms = [];
if ($this->count_args() === 2) {
$search_terms = Tag::explode(Tag::decaret($this->get_arg(0)));
}
return $search_terms;
}
public function get_page_number(): int
{
$page_number = 1;
if ($this->count_args() === 1) {
$page_number = int_escape($this->get_arg(0));
} elseif ($this->count_args() === 2) {
$page_number = int_escape($this->get_arg(1));
}
if ($page_number === 0) {
$page_number = 1;
} // invalid -> 0
return $page_number;
}
public function get_page_size(): int
{
global $config;
return $config->get_int(IndexConfig::IMAGES);
}
}
/**
* Sent when index.php is called from the command line
*/
class CommandEvent extends Event
{
/**
* @var string
*/
public $cmd = "help";
/**
* @var array
*/
public $args = [];
/**
* #param string[] $args
*/
public function __construct(array $args)
{
parent::__construct();
global $user;
$opts = [];
$log_level = SCORE_LOG_WARNING;
$arg_count = count($args);
for ($i=1; $i<$arg_count; $i++) {
switch ($args[$i]) {
case '-u':
$user = User::by_name($args[++$i]);
if (is_null($user)) {
die("Unknown user");
} else {
send_event(new UserLoginEvent($user));
}
break;
case '-q':
$log_level += 10;
break;
case '-v':
$log_level -= 10;
break;
default:
$opts[] = $args[$i];
break;
}
}
if (!defined("CLI_LOG_LEVEL")) {
define("CLI_LOG_LEVEL", $log_level);
}
if (count($opts) > 0) {
$this->cmd = $opts[0];
$this->args = array_slice($opts, 1);
} else {
print "\n";
print "Usage: php {$args[0]} [flags] [command]\n";
print "\n";
print "Flags:\n";
print "\t-u [username]\n";
print "\t\tLog in as the specified user\n";
print "\t-q / -v\n";
print "\t\tBe quieter / more verbose\n";
print "\t\tScale is debug - info - warning - error - critical\n";
print "\t\tDefault is to show warnings and above\n";
print "\n";
print "Currently known commands:\n";
}
}
}
/**
* A signal that some text needs formatting, the event carries
* both the text and the result
*/
class TextFormattingEvent extends Event
{
/**
* For reference
*
* @var string
*/
public $original;
/**
* with formatting applied
*
* @var string
*/
public $formatted;
/**
* with formatting removed
*
* @var string
*/
public $stripped;
public function __construct(string $text)
{
parent::__construct();
// We need to escape before formatting, instead of at display time,
// because formatters will add their own HTML tags into the mix and
// we don't want to escape those.
$h_text = html_escape(trim($text));
$this->original = $h_text;
$this->formatted = $h_text;
$this->stripped = $h_text;
}
}
/**
* A signal that something needs logging
*/
class LogEvent extends Event
{
/**
* a category, normally the extension name
*
* @var string
*/
public $section;
/**
* See python...
*
* @var int
*/
public $priority = 0;
/**
* Free text to be logged
*
* @var string
*/
public $message;
/**
* The time that the event was created
*
* @var int
*/
public $time;
/**
* Extra data to be held separate
*
* @var array
*/
public $args;
public function __construct(string $section, int $priority, string $message)
{
parent::__construct();
$this->section = $section;
$this->priority = $priority;
$this->message = $message;
$this->time = time();
}
}
class DatabaseUpgradeEvent extends Event
{
}

View File

@ -1,11 +0,0 @@
<?php
/**
* A base exception to be caught by the upper levels
*/
class SCoreException extends Exception {}
/**
* A fairly common, generic exception
*/
class PermissionDeniedException extends SCoreException {}
?>

83
core/exceptions.php Normal file
View File

@ -0,0 +1,83 @@
<?php declare(strict_types=1);
/**
* Class SCoreException
*
* A base exception to be caught by the upper levels.
*/
class SCoreException extends RuntimeException
{
/** @var string|null */
public $query;
/** @var string */
public $error;
public function __construct(string $msg, ?string $query=null)
{
parent::__construct($msg);
$this->error = $msg;
$this->query = $query;
}
}
class InstallerException extends RuntimeException
{
/** @var string */
public $title;
/** @var string */
public $body;
/** @var int */
public $code;
public function __construct(string $title, string $body, int $code)
{
parent::__construct($body);
$this->title = $title;
$this->body = $body;
$this->code = $code;
}
}
/**
* Class PermissionDeniedException
*
* A fairly common, generic exception.
*/
class PermissionDeniedException extends SCoreException
{
}
/**
* Class ImageDoesNotExist
*
* This exception is used when an Image cannot be found by ID.
*
* Example: Image::by_id(-1) returns null
*/
class ImageDoesNotExist extends SCoreException
{
}
/*
* For validate_input()
*/
class InvalidInput extends SCoreException
{
}
/*
* This is used by the image resizing code when there is not enough memory to perform a resize.
*/
class InsufficientMemoryException extends SCoreException
{
}
/*
* This is used by the image resizing code when there is an error while resizing
*/
class ImageResizeException extends SCoreException
{
}

View File

@ -1,230 +0,0 @@
<?php
/**
* \page eande Events and Extensions
*
* An event is a little blob of data saying "something happened", possibly
* "something happened, here's the specific data". Events are sent with the
* send_event() function. Since events can store data, they can be used to
* return data to the extension which sent them, for example:
*
* \code
* $tfe = new TextFormattingEvent($original_text);
* send_event($tfe);
* $formatted_text = $tfe->formatted;
* \endcode
*
* An extension is something which is capable of reacting to events.
*
*
* \page hello The Hello World Extension
*
* \code
* // ext/hello/main.php
* public class HelloEvent extends Event {
* public function __construct($username) {
* $this->username = $username;
* }
* }
*
* public class Hello extends Extension {
* public function onPageRequest(PageRequestEvent $event) { // Every time a page request is sent
* global $user; // Look at the global "currently logged in user" object
* send_event(new HelloEvent($user->name)); // Broadcast a signal saying hello to that user
* }
* public function onHello(HelloEvent $event) { // When the "Hello" signal is recieved
* $this->theme->display_hello($event->username); // Display a message on the web page
* }
* }
*
* // ext/hello/theme.php
* public class HelloTheme extends Themelet {
* public function display_hello($username) {
* global $page;
* $h_user = html_escape($username); // Escape the data before adding it to the page
* $block = new Block("Hello!", "Hello there $h_user"); // HTML-safe variables start with "h_"
* $page->add_block($block); // Add the block to the page
* }
* }
*
* // ext/hello/test.php
* public class HelloTest extends SCoreWebTestCase {
* public function testHello() {
* $this->get_page("post/list"); // View a page, any page
* $this->assert_text("Hello there"); // Check that the specified text is in that page
* }
* }
*
* // themes/mytheme/hello.theme.php
* public class CustomHelloTheme extends HelloTheme { // CustomHelloTheme overrides HelloTheme
* public function display_hello($username) { // the display_hello() function is customised
* global $page;
* $h_user = html_escape($username);
* $page->add_block(new Block(
* "Hello!",
* "Hello there $h_user, look at my snazzy custom theme!"
* );
* }
* }
* \endcode
*
*/
/**
* send_event(BlahEvent()) -> onBlah($event)
*
* Also loads the theme object into $this->theme if available
*
* The original concept came from Artanis's Extension extension
* --> http://github.com/Artanis/simple-extension/tree/master
* Then re-implemented by Shish after he broke the forum and couldn't
* find the thread where the original was posted >_<
*/
abstract class Extension {
/** this theme's Themelet object */
var $theme;
/** @private */
var $_child;
// in PHP5.3, late static bindings can take care of this; __CLASS__
// used here will refer to the subclass
// http://php.net/manual/en/language.oop5.late-static-bindings.php
/** @private */
public function i_am(Extension $child) {
$this->_child = $child;
if(is_null($this->theme)) $this->theme = $this->get_theme_object($child, false);
}
/**
* Find the theme object for a given extension
*/
private function get_theme_object(Extension $class, $fatal=true) {
$base = get_class($class);
if(class_exists('Custom'.$base.'Theme')) {
$class = 'Custom'.$base.'Theme';
return new $class();
}
elseif ($fatal || class_exists($base.'Theme')) {
$class = $base.'Theme';
return new $class();
} else {
return false;
}
}
/**
* Override this to change the priority of the extension,
* lower numbered ones will recieve events first
*/
public function get_priority() {
return 50;
}
}
/**
* Several extensions have this in common, make a common API
*/
abstract class FormatterExtension extends Extension {
public function onTextFormatting(TextFormattingEvent $event) {
$event->formatted = $this->format($event->formatted);
$event->stripped = $this->strip($event->stripped);
}
abstract public function format(/*string*/ $text);
abstract public function strip(/*string*/ $text);
}
/**
* This too is a common class of extension with many methods in common,
* so we have a base class to extend from
*/
abstract class DataHandlerExtension extends Extension {
public function onDataUpload(DataUploadEvent $event) {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
if(!move_upload_to_archive($event)) return;
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
/* Check if we are replacing an image */
if(array_key_exists('replace', $event->metadata) && isset($event->metadata['replace'])) {
/* hax: This seems like such a dirty way to do this.. */
/* Validate things */
$image_id = int_escape($event->metadata['replace']);
/* Check to make sure the image exists. */
$existing = Image::by_id($image_id);
if(is_null($existing)) {
throw new UploadException("Image to replace does not exist!");
}
if ($existing->hash === $event->metadata['hash']) {
throw new UploadException("The uploaded image is the same as the one to replace.");
}
// even more hax..
$event->metadata['tags'] = $existing->get_tag_list();
$image = $this->create_image_from_data(warehouse_path("images", $event->metadata['hash']), $event->metadata);
if(is_null($image)) {
throw new UploadException("Data handler failed to create image object from data");
}
$ire = new ImageReplaceEvent($image_id, $image);
send_event($ire);
$event->image_id = $image_id;
}
else {
$image = $this->create_image_from_data(warehouse_path("images", $event->hash), $event->metadata);
if(is_null($image)) {
throw new UploadException("Data handler failed to create image object from data");
}
$iae = new ImageAdditionEvent($event->user, $image);
send_event($iae);
$event->image_id = $iae->image->id;
// Rating Stuff.
if(!empty($event->metadata['rating'])){
global $user;
$rating = $event->metadata['rating'];
send_event(new RatingSetEvent($image, $user, $rating));
}
// Locked Stuff.
if(!empty($event->metadata['locked'])){
$locked = $event->metadata['locked'];
send_event(new LockSetEvent($image, !empty($locked)));
}
}
}
}
public function onThumbnailGeneration(ThumbnailGenerationEvent $event) {
if($this->supported_ext($event->type)) {
if (method_exists($this, 'create_thumb_force') && $event->force == true) {
$this->create_thumb_force($event->hash);
}
else {
$this->create_thumb($event->hash);
}
}
}
public function onDisplayingImage(DisplayingImageEvent $event) {
global $page;
if($this->supported_ext($event->image->ext)) {
$this->theme->display_image($page, $event->image);
}
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = $this->setup();
if($sb) $event->panel->add_block($sb);
}
protected function setup() {}
abstract protected function supported_ext($ext);
abstract protected function check_contents($tmpname);
abstract protected function create_image_from_data($filename, $metadata);
abstract protected function create_thumb($hash);
}
?>

446
core/extension.php Normal file
View File

@ -0,0 +1,446 @@
<?php declare(strict_types=1);
/**
* Class Extension
*
* send_event(BlahEvent()) -> onBlah($event)
*
* Also loads the theme object into $this->theme if available
*
* The original concept came from Artanis's Extension extension
* --> https://github.com/Artanis/simple-extension/tree/master
* Then re-implemented by Shish after he broke the forum and couldn't
* find the thread where the original was posted >_<
*/
abstract class Extension
{
/** @var string */
public $key;
/** @var Themelet */
protected $theme;
/** @var ExtensionInfo */
public $info;
private static $enabled_extensions = [];
public function __construct($class = null)
{
$class = $class ?? get_called_class();
$this->theme = $this->get_theme_object($class);
$this->info = ExtensionInfo::get_for_extension_class($class);
if ($this->info===null) {
throw new ScoreException("Info class not found for extension $class");
}
$this->key = $this->info->key;
}
/**
* Find the theme object for a given extension.
*/
private function get_theme_object(string $base): ?Themelet
{
$custom = 'Custom'.$base.'Theme';
$normal = $base.'Theme';
if (class_exists($custom)) {
return new $custom();
} elseif (class_exists($normal)) {
return new $normal();
} else {
return null;
}
}
/**
* Override this to change the priority of the extension,
* lower numbered ones will receive events first.
*/
public function get_priority(): int
{
return 50;
}
public static function determine_enabled_extensions()
{
self::$enabled_extensions = [];
foreach (array_merge(
ExtensionInfo::get_core_extensions(),
explode(",", EXTRA_EXTS)
) as $key) {
$ext = ExtensionInfo::get_by_key($key);
if ($ext===null || !$ext->is_supported()) {
continue;
}
// FIXME: error if one of our dependencies isn't supported
self::$enabled_extensions[] = $ext->key;
if (!empty($ext->dependencies)) {
foreach ($ext->dependencies as $dep) {
self::$enabled_extensions[] = $dep;
}
}
}
}
public static function is_enabled(string $key): ?bool
{
return in_array($key, self::$enabled_extensions);
}
public static function get_enabled_extensions(): array
{
return self::$enabled_extensions;
}
public static function get_enabled_extensions_as_string(): string
{
return implode(",", self::$enabled_extensions);
}
protected function get_version(string $name): int
{
global $config;
return $config->get_int($name, 0);
}
protected function set_version(string $name, int $ver)
{
global $config;
$config->set_int($name, $ver);
log_info("upgrade", "Set version for $name to $ver");
}
}
abstract class ExtensionInfo
{
// Every credit you get costs us RAM. It stops now.
public const SHISH_NAME = "Shish";
public const SHISH_EMAIL = "webmaster@shishnet.org";
public const SHIMMIE_URL = "https://code.shishnet.org/shimmie2/";
public const SHISH_AUTHOR = [self::SHISH_NAME=>self::SHISH_EMAIL];
public const LICENSE_GPLV2 = "GPLv2";
public const LICENSE_MIT = "MIT";
public const LICENSE_WTFPL = "WTFPL";
public const VISIBLE_ADMIN = "admin";
public const VISIBLE_HIDDEN = "hidden";
private const VALID_VISIBILITY = [self::VISIBLE_ADMIN, self::VISIBLE_HIDDEN];
public $key;
public $core = false;
public $beta = false;
public $name;
public $authors = [];
public $link;
public $license;
public $version;
public $dependencies = [];
public $visibility;
public $description;
public $documentation;
/** @var array which DBs this ext supports (blank for 'all') */
public $db_support = [];
/** @var bool */
private $supported = null;
/** @var string */
private $support_info = null;
public function is_supported(): bool
{
if ($this->supported===null) {
$this->check_support();
}
return $this->supported;
}
public function get_support_info(): string
{
if ($this->supported===null) {
$this->check_support();
}
return $this->support_info;
}
private static $all_info_by_key = [];
private static $all_info_by_class = [];
private static $core_extensions = [];
protected function __construct()
{
assert(!empty($this->key), "key field is required");
assert(!empty($this->name), "name field is required for extension $this->key");
assert(empty($this->visibility) || in_array($this->visibility, self::VALID_VISIBILITY), "Invalid visibility for extension $this->key");
assert(is_array($this->db_support), "db_support has to be an array for extension $this->key");
assert(is_array($this->authors), "authors has to be an array for extension $this->key");
assert(is_array($this->dependencies), "dependencies has to be an array for extension $this->key");
}
public function is_enabled(): bool
{
return Extension::is_enabled($this->key);
}
private function check_support()
{
global $database;
$this->support_info = "";
if (!empty($this->db_support) && !in_array($database->get_driver_name(), $this->db_support)) {
$this->support_info .= "Database not supported. ";
}
// Additional checks here as needed
$this->supported = empty($this->support_info);
}
public static function get_all(): array
{
return array_values(self::$all_info_by_key);
}
public static function get_all_keys(): array
{
return array_keys(self::$all_info_by_key);
}
public static function get_core_extensions(): array
{
return self::$core_extensions;
}
public static function get_by_key(string $key): ?ExtensionInfo
{
if (array_key_exists($key, self::$all_info_by_key)) {
return self::$all_info_by_key[$key];
} else {
return null;
}
}
public static function get_for_extension_class(string $base): ?ExtensionInfo
{
$normal = $base.'Info';
if (array_key_exists($normal, self::$all_info_by_class)) {
return self::$all_info_by_class[$normal];
} else {
return null;
}
}
public static function load_all_extension_info()
{
foreach (getSubclassesOf("ExtensionInfo") as $class) {
$extension_info = new $class();
if (array_key_exists($extension_info->key, self::$all_info_by_key)) {
throw new ScoreException("Extension Info $class with key $extension_info->key has already been loaded");
}
self::$all_info_by_key[$extension_info->key] = $extension_info;
self::$all_info_by_class[$class] = $extension_info;
if ($extension_info->core===true) {
self::$core_extensions[] = $extension_info->key;
}
}
}
}
/**
* Class FormatterExtension
*
* Several extensions have this in common, make a common API.
*/
abstract class FormatterExtension extends Extension
{
public function onTextFormatting(TextFormattingEvent $event)
{
$event->formatted = $this->format($event->formatted);
$event->stripped = $this->strip($event->stripped);
}
abstract public function format(string $text): string;
abstract public function strip(string $text): string;
}
/**
* Class DataHandlerExtension
*
* This too is a common class of extension with many methods in common,
* so we have a base class to extend from.
*/
abstract class DataHandlerExtension extends Extension
{
protected $SUPPORTED_MIME = [];
protected function move_upload_to_archive(DataUploadEvent $event)
{
$target = warehouse_path(Image::IMAGE_DIR, $event->hash);
if (!@copy($event->tmpname, $target)) {
$errors = error_get_last();
throw new UploadException(
"Failed to copy file from uploads ({$event->tmpname}) to archive ($target): ".
"{$errors['type']} / {$errors['message']}"
);
}
}
public function onDataUpload(DataUploadEvent $event)
{
$supported_ext = $this->supported_ext($event->type);
$check_contents = $this->check_contents($event->tmpname);
if ($supported_ext && $check_contents) {
$this->move_upload_to_archive($event);
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
/* Check if we are replacing an image */
if (!is_null($event->replace_id)) {
/* hax: This seems like such a dirty way to do this.. */
/* Check to make sure the image exists. */
$existing = Image::by_id($event->replace_id);
if (is_null($existing)) {
throw new UploadException("Image to replace does not exist!");
}
if ($existing->hash === $event->metadata['hash']) {
throw new UploadException("The uploaded image is the same as the one to replace.");
}
// even more hax..
$event->metadata['tags'] = $existing->get_tag_list();
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->metadata['hash']), $event->metadata);
if (is_null($image)) {
throw new UploadException("Data handler failed to create image object from data");
}
if (empty($image->ext)) {
throw new UploadException("Unable to determine extension for ". $event->tmpname);
}
try {
send_event(new MediaCheckPropertiesEvent($image));
} catch (MediaException $e) {
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
}
send_event(new ImageReplaceEvent($event->replace_id, $image));
$event->image_id = $event->replace_id;
} else {
$image = $this->create_image_from_data(warehouse_path(Image::IMAGE_DIR, $event->hash), $event->metadata);
if (is_null($image)) {
throw new UploadException("Data handler failed to create image object from data");
}
if (empty($image->ext)) {
throw new UploadException("Unable to determine extension for ". $event->tmpname);
}
try {
send_event(new MediaCheckPropertiesEvent($image));
} catch (MediaException $e) {
throw new UploadException("Unable to scan media properties: ".$e->getMessage());
}
$iae = send_event(new ImageAdditionEvent($image));
$event->image_id = $iae->image->id;
$event->merged = $iae->merged;
// Rating Stuff.
if (!empty($event->metadata['rating'])) {
$rating = $event->metadata['rating'];
send_event(new RatingSetEvent($image, $rating));
}
// Locked Stuff.
if (!empty($event->metadata['locked'])) {
$locked = $event->metadata['locked'];
send_event(new LockSetEvent($image, !empty($locked)));
}
}
} elseif ($supported_ext && !$check_contents) {
// We DO support this extension - but the file looks corrupt
throw new UploadException("Invalid or corrupted file");
}
}
public function onThumbnailGeneration(ThumbnailGenerationEvent $event)
{
$result = false;
if ($this->supported_ext($event->type)) {
if ($event->force) {
$result = $this->create_thumb($event->hash, $event->type);
} else {
$outname = warehouse_path(Image::THUMBNAIL_DIR, $event->hash);
if (file_exists($outname)) {
return;
}
$result = $this->create_thumb($event->hash, $event->type);
}
}
if ($result) {
$event->generated = true;
}
}
public function onDisplayingImage(DisplayingImageEvent $event)
{
global $page;
if ($this->supported_ext($event->image->ext)) {
/** @noinspection PhpPossiblePolymorphicInvocationInspection */
$this->theme->display_image($page, $event->image);
}
}
public function onMediaCheckProperties(MediaCheckPropertiesEvent $event)
{
if ($this->supported_ext($event->ext)) {
$this->media_check_properties($event);
}
}
protected function create_image_from_data(string $filename, array $metadata): Image
{
global $config;
$image = new Image();
$image->filesize = $metadata['size'];
$image->hash = $metadata['hash'];
$image->filename = (($pos = strpos($metadata['filename'], '?')) !== false) ? substr($metadata['filename'], 0, $pos) : $metadata['filename'];
if ($config->get_bool("upload_use_mime")) {
$image->ext = get_extension_for_file($filename);
}
if (empty($image->ext)) {
$image->ext = (($pos = strpos($metadata['extension'], '?')) !== false) ? substr($metadata['extension'], 0, $pos) : $metadata['extension'];
}
$image->tag_array = is_array($metadata['tags']) ? $metadata['tags'] : Tag::explode($metadata['tags']);
$image->source = $metadata['source'];
return $image;
}
abstract protected function media_check_properties(MediaCheckPropertiesEvent $event): void;
abstract protected function check_contents(string $tmpname): bool;
abstract protected function create_thumb(string $hash, string $type): bool;
protected function supported_ext(string $ext): bool
{
return in_array(get_mime_for_extension($ext), $this->SUPPORTED_MIME);
}
public static function get_all_supported_exts(): array
{
$arr = [];
foreach (getSubclassesOf("DataHandlerExtension") as $handler) {
$handler = (new $handler());
foreach ($handler->SUPPORTED_MIME as $mime) {
$arr = array_merge($arr, get_all_extension_for_mime($mime));
}
}
$arr = array_unique($arr);
return $arr;
}
}

448
core/filetypes.php Normal file
View File

@ -0,0 +1,448 @@
<?php
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* MIME types and extension information and resolvers *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const EXTENSION_ANI = 'ani';
const EXTENSION_ASC = 'asc';
const EXTENSION_ASF = 'asf';
const EXTENSION_AVI = 'avi';
const EXTENSION_BMP = 'bmp';
const EXTENSION_BZIP = 'bz';
const EXTENSION_BZIP2 = 'bz2';
const EXTENSION_CBR = 'cbr';
const EXTENSION_CBZ = 'cbz';
const EXTENSION_CBT = 'cbt';
const EXTENSION_CBA = 'cbA';
const EXTENSION_CB7 = 'cb7';
const EXTENSION_CSS = 'css';
const EXTENSION_CSV = 'csv';
const EXTENSION_CUR = 'cur';
const EXTENSION_FLASH = 'swf';
const EXTENSION_FLASH_VIDEO = 'flv';
const EXTENSION_GIF = 'gif';
const EXTENSION_GZIP = 'gz';
const EXTENSION_HTML = 'html';
const EXTENSION_HTM = 'htm';
const EXTENSION_ICO = 'ico';
const EXTENSION_JFIF = 'jfif';
const EXTENSION_JFI = 'jfi';
const EXTENSION_JPEG = 'jpeg';
const EXTENSION_JPG = 'jpg';
const EXTENSION_JS = 'js';
const EXTENSION_JSON = 'json';
const EXTENSION_MKV = 'mkv';
const EXTENSION_MP3 = 'mp3';
const EXTENSION_MP4 = 'mp4';
const EXTENSION_M4V = 'm4v';
const EXTENSION_M4A = 'm4a';
const EXTENSION_MPEG = 'mpeg';
const EXTENSION_MPG = 'mpg';
const EXTENSION_OGG = 'ogg';
const EXTENSION_OGG_VIDEO = 'ogv';
const EXTENSION_OGG_AUDIO = 'oga';
const EXTENSION_PDF = 'pdf';
const EXTENSION_PHP = 'php';
const EXTENSION_PHP5 = 'php5';
const EXTENSION_PNG = 'png';
const EXTENSION_PSD = 'psd';
const EXTENSION_MOV = 'mov';
const EXTENSION_RSS = 'rss';
const EXTENSION_SVG = 'svg';
const EXTENSION_TAR = 'tar';
const EXTENSION_TEXT = 'txt';
const EXTENSION_TIFF = 'tiff';
const EXTENSION_TIF = 'tif';
const EXTENSION_WAV = 'wav';
const EXTENSION_WEBM = 'webm';
const EXTENSION_WEBP = 'webp';
const EXTENSION_WMA = 'wma';
const EXTENSION_WMV = 'wmv';
const EXTENSION_XML = 'xml';
const EXTENSION_XSL = 'xsl';
const EXTENSION_ZIP = 'zip';
// Couldn't find a mimetype for ani, so made one up based on it being a riff container
const MIME_TYPE_ANI = 'application/riff+ani';
const MIME_TYPE_ASF = 'video/x-ms-asf';
const MIME_TYPE_AVI = 'video/x-msvideo';
// Went with mime types from http://fileformats.archiveteam.org/wiki/Comic_Book_Archive
const MIME_TYPE_COMIC_ZIP = 'application/vnd.comicbook+zip';
const MIME_TYPE_COMIC_RAR = 'application/vnd.comicbook-rar';
const MIME_TYPE_BMP = 'image/x-ms-bmp';
const MIME_TYPE_BZIP = 'application/x-bzip';
const MIME_TYPE_BZIP2 = 'application/x-bzip2';
const MIME_TYPE_CSS = 'text/css';
const MIME_TYPE_CSV = 'text/csv';
const MIME_TYPE_FLASH = 'application/x-shockwave-flash';
const MIME_TYPE_FLASH_VIDEO = 'video/x-flv';
const MIME_TYPE_GIF = 'image/gif';
const MIME_TYPE_GZIP = 'application/x-gzip';
const MIME_TYPE_HTML = 'text/html';
const MIME_TYPE_ICO = 'image/x-icon';
const MIME_TYPE_JPEG = 'image/jpeg';
const MIME_TYPE_JS = 'text/javascript';
const MIME_TYPE_JSON = 'application/json';
const MIME_TYPE_MKV = 'video/x-matroska';
const MIME_TYPE_MP3 = 'audio/mpeg';
const MIME_TYPE_MP4_AUDIO = 'audio/mp4';
const MIME_TYPE_MP4_VIDEO = 'video/mp4';
const MIME_TYPE_MPEG = 'video/mpeg';
const MIME_TYPE_OCTET_STREAM = 'application/octet-stream';
const MIME_TYPE_OGG = 'application/ogg';
const MIME_TYPE_OGG_VIDEO = 'video/ogg';
const MIME_TYPE_OGG_AUDIO = 'audio/ogg';
const MIME_TYPE_PDF = 'application/pdf';
const MIME_TYPE_PHP = 'text/x-php';
const MIME_TYPE_PNG = 'image/png';
const MIME_TYPE_PSD = 'image/vnd.adobe.photoshop';
const MIME_TYPE_QUICKTIME = 'video/quicktime';
const MIME_TYPE_RSS = 'application/rss+xml';
const MIME_TYPE_SVG = 'image/svg+xml';
const MIME_TYPE_TAR = 'application/x-tar';
const MIME_TYPE_TEXT = 'text/plain';
const MIME_TYPE_TIFF = 'image/tiff';
const MIME_TYPE_WAV = 'audio/x-wav';
const MIME_TYPE_WEBM = 'video/webm';
const MIME_TYPE_WEBP = 'image/webp';
const MIME_TYPE_WIN_BITMAP = 'image/x-win-bitmap';
const MIME_TYPE_XML = 'text/xml';
const MIME_TYPE_XML_APPLICATION = 'application/xml';
const MIME_TYPE_XSL = 'application/xsl+xml';
const MIME_TYPE_ZIP = 'application/zip';
const MIME_TYPE_MAP_NAME = 'name';
const MIME_TYPE_MAP_EXT = 'ext';
const MIME_TYPE_MAP_MIME = 'mime';
// Mime type map. Each entry in the MIME_TYPE_ARRAY represents a kind of file, identified by the "correct" mimetype as the key.
// The value for each entry is a map of twokeys, ext and mime.
// ext's value is an array of all of the extensions that the file type can use, with the "correct" one being first.
// mime's value is an array of all mime types that the file type is known to use, with the current "correct" one being first.
const MIME_TYPE_MAP = [
MIME_TYPE_ANI => [
MIME_TYPE_MAP_NAME => "ANI Cursor",
MIME_TYPE_MAP_EXT => [EXTENSION_ANI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ANI],
],
MIME_TYPE_AVI => [
MIME_TYPE_MAP_NAME => "AVI",
MIME_TYPE_MAP_EXT => [EXTENSION_AVI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_AVI,'video/avi','video/msvideo'],
],
MIME_TYPE_ASF => [
MIME_TYPE_MAP_NAME => "ASF/WMV",
MIME_TYPE_MAP_EXT => [EXTENSION_ASF,EXTENSION_WMA,EXTENSION_WMV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ASF,'audio/x-ms-wma','video/x-ms-wmv'],
],
MIME_TYPE_BMP => [
MIME_TYPE_MAP_NAME => "BMP",
MIME_TYPE_MAP_EXT => [EXTENSION_BMP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BMP],
],
MIME_TYPE_BZIP => [
MIME_TYPE_MAP_NAME => "BZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP],
],
MIME_TYPE_BZIP2 => [
MIME_TYPE_MAP_NAME => "BZIP2",
MIME_TYPE_MAP_EXT => [EXTENSION_BZIP2],
MIME_TYPE_MAP_MIME => [MIME_TYPE_BZIP2],
],
MIME_TYPE_COMIC_ZIP => [
MIME_TYPE_MAP_NAME => "CBZ",
MIME_TYPE_MAP_EXT => [EXTENSION_CBZ],
MIME_TYPE_MAP_MIME => [MIME_TYPE_COMIC_ZIP],
],
MIME_TYPE_CSS => [
MIME_TYPE_MAP_NAME => "Cascading Style Sheet",
MIME_TYPE_MAP_EXT => [EXTENSION_CSS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSS],
],
MIME_TYPE_CSV => [
MIME_TYPE_MAP_NAME => "CSV",
MIME_TYPE_MAP_EXT => [EXTENSION_CSV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_CSV],
],
MIME_TYPE_FLASH => [
MIME_TYPE_MAP_NAME => "Flash",
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH],
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH],
],
MIME_TYPE_FLASH_VIDEO => [
MIME_TYPE_MAP_NAME => "Flash Video",
MIME_TYPE_MAP_EXT => [EXTENSION_FLASH_VIDEO],
MIME_TYPE_MAP_MIME => [MIME_TYPE_FLASH_VIDEO,'video/flv'],
],
MIME_TYPE_GIF => [
MIME_TYPE_MAP_NAME => "GIF",
MIME_TYPE_MAP_EXT => [EXTENSION_GIF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_GIF],
],
MIME_TYPE_GZIP => [
MIME_TYPE_MAP_NAME => "GZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_GZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
],
MIME_TYPE_HTML => [
MIME_TYPE_MAP_NAME => "HTML",
MIME_TYPE_MAP_EXT => [EXTENSION_HTM, EXTENSION_HTML],
MIME_TYPE_MAP_MIME => [MIME_TYPE_HTML],
],
MIME_TYPE_ICO => [
MIME_TYPE_MAP_NAME => "Icon",
MIME_TYPE_MAP_EXT => [EXTENSION_ICO, EXTENSION_CUR],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ICO, MIME_TYPE_WIN_BITMAP],
],
MIME_TYPE_JPEG => [
MIME_TYPE_MAP_NAME => "JPEG",
MIME_TYPE_MAP_EXT => [EXTENSION_JPG, EXTENSION_JPEG, EXTENSION_JFIF, EXTENSION_JFI],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JPEG],
],
MIME_TYPE_JS => [
MIME_TYPE_MAP_NAME => "JavaScript",
MIME_TYPE_MAP_EXT => [EXTENSION_JS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JS],
],
MIME_TYPE_JSON => [
MIME_TYPE_MAP_NAME => "JSON",
MIME_TYPE_MAP_EXT => [EXTENSION_JSON],
MIME_TYPE_MAP_MIME => [MIME_TYPE_JSON],
],
MIME_TYPE_MKV => [
MIME_TYPE_MAP_NAME => "Matroska",
MIME_TYPE_MAP_EXT => [EXTENSION_MKV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MKV],
],
MIME_TYPE_MP3 => [
MIME_TYPE_MAP_NAME => "MP3",
MIME_TYPE_MAP_EXT => [EXTENSION_MP3],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP3],
],
MIME_TYPE_MP4_AUDIO => [
MIME_TYPE_MAP_NAME => "MP4 Audio",
MIME_TYPE_MAP_EXT => [EXTENSION_M4A],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_AUDIO,"audio/m4a"],
],
MIME_TYPE_MP4_VIDEO => [
MIME_TYPE_MAP_NAME => "MP4 Video",
MIME_TYPE_MAP_EXT => [EXTENSION_MP4,EXTENSION_M4V],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MP4_VIDEO,'video/x-m4v'],
],
MIME_TYPE_MPEG => [
MIME_TYPE_MAP_NAME => "MPEG",
MIME_TYPE_MAP_EXT => [EXTENSION_MPG,EXTENSION_MPEG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_MPEG],
],
MIME_TYPE_PDF => [
MIME_TYPE_MAP_NAME => "PDF",
MIME_TYPE_MAP_EXT => [EXTENSION_PDF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PDF],
],
MIME_TYPE_PHP => [
MIME_TYPE_MAP_NAME => "PHP",
MIME_TYPE_MAP_EXT => [EXTENSION_PHP,EXTENSION_PHP5],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PHP],
],
MIME_TYPE_PNG => [
MIME_TYPE_MAP_NAME => "PNG",
MIME_TYPE_MAP_EXT => [EXTENSION_PNG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PNG],
],
MIME_TYPE_PSD => [
MIME_TYPE_MAP_NAME => "PSD",
MIME_TYPE_MAP_EXT => [EXTENSION_PSD],
MIME_TYPE_MAP_MIME => [MIME_TYPE_PSD],
],
MIME_TYPE_OGG_AUDIO => [
MIME_TYPE_MAP_NAME => "Ogg Vorbis",
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_AUDIO,EXTENSION_OGG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_AUDIO,MIME_TYPE_OGG],
],
MIME_TYPE_OGG_VIDEO => [
MIME_TYPE_MAP_NAME => "Ogg Theora",
MIME_TYPE_MAP_EXT => [EXTENSION_OGG_VIDEO],
MIME_TYPE_MAP_MIME => [MIME_TYPE_OGG_VIDEO],
],
MIME_TYPE_QUICKTIME => [
MIME_TYPE_MAP_NAME => "Quicktime",
MIME_TYPE_MAP_EXT => [EXTENSION_MOV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_QUICKTIME],
],
MIME_TYPE_RSS => [
MIME_TYPE_MAP_NAME => "RSS",
MIME_TYPE_MAP_EXT => [EXTENSION_RSS],
MIME_TYPE_MAP_MIME => [MIME_TYPE_RSS],
],
MIME_TYPE_SVG => [
MIME_TYPE_MAP_NAME => "SVG",
MIME_TYPE_MAP_EXT => [EXTENSION_SVG],
MIME_TYPE_MAP_MIME => [MIME_TYPE_SVG],
],
MIME_TYPE_TAR => [
MIME_TYPE_MAP_NAME => "TAR",
MIME_TYPE_MAP_EXT => [EXTENSION_TAR],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TAR],
],
MIME_TYPE_TEXT => [
MIME_TYPE_MAP_NAME => "Text",
MIME_TYPE_MAP_EXT => [EXTENSION_TEXT, EXTENSION_ASC],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TEXT],
],
MIME_TYPE_TIFF => [
MIME_TYPE_MAP_NAME => "TIFF",
MIME_TYPE_MAP_EXT => [EXTENSION_TIF,EXTENSION_TIFF],
MIME_TYPE_MAP_MIME => [MIME_TYPE_TIFF],
],
MIME_TYPE_WAV => [
MIME_TYPE_MAP_NAME => "Wave",
MIME_TYPE_MAP_EXT => [EXTENSION_WAV],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WAV],
],
MIME_TYPE_WEBM => [
MIME_TYPE_MAP_NAME => "WebM",
MIME_TYPE_MAP_EXT => [EXTENSION_WEBM],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBM],
],
MIME_TYPE_WEBP => [
MIME_TYPE_MAP_NAME => "WebP",
MIME_TYPE_MAP_EXT => [EXTENSION_WEBP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_WEBP],
],
MIME_TYPE_XML => [
MIME_TYPE_MAP_NAME => "XML",
MIME_TYPE_MAP_EXT => [EXTENSION_XML],
MIME_TYPE_MAP_MIME => [MIME_TYPE_XML,MIME_TYPE_XML_APPLICATION],
],
MIME_TYPE_XSL => [
MIME_TYPE_MAP_NAME => "XSL",
MIME_TYPE_MAP_EXT => [EXTENSION_XSL],
MIME_TYPE_MAP_MIME => [MIME_TYPE_XSL],
],
MIME_TYPE_ZIP => [
MIME_TYPE_MAP_NAME => "ZIP",
MIME_TYPE_MAP_EXT => [EXTENSION_ZIP],
MIME_TYPE_MAP_MIME => [MIME_TYPE_ZIP],
],
];
/**
* Returns the mimetype that matches the provided extension.
*/
function get_mime_for_extension(string $ext): ?string
{
$ext = strtolower($ext);
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($ext, $value[MIME_TYPE_MAP_EXT])) {
return $key;
}
}
return null;
}
/**
* Returns the mimetype for the specified file, trying file inspection methods before falling back on extension-based detection.
* @param String $file
* @param String $ext The files extension, for if the current filename somehow lacks the extension
* @return String The extension that was found.
*/
function get_mime(string $file, string $ext=""): string
{
if (!file_exists($file)) {
throw new SCoreException("File not found: ".$file);
}
$type = false;
if (extension_loaded('fileinfo')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
try {
$type = finfo_file($finfo, $file);
} finally {
finfo_close($finfo);
}
} elseif (function_exists('mime_content_type')) {
// If anyone is still using mime_content_type()
$type = trim(mime_content_type($file));
}
if ($type===false || empty($type)) {
// Checking by extension is our last resort
if ($ext==null||strlen($ext) == 0) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
}
$type = get_mime_for_extension($ext);
}
if ($type !== false && strlen($type) > 0) {
return $type;
}
return MIME_TYPE_OCTET_STREAM;
}
/**
* Returns the file extension associated with the specified mimetype.
*/
function get_extension(?string $mime_type): ?string
{
if (empty($mime_type)) {
return null;
}
if ($mime_type==MIME_TYPE_OCTET_STREAM) {
return null;
}
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
return $value[MIME_TYPE_MAP_EXT][0];
}
}
return null;
}
/**
* Returns all of the file extensions associated with the specified mimetype.
*/
function get_all_extension_for_mime(?string $mime_type): array
{
$output = [];
if (empty($mime_type)) {
return $output;
}
foreach (MIME_TYPE_MAP as $key=>$value) {
if (in_array($mime_type, $value[MIME_TYPE_MAP_MIME])) {
$output = array_merge($output, $value[MIME_TYPE_MAP_EXT]);
}
}
return $output;
}
/**
* Gets an the extension defined in MIME_TYPE_MAP for a file.
*
* @param String $file_path
* @return String The extension that was found, or null if one can not be found.
*/
function get_extension_for_file(String $file_path): ?String
{
$mime = get_mime($file_path);
if (!empty($mime)) {
if ($mime==MIME_TYPE_OCTET_STREAM) {
return null;
} else {
$ext = get_extension($mime);
}
if (!empty($ext)) {
return $ext;
}
}
return null;
}

File diff suppressed because it is too large Load Diff

149
core/imageboard/event.php Normal file
View File

@ -0,0 +1,149 @@
<?php declare(strict_types=1);
/**
* An image is being added to the database.
*/
class ImageAdditionEvent extends Event
{
/** @var User */
public $user;
/** @var Image */
public $image;
public $merged = false;
/**
* Inserts a new image into the database with its associated
* information. Also calls TagSetEvent to set the tags for
* this new image.
*/
public function __construct(Image $image)
{
parent::__construct();
$this->image = $image;
}
}
class ImageAdditionException extends SCoreException
{
}
/**
* An image is being deleted.
*/
class ImageDeletionEvent extends Event
{
/** @var Image */
public $image;
/** @var bool */
public $force = false;
/**
* Deletes an image.
*
* Used by things like tags and comments handlers to
* clean out related rows in their tables.
*/
public function __construct(Image $image, bool $force = false)
{
parent::__construct();
$this->image = $image;
$this->force = $force;
}
}
/**
* An image is being replaced.
*/
class ImageReplaceEvent extends Event
{
/** @var int */
public $id;
/** @var Image */
public $image;
/**
* Replaces an image.
*
* Updates an existing ID in the database to use a new image
* file, leaving the tags and such unchanged. Also removes
* the old image file and thumbnail from the disk.
*/
public function __construct(int $id, Image $image)
{
parent::__construct();
$this->id = $id;
$this->image = $image;
}
}
class ImageReplaceException extends SCoreException
{
}
/**
* Request a thumbnail be made for an image object.
*/
class ThumbnailGenerationEvent extends Event
{
/** @var string */
public $hash;
/** @var string */
public $type;
/** @var bool */
public $force;
/** @var bool */
public $generated;
/**
* Request a thumbnail be made for an image object
*/
public function __construct(string $hash, string $type, bool $force=false)
{
parent::__construct();
$this->hash = $hash;
$this->type = $type;
$this->force = $force;
$this->generated = false;
}
}
/*
* ParseLinkTemplateEvent:
* $link -- the formatted text (with each element URL Escape'd)
* $text -- the formatted text (not escaped)
* $original -- the formatting string, for reference
* $image -- the image who's link is being parsed
*/
class ParseLinkTemplateEvent extends Event
{
/** @var string */
public $link;
/** @var string */
public $text;
/** @var string */
public $original;
/** @var Image */
public $image;
public function __construct(string $link, Image $image)
{
parent::__construct();
$this->link = $link;
$this->text = $link;
$this->original = $link;
$this->image = $image;
}
public function replace(string $needle, ?string $replace): void
{
if (!is_null($replace)) {
$this->link = str_replace($needle, url_escape($replace), $this->link);
$this->text = str_replace($needle, $replace, $this->text);
}
}
}

1004
core/imageboard/image.php Normal file

File diff suppressed because it is too large Load Diff

186
core/imageboard/misc.php Normal file
View File

@ -0,0 +1,186 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Misc functions *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Add a directory full of images
*
* @param string $base
* @return array
*/
function add_dir(string $base): array
{
$results = [];
foreach (list_files($base) as $full_path) {
$short_path = str_replace($base, "", $full_path);
$filename = basename($full_path);
$tags = path_to_tags($short_path);
$result = "$short_path (".str_replace(" ", ", ", $tags).")... ";
try {
add_image($full_path, $filename, $tags);
$result .= "ok";
} catch (UploadException $ex) {
$result .= "failed: ".$ex->getMessage();
}
$results[] = $result;
}
return $results;
}
/**
* Sends a DataUploadEvent for a file.
*
* @param string $tmpname
* @param string $filename
* @param string $tags
* @throws UploadException
*/
function add_image(string $tmpname, string $filename, string $tags): int
{
assert(file_exists($tmpname));
$pathinfo = pathinfo($filename);
$metadata = [];
$metadata['filename'] = $pathinfo['basename'];
if (array_key_exists('extension', $pathinfo)) {
$metadata['extension'] = $pathinfo['extension'];
}
$metadata['tags'] = Tag::explode($tags);
$metadata['source'] = null;
$due = new DataUploadEvent($tmpname, $metadata);
send_event($due);
return $due->image_id;
}
/**
* Given a full size pair of dimensions, return a pair scaled down to fit
* into the configured thumbnail square, with ratio intact.
* Optionally uses the High-DPI scaling setting to adjust the final resolution.
*
* @param int $orig_width
* @param int $orig_height
* @param bool $use_dpi_scaling Enables the High-DPI scaling.
* @return array
*/
function get_thumbnail_size(int $orig_width, int $orig_height, bool $use_dpi_scaling = false): array
{
global $config;
$fit = $config->get_string(ImageConfig::THUMB_FIT);
if (in_array($fit, [Media::RESIZE_TYPE_FILL, Media::RESIZE_TYPE_STRETCH, Media::RESIZE_TYPE_FIT_BLUR])) {
return [$config->get_int(ImageConfig::THUMB_WIDTH), $config->get_int(ImageConfig::THUMB_HEIGHT)];
}
if ($orig_width === 0) {
$orig_width = 192;
}
if ($orig_height === 0) {
$orig_height = 192;
}
if ($orig_width > $orig_height * 5) {
$orig_width = $orig_height * 5;
}
if ($orig_height > $orig_width * 5) {
$orig_height = $orig_width * 5;
}
if ($use_dpi_scaling) {
list($max_width, $max_height) = get_thumbnail_max_size_scaled();
} else {
$max_width = $config->get_int(ImageConfig::THUMB_WIDTH);
$max_height = $config->get_int(ImageConfig::THUMB_HEIGHT);
}
$output = get_scaled_by_aspect_ratio($orig_width, $orig_height, $max_width, $max_height);
if ($output[2] > 1 && $config->get_bool('thumb_upscale')) {
return [(int)$orig_width, (int)$orig_height];
} else {
return $output;
}
}
function get_scaled_by_aspect_ratio(int $original_width, int $original_height, int $max_width, int $max_height) : array
{
$xscale = ($max_width/ $original_width);
$yscale = ($max_height/ $original_height);
$scale = ($yscale < $xscale) ? $yscale : $xscale ;
return [(int)($original_width*$scale), (int)($original_height*$scale), $scale];
}
/**
* Fetches the thumbnails height and width settings and applies the High-DPI scaling setting before returning the dimensions.
*
* @return array [width, height]
*/
function get_thumbnail_max_size_scaled(): array
{
global $config;
$scaling = $config->get_int(ImageConfig::THUMB_SCALING);
$max_width = $config->get_int(ImageConfig::THUMB_WIDTH) * ($scaling/100);
$max_height = $config->get_int(ImageConfig::THUMB_HEIGHT) * ($scaling/100);
return [$max_width, $max_height];
}
function create_image_thumb(string $hash, string $type, string $engine = null)
{
global $config;
$inname = warehouse_path(Image::IMAGE_DIR, $hash);
$outname = warehouse_path(Image::THUMBNAIL_DIR, $hash);
$tsize = get_thumbnail_max_size_scaled();
create_scaled_image(
$inname,
$outname,
$tsize,
$type,
$engine,
$config->get_string(ImageConfig::THUMB_FIT)
);
}
function create_scaled_image(string $inname, string $outname, array $tsize, string $type, ?string $engine = null, ?string $resize_type = null)
{
global $config;
if (empty($engine)) {
$engine = $config->get_string(ImageConfig::THUMB_ENGINE);
}
if (empty($resize_type)) {
$resize_type = $config->get_string(ImageConfig::THUMB_FIT);
}
$output_format = $config->get_string(ImageConfig::THUMB_TYPE);
if ($output_format==EXTENSION_WEBP) {
$output_format = Media::WEBP_LOSSY;
}
send_event(new MediaResizeEvent(
$engine,
$inname,
$type,
$outname,
$tsize[0],
$tsize[1],
$resize_type,
$output_format,
$config->get_int(ImageConfig::THUMB_QUALITY),
true,
true
));
}

View File

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
class Querylet
{
/** @var string */
public $sql;
/** @var array */
public $variables;
public function __construct(string $sql, array $variables=[])
{
$this->sql = $sql;
$this->variables = $variables;
}
public function append(Querylet $querylet): void
{
$this->sql .= $querylet->sql;
$this->variables = array_merge($this->variables, $querylet->variables);
}
public function append_sql(string $sql): void
{
$this->sql .= $sql;
}
public function add_variable($var): void
{
$this->variables[] = $var;
}
}
class TagCondition
{
/** @var string */
public $tag;
/** @var bool */
public $positive;
public function __construct(string $tag, bool $positive)
{
$this->tag = $tag;
$this->positive = $positive;
}
}
class ImgCondition
{
/** @var Querylet */
public $qlet;
/** @var bool */
public $positive;
public function __construct(Querylet $qlet, bool $positive)
{
$this->qlet = $qlet;
$this->positive = $positive;
}
}

218
core/imageboard/tag.php Normal file
View File

@ -0,0 +1,218 @@
<?php declare(strict_types=1);
/**
* Class Tag
*
* A class for organising the tag related functions.
*
* All the methods are static, one should never actually use a tag object.
*
*/
class Tag
{
public static function implode(array $tags): string
{
sort($tags);
$tags = implode(' ', $tags);
return $tags;
}
/**
* Turn a human-supplied string into a valid tag array.
*
* #return string[]
*/
public static function explode(string $tags, bool $tagme=true): array
{
global $database;
$tags = explode(' ', trim($tags));
/* sanitise by removing invisible / dodgy characters */
$tag_array = self::sanitize_array($tags);
/* if user supplied a blank string, add "tagme" */
if (count($tag_array) === 0 && $tagme) {
$tag_array = ["tagme"];
}
/* resolve aliases */
$new = [];
$i = 0;
$tag_count = count($tag_array);
while ($i<$tag_count) {
$tag = $tag_array[$i];
$negative = '';
if (!empty($tag) && ($tag[0] == '-')) {
$negative = '-';
$tag = substr($tag, 1);
}
$newtags = $database->get_one(
"
SELECT newtag
FROM aliases
WHERE LOWER(oldtag)=LOWER(:tag)
",
["tag"=>$tag]
);
if (empty($newtags)) {
//tag has no alias, use old tag
$aliases = [$tag];
} else {
$aliases = explode(" ", $newtags); // Tag::explode($newtags); - recursion can be infinite
}
foreach ($aliases as $alias) {
if (!in_array($alias, $new)) {
if ($tag == $alias) {
$new[] = $negative.$alias;
} elseif (!in_array($alias, $tag_array)) {
$tag_array[] = $negative.$alias;
$tag_count++;
}
}
}
$i++;
}
/* remove any duplicate tags */
$tag_array = array_iunique($new);
/* tidy up */
sort($tag_array);
return $tag_array;
}
public static function sanitize(string $tag): string
{
$tag = preg_replace("/\s/", "", $tag); # whitespace
$tag = preg_replace('/\x20[\x0e\x0f]/', '', $tag); # unicode RTL
$tag = preg_replace("/\.+/", ".", $tag); # strings of dots?
$tag = preg_replace("/^(\.+[\/\\\\])+/", "", $tag); # trailing slashes?
$tag = trim($tag, ", \t\n\r\0\x0B");
if ($tag == ".") {
$tag = "";
} // hard-code one bad case...
if (mb_strlen($tag, 'UTF-8') > 255) {
throw new ScoreException("The tag below is longer than 255 characters, please use a shorter tag.\n$tag\n");
}
return $tag;
}
public static function compare(array $tags1, array $tags2): bool
{
if (count($tags1)!==count($tags2)) {
return false;
}
$tags1 = array_map("strtolower", $tags1);
$tags2 = array_map("strtolower", $tags2);
natcasesort($tags1);
natcasesort($tags2);
for ($i = 0; $i < count($tags1); $i++) {
if ($tags1[$i]!==$tags2[$i]) {
return false;
}
}
return true;
}
public static function get_diff_tags(array $source, array $remove): array
{
$before = array_map('strtolower', $source);
$remove = array_map('strtolower', $remove);
$after = [];
foreach ($before as $tag) {
if (!in_array($tag, $remove)) {
$after[] = $tag;
}
}
return $after;
}
public static function sanitize_array(array $tags): array
{
global $page;
$tag_array = [];
foreach ($tags as $tag) {
try {
$tag = Tag::sanitize($tag);
} catch (Exception $e) {
$page->flash($e->getMessage());
continue;
}
if (!empty($tag)) {
$tag_array[] = $tag;
}
}
return $tag_array;
}
public static function sqlify(string $term): string
{
global $database;
if ($database->get_driver_name() === DatabaseDriver::SQLITE) {
$term = str_replace('\\', '\\\\', $term);
}
$term = str_replace('_', '\_', $term);
$term = str_replace('%', '\%', $term);
$term = str_replace('*', '%', $term);
// $term = str_replace("?", "_", $term);
return $term;
}
/**
* Kind of like urlencode, but using a custom scheme so that
* tags always fit neatly between slashes in a URL. Use this
* when you want to put an arbitrary tag into a URL.
*/
public static function caret(string $input): string
{
$to_caret = [
"^" => "^",
"/" => "s",
"\\" => "b",
"?" => "q",
"&" => "a",
"." => "d",
];
foreach ($to_caret as $from => $to) {
$input = str_replace($from, '^' . $to, $input);
}
return $input;
}
/**
* Use this when you want to get a tag out of a URL
*/
public static function decaret(string $str): string
{
$from_caret = [
"^" => "^",
"s" => "/",
"b" => "\\",
"q" => "?",
"a" => "&",
"d" => ".",
];
$out = "";
$length = strlen($str);
for ($i=0; $i<$length; $i++) {
if ($str[$i] == "^") {
$i++;
$out .= $from_caret[$str[$i]] ?? '';
} else {
$out .= $str[$i];
}
}
return $out;
}
}

326
core/install.php Normal file
View File

@ -0,0 +1,326 @@
<?php
/**
* Shimmie Installer
*
* @package Shimmie
* @copyright Copyright (c) 2007-2015, Shish et al.
* @author Shish [webmaster at shishnet.org], jgen [jeffgenovy at gmail.com]
* @link https://code.shishnet.org/shimmie2/
* @license https://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
*
* Initialise the database, check that folder
* permissions are set properly.
*
* This file should be independent of the database
* and other such things that aren't ready yet
*/
function install()
{
date_default_timezone_set('UTC');
if (is_readable("data/config/shimmie.conf.php")) {
die_nicely(
"Shimmie is already installed.",
"data/config/shimmie.conf.php exists, how did you get here?"
);
}
// Pull in necessary files
require_once "vendor/autoload.php";
global $_tracer;
$_tracer = new EventTracer();
require_once "core/exceptions.php";
require_once "core/cacheengine.php";
require_once "core/dbengine.php";
require_once "core/database.php";
require_once "core/util.php";
$dsn = get_dsn();
if ($dsn) {
do_install($dsn);
} else {
ask_questions();
}
}
function get_dsn()
{
if (getenv("INSTALL_DSN")) {
$dsn = getenv("INSTALL_DSN");
;
} elseif (@$_POST["database_type"] == DatabaseDriver::SQLITE) {
/** @noinspection PhpUnhandledExceptionInspection */
$id = bin2hex(random_bytes(5));
$dsn = "sqlite:data/shimmie.{$id}.sqlite";
} elseif (isset($_POST['database_type']) && isset($_POST['database_host']) && isset($_POST['database_user']) && isset($_POST['database_name'])) {
$dsn = "{$_POST['database_type']}:user={$_POST['database_user']};password={$_POST['database_password']};host={$_POST['database_host']};dbname={$_POST['database_name']}";
} else {
$dsn = null;
}
return $dsn;
}
function do_install($dsn)
{
try {
create_dirs();
create_tables(new Database($dsn));
write_config($dsn);
} catch (InstallerException $e) {
die_nicely($e->title, $e->body, $e->code);
}
}
function ask_questions()
{
$warnings = [];
$errors = [];
if (check_gd_version() == 0 && check_im_version() == 0) {
$errors[] = "
No thumbnailers could be found - install the imagemagick
tools (or the PHP-GD library, if imagemagick is unavailable).
";
} elseif (check_im_version() == 0) {
$warnings[] = "
The 'convert' command (from the imagemagick package)
could not be found - PHP-GD can be used instead, but
the size of thumbnails will be limited.
";
}
if (!function_exists('mb_strlen')) {
$errors[] = "
The mbstring PHP extension is missing - multibyte languages
(eg non-english languages) may not work right.
";
}
$drivers = PDO::getAvailableDrivers();
if (
!in_array(DatabaseDriver::MYSQL, $drivers) &&
!in_array(DatabaseDriver::PGSQL, $drivers) &&
!in_array(DatabaseDriver::SQLITE, $drivers)
) {
$errors[] = "
No database connection library could be found; shimmie needs
PDO with either Postgres, MySQL, or SQLite drivers
";
}
$db_m = in_array(DatabaseDriver::MYSQL, $drivers) ? '<option value="'. DatabaseDriver::MYSQL .'">MySQL</option>' : "";
$db_p = in_array(DatabaseDriver::PGSQL, $drivers) ? '<option value="'. DatabaseDriver::PGSQL .'">PostgreSQL</option>' : "";
$db_s = in_array(DatabaseDriver::SQLITE, $drivers) ? '<option value="'. DatabaseDriver::SQLITE .'">SQLite</option>' : "";
$warn_msg = $warnings ? "<h3>Warnings</h3>".implode("\n<p>", $warnings) : "";
$err_msg = $errors ? "<h3>Errors</h3>".implode("\n<p>", $errors) : "";
die_nicely(
"Install Options",
<<<EOD
$warn_msg
$err_msg
<form action="index.php" method="POST">
<table class='form' style="margin: 1em auto;">
<tr>
<th>Type:</th>
<td><select name="database_type" id="database_type" onchange="update_qs();">
$db_m
$db_p
$db_s
</select></td>
</tr>
<tr class="dbconf mysql pgsql">
<th>Host:</th>
<td><input type="text" name="database_host" size="40" value="localhost"></td>
</tr>
<tr class="dbconf mysql pgsql">
<th>Username:</th>
<td><input type="text" name="database_user" size="40"></td>
</tr>
<tr class="dbconf mysql pgsql">
<th>Password:</th>
<td><input type="password" name="database_password" size="40"></td>
</tr>
<tr class="dbconf mysql pgsql">
<th>DB&nbsp;Name:</th>
<td><input type="text" name="database_name" size="40" value="shimmie"></td>
</tr>
<tr><td colspan="2"><input type="submit" value="Go!"></td></tr>
</table>
<script>
document.addEventListener('DOMContentLoaded', update_qs);
function q(n) {
return document.querySelectorAll(n);
}
function update_qs() {
Array.prototype.forEach.call(q('.dbconf'), function(el, i){
el.style.display = 'none';
});
let seldb = q("#database_type")[0].value || "none";
Array.prototype.forEach.call(q('.'+seldb), function(el, i){
el.style.display = null;
});
}
</script>
</form>
<h3>Help</h3>
<p class="dbconf mysql pgsql">
Please make sure the database you have chosen exists and is empty.<br>
The username provided must have access to create tables within the database.
</p>
<p class="dbconf sqlite">
For SQLite the database name will be a filename on disk, relative to
where shimmie was installed.
</p>
<p class="dbconf none">
Drivers can generally be downloaded with your OS package manager;
for Debian / Ubuntu you want php-pgsql, php-mysql, or php-sqlite.
</p>
EOD
);
}
function create_dirs()
{
$data_exists = file_exists("data") || mkdir("data");
$data_writable = $data_exists && (is_writable("data") || chmod("data", 0755));
if (!$data_exists || !$data_writable) {
throw new InstallerException(
"Directory Permissions Error:",
"<p>Shimmie needs to have a 'data' folder in its directory, writable by the PHP user.</p>
<p>If you see this error, if probably means the folder is owned by you, and it needs to be writable by the web server.</p>
<p>PHP reports that it is currently running as user: ".get_current_user()." (". getmyuid() .")</p>
<p>Once you have created this folder and / or changed the ownership of the shimmie folder, hit 'refresh' to continue.</p>",
7
);
}
}
function create_tables(Database $db)
{
try {
if ($db->count_tables() > 0) {
throw new InstallerException(
"Warning: The Database schema is not empty!",
"<p>Please ensure that the database you are installing Shimmie with is empty before continuing.</p>
<p>Once you have emptied the database of any tables, please hit 'refresh' to continue.</p>",
2
);
}
$db->create_table("aliases", "
oldtag VARCHAR(128) NOT NULL,
newtag VARCHAR(128) NOT NULL,
PRIMARY KEY (oldtag)
");
$db->execute("CREATE INDEX aliases_newtag_idx ON aliases(newtag)", []);
$db->create_table("config", "
name VARCHAR(128) NOT NULL,
value TEXT,
PRIMARY KEY (name)
");
$db->create_table("users", "
id SCORE_AIPK,
name VARCHAR(32) UNIQUE NOT NULL,
pass VARCHAR(250),
joindate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
class VARCHAR(32) NOT NULL DEFAULT 'user',
email VARCHAR(128)
");
$db->execute("CREATE INDEX users_name_idx ON users(name)", []);
$db->execute("INSERT INTO users(name, pass, joindate, class) VALUES(:name, :pass, now(), :class)", ["name" => 'Anonymous', "pass" => null, "class" => 'anonymous']);
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", ["name" => 'anon_id', "value" => $db->get_last_insert_id('users_id_seq')]);
if (check_im_version() > 0) {
$db->execute("INSERT INTO config(name, value) VALUES(:name, :value)", ["name" => 'thumb_engine', "value" => 'convert']);
}
$db->create_table("images", "
id SCORE_AIPK,
owner_id INTEGER NOT NULL,
owner_ip SCORE_INET NOT NULL,
filename VARCHAR(64) NOT NULL,
filesize INTEGER NOT NULL,
hash CHAR(32) UNIQUE NOT NULL,
ext CHAR(4) NOT NULL,
source VARCHAR(255),
width INTEGER NOT NULL,
height INTEGER NOT NULL,
posted TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
locked SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT
");
$db->execute("CREATE INDEX images_owner_id_idx ON images(owner_id)", []);
$db->execute("CREATE INDEX images_width_idx ON images(width)", []);
$db->execute("CREATE INDEX images_height_idx ON images(height)", []);
$db->execute("CREATE INDEX images_hash_idx ON images(hash)", []);
$db->create_table("tags", "
id SCORE_AIPK,
tag VARCHAR(64) UNIQUE NOT NULL,
count INTEGER NOT NULL DEFAULT 0
");
$db->execute("CREATE INDEX tags_tag_idx ON tags(tag)", []);
$db->create_table("image_tags", "
image_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
UNIQUE(image_id, tag_id),
FOREIGN KEY (image_id) REFERENCES images(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
");
$db->execute("CREATE INDEX images_tags_image_id_idx ON image_tags(image_id)", []);
$db->execute("CREATE INDEX images_tags_tag_id_idx ON image_tags(tag_id)", []);
$db->execute("INSERT INTO config(name, value) VALUES('db_version', 11)");
$db->commit();
} catch (PDOException $e) {
throw new InstallerException(
"PDO Error:",
"<p>An error occurred while trying to create the database tables necessary for Shimmie.</p>
<p>Please check and ensure that the database configuration options are all correct.</p>
<p>{$e->getMessage()}</p>",
3
);
}
}
function write_config($dsn)
{
$file_content = "<" . "?php\ndefine('DATABASE_DSN', '$dsn');\n";
if (!file_exists("data/config")) {
mkdir("data/config", 0755, true);
}
if (file_put_contents("data/config/shimmie.conf.php", $file_content, LOCK_EX)) {
header("Location: index.php?flash=Installation%20complete");
die_nicely(
"Installation Successful",
"<p>If you aren't redirected, <a href=\"index.php\">click here to Continue</a>."
);
} else {
$h_file_content = htmlentities($file_content);
throw new InstallerException(
"File Permissions Error:",
"The web server isn't allowed to write to the config file; please copy
the text below, save it as 'data/config/shimmie.conf.php', and upload it into the shimmie
folder manually. Make sure that when you save it, there is no whitespace
before the \"&lt;?php\".
<p><textarea cols='80' rows='2'>$h_file_content</textarea>
<p>Once done, <a href='index.php'>click here to Continue</a>.",
0
);
}
}

82
core/logging.php Normal file
View File

@ -0,0 +1,82 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Logging convenience *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
define("SCORE_LOG_CRITICAL", 50);
define("SCORE_LOG_ERROR", 40);
define("SCORE_LOG_WARNING", 30);
define("SCORE_LOG_INFO", 20);
define("SCORE_LOG_DEBUG", 10);
define("SCORE_LOG_NOTSET", 0);
const LOGGING_LEVEL_NAMES = [
SCORE_LOG_NOTSET=>"Not Set",
SCORE_LOG_DEBUG=>"Debug",
SCORE_LOG_INFO=>"Info",
SCORE_LOG_WARNING=>"Warning",
SCORE_LOG_ERROR=>"Error",
SCORE_LOG_CRITICAL=>"Critical",
];
/**
* A shorthand way to send a LogEvent
*
* When parsing a user request, a flash message should give info to the user
* When taking action, a log event should be stored by the server
* Quite often, both of these happen at once, hence log_*() having $flash
*/
function log_msg(string $section, int $priority, string $message, ?string $flash=null)
{
global $page;
send_event(new LogEvent($section, $priority, $message));
$threshold = defined("CLI_LOG_LEVEL") ? CLI_LOG_LEVEL : 0;
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && ($priority >= $threshold)) {
print date("c")." $section: $message\n";
ob_flush();
}
if (!is_null($flash)) {
$page->flash($flash);
}
}
// More shorthand ways of logging
function log_debug(string $section, string $message, ?string $flash=null)
{
log_msg($section, SCORE_LOG_DEBUG, $message, $flash);
}
function log_info(string $section, string $message, ?string $flash=null)
{
log_msg($section, SCORE_LOG_INFO, $message, $flash);
}
function log_warning(string $section, string $message, ?string $flash=null)
{
log_msg($section, SCORE_LOG_WARNING, $message, $flash);
}
function log_error(string $section, string $message, ?string $flash=null)
{
log_msg($section, SCORE_LOG_ERROR, $message, $flash);
}
function log_critical(string $section, string $message, ?string $flash=null)
{
log_msg($section, SCORE_LOG_CRITICAL, $message, $flash);
}
/**
* Get a unique ID for this request, useful for grouping log messages.
*/
function get_request_id(): string
{
static $request_id = null;
if (!$request_id) {
// not completely trustworthy, as a user can spoof this
if (@$_SERVER['HTTP_X_VARNISH']) {
$request_id = $_SERVER['HTTP_X_VARNISH'];
} else {
$request_id = "P" . uniqid();
}
}
return $request_id;
}

View File

@ -1,286 +0,0 @@
<?php
/**
* \page themes Themes
*
* Each extension has a theme with a specific name -- eg. the extension Setup
* which is stored in ext/setup/main.php will have a theme called SetupTheme
* stored in ext/setup/theme.php. If you want to customise it, create a class
* in the file themes/mytheme/setup.theme.php called CustomSetupTheme which
* extends SetupTheme and overrides some of its methods.
*
* Generally an extension should only deal with processing data; whenever it
* wants to display something, it should pass the data to be displayed to the
* theme object, and the theme will add the data into the global $page
* structure.
*
* A page should make sure that all the data it outputs is free from dangerous
* data by using html_escape(), url_escape(), or int_escape() as appropriate.
*
* Because some HTML can be placed anywhere according to the theme, coming up
* with the correct way to link to a page can be hard -- thus we have the
* make_link() function, which will take a path like "post/list" and turn it
* into a full and correct link, eg /myboard/post/list, /foo/index.php?q=post/list,
* etc depending on how things are set up. This should always be used to link
* to pages rather than hardcoding a path.
*
* Various other common functions are available as part of the Themelet class.
*/
/**
* A data structure for holding all the bits of data that make up a page.
*
* The various extensions all add whatever they want to this structure,
* then Layout turns it into HTML
*/
class Page {
/** @name Overall */
//@{
/** @private */
var $mode = "page";
/** @private */
var $type = "text/html; charset=utf-8";
/**
* Set what this page should do; "page", "data", or "redirect".
*/
public function set_mode($mode) {
$this->mode = $mode;
}
/**
* Set the page's MIME type
*/
public function set_type($type) {
$this->type = $type;
}
//@}
// ==============================================
/** @name "data" mode */
//@{
/** @private */
var $data = "";
/** @private */
var $filename = null;
/**
* Set the raw data to be sent
*/
public function set_data($data) {
$this->data = $data;
}
/**
* Set the recommended download filename
*/
public function set_filename($filename) {
$this->filename = $filename;
}
//@}
// ==============================================
/** @name "redirect" mode */
//@{
/** @private */
var $redirect = "";
/**
* Set the URL to redirect to (remember to use make_link() if linking
* to a page in the same site)
*/
public function set_redirect($redirect) {
$this->redirect = $redirect;
}
//@}
// ==============================================
/** @name "page" mode */
//@{
/** @privatesection */
var $title = "";
var $heading = "";
var $subheading = "";
var $quicknav = "";
var $html_headers = array();
var $http_headers = array();
var $blocks = array();
/** @publicsection */
/**
* Set the window title
*/
public function set_title($title) {
$this->title = $title;
}
/**
* Set the main heading
*/
public function set_heading($heading) {
$this->heading = $heading;
}
/**
* Set the sub heading
*/
public function set_subheading($subheading) {
$this->subheading = $subheading;
}
/**
* Add a line to the HTML head section
*/
public function add_html_header($line, $position=50) {
while(isset($this->html_headers[$position])) $position++;
$this->html_headers[$position] = $line;
}
/**
* Add a http header to be sent to the client.
*/
public function add_http_header($line, $position=50) {
while(isset($this->http_headers[$position])) $position++;
$this->http_headers[$position] = $line;
}
/**
* Get all the HTML headers that are currently set and return as a string.
*/
public function get_all_html_headers() {
$data = '';
foreach ($this->html_headers as $line) {
$data .= $line . "\n";
}
return $data;
}
/**
* Removes all currently set HTML headers. (Be careful..)
*/
public function delete_all_html_headers() {
$this->html_headers = array();
}
/**
* Add a Block of data
*/
public function add_block(Block $block) {
$this->blocks[] = $block;
}
//@}
// ==============================================
/**
* Display the page according to the mode and data given
*/
public function display() {
global $page, $user;
header("Content-type: ".$this->type);
header("X-Powered-By: SCore-".SCORE_VERSION);
if (!headers_sent()) {
foreach($this->http_headers as $head){ header($head); }
} else {
print "Error: Headers have already been sent to the client.";
}
switch($this->mode) {
case "page":
if(CACHE_HTTP) {
header("Vary: Cookie, Accept-Encoding");
if($user->is_anonymous() && $_SERVER["REQUEST_METHOD"] == "GET") {
header("Cache-control: public, max-age=600");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 600) . ' GMT');
}
else {
#header("Cache-control: private, max-age=0");
header("Cache-control: no-cache");
header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
}
}
#else {
# header("Cache-control: no-cache");
# header('Expires: ' . gmdate('D, d M Y H:i:s', time() - 600) . ' GMT');
#}
usort($this->blocks, "blockcmp");
$this->add_auto_html_headers();
$layout = new Layout();
$layout->display_page($page);
break;
case "data":
header("Content-Length: ".strlen($this->data));
if(!is_null($this->filename)) {
header('Content-Disposition: attachment; filename='.$this->filename);
}
print $this->data;
break;
case "redirect":
header('Location: '.$this->redirect);
print 'You should be redirected to <a href="'.$this->redirect.'">'.$this->redirect.'</a>';
break;
default:
print "Invalid page mode";
break;
}
}
protected function add_auto_html_headers() {
global $config;
$data_href = get_base_href();
$theme_name = $config->get_string('theme', 'default');
$this->add_html_header("<script type='text/javascript'>base_href = '$data_href';</script>");
# 404/static handler will map these to themes/foo/bar.ico or lib/static/bar.ico
$this->add_html_header("<link rel='icon' type='image/x-icon' href='$data_href/favicon.ico'>");
$this->add_html_header("<link rel='apple-touch-icon' href='$data_href/apple-touch-icon.png'>");
$css_files = array();
$css_latest = 0;
foreach(array_merge(zglob("lib/*.css"), zglob("ext/*/style.css"), zglob("themes/$theme_name/style.css")) as $css) {
$css_files[] = $css;
$css_latest = max($css_latest, filemtime($css));
}
$css_cache_file = data_path("cache/style.$css_latest.css");
if(!file_exists($css_cache_file)) {
$css_data = "";
foreach($css_files as $file) {
$file_data = file_get_contents($file);
$pattern = '/url[\s]*\([\s]*["\']?([^"\'\)]+)["\']?[\s]*\)/';
$replace = 'url("../../'.dirname($file).'/$1")';
$file_data = preg_replace($pattern, $replace, $file_data);
$css_data .= $file_data . "\n";
}
file_put_contents($css_cache_file, $css_data);
}
$this->add_html_header("<link rel='stylesheet' href='$data_href/$css_cache_file' type='text/css'>");
$js_files = array();
$js_latest = 0;
foreach(array_merge(zglob("lib/*.js"), zglob("ext/*/script.js"), zglob("themes/$theme_name/script.js")) as $js) {
$js_files[] = $js;
$js_latest = max($js_latest, filemtime($js));
}
$js_cache_file = data_path("cache/script.$js_latest.js");
if(!file_exists($js_cache_file)) {
$js_data = "";
foreach($js_files as $file) {
$js_data .= file_get_contents($file) . "\n";
}
file_put_contents($js_cache_file, $js_data);
}
$this->add_html_header("<script src='$data_href/$js_cache_file' type='text/javascript'></script>");
}
}
?>

106
core/permissions.php Normal file
View File

@ -0,0 +1,106 @@
<?php declare(strict_types=1);
// action_object_attribute
// action = create / view / edit / delete
// object = image / user / tag / setting
abstract class Permissions
{
public const CHANGE_SETTING = "change_setting"; # modify web-level settings, eg the config table
public const OVERRIDE_CONFIG = "override_config"; # modify sys-level settings, eg shimmie.conf.php
public const BIG_SEARCH = "big_search"; # search for more than 3 tags at once (speed mode only)
public const MANAGE_EXTENSION_LIST = "manage_extension_list";
public const MANAGE_ALIAS_LIST = "manage_alias_list";
public const MANAGE_AUTO_TAG = "manage_auto_tag";
public const MASS_TAG_EDIT = "mass_tag_edit";
public const VIEW_IP = "view_ip"; # view IP addresses associated with things
public const BAN_IP = "ban_ip";
public const CREATE_USER = "create_user";
public const CREATE_OTHER_USER = "create_other_user";
public const EDIT_USER_NAME = "edit_user_name";
public const EDIT_USER_PASSWORD = "edit_user_password";
public const EDIT_USER_INFO = "edit_user_info"; # email address, etc
public const EDIT_USER_CLASS = "edit_user_class";
public const DELETE_USER = "delete_user";
public const CREATE_COMMENT = "create_comment";
public const DELETE_COMMENT = "delete_comment";
public const BYPASS_COMMENT_CHECKS = "bypass_comment_checks"; # spam etc
public const REPLACE_IMAGE = "replace_image";
public const CREATE_IMAGE = "create_image";
public const EDIT_IMAGE_TAG = "edit_image_tag";
public const EDIT_IMAGE_SOURCE = "edit_image_source";
public const EDIT_IMAGE_OWNER = "edit_image_owner";
public const EDIT_IMAGE_LOCK = "edit_image_lock";
public const EDIT_IMAGE_TITLE = "edit_image_title";
public const EDIT_IMAGE_RELATIONSHIPS = "edit_image_relationships";
public const EDIT_IMAGE_ARTIST = "edit_image_artist";
public const BULK_EDIT_IMAGE_TAG = "bulk_edit_image_tag";
public const BULK_EDIT_IMAGE_SOURCE = "bulk_edit_image_source";
public const DELETE_IMAGE = "delete_image";
public const BAN_IMAGE = "ban_image";
public const VIEW_EVENTLOG = "view_eventlog";
public const IGNORE_DOWNTIME = "ignore_downtime";
public const VIEW_REGISTRATIONS = "view_registrations";
public const CREATE_IMAGE_REPORT = "create_image_report";
public const VIEW_IMAGE_REPORT = "view_image_report"; # deal with reported images
public const WIKI_ADMIN = "wiki_admin";
public const EDIT_WIKI_PAGE = "edit_wiki_page";
public const DELETE_WIKI_PAGE = "delete_wiki_page";
public const MANAGE_BLOCKS = "manage_blocks";
public const MANAGE_ADMINTOOLS = "manage_admintools";
public const SEND_PM = "send_pm";
public const READ_PM = "read_pm";
public const VIEW_OTHER_PMS = "view_other_pms";
public const EDIT_FEATURE = "edit_feature";
public const BULK_EDIT_VOTE = "bulk_edit_vote";
public const EDIT_OTHER_VOTE = "edit_other_vote";
public const VIEW_SYSINTO = "view_sysinfo";
public const HELLBANNED = "hellbanned";
public const VIEW_HELLBANNED = "view_hellbanned";
public const PROTECTED = "protected"; # only admins can modify protected users (stops a moderator changing an admin's password)
public const EDIT_IMAGE_RATING = "edit_image_rating";
public const BULK_EDIT_IMAGE_RATING = "bulk_edit_image_rating";
public const VIEW_TRASH = "view_trash";
public const PERFORM_BULK_ACTIONS = "perform_bulk_actions";
public const BULK_ADD = "bulk_add";
public const EDIT_FILES = "edit_files";
public const EDIT_TAG_CATEGORIES = "edit_tag_categories";
public const RESCAN_MEDIA = "rescan_media";
public const SEE_IMAGE_VIEW_COUNTS = "see_image_view_counts";
public const EDIT_FAVOURITES = "edit_favourites";
public const ARTISTS_ADMIN = "artists_admin";
public const BLOTTER_ADMIN = "blotter_admin";
public const FORUM_ADMIN = "forum_admin";
public const NOTES_ADMIN = "notes_admin";
public const POOLS_ADMIN = "pools_admin";
public const TIPS_ADMIN = "tips_admin";
public const CRON_ADMIN = "cron_admin";
public const APPROVE_IMAGE = "approve_image";
public const APPROVE_COMMENT = "approve_comment";
public const SET_PRIVATE_IMAGE = "set_private_image";
public const SET_OTHERS_PRIVATE_IMAGES = "set_others_private_images";
public const BULK_IMPORT = "bulk_import";
public const BULK_EXPORT = "bulk_export";
public const BULK_DOWNLOAD = "bulk_download";
}

777
core/polyfills.php Normal file
View File

@ -0,0 +1,777 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Things which should be in the core API *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
require_once "filetypes.php";
/**
* Return the unique elements of an array, case insensitively
*/
function array_iunique(array $array): array
{
$ok = [];
foreach ($array as $element) {
$found = false;
foreach ($ok as $existing) {
if (strtolower($element) == strtolower($existing)) {
$found = true;
break;
}
}
if (!$found) {
$ok[] = $element;
}
}
return $ok;
}
/**
* Figure out if an IP is in a specified range
*
* from https://uk.php.net/network
*/
function ip_in_range(string $IP, string $CIDR): bool
{
list($net, $mask) = explode("/", $CIDR);
$ip_net = ip2long($net);
$ip_mask = ~((1 << (32 - $mask)) - 1);
$ip_ip = ip2long($IP);
$ip_ip_net = $ip_ip & $ip_mask;
return ($ip_ip_net == $ip_net);
}
/**
* Delete an entire file heirachy
*
* from a patch by Christian Walde; only intended for use in the
* "extension manager" extension, but it seems to fit better here
*/
function deltree(string $f): void
{
//Because Windows (I know, bad excuse)
if (PHP_OS === 'WINNT') {
$real = realpath($f);
$path = realpath('./').'\\'.str_replace('/', '\\', $f);
if ($path != $real) {
rmdir($path);
} else {
foreach (glob($f.'/*') as $sf) {
if (is_dir($sf) && !is_link($sf)) {
deltree($sf);
} else {
unlink($sf);
}
}
rmdir($f);
}
} else {
if (is_link($f)) {
unlink($f);
} elseif (is_dir($f)) {
foreach (glob($f.'/*') as $sf) {
if (is_dir($sf) && !is_link($sf)) {
deltree($sf);
} else {
unlink($sf);
}
}
rmdir($f);
}
}
}
/**
* Copy an entire file hierarchy
*
* from a comment on https://uk.php.net/copy
*/
function full_copy(string $source, string $target): void
{
if (is_dir($source)) {
@mkdir($target);
$d = dir($source);
while (false !== ($entry = $d->read())) {
if ($entry == '.' || $entry == '..') {
continue;
}
$Entry = $source . '/' . $entry;
if (is_dir($Entry)) {
full_copy($Entry, $target . '/' . $entry);
continue;
}
copy($Entry, $target . '/' . $entry);
}
$d->close();
} else {
copy($source, $target);
}
}
/**
* Return a list of all the regular files in a directory and subdirectories
*/
function list_files(string $base, string $_sub_dir=""): array
{
assert(is_dir($base));
$file_list = [];
$files = [];
$dir = opendir("$base/$_sub_dir");
while ($f = readdir($dir)) {
$files[] = $f;
}
closedir($dir);
sort($files);
foreach ($files as $filename) {
$full_path = "$base/$_sub_dir/$filename";
if (!is_link($full_path) && is_dir($full_path)) {
if (!($filename == "." || $filename == "..")) {
//subdirectory found
$file_list = array_merge(
$file_list,
list_files($base, "$_sub_dir/$filename")
);
}
} else {
$full_path = str_replace("//", "/", $full_path);
$file_list[] = $full_path;
}
}
return $file_list;
}
function flush_output(): void
{
if (!defined("UNITTEST")) {
@ob_flush();
}
flush();
}
function stream_file(string $file, int $start, int $end): void
{
$fp = fopen($file, 'r');
try {
set_time_limit(0);
fseek($fp, $start);
$buffer = 1024 * 1024;
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
if ($p + $buffer > $end) {
$buffer = $end - $p + 1;
}
echo fread($fp, $buffer);
flush_output();
// After flush, we can tell if the client browser has disconnected.
// This means we can start sending a large file, and if we detect they disappeared
// then we can just stop and not waste any more resources or bandwidth.
if (connection_status() != 0) {
break;
}
}
} finally {
fclose($fp);
}
}
if (!function_exists('http_parse_headers')) { #http://www.php.net/manual/en/function.http-parse-headers.php#112917
/**
* #return string[]
*/
function http_parse_headers(string $raw_headers): array
{
$headers = []; // $headers = [];
foreach (explode("\n", $raw_headers) as $i => $h) {
$h = explode(':', $h, 2);
if (isset($h[1])) {
if (!isset($headers[$h[0]])) {
$headers[$h[0]] = trim($h[1]);
} elseif (is_array($headers[$h[0]])) {
$tmp = array_merge($headers[$h[0]], [trim($h[1])]);
$headers[$h[0]] = $tmp;
} else {
$tmp = array_merge([$headers[$h[0]]], [trim($h[1])]);
$headers[$h[0]] = $tmp;
}
}
}
return $headers;
}
}
/**
* HTTP Headers can sometimes be lowercase which will cause issues.
* In cases like these, we need to make sure to check for them if the camelcase version does not exist.
*/
function findHeader(array $headers, string $name): ?string
{
if (!is_array($headers)) {
return null;
}
$header = null;
if (array_key_exists($name, $headers)) {
$header = $headers[$name];
} else {
$headers = array_change_key_case($headers); // convert all to lower case.
$lc_name = strtolower($name);
if (array_key_exists($lc_name, $headers)) {
$header = $headers[$lc_name];
}
}
return $header;
}
if (!function_exists('mb_strlen')) {
// TODO: we should warn the admin that they are missing multibyte support
function mb_strlen($str, $encoding)
{
return strlen($str);
}
function mb_internal_encoding($encoding)
{
}
function mb_strtolower($str)
{
return strtolower($str);
}
}
/** @noinspection PhpUnhandledExceptionInspection */
function getSubclassesOf(string $parent)
{
$result = [];
foreach (get_declared_classes() as $class) {
$rclass = new ReflectionClass($class);
if (!$rclass->isAbstract() && is_subclass_of($class, $parent)) {
$result[] = $class;
}
}
return $result;
}
/**
* Like glob, with support for matching very long patterns with braces.
*/
function zglob(string $pattern): array
{
$results = [];
if (preg_match('/(.*)\{(.*)\}(.*)/', $pattern, $matches)) {
$braced = explode(",", $matches[2]);
foreach ($braced as $b) {
$sub_pattern = $matches[1].$b.$matches[3];
$results = array_merge($results, zglob($sub_pattern));
}
return $results;
} else {
$r = glob($pattern);
if ($r) {
return $r;
} else {
return [];
}
}
}
/**
* Figure out the path to the shimmie install directory.
*
* eg if shimmie is visible at https://foo.com/gallery, this
* function should return /gallery
*
* PHP really, really sucks.
*/
function get_base_href(): string
{
if (defined("BASE_HREF") && !empty(BASE_HREF)) {
return BASE_HREF;
}
$possible_vars = ['SCRIPT_NAME', 'PHP_SELF', 'PATH_INFO', 'ORIG_PATH_INFO'];
$ok_var = null;
foreach ($possible_vars as $var) {
if (isset($_SERVER[$var]) && substr($_SERVER[$var], -4) === '.php') {
$ok_var = $_SERVER[$var];
break;
}
}
assert(!empty($ok_var));
$dir = dirname($ok_var);
$dir = str_replace("\\", "/", $dir);
$dir = str_replace("//", "/", $dir);
$dir = rtrim($dir, "/");
return $dir;
}
/**
* The opposite of the standard library's parse_url
*/
function unparse_url($parsed_url)
{
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$query = !empty($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
$fragment = !empty($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
function startsWith(string $haystack, string $needle): bool
{
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
function endsWith(string $haystack, string $needle): bool
{
$length = strlen($needle);
$start = $length * -1; //negative
return (substr($haystack, $start) === $needle);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Input / Output Sanitising *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Make some data safe for printing into HTML
*/
function html_escape(?string $input): string
{
if (is_null($input)) {
return "";
}
return htmlentities($input, ENT_QUOTES, "UTF-8");
}
/**
* Unescape data that was made safe for printing into HTML
*/
function html_unescape(string $input): string
{
return html_entity_decode($input, ENT_QUOTES, "UTF-8");
}
/**
* Make sure some data is safe to be used in integer context
*/
function int_escape(?string $input): int
{
/*
Side note, Casting to an integer is FASTER than using intval.
http://hakre.wordpress.com/2010/05/13/php-casting-vs-intval/
*/
if (is_null($input)) {
return 0;
}
return (int)$input;
}
/**
* Make sure some data is safe to be used in URL context
*/
function url_escape(?string $input): string
{
if (is_null($input)) {
return "";
}
$input = rawurlencode($input);
return $input;
}
/**
* Turn all manner of HTML / INI / JS / DB booleans into a PHP one
*/
function bool_escape($input): bool
{
/*
Sometimes, I don't like PHP -- this, is one of those times...
"a boolean FALSE is not considered a valid boolean value by this function."
Yay for Got'chas!
https://php.net/manual/en/filter.filters.validate.php
*/
if (is_bool($input)) {
return $input;
} elseif (is_int($input)) {
return ($input === 1);
} else {
$value = filter_var($input, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if (!is_null($value)) {
return $value;
} else {
$input = strtolower(trim($input));
return (
$input === "y" ||
$input === "yes" ||
$input === "t" ||
$input === "true" ||
$input === "on" ||
$input === "1"
);
}
}
}
/**
* Some functions require a callback function for escaping,
* but we might not want to alter the data
*/
function no_escape(string $input): string
{
return $input;
}
/**
* Given a 1-indexed numeric-ish thing, return a zero-indexed
* number between 0 and $max
*/
function page_number(string $input, ?int $max=null): int
{
if (!is_numeric($input)) {
$pageNumber = 0;
} elseif ($input <= 0) {
$pageNumber = 0;
} elseif (!is_null($max) && $input >= $max) {
$pageNumber = $max - 1;
} else {
$pageNumber = $input - 1;
}
return $pageNumber;
}
function clamp(?int $val, ?int $min=null, ?int $max=null): int
{
if (!is_numeric($val) || (!is_null($min) && $val < $min)) {
$val = $min;
}
if (!is_null($max) && $val > $max) {
$val = $max;
}
if (!is_null($min) && !is_null($max)) {
assert($val >= $min && $val <= $max, "$min <= $val <= $max");
}
return $val;
}
function xml_tag(string $name, array $attrs=[], array $children=[]): string
{
$xml = "<$name ";
foreach ($attrs as $k => $v) {
$xv = str_replace('&#039;', '&apos;', htmlspecialchars((string)$v, ENT_QUOTES));
$xml .= "$k=\"$xv\" ";
}
if (count($children) > 0) {
$xml .= ">\n";
foreach ($children as $child) {
$xml .= xml_tag($child);
}
$xml .= "</$name>\n";
} else {
$xml .= "/>\n";
}
return $xml;
}
/**
* Original PHP code by Chirp Internet: www.chirp.com.au
* Please acknowledge use of this code by including this header.
*/
function truncate(string $string, int $limit, string $break=" ", string $pad="..."): string
{
// return with no change if string is shorter than $limit
if (strlen($string) <= $limit) {
return $string;
}
// is $break present between $limit and the end of the string?
if (false !== ($breakpoint = strpos($string, $break, $limit))) {
if ($breakpoint < strlen($string) - 1) {
$string = substr($string, 0, $breakpoint) . $pad;
}
}
return $string;
}
/**
* Turn a human readable filesize into an integer, eg 1KB -> 1024
*/
function parse_shorthand_int(string $limit): int
{
if (preg_match('/^([\d\.]+)([tgmk])?b?$/i', (string)$limit, $m)) {
$value = $m[1];
if (isset($m[2])) {
switch (strtolower($m[2])) {
/** @noinspection PhpMissingBreakStatementInspection */
case 't': $value *= 1024; // fall through
/** @noinspection PhpMissingBreakStatementInspection */
// no break
case 'g': $value *= 1024; // fall through
/** @noinspection PhpMissingBreakStatementInspection */
// no break
case 'm': $value *= 1024; // fall through
/** @noinspection PhpMissingBreakStatementInspection */
// no break
case 'k': $value *= 1024; break;
default: $value = -1;
}
}
return (int)$value;
} else {
return -1;
}
}
/**
* Turn an integer into a human readable filesize, eg 1024 -> 1KB
*/
function to_shorthand_int(int $int): string
{
assert($int >= 0);
if ($int >= pow(1024, 4)) {
return sprintf("%.1fTB", $int / pow(1024, 4));
} elseif ($int >= pow(1024, 3)) {
return sprintf("%.1fGB", $int / pow(1024, 3));
} elseif ($int >= pow(1024, 2)) {
return sprintf("%.1fMB", $int / pow(1024, 2));
} elseif ($int >= 1024) {
return sprintf("%.1fKB", $int / 1024);
} else {
return (string)$int;
}
}
const TIME_UNITS = ["s"=>60,"m"=>60,"h"=>24,"d"=>365,"y"=>PHP_INT_MAX];
function format_milliseconds(int $input): string
{
$output = "";
$remainder = floor($input / 1000);
foreach (TIME_UNITS as $unit=>$conversion) {
$count = $remainder % $conversion;
$remainder = floor($remainder / $conversion);
if ($count==0&&$remainder<1) {
break;
}
$output = "$count".$unit." ".$output;
}
return trim($output);
}
/**
* Turn a date into a time, a date, an "X minutes ago...", etc
*/
function autodate(string $date, bool $html=true): string
{
$cpu = date('c', strtotime($date));
$hum = date('F j, Y; H:i', strtotime($date));
return ($html ? "<time datetime='$cpu'>$hum</time>" : $hum);
}
/**
* Check if a given string is a valid date-time. ( Format: yyyy-mm-dd hh:mm:ss )
*/
function isValidDateTime(string $dateTime): bool
{
if (preg_match("/^(\d{4})-(\d{2})-(\d{2}) ([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/", $dateTime, $matches)) {
if (checkdate((int)$matches[2], (int)$matches[3], (int)$matches[1])) {
return true;
}
}
return false;
}
/**
* Check if a given string is a valid date. ( Format: yyyy-mm-dd )
*/
function isValidDate(string $date): bool
{
if (preg_match("/^(\d{4})-(\d{2})-(\d{2})$/", $date, $matches)) {
// checkdate wants (month, day, year)
if (checkdate((int)$matches[2], (int)$matches[3], (int)$matches[1])) {
return true;
}
}
return false;
}
function validate_input(array $inputs): array
{
$outputs = [];
foreach ($inputs as $key => $validations) {
$flags = explode(',', $validations);
if (in_array('bool', $flags) && !isset($_POST[$key])) {
$_POST[$key] = 'off';
}
if (in_array('optional', $flags)) {
if (!isset($_POST[$key]) || trim($_POST[$key]) == "") {
$outputs[$key] = null;
continue;
}
}
if (!isset($_POST[$key]) || trim($_POST[$key]) == "") {
throw new InvalidInput("Input '$key' not set");
}
$value = trim($_POST[$key]);
if (in_array('user_id', $flags)) {
$id = int_escape($value);
if (in_array('exists', $flags)) {
if (is_null(User::by_id($id))) {
throw new InvalidInput("User #$id does not exist");
}
}
$outputs[$key] = $id;
} elseif (in_array('user_name', $flags)) {
if (strlen($value) < 1) {
throw new InvalidInput("Username must be at least 1 character");
} elseif (!preg_match('/^[a-zA-Z0-9-_]+$/', $value)) {
throw new InvalidInput(
"Username contains invalid characters. Allowed characters are ".
"letters, numbers, dash, and underscore"
);
}
$outputs[$key] = $value;
} elseif (in_array('user_class', $flags)) {
global $_shm_user_classes;
if (!array_key_exists($value, $_shm_user_classes)) {
throw new InvalidInput("Invalid user class: ".html_escape($value));
}
$outputs[$key] = $value;
} elseif (in_array('email', $flags)) {
$outputs[$key] = trim($value);
} elseif (in_array('password', $flags)) {
$outputs[$key] = $value;
} elseif (in_array('int', $flags)) {
$value = trim($value);
if (empty($value) || !is_numeric($value)) {
throw new InvalidInput("Invalid int: ".html_escape($value));
}
$outputs[$key] = (int)$value;
} elseif (in_array('bool', $flags)) {
$outputs[$key] = bool_escape($value);
} elseif (in_array('date', $flags)) {
$outputs[$key] = date("Y-m-d H:i:s", strtotime(trim($value)));
} elseif (in_array('string', $flags)) {
if (in_array('trim', $flags)) {
$value = trim($value);
}
if (in_array('lower', $flags)) {
$value = strtolower($value);
}
if (in_array('not-empty', $flags)) {
throw new InvalidInput("$key must not be blank");
}
if (in_array('nullify', $flags)) {
if (empty($value)) {
$value = null;
}
}
$outputs[$key] = $value;
} else {
throw new InvalidInput("Unknown validation '$validations'");
}
}
return $outputs;
}
/**
* Translates all possible directory separators to the appropriate one for the current system,
* and removes any duplicate separators.
*/
function sanitize_path(string $path): string
{
return preg_replace('|[\\\\/]+|S', DIRECTORY_SEPARATOR, $path);
}
/**
* Combines all path segments specified, ensuring no duplicate separators occur,
* as well as converting all possible separators to the one appropriate for the current system.
*/
function join_path(string ...$paths): string
{
$output = "";
foreach ($paths as $path) {
if (empty($path)) {
continue;
}
$path = sanitize_path($path);
if (empty($output)) {
$output = $path;
} else {
$output = rtrim($output, DIRECTORY_SEPARATOR);
$path = ltrim($path, DIRECTORY_SEPARATOR);
$output .= DIRECTORY_SEPARATOR . $path;
}
}
return $output;
}
/**
* Perform callback on each item returned by an iterator.
*/
function iterator_map(callable $callback, iterator $iter): Generator
{
foreach ($iter as $i) {
yield call_user_func($callback, $i);
}
}
/**
* Perform callback on each item returned by an iterator and combine the result into an array.
*/
function iterator_map_to_array(callable $callback, iterator $iter): array
{
return iterator_to_array(iterator_map($callback, $iter));
}
function stringer($s)
{
if (is_array($s)) {
if (isset($s[0])) {
return "[" . implode(", ", array_map("stringer", $s)) . "]";
} else {
$pairs = [];
foreach ($s as $k=>$v) {
$pairs[] = "\"$k\"=>" . stringer($v);
}
return "[" . implode(", ", $pairs) . "]";
}
}
if (is_string($s)) {
return "\"$s\""; // FIXME: handle escaping quotes
}
return (string)$s;
}

63
core/sanitize_php.php Normal file
View File

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
/*
* A small number of PHP-sanity things (eg don't silently ignore errors) to
* be included right at the very start of index.php and tests/bootstrap.php
*/
$min_php = "7.3";
if (version_compare(phpversion(), $min_php, ">=") === false) {
print "
Shimmie does not support versions of PHP lower than $min_php
(PHP reports that it is version ".phpversion().").
If your web host is running an older version, they are dangerously out of
date and you should plan on moving elsewhere.
";
exit;
}
# ini_set('zend.assertions', '1'); // generate assertions
ini_set('assert.exception', '1'); // throw exceptions when failed
set_error_handler(function ($errNo, $errStr) {
// Should we turn ALL notices into errors? PHP allows a lot of
// terrible things to happen by default...
if (strpos($errStr, 'Use of undefined constant ') === 0) {
throw new Exception("PHP Error#$errNo: $errStr");
} else {
return false;
}
});
ob_start();
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
if (isset($_SERVER['REMOTE_ADDR'])) {
die("CLI with remote addr? Confused, not taking the risk.");
}
$_SERVER['REMOTE_ADDR'] = "0.0.0.0";
$_SERVER['HTTP_HOST'] = "<cli command>";
}
function die_nicely($title, $body, $code=0)
{
print("<!DOCTYPE html>
<html lang='en'>
<head>
<title>Shimmie</title>
<link rel=\"shortcut icon\" href=\"ext/static_files/static/favicon.ico\">
<link rel=\"stylesheet\" href=\"ext/static_files/style.css\" type=\"text/css\">
</head>
<body>
<div id=\"installer\">
<h1>Shimmie</h1>
<h3>$title</h3>
<div class=\"container\">
$body
</div>
</div>
</body>
</html>");
if ($code != 0) {
http_response_code(500);
}
exit($code);
}

128
core/send_event.php Normal file
View File

@ -0,0 +1,128 @@
<?php declare(strict_types=1);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Event API *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @private */
global $_shm_event_listeners;
$_shm_event_listeners = [];
function _load_event_listeners(): void
{
global $_shm_event_listeners;
$cache_path = data_path("cache/shm_event_listeners.php");
if (SPEED_HAX && file_exists($cache_path)) {
require_once($cache_path);
} else {
_set_event_listeners();
if (SPEED_HAX) {
_dump_event_listeners($_shm_event_listeners, $cache_path);
}
}
}
function _clear_cached_event_listeners(): void
{
if (file_exists(data_path("cache/shm_event_listeners.php"))) {
unlink(data_path("cache/shm_event_listeners.php"));
}
}
function _set_event_listeners(): void
{
global $_shm_event_listeners;
$_shm_event_listeners = [];
foreach (getSubclassesOf("Extension") as $class) {
/** @var Extension $extension */
$extension = new $class();
// skip extensions which don't support our current database
if (!$extension->info->is_supported()) {
continue;
}
foreach (get_class_methods($extension) as $method) {
if (substr($method, 0, 2) == "on") {
$event = substr($method, 2) . "Event";
$pos = $extension->get_priority() * 100;
while (isset($_shm_event_listeners[$event][$pos])) {
$pos += 1;
}
$_shm_event_listeners[$event][$pos] = $extension;
}
}
}
}
function _dump_event_listeners(array $event_listeners, string $path): void
{
$p = "<"."?php\n";
foreach (getSubclassesOf("Extension") as $class) {
$p .= "\$$class = new $class(); ";
}
$p .= "\$_shm_event_listeners = array(\n";
foreach ($event_listeners as $event => $listeners) {
$p .= "\t'$event' => array(\n";
foreach ($listeners as $id => $listener) {
$p .= "\t\t$id => \$".get_class($listener).",\n";
}
$p .= "\t),\n";
}
$p .= ");\n";
file_put_contents($path, $p);
}
/** @private */
global $_shm_event_count;
$_shm_event_count = 0;
/**
* Send an event to all registered Extensions.
*/
function send_event(Event $event): Event
{
global $tracer_enabled;
global $_shm_event_listeners, $_shm_event_count, $_tracer;
if (!isset($_shm_event_listeners[get_class($event)])) {
return $event;
}
$method_name = "on".str_replace("Event", "", get_class($event));
// send_event() is performance sensitive, and with the number
// of times tracer gets called the time starts to add up
if ($tracer_enabled) {
$_tracer->begin(get_class($event));
}
// SHIT: https://bugs.php.net/bug.php?id=35106
$my_event_listeners = $_shm_event_listeners[get_class($event)];
ksort($my_event_listeners);
foreach ($my_event_listeners as $listener) {
if ($tracer_enabled) {
$_tracer->begin(get_class($listener));
}
if (method_exists($listener, $method_name)) {
$listener->$method_name($event);
}
if ($tracer_enabled) {
$_tracer->end();
}
if ($event->stop_processing===true) {
break;
}
}
$_shm_event_count++;
if ($tracer_enabled) {
$_tracer->end();
}
return $event;
}

View File

@ -1,48 +0,0 @@
<?php
/*
* First, load the user-specified settings
*/
@include_once "data/config/shimmie.conf.php";
@include_once "data/config/extensions.conf.php";
/**
* For any values that aren't defined in the above files, Shimmie
* will set the values to their defaults
*
* All of these can be over-ridden by placing a 'define' in data/config/shimmie.conf.php
*
* Do NOT change them in this file. These are the defaults only!
*
* Example:
* define("SPEED_HAX", true);
*
*/
function _d($name, $value) {if(!defined($name)) define($name, $value);}
_d("DATABASE_DSN", null); // string PDO database connection details
_d("CACHE_DSN", null); // string cache connection details
_d("DEBUG", false); // boolean print various debugging details
_d("DEBUG_SQL", false); // boolean dump SQL queries to data/sql.log
_d("COVERAGE", false); // boolean activate xdebug coverage monitor
_d("CONTEXT", null); // string file to log performance data into
_d("CACHE_MEMCACHE", false); // boolean store complete rendered pages in memcache
_d("CACHE_DIR", false); // boolean store complete rendered pages on disk
_d("CACHE_HTTP", false); // boolean output explicit HTTP caching headers
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
_d("COMPILE_ELS", false); // boolean pre-build the list of event listeners
_d("NICE_URLS", false); // boolean force niceurl mode
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
_d("VERSION", 'trunk'); // string shimmie version
_d("TIMEZONE", null); // string timezone
_d("CORE_EXTS", "bbcode,user,mail,upload,image,view,handle_pixel,ext_manager,setup,upgrade,handle_404,comment,tag_list,index,tag_edit,alias_editor"); // extensions to always enable
_d("EXTRA_EXTS", ""); // optional extra extensions
/*
* Calculated settings - you should never need to change these
* directly, only the things they're built from
*/
_d("SCORE_VERSION", 's2hack/'.VERSION); // string SCore version
_d("ENABLED_EXTS", CORE_EXTS.",".EXTRA_EXTS);
?>

34
core/sys_config.php Normal file
View File

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
/**
* For any values that aren't defined in data/config/*.php,
* Shimmie will set the values to their defaults
*
* All of these can be over-ridden by placing a 'define' in
* data/config/shimmie.conf.php
*
* Do NOT change them in this file. These are the defaults only!
*
* Example:
* define("SPEED_HAX", true);
*/
function _d(string $name, $value): void
{
if (!defined($name)) {
define($name, $value);
}
}
$_g = file_exists(".git") ? '+' : '';
_d("DATABASE_DSN", null); // string PDO database connection details
_d("DATABASE_TIMEOUT", 10000);// int Time to wait for each statement to complete
_d("CACHE_DSN", null); // string cache connection details
_d("DEBUG", false); // boolean print various debugging details
_d("COOKIE_PREFIX", 'shm'); // string if you run multiple galleries with non-shared logins, give them different prefixes
_d("SPEED_HAX", false); // boolean do some questionable things in the name of performance
_d("WH_SPLITS", 1); // int how many levels of subfolders to put in the warehouse
_d("VERSION", "2.8.4$_g"); // string shimmie version
_d("TIMEZONE", null); // string timezone
_d("EXTRA_EXTS", ""); // string optional extra extensions
_d("BASE_HREF", null); // string force a specific base URL (default is auto-detect)
_d("TRACE_FILE", null); // string file to log performance data into
_d("TRACE_THRESHOLD", 0.0); // float log pages which take more time than this many seconds

View File

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/basepage.php";
class BasePageTest extends TestCase
{
public function test_page()
{
$page = new BasePage();
$page->set_mode(PageMode::PAGE);
ob_start();
$page->display();
ob_end_clean();
$this->assertTrue(true); // doesn't crash
}
public function test_file()
{
$page = new BasePage();
$page->set_mode(PageMode::FILE);
$page->set_file("tests/pbx_screenshot.jpg");
ob_start();
$page->display();
ob_end_clean();
$this->assertTrue(true); // doesn't crash
}
public function test_data()
{
$page = new BasePage();
$page->set_mode(PageMode::DATA);
$page->set_data("hello world");
ob_start();
$page->display();
ob_end_clean();
$this->assertTrue(true); // doesn't crash
}
public function test_redirect()
{
$page = new BasePage();
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect("/new/page");
ob_start();
$page->display();
ob_end_clean();
$this->assertTrue(true); // doesn't crash
}
}

17
core/tests/block.test.php Normal file
View File

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/block.php";
class BlockTest extends TestCase
{
public function test_basic()
{
$b = new Block("head", "body");
$this->assertEquals(
"<section id='headmain'><h3 data-toggle-sel='#headmain' class=''>head</h3><div class='blockbody'>body</div></section>\n",
$b->get_html()
);
}
}

18
core/tests/init.test.php Normal file
View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
class TestInit extends TestCase
{
public function testInitExt()
{
send_event(new InitExtEvent());
$this->assertTrue(true);
}
public function testDatabaseUpgrade()
{
send_event(new DatabaseUpgradeEvent());
$this->assertTrue(true);
}
}

View File

@ -0,0 +1,223 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/polyfills.php";
class PolyfillsTest extends TestCase
{
public function test_html_escape()
{
$this->assertEquals(
html_escape("Foo & <waffles>"),
"Foo &amp; &lt;waffles&gt;"
);
$this->assertEquals(
html_unescape("Foo &amp; &lt;waffles&gt;"),
"Foo & <waffles>"
);
$x = "Foo &amp; &lt;waffles&gt;";
$this->assertEquals(html_escape(html_unescape($x)), $x);
}
public function test_int_escape()
{
$this->assertEquals(int_escape(""), 0);
$this->assertEquals(int_escape("1"), 1);
$this->assertEquals(int_escape("-1"), -1);
$this->assertEquals(int_escape("-1.5"), -1);
$this->assertEquals(int_escape(null), 0);
}
public function test_url_escape()
{
$this->assertEquals(url_escape("^\o/^"), "%5E%5Co%2F%5E");
$this->assertEquals(url_escape(null), "");
}
public function test_bool_escape()
{
$this->assertTrue(bool_escape(true));
$this->assertFalse(bool_escape(false));
$this->assertTrue(bool_escape("true"));
$this->assertFalse(bool_escape("false"));
$this->assertTrue(bool_escape("t"));
$this->assertFalse(bool_escape("f"));
$this->assertTrue(bool_escape("T"));
$this->assertFalse(bool_escape("F"));
$this->assertTrue(bool_escape("yes"));
$this->assertFalse(bool_escape("no"));
$this->assertTrue(bool_escape("Yes"));
$this->assertFalse(bool_escape("No"));
$this->assertTrue(bool_escape("on"));
$this->assertFalse(bool_escape("off"));
$this->assertTrue(bool_escape(1));
$this->assertFalse(bool_escape(0));
$this->assertTrue(bool_escape("1"));
$this->assertFalse(bool_escape("0"));
}
public function test_clamp()
{
$this->assertEquals(clamp(0, 5, 10), 5);
$this->assertEquals(clamp(5, 5, 10), 5);
$this->assertEquals(clamp(7, 5, 10), 7);
$this->assertEquals(clamp(10, 5, 10), 10);
$this->assertEquals(clamp(15, 5, 10), 10);
}
public function test_xml_tag()
{
$this->assertEquals(
"<test foo=\"bar\" >\n<cake />\n</test>\n",
xml_tag("test", ["foo"=>"bar"], ["cake"])
);
}
public function test_truncate()
{
$this->assertEquals(truncate("test words", 10), "test words");
$this->assertEquals(truncate("test...", 9), "test...");
$this->assertEquals(truncate("test...", 6), "test...");
$this->assertEquals(truncate("te...", 2), "te...");
}
public function test_to_shorthand_int()
{
$this->assertEquals(to_shorthand_int(1231231231), "1.1GB");
$this->assertEquals(to_shorthand_int(2), "2");
}
public function test_parse_shorthand_int()
{
$this->assertEquals(parse_shorthand_int("foo"), -1);
$this->assertEquals(parse_shorthand_int("32M"), 33554432);
$this->assertEquals(parse_shorthand_int("43.4KB"), 44441);
$this->assertEquals(parse_shorthand_int("1231231231"), 1231231231);
}
public function test_format_milliseconds()
{
$this->assertEquals("", format_milliseconds(5));
$this->assertEquals("5s", format_milliseconds(5000));
$this->assertEquals("1y 213d 16h 53m 20s", format_milliseconds(50000000000));
}
public function test_autodate()
{
$this->assertEquals(
"<time datetime='2012-06-23T16:14:22+00:00'>June 23, 2012; 16:14</time>",
autodate("2012-06-23 16:14:22")
);
}
public function test_validate_input()
{
$_POST = [
"foo" => " bar ",
"to_null" => " ",
"num" => "42",
];
$this->assertEquals(
["foo"=>"bar"],
validate_input(["foo"=>"string,trim,lower"])
);
//$this->assertEquals(
// ["to_null"=>null],
// validate_input(["to_null"=>"string,trim,nullify"])
//);
$this->assertEquals(
["num"=>42],
validate_input(["num"=>"int"])
);
}
public function test_sanitize_path()
{
$this->assertEquals(
"one",
sanitize_path("one")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one\\two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one/two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one\\\\two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one//two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one\\\\\\two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
sanitize_path("one///two")
);
$this->assertEquals(
DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR,
sanitize_path("\\/one/\\/\\/two\\/")
);
}
public function test_join_path()
{
$this->assertEquals(
"one",
join_path("one")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two",
join_path("one", "two")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three",
join_path("one", "two", "three")
);
$this->assertEquals(
"one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three",
join_path("one/two", "three")
);
$this->assertEquals(
DIRECTORY_SEPARATOR."one".DIRECTORY_SEPARATOR."two".DIRECTORY_SEPARATOR."three".DIRECTORY_SEPARATOR,
join_path("\\/////\\\\one/\///"."\\//two\/\\//\\//", "//\/\\\/three/\\/\/")
);
}
public function test_stringer()
{
$this->assertEquals(
'["foo"=>"bar", "baz"=>[1, 2, 3], "qux"=>["a"=>"b"]]',
stringer(["foo"=>"bar", "baz"=>[1,2,3], "qux"=>["a"=>"b"]])
);
}
}

22
core/tests/tag.test.php Normal file
View File

@ -0,0 +1,22 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/imageboard/tag.php";
class TagTest extends TestCase
{
public function test_caret()
{
$this->assertEquals("foo", Tag::decaret("foo"));
$this->assertEquals("foo?", Tag::decaret("foo^q"));
$this->assertEquals("a^b/c\\d?e&f", Tag::decaret("a^^b^sc^bd^qe^af"));
}
public function test_decaret()
{
$this->assertEquals("foo", Tag::caret("foo"));
$this->assertEquals("foo^q", Tag::caret("foo?"));
$this->assertEquals("a^^b^sc^bd^qe^af", Tag::caret("a^b/c\\d?e&f"));
}
}

101
core/tests/urls.test.php Normal file
View File

@ -0,0 +1,101 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/urls.php";
class UrlsTest extends TestCase
{
public function test_make_link()
{
// basic
$this->assertEquals(
"/test/foo",
make_link("foo")
);
// remove leading slash from path
$this->assertEquals(
"/test/foo",
make_link("/foo")
);
// query
$this->assertEquals(
"/test/foo?a=1&b=2",
make_link("foo", "a=1&b=2")
);
// hash
$this->assertEquals(
"/test/foo#cake",
make_link("foo", null, "cake")
);
// query + hash
$this->assertEquals(
"/test/foo?a=1&b=2#cake",
make_link("foo", "a=1&b=2", "cake")
);
}
public function test_make_http()
{
// relative to shimmie install
$this->assertEquals(
"http://<cli command>/test/foo",
make_http("foo")
);
// relative to web server
$this->assertEquals(
"http://<cli command>/foo",
make_http("/foo")
);
// absolute
$this->assertEquals(
"https://foo.com",
make_http("https://foo.com")
);
}
public function test_modify_url()
{
$this->assertEquals(
"/foo/bar?a=3&b=2",
modify_url("/foo/bar?a=1&b=2", ["a"=>"3"])
);
$this->assertEquals(
"https://blah.com/foo/bar?b=2",
modify_url("https://blah.com/foo/bar?a=1&b=2", ["a"=>null])
);
$this->assertEquals(
"/foo/bar",
modify_url("/foo/bar?a=1&b=2", ["a"=>null, "b"=>null])
);
}
public function test_referer_or()
{
unset($_SERVER['HTTP_REFERER']);
$this->assertEquals(
"foo",
referer_or("foo")
);
$_SERVER['HTTP_REFERER'] = "cake";
$this->assertEquals(
"cake",
referer_or("foo")
);
$_SERVER['HTTP_REFERER'] = "cake";
$this->assertEquals(
"foo",
referer_or("foo", ["cake"])
);
}
}

86
core/tests/util.test.php Normal file
View File

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
require_once "core/util.php";
class UtilTest extends TestCase
{
public function test_warehouse_path()
{
$hash = "7ac19c10d6859415";
$this->assertEquals(
join_path(DATA_DIR, "base", $hash),
warehouse_path("base", $hash, false, 0)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", $hash),
warehouse_path("base", $hash, false, 1)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", $hash),
warehouse_path("base", $hash, false, 2)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", $hash),
warehouse_path("base", $hash, false, 3)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", $hash),
warehouse_path("base", $hash, false, 4)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", $hash),
warehouse_path("base", $hash, false, 5)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", "85", $hash),
warehouse_path("base", $hash, false, 6)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", "85", "94", $hash),
warehouse_path("base", $hash, false, 7)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", "85", "94", "15", $hash),
warehouse_path("base", $hash, false, 8)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", "85", "94", "15", $hash),
warehouse_path("base", $hash, false, 9)
);
$this->assertEquals(
join_path(DATA_DIR, "base", "7a", "c1", "9c", "10", "d6", "85", "94", "15", $hash),
warehouse_path("base", $hash, false, 10)
);
}
public function test_load_balance_url()
{
$hash = "7ac19c10d6859415";
$ext = "jpg";
// pseudo-randomly select one of the image servers, balanced in given ratio
$this->assertEquals(
"https://baz.mycdn.com/7ac19c10d6859415.jpg",
load_balance_url("https://{foo=10,bar=5,baz=5}.mycdn.com/$hash.$ext", $hash)
);
// N'th and N+1'th results should be different
$this->assertNotEquals(
load_balance_url("https://{foo=10,bar=5,baz=5}.mycdn.com/$hash.$ext", $hash, 0),
load_balance_url("https://{foo=10,bar=5,baz=5}.mycdn.com/$hash.$ext", $hash, 1)
);
}
}

114
core/urls.php Normal file
View File

@ -0,0 +1,114 @@
<?php declare(strict_types=1);
class Link
{
public $page;
public $query;
public function __construct(?string $page=null, ?string $query=null)
{
$this->page = $page;
$this->query = $query;
}
public function make_link(): string
{
return make_link($this->page, $this->query);
}
}
/**
* Figure out the correct way to link to a page, taking into account
* things like the nice URLs setting.
*
* eg make_link("post/list") becomes "/v2/index.php?q=post/list"
*/
function make_link(?string $page=null, ?string $query=null, ?string $fragment=null): string
{
global $config;
if (is_null($page)) {
$page = $config->get_string(SetupConfig::MAIN_PAGE);
}
$page = trim($page, "/");
$parts = [];
$install_dir = get_base_href();
if (SPEED_HAX || $config->get_bool('nice_urls', false)) {
$parts['path'] = "$install_dir/$page";
} else {
$parts['path'] = "$install_dir/index.php";
$query = empty($query) ? "q=$page" : "q=$page&$query";
}
$parts['query'] = $query; // http_build_query($query);
$parts['fragment'] = $fragment; // http_build_query($hash);
return unparse_url($parts);
}
/**
* Take the current URL and modify some parameters
*/
function modify_current_url(array $changes): string
{
return modify_url($_SERVER['REQUEST_URI'], $changes);
}
function modify_url(string $url, array $changes): string
{
$parts = parse_url($url);
$params = [];
if (isset($parts['query'])) {
parse_str($parts['query'], $params);
}
foreach ($changes as $k => $v) {
if (is_null($v) and isset($params[$k])) {
unset($params[$k]);
}
$params[$k] = $v;
}
$parts['query'] = http_build_query($params);
return unparse_url($parts);
}
/**
* Turn a relative link into an absolute one, including hostname
*/
function make_http(string $link): string
{
if (strpos($link, "://") > 0) {
return $link;
}
if (strlen($link) > 0 && $link[0] != '/') {
$link = get_base_href() . '/' . $link;
}
$protocol = is_https_enabled() ? "https://" : "http://";
$link = $protocol . $_SERVER["HTTP_HOST"] . $link;
$link = str_replace("/./", "/", $link);
return $link;
}
/**
* If HTTP_REFERER is set, and not blacklisted, then return it
* Else return a default $dest
*/
function referer_or(string $dest, ?array $blacklist=null): string
{
if (empty($_SERVER['HTTP_REFERER'])) {
return $dest;
}
if ($blacklist) {
foreach ($blacklist as $b) {
if (strstr($_SERVER['HTTP_REFERER'], $b)) {
return $dest;
}
}
}
return $_SERVER['HTTP_REFERER'];
}

View File

@ -1,199 +0,0 @@
<?php
/** @private */
function _new_user($row) {
return new User($row);
}
/**
* An object representing a row in the "users" table.
*
* The currently logged in user will always be accessable via the global variable $user
*/
class User {
var $id;
var $name;
var $email;
var $join_date;
var $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *
* *
* User objects shouldn't be created directly, they should be *
* fetched from the database like so: *
* *
* $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc
*/
public function User($row) {
global $_user_classes;
$this->id = int_escape($row['id']);
$this->name = $row['name'];
$this->email = $row['email'];
$this->join_date = $row['joindate'];
$this->passhash = $row['pass'];
$this->class = $_user_classes[$row["class"]];
}
public static function by_session(/*string*/ $name, /*string*/ $session) {
global $config, $database;
if($database->engine->name === "mysql") {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
}
else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
}
$row = $database->get_row($query, array("name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session));
return is_null($row) ? null : new User($row);
}
public static function by_id(/*int*/ $id) {
assert(is_numeric($id));
global $database;
if($id === 1) {
$cached = $database->cache->get('user-id:'.$id);
if($cached) return new User($cached);
}
$row = $database->get_row("SELECT * FROM users WHERE id = :id", array("id"=>$id));
if($id === 1) $database->cache->set('user-id:'.$id, $row, 300);
return is_null($row) ? null : new User($row);
}
public static function by_name(/*string*/ $name) {
assert(is_string($name));
global $database;
$row = $database->get_row("SELECT * FROM users WHERE name = :name", array("name"=>$name));
return is_null($row) ? null : new User($row);
}
public static function by_name_and_hash(/*string*/ $name, /*string*/ $hash) {
assert(is_string($name));
assert(is_string($hash));
assert(strlen($hash) == 32);
global $database;
$row = $database->get_row("SELECT * FROM users WHERE name = :name AND pass = :hash", array("name"=>$name, "hash"=>$hash));
return is_null($row) ? null : new User($row);
}
public static function by_list(/*int*/ $offset, /*int*/ $limit=50) {
assert(is_numeric($offset));
assert(is_numeric($limit));
global $database;
$rows = $database->get_all("SELECT * FROM users WHERE id >= :start AND id < :end", array("start"=>$offset, "end"=>$offset+$limit));
return array_map("_new_user", $rows);
}
/*
* useful user object functions start here
*/
public function can($ability) {
return $this->class->can($ability);
}
/**
* Test if this user is anonymous (not logged in)
*
* @retval bool
*/
public function is_anonymous() {
global $config;
return ($this->id === $config->get_int('anon_id'));
}
/**
* Test if this user is logged in
*
* @retval bool
*/
public function is_logged_in() {
global $config;
return ($this->id !== $config->get_int('anon_id'));
}
/**
* Test if this user is an administrator
*
* @retval bool
*/
public function is_admin() {
return ($this->class->name === "admin");
}
public function set_class(/*string*/ $class) {
assert(is_string($class));
global $database;
$database->Execute("UPDATE users SET class=:class WHERE id=:id", array("class"=>$class, "id"=>$this->id));
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
}
public function set_password(/*string*/ $password) {
global $database;
$hash = md5(strtolower($this->name) . $password);
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$hash, "id"=>$this->id));
log_info("core-user", 'Set password for '.$this->name);
}
public function set_email(/*string*/ $address) {
global $database;
$database->Execute("UPDATE users SET email=:email WHERE id=:id", array("email"=>$address, "id"=>$this->id));
log_info("core-user", 'Set email for '.$this->name);
}
/**
* Get a snippet of HTML which will render the user's avatar, be that
* a local file, a remote file, a gravatar, a something else, etc
* @retval String of HTML
*/
public function get_avatar_html() {
// FIXME: configurable
global $config;
if($config->get_string("avatar_host") === "gravatar") {
if(!empty($this->email)) {
$hash = md5(strtolower($this->email));
$s = $config->get_string("avatar_gravatar_size");
$d = urlencode($config->get_string("avatar_gravatar_default"));
$r = $config->get_string("avatar_gravatar_rating");
return "<img class=\"avatar gravatar\" src=\"http://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r\">";
}
}
return "";
}
/**
* Get an auth token to be used in POST forms
*
* password = secret, avoid storing directly
* passhash = md5(password), so someone who gets to the database can't get passwords
* sesskey = md5(passhash . IP), so if it gets sniffed it can't be used from another IP,
* and it can't be used to get the passhash to generate new sesskeys
* authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that
* the form was generated within the session. Salted and re-hashed so that
* reading a web page from the user's cache doesn't give access to the session key
*
* @retval String containing auth token (MD5sum)
*/
public function get_auth_token() {
global $config;
$salt = DATABASE_DSN;
$addr = get_session_ip($config);
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
}
public function get_auth_html() {
$at = $this->get_auth_token();
return '<input type="hidden" name="auth_token" value="'.$at.'">';
}
public function check_auth_token() {
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
}
}
?>

262
core/user.php Normal file
View File

@ -0,0 +1,262 @@
<?php declare(strict_types=1);
function _new_user(array $row): User
{
return new User($row);
}
/**
* Class User
*
* An object representing a row in the "users" table.
*
* The currently logged in user will always be accessible via the global variable $user.
*/
class User
{
/** @var int */
public $id;
/** @var string */
public $name;
/** @var string */
public $email;
public $join_date;
/** @var string */
public $passhash;
/** @var UserClass */
public $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *
* *
* User objects shouldn't be created directly, they should be *
* fetched from the database like so: *
* *
* $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc.
*
* @throws SCoreException
*/
public function __construct(array $row)
{
global $_shm_user_classes;
$this->id = int_escape((string)$row['id']);
$this->name = $row['name'];
$this->email = $row['email'];
$this->join_date = $row['joindate'];
$this->passhash = $row['pass'];
if (array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_shm_user_classes[$row["class"]];
} else {
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
}
}
public static function by_session(string $name, string $session): ?User
{
global $cache, $config, $database;
$row = $cache->get("user-session:$name-$session");
if (!$row) {
if ($database->get_driver_name() === DatabaseDriver::MYSQL) {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
} else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
}
$row = $database->get_row($query, ["name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session]);
$cache->set("user-session:$name-$session", $row, 600);
}
return is_null($row) ? null : new User($row);
}
public static function by_id(int $id): ?User
{
global $cache, $database;
if ($id === 1) {
$cached = $cache->get('user-id:'.$id);
if ($cached) {
return new User($cached);
}
}
$row = $database->get_row("SELECT * FROM users WHERE id = :id", ["id"=>$id]);
if ($id === 1) {
$cache->set('user-id:'.$id, $row, 600);
}
return is_null($row) ? null : new User($row);
}
public static function by_name(string $name): ?User
{
global $database;
$row = $database->get_row("SELECT * FROM users WHERE LOWER(name) = LOWER(:name)", ["name"=>$name]);
return is_null($row) ? null : new User($row);
}
public static function name_to_id(string $name): int
{
$u = User::by_name($name);
if (is_null($u)) {
throw new ScoreException("Can't find any user named $name");
} else {
return $u->id;
}
}
public static function by_name_and_pass(string $name, string $pass): ?User
{
$my_user = User::by_name($name);
// If user tried to log in as "foo bar" and failed, try "foo_bar"
if (!$my_user && strpos($name, " ") !== false) {
$my_user = User::by_name(str_replace(" ", "_", $name));
}
if ($my_user) {
if ($my_user->passhash == md5(strtolower($name) . $pass)) {
log_info("core-user", "Migrating from md5 to bcrypt for $name");
$my_user->set_password($pass);
}
if (password_verify($pass, $my_user->passhash)) {
log_info("core-user", "Logged in as $name ({$my_user->class->name})");
return $my_user;
} else {
log_warning("core-user", "Failed to log in as $name (Invalid password)");
}
} else {
log_warning("core-user", "Failed to log in as $name (Invalid username)");
}
return null;
}
/* useful user object functions start here */
public function can(string $ability): bool
{
return $this->class->can($ability);
}
public function is_anonymous(): bool
{
global $config;
return ($this->id === $config->get_int('anon_id'));
}
public function is_logged_in(): bool
{
global $config;
return ($this->id !== $config->get_int('anon_id'));
}
public function set_class(string $class): void
{
global $database;
$database->Execute("UPDATE users SET class=:class WHERE id=:id", ["class"=>$class, "id"=>$this->id]);
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
}
public function set_name(string $name): void
{
global $database;
if (User::by_name($name)) {
throw new ScoreException("Desired username is already in use");
}
$old_name = $this->name;
$this->name = $name;
$database->Execute("UPDATE users SET name=:name WHERE id=:id", ["name"=>$this->name, "id"=>$this->id]);
log_info("core-user", "Changed username for {$old_name} to {$this->name}");
}
public function set_password(string $password): void
{
global $database;
$hash = password_hash($password, PASSWORD_BCRYPT);
if (is_string($hash)) {
$this->passhash = $hash;
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", ["hash"=>$this->passhash, "id"=>$this->id]);
log_info("core-user", 'Set password for '.$this->name);
} else {
throw new SCoreException("Failed to hash password");
}
}
public function set_email(string $address): void
{
global $database;
$database->Execute("UPDATE users SET email=:email WHERE id=:id", ["email"=>$address, "id"=>$this->id]);
log_info("core-user", 'Set email for '.$this->name);
}
/**
* Get a snippet of HTML which will render the user's avatar, be that
* a local file, a remote file, a gravatar, a something else, etc.
*/
public function get_avatar_html(): string
{
// FIXME: configurable
global $config;
if ($config->get_string("avatar_host") === "gravatar") {
if (!empty($this->email)) {
$hash = md5(strtolower($this->email));
$s = $config->get_string("avatar_gravatar_size");
$d = urlencode($config->get_string("avatar_gravatar_default"));
$r = $config->get_string("avatar_gravatar_rating");
$cb = date("Y-m-d");
return "<img alt='avatar' class=\"avatar gravatar\" src=\"https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb\">";
}
}
return "";
}
/**
* Get an auth token to be used in POST forms
*
* password = secret, avoid storing directly
* passhash = bcrypt(password), so someone who gets to the database can't get passwords
* sesskey = md5(passhash . IP), so if it gets sniffed it can't be used from another IP,
* and it can't be used to get the passhash to generate new sesskeys
* authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that
* the form was generated within the session. Salted and re-hashed so that
* reading a web page from the user's cache doesn't give access to the session key
*/
public function get_auth_token(): string
{
global $config;
$salt = DATABASE_DSN;
$addr = get_session_ip($config);
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
}
public function get_auth_html(): string
{
$at = $this->get_auth_token();
return '<input type="hidden" name="auth_token" value="'.$at.'">';
}
public function check_auth_token(): bool
{
if (defined("UNITTEST")) {
return true;
}
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
}
public function ensure_authed(): void
{
if (!$this->check_auth_token()) {
die("Invalid auth token");
}
}
}

View File

@ -1,161 +0,0 @@
<?php
$_user_classes = array();
class UserClass {
var $name = null;
var $parent = null;
var $abilities = array();
public function __construct($name, $parent=null, $abilities=array()) {
global $_user_classes;
$this->name = $name;
$this->abilities = $abilities;
if(!is_null($parent)) {
$this->parent = $_user_classes[$parent];
}
$_user_classes[$name] = $this;
}
public function can(/*string*/ $ability) {
global $config;
if(array_key_exists($ability, $this->abilities)) {
$val = $this->abilities[$ability];
return $val;
}
else if(!is_null($this->parent)) {
return $this->parent->can($ability);
}
else {
global $_user_classes;
$min_dist = 9999;
$min_ability = null;
foreach($_user_classes['base']->abilities as $a => $cando) {
$v = levenshtein($ability, $a);
if($v < $min_dist) {
$min_dist = $v;
$min_ability = $a;
}
}
throw new SCoreException("Unknown ability '".html_escape($ability)."'. Did the developer mean '".html_escape($min_ability)."'?");
}
}
}
// action_object_attribute
// action = create / view / edit / delete
// object = image / user / tag / setting
new UserClass("base", null, array(
"change_setting" => False, # modify web-level settings, eg the config table
"override_config" => False, # modify sys-level settings, eg shimmie.conf.php
"big_search" => False, # search for more than 3 tags at once (speed mode only)
"manage_extension_list" => False,
"manage_alias_list" => False,
"mass_tag_edit" => False,
"view_ip" => False, # view IP addresses associated with things
"ban_ip" => False,
"edit_user_password" => False,
"edit_user_info" => False, # email address, etc
"edit_user_class" => False,
"delete_user" => False,
"create_comment" => False,
"delete_comment" => False,
"replace_image" => False,
"create_image" => False,
"edit_image_tag" => False,
"edit_image_source" => False,
"edit_image_owner" => False,
"edit_image_lock" => False,
"bulk_edit_image_tag" => False,
"bulk_edit_image_source" => False,
"delete_image" => False,
"ban_image" => False,
"view_eventlog" => False,
"ignore_downtime" => False,
"create_image_report" => False,
"view_image_report" => False, # deal with reported images
"edit_wiki_page" => False,
"delete_wiki_page" => False,
"manage_blocks" => False,
"manage_admintools" => False,
"view_other_pms" => False,
"edit_feature" => False,
"bulk_edit_vote" => False,
"edit_other_vote" => False,
"view_sysinfo" => False,
"protected" => False, # only admins can modify protected users (stops a moderator changing an admin's password)
));
new UserClass("anonymous", "base", array(
));
new UserClass("user", "base", array(
"big_search" => True,
"create_image" => True,
"create_comment" => True,
"edit_image_tag" => True,
"edit_image_source" => True,
"create_image_report" => True,
));
new UserClass("admin", "base", array(
"change_setting" => True,
"override_config" => True,
"big_search" => True,
"edit_image_lock" => True,
"view_ip" => True,
"ban_ip" => True,
"edit_user_password" => True,
"edit_user_info" => True,
"edit_user_class" => True,
"delete_user" => True,
"create_image" => True,
"delete_image" => True,
"ban_image" => True,
"create_comment" => True,
"delete_comment" => True,
"replace_image" => True,
"manage_extension_list" => True,
"manage_alias_list" => True,
"edit_image_tag" => True,
"edit_image_source" => True,
"edit_image_owner" => True,
"bulk_edit_image_tag" => True,
"bulk_edit_image_source" => True,
"mass_tag_edit" => True,
"create_image_report" => True,
"view_image_report" => True,
"edit_wiki_page" => True,
"delete_wiki_page" => True,
"view_eventlog" => True,
"manage_blocks" => True,
"manage_admintools" => True,
"ignore_downtime" => True,
"view_other_pms" => True,
"edit_feature" => True,
"bulk_edit_vote" => True,
"edit_other_vote" => True,
"view_sysinfo" => True,
"protected" => True,
));
if(file_exists("data/config/user-classes.conf.php")) {
require_once("data/config/user-classes.conf.php");
}
?>

211
core/userclass.php Normal file
View File

@ -0,0 +1,211 @@
<?php declare(strict_types=1);
/**
* @global UserClass[] $_shm_user_classes
*/
global $_shm_user_classes;
$_shm_user_classes = [];
/**
* Class UserClass
*/
class UserClass
{
/**
* @var ?string
*/
public $name = null;
/**
* @var ?UserClass
*/
public $parent = null;
/**
* @var array
*/
public $abilities = [];
public function __construct(string $name, string $parent = null, array $abilities = [])
{
global $_shm_user_classes;
$this->name = $name;
$this->abilities = $abilities;
if (!is_null($parent)) {
$this->parent = $_shm_user_classes[$parent];
}
$_shm_user_classes[$name] = $this;
}
/**
* Determine if this class of user can perform an action or has ability.
*
* @throws SCoreException
*/
public function can(string $ability): bool
{
if (array_key_exists($ability, $this->abilities)) {
return $this->abilities[$ability];
} elseif (!is_null($this->parent)) {
return $this->parent->can($ability);
} else {
global $_shm_user_classes;
$min_dist = 9999;
$min_ability = null;
foreach ($_shm_user_classes['base']->abilities as $a => $cando) {
$v = levenshtein($ability, $a);
if ($v < $min_dist) {
$min_dist = $v;
$min_ability = $a;
}
}
throw new SCoreException("Unknown ability '$ability'. Did the developer mean '$min_ability'?");
}
}
}
$_all_false = [];
foreach ((new ReflectionClass('Permissions'))->getConstants() as $k => $v) {
$_all_false[$v] = false;
}
new UserClass("base", null, $_all_false);
unset($_all_false);
// Ghost users can't do anything
new UserClass("ghost", "base", [
]);
// Anonymous users can't do anything by default, but
// the admin might grant them some permissions
new UserClass("anonymous", "base", [
Permissions::CREATE_USER => true,
]);
new UserClass("user", "base", [
Permissions::BIG_SEARCH => true,
Permissions::CREATE_IMAGE => true,
Permissions::CREATE_COMMENT => true,
Permissions::EDIT_IMAGE_TAG => true,
Permissions::EDIT_IMAGE_SOURCE => true,
Permissions::EDIT_IMAGE_TITLE => true,
Permissions::EDIT_IMAGE_RELATIONSHIPS => true,
Permissions::EDIT_IMAGE_ARTIST => true,
Permissions::CREATE_IMAGE_REPORT => true,
Permissions::EDIT_IMAGE_RATING => true,
Permissions::EDIT_FAVOURITES => true,
Permissions::SEND_PM => true,
Permissions::READ_PM => true,
Permissions::SET_PRIVATE_IMAGE => true,
Permissions::BULK_DOWNLOAD => true,
]);
new UserClass("hellbanned", "user", [
Permissions::HELLBANNED => true,
]);
new UserClass("admin", "base", [
Permissions::CHANGE_SETTING => true,
Permissions::OVERRIDE_CONFIG => true,
Permissions::BIG_SEARCH => true,
Permissions::MANAGE_EXTENSION_LIST => true,
Permissions::MANAGE_ALIAS_LIST => true,
Permissions::MANAGE_AUTO_TAG => true,
Permissions::MASS_TAG_EDIT => true,
Permissions::VIEW_IP => true,
Permissions::BAN_IP => true,
Permissions::CREATE_USER => true,
Permissions::CREATE_OTHER_USER => true,
Permissions::EDIT_USER_NAME => true,
Permissions::EDIT_USER_PASSWORD => true,
Permissions::EDIT_USER_INFO => true,
Permissions::EDIT_USER_CLASS => true,
Permissions::DELETE_USER => true,
Permissions::CREATE_COMMENT => true,
Permissions::DELETE_COMMENT => true,
Permissions::BYPASS_COMMENT_CHECKS => true,
Permissions::REPLACE_IMAGE => true,
Permissions::CREATE_IMAGE => true,
Permissions::EDIT_IMAGE_TAG => true,
Permissions::EDIT_IMAGE_SOURCE => true,
Permissions::EDIT_IMAGE_OWNER => true,
Permissions::EDIT_IMAGE_LOCK => true,
Permissions::EDIT_IMAGE_TITLE => true,
Permissions::EDIT_IMAGE_RELATIONSHIPS => true,
Permissions::EDIT_IMAGE_ARTIST => true,
Permissions::BULK_EDIT_IMAGE_TAG => true,
Permissions::BULK_EDIT_IMAGE_SOURCE => true,
Permissions::DELETE_IMAGE => true,
Permissions::BAN_IMAGE => true,
Permissions::VIEW_EVENTLOG => true,
Permissions::IGNORE_DOWNTIME => true,
Permissions::VIEW_REGISTRATIONS => true,
Permissions::CREATE_IMAGE_REPORT => true,
Permissions::VIEW_IMAGE_REPORT => true,
Permissions::WIKI_ADMIN => true,
Permissions::EDIT_WIKI_PAGE => true,
Permissions::DELETE_WIKI_PAGE => true,
Permissions::MANAGE_BLOCKS => true,
Permissions::MANAGE_ADMINTOOLS => true,
Permissions::SEND_PM => true,
Permissions::READ_PM => true,
Permissions::VIEW_OTHER_PMS => true, # hm
Permissions::EDIT_FEATURE => true,
Permissions::BULK_EDIT_VOTE => true,
Permissions::EDIT_OTHER_VOTE => true,
Permissions::VIEW_SYSINTO => true,
Permissions::HELLBANNED => false,
Permissions::VIEW_HELLBANNED => true,
Permissions::PROTECTED => true,
Permissions::EDIT_IMAGE_RATING => true,
Permissions::BULK_EDIT_IMAGE_RATING => true,
Permissions::VIEW_TRASH => true,
Permissions::PERFORM_BULK_ACTIONS => true,
Permissions::BULK_ADD => true,
Permissions::EDIT_FILES => true,
Permissions::EDIT_TAG_CATEGORIES => true,
Permissions::RESCAN_MEDIA => true,
Permissions::SEE_IMAGE_VIEW_COUNTS => true,
Permissions::EDIT_FAVOURITES => true,
Permissions::ARTISTS_ADMIN => true,
Permissions::BLOTTER_ADMIN => true,
Permissions::FORUM_ADMIN => true,
Permissions::NOTES_ADMIN => true,
Permissions::POOLS_ADMIN => true,
Permissions::TIPS_ADMIN => true,
Permissions::CRON_ADMIN => true,
Permissions::APPROVE_IMAGE => true,
Permissions::APPROVE_COMMENT => true,
Permissions::BULK_IMPORT =>true,
Permissions::BULK_EXPORT =>true,
Permissions::BULK_DOWNLOAD => true,
Permissions::SET_PRIVATE_IMAGE => true,
Permissions::SET_OTHERS_PRIVATE_IMAGES => true,
]);
@include_once "data/config/user-classes.conf.php";

File diff suppressed because it is too large Load Diff

799
core/util.php Normal file
View File

@ -0,0 +1,799 @@
<?php declare(strict_types=1);
use function MicroHTML\emptyHTML;
use function MicroHTML\rawHTML;
use function MicroHTML\FORM;
use function MicroHTML\INPUT;
use function MicroHTML\DIV;
use function MicroHTML\PRE;
use function MicroHTML\P;
use function MicroHTML\TABLE;
use function MicroHTML\THEAD;
use function MicroHTML\TFOOT;
use function MicroHTML\TR;
use function MicroHTML\TH;
use function MicroHTML\TD;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Misc *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const DATA_DIR = "data";
function mtimefile(string $file): string
{
$data_href = get_base_href();
$mtime = filemtime($file);
return "$data_href/$file?$mtime";
}
function get_theme(): string
{
global $config;
$theme = $config->get_string(SetupConfig::THEME, "default");
if (!file_exists("themes/$theme")) {
$theme = "default";
}
return $theme;
}
function contact_link(): ?string
{
global $config;
$text = $config->get_string('contact_link');
if (is_null($text)) {
return null;
}
if (
startsWith($text, "http:") ||
startsWith($text, "https:") ||
startsWith($text, "mailto:")
) {
return $text;
}
if (strpos($text, "@")) {
return "mailto:$text";
}
if (strpos($text, "/")) {
return "http://$text";
}
return $text;
}
/**
* Check if HTTPS is enabled for the server.
*/
function is_https_enabled(): bool
{
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
}
/**
* Compare two Block objects, used to sort them before being displayed
*/
function blockcmp(Block $a, Block $b): int
{
if ($a->position == $b->position) {
return 0;
} else {
return ($a->position > $b->position) ? 1 : -1;
}
}
/**
* Figure out PHP's internal memory limit
*/
function get_memory_limit(): int
{
global $config;
// thumbnail generation requires lots of memory
$default_limit = 8*1024*1024; // 8 MB of memory is PHP's default.
$shimmie_limit = $config->get_int(MediaConfig::MEM_LIMIT);
if ($shimmie_limit < 3*1024*1024) {
// we aren't going to fit, override
$shimmie_limit = $default_limit;
}
/*
Get PHP's configured memory limit.
Note that this is set to -1 for NO memory limit.
https://ca2.php.net/manual/en/ini.core.php#ini.memory-limit
*/
$memory = parse_shorthand_int(ini_get("memory_limit"));
if ($memory == -1) {
// No memory limit.
// Return the larger of the set limits.
return max($shimmie_limit, $default_limit);
} else {
// PHP has a memory limit set.
if ($shimmie_limit > $memory) {
// Shimmie wants more memory than what PHP is currently set for.
// Attempt to set PHP's memory limit.
if (ini_set("memory_limit", "$shimmie_limit") === false) {
/* We can't change PHP's limit, oh well, return whatever its currently set to */
return $memory;
}
$memory = parse_shorthand_int(ini_get("memory_limit"));
}
// PHP's memory limit is more than Shimmie needs.
return $memory; // return the current setting
}
}
/**
* Check if PHP has the GD library installed
*/
function check_gd_version(): int
{
$gdversion = 0;
if (function_exists('gd_info')) {
$gd_info = gd_info();
if (substr_count($gd_info['GD Version'], '2.')) {
$gdversion = 2;
} elseif (substr_count($gd_info['GD Version'], '1.')) {
$gdversion = 1;
}
}
return $gdversion;
}
/**
* Check whether ImageMagick's `convert` command
* is installed and working
*/
function check_im_version(): int
{
$convert_check = exec("convert");
return (empty($convert_check) ? 0 : 1);
}
/**
* Get the currently active IP, masked to make it not change when the last
* octet or two change, for use in session cookies and such
*/
function get_session_ip(Config $config): string
{
$mask = $config->get_string("session_hash_mask", "255.255.0.0");
$addr = $_SERVER['REMOTE_ADDR'];
$addr = inet_ntop(inet_pton($addr) & inet_pton($mask));
return $addr;
}
/**
* A shorthand way to send a TextFormattingEvent and get the results.
*/
function format_text(string $string): string
{
$tfe = send_event(new TextFormattingEvent($string));
return $tfe->formatted;
}
/**
* Generates the path to a file under the data folder based on the file's hash.
* This process creates subfolders based on octet pairs from the file's hash.
* The calculated folder follows this pattern data/$base/octet_pairs/$hash
* @param string $base
* @param string $hash
* @param bool $create
* @param int $splits The number of octet pairs to split the hash into. Caps out at strlen($hash)/2.
* @return string
*/
function warehouse_path(string $base, string $hash, bool $create=true, int $splits = WH_SPLITS): string
{
$dirs =[DATA_DIR, $base];
$splits = min($splits, strlen($hash) / 2);
for ($i = 0; $i < $splits; $i++) {
$dirs[] = substr($hash, $i * 2, 2);
}
$dirs[] = $hash;
$pa = join_path(...$dirs);
if ($create && !file_exists(dirname($pa))) {
mkdir(dirname($pa), 0755, true);
}
return $pa;
}
/**
* Determines the path to the specified file in the data folder.
*/
function data_path(string $filename, bool $create = true): string
{
$filename = join_path("data", $filename);
if ($create&&!file_exists(dirname($filename))) {
mkdir(dirname($filename), 0755, true);
}
return $filename;
}
function load_balance_url(string $tmpl, string $hash, int $n=0): string
{
static $flexihashes = [];
$matches = [];
if (preg_match("/(.*){(.*)}(.*)/", $tmpl, $matches)) {
$pre = $matches[1];
$opts = $matches[2];
$post = $matches[3];
if (isset($flexihashes[$opts])) {
$flexihash = $flexihashes[$opts];
} else {
$flexihash = new Flexihash\Flexihash();
foreach (explode(",", $opts) as $opt) {
$parts = explode("=", $opt);
$parts_count = count($parts);
$opt_val = "";
$opt_weight = 0;
if ($parts_count === 2) {
$opt_val = $parts[0];
$opt_weight = $parts[1];
} elseif ($parts_count === 1) {
$opt_val = $parts[0];
$opt_weight = 1;
}
$flexihash->addTarget($opt_val, $opt_weight);
}
$flexihashes[$opts] = $flexihash;
}
// $choice = $flexihash->lookup($pre.$post);
$choices = $flexihash->lookupList($hash, $n + 1); // hash doesn't change
$choice = $choices[$n];
$tmpl = $pre . $choice . $post;
}
return $tmpl;
}
function transload(string $url, string $mfile): ?array
{
global $config;
if ($config->get_string("transload_engine") === "curl" && function_exists("curl_init")) {
$ch = curl_init($url);
$fp = fopen($mfile, "w");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_REFERER, $url);
curl_setopt($ch, CURLOPT_USERAGENT, "Shimmie-".VERSION);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
if ($response === false) {
log_warning("core-util", "Failed to transload $url");
throw new SCoreException("Failed to fetch $url");
}
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = http_parse_headers(implode("\n", preg_split('/\R/', rtrim(substr($response, 0, $header_size)))));
$body = substr($response, $header_size);
curl_close($ch);
fwrite($fp, $body);
fclose($fp);
return $headers;
}
if ($config->get_string("transload_engine") === "wget") {
$s_url = escapeshellarg($url);
$s_mfile = escapeshellarg($mfile);
system("wget --no-check-certificate $s_url --output-document=$s_mfile");
return file_exists($mfile) ? ["ok"=>"true"] : null;
}
if ($config->get_string("transload_engine") === "fopen") {
$fp_in = @fopen($url, "r");
$fp_out = fopen($mfile, "w");
if (!$fp_in || !$fp_out) {
return null;
}
$length = 0;
while (!feof($fp_in) && $length <= $config->get_int('upload_size')) {
$data = fread($fp_in, 8192);
$length += strlen($data);
fwrite($fp_out, $data);
}
fclose($fp_in);
fclose($fp_out);
$headers = http_parse_headers(implode("\n", $http_response_header));
return $headers;
}
return null;
}
function path_to_tags(string $path): string
{
$matches = [];
$tags = [];
if (preg_match("/\d+ - (.+)\.([a-zA-Z0-9]+)/", basename($path), $matches)) {
$tags = explode(" ", $matches[1]);
}
$path = dirname($path);
$path = str_replace(";", ":", $path);
$path = str_replace("__", " ", $path);
$category = "";
foreach (explode("/", $path) as $dir) {
$category_to_inherit = "";
foreach (explode(" ", $dir) as $tag) {
$tag = trim($tag);
if ($tag=="") {
continue;
}
if (substr_compare($tag, ":", -1) === 0) {
// This indicates a tag that ends in a colon,
// which is for inheriting to tags on the subfolder
$category_to_inherit = $tag;
} else {
if ($category!=""&&strpos($tag, ":") === false) {
// This indicates that category inheritance is active,
// and we've encountered a tag that does not specify a category.
// So we attach the inherited category to the tag.
$tag = $category.$tag;
}
$tags[] = $tag;
}
}
// Category inheritance only works on the immediate subfolder,
// so we hold a category until the next iteration, and then set
// it back to an empty string after that iteration
$category = $category_to_inherit;
}
return implode(" ", $tags);
}
function join_url(string $base, string ...$paths)
{
$output = $base;
foreach ($paths as $path) {
$output = rtrim($output, "/");
$path = ltrim($path, "/");
$output .= "/".$path;
}
return $output;
}
function get_dir_contents(string $dir): array
{
assert(!empty($dir));
if (!is_dir($dir)) {
return [];
}
return array_diff(
scandir(
$dir
),
['..', '.']
);
}
function remove_empty_dirs(string $dir): bool
{
assert(!empty($dir));
$result = true;
if (!is_dir($dir)) {
return false;
}
$items = array_diff(
scandir(
$dir
),
['..', '.']
);
foreach ($items as $item) {
$path = join_path($dir, $item);
if (is_dir($path)) {
$result = $result && remove_empty_dirs($path);
} else {
$result = false;
}
}
if ($result===true) {
$result = $result && rmdir($dir);
}
return $result;
}
function get_files_recursively(string $dir): array
{
assert(!empty($dir));
if (!is_dir($dir)) {
return [];
}
$things = array_diff(
scandir(
$dir
),
['..', '.']
);
$output = [];
foreach ($things as $thing) {
$path = join_path($dir, $thing);
if (is_file($path)) {
$output[] = $path;
} else {
$output = array_merge($output, get_files_recursively($path));
}
}
return $output;
}
/**
* Returns amount of files & total size of dir.
*/
function scan_dir(string $path): array
{
$bytestotal = 0;
$nbfiles = 0;
$ite = new RecursiveDirectoryIterator(
$path,
FilesystemIterator::KEY_AS_PATHNAME |
FilesystemIterator::CURRENT_AS_FILEINFO |
FilesystemIterator::SKIP_DOTS
);
foreach (new RecursiveIteratorIterator($ite) as $filename => $cur) {
try {
$filesize = $cur->getSize();
$bytestotal += $filesize;
$nbfiles++;
} catch (RuntimeException $e) {
// This usually just means that the file got eaten by the import
continue;
}
}
$size_mb = $bytestotal / 1048576; // to mb
$size_mb = number_format($size_mb, 2, '.', '');
return ['path' => $path, 'total_files' => $nbfiles, 'total_mb' => $size_mb];
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Debugging functions *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// SHIT by default this returns the time as a string. And it's not even a
// string representation of a number, it's two numbers separated by a space.
// What the fuck were the PHP developers smoking.
$_shm_load_start = microtime(true);
/**
* Collects some debug information (execution time, memory usage, queries, etc)
* and formats it to stick in the footer of the page.
*/
function get_debug_info(): string
{
global $cache, $config, $_shm_event_count, $database, $_shm_load_start;
$i_mem = sprintf("%5.2f", ((memory_get_peak_usage(true)+512)/1024)/1024);
if ($config->get_string("commit_hash", "unknown") == "unknown") {
$commit = "";
} else {
$commit = " (".$config->get_string("commit_hash").")";
}
$time = sprintf("%.2f", microtime(true) - $_shm_load_start);
$dbtime = sprintf("%.2f", $database->dbtime);
$i_files = count(get_included_files());
$hits = $cache->get_hits();
$miss = $cache->get_misses();
$debug = "<br>Took $time seconds (db:$dbtime) and {$i_mem}MB of RAM";
$debug .= "; Used $i_files files and {$database->query_count} queries";
$debug .= "; Sent $_shm_event_count events";
$debug .= "; $hits cache hits and $miss misses";
$debug .= "; Shimmie version ". VERSION . $commit;
return $debug;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* Request initialisation stuff *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @privatesection
* @noinspection PhpIncludeInspection
*/
function require_all(array $files): void
{
foreach ($files as $filename) {
require_once $filename;
}
}
function _load_core_files()
{
require_all(array_merge(
zglob("core/*.php"),
zglob("core/imageboard/*.php"),
zglob("ext/*/info.php")
));
}
function _load_theme_files()
{
require_all(_get_themelet_files(get_theme()));
}
function _set_up_shimmie_environment(): void
{
global $tracer_enabled;
if (file_exists("images") && !file_exists("data/images")) {
die_nicely("Upgrade error", "As of Shimmie 2.7 images and thumbs should be moved to data/images and data/thumbs");
}
if (TIMEZONE) {
date_default_timezone_set(TIMEZONE);
}
if (DEBUG) {
error_reporting(E_ALL);
}
// The trace system has a certain amount of memory consumption every time it is used,
// so to prevent running out of memory during complex operations code that uses it should
// check if tracer output is enabled before making use of it.
$tracer_enabled = constant('TRACE_FILE')!==null;
}
function _get_themelet_files(string $_theme): array
{
$base_themelets = [];
$base_themelets[] = 'themes/'.$_theme.'/page.class.php';
$base_themelets[] = 'themes/'.$_theme.'/themelet.class.php';
$ext_themelets = zglob("ext/{".Extension::get_enabled_extensions_as_string()."}/theme.php");
$custom_themelets = zglob('themes/'.$_theme.'/{'.Extension::get_enabled_extensions_as_string().'}.theme.php');
return array_merge($base_themelets, $ext_themelets, $custom_themelets);
}
/**
* Used to display fatal errors to the web user.
* @noinspection PhpPossiblePolymorphicInvocationInspection
*/
function _fatal_error(Exception $e): void
{
$version = VERSION;
$message = $e->getMessage();
$phpver = phpversion();
$query = is_subclass_of($e, "SCoreException") ? $e->query : null;
//$hash = exec("git rev-parse HEAD");
//$h_hash = $hash ? "<p><b>Hash:</b> $hash" : "";
//'.$h_hash.'
if (PHP_SAPI === 'cli' || PHP_SAPI == 'phpdbg') {
print("Trace: ");
$t = array_reverse($e->getTrace());
foreach ($t as $n => $f) {
$c = $f['class'] ?? '';
$t = $f['type'] ?? '';
$a = implode(", ", array_map("stringer", $f['args']));
print("$n: {$f['file']}({$f['line']}): {$c}{$t}{$f['function']}({$a})\n");
}
print("Message: $message\n");
if ($query) {
print("Query: {$query}\n");
}
print("Version: $version (on $phpver)\n");
} else {
$q = $query ? "" : "<p><b>Query:</b> " . html_escape($query);
header("HTTP/1.0 500 Internal Error");
echo '
<!doctype html>
<html lang="en">
<head>
<title>Internal error - SCore-'.$version.'</title>
</head>
<body>
<h1>Internal Error</h1>
<p><b>Message:</b> '.html_escape($message).'
'.$q.'
<p><b>Version:</b> '.$version.' (on '.$phpver.')
<p><b>Stack Trace:</b></p><pre>'.$e->getTraceAsString().'</pre>
</body>
</html>
';
}
}
function _get_user(): User
{
global $config, $page;
$my_user = null;
if ($page->get_cookie("user") && $page->get_cookie("session")) {
$tmp_user = User::by_session($page->get_cookie("user"), $page->get_cookie("session"));
if (!is_null($tmp_user)) {
$my_user = $tmp_user;
}
}
if (is_null($my_user)) {
$my_user = User::by_id($config->get_int("anon_id", 0));
}
assert(!is_null($my_user));
return $my_user;
}
function _get_query(): string
{
return (@$_POST["q"]?:@$_GET["q"])?:"/";
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
* HTML Generation *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Give a HTML string which shows an IP (if the user is allowed to see IPs),
* and a link to ban that IP (if the user is allowed to ban IPs)
*
* FIXME: also check that IP ban ext is installed
*/
function show_ip(string $ip, string $ban_reason): string
{
global $user;
$u_reason = url_escape($ban_reason);
$u_end = url_escape("+1 week");
$ban = $user->can(Permissions::BAN_IP) ? ", <a href='".make_link("ip_ban/list", "c_ip=$ip&c_reason=$u_reason&c_expires=$u_end", "create")."'>Ban</a>" : "";
$ip = $user->can(Permissions::VIEW_IP) ? $ip.$ban : "";
return $ip;
}
/**
* Make a form tag with relevant auth token and stuff
*/
function make_form(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit=""): string
{
global $user;
if ($method == "GET") {
$link = html_escape($target);
$target = make_link($target);
$extra_inputs = "<input type='hidden' name='q' value='$link'>";
} else {
$extra_inputs = $user->get_auth_html();
}
$extra = empty($form_id) ? '' : 'id="'. $form_id .'"';
if ($multipart) {
$extra .= " enctype='multipart/form-data'";
}
if ($onsubmit) {
$extra .= ' onsubmit="'.$onsubmit.'"';
}
return '<form action="'.$target.'" method="'.$method.'" '.$extra.'>'.$extra_inputs;
}
function SHM_FORM(string $target, string $method="POST", bool $multipart=false, string $form_id="", string $onsubmit="")
{
global $user;
$attrs = [
"action"=>make_link($target),
"method"=>$method
];
if ($form_id) {
$attrs["id"] = $form_id;
}
if ($multipart) {
$attrs["enctype"] = 'multipart/form-data';
}
if ($onsubmit) {
$attrs["onsubmit"] = $onsubmit;
}
return FORM(
$attrs,
INPUT(["type"=>"hidden", "name"=>"q", "value"=>$target]),
$method == "GET" ? "" : rawHTML($user->get_auth_html())
);
}
function SHM_SIMPLE_FORM($target, ...$children)
{
$form = SHM_FORM($target);
$form->appendChild(emptyHTML(...$children));
return $form;
}
function SHM_SUBMIT(string $text)
{
return INPUT(["type"=>"submit", "value"=>$text]);
}
function SHM_COMMAND_EXAMPLE(string $ex, string $desc)
{
return DIV(
["class"=>"command_example"],
PRE($ex),
P($desc)
);
}
function SHM_USER_FORM(User $duser, string $target, string $title, $body, $foot)
{
if (is_string($foot)) {
$foot = TFOOT(TR(TD(["colspan"=>"2"], INPUT(["type"=>"submit", "value"=>$foot]))));
}
return SHM_SIMPLE_FORM(
$target,
P(
INPUT(["type"=>'hidden', "name"=>'id', "value"=>$duser->id]),
TABLE(
["class"=>"form"],
THEAD(TR(TH(["colspan"=>"2"], $title))),
$body,
$foot
)
)
);
}
const BYTE_DENOMINATIONS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
function human_filesize(int $bytes, $decimals = 2)
{
$factor = floor((strlen(strval($bytes)) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @BYTE_DENOMINATIONS[$factor];
}
/*
* Generates a unique key for the website to prevent unauthorized access.
*/
function generate_key(int $length = 20)
{
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters [rand(0, strlen($characters) - 1)];
}
return $randomString;
}

23
ext/admin/info.php Normal file
View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
class AdminPageInfo extends ExtensionInfo
{
public const KEY = "admin";
public $key = self::KEY;
public $name = "Admin Controls";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Various things to make admins' lives easier";
public $documentation =
"Various moderate-level tools for admins; for advanced, obscure, and possibly dangerous tools see the shimmie2-utils script set
<p>Lowercase all tags:
<br>Set all tags to lowercase for consistency
<p>Recount tag use:
<br>If the counts of images per tag get messed up somehow, this will reset them, and remove any unused tags
<p>Database dump:
<br>Download the contents of the database in plain text format, useful for backups.
<p>Image dump:
<br>Download all the images as a .zip file (Requires ZipArchive)";
}

View File

@ -1,191 +1,194 @@
<?php
/**
* Name: Admin Controls
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Various things to make admins' lives easier
* Documentation:
* Various moderate-level tools for admins; for advanced, obscure, and
* possibly dangerous tools see the shimmie2-utils script set
* <p>Lowercase all tags:
* <br>Set all tags to lowercase for consistency
* <p>Recount tag use:
* <br>If the counts of images per tag get messed up somehow, this will
* reset them, and remove any unused tags
* <p>Database dump:
* <br>Download the contents of the database in plain text format, useful
* for backups.
* <p>Image dump:
* <br>Download all the images as a .zip file
*/
<?php /** @noinspection PhpUnusedPrivateMethodInspection */
declare(strict_types=1);
/**
* Sent when the admin page is ready to be added to
*/
class AdminBuildingEvent extends Event {
var $page;
public function AdminBuildingEvent(Page $page) {
$this->page = $page;
}
class AdminBuildingEvent extends Event
{
/** @var Page */
public $page;
public function __construct(Page $page)
{
parent::__construct();
$this->page = $page;
}
}
class AdminActionEvent extends Event {
var $action;
var $redirect = true;
public function __construct(/*string*/ $action) {
$this->action = $action;
}
class AdminActionEvent extends Event
{
/** @var string */
public $action;
/** @var bool */
public $redirect = true;
public function __construct(string $action)
{
parent::__construct();
$this->action = $action;
}
}
class AdminPage extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $page, $user;
class AdminPage extends Extension
{
/** @var AdminPageTheme */
protected $theme;
if($event->page_matches("admin")) {
if(!$user->can("manage_admintools")) {
$this->theme->display_permission_denied();
}
else {
if($event->count_args() == 0) {
send_event(new AdminBuildingEvent($page));
}
else {
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if($user->check_auth_token()) {
log_info("admin", "Util: $action");
set_time_limit(0);
send_event($aae);
}
if ($event->page_matches("admin")) {
if (!$user->can(Permissions::MANAGE_ADMINTOOLS)) {
$this->theme->display_permission_denied();
} else {
if ($event->count_args() == 0) {
send_event(new AdminBuildingEvent($page));
} else {
$action = $event->get_arg(0);
$aae = new AdminActionEvent($action);
if($aae->redirect) {
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
}
}
}
}
}
if ($user->check_auth_token()) {
log_info("admin", "Util: $action");
set_time_limit(0);
send_event($aae);
}
public function onAdminBuilding(AdminBuildingEvent $event) {
$this->theme->display_page();
$this->theme->display_form();
}
if ($aae->redirect) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("admin"));
}
}
}
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->can("manage_admintools")) {
$event->add_link("Board Admin", make_link("admin"));
}
}
public function onCommand(CommandEvent $event)
{
if ($event->cmd == "help") {
print "\tget-page <query string>\n";
print "\t\teg 'get-page post/list'\n\n";
print "\tpost-page <query string> <urlencoded params>\n";
print "\t\teg 'post-page ip_ban/delete id=1'\n\n";
print "\tget-token\n";
print "\t\tget a CSRF auth token\n\n";
print "\tregen-thumb <id / hash>\n";
print "\t\tregenerate a thumbnail\n\n";
print "\tcache [get|set|del] [key] <value>\n";
print "\t\teg 'cache get config'\n\n";
}
if ($event->cmd == "get-page") {
global $page;
if (isset($event->args[1])) {
parse_str($event->args[1], $_GET);
}
send_event(new PageRequestEvent($event->args[0]));
$page->display();
}
if ($event->cmd == "post-page") {
global $page;
$_SERVER['REQUEST_METHOD'] = "POST";
if (isset($event->args[1])) {
parse_str($event->args[1], $_POST);
}
send_event(new PageRequestEvent($event->args[0]));
$page->display();
}
if ($event->cmd == "get-token") {
global $user;
print($user->get_auth_token());
}
if ($event->cmd == "regen-thumb") {
$uid = $event->args[0];
$image = Image::by_id_or_hash($uid);
if ($image) {
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext, true));
} else {
print("No post with ID '$uid'\n");
}
}
if ($event->cmd == "cache") {
global $cache;
$cmd = $event->args[0];
$key = $event->args[1];
switch ($cmd) {
case "get":
var_dump($cache->get($key));
break;
case "set":
$cache->set($key, $event->args[2], 60);
break;
case "del":
$cache->delete($key);
break;
}
}
}
public function onAdminAction(AdminActionEvent $event) {
$action = $event->action;
if(method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_page();
$this->theme->display_form();
}
public function onPostListBuilding(PostListBuildingEvent $event) {
global $user;
if($user->can("manage_admintools") && !empty($event->search_terms)) {
$this->theme->display_dbq(implode(" ", $event->search_terms));
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
global $user;
if ($event->parent==="system") {
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
$event->add_nav_link("admin", new Link('admin'), "Board Admin");
}
}
}
private function delete_by_query() {
global $page, $user;
$query = $_POST['query'];
assert(strlen($query) > 1);
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can(Permissions::MANAGE_ADMINTOOLS)) {
$event->add_link("Board Admin", make_link("admin"));
}
}
log_warning("admin", "Mass deleting: $query");
foreach(Image::find_images(0, 1000000, Tag::explode($query)) as $image) {
send_event(new ImageDeletionEvent($image));
}
public function onAdminAction(AdminActionEvent $event)
{
$action = $event->action;
if (method_exists($this, $action)) {
$event->redirect = $this->$action();
}
}
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list"));
return false;
}
private function set_tag_case()
{
global $database;
$database->execute(
"UPDATE tags SET tag=:tag1 WHERE LOWER(tag) = LOWER(:tag2)",
["tag1" => $_POST['tag'], "tag2" => $_POST['tag']]
);
log_info("admin", "Fixed the case of {$_POST['tag']}", "Fixed case");
return true;
}
private function lowercase_all_tags() {
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
return true;
}
private function lowercase_all_tags()
{
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
log_warning("admin", "Set all tags to lowercase", "Set all tags to lowercase");
return true;
}
private function recount_tag_use() {
global $database;
$database->Execute("
private function recount_tag_use()
{
global $database;
$database->Execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)
");
$database->Execute("DELETE FROM tags WHERE count=0");
return true;
}
private function database_dump() {
global $page;
$matches = array();
preg_match("#^(?P<proto>\w+)\:(?:user=(?P<user>\w+)(?:;|$)|password=(?P<password>\w+)(?:;|$)|host=(?P<host>[\w\.\-]+)(?:;|$)|dbname=(?P<dbname>[\w_]+)(?:;|$))+#", DATABASE_DSN, $matches);
$software = $matches['proto'];
$username = $matches['user'];
$password = $matches['password'];
$hostname = $matches['host'];
$database = $matches['dbname'];
switch($software) {
case 'mysql':
$cmd = "mysqldump -h$hostname -u$username -p$password $database";
break;
case 'pgsql':
putenv("PGPASSWORD=$password");
$cmd = "pg_dump -h $hostname -U $username $database";
break;
case 'sqlite':
$cmd = "sqlite3 $database .dump";
break;
}
$page->set_mode("data");
$page->set_type("application/x-unknown");
$page->set_filename('shimmie-'.date('Ymd').'.sql');
$page->set_data(shell_exec($cmd));
return false;
}
private function download_all_images() {
global $database, $page;
$zip = new ZipArchive;
$images = $database->get_all("SELECT * FROM images");
$filename = data_path('imgdump-'.date('Ymd').'.zip');
if($zip->open($filename, 1 ? ZIPARCHIVE::OVERWRITE:ZIPARCHIVE::CREATE) === TRUE){
foreach($images as $img){
$hash = $img["hash"];
preg_match("^[A-Za-z0-9]{2}^", $hash, $matches);
$img_loc = "images/".$matches[0]."/".$hash;
if(file_exists($img_loc)){
$zip->addFile($img_loc, $hash.".".$img["ext"]);
}
}
$zip->close();
}
$page->set_mode("redirect");
$page->set_redirect(make_link($filename)); //Fairly sure there is better way to do this..
//TODO: Delete file after downloaded?
return false; // we do want a redirect, but a manual one
}
$database->Execute("DELETE FROM tags WHERE count=0");
log_warning("admin", "Re-counted tags", "Re-counted tags");
return true;
}
}
?>

View File

@ -1,83 +1,89 @@
<?php
class AdminPageTest extends ShimmieWebTestCase {
function testAuth() {
$this->get_page('admin');
$this->assert_response(403);
$this->assert_title("Permission Denied");
<?php declare(strict_types=1);
class AdminPageTest extends ShimmiePHPUnitTestCase
{
public function testAuth()
{
send_event(new UserLoginEvent(User::by_name(self::$anon_name)));
$page = $this->get_page('admin');
$this->assertEquals(403, $page->code);
$this->assertEquals("Permission Denied", $page->title);
$this->log_in_as_user();
$this->get_page('admin');
$this->assert_response(403);
$this->assert_title("Permission Denied");
$this->log_out();
}
send_event(new UserLoginEvent(User::by_name(self::$user_name)));
$page = $this->get_page('admin');
$this->assertEquals(403, $page->code);
$this->assertEquals("Permission Denied", $page->title);
function testLowercase() {
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$page = $this->get_page('admin');
$this->assertEquals(200, $page->code);
$this->assertEquals("Admin Tools", $page->title);
}
$this->log_in_as_admin();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "TeStCase$ts");
public function testLowercaseAndSetCase()
{
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "TeStCase$ts");
$this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: TeStCase$ts");
// Validate problem
$page = $this->get_page("post/view/$image_id_1");
$this->assertEquals("Image $image_id_1: TeStCase$ts", $page->title);
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("All tags to lowercase");
// Fix
send_event(new AdminActionEvent('lowercase_all_tags'));
$this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: testcase$ts");
// Validate fix
$this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: testcase$ts");
$this->delete_image($image_id_1);
$this->log_out();
}
// Change
$_POST["tag"] = "TestCase$ts";
send_event(new AdminActionEvent('set_tag_case'));
# FIXME: make sure the admin tools actually work
function testRecount() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Recount tag use");
$this->log_out();
}
// Validate change
$this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: TestCase$ts");
}
function testPurge() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Purge unused tags");
$this->log_out();
}
# FIXME: make sure the admin tools actually work
public function testRecount()
{
global $database;
function testDump() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->click("Download database contents");
$this->assert_response(200);
$this->log_out();
}
// Create a problem
$ts = time(); // we need a tag that hasn't been used before
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
$database->execute(
"INSERT INTO tags(tag, count) VALUES(:tag, :count)",
["tag"=>"tes$ts", "count"=>42]
);
function testDBQ() {
$this->log_in_as_user();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "test2");
$image_id_3 = $this->post_image("ext/simpletest/data/favicon.png", "test");
// Fix
send_event(new AdminActionEvent('recount_tag_use'));
$this->get_page("post/list/test/1");
$this->click("Delete All These Images");
// Validate fix
$this->assertEquals(
0,
$database->get_one(
"SELECT count FROM tags WHERE tag = :tag",
["tag"=>"tes$ts"]
)
);
}
$this->get_page("post/view/$image_id_1");
$this->assert_response(404);
$this->get_page("post/view/$image_id_2");
$this->assert_response(200);
$this->get_page("post/view/$image_id_3");
$this->assert_response(404);
public function testCommands()
{
send_event(new UserLoginEvent(User::by_name(self::$admin_name)));
ob_start();
send_event(new CommandEvent(["index.php", "help"]));
send_event(new CommandEvent(["index.php", "get-page", "post/list"]));
send_event(new CommandEvent(["index.php", "post-page", "post/list", "foo=bar"]));
send_event(new CommandEvent(["index.php", "get-token"]));
send_event(new CommandEvent(["index.php", "regen-thumb", "42"]));
ob_end_clean();
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$this->delete_image($image_id_3);
$this->log_out();
}
// don't crash
$this->assertTrue(true);
}
}
?>

View File

@ -1,61 +1,54 @@
<?php
<?php declare(strict_types=1);
use function MicroHTML\INPUT;
class AdminPageTheme extends Themelet {
/*
* Show the basics of a page, for other extensions to add to
*/
public function display_page() {
global $page;
class AdminPageTheme extends Themelet
{
/*
* Show the basics of a page, for other extensions to add to
*/
public function display_page()
{
global $page;
$page->set_title("Admin Tools");
$page->set_heading("Admin Tools");
$page->add_block(new NavBlock());
}
$page->set_title("Admin Tools");
$page->set_heading("Admin Tools");
$page->add_block(new NavBlock());
}
protected function button(/*string*/ $name, /*string*/ $action, /*boolean*/ $protected=false) {
$c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, false, false, "admin$c_protected");
if($protected) {
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
$html .= "<input type='submit' id='$action' value='$name' disabled='true'>";
}
else {
$html .= "<input type='submit' id='$action' value='$name'>";
}
$html .= "</form>\n";
return $html;
}
protected function button(string $name, string $action, bool $protected=false): string
{
$c_protected = $protected ? " protected" : "";
$html = make_form(make_link("admin/$action"), "POST", false, "admin$c_protected");
if ($protected) {
$html .= "<input type='submit' id='$action' value='$name' disabled='disabled'>";
$html .= "<input type='checkbox' onclick='$(\"#$action\").attr(\"disabled\", !$(this).is(\":checked\"))'>";
} else {
$html .= "<input type='submit' id='$action' value='$name'>";
}
$html .= "</form>\n";
return $html;
}
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
* 'recount tag use'
* 'purge unused tags'
*/
public function display_form() {
global $page, $database;
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
* 'recount tag use'
* etc
*/
public function display_form()
{
global $page;
$html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_user", false);
$html .= $this->button("Download all images", "image_dump", false);
$html .= $this->button("Download database contents", "database_dump", false);
$html = "";
$html .= $this->button("All tags to lowercase", "lowercase_all_tags", true);
$html .= $this->button("Recount tag use", "recount_tag_use", false);
$page->add_block(new Block("Misc Admin Tools", $html));
$page->add_block(new Block("Misc Admin Tools", $html));
}
public function display_dbq($terms) {
global $page;
$h_terms = html_escape($terms);
$html = make_form(make_link("admin/delete_by_query"), "POST") . "
<input type='button' class='shm-unlocker' data-unlock-sel='#dbqsubmit' value='Unlock'>
<input type='hidden' name='query' value='$h_terms'>
<input type='submit' id='dbqsubmit' disabled='true' value='Delete All These Images'>
</form>
";
$page->add_block(new Block("List Controls", $html, "left"));
}
$html = (string)SHM_SIMPLE_FORM(
"admin/set_tag_case",
INPUT(["type"=>'text', "name"=>'tag', "placeholder"=>'Enter tag with correct case', "class"=>'autocomplete_tags', "autocomplete"=>'off']),
SHM_SUBMIT('Set Tag Case'),
);
$page->add_block(new Block("Set Tag Case", $html));
}
}
?>

15
ext/alias_editor/info.php Normal file
View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
class AliasEditorInfo extends ExtensionInfo
{
public const KEY = "alias_editor";
public $key = self::KEY;
public $name = "Alias Editor";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "Edit the alias list";
public $documentation = 'The list is visible at <a href="$site/alias/list">/alias/list</a>; only site admins can edit it, other people can view and download it';
public $core = true;
}

View File

@ -1,147 +1,209 @@
<?php
/**
* Name: Alias Editor
* Author: Shish <webmaster@shishnet.org>
* Link: http://code.shishnet.org/shimmie2/
* License: GPLv2
* Description: Edit the alias list
* Documentation:
* The list is visible at <a href="$site/alias/list">/alias/list</a>; only
* site admins can edit it, other people can view and download it
*/
<?php declare(strict_types=1);
class AddAliasEvent extends Event {
var $oldtag;
var $newtag;
use MicroCRUD\ActionColumn;
use MicroCRUD\TextColumn;
use MicroCRUD\Table;
public function AddAliasEvent($oldtag, $newtag) {
$this->oldtag = $oldtag;
$this->newtag = $newtag;
}
class AliasTable extends Table
{
public function __construct(\FFSPHP\PDO $db)
{
parent::__construct($db);
$this->table = "aliases";
$this->base_query = "SELECT * FROM aliases";
$this->primary_key = "oldtag";
$this->size = 100;
$this->limit = 1000000;
$this->set_columns([
new TextColumn("oldtag", "Old Tag"),
new TextColumn("newtag", "New Tag"),
new ActionColumn("oldtag"),
]);
$this->order_by = ["oldtag"];
$this->table_attrs = ["class" => "zebra"];
}
}
class AddAliasException extends SCoreException {}
class AddAliasEvent extends Event
{
/** @var string */
public $oldtag;
/** @var string */
public $newtag;
class AliasEditor extends Extension {
public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page, $user;
if($event->page_matches("alias")) {
if($event->get_arg(0) == "add") {
if($user->can("manage_alias_list")) {
if(isset($_POST['oldtag']) && isset($_POST['newtag'])) {
try {
$aae = new AddAliasEvent($_POST['oldtag'], $_POST['newtag']);
send_event($aae);
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
catch(AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
}
else if($event->get_arg(0) == "remove") {
if($user->can("manage_alias_list")) {
if(isset($_POST['oldtag'])) {
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", array("oldtag" => $_POST['oldtag']));
log_info("alias_editor", "Deleted alias for ".$_POST['oldtag']);
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
}
}
else if($event->get_arg(0) == "list") {
$page_number = $event->get_arg(1);
if(is_null($page_number) || !is_numeric($page_number)) {
$page_number = 0;
}
else if ($page_number <= 0) {
$page_number = 0;
}
else {
$page_number--;
}
$alias_per_page = $config->get_int('alias_items_per_page', 30);
$query = "SELECT oldtag, newtag FROM aliases ORDER BY newtag ASC LIMIT :limit OFFSET :offset";
$alias = $database->get_pairs($query,
array("limit"=>$alias_per_page, "offset"=>$page_number * $alias_per_page)
);
$total_pages = ceil($database->get_one("SELECT COUNT(*) FROM aliases") / $alias_per_page);
$this->theme->display_aliases($alias, $page_number + 1, $total_pages);
}
else if($event->get_arg(0) == "export") {
$page->set_mode("data");
$page->set_type("text/plain");
$page->set_data($this->get_alias_csv($database));
}
else if($event->get_arg(0) == "import") {
if($user->can("manage_alias_list")) {
if(count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
$this->add_alias_csv($database, $contents);
$page->set_mode("redirect");
$page->set_redirect(make_link("alias/list"));
}
else {
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
}
}
else {
$this->theme->display_error(401, "Admins Only", "Only admins can edit the alias list");
}
}
}
}
public function onAddAlias(AddAliasEvent $event) {
global $database;
$pair = array("oldtag" => $event->oldtag, "newtag" => $event->newtag);
if($database->get_row("SELECT * FROM aliases WHERE oldtag=:oldtag AND lower(newtag)=lower(:newtag)", $pair)) {
throw new AddAliasException("That alias already exists");
}
else {
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", $pair);
log_info("alias_editor", "Added alias for {$event->oldtag} -> {$event->newtag}");
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->can("manage_alias_list")) {
$event->add_link("Alias Editor", make_link("alias/list"));
}
}
private function get_alias_csv(Database $database) {
$csv = "";
$aliases = $database->get_pairs("SELECT oldtag, newtag FROM aliases ORDER BY newtag");
foreach($aliases as $old => $new) {
$csv .= "$old,$new\n";
}
return $csv;
}
private function add_alias_csv(Database $database, /*string*/ $csv) {
$csv = str_replace("\r", "\n", $csv);
foreach(explode("\n", $csv) as $line) {
$parts = explode(",", $line);
if(count($parts) == 2) {
$database->execute("INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)", array("oldtag" => $parts[0], "newtag" => $parts[1]));
}
}
}
// add alias *after* mass tag editing, else the MTE will
// search for the images and be redirected to the alias,
// missing out the images tagged with the oldtag
public function get_priority() {return 60;}
public function __construct(string $oldtag, string $newtag)
{
parent::__construct();
$this->oldtag = trim($oldtag);
$this->newtag = trim($newtag);
}
}
class DeleteAliasEvent extends Event
{
public $oldtag;
public function __construct(string $oldtag)
{
parent::__construct();
$this->oldtag = $oldtag;
}
}
class AddAliasException extends SCoreException
{
}
class AliasEditor extends Extension
{
/** @var AliasEditorTheme */
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{
global $config, $database, $page, $user;
if ($event->page_matches("alias")) {
if ($event->get_arg(0) == "add") {
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
$user->ensure_authed();
$input = validate_input(["c_oldtag"=>"string", "c_newtag"=>"string"]);
try {
send_event(new AddAliasEvent($input['c_oldtag'], $input['c_newtag']));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("alias/list"));
} catch (AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
} elseif ($event->get_arg(0) == "remove") {
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
$user->ensure_authed();
$input = validate_input(["d_oldtag"=>"string"]);
send_event(new DeleteAliasEvent($input['d_oldtag']));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("alias/list"));
}
} elseif ($event->get_arg(0) == "list") {
$t = new AliasTable($database->raw_db());
$t->token = $user->get_auth_token();
$t->inputs = $_GET;
$t->size = $config->get_int('alias_items_per_page', 30);
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
$t->create_url = make_link("alias/add");
$t->delete_url = make_link("alias/remove");
}
$this->theme->display_aliases($t->table($t->query()), $t->paginator());
} elseif ($event->get_arg(0) == "export") {
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_CSV);
$page->set_filename("aliases.csv");
$page->set_data($this->get_alias_csv($database));
} elseif ($event->get_arg(0) == "import") {
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
if (count($_FILES) > 0) {
$tmp = $_FILES['alias_file']['tmp_name'];
$contents = file_get_contents($tmp);
$this->add_alias_csv($database, $contents);
log_info("alias_editor", "Imported aliases from file", "Imported aliases"); # FIXME: how many?
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("alias/list"));
} else {
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
}
} else {
$this->theme->display_error(401, "Admins Only", "Only admins can edit the alias list");
}
}
}
}
public function onAddAlias(AddAliasEvent $event)
{
global $database;
$row = $database->get_row(
"SELECT * FROM aliases WHERE lower(oldtag)=lower(:oldtag)",
["oldtag"=>$event->oldtag]
);
if ($row) {
throw new AddAliasException("{$row['oldtag']} is already an alias for {$row['newtag']}");
}
$row = $database->get_row(
"SELECT * FROM aliases WHERE lower(oldtag)=lower(:newtag)",
["newtag" => $event->newtag]
);
if ($row) {
throw new AddAliasException("{$row['oldtag']} is itself an alias for {$row['newtag']}");
}
$database->execute(
"INSERT INTO aliases(oldtag, newtag) VALUES(:oldtag, :newtag)",
["oldtag" => $event->oldtag, "newtag" => $event->newtag]
);
log_info("alias_editor", "Added alias for {$event->oldtag} -> {$event->newtag}", "Added alias");
}
public function onDeleteAlias(DeleteAliasEvent $event)
{
global $database;
$database->execute("DELETE FROM aliases WHERE oldtag=:oldtag", ["oldtag" => $event->oldtag]);
log_info("alias_editor", "Deleted alias for {$event->oldtag}", "Deleted alias");
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
if ($event->parent=="tags") {
$event->add_nav_link("aliases", new Link('alias/list'), "Aliases", NavLink::is_active(["alias"]));
}
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can(Permissions::MANAGE_ALIAS_LIST)) {
$event->add_link("Alias Editor", make_link("alias/list"));
}
}
private function get_alias_csv(Database $database): string
{
$csv = "";
$aliases = $database->get_pairs("SELECT oldtag, newtag FROM aliases ORDER BY newtag");
foreach ($aliases as $old => $new) {
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;
}
private function add_alias_csv(Database $database, string $csv): int
{
$csv = str_replace("\r", "\n", $csv);
$i = 0;
foreach (explode("\n", $csv) as $line) {
$parts = str_getcsv($line);
if (count($parts) == 2) {
try {
send_event(new AddAliasEvent($parts[0], $parts[1]));
$i++;
} catch (AddAliasException $ex) {
$this->theme->display_error(500, "Error adding alias", $ex->getMessage());
}
}
}
return $i;
}
/**
* Get the priority for this extension.
*
* Add alias *after* mass tag editing, else the MTE will
* search for the images and be redirected to the alias,
* missing out the images tagged with the old tag.
*/
public function get_priority(): int
{
return 60;
}
}
?>

View File

@ -1,74 +1,85 @@
<?php
class AliasEditorTest extends ShimmieWebTestCase {
function testAliasEditor() {
$this->get_page('alias/list');
$this->assert_title("Alias List");
<?php declare(strict_types=1);
class AliasEditorTest extends ShimmiePHPUnitTestCase
{
public function testAliasList()
{
$this->get_page('alias/list');
$this->assert_response(200);
$this->assert_title("Alias List");
}
$this->log_in_as_admin();
public function testAliasListReadOnly()
{
$this->log_in_as_user();
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
# test one to one
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "test1");
$this->set_field('newtag', "test2");
$this->click("Add");
$this->assert_text("test1");
$this->log_out();
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
$this->get_page("alias/export/aliases.csv");
$this->assert_text("test1,test2");
public function testAliasOneToOne()
{
$this->log_in_as_admin();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
$this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
$this->assert_title("Image $image_id: test2");
$this->delete_image($image_id);
$this->get_page("alias/export/aliases.csv");
$this->assert_no_text("test1");
$this->get_page('alias/list');
$this->click("Remove");
$this->assert_title("Alias List");
$this->assert_no_text("test1");
send_event(new AddAliasEvent("test1", "test2"));
$this->get_page('alias/list');
$this->assert_text("test1");
$this->get_page("alias/export/aliases.csv");
$this->assert_text('"test1","test2"');
# test one to many
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->set_field('oldtag', "onetag");
$this->set_field('newtag', "multi tag");
$this->click("Add");
$this->assert_text("multi");
$this->assert_text("tag");
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test2");
$this->get_page("post/list/test1/1"); # searching for an alias should find the master tag
$this->assert_response(302);
$this->get_page("post/list/test2/1"); # check that searching for the main tag still works
$this->assert_response(302);
$this->delete_image($image_id);
$this->get_page("alias/export/aliases.csv");
$this->assert_text("onetag,multi tag");
send_event(new DeleteAliasEvent("test1"));
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
}
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("ext/simpletest/data/bedroom_workshop.jpg", "onetag");
// FIXME: known broken
//$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
//$this->assert_title("onetag");
//$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi/1");
$this->assert_title("multi");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi%20tag/1");
$this->assert_title("multi tag");
$this->assert_no_text("No Images Found");
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
public function testAliasOneToMany()
{
$this->log_in_as_admin();
$this->get_page('alias/list');
$this->click("Remove");
$this->assert_title("Alias List");
$this->assert_no_text("test1");
$this->get_page("alias/export/aliases.csv");
$this->assert_no_text("multi");
$this->log_out();
send_event(new AddAliasEvent("onetag", "multi tag"));
$this->get_page('alias/list');
$this->assert_text("multi");
$this->assert_text("tag");
$this->get_page("alias/export/aliases.csv");
$this->assert_text('"onetag","multi tag"');
$image_id_1 = $this->post_image("tests/pbx_screenshot.jpg", "onetag");
$image_id_2 = $this->post_image("tests/bedroom_workshop.jpg", "onetag");
$this->get_page("post/list/onetag/1"); # searching for an aliased tag should find its aliases
$this->assert_title("multi tag");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi/1");
$this->assert_title("multi");
$this->assert_no_text("No Images Found");
$this->get_page("post/list/multi tag/1");
$this->assert_title("multi tag");
$this->assert_no_text("No Images Found");
$this->delete_image($image_id_1);
$this->delete_image($image_id_2);
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("Add");
}
send_event(new DeleteAliasEvent("onetag"));
$this->get_page('alias/list');
$this->assert_title("Alias List");
$this->assert_no_text("test1");
}
}
?>

View File

@ -1,77 +1,36 @@
<?php
<?php declare(strict_types=1);
class AliasEditorTheme extends Themelet {
/*
* Show a page of aliases:
*
* $aliases = an array of ($old_tag => $new_tag)
* $can_manage = whether things like "add new alias" should be shown
*/
public function display_aliases($aliases, $pageNumber, $totalPages) {
global $page, $user;
class AliasEditorTheme extends Themelet
{
/**
* Show a page of aliases.
*
* Note: $can_manage = whether things like "add new alias" should be shown
*/
public function display_aliases($table, $paginator): void
{
global $page, $user;
$can_manage = $user->can("manage_alias_list");
if($can_manage) {
$h_action = "<th width='10%'>Action</th>";
$h_add = "
<tr>
".make_form(make_link("alias/add"))."
<td><input type='text' name='oldtag'></td>
<td><input type='text' name='newtag'></td>
<td><input type='submit' value='Add'></td>
</form>
</tr>
";
}
else {
$h_action = "";
$h_add = "";
}
$h_aliases = "";
$n = 0;
foreach($aliases as $old => $new) {
$h_old = html_escape($old);
$h_new = "<a href='".make_link("post/list/".url_escape($new)."/1")."'>".html_escape($new)."</a>";
$h_aliases .= "<tr><td>$h_old</td><td>$h_new</td>";
if($can_manage) {
$h_aliases .= "
<td>
".make_form(make_link("alias/remove"))."
<input type='hidden' name='oldtag' value='$h_old'>
<input type='submit' value='Remove'>
</form>
</td>
";
}
$h_aliases .= "</tr>";
}
$html = "
<table id='aliases' class='sortable zebra'>
<thead><tr><th>From</th><th>To</th>$h_action</tr></thead>
<tbody>$h_aliases</tbody>
<tfoot>$h_add</tfoot>
</table>
<p><a href='".make_link("alias/export/aliases.csv")."'>Download as CSV</a></p>
$can_manage = $user->can(Permissions::MANAGE_ALIAS_LIST);
$html = "
$table
$paginator
<p><a href='".make_link("alias/export/aliases.csv")."' download='aliases.csv'>Download as CSV</a></p>
";
$bulk_html = "
".make_form(make_link("alias/import"), $multipart=True)."
$bulk_html = "
".make_form(make_link("alias/import"), 'post', true)."
<input type='file' name='alias_file'>
<input type='submit' value='Upload List'>
</form>
";
$page->set_title("Alias List");
$page->set_heading("Alias List");
$page->add_block(new NavBlock());
$page->add_block(new Block("Aliases", $html));
if($can_manage) {
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
}
$this->display_paginator($page, "alias/list", null, $pageNumber, $totalPages);
}
$page->set_title("Alias List");
$page->set_heading("Alias List");
$page->add_block(new NavBlock());
$page->add_block(new Block("Aliases", $html));
if ($can_manage) {
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
}
}
}
?>

View File

@ -1,75 +0,0 @@
<?php
/*
* Name: Amazon S3 Mirror
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Copy uploaded files to S3
* Documentation:
*/
require_once "lib/S3.php";
class UploadS3 extends Extension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_string("amazon_s3_access", "");
$config->set_default_string("amazon_s3_secret", "");
$config->set_default_string("amazon_s3_bucket", "");
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Amazon S3");
$sb->add_text_option("amazon_s3_access", "Access key: ");
$sb->add_text_option("amazon_s3_secret", "<br>Secret key: ");
$sb->add_text_option("amazon_s3_bucket", "<br>Bucket: ");
$event->panel->add_block($sb);
}
public function onImageAddition(ImageAdditionEvent $event) {
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if(!empty($bucket)) {
log_debug("amazon_s3", "Mirroring Image #".$event->image->id." to S3 #$bucket");
$s3 = new S3($access, $secret);
$s3->putBucket($bucket, S3::ACL_PUBLIC_READ);
$s3->putObjectFile(
warehouse_path("thumbs", $event->image->hash),
$bucket,
'thumbs/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
array(),
array(
"Content-Type" => "image/jpeg",
"Content-Disposition" => "inline; filename=image-" . $event->image->id . ".jpg",
)
);
$s3->putObjectFile(
warehouse_path("images", $event->image->hash),
$bucket,
'images/'.$event->image->hash,
S3::ACL_PUBLIC_READ,
array(),
array(
"Content-Type" => $event->image->get_mime_type(),
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->ext,
)
);
}
}
public function onImageDeletion(ImageDeletionEvent $event) {
global $config;
$access = $config->get_string("amazon_s3_access");
$secret = $config->get_string("amazon_s3_secret");
$bucket = $config->get_string("amazon_s3_bucket");
if(!empty($bucket)) {
log_debug("amazon_s3", "Deleting Image #".$event->image->id." from S3");
$s3 = new S3($access, $secret);
$s3->deleteObject($bucket, "images/"+$event->image->hash);
$s3->deleteObject($bucket, "thumbs/"+$event->image->hash);
}
}
}
?>

12
ext/approval/info.php Normal file
View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
class ApprovalInfo extends ExtensionInfo
{
public const KEY = "approval";
public $key = self::KEY;
public $name = "Approval";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Adds an approval step to the upload/import process.";
}

255
ext/approval/main.php Normal file
View File

@ -0,0 +1,255 @@
<?php declare(strict_types=1);
abstract class ApprovalConfig
{
const VERSION = "ext_approval_version";
const IMAGES = "approve_images";
const COMMENTS = "approve_comments";
}
class Approval extends Extension
{
/** @var ApprovalTheme */
protected $theme;
public function onInitExt(InitExtEvent $event)
{
global $config;
$config->set_default_bool(ApprovalConfig::IMAGES, false);
$config->set_default_bool(ApprovalConfig::COMMENTS, false);
Image::$bool_props[] = "approved";
}
public function onPageRequest(PageRequestEvent $event)
{
global $page, $user;
if ($event->page_matches("approve_image") && $user->can(Permissions::APPROVE_IMAGE)) {
// Try to get the image ID
$image_id = int_escape($event->get_arg(0));
if (empty($image_id)) {
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
}
if (empty($image_id)) {
throw new SCoreException("Can not approve image: No valid Image ID given.");
}
self::approve_image($image_id);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/" . $image_id));
}
if ($event->page_matches("disapprove_image") && $user->can(Permissions::APPROVE_IMAGE)) {
// Try to get the image ID
$image_id = int_escape($event->get_arg(0));
if (empty($image_id)) {
$image_id = isset($_POST['image_id']) ? $_POST['image_id'] : null;
}
if (empty($image_id)) {
throw new SCoreException("Can not disapprove image: No valid Image ID given.");
}
self::disapprove_image($image_id);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/view/".$image_id));
}
}
public function onSetupBuilding(SetupBuildingEvent $event)
{
$this->theme->display_admin_block($event);
}
public function onAdminBuilding(AdminBuildingEvent $event)
{
$this->theme->display_admin_form();
}
public function onAdminAction(AdminActionEvent $event)
{
global $database, $user;
$action = $event->action;
$event->redirect = true;
if ($action==="approval") {
$approval_action = $_POST["approval_action"];
switch ($approval_action) {
case "approve_all":
$database->set_timeout(300000); // These updates can take a little bit
$database->execute(
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE approved = :false",
["approved_by_id"=>$user->id, "true"=>true, "false"=>false]
);
break;
case "disapprove_all":
$database->set_timeout(300000); // These updates can take a little bit
$database->execute(
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE approved = :true",
["true"=>true, "false"=>false]
);
break;
default:
break;
}
}
}
public function onDisplayingImage(DisplayingImageEvent $event)
{
global $user, $page, $config;
if ($config->get_bool(ApprovalConfig::IMAGES) && $event->image->approved===false && !$user->can(Permissions::APPROVE_IMAGE)) {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("post/list"));
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
global $user;
if ($event->parent=="posts") {
if ($user->can(Permissions::APPROVE_IMAGE)) {
$event->add_nav_link("posts_unapproved", new Link('/post/list/approved%3Ano/1'), "Pending Approval", null, 60);
}
}
}
const SEARCH_REGEXP = "/^approved:(yes|no)/";
public function onSearchTermParse(SearchTermParseEvent $event)
{
global $user, $database, $config;
if ($config->get_bool(ApprovalConfig::IMAGES)) {
$matches = [];
if (is_null($event->term) && $this->no_approval_query($event->context)) {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
}
if (is_null($event->term)) {
return;
}
if (preg_match(self::SEARCH_REGEXP, strtolower($event->term), $matches)) {
if ($user->can(Permissions::APPROVE_IMAGE) && $matches[1] == "no") {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_N ")));
} else {
$event->add_querylet(new Querylet($database->scoreql_to_sql("approved = SCORE_BOOL_Y ")));
}
}
}
}
public function onHelpPageBuilding(HelpPageBuildingEvent $event)
{
global $user, $config;
if ($event->key===HelpPages::SEARCH) {
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
$block = new Block();
$block->header = "Approval";
$block->body = $this->theme->get_help_html();
$event->add_block($block);
}
}
}
private function no_approval_query(array $context): bool
{
foreach ($context as $term) {
if (preg_match(self::SEARCH_REGEXP, $term)) {
return false;
}
}
return true;
}
public static function approve_image($image_id)
{
global $database, $user;
$database->execute(
"UPDATE images SET approved = :true, approved_by_id = :approved_by_id WHERE id = :id AND approved = :false",
["approved_by_id"=>$user->id, "id"=>$image_id, "true"=>true, "false"=>false]
);
}
public static function disapprove_image($image_id)
{
global $database;
$database->execute(
"UPDATE images SET approved = :false, approved_by_id = NULL WHERE id = :id AND approved = :true",
["id"=>$image_id, "true"=>true, "false"=>false]
);
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event)
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE) && $config->get_bool(ApprovalConfig::IMAGES)) {
$event->add_part($this->theme->get_image_admin_html($event->image));
}
}
public function onBulkActionBlockBuilding(BulkActionBlockBuildingEvent $event)
{
global $user, $config;
if ($user->can(Permissions::APPROVE_IMAGE)&& $config->get_bool(ApprovalConfig::IMAGES)) {
if (in_array("approved:no", $event->search_terms)) {
$event->add_action("bulk_approve_image", "Approve", "a");
} else {
$event->add_action("bulk_disapprove_image", "Disapprove");
}
}
}
public function onBulkAction(BulkActionEvent $event)
{
global $page, $user;
switch ($event->action) {
case "bulk_approve_image":
if ($user->can(Permissions::APPROVE_IMAGE)) {
$total = 0;
foreach ($event->items as $image) {
self::approve_image($image->id);
$total++;
}
$page->flash("Approved $total items");
}
break;
case "bulk_disapprove_image":
if ($user->can(Permissions::APPROVE_IMAGE)) {
$total = 0;
foreach ($event->items as $image) {
self::disapprove_image($image->id);
$total++;
}
$page->flash("Disapproved $total items");
}
break;
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
global $database;
if ($this->get_version(ApprovalConfig::VERSION) < 1) {
$database->execute($database->scoreql_to_sql(
"ALTER TABLE images ADD COLUMN approved SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N"
));
$database->execute(
"ALTER TABLE images ADD COLUMN approved_by_id INTEGER NULL"
);
$database->execute("CREATE INDEX images_approved_idx ON images(approved)");
$this->set_version(ApprovalConfig::VERSION, 1);
}
}
}

61
ext/approval/theme.php Normal file
View File

@ -0,0 +1,61 @@
<?php declare(strict_types=1);
use function MicroHTML\BR;
use function MicroHTML\BUTTON;
use function MicroHTML\INPUT;
class ApprovalTheme extends Themelet
{
public function get_image_admin_html(Image $image)
{
if ($image->approved===true) {
$html = SHM_SIMPLE_FORM(
'disapprove_image/'.$image->id,
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
SHM_SUBMIT("Disapprove")
);
} else {
$html = SHM_SIMPLE_FORM(
'approve_image/'.$image->id,
INPUT(["type"=>'hidden', "name"=>'image_id', "value"=>$image->id]),
SHM_SUBMIT("Approve")
);
}
return (string)$html;
}
public function get_help_html()
{
return '<p>Search for images that are approved/not approved.</p>
<div class="command_example">
<pre>approved:yes</pre>
<p>Returns images that have been approved.</p>
</div>
<div class="command_example">
<pre>approved:no</pre>
<p>Returns images that have not been approved.</p>
</div>
';
}
public function display_admin_block(SetupBuildingEvent $event)
{
$sb = new SetupBlock("Approval");
$sb->add_bool_option(ApprovalConfig::IMAGES, "Images: ");
$event->panel->add_block($sb);
}
public function display_admin_form()
{
global $page;
$html = (string)SHM_SIMPLE_FORM(
"admin/approval",
BUTTON(["name"=>'approval_action', "value"=>'approve_all'], "Approve All Images"),
BR(),
BUTTON(["name"=>'approval_action', "value"=>'disapprove_all'], "Disapprove All Images"),
);
$page->add_block(new Block("Approval", $html));
}
}

14
ext/artists/info.php Normal file
View File

@ -0,0 +1,14 @@
<?php declare(strict_types=1);
class ArtistsInfo extends ExtensionInfo
{
public const KEY = "artists";
public $key = self::KEY;
public $name = "Artists System";
public $url = self::SHIMMIE_URL;
public $authors = ["Sein Kraft"=>"mail@seinkraft.info","Alpha"=>"alpha@furries.com.ar"];
public $license = self::LICENSE_GPLV2;
public $description = "Simple artists extension";
public $beta = true;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,15 @@
<?php
class ArtistTest extends ShimmieWebTestCase {
function testSearch() {
# FIXME: check that the results are there
$this->get_page("post/list/author=bob/1");
}
<?php declare(strict_types=1);
class ArtistsTest extends ShimmiePHPUnitTestCase
{
public function testSearch()
{
global $user;
$this->log_in_as_user();
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "pbx computer screenshot");
$image = Image::by_id($image_id);
send_event(new AuthorSetEvent($image, $user, "bob"));
$this->assert_search_results(["author=bob"], [$image_id]);
}
}
?>

View File

@ -1,9 +1,10 @@
<?php
class ArtistsTheme extends Themelet {
public function get_author_editor_html(/*string*/ $author) {
$h_author = html_escape($author);
return "
<?php declare(strict_types=1);
class ArtistsTheme extends Themelet
{
public function get_author_editor_html(string $author): string
{
$h_author = html_escape($author);
return "
<tr>
<th>Author</th>
<td>
@ -12,110 +13,104 @@ class ArtistsTheme extends Themelet {
</td>
</tr>
";
}
}
public function display_artists(){
global $page;
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
//$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
}
public function sidebar_options(/*string*/ $mode, $artistID=NULL, $is_admin=FALSE){
global $page;
if($mode == "neutral"){
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
public function sidebar_options(string $mode, ?int $artistID=null, $is_admin=false): void
{
global $page, $user;
$html = "";
if ($mode == "neutral") {
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='New Artist'/>
</form>";
}
if($mode == "editor"){
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
}
if ($mode == "editor") {
$html = "<form method='post' action='".make_link("artist/new_artist")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='New Artist'/>
<input type='submit' name='edit' value='New Artist'/>
</form>
<form method='post' action='".make_link("artist/edit_artist")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Edit Artist'/>
<input type='submit' name='edit' value='Edit Artist'/>
<input type='hidden' name='artist_id' value='".$artistID."'>
</form>";
if($is_admin){
$html .= "<form method='post' action='".make_link("artist/nuke_artist")."'>
if ($is_admin) {
$html .= "<form method='post' action='".make_link("artist/nuke_artist")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Delete Artist'/>
<input type='submit' name='edit' value='Delete Artist'/>
<input type='hidden' name='artist_id' value='".$artistID."'>
</form>";
}
$html .= "<form method='post' action='".make_link("artist/add_alias")."'>
}
$html .= "<form method='post' action='".make_link("artist/add_alias")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Add Alias'/>
<input type='submit' name='edit' value='Add Alias'/>
<input type='hidden' name='artist_id' value='".$artistID."'>
</form>
<form method='post' action='".make_link("artist/add_member")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Add Member'/>
<input type='submit' name='edit' value='Add Member'/>
<input type='hidden' name='artist_id' value='".$artistID."'>
</form>
<form method='post' action='".make_link("artist/add_url")."'>
".$user->get_auth_html()."
<input type='submit' name='edit' id='edit' value='Add Url'/>
<input type='submit' name='edit' value='Add Url'/>
<input type='hidden' name='artist_id' value='".$artistID."'>
</form>";
}
$page->add_block(new Block("Manage Artists", $html, "left", 10));
}
}
public function show_artist_editor($artist, $aliases, $members, $urls)
{
$artistName = $artist['name'];
$artistNotes = $artist['notes'];
$artistID = $artist['id'];
if ($html) {
$page->add_block(new Block("Manage Artists", $html, "left", 10));
}
}
// aliases
$aliasesString = "";
$aliasesIDsString = "";
foreach ($aliases as $alias)
{
$aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." ";
}
$aliasesString = rtrim($aliasesString);
$aliasesIDsString = rtrim($aliasesIDsString);
public function show_artist_editor($artist, $aliases, $members, $urls)
{
global $user;
// members
$membersString = "";
$membersIDsString = "";
foreach ($members as $member)
{
$membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." ";
}
$membersString = rtrim($membersString);
$membersIDsString = rtrim($membersIDsString);
$artistName = $artist['name'];
$artistNotes = $artist['notes'];
$artistID = $artist['id'];
// urls
$urlsString = "";
$urlsIDsString = "";
foreach ($urls as $url)
{
$urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." ";
}
$urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString);
// aliases
$aliasesString = "";
$aliasesIDsString = "";
foreach ($aliases as $alias) {
$aliasesString .= $alias["alias_name"]." ";
$aliasesIDsString .= $alias["alias_id"]." ";
}
$aliasesString = rtrim($aliasesString);
$aliasesIDsString = rtrim($aliasesIDsString);
$html =
'
// members
$membersString = "";
$membersIDsString = "";
foreach ($members as $member) {
$membersString .= $member["name"]." ";
$membersIDsString .= $member["id"]." ";
}
$membersString = rtrim($membersString);
$membersIDsString = rtrim($membersIDsString);
// urls
$urlsString = "";
$urlsIDsString = "";
foreach ($urls as $url) {
$urlsString .= $url["url"]."\n";
$urlsIDsString .= $url["id"]." ";
}
$urlsString = substr($urlsString, 0, strlen($urlsString) -1);
$urlsIDsString = rtrim($urlsIDsString);
$html = '
<form method="POST" action="'.make_link("artist/edited/".$artist['id']).'">
'.$user->get_auth_html().'
<table>
@ -131,112 +126,118 @@ class ArtistsTheme extends Themelet {
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
';
global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10));
}
public function new_artist_composer()
{
global $page;
global $page;
$page->add_block(new Block("Edit artist", $html, "main", 10));
}
public function new_artist_composer()
{
global $page, $user;
$html = "<form action=".make_link("artist/create")." method='POST'>
".$user->get_auth_html()."
<table>
<tr><td>Name:</td><td><input type='text' name='name' /></td></tr>
<tr><td>Aliases:</td><td><input type='text' name='aliases' /></td></tr>
<tr><td>Members:</td><td><input type='text' name='members' /></td></tr>
<tr><td>URLs:</td><td><textarea name='urls'></textarea></td></tr>
<tr><td>Notes:</td><td><textarea name='notes'></textarea></td></tr>
<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
";
$html = "<form action=".make_link("artist/create")." method='POST'>
".$user->get_auth_html()."
<table>
<tr><td>Name:</td><td><input type='text' name='name' /></td></tr>
<tr><td>Aliases:</td><td><input type='text' name='aliases' /></td></tr>
<tr><td>Members:</td><td><input type='text' name='members' /></td></tr>
<tr><td>URLs:</td><td><textarea name='urls'></textarea></td></tr>
<tr><td>Notes:</td><td><textarea name='notes'></textarea></td></tr>
<tr><td colspan='2'><input type='submit' value='Submit' /></td></tr>
</table>
";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
}
public function list_artists($artists, $pageNumber, $totalPages)
{
global $user, $page;
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
}
public function list_artists($artists, $pageNumber, $totalPages)
{
global $user, $page;
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Name</th>".
"<th>Type</th>".
"<th>Last updater</th>".
"<th>Posts</th>";
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Name</th>".
"<th>Type</th>".
"<th>Last updater</th>".
"<th>Posts</th>";
if(!$user->is_anonymous()) $html .= "<th colspan='2'>Action</th>"; // space for edit link
if (!$user->is_anonymous()) {
$html .= "<th colspan='2'>Action</th>";
} // space for edit link
$html .= "</tr></thead>";
$html .= "</tr></thead>";
$n = 0;
$deletionLinkActionArray =
array('artist' => 'artist/nuke/'
, 'alias' => 'artist/alias/delete/'
, 'member' => 'artist/member/delete/'
);
$deletionLinkActionArray = [
'artist' => 'artist/nuke/',
'alias' => 'artist/alias/delete/',
'member' => 'artist/member/delete/',
];
$editionLinkActionArray =
array('artist' => 'artist/edit/'
, 'alias' => 'artist/alias/edit/'
, 'member' => 'artist/member/edit/'
);
$editionLinkActionArray = [
'artist' => 'artist/edit/',
'alias' => 'artist/alias/edit/',
'member' => 'artist/member/edit/',
];
$typeTextArray =
array('artist' => 'Artist'
, 'alias' => 'Alias'
, 'member' => 'Member'
);
$typeTextArray = [
'artist' => 'Artist',
'alias' => 'Alias',
'member' => 'Member',
];
foreach ($artists as $artist) {
if ($artist['type'] != 'artist')
$artist['name'] = str_replace("_", " ", $artist['name']);
$elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>";
$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>";
$user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>";
$edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>";
$del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>";
$html .= "<tr>".
"<td class='left'>".$elementLink;
//if ($artist['type'] == 'member')
// $html .= " (member of ".$artist_link.")";
//if ($artist['type'] == 'alias')
// $html .= " (alias for ".$artist_link.")";
$html .= "</td>".
"<td>".$typeTextArray[$artist['type']]."</td>".
"<td>".$user_link."</td>".
"<td>".$artist['posts']."</td>";
if(!$user->is_anonymous()) $html .= "<td>".$edit_link."</td>";
if($user->is_admin()) $html .= "<td>".$del_link."</td>";
$html .= "</tr>";
foreach ($artists as $artist) {
if ($artist['type'] != 'artist') {
$artist['name'] = str_replace("_", " ", $artist['name']);
}
$html .= "</tbody></table>";
$elementLink = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['name'])."</a>";
//$artist_link = "<a href='".make_link("artist/view/".$artist['artist_id'])."'>".str_replace("_", " ", $artist['artist_name'])."</a>";
$user_link = "<a href='".make_link("user/".$artist['user_name'])."'>".$artist['user_name']."</a>";
$edit_link = "<a href='".make_link($editionLinkActionArray[$artist['type']].$artist['id'])."'>Edit</a>";
$del_link = "<a href='".make_link($deletionLinkActionArray[$artist['type']].$artist['id'])."'>Delete</a>";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
$html .= "<tr>".
"<td class='left'>".$elementLink;
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
}
//if ($artist['type'] == 'member')
// $html .= " (member of ".$artist_link.")";
public function show_new_alias_composer($artistID)
{
$html =
'<form method="POST" action='.make_link("artist/alias/add").'>
//if ($artist['type'] == 'alias')
// $html .= " (alias for ".$artist_link.")";
$html .= "</td>".
"<td>".$typeTextArray[$artist['type']]."</td>".
"<td>".$user_link."</td>".
"<td>".$artist['posts']."</td>";
if (!$user->is_anonymous()) {
$html .= "<td>".$edit_link."</td>";
}
if ($user->can(Permissions::ARTISTS_ADMIN)) {
$html .= "<td>".$del_link."</td>";
}
$html .= "</tr>";
}
$html .= "</tbody></table>";
$page->set_title("Artists");
$page->set_heading("Artists");
$page->add_block(new Block("Artists", $html, "main", 10));
$this->display_paginator($page, "artist/list", null, $pageNumber, $totalPages);
}
public function show_new_alias_composer($artistID)
{
global $user;
$html = '
<form method="POST" action='.make_link("artist/alias/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>Alias:</td><td><input type="text" name="aliases" />
@ -244,271 +245,314 @@ class ArtistsTheme extends Themelet {
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
';
global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20));
}
public function show_new_member_composer($artistID)
{
$html =
' <form method="POST" action='.make_link("artist/member/add").'>
global $page;
$page->add_block(new Block("Artist Aliases", $html, "main", 20));
}
public function show_new_member_composer($artistID)
{
global $user;
$html = '
<form method="POST" action='.make_link("artist/member/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>Members:</td><td><input type="text" name="members" />
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
<table>
<tr><td>Members:</td><td><input type="text" name="members" />
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
global $page;
$page->add_block(new Block("Artist members", $html, "main", 30));
}
global $page;
$page->add_block(new Block("Artist members", $html, "main", 30));
}
public function show_new_url_composer($artistID)
{
$html =
' <form method="POST" action='.make_link("artist/url/add").'>
public function show_new_url_composer($artistID)
{
global $user;
$html = '
<form method="POST" action='.make_link("artist/url/add").'>
'.$user->get_auth_html().'
<table>
<tr><td>URL:</td><td><textarea name="urls"></textarea>
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
<table>
<tr><td>URL:</td><td><textarea name="urls"></textarea>
<input type="hidden" name="artistID" value='.$artistID.' /></td></tr>
<tr><td colspan="2"><input type="submit" value="Submit" /></td></tr>
</table>
</form>
';
global $page;
$page->add_block(new Block("Artist URLs", $html, "main", 40));
}
global $page;
$page->add_block(new Block("Artist URLs", $html, "main", 40));
}
public function show_alias_editor($alias)
{
$html =
'
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().'
<label for="alias">Alias:</label>
<input type="text" name="alias" value="'.$alias['alias'].'" />
<input type="hidden" name="aliasID" value="'.$alias['id'].'" />
<input type="submit" value="Submit" />
</form>
';
public function show_alias_editor($alias)
{
global $user;
global $page;
$page->add_block(new Block("Edit Alias", $html, "main", 10));
}
$html = '
<form method="POST" action="'.make_link("artist/alias/edited/".$alias['id']).'">
'.$user->get_auth_html().'
<label for="alias">Alias:</label>
<input type="text" name="alias" id="alias" value="'.$alias['alias'].'" />
<input type="hidden" name="aliasID" value="'.$alias['id'].'" />
<input type="submit" value="Submit" />
</form>
';
public function show_url_editor($url)
{
$html =
'
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().'
<label for="url">URL:</label>
<input type="text" name="url" value="'.$url['url'].'" />
<input type="hidden" name="urlID" value="'.$url['id'].'" />
<input type="submit" value="Submit" />
</form>
';
global $page;
$page->add_block(new Block("Edit Alias", $html, "main", 10));
}
global $page;
$page->add_block(new Block("Edit URL", $html, "main", 10));
}
public function show_url_editor($url)
{
global $user;
public function show_member_editor($member)
{
$html =
'
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().'
<label for="member">Member name:</label>
<input type="text" name="name" value="'.$member['name'].'" />
<input type="hidden" name="memberID" value="'.$member['id'].'" />
<input type="submit" value="Submit" />
</form>
';
$html = '
<form method="POST" action="'.make_link("artist/url/edited/".$url['id']).'">
'.$user->get_auth_html().'
<label for="url">URL:</label>
<input type="text" name="url" id="url" value="'.$url['url'].'" />
<input type="hidden" name="urlID" value="'.$url['id'].'" />
<input type="submit" value="Submit" />
</form>
';
global $page;
$page->add_block(new Block("Edit Member", $html, "main", 10));
}
global $page;
$page->add_block(new Block("Edit URL", $html, "main", 10));
}
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin)
{
global $user, $event, $page;
public function show_member_editor($member)
{
global $user;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$html = '
<form method="POST" action="'.make_link("artist/member/edited/".$member['id']).'">
'.$user->get_auth_html().'
<label for="name">Member name:</label>
<input type="text" name="name" id="name" value="'.$member['name'].'" />
<input type="hidden" name="memberID" value="'.$member['id'].'" />
<input type="submit" value="Submit" />
</form>
';
$n = 0;
global $page;
$page->add_block(new Block("Edit Member", $html, "main", 10));
}
$html = "<table id='poolsList' class='zebra'>
<thead>
<tr>
<th></th>
<th></th>";
public function show_artist($artist, $aliases, $members, $urls, $images, $userIsLogged, $userIsAdmin)
{
global $page;
$artist_link = "<a href='".make_link("post/list/".$artist['name']."/1")."'>".str_replace("_", " ", $artist['name'])."</a>";
$html = "<table id='poolsList' class='zebra'>
<thead>
<tr>
<th></th>
<th></th>";
if ($userIsLogged)
$html .= "<th></th>";
if ($userIsLogged) {
$html .= "<th></th>";
}
if ($userIsAdmin) {
$html .= "<th></th>";
}
if ($userIsAdmin)
$html .= "<th></th>";
$html .= " <tr>
</thead>
$html .= " <tr>
</thead>
<tr>
<td class='left'>Name:</td>
<td class='left'>".$artist_link."</td>";
if ($userIsLogged) {
$html .= "<td></td>";
}
if ($userIsAdmin) {
$html .= "<td></td>";
}
$html .= "</tr>";
$html .= $this->render_aliases($aliases, $userIsLogged, $userIsAdmin);
$html .= $this->render_members($members, $userIsLogged, $userIsAdmin);
$html .= $this->render_urls($urls, $userIsLogged, $userIsAdmin);
$html .= "<tr>
<td class='left'>Notes:</td>
<td class='left'>".$artist["notes"]."</td>";
if ($userIsLogged) {
$html .= "<td></td>";
}
if ($userIsAdmin) {
$html .= "<td></td>";
}
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes?
//same question for deletion
$html .= "</tr>
</table>";
$page->set_title("Artist");
$page->set_heading("Artist");
$page->add_block(new Block("Artist", $html, "main", 10));
//we show the images for the artist
$artist_images = "";
foreach ($images as $image) {
$thumb_html = $this->build_thumb_html($image);
$artist_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$page->add_block(new Block("Artist Images", $artist_images, "main", 20));
}
private function render_aliases(array $aliases, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";
if (count($aliases) > 0) {
$aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore
$aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[0]['alias_id']) . "'>Edit</a>";
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[0]['alias_id']) . "'>Delete</a>";
$html .= "<tr>
<td class='left'>Aliases:</td>
<td class='left'>" . $aliasViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $aliasEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>";
}
<tr>
<td class='left'>Name:</td>
<td class='left'>".$artist_link."</td>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
$html .= "</tr>";
if (count($aliases) > 0)
{
$aliasViewLink = str_replace("_", " ", $aliases[0]['alias_name']); // no link anymore
$aliasEditLink = "<a href='".make_link("artist/alias/edit/".$aliases[0]['alias_id'])."'>Edit</a>";
$aliasDeleteLink = "<a href='".make_link("artist/alias/delete/".$aliases[0]['alias_id'])."'>Delete</a>";
$html .= "<tr>
<td class='left'>Aliases:</td>
<td class='left'>".$aliasViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$aliasEditLink."</td>";
if (count($aliases) > 1) {
for ($i = 1; $i < count($aliases); $i++) {
$aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore
$aliasEditLink = "<a href='" . make_link("artist/alias/edit/" . $aliases[$i]['alias_id']) . "'>Edit</a>";
$aliasDeleteLink = "<a href='" . make_link("artist/alias/delete/" . $aliases[$i]['alias_id']) . "'>Delete</a>";
if ($userIsAdmin)
$html .= "<td class='left'>".$aliasDeleteLink."</td>";
$html .= "</tr>";
if (count($aliases) > 1)
{
for ($i = 1; $i < count($aliases); $i++)
{
$aliasViewLink = str_replace("_", " ", $aliases[$i]['alias_name']); // no link anymore
$aliasEditLink = "<a href='".make_link("artist/alias/edit/".$aliases[$i]['alias_id'])."'>Edit</a>";
$aliasDeleteLink = "<a href='".make_link("artist/alias/delete/".$aliases[$i]['alias_id'])."'>Delete</a>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>".$aliasViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$aliasEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$aliasDeleteLink."</td>";
$html .= "</tr>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>" . $aliasViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $aliasEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $aliasDeleteLink . "</td>";
}
$html .= "</tr>";
}
}
}
return $html;
}
if (count($members) > 0)
{
$memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore
$memberEditLink = "<a href='".make_link("artist/member/edit/".$members[0]['id'])."'>Edit</a>";
$memberDeleteLink = "<a href='".make_link("artist/member/delete/".$members[0]['id'])."'>Delete</a>";
private function render_members(array $members, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";
if (count($members) > 0) {
$memberViewLink = str_replace("_", " ", $members[0]['name']); // no link anymore
$memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[0]['id']) . "'>Edit</a>";
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[0]['id']) . "'>Delete</a>";
$html .= "<tr>
<td class='left'>Members:</td>
<td class='left'>".$memberViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$memberEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$memberDeleteLink."</td>";
$html .= "</tr>";
if (count($members) > 1)
{
for ($i = 1; $i < count($members); $i++)
{
$memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore
$memberEditLink = "<a href='".make_link("artist/member/edit/".$members[$i]['id'])."'>Edit</a>";
$memberDeleteLink = "<a href='".make_link("artist/member/delete/".$members[$i]['id'])."'>Delete</a>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>".$memberViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$memberEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$memberDeleteLink."</td>";
$html .= "</tr>";
}
}
$html .= "<tr>
<td class='left'>Members:</td>
<td class='left'>" . $memberViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $memberEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $memberDeleteLink . "</td>";
}
if (count($urls) > 0)
{
$urlViewLink = "<a href='".str_replace("_", " ", $urls[0]['url'])."' target='_blank'>".str_replace("_", " ", $urls[0]['url'])."</a>";
$urlEditLink = "<a href='".make_link("artist/url/edit/".$urls[0]['id'])."'>Edit</a>";
$urlDeleteLink = "<a href='".make_link("artist/url/delete/".$urls[0]['id'])."'>Delete</a>";
$html .= "</tr>";
$html .= "<tr>
<td class='left'>URLs:</td>
<td class='left'>".$urlViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$urlEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$urlDeleteLink."</td>";
$html .= "</tr>";
if (count($members) > 1) {
for ($i = 1; $i < count($members); $i++) {
$memberViewLink = str_replace("_", " ", $members[$i]['name']); // no link anymore
$memberEditLink = "<a href='" . make_link("artist/member/edit/" . $members[$i]['id']) . "'>Edit</a>";
$memberDeleteLink = "<a href='" . make_link("artist/member/delete/" . $members[$i]['id']) . "'>Delete</a>";
if (count($urls) > 1)
{
for ($i = 1; $i < count($urls); $i++)
{
$urlViewLink = "<a href='".str_replace("_", " ", $urls[$i]['url'])."' target='_blank'>".str_replace("_", " ", $urls[$i]['url'])."</a>";
$urlEditLink = "<a href='".make_link("artist/url/edit/".$urls[$i]['id'])."'>Edit</a>";
$urlDeleteLink = "<a href='".make_link("artist/url/delete/".$urls[$i]['id'])."'>Delete</a>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>".$urlViewLink."</td>";
if ($userIsLogged)
$html .= "<td class='left'>".$urlEditLink."</td>";
if ($userIsAdmin)
$html .= "<td class='left'>".$urlDeleteLink."</td>";
$html .= "</tr>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>" . $memberViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $memberEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $memberDeleteLink . "</td>";
}
$html .= "</tr>";
}
}
}
return $html;
}
$html .=
"<tr>
<td class='left'>Notes:</td>
<td class='left'>".$artist["notes"]."</td>";
if ($userIsLogged) $html .= "<td></td>";
if ($userIsAdmin) $html .= "<td></td>";
//TODO how will notes be edited? On edit artist? (should there be an editartist?) or on a editnotes?
//same question for deletion
$html .= "</tr>
</table>";
private function render_urls(array $urls, bool $userIsLogged, bool $userIsAdmin): string
{
$html = "";
if (count($urls) > 0) {
$urlViewLink = "<a href='" . str_replace("_", " ", $urls[0]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[0]['url']) . "</a>";
$urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[0]['id']) . "'>Edit</a>";
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[0]['id']) . "'>Delete</a>";
$page->set_title("Artist");
$page->set_heading("Artist");
$page->add_block(new Block("Artist", $html, "main", 10));
//we show the images for the artist
$artist_images = "";
foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image);
$artist_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$page->add_block(new Block("Artist Images", $artist_images, "main", 20));
}
$html .= "<tr>
<td class='left'>URLs:</td>
<td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $urlEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $urlDeleteLink . "</td>";
}
$html .= "</tr>";
if (count($urls) > 1) {
for ($i = 1; $i < count($urls); $i++) {
$urlViewLink = "<a href='" . str_replace("_", " ", $urls[$i]['url']) . "' target='_blank'>" . str_replace("_", " ", $urls[$i]['url']) . "</a>";
$urlEditLink = "<a href='" . make_link("artist/url/edit/" . $urls[$i]['id']) . "'>Edit</a>";
$urlDeleteLink = "<a href='" . make_link("artist/url/delete/" . $urls[$i]['id']) . "'>Delete</a>";
$html .= "<tr>
<td class='left'>&nbsp;</td>
<td class='left'>" . $urlViewLink . "</td>";
if ($userIsLogged) {
$html .= "<td class='left'>" . $urlEditLink . "</td>";
}
if ($userIsAdmin) {
$html .= "<td class='left'>" . $urlDeleteLink . "</td>";
}
$html .= "</tr>";
}
return $html;
}
}
return $html;
}
public function get_help_html()
{
return '<p>Search for images with a particular artist.</p>
<div class="command_example">
<pre>artist=leonardo</pre>
<p>Returns images with the artist "leonardo".</p>
</div>
';
}
}
?>

View File

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
abstract class AutoTaggerConfig
{
public const VERSION = "ext_auto_tagger_ver";
public const ITEMS_PER_PAGE = "auto_tagger_items_per_page";
}

12
ext/auto_tagger/info.php Normal file
View File

@ -0,0 +1,12 @@
<?php declare(strict_types=1);
class AutoTaggerInfo extends ExtensionInfo
{
public const KEY = "auto_tagger";
public $key = self::KEY;
public $name = "Auto-Tagger";
public $authors = ["Matthew Barbour"=>"matthew@darkholme.net"];
public $license = self::LICENSE_WTFPL;
public $description = "Provides several automatic tagging functions";
}

335
ext/auto_tagger/main.php Normal file
View File

@ -0,0 +1,335 @@
<?php declare(strict_types=1);
require_once 'config.php';
use MicroCRUD\ActionColumn;
use MicroCRUD\TextColumn;
use MicroCRUD\Table;
class AutoTaggerTable extends Table
{
public function __construct(\FFSPHP\PDO $db)
{
parent::__construct($db);
$this->table = "auto_tagger";
$this->base_query = "SELECT * FROM auto_tag";
$this->primary_key = "tag";
$this->size = 100;
$this->limit = 1000000;
$this->set_columns([
new TextColumn("tag", "Tag"),
new TextColumn("additional_tags", "Additional Tags"),
new ActionColumn("tag"),
]);
$this->order_by = ["tag"];
$this->table_attrs = ["class" => "zebra"];
}
}
class AddAutoTagEvent extends Event
{
/** @var string */
public $tag;
/** @var string */
public $additional_tags;
public function __construct(string $tag, string $additional_tags)
{
parent::__construct();
$this->tag = trim($tag);
$this->additional_tags = trim($additional_tags);
}
}
class DeleteAutoTagEvent extends Event
{
public $tag;
public function __construct(string $tag)
{
parent::__construct();
$this->tag = $tag;
}
}
class AutoTaggerException extends SCoreException
{
}
class AddAutoTagException extends SCoreException
{
}
class AutoTagger extends Extension
{
/** @var AutoTaggerTheme */
protected $theme;
public function onPageRequest(PageRequestEvent $event)
{
global $config, $database, $page, $user;
if ($event->page_matches("auto_tag")) {
if ($event->get_arg(0) == "add") {
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
$user->ensure_authed();
$input = validate_input(["c_tag"=>"string", "c_additional_tags"=>"string"]);
try {
send_event(new AddAutoTagEvent($input['c_tag'], $input['c_additional_tags']));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("auto_tag/list"));
} catch (AddAutoTagException $ex) {
$this->theme->display_error(500, "Error adding auto-tag", $ex->getMessage());
}
}
} elseif ($event->get_arg(0) == "remove") {
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
$user->ensure_authed();
$input = validate_input(["d_tag"=>"string"]);
send_event(new DeleteAutoTagEvent($input['d_tag']));
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("auto_tag/list"));
}
} elseif ($event->get_arg(0) == "list") {
$t = new AutoTaggerTable($database->raw_db());
$t->token = $user->get_auth_token();
$t->inputs = $_GET;
$t->size = $config->get_int(AutoTaggerConfig::ITEMS_PER_PAGE, 30);
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
$t->create_url = make_link("auto_tag/add");
$t->delete_url = make_link("auto_tag/remove");
}
$this->theme->display_auto_tagtable($t->table($t->query()), $t->paginator());
} elseif ($event->get_arg(0) == "export") {
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_CSV);
$page->set_filename("auto_tag.csv");
$page->set_data($this->get_auto_tag_csv($database));
} elseif ($event->get_arg(0) == "import") {
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
if (count($_FILES) > 0) {
$tmp = $_FILES['auto_tag_file']['tmp_name'];
$contents = file_get_contents($tmp);
$count = $this->add_auto_tag_csv($database, $contents);
log_info(AutoTaggerInfo::KEY, "Imported $count auto-tag definitions from file from file", "Imported $count auto-tag definitions");
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("auto_tag/list"));
} else {
$this->theme->display_error(400, "No File Specified", "You have to upload a file");
}
} else {
$this->theme->display_error(401, "Admins Only", "Only admins can edit the auto-tag list");
}
}
}
}
public function onPageSubNavBuilding(PageSubNavBuildingEvent $event)
{
if ($event->parent=="tags") {
$event->add_nav_link("auto_tag", new Link('auto_tag/list'), "Auto-Tag", NavLink::is_active(["auto_tag"]));
}
}
public function onDatabaseUpgrade(DatabaseUpgradeEvent $event)
{
global $database;
// Create the database tables
if ($this->get_version(AutoTaggerConfig::VERSION) < 1) {
$database->create_table("auto_tag", "
tag VARCHAR(128) NOT NULL PRIMARY KEY,
additional_tags VARCHAR(2000) NOT NULL
");
if ($database->get_driver_name() == DatabaseDriver::PGSQL) {
$database->execute('CREATE INDEX auto_tag_lower_tag_idx ON auto_tag ((lower(tag)))');
}
$this->set_version(AutoTaggerConfig::VERSION, 1);
log_info(AutoTaggerInfo::KEY, "extension installed");
}
}
public function onTagSet(TagSetEvent $event)
{
$results = $this->apply_auto_tags($event->tags);
if (!empty($results)) {
$event->tags = $results;
}
}
public function onAddAutoTag(AddAutoTagEvent $event)
{
global $page;
$this->add_auto_tag($event->tag, $event->additional_tags);
$page->flash("Added Auto-Tag");
}
public function onDeleteAutoTag(DeleteAutoTagEvent $event)
{
$this->remove_auto_tag($event->tag);
}
public function onUserBlockBuilding(UserBlockBuildingEvent $event)
{
global $user;
if ($user->can(Permissions::MANAGE_AUTO_TAG)) {
$event->add_link("Auto-Tag Editor", make_link("auto_tag/list"));
}
}
private function get_auto_tag_csv(Database $database): string
{
$csv = "";
$pairs = $database->get_pairs("SELECT tag, additional_tags FROM auto_tag ORDER BY tag");
foreach ($pairs as $old => $new) {
$csv .= "\"$old\",\"$new\"\n";
}
return $csv;
}
private function add_auto_tag_csv(Database $database, string $csv): int
{
$csv = str_replace("\r", "\n", $csv);
$i = 0;
foreach (explode("\n", $csv) as $line) {
$parts = str_getcsv($line);
if (count($parts) == 2) {
try {
send_event(new AddAutoTagEvent($parts[0], $parts[1]));
$i++;
} catch (AddAutoTagException $ex) {
$this->theme->display_error(500, "Error adding auto-tags", $ex->getMessage());
}
}
}
return $i;
}
private function add_auto_tag(string $tag, string $additional_tags)
{
global $database;
if ($database->exists("SELECT * FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag])) {
throw new AutoTaggerException("Auto-Tag is already set for that tag");
} else {
$tag = Tag::sanitize($tag);
$additional_tags = Tag::explode($additional_tags);
$database->execute(
"INSERT INTO auto_tag(tag, additional_tags) VALUES(:tag, :additional_tags)",
["tag"=>$tag, "additional_tags"=>Tag::implode($additional_tags)]
);
log_info(
AutoTaggerInfo::KEY,
"Added auto-tag for {$tag} -> {".implode(" ", $additional_tags)."}"
);
// Now we apply it to existing items
$this->apply_new_auto_tag($tag);
}
}
private function update_auto_tag(string $tag, string $additional_tags): bool
{
global $database;
$result = $database->get_row("SELECT * FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag"=>$tag]);
if ($result===null) {
throw new AutoTaggerException("Auto-tag not set for $tag, can't update");
} else {
$additional_tags = Tag::explode($additional_tags);
$current_additional_tags = Tag::explode($result["additional_tags"]);
if (!Tag::compare($additional_tags, $current_additional_tags)) {
$database->execute(
"UPDATE auto_tag SET additional_tags = :additional_tags WHERE LOWER(tag)=LOWER(:tag)",
["tag"=>$tag, "additional_tags"=>Tag::implode($additional_tags)]
);
log_info(
AutoTaggerInfo::KEY,
"Updated auto-tag for {$tag} -> {".implode(" ", $additional_tags)."}",
"Updated Auto-Tag"
);
// Now we apply it to existing items
$this->apply_new_auto_tag($tag);
return true;
}
}
return false;
}
private function apply_new_auto_tag(string $tag)
{
global $database;
$tag_id = $database->get_one("SELECT id FROM tags WHERE LOWER(tag) = LOWER(:tag)", ["tag"=>$tag]);
if (!empty($tag_id)) {
$image_ids = $database->get_col_iterable("SELECT image_id FROM image_tags WHERE tag_id = :tag_id", ["tag_id"=>$tag_id]);
foreach ($image_ids as $image_id) {
$image = Image::by_id($image_id);
$event = new TagSetEvent($image, $image->get_tag_array());
send_event($event);
}
}
}
private function remove_auto_tag(String $tag)
{
global $database;
$database->execute("DELETE FROM auto_tag WHERE LOWER(tag)=LOWER(:tag)", ["tag" => $tag]);
}
/**
* #param string[] $tags_mixed
*/
private function apply_auto_tags(array $tags_mixed): ?array
{
global $database;
while (true) {
$new_tags = [];
foreach ($tags_mixed as $tag) {
$additional_tags = $database->get_one(
"SELECT additional_tags FROM auto_tag WHERE LOWER(tag) = LOWER(:input)",
["input" => $tag]
);
if (!empty($additional_tags)) {
$additional_tags = explode(" ", $additional_tags);
$new_tags = array_merge(
$new_tags,
array_udiff($additional_tags, $tags_mixed, 'strcasecmp')
);
}
}
if (empty($new_tags)) {
break;
}
$tags_mixed = array_merge($tags_mixed, $new_tags);
}
$results = array_intersect_key(
$tags_mixed,
array_unique(array_map('strtolower', $tags_mixed))
);
return $results;
}
/**
* Get the priority for this extension.
*
*/
public function get_priority(): int
{
return 30;
}
}

58
ext/auto_tagger/test.php Normal file
View File

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
class AutoTaggerTest extends ShimmiePHPUnitTestCase
{
public function testAutoTaggerList()
{
$this->get_page('auto_tag/list');
$this->assert_response(200);
$this->assert_title("Auto-Tag");
}
public function testAutoTaggerListReadOnly()
{
$this->log_in_as_user();
$this->get_page('auto_tag/list');
$this->assert_title("Auto-Tag");
$this->assert_no_text("value=\"Add\"");
$this->log_out();
$this->get_page('auto_tag/list');
$this->assert_title("Auto-Tag");
$this->assert_no_text("value=\"Add\"");
}
public function testAutoTagger()
{
$this->log_in_as_admin();
$this->get_page("auto_tag/export/auto_tag.csv");
$this->assert_no_text("test1");
send_event(new AddAutoTagEvent("test1", "test2"));
$this->get_page('auto_tag/list');
$this->assert_text("test1");
$this->assert_text("test2");
$this->get_page("auto_tag/export/auto_tag.csv");
$this->assert_text('"test1","test2"');
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test1 test2");
$this->delete_image($image_id);
send_event(new AddAutoTagEvent("test2", "test3"));
$image_id = $this->post_image("tests/pbx_screenshot.jpg", "test1");
$this->get_page("post/view/$image_id"); # check that the tag has been replaced
$this->assert_title("Image $image_id: test1 test2 test3");
$this->delete_image($image_id);
send_event(new DeleteAutoTagEvent("test1"));
send_event(new DeleteAutoTagEvent("test2"));
$this->get_page('auto_tag/list');
$this->assert_title("Auto-Tag");
$this->assert_no_text("test1");
$this->assert_no_text("test2");
$this->assert_no_text("test3");
}
}

36
ext/auto_tagger/theme.php Normal file
View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
class AutoTaggerTheme extends Themelet
{
/**
* Show a page of auto-tag definitions.
*
* Note: $can_manage = whether things like "add new alias" should be shown
*/
public function display_auto_tagtable($table, $paginator): void
{
global $page, $user;
$can_manage = $user->can(Permissions::MANAGE_AUTO_TAG);
$html = "
$table
$paginator
<p><a href='".make_link("auto_tag/export/auto_tag.csv")."' download='auto_tag.csv'>Download as CSV</a></p>
";
$bulk_html = "
".make_form(make_link("auto_tag/import"), 'post', true)."
<input type='file' name='auto_tag_file'>
<input type='submit' value='Upload List'>
</form>
";
$page->set_title("Auto-Tag List");
$page->set_heading("Auto-Tag List");
$page->add_block(new NavBlock());
$page->add_block(new Block("Auto-Tag", $html));
if ($can_manage) {
$page->add_block(new Block("Bulk Upload", $bulk_html, "main", 51));
}
}
}

11
ext/autocomplete/info.php Normal file
View File

@ -0,0 +1,11 @@
<?php declare(strict_types=1);
class AutoCompleteInfo extends ExtensionInfo
{
public const KEY = "autocomplete";
public $key = self::KEY;
public $name = "Autocomplete";
public $authors = ["Daku"=>"admin@codeanimu.net"];
public $description = "Adds autocomplete to search & tagging.";
}

File diff suppressed because one or more lines are too long

13
ext/autocomplete/lib/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,69 @@
ul.tagit {
padding: 1px 5px;
overflow: auto;
margin-left: inherit; /* usually we don't want the regular ul margins. */
margin-right: inherit;
}
ul.tagit li {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit li.tagit-choice {
position: relative;
line-height: inherit;
}
input.tagit-hidden-field {
display: none;
}
ul.tagit li.tagit-choice-read-only {
padding: .2em .5em .2em .5em;
}
ul.tagit li.tagit-choice-editable {
padding: .2em 18px .2em .5em;
}
ul.tagit li.tagit-new {
padding: .25em 4px .25em 0;
}
ul.tagit li.tagit-choice a.tagit-label {
cursor: pointer;
text-decoration: none;
}
ul.tagit li.tagit-choice .tagit-close {
cursor: pointer;
position: absolute;
right: .1em;
top: 50%;
margin-top: -8px;
line-height: 17px;
}
/* used for some custom themes that don't need image icons */
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: none;
}
ul.tagit li.tagit-choice input {
display: block;
float: left;
margin: 2px 5px 2px 0;
}
ul.tagit input[type="text"] {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
margin: 0;
padding: 0;
width: inherit;
background-color: inherit;
outline: none;
}

18
ext/autocomplete/lib/tag-it.min.js vendored Normal file
View File

@ -0,0 +1,18 @@
//Removed TAB keybind
;(function(b){b.widget("ui.tagit",{options:{allowDuplicates:!1,caseSensitive:!0,fieldName:"tags",placeholderText:null,readOnly:!1,removeConfirmation:!1,tagLimit:null,availableTags:[],autocomplete:{},showAutocompleteOnFocus:!1,allowSpaces:!1,singleField:!1,singleFieldDelimiter:",",singleFieldNode:null,animate:!0,tabIndex:null,beforeTagAdded:null,afterTagAdded:null,beforeTagRemoved:null,afterTagRemoved:null,onTagClicked:null,onTagLimitExceeded:null,onTagAdded:null,onTagRemoved:null,tagSource:null},_create:function(){var a=
this;this.element.is("input")?(this.tagList=b("<ul></ul>").insertAfter(this.element),this.options.singleField=!0,this.options.singleFieldNode=this.element,this.element.addClass("tagit-hidden-field")):this.tagList=this.element.find("ul, ol").andSelf().last();this.tagInput=b('<input type="text" />').addClass("ui-widget-content");this.options.readOnly&&this.tagInput.attr("disabled","disabled");this.options.tabIndex&&this.tagInput.attr("tabindex",this.options.tabIndex);this.options.placeholderText&&this.tagInput.attr("placeholder",
this.options.placeholderText);this.options.autocomplete.source||(this.options.autocomplete.source=function(a,e){var d=a.term.toLowerCase(),c=b.grep(this.options.availableTags,function(a){return 0===a.toLowerCase().indexOf(d)});this.options.allowDuplicates||(c=this._subtractArray(c,this.assignedTags()));e(c)});this.options.showAutocompleteOnFocus&&(this.tagInput.focus(function(b,d){a._showAutocomplete()}),"undefined"===typeof this.options.autocomplete.minLength&&(this.options.autocomplete.minLength=
0));b.isFunction(this.options.autocomplete.source)&&(this.options.autocomplete.source=b.proxy(this.options.autocomplete.source,this));b.isFunction(this.options.tagSource)&&(this.options.tagSource=b.proxy(this.options.tagSource,this));this.tagList.addClass("tagit").addClass("ui-widget ui-widget-content ui-corner-all").append(b('<li class="tagit-new"></li>').append(this.tagInput)).click(function(d){var c=b(d.target);c.hasClass("tagit-label")?(c=c.closest(".tagit-choice"),c.hasClass("removed")||a._trigger("onTagClicked",
d,{tag:c,tagLabel:a.tagLabel(c)})):a.tagInput.focus()});var c=!1;if(this.options.singleField)if(this.options.singleFieldNode){var d=b(this.options.singleFieldNode),f=d.val().split(this.options.singleFieldDelimiter);d.val("");b.each(f,function(b,d){a.createTag(d,null,!0);c=!0})}else this.options.singleFieldNode=b('<input type="hidden" style="display:none;" value="" name="'+this.options.fieldName+'" />'),this.tagList.after(this.options.singleFieldNode);c||this.tagList.children("li").each(function(){b(this).hasClass("tagit-new")||
(a.createTag(b(this).text(),b(this).attr("class"),!0),b(this).remove())});this.tagInput.keydown(function(c){if(c.which==b.ui.keyCode.BACKSPACE&&""===a.tagInput.val()){var d=a._lastTag();!a.options.removeConfirmation||d.hasClass("remove")?a.removeTag(d):a.options.removeConfirmation&&d.addClass("remove ui-state-highlight")}else a.options.removeConfirmation&&a._lastTag().removeClass("remove ui-state-highlight");if(c.which===b.ui.keyCode.COMMA&&!1===c.shiftKey||c.which===b.ui.keyCode.ENTER||c.which==
c.which==b.ui.keyCode.SPACE&&!0!==a.options.allowSpaces&&('"'!=b.trim(a.tagInput.val()).replace(/^s*/,"").charAt(0)||'"'==b.trim(a.tagInput.val()).charAt(0)&&'"'==b.trim(a.tagInput.val()).charAt(b.trim(a.tagInput.val()).length-1)&&0!==b.trim(a.tagInput.val()).length-1))c.which===b.ui.keyCode.ENTER&&""===a.tagInput.val()||c.preventDefault(),a.options.autocomplete.autoFocus&&a.tagInput.data("autocomplete-open")||(a.tagInput.autocomplete("close"),a.createTag(a._cleanedInput()))}).blur(function(b){a.tagInput.data("autocomplete-open")||
a.createTag(a._cleanedInput())});if(this.options.availableTags||this.options.tagSource||this.options.autocomplete.source)d={select:function(b,c){a.createTag(c.item.value);return!1}},b.extend(d,this.options.autocomplete),d.source=this.options.tagSource||d.source,this.tagInput.autocomplete(d).bind("autocompleteopen.tagit",function(b,c){a.tagInput.data("autocomplete-open",!0)}).bind("autocompleteclose.tagit",function(b,c){a.tagInput.data("autocomplete-open",!1)}),this.tagInput.autocomplete("widget").addClass("tagit-autocomplete")},
destroy:function(){b.Widget.prototype.destroy.call(this);this.element.unbind(".tagit");this.tagList.unbind(".tagit");this.tagInput.removeData("autocomplete-open");this.tagList.removeClass("tagit ui-widget ui-widget-content ui-corner-all tagit-hidden-field");this.element.is("input")?(this.element.removeClass("tagit-hidden-field"),this.tagList.remove()):(this.element.children("li").each(function(){b(this).hasClass("tagit-new")?b(this).remove():(b(this).removeClass("tagit-choice ui-widget-content ui-state-default ui-state-highlight ui-corner-all remove tagit-choice-editable tagit-choice-read-only"),
b(this).text(b(this).children(".tagit-label").text()))}),this.singleFieldNode&&this.singleFieldNode.remove());return this},_cleanedInput:function(){return b.trim(this.tagInput.val().replace(/^"(.*)"$/,"$1"))},_lastTag:function(){return this.tagList.find(".tagit-choice:last:not(.removed)")},_tags:function(){return this.tagList.find(".tagit-choice:not(.removed)")},assignedTags:function(){var a=this,c=[];this.options.singleField?(c=b(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter),
""===c[0]&&(c=[])):this._tags().each(function(){c.push(a.tagLabel(this))});return c},_updateSingleTagsField:function(a){b(this.options.singleFieldNode).val(a.join(this.options.singleFieldDelimiter)).trigger("change")},_subtractArray:function(a,c){for(var d=[],f=0;f<a.length;f++)-1==b.inArray(a[f],c)&&d.push(a[f]);return d},tagLabel:function(a){return this.options.singleField?b(a).find(".tagit-label:first").text():b(a).find("input:first").val()},_showAutocomplete:function(){this.tagInput.autocomplete("search",
"")},_findTagByLabel:function(a){var c=this,d=null;this._tags().each(function(f){if(c._formatStr(a)==c._formatStr(c.tagLabel(this)))return d=b(this),!1});return d},_isNew:function(a){return!this._findTagByLabel(a)},_formatStr:function(a){return this.options.caseSensitive?a:b.trim(a.toLowerCase())},_effectExists:function(a){return Boolean(b.effects&&(b.effects[a]||b.effects.effect&&b.effects.effect[a]))},createTag:function(a,c,d){var f=this;a=b.trim(a);this.options.preprocessTag&&(a=this.options.preprocessTag(a));
if(""===a)return!1;if(!this.options.allowDuplicates&&!this._isNew(a))return a=this._findTagByLabel(a),!1!==this._trigger("onTagExists",null,{existingTag:a,duringInitialization:d})&&this._effectExists("highlight")&&a.effect("highlight"),!1;if(this.options.tagLimit&&this._tags().length>=this.options.tagLimit)return this._trigger("onTagLimitExceeded",null,{duringInitialization:d}),!1;var g=b(this.options.onTagClicked?'<a class="tagit-label"></a>':'<span class="tagit-label"></span>').text(a),e=b("<li></li>").addClass("tagit-choice ui-widget-content ui-state-default ui-corner-all").addClass(c).append(g);
this.options.readOnly?e.addClass("tagit-choice-read-only"):(e.addClass("tagit-choice-editable"),c=b("<span></span>").addClass("ui-icon ui-icon-close"),c=b('<a><span class="text-icon">\u00d7</span></a>').addClass("tagit-close").append(c).click(function(a){f.removeTag(e)}),e.append(c));this.options.singleField||(g=g.html(),e.append('<input type="hidden" value="'+g+'" name="'+this.options.fieldName+'" class="tagit-hidden-field" />'));!1!==this._trigger("beforeTagAdded",null,{tag:e,tagLabel:this.tagLabel(e),
duringInitialization:d})&&(this.options.singleField&&(g=this.assignedTags(),g.push(a),this._updateSingleTagsField(g)),this._trigger("onTagAdded",null,e),this.tagInput.val(""),this.tagInput.parent().before(e),this._trigger("afterTagAdded",null,{tag:e,tagLabel:this.tagLabel(e),duringInitialization:d}),this.options.showAutocompleteOnFocus&&!d&&setTimeout(function(){f._showAutocomplete()},0))},removeTag:function(a,c){c="undefined"===typeof c?this.options.animate:c;a=b(a);this._trigger("onTagRemoved",
null,a);if(!1!==this._trigger("beforeTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})){if(this.options.singleField){var d=this.assignedTags(),f=this.tagLabel(a),d=b.grep(d,function(a){return a!=f});this._updateSingleTagsField(d)}if(c){a.addClass("removed");var d=this._effectExists("blind")?["blind",{direction:"horizontal"},"fast"]:["fast"],g=this;d.push(function(){a.remove();g._trigger("afterTagRemoved",null,{tag:a,tagLabel:g.tagLabel(a)})});a.fadeOut("fast").hide.apply(a,d).dequeue()}else a.remove(),
this._trigger("afterTagRemoved",null,{tag:a,tagLabel:this.tagLabel(a)})}},removeTagByLabel:function(a,b){var d=this._findTagByLabel(a);if(!d)throw"No such tag exists with the name '"+a+"'";this.removeTag(d,b)},removeAll:function(){var a=this;this._tags().each(function(b,d){a.removeTag(d,!1)})}})})(jQuery);

View File

@ -0,0 +1,97 @@
/* Optional scoped theme for tag-it which mimics the zendesk widget. */
ul.tagit {
border-style: solid;
border-width: 1px;
border-color: #C6C6C6;
background: inherit;
}
ul.tagit li.tagit-choice {
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-border-radius: 6px;
border: 1px solid #CAD8F3;
background: #DEE7F8 none;
font-weight: normal;
}
ul.tagit li.tagit-choice .tagit-label:not(a) {
color: #555;
}
ul.tagit li.tagit-choice a.tagit-close {
text-decoration: none;
}
ul.tagit li.tagit-choice .tagit-close {
right: .4em;
}
ul.tagit li.tagit-choice .ui-icon {
display: none;
}
ul.tagit li.tagit-choice .tagit-close .text-icon {
display: inline;
font-family: arial, sans-serif;
font-size: 16px;
line-height: 16px;
color: #777;
}
ul.tagit li.tagit-choice:hover, ul.tagit li.tagit-choice.remove {
background-color: #bbcef1;
border-color: #6d95e0;
}
ul.tagit li.tagit-choice a.tagLabel:hover,
ul.tagit li.tagit-choice a.tagit-close .text-icon:hover {
color: #222;
}
ul.tagit input[type="text"] {
color: #333333;
background: none;
}
.ui-widget {
font-size: 1.1em;
}
/* Forked from a jQuery UI theme, so that we don't require the jQuery UI CSS as a dependency. */
.tagit-autocomplete.ui-autocomplete { position: absolute; cursor: default; }
* html .tagit-autocomplete.ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
.tagit-autocomplete.ui-menu {
list-style:none;
padding: 2px;
margin: 0;
display:block;
float: left;
}
.tagit-autocomplete.ui-menu .ui-menu {
margin-top: -3px;
}
.tagit-autocomplete.ui-menu .ui-menu-item {
margin:0;
padding: 0;
zoom: 1;
float: left;
clear: left;
width: 100%;
}
.tagit-autocomplete.ui-menu .ui-menu-item a {
text-decoration:none;
display:block;
padding:.2em .4em;
line-height:1.5;
zoom:1;
}
.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-hover,
.tagit-autocomplete .ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
}
.tagit-autocomplete.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff 50% 50% repeat-x; color: #222222; }
.tagit-autocomplete.ui-corner-all, .tagit-autocomplete .ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; -khtml-border-radius: 4px; border-radius: 4px; }
.tagit-autocomplete .ui-state-hover, .tagit-autocomplete .ui-state-focus { border: 1px solid #999999; background: #dadada; font-weight: normal; color: #212121; }
.tagit-autocomplete .ui-state-active { border: 1px solid #aaaaaa; }
.tagit-autocomplete .ui-widget-content { border: 1px solid #aaaaaa; }
.tagit .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px,1px,1px,1px); }

69
ext/autocomplete/main.php Normal file
View File

@ -0,0 +1,69 @@
<?php declare(strict_types=1);
class AutoComplete extends Extension
{
/** @var AutoCompleteTheme */
protected $theme;
public function get_priority(): int
{
return 30;
} // before Home
public function onPageRequest(PageRequestEvent $event)
{
global $cache, $page, $database;
if ($event->page_matches("api/internal/autocomplete")) {
if (!isset($_GET["s"])) {
return;
}
$page->set_mode(PageMode::DATA);
$page->set_type(MIME_TYPE_JSON);
$s = strtolower($_GET["s"]);
if (
$s == '' ||
$s[0] == '_' ||
$s[0] == '%' ||
strlen($s) > 32
) {
$page->set_data("{}");
return;
}
//$limit = 0;
$cache_key = "autocomplete-$s";
$limitSQL = "";
$s = str_replace('_', '\_', $s);
$s = str_replace('%', '\%', $s);
$SQLarr = ["search"=>"$s%"]; #, "cat_search"=>"%:$s%"];
if (isset($_GET["limit"]) && $_GET["limit"] !== 0) {
$limitSQL = "LIMIT :limit";
$SQLarr['limit'] = $_GET["limit"];
$cache_key .= "-" . $_GET["limit"];
}
$res = $cache->get($cache_key);
if (!$res) {
$res = $database->get_pairs(
"
SELECT tag, count
FROM tags
WHERE LOWER(tag) LIKE LOWER(:search)
-- OR LOWER(tag) LIKE LOWER(:cat_search)
AND count > 0
ORDER BY count DESC
$limitSQL",
$SQLarr
);
$cache->set($cache_key, $res, 600);
}
$page->set_data(json_encode($res));
}
$this->theme->build_autocomplete($page);
}
}

View File

@ -0,0 +1,81 @@
document.addEventListener('DOMContentLoaded', () => {
var metatags = ['order:id', 'order:width', 'order:height', 'order:filesize', 'order:filename'];
$('[name="search"]').tagit({
singleFieldDelimiter: ' ',
beforeTagAdded: function(event, ui) {
if(metatags.indexOf(ui.tagLabel) !== -1) {
ui.tag.addClass('tag-metatag');
} else {
console.log(ui.tagLabel);
// give special class to negative tags
if(ui.tagLabel[0] === '-') {
ui.tag.addClass('tag-negative');
}else{
ui.tag.addClass('tag-positive');
}
}
},
autocomplete : ({
source: function (request, response) {
var ac_metatags = $.map(
$.grep(metatags, function(s) {
// Only show metatags for strings longer than one character
return (request.term.length > 1 && s.indexOf(request.term) === 0);
}),
function(item) {
return {
label : item + ' [metatag]',
value : item
};
}
);
var isNegative = (request.term[0] === '-');
$.ajax({
url: base_href + '/api/internal/autocomplete',
data: {'s': (isNegative ? request.term.substring(1) : request.term)},
dataType : 'json',
type : 'GET',
success : function (data) {
response(
$.merge(ac_metatags,
$.map(data, function (count, item) {
item = (isNegative ? '-'+item : item);
return {
label : item + ' ('+count+')',
value : item
};
})
)
);
},
error : function (request, status, error) {
console.log(error);
}
});
},
minLength: 1
})
});
$('.ui-autocomplete-input').keydown(function(e) {
var keyCode = e.keyCode || e.which;
//Stop tags containing space.
if(keyCode == 32) {
e.preventDefault();
$('.autocomplete_tags').tagit('createTag', $(this).val());
$(this).autocomplete('close');
} else if (keyCode == 9) {
e.preventDefault();
var tag = $('.tagit-autocomplete[style*=\"display: block\"] > li:focus, .tagit-autocomplete[style*=\"display: block\"] > li:first').first();
if(tag.length){
$(tag).click();
$('.ui-autocomplete-input').val(''); //If tag already exists, make sure to remove duplicate.
}
}
});
});

View File

@ -0,0 +1,9 @@
#Navigationleft .blockbody { overflow: visible; }
.tagit { background: white !important; border: 1px solid grey !important; cursor: text; }
.tagit-choice { cursor: initial; }
input[name=search] ~ input[type=submit] { display: inline-block !important; }
.tag-negative { background: #ff8080 !important; }
.tag-positive { background: #40bf40 !important; }
.tag-metatag { background: #eaa338 !important; }

14
ext/autocomplete/test.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
class AutoCompleteTest extends ShimmiePHPUnitTestCase
{
public function testAuth()
{
send_event(new UserLoginEvent(User::by_name(self::$anon_name)));
$page = $this->get_page('api/internal/autocomplete', ["s"=>"not-a-tag"]);
$this->assertEquals(200, $page->code);
$this->assertEquals(PageMode::DATA, $page->mode);
$this->assertEquals("[]", $page->data);
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
class AutoCompleteTheme extends Themelet
{
public function build_autocomplete(Page $page)
{
$base_href = get_base_href();
// TODO: AJAX test and fallback.
$page->add_html_header("<script defer src='$base_href/ext/autocomplete/lib/jquery-ui.min.js' type='text/javascript'></script>");
$page->add_html_header("<script defer src='$base_href/ext/autocomplete/lib/tag-it.min.js' type='text/javascript'></script>");
$page->add_html_header('<link rel="stylesheet" type="text/css" href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/flick/jquery-ui.css">');
$page->add_html_header("<link rel='stylesheet' type='text/css' href='$base_href/ext/autocomplete/lib/jquery.tagit.css' />");
}
}

26
ext/ban_words/info.php Normal file
View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
class BanWordsInfo extends ExtensionInfo
{
public const KEY = "ban_words";
public $key = self::KEY;
public $name = "Comment Word Ban";
public $url = self::SHIMMIE_URL;
public $authors = self::SHISH_AUTHOR;
public $license = self::LICENSE_GPLV2;
public $description = "For stopping spam and other comment abuse";
public $documentation =
"Allows an administrator to ban certain words
from comments. This can be a very simple but effective way
of stopping spam; just add \"viagra\", \"porn\", etc to the
banned words list.
<p>Regex bans are also supported, allowing more complicated
bans like <code>/http:.*\.cn\//</code> to block links to
chinese websites, or <code>/.*?http.*?http.*?http.*?http.*?/</code>
to block comments with four (or more) links in.
<p>Note that for non-regex matches, only whole words are
matched, eg banning \"sex\" would block the comment \"get free
sex call this number\", but allow \"This is a photo of Bob
from Essex\"";
}

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