Compare commits

...

561 Commits

Author SHA1 Message Date
Shish
21fdbdddba 2.3.0, merry christmas! 2010-12-25 00:04:25 +00:00
Shish
1a3fa47217 Revert "checks for files without extensions, from AtomicDryad"
This seems to break something, such that uploading a jpeg results
in image size 0x0 (other bits seem to work though o_O)

Will take a look at what's up and re-commit later...

This reverts commit ead87d3934ad7d29394fd39e309bd75f5eb78fbb.
2010-12-24 20:46:52 +00:00
Shish
101cd38e1a readme updates 2010-12-23 14:10:58 +00:00
Shish
f4dc0a5764 delete by query 2010-12-23 14:01:25 +00:00
Shish
96fc3f7e94 merge of AD's updates (now with real git-merge, rather than patching by hand; apologies if the hand-patches broke anything) 2010-12-23 01:15:48 +00:00
atomicdryad
84ba3c10ab Extension: tag_editcloud: Add or remove tags to the editor via clicking 2010-12-23 00:49:13 +00:00
Shish
06c61978d7 several themes used the same script.js, and several extensions depend upon these scripts -- move all the functions to a single .js file in lib 2010-12-22 22:40:59 +00:00
Shish
ef2e94e70a checks for files without extensions, from AtomicDryad 2010-12-22 21:43:19 +00:00
Shish
992316d10c Danbooru API fixes from AtomicDryad 2010-12-22 21:38:51 +00:00
Shish
b32f2de040 use google charts for QR code generation, save 7MB 2010-12-14 20:34:35 +00:00
Shish
0ff432abf6 what the fuck, php? this should not be necessary :/ 2010-10-11 12:21:30 +01:00
Shish
068ff1ac62 use config.php as CSRF salt 2010-10-07 21:31:26 +01:00
Shish
e577ffa785 <form> -> make_form(), or add auth token manually 2010-10-07 21:31:07 +01:00
Shish
9b5c65f11f csrf-proofing for extensions 2010-10-07 21:29:59 +01:00
Shish
063cdd4746 csrf functions 2010-10-07 21:29:47 +01:00
Shish
81ea250fdf split is deprecated in php5.3, use explode instead 2010-07-30 15:37:03 +01:00
Shish
ebf25505eb external CSS comes before external JS 2010-07-30 13:39:32 +01:00
Shish
926ecf3974 specify image size in HTML rather than CSS; chrome claims it loads faster 2010-07-30 13:26:05 +01:00
Shish
f6863c7fb8 bump 2010-07-27 08:27:56 +01:00
Shish
b294486b0e another 2010-07-26 23:29:22 +01:00
Shish
6720f639c8 more url_escape 2010-07-26 23:10:57 +01:00
Shish
ef073290c0 not all imageinfoset events have favorite_action 2010-07-26 23:06:26 +01:00
Shish
501a8485ce only vote for valid images 2010-07-26 23:00:24 +01:00
Shish
9e05a34ac9 only favourite valid images 2010-07-26 22:59:36 +01:00
Shish
4e48e02856 curl might not be installed 2010-07-26 22:48:44 +01:00
Shish
4eb19631b7 escape more data, fix an XSS hole 2010-07-26 17:21:58 +01:00
Shish
d31760a334 repeat of GET forms breaking on sites without niceurls 2010-07-26 11:18:38 +01:00
Shish
85178ff127 hide metadata on windows 2010-07-19 13:57:40 +01:00
Shish
65c26e5825 more themes, more updates 2010-07-19 13:53:29 +01:00
Shish
e00e5ff0d1 bump 2010-07-19 13:38:04 +01:00
Shish
8a0eda15eb hack 2010-07-19 13:35:24 +01:00
Shish
b1f78d0f33 adodb updates 2010-07-19 13:10:02 +01:00
Shish
d2469191f9 ignore it too 2010-07-19 13:06:43 +01:00
Shish
62fd2952ff and the S3 library 2010-07-19 13:06:30 +01:00
Shish
c276063389 S3 mirroring 2010-07-12 22:27:59 +01:00
Shish
dcd4b21a01 api safety 2010-07-09 13:51:42 +01:00
Shish
7943cd7682 start of the api 2010-07-09 13:51:29 +01:00
Shish
d57d9ee2de note the docs in the readme 2010-07-07 13:03:11 +01:00
Shish
c82350da05 hide disk_free_space errors 2010-06-27 17:32:34 +01:00
Shish
68ce20ae8b blargh 2010-06-17 19:44:26 +01:00
Shish
918c645829 file extension to confuse android less 2010-06-17 19:42:41 +01:00
Shish
c555dca40f the right class... 2010-05-28 13:36:56 +01:00
Shish
806352c0cb sometimes we want test uploads to fail 2010-05-28 13:25:48 +01:00
Shish
6fdc478b0f Convert some Extensions to SimpleExtensions with priorities 2010-05-28 13:08:04 +01:00
Shish
a8da2c14d6 SimpleExtension priority framework 2010-05-28 13:07:51 +01:00
Shish
c0ba0c3f3e nullify score button 2010-05-28 11:59:56 +01:00
Shish
aea5058d18 note that bulk_add requires admin 2010-05-28 11:39:46 +01:00
Shish
306d99d56c timeline function for profiling 2010-05-27 11:48:58 +01:00
Josh Sutinen
9918c1119c Paginator link code was ugly, if no filter criteria were set it would dump "&&&" at the end of the query string. This makes it look prettier. (v2 of fix) 2010-05-23 22:03:59 +01:00
Shish
449adbaff8 selectFirst is still a whore 2010-05-16 15:28:03 +01:00
Shish
fdd8e7cb7b hack to allow hardcoded database_dsn in hiphop 2010-05-15 16:25:12 +01:00
Shish
aad087cea8 version bump 2010-05-15 16:24:52 +01:00
Shish
4457de3cda for compiling under hiphop 2010-05-15 14:53:53 +01:00
Shish
d521b3739c final one? 2010-05-15 14:24:17 +01:00
Shish
09d08fa0e3 these are redundant too 2010-05-15 14:23:00 +01:00
Shish
7e0c39fe78 get rid of extra bits 2010-05-15 14:21:02 +01:00
Shish
d2875b98f1 class type hints for hiphop's benefit 2010-05-15 12:17:54 +01:00
Shish
b9474d9aa4 more numeric_score tests 2010-05-13 10:45:14 +01:00
Shish
b46d806fb6 A bunch of testing / fixes 2010-05-05 14:16:47 +01:00
Shish
393a88ca14 selectfirst is annoying 2010-04-28 11:21:26 +01:00
Shish
f57aa7ef5c mysql can go choke on a bucket of cocks 2010-04-27 11:36:36 +01:00
Shish
40ca6316fd turns out that this was necessary o_O 2010-04-26 06:07:49 +01:00
Shish
1ca3b24973 subtitle is ugly in this theme 2010-04-26 06:05:41 +01:00
Shish
d8eb1a6b20 a bunch of autocompletion 2010-04-26 06:00:57 +01:00
Shish
67a3776cef tag completion internal api 2010-04-26 05:50:20 +01:00
Shish
f33f1b4d77 shimmie already uses ?q= 2010-04-26 05:50:07 +01:00
Shish
ea1e8249b7 jquery versions; use a custom autocomplete plugin rather than jquery-ui's 2010-04-26 05:49:54 +01:00
Shish
b0150b0b93 Revert "search field autocomplete"
This reverts commit 29d9c7ae1600abd8f0136776daef16c9a23d2b8b.

zshall has already done this, better >_<
2010-04-26 05:04:29 +01:00
Shish
7b14e24475 search field autocomplete 2010-04-26 04:57:00 +01:00
Shish
81c661fe8b Merge branch 'pagevent' 2010-04-26 04:54:12 +01:00
Shish
64ca3d6018 new jquery 2010-04-26 04:52:54 +01:00
Shish
d5d59ce608 [postgres] remove warnings about overly-committed transactions 2010-04-23 16:18:55 +01:00
Shish
9ce1ccbe6d 'offset ? limit ?' is supported by more than 'limit ?, ?' 2010-04-23 16:11:03 +01:00
Shish
727941bc6b [postgres] offset/limit 2010-04-23 16:04:45 +01:00
Shish
5dda40513c move warehouse directory creation into a single place 2010-04-23 05:46:34 +01:00
Shish
794eff56b4 crudely hide explicit images from the comment list of anons 2010-04-23 04:08:40 +01:00
Shish
b7ca52165b double-escape backslashes as well as forward 2010-04-23 03:31:38 +01:00
Shish
9e7b1b1e79 link to ext_doc rather than ext_manager, for non-admins 2010-04-21 18:04:12 +01:00
Shish
fd3ff15485 avatar options 2010-04-21 17:57:47 +01:00
Shish
40b10a06e1 in theory, selectable avatar hosts 2010-04-21 17:32:59 +01:00
Shish
230d253142 Merge branch 'alltags' 2010-04-21 17:11:27 +01:00
Shish
1f9b0a8f71 remove shadow 2010-04-20 16:43:09 +01:00
Shish
a1ca6b8b1b opera = standards :O 2010-04-20 02:52:22 +01:00
Shish
91d423b22b new default 2010-04-20 02:32:49 +01:00
Shish
2045d12bed html5 theme 2010-04-20 02:32:37 +01:00
Shish
0e2c73ec2a border cancelling; somewhat of an ugly hack... 2010-04-19 20:42:26 +01:00
Shish
b45875619e remove error marker >_< 2010-04-11 13:14:13 +01:00
Zach Hall
06bd68e128 Removed some junk text from the lite theme. 2010-04-11 13:12:30 +01:00
Erik Youngren
d92ee0f8b8 Propogate 375c73d to the rest of the default themes. 2010-04-08 14:02:01 +01:00
Erik Youngren
e7bf4f9a8a Key sort page headers. 1000 should not be first. 2010-04-08 14:00:10 +01:00
Zach Hall
0b90d02318 Err... forgot one line, but now it's finished. 2010-04-08 13:23:51 +01:00
Zach Hall
d47b903449 Titlebar updates - current page links 2010-04-08 13:23:38 +01:00
Shish
14e5e96480 Merge branch 'lite_theme' 2010-04-07 13:46:14 +01:00
Shish
a4c93959f7 Comment::get_owner() function, as an official way to access user data (eg User::get_avatar()) 2010-04-07 13:39:12 +01:00
Shish
1d3d923449 timestamped comments 2010-04-07 13:26:10 +01:00
Josh Sutinen
67698b9c35 Fix bookmarklet using relative url into absolute url with make_http magic 2010-03-29 03:58:50 +01:00
Shish
4d5a8c9303 remove carridge returns when parsing alias CSVs 2010-03-27 04:01:50 +00:00
Shish
c3cc2f6097 make memcachecache pay attention to the URI 2010-03-25 10:53:17 +00:00
Shish
08fd957933 show user ID number 2010-03-25 10:51:47 +00:00
Shish
30779e0afc if necessary 2010-03-25 10:38:14 +00:00
Shish
bbdfc1fa89 version number >_< 2010-03-24 18:36:44 +00:00
Shish
ce488eb7e9 avoid double escapes 2010-03-24 18:25:08 +00:00
Shish
cf51d26d1a _end_coverage as a shutdown function, so that it still gets called on 'exit;' 2010-03-24 12:46:00 +00:00
Shish
59b25a0fe1 browser search test 2010-03-24 12:36:55 +00:00
Shish
f6d0c4df1b admin tools tests 2010-03-24 12:32:24 +00:00
Shish
6fa8442288 save a query 2010-03-24 04:44:49 +00:00
Zach Hall
bf2c629864 If a user doesn't have permission to view an image, not only will we hide it from the search results, we'll also disallow them access, redirecting them to post/list. 2010-03-24 04:41:28 +00:00
Shish
c38a66ae8f more banned words by default 2010-03-24 02:28:19 +00:00
Shish
f9411207d1 tidy up 2010-03-23 04:30:50 +00:00
Shish
dcf2623dfe remove redundantness 2010-03-23 01:08:27 +00:00
Shish
82145b853f make this work? 2010-03-22 15:36:59 +00:00
Shish
00b0cd7a39 more coverage 2010-03-22 14:47:49 +00:00
Shish
d1e1554827 more code coverage 2010-03-22 14:42:43 +00:00
Shish
6e1eca8aeb case sensitivity? 2010-03-22 04:30:23 +00:00
Shish
21d0eecebf two comments should exist now 2010-03-22 04:00:40 +00:00
Shish
b635a6441e only show extension docs if the extension exists 2010-03-22 03:58:37 +00:00
Shish
0ba2a49730 AHA! This bug took ages to track down, as the trigger was in a completely different system to the error message... Many, many thanks to zshall for managing to reproduce the error, then narrowing it down to a single line \o/ 2010-03-21 02:20:39 +00:00
Shish
9b2299cc23 build warehouse directories on demand 2010-03-15 05:35:22 +00:00
Shish
bcb98db9fb recovery console 2010-03-15 05:30:56 +00:00
Shish
c69f6c454c two references to trac, both are old 2010-03-15 03:32:15 +00:00
Shish
3682f84f04 test unicode comments 2010-03-14 20:51:35 +00:00
Shish
f93b2ad65b ... yeah 2010-03-14 02:19:29 +00:00
Shish
db5a0c682e make captchas optional and off by default, some people are having problems 2010-03-14 02:16:14 +00:00
Shish
134e4f8824 this uses post_image 2010-03-12 20:31:06 +00:00
Shish
a4b6ebbf89 more tests for search methods 2010-03-12 18:55:22 +00:00
Shish
0f85240af7 image ext tests 2010-03-12 18:48:43 +00:00
Shish
2f31c5995a more ext manager tests 2010-03-12 18:44:34 +00:00
Shish
51a695f185 ul and ol tags 2010-03-12 18:39:22 +00:00
Shish
2d66fafcf0 tips tests 2010-03-12 18:34:54 +00:00
Shish
6f3b992e9d test generation of the actual map 2010-03-12 17:58:40 +00:00
Shish
a5efff0ce9 this link is broken v.v 2010-03-10 19:27:15 +00:00
Shish
0f66c9d0cb avoid breakage when multiple extensions are installed alongside log_db 2010-03-08 21:56:50 +00:00
Shish
958efc2101 test source editing 2010-03-08 21:23:41 +00:00
Shish
23b44d2860 actually, we can use this 2010-03-08 18:30:25 +00:00
Shish
d52aff3396 next/prev with query test 2010-03-08 18:25:59 +00:00
Shish
f6687fcca0 note for updaters from 2.0 and 2.1 2010-03-02 02:48:37 +00:00
Shish
cd6fd1e87a search votes by IDs, in case of username weirdness 2010-03-01 00:16:13 +00:00
Shish
b4584f7003 indentation 2010-02-27 02:42:05 +00:00
Shish
abebbb36b8 found a bug 2010-02-27 02:41:25 +00:00
Shish
252dc45298 doc fix 2010-02-27 02:09:35 +00:00
Shish
5e09a0dee4 no need for datadict 2010-02-23 09:14:59 +00:00
Shish
c4a58d4d48 need page for error 2010-02-18 16:26:22 +00:00
Shish
d4c25d0684 403, not 503 2010-02-18 16:24:46 +00:00
Shish
7d07ad28ef test blotter denial 2010-02-18 16:22:41 +00:00
Shish
3f4f455ad6 spell 2010-02-18 15:59:21 +00:00
Shish
b865e696bc more blotter testing 2010-02-18 15:58:45 +00:00
Shish
164a0490b1 more ignorance 2010-02-18 14:35:43 +00:00
Shish
74000231dd blotter test 2010-02-18 14:35:10 +00:00
Shish
d3234ad96f html typo 2010-02-18 14:24:25 +00:00
Shish
43871d5cff SQL niceness 2010-02-18 14:20:51 +00:00
Shish
d20a6c17bb a bit more 2010-02-18 14:14:45 +00:00
Shish
9c88160f50 whitespace consistency 2010-02-18 14:13:15 +00:00
Zach Hall
1ac292697b Blotter Extension 2010-02-18 14:06:34 +00:00
Shish
b2edef6a83 postgres is true too 2010-02-17 15:14:08 +00:00
Shish
37619e9776 image locking 2010-02-17 14:17:03 +00:00
Shish
9d88f44d07 paginated logs 2010-02-12 17:22:43 +00:00
Shish
1efb08b0fc ipban: delete the cache after changing the data 2010-02-11 11:12:41 +00:00
Shish
e4a91ec215 regex for homepage links, hopefully less brokenness than the hand written parser 2010-02-11 11:08:12 +00:00
Shish
039f721725 Use Image::count_images() rather than SQL, to take advantage of advanced features and caching 2010-02-11 11:06:52 +00:00
Shish
66ce235436 fortmatted report text 2010-02-11 10:51:13 +00:00
Shish
ac3d7960d6 the final bit? 2010-02-09 10:08:24 +00:00
Shish
ba466412e4 post/view?search -> post/view#search for better caching 2010-02-09 10:06:34 +00:00
Shish
011e23b2fc post/view?search -> post/view#search for better caching 2010-02-09 10:06:13 +00:00
Shish
60e6f9b3da take next/prev from hash rather than query 2010-02-09 10:05:41 +00:00
Shish
8e54831873 URI based hash, for nginx compatability 2010-02-09 07:42:39 +00:00
Shish
91c06bd20e a technical explanation of build_accurate_search_querylet 2010-02-09 02:07:36 +00:00
Shish
b4624e912a link to tag changes 2010-02-03 23:55:49 +00:00
Shish
17218b0b08 correct some seemingly innocent behaviour, and thus fix a corner case in another extension 2010-02-03 23:55:37 +00:00
Shish
983f8451cf only optimise this case for regular tags 2010-02-03 22:04:22 +00:00
Shish
24fcc208aa comment IDs to link to 2010-02-03 16:01:24 +00:00
Shish
48a4dbd6e1 reset anon IDs on the comment/list too 2010-02-03 15:23:21 +00:00
Shish
a02e440048 postgres barfs if you try to compare a string to an inet_addr 2010-02-03 14:30:21 +00:00
Shish
8de01a6184 searchable event log 2010-02-03 13:58:51 +00:00
Shish
cea83d65ee if the search is for one tag, then 'count(tag)' (expensive function) = 'tag.count' (index lookup) 2010-02-02 18:20:13 +00:00
Shish
f21817b0d1 ID's anons 2010-02-02 18:15:53 +00:00
Shish
fe43af788f make apc not warn 2010-02-02 18:14:50 +00:00
Shish
454ade1769 activate caches if cache_dsn is set 2010-02-02 17:36:16 +00:00
Shish
2be4d32869 APC cache mechanism, faster than memcache? 2010-02-02 17:36:03 +00:00
Shish
70498bb07f limit/offset incompatability blah 2010-02-02 17:35:50 +00:00
Shish
9fbea9b440 make IP bans work better 2010-02-02 11:53:37 +00:00
Shish
54f44c7893 make ip_bans work without mysql specific code 2010-02-02 11:53:25 +00:00
Shish
8ba3d43128 case-insensitive search 2010-02-02 11:53:13 +00:00
Shish
50683e929e SCORE_STRNORM = lowercase-if-necessary-for-comparison 2010-02-02 02:14:29 +00:00
Shish
411b8b3bf7 no ha 2010-02-02 01:57:10 +00:00
Shish
83e5fb847a this uses the warehouse too 2010-02-02 01:55:55 +00:00
Shish
3df4c712fa more warehouse_path use 2010-02-02 01:07:48 +00:00
Shish
181bbcc9b7 link to image in the event log 2010-02-02 00:57:14 +00:00
Shish
c59ef1f355 typo 2010-02-02 00:56:53 +00:00
Shish
5690ab8078 lowercase match for pgsql 2010-02-02 00:45:35 +00:00
Shish
a74e4bb0c8 some only have concat, some only have pipes... 2010-02-02 00:43:26 +00:00
Shish
41e17a16ba wtf git 2010-02-02 00:35:59 +00:00
Shish
dc7de96ed5 more ignores 2010-02-01 23:51:11 +00:00
Shish
fbfa45fadf more sanity checks 2010-02-01 23:31:45 +00:00
Shish
102c8b413d pgsql fails at db->Insert_ID() 2010-02-01 23:25:35 +00:00
Shish
a681aae873 link to user's profile from the event log 2010-02-01 23:11:16 +00:00
Shish
2a6978abd9 log the right variable 2010-02-01 18:35:03 +00:00
Shish
306a1bf093 postgres requires separate 'offset' aside from 'limit' 2010-02-01 16:19:59 +00:00
Shish
0f38ac7a3c database-neutral table creation 2010-02-01 16:18:48 +00:00
Shish
78b85cb769 MemCache is already used... 2010-02-01 16:17:29 +00:00
Shish
41811385fa no concat in postgres, use pipes 2010-02-01 16:16:26 +00:00
Shish
891fb0478e 'PRAGMA foreign_keys' is SQLite-specific 2010-02-01 16:13:42 +00:00
Shish
8a26b4e9d1 make memcaching work for pages, and log if DEBUG is set 2010-02-01 16:11:54 +00:00
Zach Hall
cd931fb036 "Change e-mail" was missing in the danbooru theme. 2010-02-01 00:17:11 +00:00
Zach Hall
f26f5b2d89 CAPTCHA in Danbooru user signup form. 2010-01-28 13:35:43 +00:00
Shish
147075f539 fix pixel thumb gen 2010-01-27 12:25:47 +00:00
Shish
f8765de7a9 Turn the QR Code extension into a SimpleExtension, trim the boilerplate 2010-01-26 13:38:25 +00:00
Zach Hall
0ca0a546e5 QR Code extension 2010-01-26 13:35:55 +00:00
Shish
695a5a3a4b tag_history logging 2010-01-23 18:07:59 +00:00
Shish
35dccaf181 non-admins are allowed to see the extension list now (read-only) 2010-01-23 13:20:45 +00:00
Shish
e9b49a794f disable captchas when debugging from localhost 2010-01-23 12:48:54 +00:00
Shish
2f32024f71 put the captcha in the table 2010-01-23 12:40:15 +00:00
Shish
dc28be215b redundant declaration 2010-01-23 12:39:49 +00:00
Shish
93d3f471c4 make convert.exe findable on windows 2010-01-22 17:42:26 +00:00
Shish
cd150f1d6d strip metadata in a single imagemagick command 2010-01-21 16:11:56 +00:00
Shish
43b957d98a make make_http not break with '' as an argument 2010-01-18 08:29:48 +00:00
Shish
e0dd4b3a3a wider danbooru margin 2010-01-18 08:21:49 +00:00
Shish
d46421c538 dupe function name 2010-01-17 09:54:16 +00:00
Shish
8ec9ba0493 try to recognise the DB engine *before* trying to connect... 2010-01-17 09:43:59 +00:00
Shish
fbc06a1973 SQLite now supports foreign keys (3.6.19 onwards) 2010-01-17 09:42:52 +00:00
Shish
ba5f436cf3 documentation updates 2010-01-12 15:01:53 +00:00
Shish
cc8c8161d8 haaaack 2010-01-11 15:19:52 +00:00
Shish
270cdf2da5 Copyright notice updates 2010-01-11 15:11:05 +00:00
Shish
647e97ba86 nearly release yet? 2010-01-11 08:55:55 +00:00
Shish
fada3d8750 htmlable t&c 2010-01-05 19:06:45 +00:00
Shish
3c40ca8c47 set user creation to use the generic captcha api 2010-01-05 17:57:33 +00:00
Shish
02c9c7eced paginated image bans 2010-01-05 17:32:12 +00:00
Shish
618f739a59 more documentation, and set some extensions to be hidden from the user docs list 2010-01-05 13:18:01 +00:00
Shish
cab222c348 admin extension is no longer core 2010-01-05 11:08:46 +00:00
Shish
da4431e58d move image deletion function to ImageIO 2010-01-05 11:08:29 +00:00
Shish
10f6874164 better logging 2010-01-05 10:52:44 +00:00
Shish
d638dc4e53 Show all extensions in the extension list 2010-01-05 10:32:58 +00:00
Shish
02b7333eb8 lots of extension docs 2010-01-05 10:12:07 +00:00
Shish
f720a4e56b documentation 2010-01-05 10:11:31 +00:00
Shish
a84055a5e1 search by number of tags 2010-01-05 09:05:48 +00:00
Shish
fcf39e933b update extensions links 2010-01-04 12:44:34 +00:00
Shish
f496bc3a54 documentation updates 2010-01-04 12:42:01 +00:00
Shish
652cc475b1 give comments their own subnav 2010-01-04 11:52:28 +00:00
Shish
dbbcb51e2c for the danbooru subnav bar, assume that the front page = post/list 2010-01-04 11:25:22 +00:00
Shish
f0c18d3547 type checking for FavoriteSetEvent 2010-01-03 10:05:40 +00:00
Shish
fa3706243c style tips on danbooru 2010-01-03 09:57:53 +00:00
Shish
08320c5b3f add subheading support to all themes 2010-01-03 09:56:20 +00:00
Shish
8275222ae6 don't halt the page load while waiting for this 2010-01-03 09:51:20 +00:00
Shish
0633ad1f81 make the version string ungooglable 2010-01-03 09:43:48 +00:00
Shish
64eb907810 more danbooru updates 2010-01-03 09:42:45 +00:00
Shish
7c2d0473d2 and add the securimage library 2010-01-03 08:57:24 +00:00
Shish
3890fbb8ee updates for the danbooru theme 2010-01-03 08:56:03 +00:00
Shish
3026e9b2cc securimage support as an alternative captcha 2010-01-03 08:22:47 +00:00
Shish
73abe594d1 view and download featured 2010-01-02 10:17:17 +00:00
Shish
ec0d732a26 don't redirect-to-image if this is a single image on the last page of several pages 2009-12-30 09:29:49 +00:00
Shish
3b758e4075 email bbcode tag 2009-12-30 09:14:12 +00:00
Shish
3d8361ab71 preliminary image locking 2009-12-30 08:54:18 +00:00
Shish
97af29d7b6 make sure all tables are innodb, for foreign keys to work 2009-12-30 08:48:59 +00:00
Shish
0df9672063 argh whitespace 2009-12-30 08:25:03 +00:00
Shish
5c2515151a publicise Ratings functions because Pools wants to do the same thing 2009-12-30 08:24:19 +00:00
Shish
36812d085a allow authenticated find_posts in danbooru api 2009-12-30 08:22:03 +00:00
Shish
cc5a6a119a don't show explicit features to those who aren't allowed 2009-12-30 08:18:02 +00:00
Shish
035f8bda6e gah 2009-12-30 08:18:00 +00:00
Shish
eeee22863d pools extension 2009-12-30 08:11:38 +00:00
Shish
ddd91196d9 proper logging 2009-12-30 08:00:00 +00:00
Shish
d6280617b6 make danbooru aware of specific extensions 2009-12-30 07:01:28 +00:00
Shish
e7f3d015c6 Content-Length header for data pages 2009-12-26 01:01:03 +00:00
Shish
b42aa936d6 get the THUMB link... *headdesk* 2009-12-24 07:34:45 +00:00
Shish
c36a49edc4 into a clean folder 2009-12-24 07:08:22 +00:00
Shish
fafe81b258 double-bump 2009-12-24 06:58:05 +00:00
Shish
5813e23da9 ignore orphanned comments 2009-12-22 17:05:51 +00:00
Shish
11d686586d this breaks on windows? 2009-11-29 09:38:45 +00:00
Shish
e0584f6e3e dos2unix 2009-11-24 14:11:44 +00:00
Shish
7c946db89f how did nobody spot this? x_x 2009-11-20 11:52:26 +00:00
Shish
4b9f311ffd forgot to replace this 2009-11-15 12:09:40 +00:00
Shish
535bcc2a00 handle the case of the SQL log failing to open 2009-11-15 10:27:08 +00:00
Shish
3bc10bf64f fixed? 2009-11-12 09:30:52 +00:00
Shish
33dc03fb80 fail 2009-11-12 09:27:25 +00:00
Shish
6c1a218353 hash_ab and hash_cd templates for image URLs 2009-11-12 09:24:19 +00:00
Shish
831dc3449c sorted advanced options, easier to browse 2009-11-12 09:14:59 +00:00
Shish
180afca67a recaptcha signup link including domain 2009-11-10 04:36:23 +00:00
Shish
d70e00754f html tooltip for autodates 2009-11-10 03:50:53 +00:00
Shish
2b691408ed months and years for autodate 2009-11-10 03:36:51 +00:00
Shish
e0ab091d09 signup captcha 2009-11-10 03:21:19 +00:00
Shish
d91acbb03f this line is jarringly long... 2009-11-10 03:11:40 +00:00
Shish
0f34d28c33 better layout 2009-11-10 03:09:53 +00:00
Shish
92c8738747 remote api keys in their own block 2009-11-10 03:07:27 +00:00
Shish
5d88c4bff0 make recaptcha work 2009-11-10 03:01:42 +00:00
Shish
68df7b45e0 recaptcha support 2009-11-10 03:55:34 +00:00
Shish
9fb32d9f66 togglable setup blocks 2009-11-10 03:43:56 +00:00
Shish
fa404edb11 noscript link to a larger upload form 2009-10-26 12:09:08 +00:00
Shish
06f9ee1834 and remove the setting 2009-10-26 11:41:56 +00:00
Shish
33f6695e28 this setting is used before the database is connected to... 2009-10-26 11:40:27 +00:00
Shish
a5df66f6bc test the right thing... 2009-10-26 11:22:48 +00:00
Shish
e8861e4768 set comment limits higher 2009-10-26 11:22:45 +00:00
Shish
58febcd400 fix for bug #779 2009-10-23 14:52:09 +01:00
Shish
2ff8dbe54c test for bug #779 2009-10-23 14:52:08 +01:00
Shish
c6f940664f optional word wrapping 2009-10-18 00:33:28 +01:00
Shish
d46dca7fab avatar css 2009-10-10 01:53:50 +01:00
Shish
4a2297bd22 spelling 2009-10-09 13:30:33 +01:00
Shish
08bb4a7671 subheading section in the default theme 2009-10-09 12:30:18 +01:00
Shish
4a75b8a7a3 prefixed cookies 2009-10-09 12:29:28 +01:00
Shish
2f7565cbcb how did this work in the first place? x_x 2009-10-09 12:28:51 +01:00
Shish
1087409364 test to confirm bug #774 2009-10-09 12:27:55 +01:00
Shish
1ccf760709 not /all/ input elements should be 100% wide 2009-10-09 12:27:49 +01:00
Shish
f4d167809b a handy thing from the danbooru theme 2009-10-09 12:27:48 +01:00
Shish
a424b3632d truthomatic 2009-10-08 17:44:41 +01:00
Shish
eeaebd6e3a find truth 2009-10-08 17:44:08 +01:00
Shish
f718193449 add test case to confirm #761 2009-10-08 12:47:55 +01:00
Shish
e0fd446d84 configurability for avatars 2009-10-08 12:41:03 +01:00
Shish
3b2a05ac2d fix avatar display on user page 2009-10-08 12:32:28 +01:00
Shish
e18c8d27db User::get_avatar_html() 2009-10-08 02:59:16 +01:00
Shish
0d34c9b422 scaled category thumbnails 2009-10-08 02:52:03 +01:00
Shish
7b3cef4a9b easier to style quotes 2009-10-08 02:45:59 +01:00
Shish
49502e82c1 more testing 2009-09-27 15:48:57 +01:00
Shish
6af8fa8d72 make debugging and coverage separate settings 2009-09-27 14:04:38 +01:00
Shish
74b471c30a tips extension 2009-09-27 14:00:50 +01:00
Shish
3e9bf58335 wide textareas in tables too 2009-09-27 12:35:16 +01:00
Shish
d49e16ed9d more testes 2009-09-20 04:10:46 +01:00
Shish
efec93833a test that comments work in the comment RSS 2009-09-20 03:59:03 +01:00
Shish
8a2914ca74 test that this doesn't crash 2009-09-20 03:53:36 +01:00
Shish
8966cc6345 another test 2009-09-20 03:50:41 +01:00
Shish
c83f872723 nobody should be linking to pages like this any more... 2009-09-20 03:49:02 +01:00
Shish
a32c7997b4 not finished, but add to test coverage 2009-09-20 03:48:34 +01:00
Shish
c3f5ca032b test blank lines 2009-09-20 03:47:56 +01:00
Shish
8f17ef521c this isn't slow any more 2009-09-19 23:08:27 +01:00
Shish
5f7cba6940 more testing 2009-09-19 23:08:26 +01:00
Shish
2e3967bcb1 code coverage stuff 2009-09-19 20:25:12 +01:00
Shish
223f0cc4eb test spoiler code 2009-09-19 20:24:45 +01:00
Shish
b44f2919db test the next and prev buttons 2009-09-19 20:24:42 +01:00
Shish
486a3ae08e test the emoticon list 2009-09-19 20:24:39 +01:00
Shish
da46e000fb ignore more stuff 2009-09-19 15:22:27 +01:00
Shish
bd71a71020 link to target of alias 2009-09-15 18:43:15 +01:00
Shish
e2cb8f8cb0 don't code while asleep -_- 2009-09-15 18:35:09 +01:00
Shish
2ba033de32 configurable autodate format 2009-09-15 18:30:10 +01:00
Shish
a47bc67c50 split() is deprecated in php5.3 2009-09-14 21:19:35 +01:00
Shish
016f62dda4 extra unit testing note 2009-08-25 18:40:00 +01:00
Shish
b9fabb5d8a paginated aliases 2009-08-25 02:36:18 +01:00
Shish
fc6ed22988 meta keywords with commas 2009-08-24 06:50:43 +01:00
Shish
fd619d15d7 remove the whitespace 2009-08-24 05:34:14 +01:00
Shish
4a4bc1a37a allow themes to override this function 2009-08-24 03:01:03 +01:00
Shish
362891b4ed zebra styling 2009-08-24 03:57:34 +01:00
Shish
80e450ff0f odd/even comments 2009-08-24 03:43:20 +01:00
Shish
9f9986970e ignore duplicate tag history entries 2009-08-24 03:34:14 +01:00
Shish
283b2e974f bleh 2009-08-24 01:56:45 +01:00
Shish
ad772677a8 home page updates 2009-08-24 02:16:15 +01:00
Shish
e4f987c7ab extension documentation updates 2009-08-20 23:37:17 +01:00
Shish
695352ad16 correct docs 2009-08-20 20:51:40 +01:00
Shish
046fb2c33a did I mention 'aaaaaaargh php'? 2009-08-19 05:53:38 +01:00
Shish
ca97036735 sometimes these return values are used 2009-08-19 05:04:32 +01:00
Shish
fc28b7a663 this does need to be at the top, but not double star started... 2009-08-19 04:57:42 +01:00
Shish
cc07077621 more testing docs 2009-08-19 02:13:58 +01:00
Shish
81afbb30be consistent naming for test functions 2009-08-19 01:34:55 +01:00
Shish
0c9bd105cc only show the recent few comments on each image (for the long comment list) 2009-08-19 01:30:01 +01:00
Shish
45f6f89aed a start on testing docs, and some underscore-camelcase maps 2009-08-19 01:26:37 +01:00
Shish
35dd7fdf11 tags as page keywords 2009-08-19 00:51:55 +01:00
Shish
0ed1db7838 use_autodate option 2009-08-18 23:08:11 +01:00
Shish
7fcf6c8de1 zebraify reported images table 2009-08-18 22:42:37 +01:00
Shish
3677bcd793 update other themes 2009-08-18 22:33:24 +01:00
Shish
64d8b52b7b extendable user stats rather than hardcoded with assumptions of extensions 2009-08-18 22:31:28 +01:00
Shish
1c81ee280a fix the tests.. 2009-08-16 20:19:45 +01:00
Shish
9aa87ba8ea minmal rating tests 2009-08-13 21:43:06 +01:00
Shish
36f1fbc8bc don't force the user to set a rating 2009-08-13 20:11:05 +01:00
Shish
9ca3e6e03f danbooru theme updates by zshall <http://seemslegit.com> 2009-08-13 19:41:35 +01:00
Shish
49030ecffd only cache gets 2009-08-13 19:29:38 +01:00
Shish
e694d69eb6 test it 2009-08-13 19:27:20 +01:00
Shish
8ad176ade5 replace numeric_score 'favorite' with 'upvoted_by=name' (and add the same for downvote) 2009-08-13 19:26:23 +01:00
Shish
eacc3af131 fix this test 2009-08-13 19:01:23 +01:00
Shish
40f04677d0 test searching by score 2009-08-13 17:48:50 +01:00
Shish
2e63812024 test deleting one comment at a time 2009-08-12 15:45:38 +01:00
Shish
2661e26d81 make comment deletion do something... 2009-08-12 15:40:27 +01:00
Shish
00b66e383c missed a spot 2009-08-12 15:35:52 +01:00
Shish
b13ccccd69 settable email field 2009-08-11 17:09:56 +01:00
Shish
ea03506471 gravatar on user page 2009-08-11 15:43:51 +01:00
Shish
cc3af2b76f default to showing user links in danbooru submenu 2009-08-11 15:38:11 +01:00
Shish
dd5ffa26ef slightly less strict filename sanitising, for the unicode users 2009-08-11 15:13:20 +01:00
Shish
ae0498e4f0 missed a spot 2009-08-10 21:51:34 +01:00
Shish
612cb3768b if hit on disk, reload into memcache 2009-08-10 13:34:19 +01:00
Shish
e9ab06e1e9 make image metadata optional 2009-08-09 22:01:28 +01:00
Shish
88eecb5a6b better caching code 2009-08-09 13:14:27 +01:00
Shish
dca588450b load config.php sooner 2009-08-09 13:14:25 +01:00
Shish
8b29aa80fe emoticon list 2009-08-08 17:43:40 +01:00
Shish
994fa2a9b6 fix comment page count 2009-08-04 17:58:41 +01:00
Shish
fa30a08246 make user email accessable to comment themers 2009-08-04 17:52:00 +01:00
Shish
f83b8b473e this went in the wrong theme 2009-08-04 17:49:00 +01:00
Shish
bdd0299cf0 comment theme API changes 2009-08-04 17:45:42 +01:00
Shish
bc706fde05 ignore more 2009-08-04 17:19:21 +01:00
Shish
374a85e545 remove minimal theme, it is full of ugly 2009-08-04 17:18:21 +01:00
Shish
0528d0a765 better looking downtime message, and login box 2009-08-03 19:58:12 +01:00
Shish
be3e582de6 sitemap test file 2009-08-03 18:35:49 +01:00
Shish
8e00495f7e sitemap extension from Sein Kraft 2009-08-03 18:25:51 +01:00
Shish
c2c7e245d8 case 2009-08-03 12:08:06 +01:00
Shish
047166a92c bbcode img tag support, and unwrap wordwrapped urls 2009-08-03 12:37:35 +01:00
Shish
b1407028b1 wiki diff on conflict 2009-08-03 11:23:49 +01:00
Shish
2eeb0b1caf wiki updates 2009-08-03 11:04:43 +01:00
Shish
b2c2368cfc themable user links 2009-08-03 10:47:09 +01:00
Shish
6322589265 only show uploader to admins 2009-08-03 10:24:48 +01:00
Shish
83d92c6413 bulk upload / zip upload betterness 2009-08-03 10:19:33 +01:00
Shish
80cb9d0083 search images by comment metadata 2009-08-03 10:18:40 +01:00
Shish
632bf99261 xspf player is bsd, and why did we have the full size version instead of slim? 2009-08-02 14:44:51 +01:00
Shish
33fbc52862 glob rather than readdir, sorts too 2009-08-02 09:36:27 +01:00
Shish
f12c963ee0 version bump 2009-08-02 09:28:28 +01:00
Shish
5a8bcad72c any search for a rating will override the default 2009-08-02 09:24:55 +01:00
Shish
f44634b67d update this too 2009-08-02 09:10:43 +01:00
Shish
8f7041f1d7 everything else only has add boxes at the bottom 2009-08-02 09:08:28 +01:00
Shish
1b63cffd2d test that too 2009-08-02 08:59:54 +01:00
Shish
50acd8c6ad Merge branch 'branch_2.3' of ssh://shish@marigold.shishnet.org/home/shish/git/shimmie2 into branch_2.3 2009-08-02 08:57:42 +01:00
Shish
c6636265cb Merge branch 'branch_2.3' of ssh://marigold.shishnet.org/git/shimmie2 into branch_2.3 2009-08-02 09:47:34 +01:00
Shish
c525be6b31 too much search & replace 2009-08-02 09:29:06 +01:00
Shish
02ea593091 also allow search by 'md5=' 2009-08-02 08:57:30 +01:00
Shish
98fa324786 search for ratings with full words 2009-08-02 08:43:13 +01:00
Shish
613be41240 this needs autoblanking too 2009-08-02 08:33:33 +01:00
Shish
008709af26 bulk image rater 2009-08-02 08:20:07 +01:00
Shish
c2a56728d6 crushed pngs 2009-08-01 09:16:26 +01:00
Shish
aaa379930b Revert "load scripts at the bottom of the page" - inline scripts require jquery >_<
This reverts commit efe4852dc4be1e010d204bdc1fa221c6440c50d7.
2009-08-01 09:04:40 +01:00
Shish
0d2c597010 not in this theme 2009-08-01 09:00:06 +01:00
Shish
efe4852dc4 load scripts at the bottom of the page 2009-08-01 08:58:20 +01:00
Shish
9575a06c68 compressed cached pages 2009-08-01 08:57:50 +01:00
Shish
85e65075e6 off by default 2009-08-01 02:32:28 +01:00
Shish
57d8a8bfd6 static file caching 2009-08-01 02:32:07 +01:00
Shish
0e4eac909c give a better label to flash things 2009-07-30 05:12:36 +01:00
Shish
9913937a25 bring back X blah per day 2009-07-30 03:48:16 +01:00
Shish
476d2a33c7 make search by favourite count work 2009-07-30 03:27:40 +01:00
Shish
2758b13601 make autodate work, and use it a bit 2009-07-28 23:56:56 +01:00
Shish
b1dbb3a792 long line splitter 2009-07-28 23:42:55 +01:00
Shish
4cc77668e6 link to help 2009-07-28 23:21:59 +01:00
Shish
0563d3fb1f danbooru theme updates 2009-07-28 23:07:21 +01:00
Shish
450102233a link to site needs httpisation too 2009-07-28 21:13:17 +01:00
Shish
353970a902 favorites testing and tweak 2009-07-28 11:45:21 +01:00
Shish
e29db93834 make favorites work with 2.3 2009-07-28 11:22:13 +01:00
Shish
7336ff2b22 initial import of favorites extension 2009-07-28 11:22:08 +01:00
Shish
f8feecf5e2 not only for admins 2009-07-28 11:18:24 +01:00
Shish
8fd6d44958 a ton of wiki improvements 2009-07-28 09:40:37 +01:00
Shish
a69bf05e36 a bunch of wiki fixes 2009-07-28 04:49:24 +01:00
Shish
fdd1ffaf0f make testing work again... 2009-07-28 04:45:50 +01:00
Shish
66ef82e460 wiki updates (broken atm) 2009-07-28 02:07:07 +01:00
Shish
56072075f7 escape it 2009-07-28 01:32:18 +01:00
Shish
adffa0f18f show wiki page title 2009-07-28 01:30:41 +01:00
Shish
8680f27bf1 add label for tag_history 2009-07-28 01:25:49 +01:00
Shish
1052b5d356 mysql fails at count() = 0 2009-07-28 01:20:41 +01:00
Shish
8a64e171b7 updateses 2009-07-28 01:09:49 +01:00
Shish
cc9cefe41e somewhat updated danbooru comment theme 2009-07-28 01:09:44 +01:00
Shish
2c5a688335 exif ifd0 info in the sidebar 2009-07-28 00:22:02 +01:00
Shish
fc7fd8d1f7 here too 2009-07-24 08:10:23 +01:00
Shish
fd85f390e5 make_http function 2009-07-24 08:10:11 +01:00
Shish
2b4733bf06 site description keywords 2009-07-24 04:36:05 +01:00
Shish
ce76dfbb2b show tag count, rather than calculated count 2009-07-24 03:07:25 +01:00
Shish
58f2ba539a new theme 2009-07-23 02:33:21 +01:00
Shish
88c7233ced Merge branch 'branch_2.3' of ssh://shish@marigold.shishnet.org/home/shish/git/shimmie2 into branch_2.3 2009-07-22 22:09:28 +01:00
Shish
cff145d72b one transparent circle, rather than a colour per rect 2009-07-22 22:09:05 +01:00
Shish
6f2711f422 custom page is custom 2009-07-21 07:40:00 +01:00
Shish
67f3ce0543 Merge branch 'branch_2.3' of ssh://marigold.shishnet.org/git/shimmie2 into branch_2.3 2009-07-21 07:36:27 +01:00
Shish
f712147553 lots of docs, and some internal changes 2009-07-21 07:36:12 +01:00
Shish
c31342a1c7 bump 2009-07-21 05:08:23 +01:00
Shish
f27fa1f314 docs 2009-07-21 04:18:40 +01:00
Shish
05449ef8d3 more tests... 2009-07-20 07:31:41 +01:00
Shish
2cd3b0812b MOAR TESTING. 2009-07-20 06:51:36 +01:00
Shish
8b0d1dc3db hide user stats from anonymous 2009-07-20 06:42:09 +01:00
Shish
a348e3df29 disk stats for et 2009-07-20 04:27:21 +01:00
Shish
9a1de36a23 more tests, and make link to image work better 2009-07-19 19:35:46 +01:00
Shish
281638c295 use the current favicon rather than a hardcoded one 2009-07-19 18:04:52 +01:00
Shish
487a59b0db more tests, more fixes 2009-07-19 17:59:57 +01:00
Shish
bec162293e more tests and fixes 2009-07-19 17:39:07 +01:00
Shish
3ebf861441 someone else is defining these... 2009-07-19 17:21:49 +01:00
Shish
d9ec5056d4 res_limit testing and fixing 2009-07-19 17:18:06 +01:00
Shish
16fe1424dd bug fixing & tidying 2009-07-19 08:56:24 +01:00
Shish
96ce9c70a7 lots of docs 2009-07-19 08:38:13 +01:00
Shish
cdeacb9765 make images in the nav bar break less 2009-07-19 07:32:56 +01:00
Shish
96352152b2 make the featured image extension work again 2009-07-19 07:28:54 +01:00
Shish
eda3b0aa61 a load more tests 2009-07-19 04:48:25 +01:00
Shish
2da1cd8c9d Merge branch 'branch_2.3' of ssh://marigold.shishnet.org/git/shimmie2 into branch_2.3 2009-07-19 01:54:19 +01:00
Shish
9c6d0dc394 more testing 2009-07-19 01:29:59 +01:00
Shish
8cd6dbc2a0 allow people to view images rated 'unknown'... 2009-07-19 01:29:48 +01:00
Shish
af8c59fcd1 version bump 2009-07-17 03:00:04 +01:00
Shish
4148106ff0 sqlite compat for ipbans 2009-07-17 02:43:57 +01:00
Shish
9724b13f81 merge both log functions into one; simply saying that they take different numbers of arguments isn't enough... 2009-07-17 01:55:07 +01:00
Shish
2082334c24 Merge branch 'branch_2.3' of ssh://marigold.shishnet.org/git/shimmie2 into branch_2.3 2009-07-17 00:36:50 +01:00
Shish
8230a36565 save some bandwidth by removing a load of decimal places 2009-07-17 00:35:52 +01:00
Shish
e81c3e288d sqlite fails at count distinct 2009-07-17 00:28:07 +01:00
Shish
c9f743bf8f sqlite returns column names as table.name, not just name 2009-07-17 00:27:40 +01:00
Shish
c917bf0434 only hidablise relevant things, and make the whole block clickable 2009-07-16 21:05:00 +01:00
Shish
cb56587970 make sure 0 is printed 2009-07-16 20:56:39 +01:00
Shish
7a3e032096 rename an ambiguous column 2009-07-16 20:51:35 +01:00
Shish
5922fb6f25 Merge branch 'branch_2.3' of ssh://marigold.shishnet.org/git/shimmie2 into branch_2.3 2009-07-16 20:44:20 +01:00
Shish
bf2fdffc75 re-initialise the database after re-opening the connection (allows sqlite to use now()) 2009-07-16 20:44:04 +01:00
Shish
75d4c6cf73 yet more tests 2009-07-16 20:21:49 +01:00
Shish
d08b7faf63 more tests 2009-07-16 20:20:53 +01:00
Shish
d7ce9add6a aaaaaaaargh dates and times in sql 2009-07-15 23:29:14 +01:00
Shish
9d968b630d more reliable self finding 2009-07-15 23:29:01 +01:00
Shish
4f14197577 remove the auto install file after using it 2009-07-15 22:20:10 +01:00
Shish
179e18abb2 warn about missing mysql 2009-07-15 22:17:53 +01:00
Shish
9483cc59f3 actually that doesn't work v_v 2009-07-15 19:07:09 +01:00
Shish
ead9e27dea more tests 2009-07-15 17:09:50 +01:00
Shish
d2cad188b4 A ton of tests, figured out how to test uploads \o/ 2009-07-15 02:42:18 +01:00
Shish Moom
f59e39d42b minor fix 2009-07-14 13:10:16 -07:00
Shish Moom
819fb722c3 store arrays in config, edit in setup with add_multichoice_option 2009-07-11 04:44:08 -07:00
Shish Moom
412b71c2d3 margiiiiin~ 2009-07-09 12:51:00 -07:00
Shish Moom
4ac62e5544 tweaks for fresh install 2009-07-07 08:52:09 -07:00
Shish Moom
6b88e5fd5a .htaccess updates, expires and compression headers 2009-07-07 08:33:42 -07:00
Shish Moom
5c1093bcf0 no more trac 2009-07-07 08:08:19 -07:00
Shish Moom
fe09b18832 contact details 2009-07-07 07:56:06 -07:00
Shish Moom
ac6812d184 bump 2009-07-07 07:51:05 -07:00
Shish Moom
b9d80f8d38 get rid of shimmie's JS lib, use jquery 2009-07-07 07:50:09 -07:00
Shish Moom
8db522b916 remove table border 2009-07-07 07:50:05 -07:00
Shish Moom
ae85d76f15 auto-include library scripts 2009-07-07 07:49:58 -07:00
Shish Moom
2ad2689d28 jquery start 2009-07-07 07:49:54 -07:00
Shish Moom
20ed4f1503 more theme niceness 2009-07-06 14:24:02 -07:00
Shish Moom
99534afe08 feature! 2009-07-06 05:09:33 -07:00
Shish Moom
4fbd714930 smaller font for setup boxes 2009-07-06 05:08:11 -07:00
Shish Moom
c953d5bd6c more niceness 2009-07-06 04:58:43 -07:00
Shish Moom
9a830edbb7 hide the whole block 2009-07-06 04:58:35 -07:00
Shish Moom
784296d22c textarea tweak 2009-07-06 04:58:31 -07:00
Shish Moom
2644eed5bc new default theme 2009-07-06 04:58:27 -07:00
Shish Moom
ac0dad4093 move old default 2009-07-06 04:58:23 -07:00
Shish Moom
8af3c4f24e theme changes 2009-07-06 04:58:14 -07:00
Shish Moom
f8ec3baeea User::by_list from trunk 2009-07-01 16:55:49 -07:00
Shish Moom
ab1d755a01 another simpleextnsion 2009-07-01 16:52:48 -07:00
Shish Moom
351c3469dc regen_thumb still had event->page, and version bump 2009-07-01 16:45:06 -07:00
Shish Moom
7d827d3392 no need for hostname 2009-07-01 05:17:31 -07:00
Shish
ef5d7474c5 don't break when base_href='' 2009-06-29 19:03:14 -07:00
Shish Moom
ca675fcbb0 fix login... 2009-06-06 12:08:19 -07:00
Shish Moom
d240a29aa9 basic user list 2009-06-06 12:07:44 -07:00
Shish Moom
b6a4911b67 allow null as navigation 2009-06-06 12:01:33 -07:00
Erik Youngren
91829cc2ea You know that feeling you've done something the hard way? Yeah. Replacing theme_redirect with a three line function in core/utils.inc.php 2009-06-06 06:09:47 -07:00
Shish Moom
43c98e51c2 array_contains -> in_array 2009-06-05 12:54:23 -07:00
Shish Moom
5c88b84378 update links 2009-06-05 09:57:22 -07:00
Shish Moom
3da11198af wiki fixes 2009-05-30 06:50:24 -07:00
Shish
18f9a72851 explanation and credit 2009-05-15 01:53:15 -07:00
Shish
de7f1f9cfe forum is broken >_< 2009-05-12 06:00:33 -07:00
JJS
06cfda990d Fix bug in Source: link in danbooru theme 2009-05-12 03:12:48 -07:00
JJS
dd60840e4b Bugfix: $c->get_string to $config->get_string 2009-05-12 03:11:29 -07:00
Shish
5ab092946a make things use SimpleExtension 2009-05-11 14:10:15 -07:00
Shish
e29d475dea SimpleExtension, like Extension but with more Magic 2009-05-11 14:10:09 -07:00
Shish
0ac787c3e3 allow this to be turned on via config 2009-05-11 14:10:01 -07:00
Shish
aeba6665f0 a couple of fixes 2009-05-11 07:48:43 -07:00
Shish
74a0bc642e typo 2009-05-11 07:13:49 -07:00
Shish
2bdfec3e76 merge changes from master 2009-05-11 07:10:18 -07:00
Shish
7fcaf851f1 a couple of changes from master 2009-05-11 03:46:18 -07:00
Shish
bc23362bda more 2009-05-11 03:42:54 -07:00
Shish
74655bac27 comment out example thingy 2009-05-11 03:42:44 -07:00
Shish
5384e11735 punctuations 2009-05-11 03:42:36 -07:00
Shish
509ea8614c no need for formatting 2009-05-11 03:42:31 -07:00
Shish
a1cc9d1953 newline 2009-05-11 03:42:24 -07:00
Shish
70173468a5 logging things 2009-05-11 03:42:18 -07:00
Shish
7229b2669a php has a constant for this 2009-05-11 03:37:30 -07:00
Shish
482e22c90e static 2009-05-11 03:32:53 -07:00
Shish
40b2a4cb05 fixes for mysql 2009-05-09 05:40:26 -07:00
Shish
52cc6c95db search term may expand into several querylets 2009-05-09 05:37:50 -07:00
Shish
d525fc6894 missed a spot 2009-05-09 04:57:38 -07:00
Shish
4bdd544ff1 space 2009-05-09 01:47:02 -07:00
Shish
47bc2570b6 fix in 2.3 too 2009-05-09 01:31:19 -07:00
Shish
09b1bb3554 separate query path for mysql, because it chokes on subqueries 2009-05-08 07:44:23 -07:00
Shish
8512f45c9d note favourite search 2009-05-08 04:49:47 -07:00
Shish
8b207b84fe only search for prev/next when asked for 2009-05-08 04:48:21 -07:00
Shish
25e77bb7c1 search for upvoted images 2009-05-08 04:48:13 -07:00
Shish
d15e8d231f for searches with a single result, view the result 2009-05-08 04:47:57 -07:00
Shish
dfc7411975 Merge commit 'origin/master' into branch_2.3 2009-01-25 09:58:56 -08:00
Shish
a543f75c99 basic word filter test 2009-01-25 12:42:37 +00:00
Shish
bb38904cdf call assert() from the right place 2009-01-25 12:42:21 +00:00
Shish
fbd7039ad3 preliminary hash ban testing 2009-01-25 12:37:45 +00:00
Shish
963535b748 more bbcode tests 2009-01-25 12:26:07 +00:00
Shish
b49bc84c51 move shimmie-specific sqlite things out of adodb and into shimmie's database engine 2009-01-25 12:10:10 +00:00
Shish
6d8ac0b4e2 more functions that sqlite needs 2009-01-25 11:49:58 +00:00
Shish
275e0b3b64 remove in-development extensions 2009-01-25 11:49:42 +00:00
Shish
cca47a1df9 new feature list 2009-01-25 11:47:07 +00:00
Shish
26e1383b0a tag_explode -> Tag::explode 2009-01-24 11:05:07 -08:00
Shish
199a0d709c fixes 2009-01-24 11:01:23 -08:00
302 changed files with 20207 additions and 9616 deletions

17
.gitignore vendored
View File

@ -2,9 +2,13 @@
config.php
images
thumbs
data
sql.log
shimmie.log
ext/admin
ext/autocomplete
ext/ban_words
ext/blotter
ext/browser_search
ext/bulk_add
ext/danbooru_api
@ -12,7 +16,9 @@ ext/downtime
ext/emoticons
ext/et
ext/event_log
ext/favorites
ext/featured
ext/forum
ext/handle_archive
ext/handle_flash
ext/handle_ico
@ -22,11 +28,15 @@ ext/home
ext/image_hash_ban
ext/ipban
ext/link_image
ext/log_db
ext/news
ext/notes
ext/notes
ext/numeric_score
ext/piclens
ext/pm
ext/pools
ext/qr_code
ext/random_image
ext/rating
ext/regen_thumb
@ -34,12 +44,17 @@ ext/report_image
ext/res_limit
ext/rss_comments
ext/rss_images
ext/shimmie_api
ext/simpletest
ext/site_description
ext/sitemap
ext/svn_update
ext/tag_history
ext/tagger
ext/tag_history
ext/text_score
ext/tips
ext/amazon_s3
ext/upload_cmd
ext/wiki
ext/word_filter
ext/zoom

View File

@ -1,23 +1,23 @@
<IfModule mod_dir.c>
DirectoryIndex index.php5 index.php
DirectoryIndex index.php5 index.php
</IfModule>
<FilesMatch "\.(sqlite|sdb|s3db|db)$">
Deny from all
Deny from all
</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]
# 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]
# 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]
# 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>
@ -27,3 +27,15 @@ RewriteRule ^(.*)$ index.php?q=$1&%{QUERY_STRING} [L]
</IfModule>
DefaultType image/jpeg
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 month"
ExpiresByType text/html "now"
ExpiresByType text/plain "now"
</IfModule>
<ifmodule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/x-javascript application/javascript
</ifmodule>

View File

@ -6,14 +6,52 @@
/_______ /|___| /__|__|_| /__|_| /__|\___ >_______ \
\/ \/ \/ \/ \/ \/
Shimmie Alpha
~~~~~~~~~~~~~
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.
Shimmie 2.3
~~~~~~~~~~~
1000 days since the first import was converted to git \o/
If there is a feature here, and not in the stable branch, that's probably
because the feature doesn't work yet :P
And over a year since the last non-beta release x_x
New since 2.2
~~~~~~~~~~~~~
For admins:
o) Simplified installer, only one question
o) Improved mass tag editor, allows searching and replacing multiple tags
o) Automated installation
o) Automatic detection of niceurl support
o) Support for caching data, to lower database load
o) Improved extension management, built-in documentation function
o) Built-in documentation for extensions
o) Better spam filtering
o) CAPTCHA tests for signup and anonymous comments
o) Detailed event logging
o) Delete by query
For users:
o) New default theme
o) Better BBCode support
o) Image ratings, with eg. hiding of explicit images to anonymous users
o) Theme improvements, new themes
o) Better searching
o) Cooliris support
o) Search for images you've voted for (simple "favourites" system)
o) Gravatar support
o) Nicer date formatting
o) Random image extension
o) The current featured image is linkable
o) Private message system
o) Only the most recent posts for each image shown on the comment list
o) Image pools extension
o) Tagger cloud
For developers:
o) More sensible APIs
o) Automated testing
o) Preliminary SQLite & Postgres support, for people with very small and
very large sites (Not all extensions are compatible yet though)
And much more that I can't remember \o/
Requirements
@ -41,17 +79,32 @@ Installation
Upgrade from 2.2.X
~~~~~~~~~~~~~~~~~~
Should be automatic, just unzip and copy across config.php, images and thumbs
folders from the old version. This includes automatically messing with the
database -- back it up first!
Should be automatic, just unzip into a clean folder and copy across
config.php, images and thumbs folders from the old version. This
includes automatically messing with the database -- back it up first!
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.
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
~~~~~~~
http://forum.shishnet.org/viewforum.php?f=6 -- discussion forum
http://trac.shishnet.org/shimmie2/ -- bug tracker
webmaster at shishnet.org -- email
#shimmie on Freenode -- IRC
webmaster at shishnet.org -- email
https://github.com/shish/shimmie2/issues -- bug tracker
Licence

View File

@ -1,6 +1,31 @@
<?php
/* AdminBuildingEvent {{{
*
/**
* 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:
* <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
* <p>Purge unused tags:
* <br>Get rid of all the tags that don't have any images associated with
* them (normally they were created as typos or spam); this is mostly for
* neatness, the performance gain is tiny...
* <p>Convert to InnoDB:
* <br>Convert your database tables to InnoDB, thus allowing shimmie to
* take advantage of useful InnoDB-only features (this should be done
* automatically, this button only exists as a backup). This only applies
* to MySQL -- all other databases come with useful features enabled
* as standard.
* <p>Database dump:
* <br>Download the contents of the database in plain text format, useful
* for backups.
*/
/**
* Sent when the admin page is ready to be added to
*/
class AdminBuildingEvent extends Event {
@ -9,7 +34,6 @@ class AdminBuildingEvent extends Event {
$this->page = $page;
}
}
// }}}
class AdminPage implements Extension {
var $theme;
@ -23,30 +47,21 @@ class AdminPage implements Extension {
$this->theme->display_permission_denied($page);
}
else {
if($event->get_arg(0) == "delete_image") {
// FIXME: missing lots of else {complain}
if(isset($_POST['image_id'])) {
$image = Image::by_id($_POST['image_id']);
if($image) {
send_event(new ImageDeletionEvent($image));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list"));
}
}
}
else {
send_event(new AdminBuildingEvent($page));
}
send_event(new AdminBuildingEvent($page));
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("admin_utils")) {
if($user->is_admin()) {
if($user->is_admin() && $user->check_auth_token()) {
log_info("admin", "Util: {$_POST['action']}");
set_time_limit(0);
$redirect = false;
switch($_POST['action']) {
case 'delete by query':
$this->delete_by_query($_POST['query']);
$redirect = true;
break;
case 'lowercase all tags':
$this->lowercase_all_tags();
$redirect = true;
@ -59,6 +74,10 @@ class AdminPage implements Extension {
$this->purge_unused_tags();
$redirect = true;
break;
case 'convert to innodb':
$this->convert_to_innodb();
$redirect = true;
break;
case 'database dump':
$this->dbdump($page);
break;
@ -71,12 +90,6 @@ class AdminPage implements Extension {
}
}
if($event instanceof ImageAdminBlockBuildingEvent) {
if($user->is_admin()) {
$event->add_part($this->theme->get_deleter_html($event->image->id));
}
}
if($event instanceof AdminBuildingEvent) {
$this->theme->display_page($page);
$this->theme->display_form($page);
@ -89,6 +102,14 @@ class AdminPage implements Extension {
}
}
private function delete_by_query($query) {
global $page, $user;
assert(strlen($query) > 1);
foreach(Image::find_images(0, 1000000, Tag::explode($query)) as $image) {
send_event(new ImageDeletionEvent($image));
}
}
private function lowercase_all_tags() {
global $database;
$database->execute("UPDATE tags SET tag=lower(tag)");
@ -96,7 +117,12 @@ class AdminPage implements Extension {
private function recount_tag_use() {
global $database;
$database->Execute("UPDATE tags SET count=(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id)");
$database->Execute("
UPDATE tags
SET count = COALESCE(
(SELECT COUNT(image_id) FROM image_tags WHERE tag_id=tags.id GROUP BY tag_id),
0
)");
}
private function purge_unused_tags() {
@ -139,6 +165,17 @@ class AdminPage implements Extension {
}
}
}
private function convert_to_innodb() {
global $database;
if($database->engine->name == "mysql") {
$tables = $database->db->MetaTables();
foreach($tables as $table) {
log_info("upgrade", "converting $table to innodb");
$database->execute("ALTER TABLE $table TYPE=INNODB");
}
}
}
}
add_event_listener(new AdminPage());
?>

75
contrib/admin/test.php Normal file
View File

@ -0,0 +1,75 @@
<?php
class AdminPageTest extends ShimmieWebTestCase {
function testAuth() {
$this->get_page('admin');
$this->assert_response(403);
$this->assert_title("Permission Denied");
$this->log_in_as_user();
$this->get_page('admin');
$this->assert_response(403);
$this->assert_title("Permission Denied");
$this->log_out();
}
function testLowercase() {
$ts = time(); // we need a tag that hasn't been used before
$this->log_in_as_admin();
$image_id_1 = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "TeStCase$ts");
$this->get_page("post/view/$image_id_1");
$this->assert_title("Image $image_id_1: TeStCase$ts");
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field("action", "lowercase all tags");
$this->click("Go");
$this->log_out();
$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();
}
# 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->set_field("action", "recount tag use");
$this->click("Go");
$this->log_out();
}
function testPurge() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field("action", "purge unused tags");
$this->click("Go");
$this->log_out();
}
function testConvert() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field("action", "convert to inodb");
$this->click("Go");
$this->log_out();
}
function testDump() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field("action", "database dump");
$this->click("Go");
$this->log_out();
}
}
?>

View File

@ -10,22 +10,6 @@ class AdminPageTheme extends Themelet {
$page->add_block(new NavBlock());
}
/*
* Display a link to delete an image
*
* $image_id = the image to delete
*/
public function get_deleter_html($image_id) {
$i_image_id = int_escape($image_id);
$html = "
<form action='".make_link("admin/delete_image")."' method='POST'>
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='submit' value='Delete'>
</form>
";
return $html;
}
/*
* Show a form which links to admin_utils with POST[action] set to one of:
* 'lowercase all tags'
@ -33,18 +17,30 @@ class AdminPageTheme extends Themelet {
* 'purge unused tags'
*/
public function display_form(Page $page) {
global $user;
$html = "
<p><form action='".make_link("admin_utils")."' method='POST'>
".make_form(make_link("admin_utils"))."
<select name='action'>
<option value='lowercase all tags'>All tags to lowercase</option>
<option value='recount tag use'>Recount tag use</option>
<option value='purge unused tags'>Purge unused tags</option>
<option value='database dump'>Download database contents</option>
<option value='convert to innodb'>Convert database to InnoDB (MySQL only)</option>
</select>
<input type='submit' value='Go'>
</form>
";
$page->add_block(new Block("Misc Admin Tools", $html));
$html = "
".make_form(make_link("admin_utils"))."
<input type='hidden' name='action' value='delete by query'>
<input type='text' name='query'>
<input type='submit' value='Go'>
</form>
";
$page->add_block(new Block("Delete by Query", $html));
}
}
?>

View File

@ -0,0 +1,75 @@
<?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 SimpleExtension {
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" => "image/" . $event->image->type,
"Content-Disposition" => "inline; filename=image-" . $event->image->id . "." . $event->image->type,
)
);
}
}
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);
}
}
}
?>

View File

@ -1,31 +0,0 @@
<?php
/**
* Name: Autocomplete
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Auto-complete for search and upload tags
* Documentation:
* Just enable and things should start autocompleting as if
* by magic. That is, if this extension actually worked...
*/
class AutoComplete implements Extension {
public function receive_event(Event $event) {
if(($event instanceof PageRequestEvent) && ($event->page_matches("index") || $event->page_matches("view"))) {
$event->page->add_header("<script>autocomplete_url='".html_escape(make_link("autocomplete"))."';</script>");
}
if(($event instanceof PageRequestEvent) && $event->page_matches("autocomplete")) {
$event->page->set_mode("data");
$event->page->set_type("text/plain");
$event->page->set_data($this->get_completions($event->get_arg(0)));
}
}
private function get_completions($start) {
global $database;
$tags = $database->db->GetCol("SELECT tag,count FROM tags WHERE tag LIKE ? ORDER BY count DESC", array($start.'%'));
return implode("\n", $tags);
}
}
add_event_listener(new AutoComplete());
?>

View File

@ -1,98 +0,0 @@
// addEvent(window, "load", function() {
// initAjax("searchBox", "search_completions");
// initAjax("tagBox", "upload_completions");
// });
function endWord(sentance) {
words = sentance.split(" ");
return words[words.length-1];
}
var resultCache = new Array();
resultCache[""] = new Array();
function complete(boxname, text) {
box = byId(boxname);
words = box.value.split(" ");
box.value = "";
for(n=0; n<words.length-1; n++) {
box.value += words[n]+" ";
}
box.value += text+" ";
box.focus();
return false;
}
function fillCompletionZone(boxname, areaname, results) {
byId(areaname).innerHTML = "";
for(i=0; i<results.length; i++) {
byId(areaname).innerHTML += "<br><a href=\"#\" onclick=\"complete('"+boxname+"', '"+results[i]+"');\">"+results[i]+"</a>";
}
}
function initAjax(boxname, areaname) {
var box = byId(boxname);
if(!box) return;
addEvent(
box,
"keyup",
function f() {
starter = endWord(box.value);
if(resultCache[starter]) {
fillCompletionZone(boxname, areaname, resultCache[starter]);
}
else {
ajaxRequest(
"ajax.php?start="+starter,
function g(text) {
resultCache[starter] = text.split("\n");
fillCompletionZone(boxname, areaname, resultCache[starter]);
}
);
}
},
false
);
}
//completion_cache = new array();
input = byId("search_input");
output = byId("search_completions");
function get_cached_completions(start) {
// if(completion_cache[start]) {
// return completion_cache[start];
// }
// else {
return null;
// }
}
function get_completions(start) {
cached = get_cached_completions(start);
if(cached) {
output.innerHTML = cached;
}
else {
ajaxRequest(autocomplete_url+"/"+start, function(data) {set_completions(start, data);});
}
}
function set_completions(start, data) {
// completion_cache[start] = data;
output.innerHTML = data;
}
if(input) {
input.onkeyup = function() {
if(input.value.length < 3) {
output.innerHTML = "";
}
else {
get_completions(input.value);
}
};
}

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Comment Word Ban
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -19,49 +19,72 @@
* from Essex"
*/
class BanWords implements Extension {
public function receive_event(Event $event) {
if($event instanceof InitExtEvent) {
global $config;
$config->set_default_string('banned_words', "
class BanWords extends SimpleExtension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_string('banned_words', "
a href=
anal
blowjob
/buy-.*-online/
casino
cialis
doors.txt
fuck
hot video
kaboodle.com
lesbian
nexium
penis
/pokerst.*/
pornhub
porno
purchase
sex
sex tape
spinnenwerk.de
thx for all
TRAMADOL
ultram
very nice site
viagra
porn
");
}
xanax
");
}
if($event instanceof CommentPostingEvent) {
global $config;
$banned = $config->get_string("banned_words");
$comment = strtolower($event->comment);
public function onCommentPosting(CommentPostingEvent $event) {
global $config;
$banned = $config->get_string("banned_words");
$comment = strtolower($event->comment);
foreach(explode("\n", $banned) as $word) {
$word = trim(strtolower($word));
if(strlen($word) == 0) {
// line is blank
continue;
foreach(explode("\n", $banned) as $word) {
$word = trim(strtolower($word));
if(strlen($word) == 0) {
// line is blank
continue;
}
else if($word[0] == '/') {
// lines that start with slash are regex
if(preg_match($word, $comment)) {
throw new CommentPostingException("Comment contains banned terms");
}
else if($word[0] == '/') {
// lines that start with slash are regex
if(preg_match($word, $comment)) {
throw new CommentPostingException("Comment contains banned terms");
}
}
else {
// other words are literal
if(strpos($comment, $word) !== false) {
throw new CommentPostingException("Comment contains banned terms");
}
}
else {
// other words are literal
if(strpos($comment, $word) !== false) {
throw new CommentPostingException("Comment contains banned terms");
}
}
}
if($event instanceof SetupBuildingEvent) {
$sb = new SetupBlock("Banned Phrases");
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
$sb->add_longtext_option("banned_words");
$event->panel->add_block($sb);
}
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Banned Phrases");
$sb->add_label("One per line, lines that start with slashes are treated as regex<br/>");
$sb->add_longtext_option("banned_words");
$event->panel->add_block($sb);
}
public function get_priority() {return 30;}
}
add_event_listener(new BanWords(), 30); // before the comment is added
?>

View File

@ -0,0 +1,45 @@
<?php
class BanWordsTest extends ShimmieWebTestCase {
function testWordBan() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_banned_words", "viagra\nporn\n\n/http:.*\.cn\//");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "kittens and viagra");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "kittens and ViagrA");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "kittens and viagra!");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "some link to http://something.cn/");
$this->click("Post Comment");
$this->assert_title("Comment Blocked");
$this->get_page('comment/list');
$this->assert_title('Comments');
$this->assert_no_text('viagra');
$this->assert_no_text('ViagrA');
$this->assert_no_text('http://something.cn/');
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

130
contrib/blotter/main.php Normal file
View File

@ -0,0 +1,130 @@
<?php
/*
* Name: Blotter
* Author: Zach Hall <zach@sosguy.net> [http://seemslegit.com/]
* License: GPLv2
* Description: Displays brief updates about whatever you want on every page.
* Colors and positioning can be configured to match your site's design.
*
* Development TODO at http://github.com/zshall/shimmie2/issues
*/
class Blotter extends SimpleExtension {
public function onInitExt(Event $event) {
/**
* I love re-using this installer don't I...
*/
global $config;
$version = $config->get_int("blotter_version", 0);
/**
* If this version is less than "1", it's time to install.
*
* REMINDER: If I change the database tables, I must change up version by 1.
*/
if($version < 1) {
/**
* Installer
*/
global $database, $config;
$database->create_table("blotter", "
id SCORE_AIPK,
entry_date SCORE_DATETIME DEFAULT SCORE_NOW,
entry_text TEXT NOT NULL,
important SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N
");
// Insert sample data:
$database->execute("INSERT INTO blotter (id, entry_date, entry_text, important) VALUES (?, now(), ?, ?)",
array(NULL, "Installed the blotter extension!", "Y"));
log_info("blotter", "Installed tables for blotter extension.");
$config->set_int("blotter_version", 1);
}
// Set default config:
$config->set_default_int("blotter_recent", 5);
$config->set_default_string("blotter_color", "FF0000");
$config->set_default_string("blotter_position", "subheading");
}
public function onSetupBuilding(Event $event) {
global $config;
$sb = new SetupBlock("Blotter");
$sb->add_int_option("blotter_recent", "<br />Number of recent entries to display: ");
$sb->add_text_option("blotter_color", "<br />Color of important updates: (ABCDEF format) ");
$sb->add_choice_option("blotter_position", array("Top of page" => "subheading", "In navigation bar" => "left"), "<br>Position: ");
$event->panel->add_block($sb);
}
public function onUserBlockBuilding(Event $event) {
global $user;
if($user->is_admin()) {
$event->add_link("Blotter Editor", make_link("blotter/editor"));
}
}
public function onPageRequest(Event $event) {
global $page, $database, $user;
if($event->page_matches("blotter")) {
switch($event->get_arg(0)) {
case "editor":
/**
* Displays the blotter editor.
*/
if(!$user->is_admin()) {
$this->theme->display_permission_denied($page);
} else {
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
$this->theme->display_editor($entries);
}
break;
case "add":
/**
* Adds an entry
*/
if(!$user->is_admin() || !$user->check_auth_token()) {
$this->theme->display_permission_denied($page);
} else {
$entry_text = $_POST['entry_text'];
if($entry_text == "") { die("No entry message!"); }
if(isset($_POST['important'])) { $important = 'Y'; } else { $important = 'N'; }
// Now insert into db:
$database->execute("INSERT INTO blotter (entry_date, entry_text, important) VALUES (now(), ?, ?)",
array($entry_text, $important));
log_info("blotter", "Added Message: $entry_text");
$page->set_mode("redirect");
$page->set_redirect(make_link("blotter/editor"));
}
break;
case "remove":
/**
* Removes an entry
*/
if(!$user->is_admin() || !$user->check_auth_token()) {
$this->theme->display_permission_denied($page);
} else {
$id = int_escape($_POST['id']);
if(!isset($id)) { die("No ID!"); }
$database->Execute("DELETE FROM blotter WHERE id=?", array($id));
log_info("blotter", "Removed Entry #$id");
$page->set_mode("redirect");
$page->set_redirect(make_link("blotter/editor"));
}
break;
case "":
/**
* Displays all blotter entries
*/
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC");
$this->theme->display_blotter_page($entries);
break;
}
}
/**
* Finally, display the blotter on whatever page we're viewing.
*/
$this->display_blotter();
}
private function display_blotter() {
global $database, $config;
$limit = $config->get_int("blotter_recent", 5);
$entries = $database->get_all("SELECT * FROM blotter ORDER BY id DESC LIMIT ?", array($limit));
$this->theme->display_blotter($entries);
}
}
?>

37
contrib/blotter/test.php Normal file
View File

@ -0,0 +1,37 @@
<?php
class BlotterTest extends SCoreWebTestCase {
function testLogin() {
$this->log_in_as_admin();
$this->assert_text("Blotter Editor");
$this->click("Blotter Editor");
$this->log_out();
}
function testDenial() {
$this->get_page("blotter/editor");
$this->assert_response(403);
$this->get_page("blotter/add");
$this->assert_response(403);
$this->get_page("blotter/remove");
$this->assert_response(403);
}
function testAddViewRemove() {
$this->log_in_as_admin();
$this->get_page("blotter/editor");
$this->set_field("entry_text", "blotter testing");
$this->click("Add");
$this->assert_text("blotter testing");
$this->get_page("blotter");
$this->assert_text("blotter testing");
$this->get_page("blotter/editor");
$this->click("Remove");
$this->assert_no_text("blotter testing");
$this->log_out();
}
}
?>

189
contrib/blotter/theme.php Normal file
View File

@ -0,0 +1,189 @@
<?php
class BlotterTheme extends Themelet {
public function display_editor($entries) {
global $page;
$html = $this->get_html_for_blotter_editor($entries);
$page->set_title("Blotter Editor");
$page->set_heading("Blotter Editor");
$page->add_block(new Block("Welcome to the Blotter Editor!", $html, "main", 10));
$page->add_block(new Block("Navigation", "<a href='".make_link()."'>Index</a>", "left", 0));
}
public function display_blotter_page($entries) {
global $page;
$html = $this->get_html_for_blotter_page($entries);
$page->set_mode("data");
$page->set_data($html);
}
public function display_blotter($entries) {
global $page, $config;
$html = $this->get_html_for_blotter($entries);
$position = $config->get_string("blotter_position", "subheading");
$page->add_block(new Block(null, $html, $position, 20));
}
private function is_odd($number) {
return $number & 1; // 0 = even, 1 = odd
}
private function get_html_for_blotter_editor($entries) {
global $user;
/**
* Long function name, but at least I won't confuse it with something else ^_^
*/
$html = "";
// Add_new stuff goes here.
$table_header = "
<tr>
<th>Date</th>
<th>Message</th>
<th>Important?</th>
<th>Action</th>
</tr>";
$add_new = "
<tr class='even'>
".make_form(make_link("blotter/add"))."
<td colspan='2'><textarea style='text-align:left;' name='entry_text' rows='2' /></textarea></td>
<td><input type='checkbox' name='important' /></td>
<td><input type='submit' value='Add'></td>
</form>
</tr>";
// Now, time for entries list.
$table_rows = "";
for ($i = 0 ; $i < count($entries) ; $i++)
{
/**
* Add table rows
*/
$id = $entries[$i]['id'];
$entry_date = $entries[$i]['entry_date'];
$entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') { $important = 'Y'; } else { $important = 'N'; }
if(!$this->is_odd($i)) {$tr_class = "odd";}
if($this->is_odd($i)) {$tr_class = "even";}
// Add the new table row(s)
$table_rows .=
"<tr class='{$tr_class}'>
<td>$entry_date</td>
<td>$entry_text</td>
<td>$important</td>
<td><form name='remove$id' method='post' action='".make_link("blotter/remove")."'>
".$user->get_auth_html()."
<input type='hidden' name='id' value='$id' />
<input type='submit' style='width: 100%;' value='Remove' />
</form>
</td>
</tr>";
}
$html = "
<table id='blotter_entries' class='zebra'>
<thead>$table_header</thead>
<tbody>$add_new</tbody>
<tfoot>$table_rows</tfoot>
</table>
<br />
<b>Help:</b><br />
<blockquote>Add entries to the blotter, and they will be displayed.</blockquote>";
return $html;
}
private function get_html_for_blotter_page($entries) {
/**
* This one displays a list of all blotter entries.
*/
global $config;
$i_color = $config->get_string("blotter_color","#FF0000");
$html = "";
$html .= "<html><head><title>Blotter</title></head>
<body><pre>";
for ($i = 0 ; $i < count($entries) ; $i++)
{
/**
* Blotter entries
*/
// Reset variables:
$i_open = "";
$i_close = "";
$id = $entries[$i]['id'];
$messy_date = $entries[$i]['entry_date'];
$clean_date = date("m/d/y",strtotime($messy_date));
$entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') { $i_open = "<font color='#{$i_color}'>"; $i_close="</font>"; }
$html .= "{$i_open}{$clean_date} - {$entry_text}{$i_close}<br /><br />";
}
$html .= "</pre></body></html>";
return $html;
}
private function get_html_for_blotter($entries) {
/**
* Show the blotter widget
* Although I am starting to learn PHP, I got no idea how to do javascript... to the tutorials!
*/
global $config;
$i_color = $config->get_string("blotter_color","#FF0000");
$position = $config->get_string("blotter_position", "subheading");
$html = "<style type='text/css'>
#blotter1 {font-size: 80%; position: relative;}
#blotter2 {font-size: 80%;}
</style>";
$html .= "<script><!--
$(document).ready(function() {
$(\"#blotter2-toggle\").click(function() {
$(\"#blotter2\").slideToggle(\"slow\", function() {
if($(\"#blotter2\").is(\":hidden\")) {
$.cookie(\"blotter2-hidden\", 'true', {path: '/'});
}
else {
$.cookie(\"blotter2-hidden\", 'false', {path: '/'});
}
});
});
if($.cookie(\"blotter2-hidden\") == 'true') {
$(\"#blotter2\").hide();
}
});
//--></script>";
$entries_list = "";
for ($i = 0 ; $i < count($entries) ; $i++)
{
/**
* Blotter entries
*/
// Reset variables:
$i_open = "";
$i_close = "";
$id = $entries[$i]['id'];
$messy_date = $entries[$i]['entry_date'];
$clean_date = date("m/d/y",strtotime($messy_date));
$entry_text = $entries[$i]['entry_text'];
if($entries[$i]['important'] == 'Y') { $i_open = "<font color='#{$i_color}'>"; $i_close="</font>"; }
$entries_list .= "<li>{$i_open}{$clean_date} - {$entry_text}{$i_close}</li>";
}
$out_text = "";
$in_text = "";
$pos_break = "";
$pos_align = "text-align: right; position: absolute; right: 0px;";
if($position == "left") { $pos_break = "<br />"; $pos_align = ""; }
if(count($entries) == 0) { $out_text = "No blotter entries yet."; $in_text = "Empty.";}
else { $clean_date = date("m/d/y",strtotime($entries[0]['entry_date']));
$out_text = "Blotter updated: {$clean_date}";
$in_text = "<ul>$entries_list</ul>";
}
$html .= "<div id='blotter1'><span>$out_text</span>{$pos_break}<span style='{$pos_align}'><a href='#' id='blotter2-toggle'>Show/Hide</a> <a href='".make_link("blotter")."'>Show All</a></span></div>";
$html .= "<div id='blotter2'>$in_text</div>";
return $html;
}
}
?>

View File

@ -1,68 +0,0 @@
<?php
/**
* Name: Bookmarks
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Allow users to bookmark searches
*/
class Bookmarks implements Extension {
var $theme;
public function receive_event(Event $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof PageRequestEvent) && $event->page_matches("bookmark")) {
if($event->get_arg(0) == "add") {
if(isset($_POST['url'])) {
$page->set_mode("redirect");
$page->set_redirect(make_link("user"));
}
}
else if($event->get_arg(0) == "remove") {
if(isset($_POST['id'])) {
$page->set_mode("redirect");
$page->set_redirect(make_link("user"));
}
}
}
}
protected function install() {
global $database;
global $config;
// shortcut to latest
if($config->get_int("ext_bookmarks_version") < 1) {
$database->create_table("bookmark", "
id SCORE_AIPK,
owner_id INTEGER NOT NULL,
url TEXT NOT NULL,
title TET NOT NULL,
INDEX (owner_id),
FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE
");
$config->set_int("ext_bookmarks_version", 1);
}
}
private function get_bookmarks() {
global $database;
$bms = $database->get_all("
SELECT *
FROM bookmark
WHERE bookmark.owner_id = ?
");
if($bms) {return $bms;}
else {return array();}
}
private function add_bookmark($url, $title) {
global $database;
$sql = "INSERT INTO bookmark(owner_id, url, title) VALUES (?, ?, ?)";
$database->Execute($sql, array($user->id, $url, $title));
}
}
add_event_listener(new Bookmarks());
?>

View File

@ -1,5 +0,0 @@
<?php
class BookmarksTheme extends Themelet {
}
?>

View File

@ -1,14 +1,16 @@
<?php
/**
/*
* Name: Browser Search
* Author: ATravelingGeek <atg@atravelinggeek.com>
* Some code (and lots of help) by Artanis (Erik Youngren <artanis.00@gmail.com>) from the 'tagger' extention - Used with permission
* Link: http://atravelinggeek.com/
* License: GPLv2
* Description: Allows the user to add a browser 'plugin' to search the site with real-time suggestions
* Version 0.1c
* October 26, 2007
*
* Version: 0.1c, October 26, 2007
* Documentation:
* Once installed, users with an opensearch compatible browser should see
* their search box light up with whatever "click here to add a search
* engine" notification they have
*/
class BrowserSearch implements Extension {
@ -37,6 +39,7 @@ class BrowserSearch implements Extension {
//$search_form_url = $config->get_string('base_href'); //make_link('post/list');
$search_form_url = make_link('post/list/{searchTerms}');
$suggenton_url = make_link('browser_search/')."{searchTerms}";
$icon_b64 = base64_encode(file_get_contents("favicon.ico"));
// Now for the XML
@ -44,8 +47,7 @@ class BrowserSearch implements Extension {
<SearchPlugin xmlns='http://www.mozilla.org/2006/browser/search/' xmlns:os='http://a9.com/-/spec/opensearch/1.1/'>
<os:ShortName>$search_title</os:ShortName>
<os:InputEncoding>UTF-8</os:InputEncoding>
<os:Image width='16'
height='16'>data:image/x-icon;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFhYAfX19AH9/fwCAgIAAgYGBAIODgwCEhIQAhoaGAIeHhwCJiYkAioqKAIyMjACPj48AkJCQAJKSkgCTk5MAlZWVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEREREREREREREREREREREREAAAAAAAAAAAAAABERERERAAEBAQEBAQEBAQAAEREREQABAQEBAQEBAQEAAQAREREAAQEBAQEBAQEBAAEBABERAAEBAQEBAQEBAQABAQAREQAGBgUEAwIBAQEAAgEAEREADAwMCwsKCQkHAAkIABERABAQEBAQEA4ODAANDQAREQAQEBAQEBAQEBAAEBAAEREAEBAQEBAQEBAQABAQABERAAAAAAAAAAAAAAAQEAAREREAEBAPEBAQEBAQABAAEREREQAQEBAQEBAPEBAAABERERERAAAAAAAAAAAAAAAREREREREREREREREREREREf//AACADwAAgAcAAIADAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAMABAADgAQAA8AEAAP//AAA=</os:Image>
<os:Image width='16' height='16'>data:image/x-icon;base64,$icon_b64</os:Image>
<SearchForm>$search_form_url</SearchForm>
<os:Url type='text/html' method='GET' template='$search_form_url'>
<os:Param name='search' value='{searchTerms}'/>
@ -104,7 +106,7 @@ class BrowserSearch implements Extension {
$sort_by['Tag Count'] = 't';
$sb = new SetupBlock("Browser Search");
$sb->add_bool_option("disable_search_suggestions", "Disable search suggestions when using browser-based search: ");
$sb->add_bool_option("disable_search_suggestions", "Disable search suggestions: ");
$sb->add_label("<br>");
$sb->add_choice_option("search_suggestions_results_order", $sort_by, "Sort the suggestions by:");
$event->panel->add_block($sb);

View File

@ -0,0 +1,8 @@
<?php
class BrowserSearchTest extends SCoreWebTestCase {
function testBasic() {
$this->get_page("browser_search/please_dont_use_this_tag_as_it_would_break_stuff__search.xml");
$this->get_page("browser_search/test");
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Bulk Add
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -11,44 +11,42 @@
* upload into <code>/home/bob/uploads/holiday/2008/</code> and point
* shimmie at <code>/home/bob/uploads</code>, then images will be
* tagged "holiday 2008")
* <p><b>Note:</b> requires the "admin" extension to be enabled
*/
class BulkAdd implements Extension {
var $theme;
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof PageRequestEvent) && $event->page_matches("bulk_add")) {
if($event->user->is_admin() && isset($_POST['dir'])) {
class BulkAdd extends SimpleExtension {
public function onPageRequest($event) {
global $page, $user;
if($event->page_matches("bulk_add")) {
if($user->is_admin() && $user->check_auth_token() && isset($_POST['dir'])) {
set_time_limit(0);
$this->add_dir($_POST['dir']);
$this->theme->display_upload_results($event->page);
$this->theme->display_upload_results($page);
}
}
if($event instanceof AdminBuildingEvent) {
global $page;
$this->theme->display_admin_block($page);
}
}
public function onAdminBuilding($event) {
$this->theme->display_admin_block();
}
private function add_image($tmpname, $filename, $tags) {
if(file_exists($tmpname)) {
global $user;
$pathinfo = pathinfo($filename);
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags;
$metadata['source'] = null;
try {
$event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event);
}
catch(Exception $ex) {
return $ex->getMessage();
}
assert(file_exists($tmpname));
global $user;
$pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags;
$metadata['source'] = null;
$event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event);
if($event->image_id == -1) {
throw new UploadException("File type not recognised");
}
}
@ -62,40 +60,36 @@ class BulkAdd implements Extension {
$list = "";
$dir = opendir("$base/$subdir");
while($filename = readdir($dir)) {
$fullpath = "$base/$subdir/$filename";
foreach(glob("$base/$subdir/*") as $fullpath) {
$fullpath = str_replace("//", "/", $fullpath);
$shortpath = str_replace($base, "", $fullpath);
if(is_link($fullpath)) {
// ignore
}
else if(is_dir($fullpath)) {
if($filename[0] != ".") {
$this->add_dir($base, "$subdir/$filename");
}
$this->add_dir($base, str_replace($base, "", $fullpath));
}
else {
$tmpfile = $fullpath;
$pathinfo = pathinfo($fullpath);
$tags = $subdir;
$tags = str_replace("/", " ", $tags);
$tags = str_replace("__", " ", $tags);
$tags = trim($tags);
$list .= "<br>".html_escape("$subdir/$filename (".str_replace(" ", ", ", $tags).")... ");
$error = $this->add_image($tmpfile, $filename, $tags);
if(is_null($error)) {
$list .= "<br>".html_escape("$shortpath (".str_replace(" ", ", ", $tags).")... ");
try{
$this->add_image($fullpath, $pathinfo["basename"], $tags);
$list .= "ok\n";
}
else {
$list .= "failed:<br>$error\n";
catch(Exception $ex) {
$list .= "failed:<br>". $ex->getMessage();
}
}
}
closedir($dir);
if(strlen($list) > 0) {
$this->theme->add_status("Adding $subdir", $list);
}
}
}
add_event_listener(new BulkAdd());
?>

30
contrib/bulk_add/test.php Normal file
View File

@ -0,0 +1,30 @@
<?php
class BulkAddTest extends ShimmieWebTestCase {
function testBulkAdd() {
$this->log_in_as_admin();
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field('dir', "asdf");
$this->click("Add");
$this->assert_text("is not a directory");
$this->get_page('admin');
$this->assert_title("Admin Tools");
$this->set_field('dir', "contrib/simpletest");
$this->click("Add");
# FIXME: test that the output here makes sense, no "adding foo.php ... ok"
$this->get_page("post/list/hash=17fc89f372ed3636e28bd25cc7f3bac1/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete");
$this->get_page("post/list/hash=feb01bab5698a11dd87416724c7a89e3/1");
$this->assert_title(new PatternExpectation("/^Image \d+: data/"));
$this->click("Delete");
$this->log_out();
}
}
?>

View File

@ -20,14 +20,15 @@ class BulkAddTheme extends Themelet {
* links to bulk_add with POST[dir] set to the name of a server-side
* directory full of images
*/
public function display_admin_block(Page $page) {
public function display_admin_block() {
global $page, $user;
$html = "
Add a folder full of images; any subfolders will have their names
used as tags for the images within.
<br>Note: this is the folder as seen by the server -- you need to
upload via FTP or something first.
<p><form action='".make_link("bulk_add")."' method='POST'>
<p>".make_form(make_link("bulk_add"))."
Directory to add: <input type='text' name='dir' size='40'>
<input type='submit' value='Add'>
</form>

View File

@ -1,17 +1,20 @@
<?php
/*
Name: Danbooru Client API
Description: Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie
Author: JJS <jsutinen@gmail.com>
Notes:
danbooru API based on documentation from danbooru 1.0 - http://attachr.com/7569
I've only been able to test add_post and find_tags because I use the old danbooru firefox extension for firefox 1.5
Functions currently implemented:
add_post - title and rating are currently ignored because shimmie does not support them
find_posts - sort of works, filename is returned as the original filename and probably won't help when it comes to actually downloading it
find_tags - id, name, and after_id all work but the tags parameter is ignored just like danbooru 1.0 ignores it
Description: Allow Danbooru apps like Danbooru Uploader for Firefox to communicate with Shimmie
Documentation:
<p>Notes:
<br>danbooru API based on documentation from danbooru 1.0 -
http://attachr.com/7569
<br>I've only been able to test add_post and find_tags because I use the
old danbooru firefox extension for firefox 1.5
<p>Functions currently implemented:
<ul>
<li>add_post - title and rating are currently ignored because shimmie does not support them
<li>find_posts - sort of works, filename is returned as the original filename and probably won't help when it comes to actually downloading it
<li>find_tags - id, name, and after_id all work but the tags parameter is ignored just like danbooru 1.0 ignores it
</ul>
CHANGELOG
13-OCT-08 8:00PM CST - JJS
@ -61,7 +64,8 @@ class DanbooruApi implements Extension
if(preg_match("/^md5:([0-9a-fA-F]*)$/i", $event->term, $matches))
{
$hash = strtolower($matches[1]);
$event->set_querylet(new Querylet("images.hash = '$hash'"));
$event->add_querylet(new Querylet("images.hash = '$hash'")); // :-O
// $event->set_querylet(new Querylet("images.hash = '$hash'"));
}
}
}
@ -80,6 +84,8 @@ class DanbooruApi implements Extension
$results = array();
$danboorup_kludge=1; // danboorup for firefox makes broken links out of location: /path
/*
add_post()
Adds a post to the database.
@ -113,6 +119,7 @@ class DanbooruApi implements Extension
$this->authenticate_user();
// Now we check if a file was uploaded or a url was provided to transload
// Much of this code is borrowed from /ext/upload
if($config->get_bool("upload_anon") || !$user->is_anonymous())
{
$file = null;
@ -213,7 +220,9 @@ class DanbooruApi implements Extension
header("HTTP/1.0 409 Conflict");
header("X-Danbooru-Errors: duplicate");
$existinglink = make_link("post/view/" . $existing->id);
if($danboorup_kludge) $existinglink=make_http($existinglink);
header("X-Danbooru-Location: $existinglink");
return; // wut!
}
// Fire off an event which should process the new file and add it to the db
@ -222,13 +231,18 @@ class DanbooruApi implements Extension
$metadata['extension'] = $fileinfo['extension'];
$metadata['tags'] = $posttags;
$metadata['source'] = $source;
//log_debug("danbooru_api","========== NEW($filename) =========");
//log_debug("danbooru_api", "upload($filename): fileinfo(".var_export($fileinfo,TRUE)."), metadata(".var_export($metadata,TRUE).")...");
try {
$nevent = new DataUploadEvent($user, $file, $metadata);
//log_debug("danbooru_api", "send_event(".var_export($nevent,TRUE).")");
send_event($nevent);
// If it went ok, grab the id for the newly uploaded image and pass it in the header
$newimg = Image::by_hash($hash);
$newimg = Image::by_hash($hash); // FIXME: Unsupported file doesn't throw an error?
$newid = make_link("post/view/" . $newimg->id);
if($danboorup_kludge) $newid=make_http($newid);
// Did we POST or GET this call?
if($_SERVER['REQUEST_METHOD'] == 'POST')
{
@ -240,7 +254,7 @@ class DanbooruApi implements Extension
catch(UploadException $ex) {
// Did something screw up?
header("HTTP/1.0 409 Conflict");
header("X-Danbooru-Errors: ". $ex->getMessage());
header("X-Danbooru-Errors: exception - " . $ex->getMessage());
return;
}
} else
@ -264,6 +278,7 @@ class DanbooruApi implements Extension
*/
if(($event->get_arg(1) == 'find_posts') || (($event->get_arg(1) == 'post') && ($event->get_arg(2) == 'index.xml')))
{
$this->authenticate_user();
if(isset($_GET['md5']))
{
$md5list = explode(",",$_GET['md5']);

View File

@ -1,9 +1,14 @@
<?php
/**
/*
* Name: Downtime
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Show a "down for maintenance" page
* Documentation:
* Once installed there will be some more options on the config page --
* Ticking "disable non-admin access" will mean that regular and anonymous
* users will be blocked from accessing the site, only able to view the
* message specified in the box.
*/
class Downtime implements Extension {
@ -22,22 +27,16 @@ class Downtime implements Extension {
if($event instanceof PageRequestEvent) {
if($config->get_bool("downtime")) {
$this->check_downtime($event);
if(!$user->is_admin() && !$this->is_safe_page($event)) {
$msg = $config->get_string("downtime_message");
$this->theme->display_message($msg);
exit;
}
$this->theme->display_notification($page);
}
}
}
private function check_downtime(PageRequestEvent $event) {
global $user, $config;
if($config->get_bool("downtime") && !$user->is_admin() &&
($event instanceof PageRequestEvent) && !$this->is_safe_page($event)) {
$msg = $config->get_string("downtime_message");
$this->theme->display_message($msg);
}
}
private function is_safe_page(PageRequestEvent $event) {
if($event->page_matches("user_admin/login")) return true;
else return false;

23
contrib/downtime/test.php Normal file
View File

@ -0,0 +1,23 @@
<?php
class DowntimeTest extends SCoreWebTestCase {
function testDowntime() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_downtime", true);
$this->set_field("_config_downtime_message", "brb, unit testing");
$this->click("Save Settings");
$this->assert_text("DOWNTIME MODE IS ON!");
$this->log_out();
$this->get_page("post/list");
$this->assert_text("brb, unit testing");
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_downtime", false);
$this->click("Save Settings");
$this->assert_no_text("DOWNTIME MODE IS ON!");
$this->log_out();
}
}
?>

View File

@ -1,7 +1,7 @@
<?php
class DowntimeTheme Extends Themelet {
/*
class DowntimeTheme extends Themelet {
/**
* Show the admin that downtime mode is enabled
*/
public function display_notification(Page $page) {
@ -9,22 +9,50 @@ class DowntimeTheme Extends Themelet {
"<span style='font-size: 1.5em'><b>DOWNTIME MODE IS ON!</b></span>", "left", 0));
}
/*
/**
* Display $message and exit
*/
public function display_message($message) {
global $config;
$theme_name = $config->get_string('theme');
$data_href = get_base_href();
$login_link = make_link("user_admin/login");
header("HTTP/1.0 503 Service Temporarily Unavailable");
$auth = $user->get_auth_html();
print <<<EOD
<html>
<head>
<title>Downtime</title>
<link rel="stylesheet" href="$data_href/themes/$theme_name/style.css" type="text/css">
</head>
<body>
$message
<div id="downtime">
<h1>Down for Maintenance</h1>
<div id="message">
$message
</div>
<h3>Admin Login</h3>
<div id="login">
<form action="$login_link" method="POST">
$auth
<table id="login_table" summary="Login Form">
<tr>
<td width="70"><label for="user">Name</label></td>
<td width="70"><input id="user" type="text" name="user"></td>
</tr>
<tr>
<td><label for="pass">Password</label></td>
<td><input id="pass" type="password" name="pass"></td>
</tr>
<tr><td colspan="2"><input type="submit" value="Log In"></td></tr>
</table>
</form>
</div>
</div>
</body>
</html>
EOD;
exit;
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Emoticon Filter
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -24,4 +24,12 @@ class Emoticons extends FormatterExtension {
}
}
add_event_listener(new Emoticons());
class EmoticonList extends SimpleExtension {
public function onPageRequest($event) {
if($event->page_matches("emote/list")) {
$this->theme->display_emotes(glob("ext/emoticons/default/*"));
}
}
}
?>

View File

@ -0,0 +1,22 @@
<?php
class EmoticonTest extends ShimmieWebTestCase {
function testEmoticons() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id");
$this->set_field('comment', ":cool: :beans:");
$this->click("Post Comment");
$this->assert_no_text(":cool:"); # FIXME: test for working image link
#$this->assert_text(":beans:"); # FIXME: this should be left as-is
$this->get_page("emote/list");
$this->assert_text(":arrow:");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -0,0 +1,21 @@
<?php
class EmoticonListTheme extends Themelet {
public function display_emotes($list) {
global $page;
$data_href = get_base_href();
$html = "<html><head><title>Emoticon list</title></head><body>";
$html .= "<table><tr>";
$n = 1;
foreach($list as $item) {
$pathinfo = pathinfo($item);
$name = $pathinfo["filename"];
$html .= "<td><img src='$data_href/$item'> :$name:</td>";
if($n++ % 3 == 0) $html .= "</tr><tr>";
}
$html .= "</tr></table>";
$html .= "</body></html>";
$page->set_mode("data");
$page->set_data($html);
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: System Info
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -39,13 +39,14 @@ class ET implements Extension {
$info = array();
$info['site_title'] = $config->get_string("title");
$info['site_theme'] = $config->get_string("theme");
$info['site_genre'] = "[please write something here]";
$info['site_url'] = isset($_SERVER['SCRIPT_URI']) ? dirname($_SERVER['SCRIPT_URI']) : "???";
$info['site_url'] = "http://" . $_SERVER["HTTP_HOST"] . get_base_href();
$info['sys_shimmie'] = VERSION;
$info['sys_schema'] = $config->get_string("db_version");
$info['sys_php'] = phpversion();
$info['sys_os'] = php_uname();
$info['sys_disk'] = to_shorthand_int(disk_total_space("./") - disk_free_space("./")) . " / " .
to_shorthand_int(disk_total_space("./"));
$info['sys_server'] = $_SERVER["SERVER_SOFTWARE"];
include "config.php"; // more magical hax
$proto = preg_replace("#(.*)://.*#", "$1", $database_dsn);

10
contrib/et/test.php Normal file
View File

@ -0,0 +1,10 @@
<?php
class ETTest extends ShimmieWebTestCase {
function testET() {
$this->log_in_as_admin();
$this->get_page("system_info");
$this->assert_title("System Info");
$this->log_out();
}
}
?>

View File

@ -16,10 +16,9 @@ class ETTheme extends Themelet {
protected function build_data_form($info) {
$data = <<<EOD
Optional:
Add this site to the public shimmie users list: No
Site title: {$info['site_title']}
Theme: {$info['site_theme']}
Genre: {$info['site_genre']}
Genre: [describe your site here]
URL: {$info['site_url']}
System stats:
@ -29,7 +28,7 @@ PHP: {$info['sys_php']}
OS: {$info['sys_os']}
Server: {$info['sys_server']}
Database: {$info['sys_db']}
Extensions: {$info['sys_extensions']}
Disk use: {$info['sys_disk']}
Shimmie stats:
Images: {$info['stat_images']}
@ -37,9 +36,12 @@ Comments: {$info['stat_comments']}
Users: {$info['stat_users']}
Tags: {$info['stat_tags']}
Applications: {$info['stat_image_tags']}
Extensions: {$info['sys_extensions']}
EOD;
$auth = $user->get_auth_html();
$html = <<<EOD
<form action='http://shimmie.shishnet.org/register.php' method='POST'>
$auth
<input type='hidden' name='registration_api' value='1'>
<textarea name='data' rows='20' cols='80'>$data</textarea>
<br><input type='submit' value='Click to send to Shish'>

186
contrib/favorites/main.php Normal file
View File

@ -0,0 +1,186 @@
<?php
/*
* Name: Favorites
* Author: Daniel Marschall <info@daniel-marschall.de>
* License: GPLv2
* Description: Allow users to favorite images
* Documentation:
* Gives users a "favorite this image" button that they can press
* <p>Favorites for a user can then be retrieved by searching for
* "favorited_by=UserName"
* <p>Popular images can be searched for by eg. "favorites>5"
* <p>Favorite info can be added to an image's filename or tooltip
* using the $favorites placeholder
*/
class FavoriteSetEvent extends Event {
var $image_id, $user, $do_set;
public function FavoriteSetEvent($image_id, User $user, $do_set) {
assert(is_numeric($image_id));
assert(is_bool($do_set));
$this->image_id = $image_id;
$this->user = $user;
$this->do_set = $do_set;
}
}
class Favorites extends SimpleExtension {
public function onInitExt($event) {
global $config;
if($config->get_int("ext_favorites_version", 0) < 1) {
$this->install();
}
}
public function onImageAdminBlockBuilding($event) {
global $database, $page, $user;
if(!$user->is_anonymous()) {
$user_id = $user->id;
$image_id = $event->image->id;
$is_favorited = $database->db->GetOne(
"SELECT COUNT(*) AS ct FROM user_favorites WHERE user_id = ? AND image_id = ?",
array($user_id, $image_id)) > 0;
$event->add_part($this->theme->get_voter_html($event->image, $is_favorited));
}
}
public function onDisplayingImage($event) {
$people = $this->list_persons_who_have_favorited($event->image);
if(count($people) > 0) {
$html = $this->theme->display_people($people);
}
}
public function onPageRequest($event) {
global $page, $user;
if($event->page_matches("change_favorite") && !$user->is_anonymous() && $user->check_auth_token()) {
$image_id = int_escape($_POST['image_id']);
if((($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset")) && ($image_id > 0)) {
send_event(new FavoriteSetEvent($image_id, $user, ($_POST['favorite_action'] == "set")));
}
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$image_id"));
}
}
public function onUserPageBuilding($event) {
$i_favorites_count = Image::count_images(array("favorited_by={$event->display_user->name}"));
$i_days_old = ((time() - strtotime($event->display_user->join_date)) / 86400) + 1;
$h_favorites_rate = sprintf("%.1f", ($i_favorites_count / $i_days_old));
$favorites_link = make_link("post/list/favorited_by={$event->display_user->name}/1");
$event->add_stats("<a href='$favorites_link'>Images favorited</a>: $i_favorites_count, $h_favorites_rate per day");
}
public function onImageInfoSet($event) {
global $user;
if(
in_array('favorite_action', $_POST) &&
(($_POST['favorite_action'] == "set") || ($_POST['favorite_action'] == "unset"))
) {
send_event(new FavoriteSetEvent($event->image_id, $user, ($_POST['favorite_action'] == "set")));
}
}
public function onFavoriteSet($event) {
global $user;
$this->add_vote($event->image_id, $user->id, $event->do_set);
}
public function onImageDeletion($event) {
global $database;
$database->execute("DELETE FROM user_favorites WHERE image_id=?", array($event->image->id));
}
public function onParseLinkTemplate($event) {
$event->replace('$favorites', $event->image->favorites);
}
public function onUserBlockBuilding($event) {
global $user;
if(strpos($user->name, ' ') === false) {
$username = url_escape($user->name);
$link = make_link("post/list/favorited_by=$username/1");
} else {
$userid = $user->id;
$link = make_link("post/list/favorited_by_userno=$userid/1");
}
$event->add_link("My Favorites", $link);
}
public function onSearchTermParse($event) {
$matches = array();
if(preg_match("/favorites(<|>|<=|>=|=)(\d+)/", $event->term, $matches)) {
$cmp = $matches[1];
$favorites = $matches[2];
$event->add_querylet(new Querylet("images.id IN (SELECT id FROM images WHERE favorites $cmp $favorites)"));
}
else if(preg_match("/favorited_by=(.*)/i", $event->term, $matches)) {
global $database;
$user = User::by_name($matches[1]);
if(!is_null($user)) {
$user_id = $user->id;
}
else {
$user_id = -1;
}
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
}
else if(preg_match("/favorited_by_userno=([0-9]+)/i", $event->term, $matches)) {
$user_id = int_escape($matches[1]);
$event->add_querylet(new Querylet("images.id IN (SELECT image_id FROM user_favorites WHERE user_id = $user_id)"));
}
}
private function install() {
global $database;
global $config;
if($config->get_int("ext_favorites_version") < 1) {
$database->Execute("ALTER TABLE images ADD COLUMN favorites INTEGER NOT NULL DEFAULT 0");
$database->Execute("CREATE INDEX images__favorites ON images(favorites)");
$database->Execute("
CREATE TABLE user_favorites (
image_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
created_at DATETIME NOT NULL,
UNIQUE(image_id, user_id),
INDEX(image_id)
)
");
$config->set_int("ext_favorites_version", 1);
}
}
private function add_vote($image_id, $user_id, $do_set) {
global $database;
if ($do_set) {
$database->Execute(
"INSERT INTO user_favorites(image_id, user_id, created_at) VALUES(?, ?, NOW())",
array($image_id, $user_id));
} else {
$database->Execute(
"DELETE FROM user_favorites WHERE image_id = ? AND user_id = ?",
array($image_id, $user_id));
}
$database->Execute(
"UPDATE images SET favorites=(SELECT COUNT(*) FROM user_favorites WHERE image_id=?) WHERE id=?",
array($image_id, $image_id));
}
private function list_persons_who_have_favorited($image) {
global $database;
$result = $database->execute(
"SELECT name FROM users WHERE id IN (SELECT user_id FROM user_favorites WHERE image_id = ?) ORDER BY name",
array($image->id));
return $result->GetArray();
}
}
?>

View File

@ -0,0 +1,33 @@
<?php
class FavoritesTest extends ShimmieWebTestCase {
function testFavorites() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
$this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: test");
$this->assert_no_text("Favorited By");
$this->click("Favorite");
$this->assert_text("Favorited By");
$this->get_page("post/list/favorited_by=test/1");
$this->assert_title("Image $image_id: test");
$this->assert_text("Favorited By");
$this->get_page("user/test");
$this->assert_text("Images favorited: 1");
$this->click("Images favorited");
$this->assert_title("Image $image_id: test");
$this->click("Un-Favorite");
$this->assert_no_text("Favorited By");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -0,0 +1,36 @@
<?php
class FavoritesTheme extends Themelet {
public function get_voter_html(Image $image, $is_favorited) {
global $page, $user;
$i_image_id = int_escape($image->id);
$name = $is_favorited ? "unset" : "set";
$label = $is_favorited ? "Un-Favorite" : "Favorite";
$html = "
".make_form(make_link("change_favorite"))."
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='favorite_action' value='$name'>
<input type='submit' value='$label'>
</form>
";
return $html;
}
public function display_people($username_array) {
global $page;
$i_favorites = count($username_array);
$html = "$i_favorites people:";
foreach($username_array as $row) {
$username = html_escape($row['name']);
$html .= "<br><a href='".make_link("user/$username")."'>$username</a>";
}
$page->add_block(new Block("Favorited By", $html, "left", 25));
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Featured Image
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -9,57 +9,72 @@
* to the other image control buttons (delete, rotate, etc).
* Clicking it will set the image as the site's current feature,
* which will be shown in the side bar of the post list.
* <p><b>Viewing a featured image</b>
* <br>Visit <code>/featured_image/view</code>
* <p><b>Downloading a featured image</b>
* <br>Link to <code>/featured_image/download</code>. This will give
* the raw data for an image (no HTML). This is useful so that you
* can set your desktop wallpaper to be the download URL, refreshed
* every couple of hours.
*/
class Featured implements Extension {
var $theme;
class Featured extends SimpleExtension {
public function onInitExt($event) {
global $config;
$config->set_default_int('featured_id', 0);
}
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof InitExtEvent) {
global $config;
$config->set_default_int('featured_id', 0);
}
if(($event instanceof PageRequestEvent) && $event->page_matches("set_feature")) {
global $user;
if($user->is_admin() && isset($_POST['image_id'])) {
global $config;
$id = int_escape($_POST['image_id']);
if($id > 0) {
$config->set_int("featured_id", $id);
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$id"));
public function onPageRequest($event) {
global $config, $page, $user;
if($event->page_matches("featured_image")) {
if($event->get_arg(0) == "set" && $user->check_auth_token()) {
if($user->is_admin() && isset($_POST['image_id'])) {
$id = int_escape($_POST['image_id']);
if($id > 0) {
$config->set_int("featured_id", $id);
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$id"));
}
}
}
}
if($event instanceof PostListBuildingEvent) {
global $config, $page;
$fid = $config->get_int("featured_id");
if($fid > 0) {
$image = Image::by_id($fid);
if($event->get_arg(0) == "download") {
$image = Image::by_id($config->get_int("featured_id"));
if(!is_null($image)) {
$this->theme->display_featured($page, $image);
$page->set_mode("data");
$page->set_type("image/jpeg");
$page->set_data(file_get_contents($image->get_image_filename()));
}
}
}
/*
if(($event instanceof SetupBuildingEvent)) {
$sb = new SetupBlock("Featured Image");
$sb->add_int_option("featured_id", "Image ID: ");
$event->panel->add_block($sb);
}
*/
if($event instanceof ImageAdminBlockBuildingEvent) {
if($event->user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
if($event->get_arg(0) == "view") {
$image = Image::by_id($config->get_int("featured_id"));
if(!is_null($image)) {
send_event(new DisplayingImageEvent($image, $page));
}
}
}
}
public function onPostListBuilding($event) {
global $config, $page, $user;
$fid = $config->get_int("featured_id");
if($fid > 0) {
$image = Image::by_id($fid);
if(!is_null($image)) {
if(class_exists("Ratings")) {
if(strpos(Ratings::get_user_privs($user), $image->rating) === FALSE) {
return;
}
}
$this->theme->display_featured($page, $image);
}
}
}
public function onImageAdminBlockBuilding($event) {
global $user;
if($user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
}
add_event_listener(new Featured());
?>

26
contrib/featured/test.php Normal file
View File

@ -0,0 +1,26 @@
<?php
class FeaturedTest extends ShimmieWebTestCase {
function testFeatured() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx");
$this->log_out();
# FIXME: test that regular users can't feature things
$this->log_in_as_admin();
$this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx");
$this->click("Feature This");
$this->get_page("post/list");
$this->assert_text("Featured Image");
$this->delete_image($image_id);
$this->log_out();
# after deletion, there should be no feature
$this->get_page("post/list");
$this->assert_no_text("Featured Image");
# FIXME: test changing from one feature to another
}
}
?>

View File

@ -9,8 +9,9 @@ class FeaturedTheme extends Themelet {
}
public function get_buttons_html($image_id) {
global $user;
return "
<form action='".make_link("set_feature")."' method='POST'>
".make_form(make_link("featured_image/set"))."
<input type='hidden' name='image_id' value='$image_id'>
<input type='submit' value='Feature This'>
</form>

View File

@ -1,6 +1,6 @@
<?php
/**
* Name: Archive File Handler
/*
* Name: Handle Archives
* Author: Shish <webmaster@shishnet.org>
* Description: Allow users to upload archives (zip, etc)
* Documentation:
@ -10,55 +10,59 @@
* <br>7-zip: <code>7zr x -o"%d" "%f"</code>
*/
class ArchiveFileHandler implements Extension {
public function receive_event(Event $event) {
if($event instanceof InitExtEvent) {
global $config;
$config->set_default_string('archive_extract_command', 'unzip -d "%d" "%f"');
}
class ArchiveFileHandler extends SimpleExtension {
public function onInitExt($event) {
global $config;
$config->set_default_string('archive_extract_command', 'unzip -d "%d" "%f"');
}
if($event instanceof SetupBuildingEvent) {
$sb = new SetupBlock("Archive Handler Options");
$sb->add_text_option("archive_tmp_dir", "Temporary folder: ");
$sb->add_text_option("archive_extract_command", "<br>Extraction command: ");
$sb->add_label("<br>%f for archive, %d for temporary directory");
$event->panel->add_block($sb);
}
public function onSetupBuilding($event) {
$sb = new SetupBlock("Archive Handler Options");
$sb->add_text_option("archive_tmp_dir", "Temporary folder: ");
$sb->add_text_option("archive_extract_command", "<br>Extraction command: ");
$sb->add_label("<br>%f for archive, %d for temporary directory");
$event->panel->add_block($sb);
}
if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type)) {
public function onDataUpload($event) {
if($this->supported_ext($event->type)) {
global $config;
$tmp = sys_get_temp_dir();
$tmpdir = "$tmp/shimmie-archive-{$event->hash}";
$cmd = $config->get_string('archive_extract_command');
$cmd = str_replace('%f', $event->tmpfile);
$cmd = str_replace('%d', $tmpdir);
system($cmd);
$cmd = str_replace('%f', $event->tmpname, $cmd);
$cmd = str_replace('%d', $tmpdir, $cmd);
exec($cmd);
$this->add_dir($tmpdir);
unlink($tmpdir);
deltree($tmpdir);
}
}
private function supported_ext($ext) {
$exts = array("zip");
return array_contains($exts, strtolower($ext));
return in_array(strtolower($ext), $exts);
}
// copied from bulk add extension
private function add_image($tmpname, $filename, $tags) {
if(file_exists($tmpname)) {
assert(file_exists($tmpname));
try {
global $user;
$pathinfo = pathinfo($filename);
if(!array_key_exists('extension', $pathinfo)) {
throw new UploadException("File has no extension");
}
$metadata['filename'] = $pathinfo['basename'];
$metadata['extension'] = $pathinfo['extension'];
$metadata['tags'] = $tags;
$metadata['source'] = null;
try {
$event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event);
}
catch(UploadException $ex) {
return $ex->getMessage();
}
$event = new DataUploadEvent($user, $tmpname, $metadata);
send_event($event);
}
catch(UploadException $ex) {
return $ex->getMessage();
}
}
@ -100,5 +104,4 @@ class ArchiveFileHandler implements Extension {
// $this->theme->add_status("Adding $subdir", $list);
}
}
add_event_listener(new ArchiveFileHandler());
?>

View File

@ -1,48 +1,22 @@
<?php
/**
* Name: Flash File Handler
/*
* Name: Handle Flash
* Author: Shish <webmaster@shishnet.org>
* Description: Handle Flash files
*/
class FlashFileHandler implements Extension {
var $theme;
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash;
$ha = substr($hash, 0, 2);
if(!move_upload_to_archive($event)) return;
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) {
throw new UploadException(
"Flash Handler failed to create image object from data. ".
"Note: compressed flash files are currently unsupported");
}
send_event(new ImageAdditionEvent($event->user, $image));
}
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {
$hash = $event->hash;
$ha = substr($hash, 0, 2);
// FIXME: scale image, as not all boards use 192x192
copy("ext/handle_flash/thumb.jpg", "thumbs/$ha/$hash");
}
if(($event instanceof DisplayingImageEvent) && $this->supported_ext($event->image->ext)) {
$this->theme->display_image($event->page, $event->image);
}
class FlashFileHandler extends DataHandlerExtension {
protected function create_thumb($hash) {
// FIXME: scale image, as not all boards use 192x192
copy("ext/handle_flash/thumb.jpg", warehouse_path("thumbs", $hash));
}
private function supported_ext($ext) {
protected function supported_ext($ext) {
$exts = array("swf");
return array_contains($exts, strtolower($ext));
return in_array(strtolower($ext), $exts);
}
private function create_image_from_data($filename, $metadata) {
protected function create_image_from_data($filename, $metadata) {
global $config;
$image = new Image();
@ -70,6 +44,17 @@ class FlashFileHandler implements Extension {
return $image;
}
protected function check_contents($file) {
if(!file_exists($file)) return false;
$fp = fopen($file, "r");
$head = fread($fp, 3);
fclose($fp);
if(!in_array($head, array("CWS", "FWS"))) return false;
return true;
}
private function str_to_binarray($string) {
$binary = array();
for($j=0; $j<strlen($string); $j++) {
@ -115,17 +100,6 @@ class FlashFileHandler implements Extension {
return $bounds;
}
private function check_contents($file) {
if(!file_exists($file)) return false;
$fp = fopen($file, "r");
$head = fread($fp, 3);
fclose($fp);
if(!array_contains(array("CWS", "FWS"), $head)) return false;
return true;
}
}
add_event_listener(new FlashFileHandler());
?>

View File

@ -18,7 +18,7 @@ class FlashFileHandlerTheme extends Themelet {
width='{$image->width}'
type='application/x-shockwave-flash'></embed>
</object>";
$page->add_block(new Block("Image", $html, "main", 0));
$page->add_block(new Block("Flash Animation", $html, "main", 0));
}
}
?>

View File

@ -1,17 +1,13 @@
<?php
/**
* Name: ICO File Handler
/*
* Name: Handle ICO
* Author: Shish <webmaster@shishnet.org>
* Description: Handle windows icons
*/
class IcoFileHandler implements Extension {
var $theme;
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
class IcoFileHandler extends SimpleExtension {
public function onDataUpload($event) {
if($this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash;
$ha = substr($hash, 0, 2);
if(!move_upload_to_archive($event)) return;
@ -20,34 +16,43 @@ class IcoFileHandler implements Extension {
if(is_null($image)) {
throw new UploadException("Icon handler failed to create image object from data");
}
send_event(new ImageAdditionEvent($event->user, $image));
$iae = new ImageAdditionEvent($event->user, $image);
send_event($iae);
$event->image_id = $iae->image->id;
}
}
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {
public function onThumbnailGeneration($event) {
if($this->supported_ext($event->type)) {
$this->create_thumb($event->hash);
}
}
if(($event instanceof DisplayingImageEvent) && $this->supported_ext($event->image->ext)) {
$this->theme->display_image($event->page, $event->image);
public function onDisplayingImage($event) {
global $page;
if($this->supported_ext($event->image->ext)) {
$this->theme->display_image($page, $event->image);
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("get_ico")) {
global $config;
global $database;
public function onPageRequest($event) {
global $config, $database, $page;
if($event->page_matches("get_ico")) {
$id = int_escape($event->get_arg(0));
$image = Image::by_id($id);
$hash = $image->hash;
$ha = substr($hash, 0, 2);
$event->page->set_type("image/x-icon");
$event->page->set_mode("data");
$event->page->set_data(file_get_contents("images/$ha/$hash"));
$page->set_type("image/x-icon");
$page->set_mode("data");
$page->set_data(file_get_contents("images/$ha/$hash"));
}
}
private function supported_ext($ext) {
$exts = array("ico", "ani", "cur");
return array_contains($exts, strtolower($ext));
return in_array(strtolower($ext), $exts);
}
private function create_image_from_data($filename, $metadata) {
@ -86,9 +91,8 @@ class IcoFileHandler implements Extension {
private function create_thumb($hash) {
global $config;
$ha = substr($hash, 0, 2);
$inname = "images/$ha/$hash";
$outname = "thumbs/$ha/$hash";
$inname = warehouse_path("images", $hash);
$outname = warehouse_path("thumbs", $hash);
$w = $config->get_int("thumb_width");
$h = $config->get_int("thumb_height");
@ -106,5 +110,4 @@ class IcoFileHandler implements Extension {
return true;
}
}
add_event_listener(new IcoFileHandler());
?>

View File

@ -0,0 +1,19 @@
<?php
class IcoHandlerTest extends ShimmieWebTestCase {
function testPixelHander() {
$this->log_in_as_user();
$image_id = $this->post_image("favicon.ico", "shimmie favicon");
$this->assert_response(302);
$this->log_out();
$raw = $this->get_page("get_ico/$image_id"); // test for no crash
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test that the thumb works
# FIXME: test that it gets displayed properly
}
}
?>

View File

@ -1,46 +1,22 @@
<?php
/**
* Name: MP3 File Handler
/*
* Name: Handle MP3
* Author: Shish <webmaster@shishnet.org>
* Description: Handle MP3 files
*/
class MP3FileHandler implements Extension {
var $theme;
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof DataUploadEvent) && $this->supported_ext($event->type) && $this->check_contents($event->tmpname)) {
$hash = $event->hash;
$ha = substr($hash, 0, 2);
if(!move_upload_to_archive($event)) return;
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
if(is_null($image)) {
throw new UploadException("MP3 handler failed to create image object from data");
}
send_event(new ImageAdditionEvent($event->user, $image));
}
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {
$hash = $event->hash;
$ha = substr($hash, 0, 2);
// FIXME: scale image, as not all boards use 192x192
copy("ext/handle_mp3/thumb.jpg", "thumbs/$ha/$hash");
}
if(($event instanceof DisplayingImageEvent) && $this->supported_ext($event->image->ext)) {
$this->theme->display_image($event->page, $event->image);
}
class MP3FileHandler extends DataHandlerExtension {
protected function create_thumb($hash) {
// FIXME: scale image, as not all boards use 192x192
copy("ext/handle_mp3/thumb.jpg", warehouse_path("thumbs", $hash));
}
private function supported_ext($ext) {
protected function supported_ext($ext) {
$exts = array("mp3");
return array_contains($exts, strtolower($ext));
return in_array(strtolower($ext), $exts);
}
private function create_image_from_data($filename, $metadata) {
protected function create_image_from_data($filename, $metadata) {
global $config;
$image = new Image();
@ -59,7 +35,7 @@ class MP3FileHandler implements Extension {
return $image;
}
private function check_contents($file) {
protected function check_contents($file) {
// FIXME: mp3 magic header?
return (file_exists($file));
}

View File

@ -7,12 +7,12 @@ class MP3FileHandlerTheme extends Themelet {
$html = "
<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
codebase='http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,19,0'
width='400' height='170'>
<param name='movie' value='$data_href/ext/handle_mp3/xspf_player.swf?song_url=$ilink'/>
width='400' height='15'>
<param name='movie' value='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink'/>
<param name='quality' value='high' />
<embed src='$data_href/ext/handle_mp3/xspf_player.swf?song_url=$ilink' quality='high'
<embed src='$data_href/ext/handle_mp3/xspf_player_slim.swf?song_url=$ilink' quality='high'
pluginspage='http://www.macromedia.com/go/getflashplayer'
width='400' height='170'
width='400' height='15'
type='application/x-shockwave-flash'></embed>
</object>
<p><a href='$ilink'>Download</a>";

View File

@ -1,448 +0,0 @@
/*
Copyright (c) 2005, Fabricio Zuardi
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
stop();
//autoplay=true
//repeat_playlist = true;
//playlist_size = 3;
//player_title = "customizeable title test"
//song_url = "http://downloads.betterpropaganda.com/music/Imperial_Teen-Ivanka_128.mp3";
//playlist_url = "http://cchits.ning.com/recent/xspf/?xn_auth=no";
//playlist_url = "http://hideout.com.br/shows/radio-test.xspf";
//radio_mode = true;
//song_title = "Imperial Teen - Ivanka";
autoload=true;
//info_button_text = "Add to Cart"
//playlist_url = "http%3A%2F%2Fwebjay%2Eorg%2Fby%2Flucas%5Fgonze%2Flaconic%2Exspf"
//playlist_url= "http://hideout.com.br/tests/hideout2325.xspf"
//constants
DEFAULT_PLAYLIST_URL = "http://hideout.com.br/shows/allshows.xspf";
DEFAULT_WELCOME_MSG = "Hideout XSPF Music Player - by Fabricio Zuardi";
LOADING_PLAYLIST_MSG = "Loading Playlist...";
DEFAULT_LOADED_PLAYLIST_MSG = "- click to start"
DEFAULT_INFOBUTTON_TXT = "Info"
//playlists has priority over songs, if a playlist_url parameter is found the song_url is ignored
//default playlist if none is passed through query string
if(!playlist_url){
if(!song_url){
playlist_url = DEFAULT_PLAYLIST_URL;
}else{
single_music_playlist = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><playlist version=\"1\" xmlns = \"http://xspf.org/ns/0/\"><trackList>";
single_music_playlist += "<track><location>"+song_url+"</location><annotation>"+song_title+"</annotation></track>"
single_music_playlist += "</trackList></playlist>"
}
}
info_mc._visible=false;
if(!info_button_text){
info_button_text = DEFAULT_INFOBUTTON_TXT;
}
info_mc.display_txt.text = info_button_text;
//variables initialization
playlist_array = [];
track_index = 0;
playlist_mc.track_count = 0;
pause_position = 0;
volume_level = 100;
playlist_xml = new XML();
playlist_xml.ignoreWhite = true;
playlist_xml.onLoad = playlistLoaded;
mysound = new Sound(this);
playlist_listener = new Object();
playlist_list.addEventListener("change", playlist_listener)
//play_btn.onPress = playTrack;
//functions
//xml parser
function playlistLoaded (success){
if(success){
var root_node = this.firstChild;
for(var node = root_node.firstChild; node != null; node = node.nextSibling){
if(node.nodeName == "title"){
playlist_title = node.firstChild.nodeValue;
}
if(node.nodeName == "trackList"){
//tracks
var tracks_array = [];
for(var track_node = node.firstChild; track_node != null; track_node = track_node.nextSibling){
var track_obj = new Object()
//track attributes
for(var track_child = track_node.firstChild; track_child != null; track_child = track_child.nextSibling){
if(track_child.nodeName=="location"){
track_obj.location = track_child.firstChild.nodeValue
}
if(track_child.nodeName=="image"){
track_obj.image = track_child.firstChild.nodeValue
}
if(track_child.nodeName=="title"){
track_obj.title = track_child.firstChild.nodeValue
}
if(track_child.nodeName=="creator"){
track_obj.creator = track_child.firstChild.nodeValue
}
if(track_child.nodeName=="annotation"){
track_obj.annotation = track_child.firstChild.nodeValue
}
if(track_child.nodeName=="info"){
track_obj.info = track_child.firstChild.nodeValue
}
}
track_obj.label = (tracks_array.length+1) +". ";
if(track_obj.title) {
if(track_obj.creator) {
track_obj.label += track_obj.creator+' - ';
}
track_obj.label += track_obj.title;
} else {
track_obj.label += track_obj.annotation;
}
tracks_array.push(track_obj)
addTrack(track_obj.label);
}
}
}
playlist_array = tracks_array;
if(!playlist_size) playlist_size = playlist_array.length;
playlist_mc.tracks_mc["track_"+track_index+"_mc"].bg_mc.gotoAndStop(2)
if(autoplay){
loadTrack()
}else{
start_btn_mc.start_btn.onPress = loadTrack;
track_display_mc.display_txt.text = playlist_title+" "+DEFAULT_LOADED_PLAYLIST_MSG;
if(track_display_mc.display_txt._width>track_display_mc.mask_mc._width){
track_display_mc.onEnterFrame = scrollTitle;
}else{
track_display_mc.onEnterFrame = null;
track_display_mc.display_txt._x = 0;
}
}
}else{
annotation_txt.text = "Error opening "+playlist_url;
}
}
playlist_listener.change = function(eventObject){
annotation_txt.text = playlist_list.selectedItem.annotation;
location_txt.text = playlist_list.selectedItem.location;
}
function loadTrack(){
//Radio Mode feature by nosferathoo, more info in: https://sourceforge.net/tracker/index.php?func=detail&aid=1341940&group_id=128363&atid=711474
if (radio_mode && track_index==playlist_size-1) {
playlist_url=playlist_array[track_index].location;
for (i=0;i<playlist_mc.track_count;++i) {
removeMovieClip(playlist_mc.tracks_mc["track_"+i+"_mc"]);
}
playlist_mc.track_count=0;
playlist_size=0;
track_index=0;
autoload=true;
autoplay=true;
loadPlaylist();
return(0);
}
start_btn_mc.start_btn._visible = false;
track_display_mc.display_txt.text = playlist_array[track_index].label;
if(track_display_mc.display_txt._width>track_display_mc.mask_mc._width){
track_display_mc.onEnterFrame = scrollTitle;
}else{
track_display_mc.onEnterFrame = null;
track_display_mc.display_txt._x = 0;
}
cover_mc.content_mc["photo"+last_track_index].removeMovieClip();
mysound.loadSound(playlist_array[track_index].location,true);
play_mc.gotoAndStop(2)
//image from playlist
if(playlist_array[track_index].image!=undefined){
cover_mc.content_mc.createEmptyMovieClip("photo"+track_index,track_index)
cover_mc.content_mc["photo"+track_index].loadMovie(playlist_array[track_index].image)
}else{
}
//info button
if(playlist_array[track_index].info!=undefined){
info_mc._visible = true;
info_mc.info_btn.onPress = function(){
getURL(playlist_array[track_index].info,"_blank")
}
}else{
info_mc._visible = false;
}
_root.onEnterFrame=function(){
//HACK doesnt need to set the volume at every enterframe
mysound.setVolume(this.volume_level)
var sound_load_percent = (mysound.getBytesLoaded()/mysound.getBytesTotal())*100
track_display_mc.loader_mc.load_bar_mc._xscale = sound_load_percent;
var image_load_percent = (cover_mc.content_mc["photo"+track_index].getBytesLoaded()/cover_mc.content_mc["photo"+track_index].getBytesTotal())*100
cover_mc.load_bar_mc._xscale = image_load_percent;
if((cover_mc.content_mc["photo"+track_index].getBytesLoaded()>4)&&(image_load_percent==100)){
//image loaded
//make image fit
cover_mc.content_mc["photo"+track_index]._width = cover_mc.load_bar_mc._width
cover_mc.content_mc["photo"+track_index]._height = cover_mc.load_bar_mc._height
}
}
}
stop_btn.onRelease = stopTrack;
play_mc.play_btn.onRelease = playTrack
next_btn.onRelease = nextTrack
prev_btn.onRelease = prevTrack
mysound.onSoundComplete = nextTrack;
volume_mc.volume_btn.onPress = volumeChange;
volume_mc.volume_btn.onRelease = volume_mc.volume_btn.onReleaseOutside = function(){
this._parent.onMouseMove = this._parent.onMouseDown = null;
}
function volumeChange(){
this._parent.onMouseMove = this._parent.onMouseDown = function(){
var percent = (this._xmouse/this._width)*100
if(percent>100)percent=100;
if(percent<0)percent=0;
this.volume_bar_mc._xscale = percent
this._parent.volume_level = percent;
mysound.setVolume(percent)
}
}
function stopTrack() {
mysound.stop();
play_mc.gotoAndStop(1)
mysound.stop();
mysound.start();
mysound.stop();
_root.pause_position = 0;
};
function playTrack() {
if(play_mc._currentframe==1){ //play
seekTrack(_root.pause_position)
play_mc.gotoAndStop(2)
}else if(play_mc._currentframe==2){
_root.pause_position = mysound.position;
mysound.stop();
play_mc.gotoAndStop(1)
}
};
function seekTrack(p_offset:Number){
mysound.stop()
mysound.start(int((p_offset)/1000),1)
}
function nextTrack(){
if(track_index<playlist_size-1){
last_track_index = track_index;
track_index ++;
loadTrack();
}else{
if(repeat_playlist){
last_track_index = track_index;
track_index = 0;
loadTrack()
}
}
playlist_mc.tracks_mc["track_"+last_track_index+"_mc"].bg_mc.gotoAndStop(1)
playlist_mc.tracks_mc["track_"+track_index+"_mc"].bg_mc.gotoAndStop(2)
}
function prevTrack(){
if(track_index>0){
last_track_index = track_index;
track_index --;
loadTrack();
}
playlist_mc.tracks_mc["track_"+last_track_index+"_mc"].bg_mc.gotoAndStop(1)
playlist_mc.tracks_mc["track_"+track_index+"_mc"].bg_mc.gotoAndStop(2)
}
function scrollTitle(){
track_display_mc.display_txt._x -= 5;
if (track_display_mc.display_txt._x+track_display_mc.display_txt._width<0){
track_display_mc.display_txt._x = track_display_mc.mask_mc._width;
}
}
function resizeUI(){
bg_mc._width = Stage.width;
track_display_mc.loader_mc._width = Stage.width - track_display_mc._x - 2;
track_display_mc.mask_mc._width = track_display_mc.loader_mc._width-3;
if(track_display_mc.display_txt._width>track_display_mc.mask_mc._width){
track_display_mc.onEnterFrame = scrollTitle;
}else{
track_display_mc.onEnterFrame = null;
track_display_mc.display_txt._x = 0;
}
volume_mc._x = Stage.width - 22;
start_btn_mc._xscale = Stage.width;
//playlist area tinnier that the album cover
if(Stage.width<2.5*cover_mc._width){
//
if (Stage.height>2.5*cover_mc._height){
//send album cover to bottom
cover_mc._y = Stage.height - cover_mc._height -2- info_mc._height -2;
info_mc._y = Stage.height - info_mc._height -2;
var covervisible =1;
}else{
var covervisible =0;
//hide album cover
cover_mc._y = Stage.height;
}
//send playlist to left
playlist_mc._x = cover_mc._x;
scrollbar_mc.bg_mc._height = Stage.height - (19+(cover_mc._height+info_mc._height+4)*covervisible);
playlist_mc.bg_mc._height = Stage.height - (19+(cover_mc._height+info_mc._height+4)*covervisible);
playlist_mc.mask_mc._height = Stage.height - (23+(cover_mc._height+info_mc._height+4)*covervisible);
}else{
cover_mc._y = 17;
info_mc._y = 153;
playlist_mc._x = 138;
scrollbar_mc.bg_mc._height = Stage.height -19;
playlist_mc.bg_mc._height = Stage.height - 19;
playlist_mc.mask_mc._height = Stage.height - 23;
}
scrollbar_mc._x = Stage.width - 12;
playlist_mc.mask_mc._width = Stage.width - (playlist_mc._x + 19);
playlist_mc.bg_mc._width = Stage.width - (playlist_mc._x + 14);
}
function addTrack(track_label){
if(playlist_mc.track_count>0) {
playlist_mc.tracks_mc.track_0_mc.duplicateMovieClip("track_"+playlist_mc.track_count+"_mc",playlist_mc.track_count);
}
playlist_mc.tracks_mc["track_"+playlist_mc.track_count+"_mc"]._y += playlist_mc.track_count*14;
playlist_mc.tracks_mc["track_"+playlist_mc.track_count+"_mc"].display_txt.autoSize = "left";
playlist_mc.tracks_mc["track_"+playlist_mc.track_count+"_mc"].display_txt.text = track_label;
playlist_mc.tracks_mc["track_"+playlist_mc.track_count+"_mc"].bg_mc.index = playlist_mc.track_count;
playlist_mc.tracks_mc["track_"+playlist_mc.track_count+"_mc"].bg_mc.select_btn.onPress = function(){
last_track_index = track_index;
playlist_mc.tracks_mc["track_"+track_index+"_mc"].bg_mc.gotoAndStop(1)
track_index = this._parent.index;
playlist_mc.tracks_mc["track_"+track_index+"_mc"].bg_mc.gotoAndStop(2)
loadTrack();
}
playlist_mc.track_count ++;
}
//scroll
scrollbar_mc.up_btn.onPress = function(){
this._parent.v = -1;
this._parent.onEnterFrame = scrollTracks;
}
scrollbar_mc.down_btn.onPress = function(){
this._parent.v = 1;
this._parent.onEnterFrame = scrollTracks;
}
scrollbar_mc.up_btn.onRelease = scrollbar_mc.down_btn.onRelease = function(){
this._parent.onEnterFrame = null;
}
scrollbar_mc.handler_mc.drag_btn.onPress = function(){
var scroll_top_limit = 19;
var scroll_bottom_limit = scrollbar_mc.bg_mc._height - scrollbar_mc.handler_mc._height - 2;
this._parent.startDrag(false,this._parent._x,scroll_top_limit,this._parent._x,scroll_bottom_limit)
this._parent.isdragging = true;
this._parent.onEnterFrame = scrollTracks;
}
scrollbar_mc.handler_mc.drag_btn.onRelease = scrollbar_mc.handler_mc.drag_btn.onReleaseOutside = function(){
stopDrag()
this._parent.isdragging = false;
this._parent.onEnterFrame = null;
}
function scrollTracks(){
var scroll_top_limit = 19;
var scroll_bottom_limit = scrollbar_mc.bg_mc._height - scrollbar_mc.handler_mc._height - 2;
var list_bottom_limit = 1;
var list_top_limit = (1-Math.round(playlist_mc.tracks_mc._height))+Math.floor(playlist_mc.mask_mc._height/14)*14
if(playlist_mc.tracks_mc._height>playlist_mc.mask_mc._height){
if(scrollbar_mc.handler_mc.isdragging){
var percent = (scrollbar_mc.handler_mc._y-scroll_top_limit)/(scroll_bottom_limit-scroll_top_limit)
playlist_mc.tracks_mc._y = (list_top_limit-list_bottom_limit)*percent+list_bottom_limit;
}else{
if(scrollbar_mc.v==-1){
if(playlist_mc.tracks_mc._y+14<list_bottom_limit){
playlist_mc.tracks_mc._y += 14;
}else{
playlist_mc.tracks_mc._y = list_bottom_limit;
}
var percent = (playlist_mc.tracks_mc._y-1)/(list_top_limit-1)
scrollbar_mc.handler_mc._y = percent*(scroll_bottom_limit - scroll_top_limit)+scroll_top_limit;
}else if(scrollbar_mc.v==1){
if(playlist_mc.tracks_mc._y-14>list_top_limit){
playlist_mc.tracks_mc._y -= 14;
}else{
playlist_mc.tracks_mc._y = list_top_limit;
}
var percent = (playlist_mc.tracks_mc._y-1)/(list_top_limit-1)
scrollbar_mc.handler_mc._y = percent*(scroll_bottom_limit - scroll_top_limit)+scroll_top_limit;
}
}
}
}
function loadPlaylist(){
track_display_mc.display_txt.text = LOADING_PLAYLIST_MSG;
if(track_display_mc.display_txt._width>track_display_mc.mask_mc._width){
track_display_mc.onEnterFrame = scrollTitle;
}else{
track_display_mc.onEnterFrame = null;
track_display_mc.display_txt._x = 0;
}
//playlist
if(playlist_url){
playlist_xml.load(unescape(playlist_url))
}else{
//single track
playlist_xml.parseXML(single_music_playlist)
playlist_xml.onLoad(true);
}
}
//first click - load playlist
start_btn_mc.start_btn.onPress = function(){
autoplay = true;
loadPlaylist();
}
//main
Stage.scaleMode = "noScale"
Stage.align = "LT";
this.onResize = resizeUI;
Stage.addListener(this);
if(!player_title) player_title = DEFAULT_WELCOME_MSG;
track_display_mc.display_txt.autoSize = "left";
track_display_mc.display_txt.text = player_title;
if(track_display_mc.display_txt._width>track_display_mc.mask_mc._width){
track_display_mc.onEnterFrame = scrollTitle;
}else{
track_display_mc.onEnterFrame = null;
track_display_mc.display_txt._x = 0;
}
//start to play automatically if parameter autoplay is present
if(autoplay){
start_btn_mc.start_btn.onPress();
} else if (autoload){
loadPlaylist()
}
//customized menu
var my_cm:ContextMenu = new ContextMenu();
my_cm.customItems.push(new ContextMenuItem("Stop", stopTrack));
my_cm.customItems.push(new ContextMenuItem("Play!", playTrack));
my_cm.customItems.push(new ContextMenuItem("Next", nextTrack));
my_cm.customItems.push(new ContextMenuItem("Previous", prevTrack));
my_cm.customItems.push(new ContextMenuItem("Download this song", function(){getURL(playlist_array[track_index].location,"_blank")},true));
my_cm.customItems.push(new ContextMenuItem("Add song to Webjay playlist", function(){getURL("http://webjay.org/poster?media="+escape(playlist_array[track_index].location),"_blank")}));
my_cm.customItems.push(new ContextMenuItem("About Hideout", function(){getURL("http://www.hideout.com.br","_blank")},true));
//my_cm.customItems.push(new ContextMenuItem("Crossfade", function(){}));
//my_cm.customItems.push(new ContextMenuItem("Mando Diao - Paralyzed", function(){}));
my_cm.hideBuiltInItems();
this.menu = my_cm;
resizeUI();

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
<?php
/**
* Name: SVG File Handler
/*
* Name: Handle SVG
* Author: Shish <webmaster@shishnet.org>
* Description: Handle SVG files
*/
@ -16,11 +16,13 @@ class SVGFileHandler implements Extension {
$ha = substr($hash, 0, 2);
if(!move_upload_to_archive($event)) return;
send_event(new ThumbnailGenerationEvent($event->hash, $event->type));
$image = $this->create_image_from_data("images/$ha/$hash", $event->metadata);
$image = $this->create_image_from_data(warehouse_path("images", $hash), $event->metadata);
if(is_null($image)) {
throw new UploadException("SVG handler failed to create image object from data");
}
send_event(new ImageAdditionEvent($event->user, $image));
$iae = new ImageAdditionEvent($event->user, $image);
send_event($iae);
$event->image_id = $iae->image->id;
}
if(($event instanceof ThumbnailGenerationEvent) && $this->supported_ext($event->type)) {
@ -39,31 +41,30 @@ class SVGFileHandler implements Extension {
// }
// else {
// FIXME: scale image, as not all boards use 192x192
copy("ext/handle_svg/thumb.jpg", "thumbs/$ha/$hash");
copy("ext/handle_svg/thumb.jpg", warehouse_path("thumbs", $hash));
// }
}
if(($event instanceof DisplayingImageEvent) && $this->supported_ext($event->image->ext)) {
$this->theme->display_image($event->page, $event->image);
global $page;
$this->theme->display_image($page, $event->image);
}
if(($event instanceof PageRequestEvent) && $event->page_matches("get_svg")) {
global $config;
global $database;
global $config, $database, $page;
$id = int_escape($event->get_arg(0));
$image = Image::by_id($id);
$hash = $image->hash;
$ha = substr($hash, 0, 2);
$event->page->set_type("image/svg+xml");
$event->page->set_mode("data");
$event->page->set_data(file_get_contents("images/$ha/$hash"));
$page->set_type("image/svg+xml");
$page->set_mode("data");
$page->set_data(file_get_contents(warehouse_path("images", $hash)));
}
}
private function supported_ext($ext) {
$exts = array("svg");
return array_contains($exts, strtolower($ext));
return in_array(strtolower($ext), $exts);
}
private function create_image_from_data($filename, $metadata) {

View File

@ -0,0 +1,37 @@
<?php
class SVGHandlerTest extends ShimmieWebTestCase {
function testSVGHander() {
file_put_contents("test.svg", '<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="128"
height="128"
id="svg2"
version="1.0">
<g id="layer1">
<path
style="fill:#0000ff;stroke:#213847;stroke-opacity:1"
id="path2383"
d="M 120.07832,64.983688 A 55.573441,53.092484 0 1 1 8.9314423,64.983688 A 55.573441,53.092484 0 1 1 120.07832,64.983688 z" />
</g>
</svg>');
$this->log_in_as_user();
$image_id = $this->post_image("test.svg", "something");
$this->assert_response(302);
$this->log_out();
$raw = $this->get_page("get_svg/$image_id");
$this->assertTrue(strpos($raw, "www.w3.org") > 0);
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
unlink("test.svg");
# FIXME: test that the thumb works
# FIXME: test that it gets displayed properly
}
}
?>

View File

@ -1,8 +1,9 @@
<?php
/**
/*
* Name: Home Page
* Author: Bzchan <bzchan@animemahou.com>
* License: GPLv2
* Visibility: admin
* Description: Displays a front page with logo, search box and image count
* Documentation:
* Once enabled, the page will show up at the URL "home", so if you want
@ -14,37 +15,46 @@
* alongside the default choices.
*/
class Home implements Extension {
var $theme;
class Home extends SimpleExtension {
public function onInitExt(InitExtEvent $event) {
global $config;
$config->set_default_string("home_links", '[$base/post/list|Posts]
[$base/comment/list|Comments]
[$base/tags|Tags]
[$base/wiki|Wiki]
[$base/wiki/more|&raquo;]');
}
public function receive_event(Event $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
public function onPageRequest(PageRequestEvent $event) {
global $config, $page;
if($event->page_matches("home")) {
$base_href = $config->get_string('base_href');
$data_href = get_base_href();
$sitename = $config->get_string('title');
$theme_name = $config->get_string('theme');
if(($event instanceof PageRequestEvent) && $event->page_matches("home"))
{
// this is a request to display this page so output the page.
$this->output_pages($page);
}
if($event instanceof SetupBuildingEvent)
{
$counters = array();
foreach(glob("ext/home/counters/*") as $counter_dirname) {
$name = str_replace("ext/home/counters/", "", $counter_dirname);
$counters[ucfirst($name)] = $name;
}
$body = $this->get_body();
$sb = new SetupBlock("Home Page");
$sb->add_label("Page Links - Example: [$"."base/index|Posts]");
$sb->add_longtext_option("home_links", "<br>");
$sb->add_choice_option("home_counter", $counters, "<br>Counter: ");
$sb->add_label("<br>Note: page accessed via /home");
$event->panel->add_block($sb);
$this->theme->display_page($page, $sitename, $data_href, $theme_name, $body);
}
}
private function get_body()
{
public function onSetupBuilding(SetupBuildingEvent $event) {
$counters = array();
foreach(glob("ext/home/counters/*") as $counter_dirname) {
$name = str_replace("ext/home/counters/", "", $counter_dirname);
$counters[ucfirst($name)] = $name;
}
$sb = new SetupBlock("Home Page");
$sb->add_longtext_option("home_links", 'Page Links - Example: [/post/list|Posts]<br>');
$sb->add_longtext_option("home_text", "<br>Page Text:<br>");
$sb->add_choice_option("home_counter", $counters, "<br>Counter: ");
$event->panel->add_block($sb);
}
private function get_body() {
// returns just the contents of the body
global $database;
global $config;
@ -54,14 +64,12 @@ class Home implements Extension {
$contact_link = $config->get_string('contact_link');
$counter_dir = $config->get_string('home_counter', 'default');
$total = ceil($database->db->GetOne("SELECT COUNT(*) FROM images"));
$total = Image::count_images();
$strtotal = "$total";
$num_comma = number_format($total);
$counter_text = "";
for($n=0; $n<strlen($strtotal); $n++)
{
for($n=0; $n<strlen($strtotal); $n++) {
$cur = $strtotal[$n];
$counter_text .= " <img alt='$cur' src='$data_href/ext/home/counters/$counter_dir/$cur.gif' /> ";
}
@ -69,27 +77,12 @@ class Home implements Extension {
// get the homelinks and process them
$main_links = $config->get_string('home_links');
$main_links = str_replace('$base', $base_href, $main_links);
$main_links = str_replace('[', "<a href='", $main_links);
$main_links = str_replace('|', "'>", $main_links);
$main_links = str_replace(']', "</a>", $main_links);
$main_links = preg_replace('#\[(.*?)\|(.*?)\]#', "<a href='\\1'>\\2</a>", $main_links);
$main_links = str_replace('//', "/", $main_links);
return $this->theme->build_body($sitename, $main_links, $contact_link, $num_comma, $counter_text);
$main_text = $config->get_string('home_text');
return $this->theme->build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text);
}
private function output_pages($page)
{
// output a sectionalised list of all the main pages on the site.
global $config;
$base_href = $config->get_string('base_href');
$data_href = get_base_href();
$sitename = $config->get_string('title');
$theme_name = $config->get_string('theme');
$body = $this->get_body();
$this->theme->display_page($page, $sitename, $data_href, $theme_name, $body);
}
}
add_event_listener(new Home());
?>

View File

@ -1,9 +1,11 @@
<?php
class HomeTest extends WebTestCase {
class HomeTest extends ShimmieWebTestCase {
function testHomePage() {
$this->get(TEST_BASE.'/home');
$this->assertTitle('Shimmie Testbed');
$this->assertText('Shimmie Testbed');
$this->get_page('home');
$this->assert_title('Shimmie');
$this->assert_text('Shimmie');
# FIXME: test search box
}
}
?>

View File

@ -10,7 +10,7 @@ class HomeTheme extends Themelet {
<link rel='stylesheet' href='$data_href/themes/$theme_name/style.css' type='text/css'>
</head>
<style>
div#front-page h1 {font-size: 4em; margin-top: 2em; text-align: center; border: none; background: none;}
div#front-page h1 {font-size: 4em; margin-top: 2em; margin-bottom: 0px; text-align: center; border: none; background: none; box-shadow: none; -webkit-box-shadow: none; -moz-box-shadow: none;}
div#front-page {text-align:center;}
.space {margin-bottom: 1em;}
div#front-page div#links a {margin: 0 0.5em;}
@ -24,31 +24,31 @@ EOD
);
}
public function build_body($sitename, $main_links, $contact_link, $num_comma, $counter_text) {
return "
<div id='front-page'>
<h1>
<a style='text-decoration: none;' href='".make_link()."'><span>$sitename</span></a>
</h1>
<div class='space' id='links'>
$main_links
</div>
<div class='space'>
public function build_body($sitename, $main_links, $main_text, $contact_link, $num_comma, $counter_text) {
$main_links_html = empty($main_links) ? "" : "<div class='space' id='links'>$main_links</div>";
$message_html = empty($main_text) ? "" : "<div class='space' id='message'>$main_text</div>";
$counter_html = empty($counter_text) ? "" : "<div class='space' id='counter'>$counter_text</div>";
$search_html = "
<div class='space' id='search'>
<form action='".make_link("post/list")."' method='GET'>
<input id='search_input' name='search' size='55' type='text' value='' autocomplete='off' /><br/>
<input id='search_input' name='search' size='30' type='text' value='' autocomplete='off' />
<input type='hidden' name='q' value='/post/list'>
<input type='submit' value='Search'/>
</form>
</div>
<div style='font-size: 80%; margin-bottom: 2em;'>
<a href='$contact_link'>contact</a> &ndash; Serving $num_comma posts
</div>
<div class='space'>
Powered by <a href='http://trac.shishnet.org/shimmie2/'>Shimmie</a>
</div>
<div class='space'>
$counter_text
";
return "
<div id='front-page'>
<h1><a style='text-decoration: none;' href='".make_link()."'><span>$sitename</span></a></h1>
$main_links_html
$search_html
$message_html
$counter_html
<div class='space' id='foot'>
<small><small>
<a href='$contact_link'>Contact</a> &ndash; Serving $num_comma posts &ndash;
Running <a href='http://code.shishnet.org/shimmie2/'>Shimmie</a>
</small></small>
</div>
</div>";
}

View File

@ -1,13 +1,12 @@
<?php
/**
/*
* Name: Image Hash Ban
* Author: ATravelingGeek <atg@atravelinggeek.com>
* Link: http://atravelinggeek.com/
* License: GPLv2
* Description: Ban images based on their hash
* Based on the ResolutionLimit and IPban extensions by Shish
* Version 0.1
* October 21, 2007
* Version 0.1, October 21, 2007
*/
// RemoveImageHashBanEvent {{{
@ -30,35 +29,40 @@ class AddImageHashBanEvent extends Event {
}
}
// }}}
class ImageBan implements Extension {
var $theme;
class ImageBan extends SimpleExtension {
public function onInitExt(InitExtEvent $event) {
global $config, $database;
if($config->get_int("ext_imageban_version") < 1) {
$database->create_table("image_bans", "
id SCORE_AIPK,
hash CHAR(32) NOT NULL,
date DATETIME DEFAULT SCORE_NOW,
reason TEXT NOT NULL
");
$config->set_int("ext_imageban_version", 1);
}
}
public function receive_event(Event $event) {
public function onDataUpload(DataUploadEvent $event) {
global $database;
$row = $database->db->GetRow("SELECT * FROM image_bans WHERE hash = ?", $event->hash);
if($row) {
log_info("image_hash_ban", "Blocked image ({$event->hash})");
throw new UploadException("Image ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"]));
}
}
public function onPageRequest(PageRequestEvent $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof InitExtEvent) {
if($config->get_int("ext_imageban_version") < 1) {
$this->install();
}
}
if($event instanceof DataUploadEvent) {
$row = $database->db->GetRow("SELECT * FROM image_bans WHERE hash = ?", $event->hash);
if($row) {
log_info("image_hash_ban", "Blocked image ({$event->hash})");
throw new UploadException("Image ".html_escape($row["hash"])." has been banned, reason: ".format_text($row["reason"]));
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("image_hash_ban")) {
if($event->page_matches("image_hash_ban")) {
if($user->is_admin()) {
if($event->get_arg(0) == "add") {
if(isset($_POST['hash']) && isset($_POST['reason'])) {
send_event(new AddImageHashBanEvent($_POST['hash'], $_POST['reason']));
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
$page->set_redirect(make_link("image_hash_ban/list/1"));
}
if(isset($_POST['image_id'])) {
$image = Image::by_id(int_escape($_POST['image_id']));
@ -74,7 +78,7 @@ class ImageBan implements Extension {
send_event(new RemoveImageHashBanEvent($_POST['hash']));
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
$page->set_redirect(make_link("image_hash_ban/list/1"));
}
}
else if($event->get_arg(0) == "list") {
@ -82,47 +86,39 @@ class ImageBan implements Extension {
if($event->count_args() == 2) {
$page_num = int_escape($event->get_arg(1));
}
$this->theme->display_Image_hash_Bans($page, $page_num, $this->get_image_hash_bans($page_num));
$page_size = 100;
$page_count = ceil($database->db->getone("SELECT COUNT(id) FROM image_bans")/$page_size);
$this->theme->display_Image_hash_Bans($page, $page_num, $page_count, $this->get_image_hash_bans($page_num, $page_size));
}
}
}
if($event instanceof UserBlockBuildingEvent) {
if($user->is_admin()) {
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
}
}
if($event instanceof AddImageHashBanEvent) {
$this->add_image_hash_ban($event->hash, $event->reason);
}
if($event instanceof RemoveImageHashBanEvent) {
$this->remove_image_hash_ban($event->hash);
}
if($event instanceof ImageAdminBlockBuildingEvent) {
if($user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image));
}
}
}
protected function install() {
global $database;
global $config;
$database->create_table("image_bans", "
id SCORE_AIPK,
hash CHAR(32) NOT NULL,
date DATETIME DEFAULT SCORE_NOW,
reason TEXT NOT NULL
");
$config->set_int("ext_imageban_version", 1);
public function onUserBlockBuilding(UserBlockBuildingEvent $event) {
global $user;
if($user->is_admin()) {
$event->add_link("Image Bans", make_link("image_hash_ban/list/1"));
}
}
public function onAddImageHashBan(AddImageHashBanEvent $event) {
$this->add_image_hash_ban($event->hash, $event->reason);
}
public function onRemoveImageHashBan(RemoveImageHashBanEvent $event) {
$this->remove_image_hash_ban($event->hash);
}
public function onImageAdminBlockBuilding(ImageAdminBlockBuildingEvent $event) {
global $user;
if($user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image));
}
}
// DB funness
public function get_image_hash_bans($page, $size=1000) {
public function get_image_hash_bans($page, $size=100) {
// FIXME: many
$size_i = int_escape($size);
$offset_i = int_escape($page-1)*$size_i;
@ -144,6 +140,7 @@ class ImageBan implements Extension {
$database->Execute("DELETE FROM image_bans WHERE hash = ?", array($hash));
}
// in before resolution limit plugin
public function get_priority() {return 30;}
}
add_event_listener(new ImageBan(), 30); // in before resolution limit plugin
?>

View File

@ -1,23 +0,0 @@
<?php
class ImageHashBanTest extends WebTestCase {}
if(!defined(VERSION)) return;
class ImageHashBanUnitTest extends UnitTestCase {
public function _broken_testUploadFailsWhenBanned() {
$ihb = new ImageHashBan();
$due = new DataUploadEvent();
try {
$ihb->receive_event($due);
$this->assertTrue(false); // shouldn't work
}
catch(DataUploadException $ex) {
$this->assertTrue(true); // should fail
}
catch(Exception $ex) {
$this->assertTrue(false); // but not with any other error
}
}
}
?>

View File

@ -20,7 +20,7 @@ class ImageBanTheme extends Themelet {
* 'date' => when the ban started
* )
*/
public function display_image_hash_bans(Page $page, $page_number, $bans) {
public function display_image_hash_bans(Page $page, $page_number, $page_count, $bans) {
$h_bans = "";
$n = 0;
foreach($bans as $ban) {
@ -30,7 +30,7 @@ class ImageBanTheme extends Themelet {
<td width='30%'>{$ban['hash']}</td>
<td>{$ban['reason']}</td>
<td width='10%'>
<form action='".make_link("image_hash_ban/remove")."' method='POST'>
".make_form(make_link("image_hash_ban/remove"))."
<input type='hidden' name='hash' value='{$ban['hash']}'>
<input type='submit' value='Remove'>
</form>
@ -39,11 +39,16 @@ class ImageBanTheme extends Themelet {
";
}
$html = "
<table class='zebra'>
<script>
$(document).ready(function() {
$(\"#image_bans\").tablesorter();
});
</script>
<table id='image_bans' class='zebra'>
<thead><th>Hash</th><th>Reason</th><th>Action</th></thead>
$h_bans
<tfoot><tr>
<form action='".make_link("image_hash_ban/add")."' method='POST'>
".make_form(make_link("image_hash_ban/add"))."
<td><input type='text' name='hash'></td>
<td><input type='text' name='reason'></td>
<td><input type='submit' value='Ban'></td>
@ -57,7 +62,7 @@ class ImageBanTheme extends Themelet {
$h_prev = ($page_number <= 1) ? "Prev" : "<a href='".make_link("image_hash_ban/list/$prev")."'>Prev</a>";
$h_index = "<a href='".make_link()."'>Index</a>";
$h_next = "<a href='".make_link("image_hash_ban/list/$next")."'>Next</a>";
$h_next = ($page_number >= $page_count) ? "Next" : "<a href='".make_link("image_hash_ban/list/$next")."'>Next</a>";
$nav = "$h_prev | $h_index | $h_next";
@ -65,6 +70,7 @@ class ImageBanTheme extends Themelet {
$page->set_heading("Image Bans");
$page->add_block(new Block("Edit Image Bans", $html));
$page->add_block(new Block("Navigation", $nav, "left", 0));
$this->display_paginator($page, "image_hash_ban/list", null, $page_number, $page_count);
}
/*
@ -74,7 +80,7 @@ class ImageBanTheme extends Themelet {
*/
public function get_buttons_html(Image $image) {
$html = "
<form action='".make_link("image_hash_ban/add")."' method='POST'>
".make_form(make_link("image_hash_ban/add"))."
<input type='hidden' name='hash' value='{$image->hash}'>
<input type='hidden' name='image_id' value='{$image->id}'>
<input type='text' name='reason'>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: IP Ban
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -51,7 +51,7 @@ class IPBan implements Extension {
if(($event instanceof PageRequestEvent) && $event->page_matches("ip_ban")) {
if($user->is_admin()) {
if($event->get_arg(0) == "add") {
if($event->get_arg(0) == "add" && $user->check_auth_token()) {
if(isset($_POST['ip']) && isset($_POST['reason']) && isset($_POST['end'])) {
if(empty($_POST['end'])) $end = null;
else $end = $_POST['end'];
@ -61,7 +61,7 @@ class IPBan implements Extension {
$page->set_redirect(make_link("ip_ban/list"));
}
}
else if($event->get_arg(0) == "remove") {
else if($event->get_arg(0) == "remove" && $user->check_auth_token()) {
if(isset($_POST['id'])) {
send_event(new RemoveIPBanEvent($_POST['id']));
$page->set_mode("redirect");
@ -90,6 +90,7 @@ class IPBan implements Extension {
if($event instanceof RemoveIPBanEvent) {
$database->Execute("DELETE FROM bans WHERE id = ?", array($event->id));
$database->cache->delete("ip_bans");
}
}
// }}}
@ -163,16 +164,20 @@ class IPBan implements Extension {
global $config;
global $database;
$prefix = ($database->engine->name == "sqlite" ? "bans." : "");
$remote = $_SERVER['REMOTE_ADDR'];
$bans = $this->get_active_bans();
foreach($bans as $row) {
$ip = $row[$prefix."ip"];
if(
(strstr($row['ip'], '/') && ip_in_range($remote, $row['ip'])) ||
($row['ip'] == $remote)
(strstr($ip, '/') && ip_in_range($remote, $ip)) ||
($ip == $remote)
) {
$admin = User::by_id($row['banner_id']);
$date = date("Y-m-d", $row['end_timestamp']);
print "IP <b>{$row['ip']}</b> has been banned until <b>$date</b> by <b>{$admin->name}</b> because of <b>{$row['reason']}</b>";
$reason = $row[$prefix.'reason'];
$admin = User::by_id($row[$prefix.'banner_id']);
$date = date("Y-m-d", $row[$prefix.'end_timestamp']);
print "IP <b>$ip</b> has been banned until <b>$date</b> by <b>{$admin->name}</b> because of <b>$reason</b>";
$contact_link = $config->get_string("contact_link");
if(!empty($contact_link)) {
@ -190,7 +195,7 @@ class IPBan implements Extension {
SELECT bans.*, users.name as banner_name
FROM bans
JOIN users ON banner_id = users.id
ORDER BY end_timestamp, id
ORDER BY end_timestamp, bans.id
");
if($bans) {return $bans;}
else {return array();}
@ -199,18 +204,18 @@ class IPBan implements Extension {
private function get_active_bans() {
global $database;
$cached = $database->cache->get("bans");
$cached = $database->cache->get("ip_bans");
if($cached) return $cached;
$bans = $database->get_all("
SELECT bans.*, users.name as banner_name
FROM bans
JOIN users ON banner_id = users.id
WHERE (end_timestamp > UNIX_TIMESTAMP(now())) OR (end_timestamp IS NULL)
ORDER BY end_timestamp, id
");
WHERE (end_timestamp > ?) OR (end_timestamp IS NULL)
ORDER BY end_timestamp, bans.id
", array(time()));
$database->cache->set("bans", $bans);
$database->cache->set("ip_bans", $bans, 600);
if($bans) {return $bans;}
else {return array();}
@ -220,7 +225,7 @@ class IPBan implements Extension {
global $database;
$sql = "INSERT INTO bans (ip, reason, end_timestamp, banner_id) VALUES (?, ?, ?, ?)";
$database->Execute($sql, array($ip, $reason, strtotime($end), $user->id));
$database->cache->delete("bans");
$database->cache->delete("ip_bans");
}
// }}}
}

View File

@ -1,28 +1,28 @@
<?php
class IPBanTest extends WebTestCase {
class IPBanTest extends SCoreWebTestCase {
function testIPBan() {
$this->get(TEST_BASE.'/ip_ban/list');
$this->assertResponse(403);
$this->assertTitle("Permission Denied");
$this->get_page('ip_ban/list');
$this->assert_response(403);
$this->assert_title("Permission Denied");
$this->get(TEST_BASE.'/user');
$this->assertText("Login");
$this->setField('user', ADMIN_NAME);
$this->setField('pass', ADMIN_PASS);
$this->click("Log In");
$this->log_in_as_admin();
$this->get(TEST_BASE.'/ip_ban/list');
$this->assertNoText("42.42.42.42");
$this->setField('ip', '42.42.42.42');
$this->setField('reason', 'unit testing');
$this->setField('end', '1 week');
$this->get_page('ip_ban/list');
$this->assert_no_text("42.42.42.42");
$this->set_field('ip', '42.42.42.42');
$this->set_field('reason', 'unit testing');
$this->set_field('end', '1 week');
$this->click("Ban");
$this->assertText("42.42.42.42");
$this->assert_text("42.42.42.42");
$this->click("Remove"); // FIXME: remove which ban? :S
$this->assertNoText("42.42.42.42");
$this->assert_no_text("42.42.42.42");
$this->click('Log Out');
$this->get_page('ip_ban/list?all=on'); // just test it doesn't crash for now
$this->log_out();
# FIXME: test that the IP is actually banned
}
}
?>

View File

@ -12,21 +12,23 @@ class IPBanTheme extends Themelet {
* )
*/
public function display_bans(Page $page, $bans) {
global $user;
global $database, $user;
$h_bans = "";
$n = 0;
$prefix = ($database->engine->name == "sqlite" ? "bans." : "");
$prefix2 = ($database->engine->name == "sqlite" ? "users." : "");
foreach($bans as $ban) {
$end_human = date('Y-m-d', $ban['end_timestamp']);
$end_human = date('Y-m-d', $ban[$prefix.'end_timestamp']);
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$h_bans .= "
<tr class='$oe'>
<td width='10%'>{$ban['ip']}</td>
<td>{$ban['reason']}</td>
<td width='10%'>{$ban[$prefix.'ip']}</td>
<td>{$ban[$prefix.'reason']}</td>
<td width='10%'>{$ban['banner_name']}</td>
<td width='15%'>{$end_human}</td>
<td width='10%'>
<form action='".make_link("ip_ban/remove")."' method='POST'>
<input type='hidden' name='id' value='{$ban['id']}'>
".make_form(make_link("ip_ban/remove"))."
<input type='hidden' name='id' value='{$ban[$prefix.'id']}'>
<input type='submit' value='Remove'>
</form>
</td>
@ -34,12 +36,17 @@ class IPBanTheme extends Themelet {
";
}
$html = "
<script>
$(document).ready(function() {
$(\"#bans\").tablesorter();
});
</script>
<a href='".make_link("ip_ban/list", "all=on")."'>Show All</a>
<p><table class='zebra'>
<thead><th>IP</th><th>Reason</th><th>By</th><th>Until</th><th>Action</th></thead>
<p><table id='bans' class='zebra'>
<thead><tr><th>IP</th><th>Reason</th><th>By</th><th>Until</th><th>Action</th></tr></thead>
$h_bans
<tfoot><tr>
<form action='".make_link("ip_ban/add")."' method='POST'>
".make_form(make_link("ip_ban/add"))."
<td><input type='text' name='ip'></td>
<td><input type='text' name='reason'></td>
<td>{$user->name}</td>

View File

@ -1,41 +0,0 @@
/*
* This file may not be distributed without its readme.txt
**/
/* * * Link to Image * * */
#Link_to_Image {
/* allows borders to encompass the content; */
overflow:hidden;
}
#Link_to_Image fieldset {
width: 32%;
float:left;
min-width:25em;
}
#Link_to_Image input, #Link_to_Image label {
display:block;
width:66%;
float:left;
margin-bottom:2.5px;
}
#Link_to_Image label {
width:30%;
text-align:left;
padding-right:2%;
cursor:pointer;
}
#Link_to_Image input {
font-size:0.7em;
font-family:courier, fixed, monospace;
}
#Link_to_Image br {
clear:both;
}
#Link_to_Image label:hover {
border-bottom:1px dashed;
}

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Link to Image
* Author: Artanis <artanis.00@gmail.com>
* Description: Show various forms of link to each image, for copy & paste
@ -12,9 +12,6 @@ class LinkImage implements Extension {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof DisplayingImageEvent)) {
$data_href = get_base_href();
$page->add_header("<link rel='stylesheet' href='$data_href/ext/link_image/_style.css' type='text/css'>",0);
$this->theme->links_block($page, $this->data($event->image));
}
if($event instanceof SetupBuildingEvent) {
@ -28,17 +25,27 @@ class LinkImage implements Extension {
'$title - $id ($ext $size $filesize)');
}
}
private function hostify($str) {
$str = str_replace(" ", "%20", $str);
if(strpos($str, "ttp://") > 0) {
return $str;
}
else {
return "http://" . $_SERVER["HTTP_HOST"] . $str;
}
}
private function data($image) {
global $config;
$text_link = $image->parse_link_template($config->get_string("ext_link-img_text-link_format"));
$text_link = $text_link==" "? null : $text_link; // null blank setting so the url gets filled in on the text links.
$text_link = trim($text_link) == "" ? null : $text_link; // null blank setting so the url gets filled in on the text links.
return array(
'thumb_src' => $image->get_thumb_link(),
'image_src' => $image->get_image_link(),
'post_link' => $image->get_short_link(),
'text_link' => $text_link);
'thumb_src' => $this->hostify($image->get_thumb_link()),
'image_src' => $this->hostify($image->get_image_link()),
'post_link' => $this->hostify($_SERVER["REQUEST_URI"]),
'text_link' => $text_link);
}
}
add_event_listener(new LinkImage());

View File

@ -0,0 +1,23 @@
<?php
class LinkImageTest extends ShimmieWebTestCase {
function testLinkImage() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pie");
# look in the "plain text link to post" box, follow the link
# in there, see if it takes us to the right page
$raw = $this->get_page("post/view/$image_id");
$matches = array();
preg_match("#value='(http://.*(/|%2F)post(/|%2F)view(/|%2F)[0-9]+)'#", $raw, $matches);
$this->assertTrue(count($matches) > 0);
$this->get($matches[1]);
$this->assert_title("Image $image_id: pie");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -4,36 +4,55 @@ class LinkImageTheme extends Themelet {
$thumb_src = $data['thumb_src'];
$image_src = $data['image_src'];
$post_link = $data['post_link'];
$text_link = $data['text_link'];
$text_link = $data['text_link'];
$page->add_block( new Block(
"Link to Image",
"<fieldset>".
"<legend><a href='http://en.wikipedia.org/wiki/Bbcode' target='_blank'>BBCode</a></legend>".
$this->link_code("Text Link",$this->url($post_link, $text_link,"ubb"),"ubb_text-link").
$this->link_code("Thumbnail Link",$this->url($post_link, $this->img($thumb_src,"ubb"),"ubb"),"ubb_thumb-link").
$this->link_code("Inline Image", $this->img($image_src,"ubb"), "ubb_full-img").
"</fieldset>".
"
<table><tr>
"<fieldset>".
"<legend><a href='http://en.wikipedia.org/wiki/Html' target='_blank'>HTML</a></legend>".
$this->link_code("Text Link", $this->url($post_link, $text_link,"html"), "html_text-link").
$this->link_code("Thumbnail Link", $this->url($post_link,$this->img($thumb_src,"html"),"html"), "html_thumb-link").
$this->link_code("Inline Image", $this->img($image_src,"html"), "html_full-image").
"</fieldset>".
<td><fieldset>
<legend><a href='http://en.wikipedia.org/wiki/Bbcode' target='_blank'>BBCode</a></legend>
<table>
".
$this->link_code("Link",$this->url($post_link, $text_link,"ubb"),"ubb_text-link").
$this->link_code("Thumb",$this->url($post_link, $this->img($thumb_src,"ubb"),"ubb"),"ubb_thumb-link").
$this->link_code("Image", $this->img($image_src,"ubb"), "ubb_full-img").
"
</table>
</fieldset></td>
"<fieldset>".
"<legend>Plain Text</legend>".
$this->link_code("Post URL",$post_link,"text_post-link").
$this->link_code("Thumbnail URL",$thumb_src,"text_thumb-url").
$this->link_code("Image URL",$image_src,"text_image-src").
"</fieldset>",
<td><fieldset>
<legend><a href='http://en.wikipedia.org/wiki/Html' target='_blank'>HTML</a></legend>
<table>
".
$this->link_code("Link", $this->url($post_link, $text_link,"html"), "html_text-link").
$this->link_code("Thumb", $this->url($post_link,$this->img($thumb_src,"html"),"html"), "html_thumb-link").
$this->link_code("Image", $this->img($image_src,"html"), "html_full-image").
"
</table>
</fieldset></td>
<td><fieldset>
<legend>Plain Text</legend>
<table>
".
$this->link_code("Link",$post_link,"text_post-link").
$this->link_code("Thumb",$thumb_src,"text_thumb-url").
$this->link_code("Image",$image_src,"text_image-src").
"
</table>
</fieldset></td>
</tr></table>
",
"main",
50));
}
protected function url ($url,$content,$type) {
if ($content == NULL) {$content=$url;}
if ($content == NULL) {$content=$url;}
switch ($type) {
case "html":
@ -62,9 +81,14 @@ class LinkImageTheme extends Themelet {
return $text;
}
protected function link_code($label,$content,$id=NULL) {
return "<label for='".$id."' title='Click to select the textbox'>$label</label>\n".
"<input type='text' readonly='readonly' id='".$id."' name='".$id."' value='".html_escape($content)."' onfocus='this.select();'></input>\n<br/>\n";
protected function link_code($label,$content,$id=NULL) {
return "
<tr>
<td><label for='".$id."' title='Click to select the textbox'>$label</label></td>
<td><input type='text' readonly='readonly' id='".$id."' name='".$id."'
value='".html_escape($content)."' onfocus='this.select();'></input></td>
</tr>
";
}
}
?>

132
contrib/log_db/main.php Normal file
View File

@ -0,0 +1,132 @@
<?php
/*
* Name: Logging (Database)
* Author: Shish
* Description: Keep a record of SCore events
* Visibility: admin
*/
class LogDatabase extends SimpleExtension {
public function onInitExt($event) {
global $database;
global $config;
if($config->get_int("ext_log_database_version") < 1) {
$database->create_table("score_log", "
id SCORE_AIPK,
date_sent SCORE_DATETIME NOT NULL,
section VARCHAR(32) NOT NULL,
username VARCHAR(32) NOT NULL,
address SCORE_INET NOT NULL,
priority INT NOT NULL,
message TEXT NOT NULL
");
//INDEX(section)
$config->set_int("ext_log_database_version", 1);
}
$config->set_default_int("log_db_priority", SCORE_LOG_INFO);
}
public function onSetupBuilding($event) {
$sb = new SetupBlock("Logging (Database)");
$sb->add_choice_option("log_db_priority", array(
"Debug" => SCORE_LOG_DEBUG,
"Info" => SCORE_LOG_INFO,
"Warning" => SCORE_LOG_WARNING,
"Error" => SCORE_LOG_ERROR,
"Critical" => SCORE_LOG_CRITICAL,
), "Debug Level: ");
$event->panel->add_block($sb);
}
public function onPageRequest($event) {
global $database, $user;
if($event->page_matches("log/view")) {
if($user->is_admin()) {
$wheres = array();
$args = array();
$page_num = int_escape($event->get_arg(0));
if($page_num <= 0) $page_num = 1;
if(!empty($_GET["time"])) {
$wheres[] = "date_sent LIKE ?";
$args[] = $_GET["time"]."%";
}
if(!empty($_GET["module"])) {
$wheres[] = "section = ?";
$args[] = $_GET["module"];
}
if(!empty($_GET["user"])) {
if($database->engine->name == "pgsql") {
if(preg_match("#\d+\.\d+\.\d+\.\d+(/\d+)?#", $_GET["user"])) {
$wheres[] = "(username = ? OR address << ?)";
$args[] = $_GET["user"];
$args[] = $_GET["user"];
}
else {
$wheres[] = "lower(username) = lower(?)";
$args[] = $_GET["user"];
}
}
else {
$wheres[] = "(username = ? OR address = ?)";
$args[] = $_GET["user"];
$args[] = $_GET["user"];
}
}
if(!empty($_GET["priority"])) {
$wheres[] = "priority >= ?";
$args[] = int_escape($_GET["priority"]);
}
else {
$wheres[] = "priority >= ?";
$args[] = 20;
}
$where = "";
if(count($wheres) > 0) {
$where = "WHERE ";
$where .= join(" AND ", $wheres);
}
$limit = 50;
$offset = ($page_num-1) * $limit;
$page_total = $database->cache->get("event_log_length");
if(!$page_total) {
$page_total = $database->db->GetOne("SELECT count(*) FROM score_log $where", $args);
// don't cache a length of zero when the extension is first installed
if($page_total > 10) {
$database->cache->set("event_log_length", 600);
}
}
$args[] = $limit;
$args[] = $offset;
$events = $database->get_all("SELECT * FROM score_log $where ORDER BY id DESC LIMIT ? OFFSET ?", $args);
$this->theme->display_events($events, $page_num, 100);
}
}
}
public function onUserBlockBuilding($event) {
global $user;
if($user->is_admin()) {
$event->add_link("Event Log", make_link("log/view"));
}
}
public function onLog($event) {
global $config, $database, $user;
// not installed yet...
if($config->get_int("ext_log_database_version") < 1) return;
if($event->priority >= $config->get_int("log_db_priority")) {
$database->execute("
INSERT INTO score_log(date_sent, section, priority, username, address, message)
VALUES(now(), ?, ?, ?, ?, ?)
", array($event->section, $event->priority, $user->name, $_SERVER['REMOTE_ADDR'], $event->message));
}
}
}
?>

8
contrib/log_db/test.php Normal file
View File

@ -0,0 +1,8 @@
<?php
class LogDatabaseTest extends SCoreWebTestCase {
function testLog() {
$this->log_in_as_admin();
$this->log_out();
}
}
?>

106
contrib/log_db/theme.php Normal file
View File

@ -0,0 +1,106 @@
<?php
class LogDatabaseTheme extends Themelet {
protected function heie($var) {
if(isset($_GET[$var])) return html_escape($_GET[$var]);
else return "";
}
protected function ueie($var) {
if(isset($_GET[$var])) return $var."=".url_escape($_GET[$var]);
else return "";
}
public function display_events($events, $page_num, $page_total) {
$table = "
<style>
.sizedinputs TD INPUT {
width: 100%;
}
</style>
<table class='zebra'>
<thead>
<tr><th>Time</th><th>Module</th><th>User</th><th>Message</th></tr>
<form action='".make_link("log/view")."' method='GET'>
<tr class='sizedinputs'>
<td><input type='text' name='time' value='".$this->heie("time")."'></td>
<td><input type='text' name='module' value='".$this->heie("module")."'></td>
<td><input type='text' name='user' value='".$this->heie("user")."'></td>
<td>
<select name='priority'>
<option value='".SCORE_LOG_DEBUG."'>Debug</option>
<option value='".SCORE_LOG_INFO."' selected>Info</option>
<option value='".SCORE_LOG_WARNING."'>Warning</option>
<option value='".SCORE_LOG_ERROR."'>Error</option>
<option value='".SCORE_LOG_CRITICAL."'>Critical</option>
</select>
<input type='submit' value='Search' style='width: 20%'>
</td>
</tr>
</form>
</thead>
<tbody>\n";
$n = 0;
foreach($events as $event) {
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$c = $this->pri_to_col($event['priority']);
$table .= "<tr style='color: $c' class='$oe'>";
$table .= "<td>".str_replace(" ", "&nbsp;", $event['date_sent'])."</td>";
$table .= "<td>".$event['section']."</td>";
if($event['username'] == "Anonymous") {
$table .= "<td>".$event['address']."</td>";
}
else {
$table .= "<td><span title='".$event['address']."'>".
"<a href='".make_link("user/".url_escape($event['username']))."'>".html_escape($event['username'])."</a>".
"</span></td>";
}
$table .= "<td>".$this->scan_entities(html_escape($event['message']))."</td>";
$table .= "</tr>\n";
}
$table .= "</tbody></table>";
global $page;
$page->set_title("Event Log");
$page->set_heading("Event Log");
$page->add_block(new NavBlock());
$page->add_block(new Block("Events", $table));
$args = "";
// Check if each arg is actually empty and skip it if so
if(strlen($this->ueie("time")))
$args .= $this->ueie("time")."&";
if(strlen($this->ueie("module")))
$args .= $this->ueie("module")."&";
if(strlen($this->ueie("user")))
$args .= $this->ueie("user")."&";
if(strlen($this->ueie("priority")))
$args .= $this->ueie("priority");
// If there are no args at all, set $args to null to prevent an unnecessary ? at the end of the paginator url
if(strlen($args) == 0)
$args = null;
$this->display_paginator($page, "log/view", $args, $page_num, $page_total);
}
protected function pri_to_col($pri) {
switch($pri) {
case SCORE_LOG_DEBUG: return "#999";
case SCORE_LOG_INFO: return "#000";
case SCORE_LOG_WARNING: return "#800";
case SCORE_LOG_ERROR: return "#C00";
case SCORE_LOG_CRITICAL: return "#F00";
default: return "";
}
}
protected function scan_entities($line) {
$line = preg_replace_callback("/Image #(\d+)/s", array($this, "link_image"), $line);
return $line;
}
protected function link_image($id) {
$iid = int_escape($id[1]);
return "<a href='".make_link("post/view/$iid")."'>Image #$iid</a>";
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: News
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -8,25 +8,18 @@
* Any HTML is allowed
*/
class News implements Extension {
var $theme;
public function receive_event(Event $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof PostListBuildingEvent) {
if(strlen($config->get_string("news_text")) > 0) {
$this->theme->display_news($page, $config->get_string("news_text"));
}
}
if($event instanceof SetupBuildingEvent) {
$sb = new SetupBlock("News");
$sb->add_longtext_option("news_text");
$event->panel->add_block($sb);
class News extends SimpleExtension {
public function onPostListBuilding($event) {
global $config, $page;
if(strlen($config->get_string("news_text")) > 0) {
$this->theme->display_news($page, $config->get_string("news_text"));
}
}
public function onSetupBuilding($event) {
$sb = new SetupBlock("News");
$sb->add_longtext_option("news_text");
$event->panel->add_block($sb);
}
}
add_event_listener(new News());
?>

25
contrib/news/test.php Normal file
View File

@ -0,0 +1,25 @@
<?php
class NewsTest extends SCoreWebTestCase {
function testNews() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_news_text", "kittens");
$this->click("Save Settings");
$this->get_page("post/list");
$this->assert_text("Note");
$this->assert_text("kittens");
$this->get_page("setup");
$this->set_field("_config_news_text", "");
$this->click("Save Settings");
$this->get_page("post/list");
$this->assert_no_text("Note");
$this->assert_no_text("kittens");
$this->log_out();
}
}
?>

View File

@ -1,52 +0,0 @@
<?php
/**
* Name: Image Notes
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Adds notes overlaid on the images
* Documentation:
* This is quite broken :(
*/
class Notes implements Extension {
var $theme;
public function receive_event(Event $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof InitExtEvent) {
if($config->get_int("ext_notes_version") < 1) {
$this->install();
}
}
if($event instanceof DisplayingImageEvent) {
$notes = $database->get_all("SELECT * FROM image_notes WHERE image_id = ?", array($event->image->id));
$this->theme->display_notes($page, $notes);
}
}
protected function install() {
global $database;
global $config;
$database->create_table("image_notes", "
id SCORE_AIPK,
image_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
owner_ip SCORE_INET NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
version INTEGER DEFAULT 1 NOT NULL,
is_active SCORE_BOOL DEFAULT SCORE_BOOL_Y NOT NULL,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
w INTEGER NOT NULL,
h INTEGER NOT NULL,
body TEXT NOT NULL
");
$config->set_int("ext_notes_version", 1);
}
}
add_event_listener(new Notes());
?>

View File

@ -1,13 +0,0 @@
<?php
class NotesTheme extends Themelet {
public function display_notes(Page $page, $notes) {
$html = <<<EOD
<script type="text/javascript">
img = byId("main_image");
</script>
EOD;
$page->add_block(new Block(null, $html));
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Image Scores (Numeric)
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -39,14 +39,15 @@ class NumericScore implements Extension {
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("numeric_score_vote")) {
if(($event instanceof PageRequestEvent) && $event->page_matches("numeric_score_vote") && $user->check_auth_token()) {
if(!$user->is_anonymous()) {
$image_id = int_escape($_POST['image_id']);
$char = $_POST['vote'];
$score = 0;
$score = null;
if($char == "up") $score = 1;
else if($char == "null") $score = 0;
else if($char == "down") $score = -1;
if($score != 0) send_event(new NumericScoreSetEvent($image_id, $user, $score));
if(!is_null($score) && $image_id>0) send_event(new NumericScoreSetEvent($image_id, $user, $score));
$page->set_mode("redirect");
$page->set_redirect(make_link("post/view/$image_id"));
}
@ -69,10 +70,39 @@ class NumericScore implements Extension {
if(preg_match("/^score(<|<=|=|>=|>)(\d+)$/", $event->term, $matches)) {
$cmp = $matches[1];
$score = $matches[2];
$event->set_querylet(new Querylet("numeric_score $cmp $score"));
$event->add_querylet(new Querylet("numeric_score $cmp $score"));
}
if(preg_match("/^favou?rite$/", $event->term, $matches)) {
$event->set_querylet(new Querylet("images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=? AND score=1)", array($user->id)));
if(preg_match("/^upvoted_by=(.*)$/", $event->term, $matches)) {
$duser = User::by_name($matches[1]);
if(is_null($duser)) {
throw new SearchTermParseException(
"Can't find the user named ".html_escape($matches[1]));
}
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=? AND score=1)",
array($duser->id)));
}
if(preg_match("/^downvoted_by=(.*)$/", $event->term, $matches)) {
$duser = User::by_name($matches[1]);
if(is_null($duser)) {
throw new SearchTermParseException(
"Can't find the user named ".html_escape($matches[1]));
}
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=? AND score=-1)",
array($duser->id)));
}
if(preg_match("/^upvoted_by_id=(\d+)$/", $event->term, $matches)) {
$iid = int_escape($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=? AND score=1)",
array($iid)));
}
if(preg_match("/^downvoted_by_id=(\d+)$/", $event->term, $matches)) {
$iid = int_escape($matches[1]);
$event->add_querylet(new Querylet(
"images.id in (SELECT image_id FROM numeric_score_votes WHERE user_id=? AND score=-1)",
array($iid)));
}
}
}
@ -96,7 +126,7 @@ class NumericScore implements Extension {
$config->set_int("ext_numeric_score_version", 1);
}
if($config->get_int("ext_numeric_score_version") < 2) {
$database->Execute("CREATE INDEX numeric_score_votes__user_votes ON numeric_score_votes(user_id, scores)");
$database->Execute("CREATE INDEX numeric_score_votes__user_votes ON numeric_score_votes(user_id, score)");
$config->set_int("ext_numeric_score_version", 2);
}
}
@ -106,9 +136,11 @@ class NumericScore implements Extension {
$database->Execute(
"DELETE FROM numeric_score_votes WHERE image_id=? AND user_id=?",
array($image_id, $user_id));
$database->Execute(
"INSERT INTO numeric_score_votes(image_id, user_id, score) VALUES(?, ?, ?)",
array($image_id, $user_id, $score));
if($score != 0) {
$database->Execute(
"INSERT INTO numeric_score_votes(image_id, user_id, score) VALUES(?, ?, ?)",
array($image_id, $user_id, $score));
}
$database->Execute(
"UPDATE images SET numeric_score=(SELECT SUM(score) FROM numeric_score_votes WHERE image_id=?) WHERE id=?",
array($image_id, $image_id));

View File

@ -0,0 +1,57 @@
<?php
class NumericScoreTest extends ShimmieWebTestCase {
function testNumericScore() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx");
$this->get_page("post/view/$image_id");
$this->assert_text("Current Score: 0");
$this->click("Vote Down");
$this->assert_text("Current Score: -1");
$this->click("Vote Up");
$this->assert_text("Current Score: 1");
# FIXME: "remove vote" button?
# FIXME: test that up and down are hidden if already voted up or down
# test search by score
$this->get_page("post/list/score=1/1");
$this->assert_title("Image $image_id: pbx");
$this->get_page("post/list/score>0/1");
$this->assert_title("Image $image_id: pbx");
$this->get_page("post/list/score>-5/1");
$this->assert_title("Image $image_id: pbx");
$this->get_page("post/list/-score>5/1");
$this->assert_title("Image $image_id: pbx");
$this->get_page("post/list/-score<-5/1");
$this->assert_title("Image $image_id: pbx");
# test search by vote
$this->get_page("post/list/upvoted_by=test/1");
$this->assert_title("Image $image_id: pbx");
$this->assert_no_text("No Images Found");
# and downvote
$this->get_page("post/list/downvoted_by=test/1");
$this->assert_text("No Images Found");
# test errors
$this->get_page("post/list/upvoted_by=asdfasdf/1");
$this->assert_text("No Images Found");
$this->get_page("post/list/downvoted_by=asdfasdf/1");
$this->assert_text("No Images Found");
$this->get_page("post/list/upvoted_by_id=0/1");
$this->assert_text("No Images Found");
$this->get_page("post/list/downvoted_by_id=0/1");
$this->assert_text("No Images Found");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -2,6 +2,7 @@
class NumericScoreTheme extends Themelet {
public function get_voter_html(Image $image) {
global $user;
$i_image_id = int_escape($image->id);
$i_score = int_escape($image->numeric_score);
@ -9,12 +10,21 @@ class NumericScoreTheme extends Themelet {
Current Score: $i_score
<p><form action='".make_link("numeric_score_vote")."' method='POST'>
".$user->get_auth_html()."
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='vote' value='up'>
<input type='submit' value='Vote Up'>
</form>
<p><form action='".make_link("numeric_score_vote")."' method='POST'>
<form action='".make_link("numeric_score_vote")."' method='POST'>
".$user->get_auth_html()."
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='vote' value='null'>
<input type='submit' value='Remove Vote'>
</form>
<form action='".make_link("numeric_score_vote")."' method='POST'>
".$user->get_auth_html()."
<input type='hidden' name='image_id' value='$i_image_id'>
<input type='hidden' name='vote' value='down'>
<input type='submit' value='Vote Down'>

View File

@ -1,30 +0,0 @@
<?php
/**
* Name: PicLens Button
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Adds a link to piclensify the gallery
* Documentation:
* This extension only provides a button to the javascript
* version of the gallery; the "RSS for Images" extension
* is piclens-compatible to start with. (And that extension
* must be active for this one to do anything useful)
*/
class PicLens implements Extension {
public function receive_event(Event $event) {
global $page;
if($event instanceof PageRequestEvent) {
$page->add_header("<script type=\"text/javascript\" src=\"http://lite.piclens.com/current/piclens.js\"></script>");
}
if($event instanceof PostListBuildingEvent) {
$foo='
<a href="javascript:PicLensLite.start();">Start Slideshow
<img src="http://lite.piclens.com/images/PicLensButton.png"
alt="PicLens" width="16" height="12" border="0"
align="absmiddle"></a>';
$page->add_block(new Block("PicLens", $foo, "left", 20));
}
}
}
add_event_listener(new PicLens());
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Private Messaging
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -11,110 +11,39 @@
*/
class SendPMEvent extends Event {
public function __construct($from_id, $from_ip, $to_id, $subject, $message) {
$this->from_id = $from_id;
$this->from_ip = $from_ip;
$this->to_id = $to_id;
$this->subject = $subject;
$this->message = $message;
public function __construct($pm) {
$this->pm = $pm;
}
}
class PM implements Extension {
var $theme;
public function receive_event(Event $event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof InitExtEvent) {
if($config->get_int("pm_version") < 1) {
$this->install();
}
class PM {
public function __construct($from_id=0, $from_ip="0.0.0.0", $to_id=0, $subject="A Message", $message="Some Text", $read=False) {
# PHP: the P stands for "really", the H stands for "awful" and the other P stands for "language"
if(is_array($from_id)) {
$a = $from_id;
$this->id = $a["id"];
$this->from_id = $a["from_id"];
$this->from_ip = $a["from_ip"];
$this->to_id = $a["to_id"];
$this->sent_date = $a["sent_date"];
$this->subject = $a["subject"];
$this->message = $a["message"];
$this->is_read = undb_bool($a["is_read"]);
}
/*
if($event instanceof UserBlockBuildingEvent) {
if(!$user->is_anonymous()) {
$event->add_link("Private Messages", make_link("pm"));
}
}
*/
if($event instanceof UserPageBuildingEvent) {
$duser = $event->display_user;
if(!$user->is_anonymous() && !$duser->is_anonymous()) {
if(($user->id == $duser->id) || $user->is_admin()) {
$this->theme->display_pms($page, $this->get_pms($duser));
}
if($user->id != $duser->id) {
$this->theme->display_composer($page, $user, $duser);
}
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("pm")) {
if(!$user->is_anonymous()) {
switch($event->get_arg(0)) {
case "read":
$pm_id = int_escape($event->get_arg(1));
$pm = $database->get_row("SELECT * FROM private_message WHERE id = ?", array($pm_id));
if(is_null($pm)) {
$this->theme->display_error($page, "No such PM", "There is no PM #$pm_id");
}
else if(($pm["to_id"] == $user->id) || $user->is_admin()) {
$from_user = User::by_id(int_escape($pm["from_id"]));
$database->get_row("UPDATE private_message SET is_read='Y' WHERE id = ?", array($pm_id));
$this->theme->display_message($page, $from_user, $user, $pm);
}
else {
// permission denied
}
break;
case "delete":
$pm_id = int_escape($event->get_arg(1));
$pm = $database->get_row("SELECT * FROM private_message WHERE id = ?", array($pm_id));
if(is_null($pm)) {
$this->theme->display_error($page, "No such PM", "There is no PM #$pm_id");
}
else if(($pm["to_id"] == $user->id) || $user->is_admin()) {
$database->execute("DELETE FROM private_message WHERE id = ?", array($pm_id));
log_info("pm", "Deleted PM #$pm_id");
$page->set_mode("redirect");
$page->set_redirect(make_link("user"));
}
else {
// permission denied
}
break;
case "send":
$to_id = int_escape($_POST["to_id"]);
$from_id = $user->id;
$subject = $_POST["subject"];
$message = $_POST["message"];
send_event(new SendPMEvent($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message));
$page->set_mode("redirect");
$page->set_redirect(make_link($_SERVER["REFERER"]));
break;
}
}
}
if($event instanceof SendPMEvent) {
$database->execute("
INSERT INTO private_message(
from_id, from_ip, to_id,
sent_date, subject, message)
VALUES(?, ?, ?, now(), ?, ?)",
array($event->from_id, $event->from_ip,
$event->to_id, $event->subject, $event->message)
);
log_info("pm", "Sent PM to User #$to_id");
else {
$this->id = -1;
$this->from_id = $from_id;
$this->from_ip = $from_ip;
$this->to_id = $to_id;
$this->subject = $subject;
$this->message = $message;
$this->is_read = $read;
}
}
}
protected function install() {
class PrivMsg extends SimpleExtension {
public function onInitExt($event) {
global $config, $database;
// shortcut to latest
@ -131,21 +60,114 @@ class PM implements Extension {
INDEX (to_id)
");
$config->set_int("pm_version", 1);
log_info("pm", "extension installed");
}
log_info("pm", "extension installed");
}
/*
public function onUserBlockBuilding($event) {
global $user;
if(!$user->is_anonymous()) {
$event->add_link("Private Messages", make_link("pm"));
}
}
*/
public function onUserPageBuilding($event) {
global $page, $user;
$duser = $event->display_user;
if(!$user->is_anonymous() && !$duser->is_anonymous()) {
if(($user->id == $duser->id) || $user->is_admin()) {
$this->theme->display_pms($page, $this->get_pms($duser));
}
if($user->id != $duser->id) {
$this->theme->display_composer($page, $user, $duser);
}
}
}
public function onPageRequest($event) {
global $database, $page, $user;
if($event->page_matches("pm")) {
if(!$user->is_anonymous()) {
switch($event->get_arg(0)) {
case "read":
$pm_id = int_escape($event->get_arg(1));
$pm = $database->get_row("SELECT * FROM private_message WHERE id = ?", array($pm_id));
if(is_null($pm)) {
$this->theme->display_error($page, "No such PM", "There is no PM #$pm_id");
}
else if(($pm["to_id"] == $user->id) || $user->is_admin()) {
$from_user = User::by_id(int_escape($pm["from_id"]));
$database->get_row("UPDATE private_message SET is_read='Y' WHERE id = ?", array($pm_id));
$this->theme->display_message($page, $from_user, $user, new PM($pm));
}
else {
// permission denied
}
break;
case "delete":
if($user->check_auth_token()) {
$pm_id = int_escape($_POST["pm_id"]);
$pm = $database->get_row("SELECT * FROM private_message WHERE id = ?", array($pm_id));
if(is_null($pm)) {
$this->theme->display_error($page, "No such PM", "There is no PM #$pm_id");
}
else if(($pm["to_id"] == $user->id) || $user->is_admin()) {
$database->execute("DELETE FROM private_message WHERE id = ?", array($pm_id));
log_info("pm", "Deleted PM #$pm_id");
$page->set_mode("redirect");
$page->set_redirect($_SERVER["HTTP_REFERER"]);
}
}
break;
case "send":
if($user->check_auth_token()) {
$to_id = int_escape($_POST["to_id"]);
$from_id = $user->id;
$subject = $_POST["subject"];
$message = $_POST["message"];
send_event(new SendPMEvent(new PM($from_id, $_SERVER["REMOTE_ADDR"], $to_id, $subject, $message)));
$page->set_mode("redirect");
$page->set_redirect($_SERVER["HTTP_REFERER"]);
}
break;
default:
$this->theme->display_error($page, "Invalid action", "That's not something you can do with a PM");
break;
}
}
}
}
public function onSendPM($event) {
global $database;
$database->execute("
INSERT INTO private_message(
from_id, from_ip, to_id,
sent_date, subject, message)
VALUES(?, ?, ?, now(), ?, ?)",
array($event->pm->from_id, $event->pm->from_ip,
$event->pm->to_id, $event->pm->subject, $event->pm->message)
);
log_info("pm", "Sent PM to User #{$event->pm->to_id}");
}
private function get_pms(User $user) {
global $database;
return $database->get_all("
$arr = $database->get_all("
SELECT private_message.*,user_from.name AS from_name
FROM private_message
JOIN users AS user_from ON user_from.id=from_id
WHERE to_id = ?
", array($user->id));
$pms = array();
foreach($arr as $pm) {
$pms[] = new PM($pm);
}
return $pms;
}
}
add_event_listener(new PM());
?>

52
contrib/pm/test.php Normal file
View File

@ -0,0 +1,52 @@
<?php
class PrivMsgTest extends SCoreWebTestCase {
function testPM() {
$this->log_in_as_admin();
$this->get_page("user/test");
$this->set_field('subject', "message demo to test");
$this->set_field('message', "message contents");
$this->click("Send");
$this->log_out();
$this->log_in_as_user();
$this->get_page("user");
$this->assert_text("message demo to test");
$this->click("message demo to test");
$this->assert_text("message contents");
$this->back();
$this->click("Delete");
$this->assert_no_text("message demo to test");
$this->get_page("pm/read/0");
$this->assert_text("No such PM");
$this->get_page("pm/delete/0");
$this->assert_text("No such PM");
$this->get_page("pm/waffle/0");
$this->assert_text("Invalid action");
$this->log_out();
}
function testAdminAccess() {
$this->log_in_as_admin();
$this->get_page("user/test");
$this->set_field('subject', "message demo to test");
$this->set_field('message', "message contents");
$this->click("Send");
$this->get_page("user/test");
$this->assert_text("message demo to test");
$this->click("message demo to test");
$this->assert_text("message contents");
$this->back();
$this->click("Delete");
# simpletest bug? - redirect(referrer) works in opera, not in
# webtestcase, so we end up at the wrong page...
$this->get_page("user/test");
$this->assert_title("test's Page");
$this->assert_no_text("message demo to test");
$this->log_out();
}
}
?>

View File

@ -1,23 +1,42 @@
<?php
class PMTheme extends Themelet {
class PrivMsgTheme extends Themelet {
public function display_pms(Page $page, $pms) {
$html = "<table>";
$html .= "<tr><th>Subject</th><th>From</th><th>Date</th><th>Action</th></tr>";
global $user;
$html = "
<script>
$(document).ready(function() {
$(\"#pms\").tablesorter();
});
</script>
<table id='pms' class='zebra'>
<thead><tr><th>Subject</th><th>From</th><th>Date</th><th>Action</th></tr></thead>
<tbody>";
$n = 0;
foreach($pms as $pm) {
$h_subject = html_escape($pm["subject"]);
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$h_subject = html_escape($pm->subject);
if(strlen(trim($h_subject)) == 0) $h_subject = "(No subject)";
$h_from = html_escape($pm["from_name"]);
$from_url = make_link("user/".url_escape($pm["from_name"]));
$pm_url = make_link("pm/read/".$pm["id"]);
$del_url = make_link("pm/delete/".$pm["id"]);
$h_date = html_escape($pm["sent_date"]);
if($pm["is_read"] == "N") $h_subject = "<b>$h_subject</b>";
$html .= "<tr><td><a href='$pm_url'>$h_subject</a></td>
$from_name = User::by_id($pm->from_id)->name;
$h_from = html_escape($from_name);
$from_url = make_link("user/".url_escape($from_name));
$pm_url = make_link("pm/read/".$pm->id);
$del_url = make_link("pm/delete");
$h_date = html_escape($pm->sent_date);
if($pm->is_read) $h_subject = "<b>$h_subject</b>";
$html .= "<tr class='$oe'><td><a href='$pm_url'>$h_subject</a></td>
<td><a href='$from_url'>$h_from</a></td><td>$h_date</td>
<td><form action='$del_url'><input type='submit' value='Delete'></form></td></tr>";
<td><form action='$del_url' method='POST'>
<input type='hidden' name='pm_id' value='{$pm->id}'>
".$user->get_auth_html()."
<input type='submit' value='Delete'>
</form></td></tr>";
}
$html .= "</table>";
$html .= "
</tbody>
</table>
";
$page->add_block(new Block("Private Messages", $html, "main", 10));
}
@ -25,8 +44,10 @@ class PMTheme extends Themelet {
$post_url = make_link("pm/send");
$h_subject = html_escape($subject);
$to_id = $to->id;
$auth = $user->get_auth_html();
$html = <<<EOD
<form action="$post_url" method="POST">
$auth
<input type="hidden" name="to_id" value="$to_id">
<table style="width: 400px;">
<tr><td>Subject:</td><td><input type="text" name="subject" value="$h_subject"></td></tr>
@ -38,12 +59,12 @@ EOD;
$page->add_block(new Block("Write a PM", $html, "main", 20));
}
public function display_message(Page $page, User $from, User $to, $pm) {
$this->display_composer($page, $to, $from, "Re: ".$pm["subject"]);
public function display_message(Page $page, User $from, User $to, PM $pm) {
$this->display_composer($page, $to, $from, "Re: ".$pm->subject);
$page->set_title("Private Message");
$page->set_heading(html_escape($pm["subject"]));
$page->set_heading(html_escape($pm->subject));
$page->add_block(new NavBlock());
$page->add_block(new Block("Message", format_text($pm["message"]), "main", 10));
$page->add_block(new Block("Message", format_text($pm->message), "main", 10));
}
}
?>

686
contrib/pools/main.php Normal file
View File

@ -0,0 +1,686 @@
<?php
/**
* Name: Pools System
* Author: Sein Kraft <mail@seinkraft.info>
* License: GPLv2
* Description: Allow users to create groups of images
* Documentation:
*/
class PoolCreationException extends SCoreException {
}
class Pools extends SimpleExtension {
public function onInitExt($event) {
global $config, $database;
if ($config->get_int("ext_pools_version") < 1){
$database->create_table("pools", "
id SCORE_AIPK,
user_id INTEGER NOT NULL,
public SCORE_BOOL NOT NULL DEFAULT SCORE_BOOL_N,
title VARCHAR(255) NOT NULL,
description TEXT,
date DATETIME NOT NULL,
posts INTEGER NOT NULL DEFAULT 0,
INDEX (id)
");
$database->create_table("pool_images", "
pool_id INTEGER NOT NULL,
image_id INTEGER NOT NULL,
image_order INTEGER NOT NULL DEFAULT 0
");
$database->create_table("pool_history", "
id SCORE_AIPK,
pool_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
action INTEGER NOT NULL,
images TEXT,
count INTEGER NOT NULL DEFAULT 0,
date DATETIME NOT NULL,
INDEX (id)
");
$config->set_int("ext_pools_version", 1);
$config->set_int("poolsMaxImportResults", 1000);
$config->set_int("poolsImagesPerPage", 20);
$config->set_int("poolsListsPerPage", 20);
$config->set_int("poolsUpdatedPerPage", 20);
$config->set_bool("poolsInfoOnViewImage", "N");
$config->set_bool("poolsAdderOnViewImage", "N");
log_info("pools", "extension installed");
}
}
public function onSetupBuilding(SetupBuildingEvent $event) {
$sb = new SetupBlock("Pools");
$sb->add_int_option("poolsMaxImportResults", "Max results on import: ");
$sb->add_int_option("poolsImagesPerPage", "<br>Images per page: ");
$sb->add_int_option("poolsListsPerPage", "<br>Index list items per page: ");
$sb->add_int_option("poolsUpdatedPerPage", "<br>Updated list items per page: ");
$sb->add_bool_option("poolsInfoOnViewImage", "<br>Show pool info on image: ");
//$sb->add_bool_option("poolsAdderOnViewImage", "<br>Show pool adder on image: ");
$event->panel->add_block($sb);
}
public function onPageRequest($event) {
global $config, $page, $user;
if($event->page_matches("pool")) {
switch($event->get_arg(0)) {
case "list": //index
$this->list_pools($page, int_escape($event->get_arg(1)));
break;
case "new": // Show form
if(!$user->is_anonymous()){
$this->theme->new_pool_composer($page);
} else {
$errMessage = "You must be registered and logged in to create a new pool.";
$this->theme->display_error($errMessage);
}
break;
case "create": // ADD _POST
try {
$newPoolID = $this->add_pool();
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$newPoolID));
}
catch(PoolCreationException $pce) {
$this->theme->display_error($pce->getMessage());
}
break;
case "view":
$poolID = int_escape($event->get_arg(1));
$this->get_posts($event, $poolID);
break;
case "updated":
$this->get_history(int_escape($event->get_arg(1)));
break;
case "revert":
if(!$user->is_anonymous()) {
$historyID = int_escape($event->get_arg(1));
$this->revert_history($historyID);
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/updated"));
}
break;
case "edit":
$poolID = int_escape($event->get_arg(1));
$pools = $this->get_pool($poolID);
foreach($pools as $pool) {
// if the pool is public and user is logged OR if the user is admin OR the user is the owner
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->theme->edit_pool($page, $this->get_pool($poolID), $this->edit_posts($poolID));
} else {
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$poolID));
}
}
break;
case "order":
if($_SERVER["REQUEST_METHOD"] == "GET") {
$poolID = int_escape($event->get_arg(1));
$pools = $this->get_pool($poolID);
foreach($pools as $pool) {
//if the pool is public and user is logged OR if the user is admin
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->theme->edit_order($page, $this->get_pool($poolID), $this->edit_order($poolID));
} else {
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$poolID));
}
}
}
else {
$pool_id = int_escape($_POST["pool_id"]);
$pool = $this->get_single_pool($pool_id);
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->order_posts();
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$pool_id));
} else {
$this->theme->display_error("Permssion denied.");
}
}
break;
case "import":
$pool_id = int_escape($_POST["pool_id"]);
$pool = $this->get_single_pool($pool_id);
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->import_posts();
} else {
$this->theme->display_error("Permssion denied.");
}
break;
case "add_posts":
$pool_id = int_escape($_POST["pool_id"]);
$pool = $this->get_single_pool($pool_id);
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->add_posts();
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$pool_id));
} else {
$this->theme->display_error("Permssion denied.");
}
break;
case "remove_posts":
$pool_id = int_escape($_POST["pool_id"]);
$pool = $this->get_single_pool($pool_id);
if(($pool['public'] == "Y" && !$user->is_anonymous()) || $user->is_admin() || $user->id == $pool['user_id']) {
$this->remove_posts();
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/view/".$pool_id));
} else {
$this->theme->display_error("Permssion denied.");
}
break;
case "nuke":
$pool_id = int_escape($_POST['pool_id']);
$pool = $this->get_single_pool($pool_id);
// only admins and owners may do this
if($user->is_admin() || $user->id == $pool['user_id']) {
$this->nuke_pool($pool_id);
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/list"));
} else {
$this->theme->display_error("Permssion denied.");
}
break;
default:
$page->set_mode("redirect");
$page->set_redirect(make_link("pool/list"));
break;
}
}
}
public function onUserBlockBuilding($event) {
$event->add_link("Pools", make_link("pool/list"));
}
/*
* HERE WE GET THE POOLS WHERE THE IMAGE APPEARS WHEN THE IMAGE IS DISPLAYED
*/
public function onDisplayingImage($event) {
global $config, $database, $page;
if($config->get_bool("poolsInfoOnViewImage")) {
$imageID = $event->image->id;
$poolsIDs = $this->get_pool_id($imageID);
$linksPools = array();
foreach($poolsIDs as $poolID) {
$pools = $this->get_pool($poolID['pool_id']);
foreach ($pools as $pool){
$linksPools[] = "<a href='".make_link("pool/view/".$pool['id'])."'>".html_escape($pool['title'])."</a>";
}
}
$this->theme->pool_info($linksPools);
}
}
public function onImageAdminBlockBuilding($event) {
global $config, $database, $user;
if($config->get_bool("poolsAdderOnViewImage") && !$user->is_anonymous()) {
if($user->is_admin()) {
$pools = $database->get_all("SELECT * FROM pools");
}
else {
$pools = $database->get_all("SELECT * FROM pools WHERE user_id=?", array($user->id));
}
if(count($pools) > 0) {
$event->add_part($this->theme->get_adder_html($event->image, $pools));
}
}
}
/*
* HERE WE GET THE LIST OF POOLS
*/
private function list_pools(Page $page, $pageNumber) {
global $config, $database;
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
$poolsPerPage = $config->get_int("poolsListsPerPage");
$pools = $database->get_all("
SELECT p.id, p.user_id, p.public, p.title, p.description,
p.posts, u.name as user_name
FROM pools AS p
INNER JOIN users AS u
ON p.user_id = u.id
ORDER BY p.date DESC
LIMIT ? OFFSET ?
", array($poolsPerPage, $pageNumber * $poolsPerPage)
);
$totalPages = ceil($database->db->GetOne("SELECT COUNT(*) FROM pools") / $poolsPerPage);
$this->theme->list_pools($page, $pools, $pageNumber + 1, $totalPages);
}
/*
* HERE WE CREATE A NEW POOL
*/
private function add_pool() {
global $user, $database;
if($user->is_anonymous()) {
throw new PoolCreationException("You must be registered and logged in to add a image.");
}
if(empty($_POST["title"])) {
throw new PoolCreationException("Pool needs a title");
}
$public = $_POST["public"] == "Y" ? "Y" : "N";
$database->execute("
INSERT INTO pools (user_id, public, title, description, date)
VALUES (?, ?, ?, ?, now())",
array($user->id, $public, $_POST["title"], $_POST["description"]));
$result = $database->get_row("SELECT LAST_INSERT_ID() AS poolID"); # FIXME database specific?
log_info("pools", "Pool {$result["poolID"]} created by {$user->name}");
return $result["poolID"];
}
private function get_pool($poolID) {
global $database;
return $database->get_all("SELECT * FROM pools WHERE id=?", array($poolID));
}
private function get_single_pool($poolID) {
global $database;
return $database->get_row("SELECT * FROM pools WHERE id=?", array($poolID));
}
/*
* HERE WE GET THE ID OF THE POOL FROM AN IMAGE
*/
private function get_pool_id($imageID) {
global $database;
return $database->get_all("SELECT pool_id FROM pool_images WHERE image_id=?", array($imageID));
}
/*
* HERE WE GET THE IMAGES FROM THE TAG ON IMPORT
*/
private function import_posts() {
global $page, $config, $database;
$pool_id = int_escape($_POST["pool_id"]);
$poolsMaxResults = $config->get_int("poolsMaxImportResults", 1000);
$images = $images = Image::find_images(0, $poolsMaxResults, Tag::explode($_POST["pool_tag"]));
$this->theme->pool_result($page, $images, $pool_id);
}
/*
* HERE WE ADD CHECKED IMAGES FROM POOL AND UPDATE THE HISTORY
*/
private function add_posts() {
global $database;
$poolID = int_escape($_POST['pool_id']);
$images = "";
foreach ($_POST['check'] as $imageID){
if(!$this->check_post($poolID, $imageID)){
$database->execute("
INSERT INTO pool_images (pool_id, image_id)
VALUES (?, ?)",
array($poolID, $imageID));
$images .= " ".$imageID;
}
}
if(!strlen($images) == 0) {
$count = $database->db->GetOne("SELECT COUNT(*) FROM pool_images WHERE pool_id=?", array($poolID));
$this->add_history($poolID, 1, $images, $count);
}
$database->Execute("
UPDATE pools
SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=?)
WHERE id=?",
array($poolID, $poolID)
);
return $poolID;
}
private function order_posts() {
global $database;
$poolID = int_escape($_POST['pool_id']);
foreach($_POST['imgs'] as $data) {
list($imageORDER, $imageID) = $data;
$database->Execute("
UPDATE pool_images
SET image_order = ?
WHERE pool_id = ? AND image_id = ?",
array($imageORDER, $poolID, $imageID)
);
}
return $poolID;
}
/*
* HERE WE REMOVE CHECKED IMAGES FROM POOL AND UPDATE THE HISTORY
*/
private function remove_posts() {
global $database;
$poolID = int_escape($_POST['pool_id']);
$images = "";
foreach($_POST['check'] as $imageID) {
$database->execute("DELETE FROM pool_images WHERE pool_id = ? AND image_id = ?", array($poolID, $imageID));
$images .= " ".$imageID;
}
$count = $database->db->GetOne("SELECT COUNT(*) FROM pool_images WHERE pool_id=?", array($poolID));
$this->add_history($poolID, 0, $images, $count);
return $poolID;
}
/*
* HERE WE CHECK IF THE POST IS ALREADY ON POOL
* USED IN add_posts()
*/
private function check_post($poolID, $imageID) {
global $database;
$result = $database->db->GetOne("SELECT COUNT(*) FROM pool_images WHERE pool_id=? AND image_id=?", array($poolID, $imageID));
return ($result != 0);
}
/*
* HERE WE GET ALL IMAGES FOR THE POOL
*/
private function get_posts($event, $poolID) {
global $config, $user, $database;
$pageNumber = int_escape($event->get_arg(2));
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
$poolID = int_escape($poolID);
$imagesPerPage = $config->get_int("poolsImagesPerPage");
// WE CHECK IF THE EXTENSION RATING IS INSTALLED, WHICH VERSION AND IF IT
// WORKS TO SHOW/HIDE SAFE, QUESTIONABLE, EXPLICIT AND UNRATED IMAGES FROM USER
if(class_exists("Ratings")) {
$rating = Ratings::privs_to_sql(Ratings::get_user_privs($user));
$result = $database->get_all("
SELECT p.image_id
FROM pool_images AS p
INNER JOIN images AS i ON i.id = p.image_id
WHERE p.pool_id = ? AND i.rating IN ($rating)
ORDER BY p.image_order ASC
LIMIT ? OFFSET ?",
array($poolID, $imagesPerPage, $pageNumber * $imagesPerPage));
$totalPages = ceil($database->db->GetOne("
SELECT COUNT(*)
FROM pool_images AS p
INNER JOIN images AS i ON i.id = p.image_id
WHERE pool_id=? AND i.rating IN ($rating)",
array($poolID)) / $imagesPerPage);
}
else {
$result = $database->get_all("
SELECT image_id
FROM pool_images
WHERE pool_id=?
ORDER BY image_order ASC
LIMIT ? OFFSET ?",
array($poolID, $imagesPerPage, $pageNumber * $imagesPerPage));
$totalPages = ceil($database->db->GetOne("SELECT COUNT(*) FROM pool_images WHERE pool_id=?", array($poolID)) / $imagesPerPage);
}
$images = array();
foreach($result as $singleResult) {
$images[] = Image::by_id($singleResult["image_id"]);
}
$pool = $this->get_pool($poolID);
$this->theme->view_pool($pool, $images, $pageNumber + 1, $totalPages);
}
/*
* WE GET THE ORDER OF THE IMAGES
*/
private function edit_posts($poolID) {
global $database;
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=? ORDER BY image_order ASC", array($poolID));
$images = array();
while(!$result->EOF) {
$image = Image::by_id($result->fields["image_id"]);
$images[] = array($image);
$result->MoveNext();
}
return $images;
}
/*
* WE GET THE ORDER OF THE IMAGES BUT HERE WE SEND KEYS ADDED IN ARRAY TO GET THE ORDER IN THE INPUT VALUE
*/
private function edit_order($poolID) {
global $database;
$result = $database->Execute("SELECT image_id FROM pool_images WHERE pool_id=? ORDER BY image_order ASC", array($poolID));
$images = array();
while(!$result->EOF) {
$image = $database->get_row("
SELECT * FROM images AS i
INNER JOIN pool_images AS p ON i.id = p.image_id
WHERE pool_id=? AND i.id=?",
array($poolID, $result->fields["image_id"]));
$image = ($image ? new Image($image) : null);
$images[] = array($image);
$result->MoveNext();
}
// Original code
//
// $images = array();
// while(!$result->EOF) {
// $image = Image::by_id($result->fields["image_id"]);
// $images[] = array($image);
// $result->MoveNext();
// }
return $images;
}
/*
* HERE WE NUKE ENTIRE POOL. WE REMOVE POOLS AND POSTS FROM REMOVED POOL AND HISTORIES ENTRIES FROM REMOVED POOL
*/
private function nuke_pool($poolID) {
global $user, $database;
if($user->is_admin()) {
$database->execute("DELETE FROM pool_history WHERE pool_id = ?", array($poolID));
$database->execute("DELETE FROM pool_images WHERE pool_id = ?", array($poolID));
$database->execute("DELETE FROM pools WHERE id = ?", array($poolID));
} elseif(!$user->is_anonymous()) {
// FIXME: WE CHECK IF THE USER IS THE OWNER OF THE POOL IF NOT HE CAN'T DO ANYTHING
$database->execute("DELETE FROM pool_history WHERE pool_id = ?", array($poolID));
$database->execute("DELETE FROM pool_images WHERE pool_id = ?", array($poolID));
$database->execute("DELETE FROM pools WHERE id = ? AND user_id = ?", array($poolID, $user->id));
}
}
/*
* HERE WE ADD A HISTORY ENTRY
* FOR $action 1 (one) MEANS ADDED, 0 (zero) MEANS REMOVED
*/
private function add_history($poolID, $action, $images, $count) {
global $user, $database;
$database->execute("
INSERT INTO pool_history (pool_id, user_id, action, images, count, date)
VALUES (?, ?, ?, ?, ?, now())",
array($poolID, $user->id, $action, $images, $count));
}
/*
* HERE WE GET THE HISTORY LIST
*/
private function get_history($pageNumber) {
global $config, $database;
if(is_null($pageNumber) || !is_numeric($pageNumber))
$pageNumber = 0;
else if ($pageNumber <= 0)
$pageNumber = 0;
else
$pageNumber--;
$historiesPerPage = $config->get_int("poolsUpdatedPerPage");
$history = $database->get_all("
SELECT h.id, h.pool_id, h.user_id, h.action, h.images,
h.count, h.date, u.name as user_name, p.title as title
FROM pool_history AS h
INNER JOIN pools AS p
ON p.id = h.pool_id
INNER JOIN users AS u
ON h.user_id = u.id
ORDER BY h.date DESC
LIMIT ? OFFSET ?
", array($historiesPerPage, $pageNumber * $historiesPerPage));
$totalPages = ceil($database->db->GetOne("SELECT COUNT(*) FROM pool_history") / $historiesPerPage);
$this->theme->show_history($history, $pageNumber + 1, $totalPages);
}
/*
* HERE GO BACK IN HISTORY AND ADD OR REMOVE POSTS TO POOL
*/
private function revert_history($historyID) {
global $database;
$status = $database->get_all("SELECT * FROM pool_history WHERE id=?", array($historyID));
foreach($status as $entry) {
$images = trim($entry['images']);
$images = explode(" ", $images);
$poolID = $entry['pool_id'];
$imageArray = "";
if($entry['action'] == 0) {
// READ ENTRIES
foreach($images as $image) {
$imageID = $image;
$this->add_post($poolID, $imageID);
$imageArray .= " ".$imageID;
$newAction = 1;
}
}
else if($entry['action'] == 1) {
// DELETE ENTRIES
foreach($images as $image) {
$imageID = $image;
$this->delete_post($poolID, $imageID);
$imageArray .= " ".$imageID;
$newAction = 0;
}
}
$count = $database->db->GetOne("SELECT COUNT(*) FROM pool_images WHERE pool_id=?", array($poolID));
$this->add_history($poolID, $newAction, $imageArray, $count);
}
}
/*
* HERE WE ADD A SIMPLE POST FROM POOL
* USED WITH FOREACH IN revert_history()
*/
private function add_post($poolID, $imageID) {
global $database;
if(!$this->check_post($poolID, $imageID)) {
$database->execute("
INSERT INTO pool_images (pool_id, image_id)
VALUES (?, ?)",
array($poolID, $imageID));
}
$database->execute("UPDATE pools SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=?) WHERE id=?", array($poolID, $poolID));
}
/*
* HERE WE REMOVE A SIMPLE POST FROM POOL
* USED WITH FOREACH IN revert_history()
*/
private function delete_post($poolID, $imageID) {
global $database;
$database->execute("DELETE FROM pool_images WHERE pool_id = ? AND image_id = ?", array($poolID, $imageID));
$database->execute("UPDATE pools SET posts=(SELECT COUNT(*) FROM pool_images WHERE pool_id=?) WHERE id=?", array($poolID, $poolID));
}
}
?>

41
contrib/pools/test.php Normal file
View File

@ -0,0 +1,41 @@
<?php
class PoolsTest extends ShimmieWebTestCase {
function testPools() {
$this->get_page('pool/list');
$this->assert_title("Pools");
$this->get_page('pool/new');
$this->assert_title("Error");
$this->log_in_as_user();
$this->get_page('pool/list');
$this->click("Create Pool");
$this->assert_title("Create Pool");
$this->click("Create");
$this->assert_title("Error");
$this->get_page('pool/new');
$this->assert_title("Create Pool");
$this->set_field("title", "Test Pool Title");
$this->set_field("description", "Test pool description");
$this->click("Create");
$this->assert_title("Pool: Test Pool Title");
$this->log_out();
$this->log_in_as_admin();
$this->get_page('pool/list');
$this->click("Test Pool Title");
$this->assert_title("Pool: Test Pool Title");
$this->click("Delete Pool");
$this->assert_title("Pools");
$this->assert_no_text("Test Pool Title");
$this->log_out();
}
}
?>

406
contrib/pools/theme.php Normal file
View File

@ -0,0 +1,406 @@
<?php
class PoolsTheme extends Themelet {
/*
* HERE WE ADD THE POOL INFO ON IMAGE
*/
public function pool_info($linksPools) {
global $page;
if(count($linksPools) > 0) {
$page->add_block(new Block("Pools", implode("<br>", $linksPools), "left"));
}
}
public function get_adder_html(Image $image, $pools) {
$editor = "";
$h = "";
foreach($pools as $pool) {
$h .= "<option value='".$pool['id']."'>".html_escape($pool['title'])."</option>";
}
$editor = "
".make_form(make_link("pool/add_post"))."
<select name='pool_id'>
$h
</select>
<input type='hidden' name='image_id' value='{$image->id}'>
<input type='submit' value='Add Image to Pool'>
</form>
";
return $editor;
}
/*
* HERE WE SHOWS THE LIST OF POOLS
*/
public function list_pools(Page $page, $pools, $pageNumber, $totalPages) {
global $user;
$html = '<table id="poolsList" class="zebra">'.
"<thead><tr>".
"<th>Name</th>".
"<th>Creator</th>".
"<th>Posts</th>".
"<th>Public</th>".
"</tr></thead>";
$n = 0;
foreach($pools as $pool) {
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$pool_link = '<a href="'.make_link("pool/view/".$pool['id']).'">'.html_escape($pool['title'])."</a>";
$user_link = '<a href="'.make_link("user/".url_escape($pool['user_name'])).'">'.html_escape($pool['user_name'])."</a>";
$public = ($pool['public'] == "Y" ? "Yes" : "No");
$html .= "<tr class='$oe'>".
"<td class='left'>".$pool_link."</td>".
"<td>".$user_link."</td>".
"<td>".$pool['posts']."</td>".
"<td>".$public."</td>".
"</tr>";
}
$html .= "</tbody></table>";
$nav_html = "
<a href=".make_link().">Index</a>
<br><a href=".make_link("pool/new").">Create Pool</a>
<br><a href=".make_link("pool/updated").">Pool Changes</a>
";
$blockTitle = "Pools";
$page->set_title(html_escape($blockTitle));
$page->set_heading(html_escape($blockTitle));
$page->add_block(new Block($blockTitle, $html, "main", 10));
$page->add_block(new Block("Navigation", $nav_html, "left", 10));
$this->display_paginator($page, "pool/list", null, $pageNumber, $totalPages);
}
/*
* HERE WE DISPLAY THE NEW POOL COMPOSER
*/
public function new_pool_composer(Page $page) {
$create_html = "
".make_form(make_link("pool/create"))."
<table>
<tr><td>Title:</td><td><input type='text' name='title'></td></tr>
<tr><td>Public?</td><td><input name='public' type='checkbox' value='Y' checked='checked'/></td></tr>
<tr><td>Description:</td><td><textarea name='description'></textarea></td></tr>
<tr><td colspan='2'><input type='submit' value='Create' /></td></tr>
</table>
</form>
";
$blockTitle = "Create Pool";
$page->set_title(html_escape($blockTitle));
$page->set_heading(html_escape($blockTitle));
$page->add_block(new Block("Create Pool", $create_html, "main", 20));
}
private function display_top($pools, $heading, $check_all=false) {
global $page, $user;
$page->set_title($heading);
$page->set_heading($heading);
if(count($pools) == 1) {
$pool = $pools[0];
if($pool['public'] == "Y" || $user->is_admin()) {// IF THE POOL IS PUBLIC OR IS ADMIN SHOW EDIT PANEL
if(!$user->is_anonymous()) {// IF THE USER IS REGISTERED AND LOGGED IN SHOW EDIT PANEL
$this->sidebar_options($page, $pool, $check_all);
}
}
$page->add_block(new Block(html_escape($pool['title']), html_escape($pool['description']), "main", 10));
}
else {
$pool_info = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th class='left'>Title</th>".
"<th class='left'>Description</th>".
"</tr></thead>";
$n = 0;
foreach($pools as $pool) {
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$pool_info .= "<tr class='$oe'>".
"<td class='left'>".html_escape($pool['title'])."</td>".
"<td class='left'>".html_escape($pool['description'])."</td>".
"</tr>";
// this will make disasters if more than one pool comes in the parameter
if($pool['public'] == "Y" || $user->is_admin()) {// IF THE POOL IS PUBLIC OR IS ADMIN SHOW EDIT PANEL
if(!$user->is_anonymous()) {// IF THE USER IS REGISTERED AND LOGGED IN SHOW EDIT PANEL
$this->sidebar_options($page, $pool, $check_all);
}
}
}
$pool_info .= "</tbody></table>";
$page->add_block(new Block($heading, $pool_info, "main", 10));
}
}
/*
* HERE WE DISPLAY THE POOL WITH TITLE DESCRIPTION AND IMAGES WITH PAGINATION
*/
public function view_pool($pools, $images, $pageNumber, $totalPages) {
global $user, $page;
$this->display_top($pools, "Pool: ".html_escape($pools[0]['title']));
$pool_images = '';
foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'</span>';
}
$page->add_block(new Block("Viewing Posts", $pool_images, "main", 30));
$this->display_paginator($page, "pool/view/".$pools[0]['id'], null, $pageNumber, $totalPages);
}
/*
* HERE WE DISPLAY THE POOL OPTIONS ON SIDEBAR BUT WE HIDE REMOVE OPTION IF THE USER IS NOT THE OWNER OR ADMIN
*/
public function sidebar_options(Page $page, $pool, $check_all) {
global $user;
$editor = "
".make_form(make_link("pool/import"))."
<input type='text' name='pool_tag' id='edit' value='Please enter a tag' onclick='this.value=\"\";'/>
<input type='submit' name='edit' id='edit' value='Import'/>
<input type='hidden' name='pool_id' value='".$pool['id']."'>
</form>
<form method='GET' action='".make_link("pool/edit/".$pool['id'])."'>
<input type='submit' name='edit' id='edit' value='Edit Pool'/>
</form>
<form method='GET' action='".make_link("pool/order/".$pool['id'])."'>
<input type='submit' name='edit' id='edit' value='Order Pool'/>
</form>
";
if($user->id == $pool['user_id'] || $user->is_admin()){
$editor .= "
<script type='text/javascript'>
function confirm_action() {
return confirm('Are you sure that you want to delete this pool?');
}
</script>
".make_form(make_link("pool/nuke"))."
<input type='submit' name='delete' id='delete' value='Delete Pool' onclick='return confirm_action()' />
<input type='hidden' name='pool_id' value='".$pool['id']."'>
</form>
";
}
if($check_all) {
$editor .= "
<script language='JavaScript' type='text/javascript'>
function setAll(value) {
var a=new Array();
a=document.getElementsByName('check[]');
var p=0;
for(i=0;i<a.length;i++){
a[i].checked = value;
}
}
</script>
<br><input type='button' name='CheckAll' value='Check All' onClick='setAll(true)'>
<input type='button' name='UnCheckAll' value='Uncheck All' onClick='setAll(false)'>
";
}
$page->add_block(new Block("Manage Pool", $editor, "left", 10));
}
/*
* HERE WE DISPLAY THE RESULT OF THE SEARCH ON IMPORT
*/
public function pool_result(Page $page, $images, $pool_id) {
$pool_images = "
<script language='JavaScript' type='text/javascript'>
function setAll(value) {
var a=new Array();
a=document.getElementsByName('check[]');
var p=0;
for(i=0;i<a.length;i++) {
a[i].checked = value;
}
}
function confirm_action() {
return confirm('Are you sure you want to add selected posts to this pool?');
}
</script>
";
$pool_images .= "<form action='".make_link("pool/add_posts")."' method='POST' name='checks'>";
foreach($images as $image) {
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'<br>'.
'<input name="check[]" type="checkbox" value="'.$image->id.'" />'.
'</span>';
}
$pool_images .= "<br>".
"<input type='submit' name='edit' id='edit' value='Add Selected' onclick='return confirm_action()'/>".
"<input type='hidden' name='pool_id' value='".$pool_id."'>".
"</form>";
$page->add_block(new Block("Import", $pool_images, "main", 10));
$editor = "
<input type='button' name='CheckAll' value='Check All' onClick='setAll(true)'>
<input type='button' name='UnCheckAll' value='Uncheck All' onClick='setAll(false)'>
";
$page->add_block(new Block("Manage Pool", $editor, "left", 10));
}
/*
* HERE WE DISPLAY THE POOL ORDERER
* WE LIST ALL IMAGES ON POOL WITHOUT PAGINATION AND WITH A TEXT INPUT TO SET A NUMBER AND CHANGE THE ORDER
*/
public function edit_order(Page $page, $pools, $images) {
global $user;
$this->display_top($pools, "Sorting Pool");
$pool_images = "<form action='".make_link("pool/order")."' method='POST' name='checks'>";
$n = 0;
foreach($images as $pair) {
$image = $pair[0];
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'<br><input name="imgs['.$n.'][]" type="text" style="max-width:50px;" value="'.$image->image_order.'" />'.
'<input name="imgs['.$n.'][]" type="hidden" value="'.$image->id.'" />'.
'</span>';
$n++;
}
$pool_images .= "<br>".
"<input type='submit' name='edit' id='edit' value='Order'/>".
"<input type='hidden' name='pool_id' value='".$pools[0]['id']."'>".
"</form>";
$page->add_block(new Block("Sorting Posts", $pool_images, "main", 30));
}
/*
* HERE WE DISPLAY THE POOL EDITOR
* WE LIST ALL IMAGES ON POOL WITHOUT PAGINATION AND WITH
* A CHECKBOX TO SELECT WHICH IMAGE WE WANT TO REMOVE
*/
public function edit_pool(Page $page, $pools, $images) {
global $user;
$this->display_top($pools, "Editing Pool", true);
$pool_images = "
";
$pool_images = "<form action='".make_link("pool/remove_posts")."' method='POST' name='checks'>";
foreach($images as $pair) {
$image = $pair[0];
$thumb_html = $this->build_thumb_html($image);
$pool_images .= '<span class="thumb">'.
'<a href="$image_link">'.$thumb_html.'</a>'.
'<br><input name="check[]" type="checkbox" value="'.$image->id.'" />'.
'</span>';
}
$pool_images .= "<br>".
"<input type='submit' name='edit' id='edit' value='Remove Selected'/>".
"<input type='hidden' name='pool_id' value='".$pools[0]['id']."'>".
"</form>";
$page->add_block(new Block("Editing Posts", $pool_images, "main", 30));
}
/*
* HERE WE DISPLAY THE HISTORY LIST
*/
public function show_history($histories, $pageNumber, $totalPages) {
global $page;
$html = "<table id='poolsList' class='zebra'>".
"<thead><tr>".
"<th>Pool</th>".
"<th>Post Count</th>".
"<th>Changes</th>".
"<th>Updater</th>".
"<th>Date</th>".
"<th>Action</th>".
"</tr></thead>";
$n = 0;
foreach($histories as $history) {
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$pool_link = "<a href='".make_link("pool/view/".$history['pool_id'])."'>".html_escape($history['title'])."</a>";
$user_link = "<a href='".make_link("user/".url_escape($history['user_name']))."'>".html_escape($history['user_name'])."</a>";
$revert_link = "<a href='".make_link("pool/revert/".$history['id'])."'>Revert</a>";
if ($history['action'] == 1) {
$prefix = "+";
} elseif ($history['action'] == 0) {
$prefix = "-";
}
$images = trim($history['images']);
$images = explode(" ", $images);
$image_link = "";
foreach ($images as $image) {
$image_link .= "<a href='".make_link("post/view/".$image)."'>".$prefix.$image." </a>";
}
$html .= "<tr class='$oe'>".
"<td class='left'>".$pool_link."</td>".
"<td>".$history['count']."</td>".
"<td>".$image_link."</td>".
"<td>".$user_link."</td>".
"<td>".$history['date']."</td>".
"<td>".$revert_link."</td>".
"</tr>";
}
$html .= "</tbody></table>";
$page->set_title("Recent Changes");
$page->set_heading("Recent Changes");
$page->add_block(new Block("Recent Changes", $html, "main", 10));
$this->display_paginator($page, "pool/updated", null, $pageNumber, $totalPages);
}
/*
* HERE WE DISPLAY THE ERROR
*/
public function display_error($errMessage) {
global $page;
$page->set_title("Error");
$page->set_heading("Error");
$page->add_block(new Block("Error", $errMessage, "main", 10));
}
}
?>

15
contrib/qr_code/main.php Normal file
View File

@ -0,0 +1,15 @@
<?php
/*
* Name: QR Codes
* Author: Zach Hall <zach@sosguy.net> [http://seemslegit.com]
* Description: Shows a QR Code for downloading an image to cell phones.
* Based on Artanis's Link to Image Extension <artanis.00@gmail.com>
* Further modified by Shish to remove the 7MB local QR generator
* and replace it with a link to google chart APIs
*/
class QRImage extends SimpleExtension {
public function onDisplayingImage($event) {
$this->theme->links_block(make_http(make_link('image/'.$event->image->id.'.jpg')));
}
}
?>

View File

@ -0,0 +1,9 @@
<?php
class QRImageTheme extends Themelet {
public function links_block($link) {
global $page;
$page->add_block( new Block(
"QR Code","<img src='http://chart.apis.google.com/chart?chs=150x150&cht=qr&chl=$link' />","left",50));
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Random Image
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -16,17 +16,13 @@
* <br>Adding a slash and some search terms will get a random image
* from those results. This can be useful if you want a specific size
* of random image, or from a category. You could link to
* <code>/random_image/download/size:1024x768+cute</code>
* <code>/random_image/download/size=1024x768+cute</code>
*/
class RandomImage implements Extension {
var $theme;
public function receive_event(Event $event) {
class RandomImage extends SimpleExtension {
public function onPageRequest($event) {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof PageRequestEvent) && $event->page_matches("random_image")) {
if($event->page_matches("random_image")) {
if($event->count_args() == 1) {
$action = $event->get_arg(0);
$search_terms = array();
@ -50,22 +46,22 @@ class RandomImage implements Extension {
}
}
}
}
if(($event instanceof SetupBuildingEvent)) {
$sb = new SetupBlock("Random Image");
$sb->add_bool_option("show_random_block", "Show Random Block: ");
$event->panel->add_block($sb);
}
public function onSetupBuilding($event) {
$sb = new SetupBlock("Random Image");
$sb->add_bool_option("show_random_block", "Show Random Block: ");
$event->panel->add_block($sb);
}
if($event instanceof PostListBuildingEvent) {
if($config->get_bool("show_random_block")) {
$image = Image::by_random($event->search_terms);
if(!is_null($image)) {
$this->theme->display_random($page, $image);
}
public function onPostListBuilding($event) {
global $config, $page;
if($config->get_bool("show_random_block")) {
$image = Image::by_random($event->search_terms);
if(!is_null($image)) {
$this->theme->display_random($page, $image);
}
}
}
}
add_event_listener(new RandomImage());
?>

View File

@ -0,0 +1,19 @@
<?php
class RandomTest extends ShimmieWebTestCase {
function testRandom() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "test");
$this->log_out();
$this->get_page("random_image/view");
$this->assert_title("Image $image_id: test");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
# FIXME: test random_image/download
# FIXME: test random_image/ratio=4:3/download
}
}
?>

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Image Ratings
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -7,10 +7,11 @@
*/
class RatingSetEvent extends Event {
var $image_id, $user, $rating;
var $image, $user, $rating;
public function RatingSetEvent($image_id, $user, $rating) {
$this->image_id = $image_id;
public function RatingSetEvent(Image $image, User $user, $rating) {
assert(in_array($rating, array("s", "q", "e", "u")));
$this->image = $image;
$this->user = $user;
$this->rating = $rating;
}
@ -23,40 +24,73 @@ class Ratings implements Extension {
global $config, $database, $page, $user;
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if($event instanceof AdminBuildingEvent) {
$this->theme->display_bulk_rater();
}
if(($event instanceof PageRequestEvent) && $event->page_matches("admin/bulk_rate")) {
global $database, $user, $page;
if(!$user->is_admin()) {
throw PermissionDeniedException();
}
else {
$n = 0;
while(true) {
$images = Image::find_images($n, 100, Tag::explode($_POST["query"]));
if(count($images) == 0) break;
foreach($images as $image) {
send_event(new RatingSetEvent($image, $user, $_POST['rating']));
}
$n += 100;
}
#$database->execute("
# update images set rating=? where images.id in (
# select image_id from image_tags join tags
# on image_tags.tag_id = tags.id where tags.tag = ?);
# ", array($_POST["rating"], $_POST["tag"]));
$page->set_mode("redirect");
$page->set_redirect(make_link("admin"));
}
}
if($event instanceof InitExtEvent) {
if($config->get_int("ext_ratings2_version") < 2) {
$this->install();
}
$config->set_default_string("ext_rating_anon_privs", 'sq');
$config->set_default_string("ext_rating_user_privs", 'sq');
$config->set_default_string("ext_rating_anon_privs", 'squ');
$config->set_default_string("ext_rating_user_privs", 'sqeu');
$config->set_default_string("ext_rating_admin_privs", 'sqeu');
}
if($event instanceof RatingSetEvent) {
$this->set_rating($event->image_id, $event->rating);
$this->set_rating($event->image->id, $event->rating);
}
if($event instanceof ImageInfoBoxBuildingEvent) {
if($user->is_admin()) {
if($this->can_rate()) {
$event->add_part($this->theme->get_rater_html($event->image->id, $event->image->rating), 80);
}
}
if($event instanceof ImageInfoSetEvent) {
if($user->is_admin()) {
send_event(new RatingSetEvent($event->image->id, $user, $_POST['rating']));
if($this->can_rate() && isset($_POST["rating"])) {
send_event(new RatingSetEvent($event->image, $user, $_POST['rating']));
}
}
if($event instanceof SetupBuildingEvent) {
$privs = array();
$privs['Safe Only'] = 's';
$privs['Safe and Unknown'] = 'su';
$privs['Safe and Questionable'] = 'sq';
$privs['Safe, Questionable, Unknown'] = 'squ';
$privs['All'] = 'sqeu';
$sb = new SetupBlock("Image Ratings");
$sb->add_choice_option("ext_rating_anon_privs", $privs, "Anonymous: ");
$sb->add_choice_option("ext_rating_user_privs", $privs, "<br>Logged in: ");
$sb->add_choice_option("ext_rating_user_privs", $privs, "<br>Users: ");
$sb->add_choice_option("ext_rating_admin_privs", $privs, "<br>Admins: ");
$event->panel->add_block($sb);
}
@ -67,17 +101,7 @@ class Ratings implements Extension {
if($event instanceof SearchTermParseEvent) {
$matches = array();
if(is_null($event->term) && $this->no_rating_query($event->context)) {
if($user->is_anonymous()) {
$sqes = $config->get_string("ext_rating_anon_privs");
}
else {
$sqes = $config->get_string("ext_rating_user_privs");
}
$arr = array();
for($i=0; $i<strlen($sqes); $i++) {
$arr[] = "'" . $sqes[$i] . "'";
}
$set = join(', ', $arr);
$set = Ratings::privs_to_sql(Ratings::get_user_privs($user));
$event->add_querylet(new Querylet("rating IN ($set)"));
}
if(preg_match("/^rating=([sqeu]+)$/", $event->term, $matches)) {
@ -89,12 +113,71 @@ class Ratings implements Extension {
$set = join(', ', $arr);
$event->add_querylet(new Querylet("rating IN ($set)"));
}
if(preg_match("/^rating=(safe|questionable|explicit|unknown)$/", strtolower($event->term), $matches)) {
$text = $matches[1];
$char = $text[0];
$event->add_querylet(new Querylet("rating = ?", array($char)));
}
}
if($event instanceof DisplayingImageEvent) {
/**
* Deny images upon insufficient permissions.
**/
global $user, $database, $page;
$user_view_level = Ratings::get_user_privs($user);
$user_view_level = preg_split('//', $user_view_level, -1);
if(!in_array($event->image->rating, $user_view_level)) {
$page->set_mode("redirect");
$page->set_redirect(make_link("post/list"));
}
}
}
public static function get_user_privs($user) {
global $config;
if($user->is_anonymous()) {
$sqes = $config->get_string("ext_rating_anon_privs");
}
else if($user->is_admin()) {
$sqes = $config->get_string("ext_rating_admin_privs");
}
else {
$sqes = $config->get_string("ext_rating_user_privs");
}
return $sqes;
}
public static function privs_to_sql($sqes) {
$arr = array();
for($i=0; $i<strlen($sqes); $i++) {
$arr[] = "'" . $sqes[$i] . "'";
}
$set = join(', ', $arr);
return $set;
}
public static function rating_to_human($rating) {
switch($rating) {
case "s": return "Safe";
case "q": return "Questionable";
case "e": return "Explicit";
default: return "Unknown";
}
}
// FIXME: this is a bit ugly and guessey, should have proper options
private function can_rate() {
global $config, $user;
if($user->is_anonymous() && $config->get_string("ext_rating_anon_privs") == "sqeu") return false;
if($user->is_admin()) return true;
if(!$user->is_anonymous() && $config->get_string("ext_rating_user_privs") == "sqeu") return true;
return false;
}
private function no_rating_query($context) {
foreach($context as $term) {
if(preg_match("/^rating=([sqeu]+)$/", $term)) {
if(preg_match("/^rating=/", $term)) {
return false;
}
}

55
contrib/rating/test.php Normal file
View File

@ -0,0 +1,55 @@
<?php
class RatingTest extends ShimmieWebTestCase {
function testRating() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx");
# test for bug #735: user forced to set rating, can't
# set tags and leave unrated
$this->get_page("post/view/$image_id");
$this->assert_title("Image $image_id: pbx");
$this->set_field("tag_edit__tags", "new");
$this->click("Set");
$this->assert_title("Image $image_id: new");
# set safe
$this->set_field("rating", "s");
$this->click("Set");
$this->assert_title("Image $image_id: new");
# search for it in various ways
$this->get_page("post/list/rating=Safe/1");
$this->assert_title("Image $image_id: new");
$this->get_page("post/list/rating=s/1");
$this->assert_title("Image $image_id: new");
$this->get_page("post/list/rating=sqe/1");
$this->assert_title("Image $image_id: new");
# test that search by tag still works
$this->get_page("post/list/new/1");
$this->assert_title("Image $image_id: new");
# searching for a different rating should return nothing
$this->get_page("post/list/rating=q/1");
$this->assert_text("No Images Found");
# now set explicit, for the next test
$this->get_page("post/view/$image_id");
$this->set_field("rating", "e");
$this->click("Set");
$this->assert_title("Image $image_id: new");
$this->log_out();
# the explicit image shouldn't show up in anon's searches
$this->get_page("post/list/new/1");
$this->assert_text("No Images Found");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -19,6 +19,37 @@ class RatingsTheme extends Themelet {
return $html;
}
public function display_bulk_rater() {
global $page;
$html = "
".make_form(make_link("admin/bulk_rate"))."
<table style='width: 300px'>
<tr>
<td>Search</td>
<td>
<input type='text' name='query'>
</td>
</tr>
<tr>
<td>Rating</td>
<td>
<select name='rating'>
<option value='s'>Safe</option>
<option value='q'>Questionable</option>
<option value='e'>Explicit</option>
<option value='u'>Unrated</option>
</select>
</td>
</tr>
<tr>
<td colspan='2'><input type='submit' value='Go'></td>
</tr>
</table>
</form>
";
$page->add_block(new Block("Bulk Rating", $html));
}
public function rating_to_name($rating) {
switch($rating) {
case 's': return "Safe";

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Regen Thumb
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -12,29 +12,22 @@
* since been increased.
*/
class RegenThumb implements Extension {
var $theme;
class RegenThumb extends SimpleExtension {
public function onPageRequest($event) {
global $config, $database, $page, $user;
public function receive_event(Event $event) {
if(is_null($this->theme)) $this->theme = get_theme_object($this);
if(($event instanceof PageRequestEvent) && $event->page_matches("regen_thumb")) {
global $user;
if($user->is_admin() && isset($_POST['image_id'])) {
global $config;
global $database;
$image = Image::by_id(int_escape($_POST['image_id']));
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext));
$this->theme->display_results($event->page, $image);
}
if($event->page_matches("regen_thumb") && $user->is_admin() && isset($_POST['image_id'])) {
$image = Image::by_id(int_escape($_POST['image_id']));
send_event(new ThumbnailGenerationEvent($image->hash, $image->ext));
$this->theme->display_results($page, $image);
}
}
if($event instanceof ImageAdminBlockBuildingEvent) {
if($event->user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
public function onImageAdminBlockBuilding($event) {
global $user;
if($user->is_admin()) {
$event->add_part($this->theme->get_buttons_html($event->image->id));
}
}
}
add_event_listener(new RegenThumb());
?>

View File

@ -0,0 +1,15 @@
<?php
class RegenThumbTest extends ShimmieWebTestCase {
function testRegenThumb() {
$this->log_in_as_admin();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id");
$this->click("Regenerate");
$this->assert_title("Thumbnail Regenerated");
$this->delete_image($image_id);
$this->log_out();
# FIXME: test that the thumb's modified time has been updated
}
}
?>

View File

@ -6,7 +6,7 @@ class RegenThumbTheme extends Themelet {
*/
public function get_buttons_html($image_id) {
return "
<form action='".make_link("regen_thumb")."' method='POST'>
".make_form(make_link("regen_thumb"))."
<input type='hidden' name='image_id' value='$image_id'>
<input type='submit' value='Regenerate'>
</form>

View File

@ -0,0 +1,40 @@
<?php
class ReportImageTest extends ShimmieWebTestCase {
function testReportImage() {
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->get_page("post/view/$image_id");
$this->set_field('reason', "report details");
$this->click("Report");
$this->log_out();
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_report_image_show_thumbs", true);
$this->click("Save Settings");
$this->get_page("image_report/list");
$this->assert_title("Reported Images");
$this->assert_text("report details");
$this->get_page("setup");
$this->set_field("_config_report_image_show_thumbs", false);
$this->click("Save Settings");
$this->get_page("image_report/list");
$this->assert_title("Reported Images");
$this->assert_text("report details");
$this->assert_text("$image_id");
$this->get_page("image_report/list");
$this->click("Remove Report");
$this->assert_title("Reported Images");
$this->assert_no_text("report details");
$this->delete_image($image_id);
$this->log_out();
# FIXME: test delete image from report screen
# FIXME: test that >>123 works
}
}
?>

View File

@ -15,9 +15,10 @@ class ReportImageTheme extends Themelet {
global $config;
$h_reportedimages = "";
$n = 0;
foreach($reports as $report) {
$image = $report['image'];
$h_reason = html_escape($report['reason']);
$h_reason = format_text($report['reason']);
if($config->get_bool('report_image_show_thumbs')) {
$image_link = $this->build_thumb_html($image);
@ -35,12 +36,13 @@ class ReportImageTheme extends Themelet {
ksort($iabbe->parts);
$actions = join("<br>", $iabbe->parts);
$oe = ($n++ % 2 == 0) ? "even" : "odd";
$h_reportedimages .= "
<tr>
<tr class='$oe'>
<td>{$image_link}</td>
<td>Report by $userlink: $h_reason</td>
<td class='formstretch'>
<form action='".make_link("image_report/remove")."' method='POST'>
".make_form(make_link("image_report/remove"))."
<input type='hidden' name='id' value='{$report['id']}'>
<input type='submit' value='Remove Report'>
</form>
@ -53,12 +55,7 @@ class ReportImageTheme extends Themelet {
$thumb_width = $config->get_int("thumb_width");
$html = "
<style>
.formstretch FORM INPUT {
width: 100%;
}
</style>
<table border='1'>
<table id='reportedimage' class='zebra'>
<thead><td width='$thumb_width'>Image</td><td>Reason</td><td width='128'>Action</td></thead>
$h_reportedimages
</table>
@ -76,9 +73,9 @@ class ReportImageTheme extends Themelet {
$i_image = int_escape($image->id);
$html = "
<form action='".make_link("image_report/add")."' method='POST'>
".make_form(make_link("image_report/add"))."
<input type='hidden' name='image_id' value='$i_image'>
<input type='field' name='reason' value='Please enter a reason' onclick='this.value=\"\";'>
<input type='text' name='reason' value='Please enter a reason' onclick='this.value=\"\";'>
<input type='submit' value='Report'>
</form>
";

View File

@ -1,5 +1,5 @@
<?php
/**
/*
* Name: Resolution Limiter
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
@ -14,20 +14,21 @@ class ResolutionLimit implements Extension {
$max_w = $config->get_int("upload_max_width", -1);
$max_h = $config->get_int("upload_max_height", -1);
$ratios = explode(" ", $config->get_string("upload_ratios", ""));
$ratios = array_filter($ratios, "strlen");
$image = $event->image;
if($min_w > 0 && $image->width < $min_w) throw new UploadException("Image too small");
if($min_h > 0 && $image->height < $min_h) throw new UploadException("Image too small");
if($max_w > 0 && $image->width > $min_w) throw new UploadExceptiono("Image too large");
if($max_h > 0 && $image->height > $min_h) throw new UploadException("Image too large");
if($max_w > 0 && $image->width > $max_w) throw new UploadException("Image too large");
if($max_h > 0 && $image->height > $max_h) throw new UploadException("Image too large");
if(count($ratios) > 0) {
$ok = false;
$valids = 0;
foreach($ratios as $ratio) {
$parts = explode(":", $ratio);
if(count($parts) < 2) continue;
$valids++;
$width = $parts[0];
$height = $parts[1];
if($image->width / $width == $image->height / $height) {
@ -35,7 +36,7 @@ class ResolutionLimit implements Extension {
break;
}
}
if(!$ok) {
if($valids > 0 && !$ok) {
throw new UploadException(
"Image needs to be in one of these ratios: ".
html_escape($config->get_string("upload_ratios", "")));

113
contrib/res_limit/test.php Normal file
View File

@ -0,0 +1,113 @@
<?php
class ResLimitTest extends ShimmieWebTestCase {
function testResLimitOK() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_upload_min_height", "0");
$this->set_field("_config_upload_min_width", "0");
$this->set_field("_config_upload_max_height", "2000");
$this->set_field("_config_upload_max_width", "2000");
$this->set_field("_config_upload_ratios", "4:3 16:9");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assert_response(302);
$this->assert_no_text("Image too large");
$this->assert_no_text("Image too small");
$this->assert_no_text("ratio");
$this->log_out();
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
function testResLimitSmall() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_upload_min_height", "900");
$this->set_field("_config_upload_min_width", "900");
$this->set_field("_config_upload_max_height", "-1");
$this->set_field("_config_upload_max_width", "-1");
$this->set_field("_config_upload_ratios", "4:3 16:9");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assert_response(200);
$this->assert_title("Upload Status");
$this->assert_text("Image too small");
$this->log_out();
# hopefully a noop, but just in case
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
function testResLimitLarge() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_upload_min_height", "0");
$this->set_field("_config_upload_min_width", "0");
$this->set_field("_config_upload_max_height", "100");
$this->set_field("_config_upload_max_width", "100");
$this->set_field("_config_upload_ratios", "4:3 16:9");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assert_response(200);
$this->assert_title("Upload Status");
$this->assert_text("Image too large");
$this->log_out();
# hopefully a noop, but just in case
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
function testResLimitRatio() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_upload_min_height", "-1");
$this->set_field("_config_upload_min_width", "-1");
$this->set_field("_config_upload_max_height", "-1");
$this->set_field("_config_upload_max_width", "-1");
$this->set_field("_config_upload_ratios", "16:9");
$this->click("Save Settings");
$this->log_out();
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->assert_response(200);
$this->assert_title("Upload Status");
$this->assert_text("Image needs to be in one of these ratios");
$this->log_out();
# hopefully a noop, but just in case
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
# reset to defaults, otherwise this can interfere with
# other extensions' test suites
public function tearDown() {
$this->log_in_as_admin();
$this->get_page("setup");
$this->set_field("_config_upload_min_height", "-1");
$this->set_field("_config_upload_min_width", "-1");
$this->set_field("_config_upload_max_height", "-1");
$this->set_field("_config_upload_max_width", "-1");
$this->set_field("_config_upload_ratios", "");
$this->click("Save Settings");
$this->log_out();
}
}
?>

View File

@ -1,79 +1,68 @@
<?php
/**
/*
* Name: RSS for Comments
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Self explanitory
* Description: Self explanatory
*/
class RSS_Comments implements Extension {
// event handling {{{
public function receive_event(Event $event) {
if($event instanceof PostListBuildingEvent) {
global $page;
global $config;
$title = $config->get_string('title');
$page->add_header("<link rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Comments\" href=\"".make_link("rss/comments")."\" />");
}
if(($event instanceof PageRequestEvent) && $event->page_matches("rss")) {
if($event->get_arg(0) == 'comments') {
global $database;
$this->do_rss($database);
}
}
}
// }}}
// output {{{
private function do_rss($database) {
global $page;
global $config;
$page->set_mode("data");
$page->set_type("application/rss+xml");
$comments = $database->get_all("
SELECT
users.id as user_id, users.name as user_name,
comments.comment as comment, comments.id as comment_id,
comments.image_id as image_id, comments.owner_ip as poster_ip,
UNIX_TIMESTAMP(posted) AS posted_timestamp
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
ORDER BY comments.id DESC
LIMIT 10
");
$data = "";
foreach($comments as $comment) {
$image_id = $comment['image_id'];
$comment_id = $comment['comment_id'];
$link = make_link("post/view/$image_id");
$owner = html_escape($comment['user_name']);
$posted = date(DATE_RSS, $comment['posted_timestamp']);
$comment = html_escape($comment['comment']);
$content = html_escape("$owner: $comment");
$data .= "
<item>
<title>$owner comments on $image_id</title>
<link>$link</link>
<guid isPermaLink=\"false\">$comment_id</guid>
<pubDate>$posted</pubDate>
<description>$content</description>
</item>
";
}
class RSS_Comments extends SimpleExtension {
public function onPostListBuilding($event) {
global $config, $page;
$title = $config->get_string('title');
$base_href = $config->get_string('base_href');
$version = $config->get_string('version');
$xml = <<<EOD
$page->add_header("<link rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Comments\" href=\"".make_link("rss/comments")."\" />");
}
public function onPageRequest($event) {
global $config, $database, $page;
if($event->page_matches("rss/comments")) {
$page->set_mode("data");
$page->set_type("application/rss+xml");
$comments = $database->get_all("
SELECT
users.id as user_id, users.name as user_name,
comments.comment as comment, comments.id as comment_id,
comments.image_id as image_id, comments.owner_ip as poster_ip,
UNIX_TIMESTAMP(posted) AS posted_timestamp
FROM comments
LEFT JOIN users ON comments.owner_id=users.id
ORDER BY comments.id DESC
LIMIT 10
");
$data = "";
foreach($comments as $comment) {
$image_id = $comment['image_id'];
$comment_id = $comment['comment_id'];
$link = make_http(make_link("post/view/$image_id"));
$owner = html_escape($comment['user_name']);
$posted = date(DATE_RSS, $comment['posted_timestamp']);
$comment = html_escape($comment['comment']);
$content = html_escape("$owner: $comment");
$data .= "
<item>
<title>$owner comments on $image_id</title>
<link>$link</link>
<guid isPermaLink=\"false\">$comment_id</guid>
<pubDate>$posted</pubDate>
<description>$content</description>
</item>
";
}
$title = $config->get_string('title');
$base_href = make_http($config->get_string('base_href'));
$version = $config->get_string('version');
$xml = <<<EOD
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0">
<channel>
<title>$title</title>
<description>The latest comments on the image board</description>
<channel>
<title>$title</title>
<description>The latest comments on the image board</description>
<link>$base_href</link>
<generator>$version</generator>
<copyright>(c) 2007 Shish</copyright>
@ -81,9 +70,8 @@ class RSS_Comments implements Extension {
</channel>
</rss>
EOD;
$page->set_data($xml);
$page->set_data($xml);
}
}
// }}}
}
add_event_listener(new RSS_Comments());
?>

View File

@ -1,9 +1,22 @@
<?php
class RSSCommentsTest extends WebTestCase {
class RSSCommentsTest extends ShimmieWebTestCase {
function testImageFeed() {
$this->get(TEST_BASE.'/rss/comments');
$this->assertMime("application/rss+xml");
$this->assertNoText("Exception");
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx");
$this->get_page("post/view/$image_id");
$this->set_field('comment', "Test Comment ASDFASDF");
$this->click("Post Comment");
$this->assert_text("ASDFASDF");
$this->log_out();
$this->get_page('rss/comments');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
$this->assert_text("ASDFASDF");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -1,53 +1,38 @@
<?php
/**
/*
* Name: RSS for Images
* Author: Shish <webmaster@shishnet.org>
* License: GPLv2
* Description: Self explanitory
* Description: Self explanatory
*/
class RSS_Images implements Extension {
// event handling {{{
public function receive_event(Event $event) {
global $config, $database, $page, $user;
class RSS_Images extends SimpleExtension {
public function onPostListBuilding($event) {
global $config, $page;
$title = $config->get_string('title');
if($event instanceof PostListBuildingEvent) {
$title = $config->get_string('title');
if(count($event->search_terms) > 0) {
$search = implode(' ', $event->search_terms);
$page->add_header("<link id=\"images\" rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Images with tags: $search\" href=\"".make_link("rss/images/$search/1")."\" />");
}
else {
$page->add_header("<link id=\"images\" rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Images\" href=\"".make_link("rss/images/1")."\" />");
}
if(count($event->search_terms) > 0) {
$search = html_escape(implode(' ', $event->search_terms));
$page->add_header("<link id=\"images\" rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Images with tags: $search\" href=\"".make_link("rss/images/$search/1")."\" />");
}
else {
$page->add_header("<link id=\"images\" rel=\"alternate\" type=\"application/rss+xml\" ".
"title=\"$title - Images\" href=\"".make_link("rss/images/1")."\" />");
}
}
if(($event instanceof PageRequestEvent) && $event->page_matches("rss/images")) {
$page_number = 0;
$search_terms = array();
if($event->count_args() == 1) {
$page_number = int_escape($event->get_arg(0));
// compat hack, deprecate this later
if($page_number == 0) {
$search_terms = explode(' ', $event->get_arg(0));
$page_number = 1;
}
}
else if($event->count_args() == 2) {
$search_terms = explode(' ', $event->get_arg(0));
$page_number = int_escape($event->get_arg(1));
}
$images = Image::find_images(($page_number-1)*10, 10, $search_terms);
public function onPageRequest($event) {
if($event->page_matches("rss/images")) {
$search_terms = $event->get_search_terms();
$page_number = $event->get_page_number();
$page_size = $event->get_page_size();
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
$this->do_rss($images, $search_terms, $page_number);
}
}
// }}}
// output {{{
private function do_rss($images, $search_terms, $page_number) {
global $page;
global $config;
@ -56,15 +41,15 @@ class RSS_Images implements Extension {
$data = "";
foreach($images as $image) {
$link = make_link("post/view/{$image->id}");
$tags = $image->get_tag_list();
$link = make_http(make_link("post/view/{$image->id}"));
$tags = html_escape($image->get_tag_list());
$owner = $image->get_owner();
$thumb_url = $image->get_thumb_link();
$image_url = $image->get_image_link();
$posted = date(DATE_RSS, $image->posted_timestamp);
$content = html_escape(
"<p>" . Themelet::build_thumb_html($image) . "</p>" .
"<p>Uploaded by " . $owner->name . "</p>"
"<p>Uploaded by " . html_escape($owner->name) . "</p>"
);
$data .= "
@ -81,10 +66,10 @@ class RSS_Images implements Extension {
}
$title = $config->get_string('title');
$base_href = $config->get_string('base_href');
$base_href = make_http($config->get_string('base_href'));
$search = "";
if(count($search_terms) > 0) {
$search = html_escape(implode(" ", $search_terms)) . "/";
$search = url_escape(implode(" ", $search_terms)) . "/";
}
if($page_number > 1) {
@ -113,7 +98,5 @@ class RSS_Images implements Extension {
</rss>";
$page->set_data($xml);
}
// }}}
}
add_event_listener(new RSS_Images());
?>

View File

@ -1,21 +1,36 @@
<?php
class RSSImagesTest extends WebTestCase {
class RSSImagesTest extends ShimmieWebTestCase {
function testImageFeed() {
$this->get(TEST_BASE.'/rss/images');
$this->assertMime("application/rss+xml");
$this->assertNoText("Exception");
$this->log_in_as_user();
$image_id = $this->post_image("ext/simpletest/data/pbx_screenshot.jpg", "pbx computer screenshot");
$this->log_out();
$this->get(TEST_BASE.'/rss/images/1');
$this->assertMime("application/rss+xml");
$this->assertNoText("Exception");
$this->get_page('rss/images');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
$this->get(TEST_BASE.'/rss/images/tagme/1');
$this->assertMime("application/rss+xml");
$this->assertNoText("Exception");
$this->get_page('rss/images/1');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
$this->get(TEST_BASE.'/rss/images/tagme/2');
$this->assertMime("application/rss+xml");
$this->assertNoText("Exception");
# FIXME: test that the image is actually found
$this->get_page('rss/images/computer/1');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
# valid tag, invalid page
$this->get_page('rss/images/computer/2');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
# not found
$this->get_page('rss/images/waffle/2');
$this->assert_mime("application/rss+xml");
$this->assert_no_text("Exception");
$this->log_in_as_admin();
$this->delete_image($image_id);
$this->log_out();
}
}
?>

View File

@ -0,0 +1,73 @@
<?php
/*
* Name: [Beta] Shimmie JSON API
* Author: Shish <webmaster@shishnet.org>
* Description: A JSON interface to shimmie data [WARNING]
* Documentation:
* <b>Admin Warning:</b> this exposes private data, eg IP addresses
* <p><b>Developer Warning:</b> the API is unstable; notably, private data may get hidden
*/
class _SafeImage {
#{"id":"2","height":"768","width":"1024","hash":"71cdfaabbcdad3f777e0b60418532e94","filesize":"439561","filename":"HeilAmu.png","ext":"png","owner_ip":"0.0.0.0","posted":"0000-00-00 00:00:00","source":null,"locked":"N","owner_id":"0","rating":"u","numeric_score":"0","text_score":"0","notes":"0","favorites":"0","posted_timestamp":-62169955200,"tag_array":["cat","kunimitsu"]}
function __construct(Image $img) {
$this->id = $img->id;
$this->height = $img->height;
$this->width = $img->width;
$this->hash = $img->hash;
$this->filesize = $img->filesize;
$this->ext = $img->ext;
$this->posted = $img->posted_timestamp;
$this->source = $img->source;
$this->owner_id = $img->owner_id;
$this->tags = $img->tag_array;
}
}
class ShimmieApi extends SimpleExtension {
public function onPageRequest(PageRequestEvent $event) {
global $database, $page;
if($event->page_matches("api/shimmie")) {
$page->set_mode("data");
$page->set_type("text/plain");
if($event->page_matches("api/shimmie/get_tags")) {
if($event->count_args() == 2) {
$all = $database->get_all(
"SELECT tag FROM tags WHERE tag LIKE ?",
array($event->get_arg(0)."%"));
}
else {
$all = $database->get_all("SELECT tag FROM tags");
}
$res = array();
foreach($all as $row) {$res[] = $row["tag"];}
$page->set_data(json_encode($res));
}
if($event->page_matches("api/shimmie/get_image")) {
$image = Image::by_id(int_escape($event->get_arg(0)));
$image->get_tag_array(); // tag data isn't loaded into the object until necessary
$safe_image = new _SafeImage($image);
$page->set_data(json_encode($safe_image));
}
if($event->page_matches("api/shimmie/find_images")) {
$search_terms = $event->get_search_terms();
$page_number = $event->get_page_number();
$page_size = $event->get_page_size();
$images = Image::find_images(($page_number-1)*$page_size, $page_size, $search_terms);
$safe_images = array();
foreach($images as $image) {
$image->get_tag_array();
$safe_images[] = new _SafeImage($image);
}
$page->set_data(json_encode($safe_images));
}
}
}
}
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

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