Compare commits

...

1042 Commits

Author SHA1 Message Date
2261b4b1b7 1.0.4 2021-08-17 08:44:50 +01:00
91c98fbad8 add tasks to end 2021-08-17 08:40:48 +01:00
5c58e7ebd3 locks changed 2021-08-17 08:40:48 +01:00
9a7be5f2cb (hopefully) fix desync problems 2021-08-17 08:40:29 +01:00
b14228e28e Add python highlighting 2021-08-17 05:50:56 +01:00
a54f5a228f shut up the pre-push hook 2021-08-15 16:39:22 +01:00
ba5ead445b Fix the packaging script 2021-08-15 16:32:47 +01:00
05b0c8c29e 1.0.3 2021-08-15 16:08:44 +01:00
45ff69bd3d custom name for our fork 2021-08-15 16:08:26 +01:00
7fba19749b Fix warning when sharedTask not set 2021-08-14 09:39:50 +01:00
29b919fd15 Add a proper config system 2021-08-14 09:28:42 +01:00
6de2ac40df Fix Postgres support
as far as I can tell
2021-08-14 09:28:19 +01:00
50b0b40514 Add build instructions 2021-08-14 07:25:25 +01:00
f44f917af4 Readme change 2021-08-13 21:29:09 +01:00
Matthew Ross
8d81d7d204
Update .travis.yml
Maybe this will fix the uploader?
2021-03-05 09:00:46 -05:00
Matthew Ross
c35d6c166f
Update .travis.yml
Not ready for PHP 8 CI. Also updated uploader settings.
2021-03-05 08:30:23 -05:00
Matthew Ross
0f21d0e925
Update .travis.yml 2021-03-05 08:11:03 -05:00
Matthew Ross
e4de543809
Update .travis.yml
Travis CI no longer include private variables in fork builds. This breaks the upload of results, so now they aren't uploaded if it's a fork build.
2021-01-15 08:17:44 -05:00
Matthew Ross
ecf970d267
Merge pull request #568 from Forceu/dev
Added Nginx example config
2020-11-16 07:36:15 -05:00
Marc Ole Bulling
8be9c88a36
Added Nginx example config 2020-11-13 19:31:22 +01:00
Matthew Ross
582309fe55
Update README.md 2020-09-28 09:00:08 -04:00
Matthew Ross
99c5289133
Merge pull request #556 from kiswa/master
Update README.md
2020-09-09 11:47:52 -04:00
Matthew Ross
df66f9e11f
Update README.md 2020-09-09 11:47:16 -04:00
Matthew Ross
f9b464fa6b Proper JSON format 2020-09-05 14:22:57 -04:00
Matthew Ross
a0df9afca1 Update dependencies 2020-09-05 14:17:05 -04:00
Matthew Ross
99d55dcddb Merge master to dev... because I'm an idiot 2020-09-05 14:05:59 -04:00
Matthew Ross
aca954f21f Update to v1.0.2 2020-09-05 13:54:31 -04:00
Matthew Ross
966e4cfb68 Fix bad return in updateTask method 2020-09-05 13:52:50 -04:00
Matthew Ross
dc7fb7ee76 Update tests for latest changes 2020-09-05 13:52:26 -04:00
Matthew Ross
b00203ba92 Update counts 2020-09-05 13:51:43 -04:00
Matthew Ross
12402f867d Correct column update when using drag/drop. Fixes #520 2020-09-05 12:45:22 -04:00
Matthew Ross
f9d112bb20 Update manifest 2020-09-05 11:48:09 -04:00
Matthew Ross
47bb5d8a3b Update changelog with v1.0.2 updates 2020-08-20 07:45:07 -04:00
Matthew Ross
9a03d26cd4 Minor translation changes. Fixes #516 2020-08-20 07:36:44 -04:00
Matthew Ross
805ed4b653 Update counts 2020-08-19 19:46:24 -04:00
Matthew Ross
184cb08960 Refactor to refresh token on each call to API. Fixes #514 2020-08-19 19:40:04 -04:00
Matthew Ross
44a52cd3b7 Update dependencies 2020-08-19 19:20:58 -04:00
Matthew Ross
fb54058aa0
Merge pull request #543 from ben-so/dev
Set Charset for PHPMailer to UTF-8
2020-07-31 11:24:43 -04:00
Benjamin Solbrig
52ae46ee1e Set Charset for PHPMailer to UTF-8 2020-07-27 10:59:42 +02:00
Matthew Ross
3ac245e384
Merge pull request #539 from ben-so/dev
Fixed due date in email notification
2020-07-21 11:59:23 -04:00
Benjamin Solbrig
fec8dbe915 Removed time from due date in email notification 2020-07-20 14:37:17 +02:00
Benjamin Solbrig
5523ec76ae Format due date in Email with strtotime 2020-07-20 14:19:27 +02:00
Matthew Ross
c509397831
Merge pull request #535 from ben-so/dev
First complete German translation
2020-07-16 10:10:42 -04:00
Matthew Ross
4f77e89d4d
Merge pull request #534 from marner2/feature/add_dockerfiles
Add Docker dev support
2020-07-16 10:10:01 -04:00
ben-so
681e573139
Added files for German translation 2020-07-15 10:02:07 +02:00
ben-so
76479e1ad2
German translation – add option in drop-down 2020-07-15 09:56:08 +02:00
Joshua Marner
a3602b436d Small fixes 2020-07-14 16:04:44 -05:00
Joshua Marner
a8fadad03e Add ability to debug with vscode and docker 2020-07-14 15:45:05 -05:00
Joshua Marner
2a8aa69bbe Fix dependency issue with angular and typescript 2020-07-14 15:43:34 -05:00
Joshua Marner
18b9ba541e Add Dockerfile 2020-07-14 15:43:17 -05:00
Matthew Ross
0b28e3dd81
Merge pull request #530 from christoshrousis/master
Set a height on the parent and move the toggle.
2020-07-10 08:41:08 -04:00
Christos Hrousis
723ea49795 💄 Set a height on the parent and move the toggle. 2020-07-10 14:12:35 +10:00
Matthew Ross
0964f669fa
Merge pull request #524 from marner2/patch-1
Make base rewriting script account for /files urls
2020-07-06 08:33:48 -04:00
marner2
0d66dd9081
Make base rewriting script account for /files urls
Make the file preview screen work. Previously it was setting the base element equal to the full url in the file preview screen, breaking the script loading.
2020-07-03 13:26:32 -05:00
Matthew Ross
d664355ffa
Merge pull request #522 from marner2/patch-1
Fix base where Taskboard is hosted at root.
2020-07-02 11:51:20 -04:00
marner2
ab08f79721
Update index.html
Fix case where taskboard is hosted at the base url

String.indexOf(x) returns 0 when x is at the beginning of the source string, and -1 if it is not found.
2020-07-02 08:44:48 -05:00
Matthew Ross
5493ce5d6e Update task visually when changed by drag and drop. Fixes #507. 2020-06-28 10:25:18 -04:00
Matthew Ross
ba9771ba79 Update version test 2020-06-27 19:16:31 -04:00
Matthew Ross
af129bca20 Merge branch 'user-sec-level' into dev 2020-06-21 16:47:16 -04:00
Matthew Ross
74028dd84a Default new user to User security level (Fixes #503) 2020-06-21 16:42:12 -04:00
Matthew Ross
3ba31e28ec Update counts 2020-06-21 16:40:33 -04:00
Matthew Ross
0f1f8037ab Update dependencies 2020-06-21 16:37:57 -04:00
Matthew Ross
efad6e9a38 Merge branch 'task-link' into dev 2020-06-16 11:01:28 -04:00
Matthew Ross
f2f0f052cf Add direct linking to a task. Fixes #81 2020-06-16 11:00:01 -04:00
Matthew Ross
88535c3ab1 Update dependencies 2020-06-16 10:53:34 -04:00
Matthew Ross
7244d22d01 Update counts 2020-06-11 15:54:19 -04:00
Matthew Ross
2bc585f205 Initial dashboard work 2020-06-11 15:47:43 -04:00
Matthew Ross
61ab341e33 Update dependencies 2020-06-11 15:47:19 -04:00
Matthew Ross
a27a7a97fb Enable Dashboard link and some minor cleanup 2020-06-10 14:49:35 -04:00
Matthew Ross
a0da09e88a Update dependencies 2020-06-10 13:15:50 -04:00
Matthew Ross
c609af86f1 Update dependencies 2020-06-10 13:13:18 -04:00
Matthew Ross
eef258a60f
Merge pull request #498 from kiswa/master
Update README.md
2020-06-06 09:01:50 -04:00
Matthew Ross
1728dac96c
Update README.md 2020-06-06 09:01:12 -04:00
Matthew Ross
8c9730275b Update counts 2020-06-04 10:31:16 -04:00
Matthew Ross
cd19309669 Update for v1.0.1 2020-06-04 10:21:30 -04:00
Matthew Ross
d0958e0727 Update unit tests 2020-06-04 10:19:27 -04:00
Matthew Ross
16daaf8e5e Set version to v1.0.1 2020-06-04 09:06:19 -04:00
Matthew Ross
7191282d2c Re-hide dashboard for now 2020-06-04 09:05:58 -04:00
Matthew Ross
cccfcc827b Make base href truly dynamic
This allows a user to 'install' TaskBoard anywhere without worrying
about the directory structure.

Also, adds api-service which is extended by all services that call the
API to ensure they use the correct path.
2020-06-04 08:57:03 -04:00
Matthew Ross
cce6e23aee Merge base-path to dev 2020-06-02 11:34:57 -04:00
Matthew Ross
308debb82b Actual dynamic base href 2020-06-02 11:31:47 -04:00
Matthew Ross
0fd7817ff5 Make base-href dynamic 2020-06-02 10:43:32 -04:00
Matthew Ross
5e53c1ad91 Update dependencies and re-instate dashboard 2020-06-02 10:01:49 -04:00
Matthew Ross
5cf7d3faa3
Merge pull request #493 from kiswa/master
Merge master to dev
2020-06-01 15:00:26 -04:00
Matthew Ross
baa928a10d Merge cleanup 2020-06-01 14:46:03 -04:00
Matthew Ross
bde1e55e6b Merge dev to master 2020-06-01 14:32:07 -04:00
Matthew Ross
c054776ce8 Remove dev base href 2020-06-01 14:23:26 -04:00
Matthew Ross
a456ff978d Better package script 2020-06-01 14:22:29 -04:00
Matthew Ross
fdc50de1df Update tests 2020-06-01 14:05:41 -04:00
Matthew Ross
ad37cb2880 Fixes found in docs update 2020-06-01 13:55:58 -04:00
Matthew Ross
0c9b1b00d6 Fix loading display when no boards exist.
Also, added a link to releases to README.
2020-05-28 10:29:30 -04:00
Matthew Ross
508a7cb622 Fix merge conflicts 2020-05-28 10:01:17 -04:00
Matthew Ross
288cb3fce7 Merge re-write to dev 2020-05-28 09:54:39 -04:00
Matthew Ross
5bc7c07f98 Update counts 2020-05-28 09:30:31 -04:00
Matthew Ross
593252532e Test updates 2020-05-28 09:18:43 -04:00
Matthew Ross
f476d3fb35 Update screenshots and minor fixes from demo creation 2020-05-28 08:45:44 -04:00
Matthew Ross
f99d6fdf89 Update dependencies 2020-05-28 07:14:30 -04:00
Matthew Ross
e878e8b1d8 Use contributed icon 2020-05-27 15:33:29 -04:00
Matthew Ross
c9444fa5a5 Merge re-write into demo 2020-05-27 14:09:57 -04:00
Matthew Ross
f5971e65b6 Minor cleanup and sorting tweak 2020-05-27 13:59:15 -04:00
Matthew Ross
289ea397bb Add package script 2020-05-27 13:18:43 -04:00
Matthew Ross
ee32c3804e Update dependencies 2020-05-26 19:19:08 -04:00
Matthew Ross
25ce5ac92a Performance improvements, refactoring, and package size reduction 2020-05-26 19:00:45 -04:00
Matthew Ross
8da3175402 Update dependencies 2020-05-20 18:48:29 -04:00
Matthew Ross
adcfa4b2ad Minor Codacy changes 2020-05-20 17:11:40 -04:00
Matthew Ross
d7aa23bdee Hide Dashboard button 2020-05-20 17:10:33 -04:00
Matthew Ross
2bbc4114f7 Fix for PHP 7.2-3 2020-05-20 17:10:21 -04:00
Matthew Ross
d0d1cb2c45 Finished email functionality 2020-05-20 16:25:44 -04:00
Matthew Ross
0c9b66a18f Cleanup 2020-05-14 16:09:01 -04:00
Matthew Ross
a2bfdff93a Always show total task count in columns 2020-05-14 16:08:27 -04:00
Matthew Ross
b79e755060 Update dependencies 2020-05-14 16:07:11 -04:00
Matthew Ross
b241a08536 Show task count in column headers 2020-05-14 15:15:51 -04:00
Matthew Ross
6bf0090979 Update counts 2020-05-14 13:15:03 -04:00
Matthew Ross
6910627df5 Test cleanup 2020-05-14 13:12:47 -04:00
Matthew Ross
1696e069a2 Remove more invisible characters 2020-05-13 12:03:24 -04:00
Matthew Ross
50173e462b Codacy cleanup 2020-05-13 11:59:50 -04:00
Matthew Ross
af2cdf8164 Update README 2020-05-13 11:50:11 -04:00
Matthew Ross
5c5f2bc0fd Update dependencies and minor fix 2020-05-13 11:46:45 -04:00
Matthew Ross
c58bea87cf Initial email setup in place 2020-05-13 11:37:17 -04:00
Matthew Ross
3eb38905b9 API uses translations! 2020-05-13 09:08:32 -04:00
Matthew Ross
ad17f51d71 Refactoring/cleanup and other minor changes 2020-05-12 15:47:36 -04:00
Matthew Ross
66c06c01e8 Codacy lint fix 2020-05-11 19:27:31 -04:00
Matthew Ross
cf7eb5b0fc Style updates 2020-05-11 19:13:20 -04:00
Matthew Ross
8e733aa12c Update tests 2020-05-11 19:12:48 -04:00
Matthew Ross
ee2a0f4f0c Update language files for attachments 2020-05-11 19:12:05 -04:00
Matthew Ross
a109c9d0a5 Attachments fully working 2020-05-11 19:10:36 -04:00
Matthew Ross
5748f762e9 Allow form data through without JSON header 2020-05-11 19:09:24 -04:00
Matthew Ross
f28ab24634 Attachment endpoints and minor cleanup 2020-05-11 19:07:30 -04:00
Matthew Ross
1b3cf2665f Move PHP settings into .htaccess 2020-05-11 18:56:37 -04:00
Matthew Ross
2444a43459 Remove unused file 2020-05-11 18:49:31 -04:00
Matthew Ross
067d0b0d64 Disable unit test console logging 2020-05-11 18:45:41 -04:00
Matthew Ross
b4ea7c8b9f Update counts 2020-05-11 18:43:34 -04:00
Matthew Ross
4638498a71 Add input for hiding buttons 2020-05-06 07:38:54 -04:00
Matthew Ross
aea06f94af Formatting and minor refactoring 2020-05-06 07:38:10 -04:00
Matthew Ross
e4b32cd7ad Update GitHub files 2020-05-03 09:32:56 -04:00
Matthew Ross
15b0317490 Update issue templates 2020-05-03 09:19:48 -04:00
Matthew Ross
640a2dbff9
Delete ISSUE_TEMPLATE.md 2020-05-03 09:11:41 -04:00
Matthew Ross
d77eef9220 Initial file viewer 2020-04-27 14:00:22 -04:00
Matthew Ross
babbf81610 More for attachments, and cleanup 2020-04-27 13:59:39 -04:00
Matthew Ross
fbddac7302 Cleanup 2020-04-27 13:39:22 -04:00
Matthew Ross
672c1d031d Attachments nearly complete 2020-04-27 13:38:21 -04:00
Matthew Ross
dd08499be9 Format tools files 2020-04-22 08:34:10 -04:00
Matthew Ross
c2575f08b0 Update dependencies 2020-04-21 17:07:17 -04:00
Matthew Ross
1c941c1d2e Update counts 2020-04-21 16:43:50 -04:00
Matthew Ross
dc3f1d78ac Add notice to README 2020-04-21 16:39:38 -04:00
Matthew Ross
a8a66dff7d Update link to RedBeanPHP 2020-04-21 16:33:58 -04:00
Matthew Ross
3e45765577 Remove no longer valid test 2020-04-21 16:29:01 -04:00
Matthew Ross
70427ef7bd Board admin drag/drop (column order) now uses Angular CDK 2020-04-21 16:24:08 -04:00
Matthew Ross
fe5f529320 Board drag/drop now uses Angular CDK - minor cleanup 2020-04-21 16:23:31 -04:00
Matthew Ross
4cc8512713 Minor translation change for layout 2020-04-21 16:21:37 -04:00
Matthew Ross
454841d1cd One more try at Codacy table issue 2020-04-13 08:41:45 -04:00
Matthew Ross
00b37437da Fix Codacy issues in README (?) 2020-04-13 08:34:27 -04:00
Matthew Ross
5d4a20c5c6 Update README 2020-04-12 07:56:40 -04:00
Matthew Ross
3ad075fa05 Update styles 2020-04-12 07:56:28 -04:00
Matthew Ross
fb3d99c4dc Add Ctrl+Enter to save comment 2020-04-12 07:56:09 -04:00
Matthew Ross
cff9be1987 Allow Ctrl+Enter to submit comments (and comment edits). Fixes #302 2020-04-02 12:23:15 -04:00
Matthew Ross
a24594d791 Remove Dragula from tests as well 2020-04-02 10:37:21 -04:00
Matthew Ross
46c09e8a73 Update API dependencies 2020-04-02 09:19:58 -04:00
Matthew Ross
8a381080d7 Completely remove Dragula 2020-04-02 09:19:36 -04:00
Matthew Ross
b44de68593 Minor change to French translation 2020-04-02 09:18:23 -04:00
Matthew Ross
6ca7689a0c Merge branch 'Alex131089-Alex131089-lang-fr' into re-write 2020-04-02 07:56:24 -04:00
Matthew Ross
08d6bcedc6 Merge branch 'Alex131089-lang-fr' of https://github.com/Alex131089/TaskBoard into Alex131089-Alex131089-lang-fr 2020-04-02 07:55:04 -04:00
Matthew Ross
14a1ea4950 Update license dates 2020-04-01 19:16:45 -04:00
Matthew Ross
feb31c23f9 Update counts 2020-04-01 19:00:18 -04:00
Matthew Ross
6617feee0d Upgrade to latest everything, update unit tests, remove Dragula, start of using CDK DragDrop 2020-04-01 18:51:48 -04:00
Matthew Ross
cacdc3c432 Update dependencies 2020-01-16 18:46:49 -05:00
Matthew Ross
515765e296 Build should pass again 2020-01-16 15:05:31 -05:00
Matthew Ross
7c1fe83a97
Update FUNDING.yml 2020-01-08 13:59:23 -05:00
Matthew Ross
5123c8a197
Create FUNDING.yml 2020-01-08 13:58:24 -05:00
Matthew Ross
e6233405ff Final Codacy fix and App unit tests pass (not API yet) 2020-01-01 17:01:21 -05:00
Matthew Ross
1da9cb51b2 More Codacy cleanup 2020-01-01 16:44:57 -05:00
Matthew Ross
551761972d Move tslint.json for Codacy 2020-01-01 16:33:34 -05:00
Matthew Ross
8f628df3d0 More Codacy cleanup 2020-01-01 16:28:40 -05:00
Matthew Ross
7d0f5041f9 Codacy cleanup 2020-01-01 16:16:38 -05:00
Matthew Ross
6195391cff Merge 2019-12-29 08:41:40 -05:00
Matthew Ross
3dac31c7d3 Upgrade to Angular 8 - CI build will fail 2019-12-29 08:35:31 -05:00
Matthew Ross
f563da9949
Merge pull request #469 from davidmind/master
Update README.md
2019-05-28 13:17:16 -04:00
davidmind
32d8ded014
Logo Files 2019-05-28 16:52:58 +01:00
davidmind
d819c6a710
Update README.md 2019-05-04 00:49:08 +01:00
Matthew Ross
d1babfb135
Merge pull request #467 from bbichero/master
Add Template for issues
2019-04-01 14:18:32 -04:00
baptiste bicheron
e45041509e Add Template for issues 2019-04-01 12:16:16 +02:00
Alexandre L
675f5ea759
[FR] Question mark also have to use non-breakable spaces.. 2019-02-10 23:11:14 +01:00
Alexandre L
6726c10169
[FR] Filter by "Any"
Should be "n'importe lequel" (user ; male noun) and "n'importe laquelle" (category ; female noun), so let's use something generic.
2019-02-10 19:40:27 +01:00
Alexandre L
ff4ae32df6
[FR] Aaaand more nbsp & formating 2019-02-10 19:25:09 +01:00
Alexandre L
2cf84fd527
More non-breaking space (before ":") 2019-02-10 19:07:51 +01:00
Alexandre L
7fa4fb5057
Be more explicit 2019-02-10 18:10:05 +01:00
Alexandre L
691318ec68
Fix typo & adjust from real view 2019-02-10 18:04:43 +01:00
Alexandre L
49dd6fb577
French translation – add option in drop-down 2019-02-10 17:07:57 +01:00
Alexandre L
3a927ea4ba
French translation – part 3 2019-02-10 17:06:05 +01:00
Alexandre L
3b5c5492bb
French translation – part 2 2019-02-10 16:40:38 +01:00
Alexandre L
26b3defd5a
French translation – part 1 2019-02-10 13:46:35 +01:00
Matt Ross
bc99a1571f Update unit tests for new Dragula 2018-09-26 09:17:38 -04:00
Matthew Ross
43abb1773e Fix for side-by-side task display 2018-09-26 07:43:55 -04:00
Matthew Ross
a285f57367 Changes for updated Dragula and some file attachment work 2018-09-26 07:43:23 -04:00
Matthew Ross
48b6eebd2b Update dependencies 2018-09-26 07:41:38 -04:00
Matthew Ross
8b7681b364 Codacy cleanup 2018-07-28 21:59:11 -04:00
Matthew Ross
ee3680057b Fix formatting of PHP tools 2018-07-28 21:43:38 -04:00
Matthew Ross
d3dfc5f7b5 Fix unit tests, and initial attachment work 2018-07-18 19:08:10 -04:00
Matthew Ross
061891d7ff Layout tweak for language add link 2018-07-12 08:40:06 -04:00
Matthew Ross
9f1e422314 Set user options for newly created user 2018-07-12 08:38:47 -04:00
Matthew Ross
2ad1f70845 Remove unecessary test 2018-07-12 08:28:02 -04:00
Matthew Ross
ca8d91e341 Update dependencies 2018-07-12 08:27:12 -04:00
Matthew Ross
f4bf8564db Minor bugfixes 2018-07-11 10:28:04 -04:00
Matthew Ross
ffde81c85b WIP - Tools for migrate/import 2018-07-11 10:27:17 -04:00
Matthew Ross
6a2a0eb645 Update dependencies 2018-07-11 10:26:37 -04:00
Matthew Ross
dece32271b Fix unit tests for new context menu behaviors 2018-07-05 09:05:37 -04:00
Matthew Ross
2ec7cf7554 Update dependencies 2018-07-05 09:02:21 -04:00
Matthew Ross
044b72c820 WIP - New context menu impl 2018-06-30 08:33:22 -04:00
Matthew Ross
c58e54b176 Remove compile directive and initial new context menu 2018-06-08 16:41:01 -04:00
Matthew Ross
fe080b9817 WIP - Various updates 2018-06-01 06:43:09 -04:00
Matthew Ross
9347ef7f83
Fixes #436 2018-05-30 08:16:55 -04:00
Matthew Ross
3774d2ba71 WIP - Update unit tests 2018-05-26 09:11:01 -04:00
Matthew Ross
61dbefbadb WIP - RxJs updated, but runtime errors 2018-05-24 20:32:29 -04:00
Matthew Ross
f8632e2501
Merge pull request #401 from hbayindir/dev
Fix the scrollback problem, closes #400.
2018-05-24 07:57:24 -04:00
Matthew Ross
ab2f107e2b
Merge pull request #435 from redjamesg/patch-1
Fail to build with latest JS engine
2018-05-24 07:54:47 -04:00
Kenneth H. Nielsen
4113c5d73c
Fail to build with latest JS engine
It seems the misplaced ',' at line 241 is causing a failure to build with openjdk-8-jre
2018-05-24 11:12:34 +02:00
Matthew Ross
160a954b4f Update Travis configuration 2018-05-18 20:48:01 -04:00
Matthew Ross
5583515df9 Initial conversion to Angular 6 2018-05-18 17:39:27 -04:00
Matthew Ross
fa5941fe73 Update deps and scripts 2018-04-13 14:41:12 -04:00
Matthew Ross
e5fd5cd523 Final unit tests? 2018-04-12 15:20:08 -04:00
Matthew Ross
68550a2276 Modal Service tests. 2018-04-11 18:43:20 -04:00
Matthew Ross
7132e26f8a Unit tests 2018-04-11 17:21:27 -04:00
Matthew Ross
c8acf01ea2 Unit tests and update counts 2018-04-10 19:54:48 -04:00
Matthew Ross
7d8e2639f3 Update dependencies 2018-04-06 14:56:18 -04:00
Matthew Ross
4b0a8b628a Unit tests 2018-04-06 14:55:20 -04:00
Matthew Ross
4e2843499e Unit tests 2018-04-02 14:59:11 -04:00
Matthew Ross
0b1243b9bd Unit tests 2018-04-02 06:29:47 -04:00
Matthew Ross
fabca7685c Unit test work 2018-03-30 14:54:06 -04:00
Matthew Ross
b0432bc6bd Unit tests 2018-03-28 15:10:52 -04:00
Matthew Ross
a1610bc0c2 More tests 2018-03-26 15:16:25 -04:00
Matthew Ross
d520fc9244 More unit tests 2018-03-25 21:20:27 -04:00
Matthew Ross
f12da9b370 More unit testing 2018-03-22 21:27:17 -04:00
Matthew Ross
705fe0e407 More TS unit tests 2018-03-22 15:04:58 -04:00
Matthew Ross
c39ff51238 Still tweaking CI 2018-03-22 07:38:49 -04:00
Matthew Ross
ed93691535 Still tweaking CI 2018-03-22 07:35:46 -04:00
Matthew Ross
f21616a2a7 Still tweaking CI 2018-03-22 07:26:18 -04:00
Matthew Ross
194cea74be More CI tweaks 2018-03-21 22:06:41 -04:00
Matthew Ross
f33d42889c Never mind PHP 7.3 in CI 2018-03-21 22:01:39 -04:00
Matthew Ross
51cc0bc43f Try new CI settings 2018-03-21 21:58:48 -04:00
Matthew Ross
ebf2b556fa Unit test changes and CI updates 2018-03-21 21:53:32 -04:00
Matthew Ross
9b7120de09 Update readme and CI test 2018-03-21 18:03:08 -04:00
Matthew Ross
80cbbba967 Moving tests over to new framework 2018-03-21 17:11:02 -04:00
Matthew Ross
d8d385e2a2 Move from deprecated HttpModule to newer HttpClientModule 2018-03-19 19:51:53 -04:00
kiswa
292f0d048c Update API lock file 2018-03-17 13:51:54 +00:00
Matthew Ross
75a4c20aeb Move to modules 2018-03-15 14:40:34 -04:00
Matthew Ross
87071c1135 First module 2018-03-15 07:01:20 -04:00
Matthew Ross
cbf231aa95 WIP - Build will fail 2018-03-14 16:28:43 -04:00
Matthew Ross
819bebbeb3 Fix CI build 2018-03-10 19:26:49 -05:00
Matthew Ross
0bc6bd5bb0 Fix CI build? 2018-03-10 19:16:33 -05:00
Matthew Ross
6be303f455 Update dependencies, maybe fix CI build. 2018-03-10 18:43:45 -05:00
Matthew Ross
bdbe3de2c3 Initial transition to @angular/cli and webpack
All unit tests need to be reworked to use the official Angular testing
setup. API tests are fine.
2018-03-09 20:05:40 -05:00
Matthew Ross
9577890993 Revert "Update API dependencies"
This reverts commit 78023d3ab06846fca71443eae78cf531f5e37208.
2018-03-07 19:41:39 -05:00
Matthew Ross
64923172a5 Update counts 2018-03-07 19:34:30 -05:00
Matthew Ross
fc86973bb5 Update task comments correctly. Fixes #418. 2018-03-07 18:59:27 -05:00
Matthew Ross
78023d3ab0 Update API dependencies 2018-03-07 18:30:46 -05:00
Matthew Ross
9a654e439a Revert test dependency. 2018-03-02 22:40:14 -05:00
Matthew Ross
51be3921ee Update dependencies and initial file attachments UI 2018-02-27 19:37:01 -05:00
Hakan Bayindir
09cb87c22e Fix the scrollback problem, closes #400. 2017-11-20 14:37:55 +03:00
kiswa
27bf356bbf Fix drag/drop bug and tweak task activity 2017-11-13 22:29:58 +00:00
kiswa
d6a3222ef8 Update dependencies 2017-11-13 21:41:06 +00:00
Matthew Ross
6a2604eb21
Merge pull request #398 from kiswa/dev
Dev merge
2017-11-13 16:10:48 -05:00
Matthew Ross
07967c09d1
Merge pull request #397 from mbasaglia/fix_mod_expires
Add check for mod_expires in the api .hraccess
2017-11-13 16:09:35 -05:00
Mattia Basaglia
5539029b0e Add check for mod_expires in the api .hraccess 2017-11-11 10:22:37 +01:00
kiswa
db5d7c793e Tweak task activity display 2017-10-28 12:54:41 +00:00
kiswa
8d8219934f Update dependencies 2017-10-26 00:40:36 +00:00
Matthew Ross
483c4e5717 Update counts 2017-10-07 14:36:15 -04:00
Matthew Ross
abb391c378 Initial task activity display WIP 2017-10-07 14:33:58 -04:00
Matthew Ross
1aa82d032e Add link to translations wiki page 2017-10-07 12:47:27 -04:00
kiswa
4760b5382a Update API dependencies 2017-10-07 16:19:16 +00:00
Matthew Ross
e161edba97 Codacy cleanup 2017-10-07 12:17:13 -04:00
Matthew Ross
ce63479976 Initial activity log data for tasks 2017-10-07 12:11:56 -04:00
Matthew Ross
1c1f585d38 Update counts 2017-10-07 09:24:23 -04:00
Matthew Ross
6987bb9338 Add cancel option when editing comments 2017-10-07 09:20:41 -04:00
Matthew Ross
7531d645b4 Update dependencies 2017-10-07 09:20:20 -04:00
kiswa
673c100875 Merge branch 're-write' of https://github.com/kiswa/TaskBoard into re-write 2017-09-27 12:07:45 +00:00
kiswa
9084eed767 Update counts 2017-09-27 12:07:34 +00:00
Matthew Ross
5bf330e91e Task comment editing complete 2017-09-24 08:44:29 -04:00
Matthew Ross
53027ecd05 Functional comment removal 2017-09-21 17:57:28 -04:00
Matthew Ross
f94e9d8862 Update dependencies 2017-09-21 06:58:43 -04:00
Matthew Ross
68e813f068 Fix API tests 2017-09-16 12:50:48 -04:00
kiswa
0b1283483e View task modal and initial comments work 2017-09-16 16:28:59 +00:00
kiswa
2f71f8c5da Update dependencies 2017-09-16 16:28:21 +00:00
kiswa
9a324325a9 Update task description to custom component and initial view task modal 2017-09-06 20:15:38 +00:00
Matthew Ross
e448a0651c Fix code style issue 2017-09-05 20:14:10 -04:00
kiswa
54da613dce Update app unit tests 2017-09-05 23:59:27 +00:00
kiswa
e7d96b8aca Update API dependencies 2017-09-05 23:59:04 +00:00
kiswa
161d3eed42 Update counts 2017-09-05 23:58:27 +00:00
Matthew Ross
9c214ed016 Fix collapse column bug 2017-09-02 14:22:05 -04:00
Matthew Ross
eedcedab38 Update PHP version for Travis CI again 2017-09-02 13:43:34 -04:00
kiswa
8af6de31d2 Update PHP 7 version for Travis CI 2017-09-02 17:39:03 +00:00
Matthew Ross
18d245958d Fix task ordering when using drag/drop 2017-09-02 13:25:24 -04:00
Matthew Ross
5d5af55cdd Codacy cleanup. 2017-09-02 08:28:21 -04:00
kiswa
b1aec2c146 Initial automatic actions work 2017-09-01 23:43:53 +00:00
kiswa
b36ac3d3e6 Update dependencies 2017-09-01 23:40:45 +00:00
kiswa
4bfc5821ca Update counts 2017-09-01 23:33:38 +00:00
Matthew Ross
bd56f5b287 Fix broken test 2017-08-20 19:59:44 -04:00
Matthew Ross
7a09eeb4ea Update counts 2017-08-20 19:55:27 -04:00
kiswa
8ff331c683 Rework context menu behavior 2017-08-20 23:44:24 +00:00
kiswa
f3c4af20a6 WIP 2017-08-05 00:01:14 +00:00
kiswa
820b2511b1 Fix test and add missing file 2017-08-04 23:52:09 +00:00
kiswa
92132b3f59 Task drag-and-drop. 2017-08-04 23:41:14 +00:00
kiswa
ad051c1220 Fix task order bug 2017-08-04 23:37:54 +00:00
kiswa
7a8f632221 Codacy cleanup 2017-08-04 10:20:01 +00:00
kiswa
44027d152d Task ordering by position when added 2017-08-04 01:42:05 +00:00
kiswa
0510e1d835 WIP 2017-08-03 16:03:54 +00:00
kiswa
f5fc653fff Add isAnyAdmin method to user model 2017-07-31 21:20:10 +00:00
Matthew Ross
5e83436e15 Fix tests 2017-07-28 15:51:23 -04:00
kiswa
9d35ca7594 Merge branch 're-write' of https://github.com/kiswa/TaskBoard into re-write 2017-07-28 19:36:50 +00:00
kiswa
29edbaaed6 Boards use language settings, columns have task limit 2017-07-28 19:36:33 +00:00
Matthew Ross
16a737f874 Fix typo 2017-07-28 05:24:09 -04:00
Matthew Ross
551ddc2a1a Update app tests 2017-07-28 05:22:42 -04:00
kiswa
d133903a37 Initial column task limit WIP 2017-07-28 01:33:50 +00:00
kiswa
b0c770b221 Update icon font 2017-07-28 01:33:14 +00:00
kiswa
42c20d7339 Update dependencies and code counts 2017-07-28 01:32:43 +00:00
kiswa
ae9834be5c Fix task complete percentage 2017-07-26 23:45:25 +00:00
kiswa
cb3b0902fb UJpdate dependencies 2017-07-26 23:43:34 +00:00
kiswa
00e54d8f96 Update counts 2017-07-26 23:42:55 +00:00
Matthew Ross
8ec733cee2 Update README.md 2017-07-25 13:53:36 -04:00
Matthew Ross
b24bd4d918 Update API dependencies 2017-07-22 19:45:15 -04:00
Matthew Ross
d26b007482 Restore missing heading 2017-07-20 16:52:54 -04:00
Matthew Ross
fb9ac1632a Update counts 2017-07-20 16:50:56 -04:00
Matthew Ross
e80493532d Update dependencies 2017-07-20 16:48:05 -04:00
Matthew Ross
c179db39d3 Fix mock for failing test 2017-07-14 20:08:10 -04:00
kiswa
2a566e94c3 Update board naming 2017-07-14 23:47:33 +00:00
kiswa
b4a51d7f14 Codacy cleanup 2017-07-14 23:47:09 +00:00
kiswa
1d06702b7f Update app tests 2017-07-14 23:43:39 +00:00
kiswa
096a6898aa Update dependencies 2017-07-14 23:43:07 +00:00
Matthew Ross
60242f5fe3 Merge pull request #374 from kiswa/dev
Dev
2017-07-13 07:14:51 -04:00
Matthew Ross
c46233182b Merge pull request #373 from JoelLisenby/patch-2
Update build-all
2017-07-13 07:11:11 -04:00
kiswa
7f678ca886 Fix build error 2017-07-13 11:00:56 +00:00
Joel Lisenby
b05a062d40 Update build-all
Fix 301 redirect appearing in min.css upon minification attempt.
2017-07-12 14:26:20 -07:00
Matthew Ross
b221880808 Update dependencies and use npm@5 2017-07-11 20:30:32 -04:00
Matthew Ross
cddf75f800 Initial task column changing 2017-07-09 21:08:20 -04:00
Matthew Ross
5c2d512f67 Specify Typescript version until bug is fixed 2017-07-03 13:39:39 -04:00
Matthew Ross
30515b9c88 Fix broken test 2017-07-03 13:32:04 -04:00
Matthew Ross
5f4c8a7222 Set foreground color in context menus 2017-07-03 13:14:42 -04:00
kiswa
4fa6450aef Edit Task functional - also, checklist status bar 2017-07-03 16:34:08 +00:00
kiswa
1bf4af76b3 Update counts 2017-07-03 16:33:41 +00:00
kiswa
2c1f092cd8 Update dependencies 2017-07-03 15:27:53 +00:00
Matthew Ross
96dae8a64b Update dependencies and remove npm@5 file 2017-06-24 15:46:55 -04:00
kiswa
38a1f45413 Confirmation modal tweaks and typo fix 2017-06-05 23:58:23 +00:00
kiswa
e77cbc5bb7 Update counts and Settings image 2017-06-05 22:58:39 +00:00
kiswa
d4ca6f4f53 Fix display bug with inactive boards 2017-06-05 22:58:15 +00:00
Matthew Ross
bed1a1bcbf Update README.md 2017-06-05 07:59:20 -04:00
Matthew Ross
085291ce94 Task collapsing and test updates 2017-06-04 11:55:48 -04:00
Matthew Ross
7c7d64f364 Completely revert CI build versions 2017-06-03 10:10:48 -04:00
Matthew Ross
be52383cba Revert Node version 2017-06-03 10:03:16 -04:00
Matthew Ross
7e67886f3a Switch to older Node version for CI 2017-06-03 09:54:19 -04:00
Matthew Ross
5b712d6054 Update Node and npm versions in CI builds 2017-06-03 09:43:46 -04:00
Matthew Ross
7351086a98 Update dependencies 2017-06-03 09:40:13 -04:00
Matthew Ross
fa590de922 Merge branch 're-write' of github.com:kiswa/TaskBoard into re-write 2017-06-03 09:05:22 -04:00
kiswa
66e2c325a1 Category colors editable in modal 2017-06-03 13:05:00 +00:00
Matthew Ross
2cee0886b9 Update dependencies 2017-05-28 07:34:54 -04:00
kiswa
0a54d60f7c Update README 2017-05-23 23:19:16 +00:00
kiswa
c5dcce74c4 Update models and test coverage 2017-05-23 23:13:19 +00:00
kiswa
bdf7393ca4 Initial edit task modal work 2017-05-23 21:53:08 +00:00
Matthew Ross
89c12750c9 Codacy cleanup 2017-05-21 13:19:43 -04:00
Matthew Ross
ddf150b479 Fix typo 2017-05-21 13:16:22 -04:00
Matthew Ross
233c57f56c Update counts 2017-05-21 12:56:12 -04:00
Matthew Ross
1461b25987 Update unit test coverage 2017-05-21 12:52:50 -04:00
Matthew Ross
53fa3aa785 Update dependencies 2017-05-21 12:51:46 -04:00
kiswa
7b20a491a9 Ensure board data is converted to Board model 2017-05-17 22:27:21 +00:00
kiswa
8c458dddb7 Create default options when creating new user 2017-05-17 21:58:19 +00:00
kiswa
0c40edd3ae Update dependencies 2017-05-17 21:55:16 +00:00
kiswa
90ade9a776 Update supported PHP versions 2017-05-17 21:55:00 +00:00
Matthew Ross
8c19f82ce2 Update dependencies and style tweaks 2017-05-14 13:32:50 -04:00
Matthew Ross
588dabae44 Task context menu generation and refactoring 2017-05-10 21:10:15 -04:00
Matthew Ross
6cb8069234 Update counts 2017-05-08 17:20:57 -04:00
Matthew Ross
2243eebda9 Add and remove tasks from context menus 2017-05-08 17:18:45 -04:00
Matthew Ross
783b868220 Update dependencies 2017-05-05 18:12:44 -04:00
Matthew Ross
140433bc33 I'm a moron 2017-05-02 17:37:51 -04:00
Matthew Ross
932b64b1f6 Codacy cleanup? 2017-05-02 17:34:23 -04:00
Matthew Ross
13ed45a69d Codacy cleanup 2017-05-02 17:27:57 -04:00
Matthew Ross
ad6eb157a4 Update dependencies 2017-05-02 17:08:19 -04:00
kiswa
13b9bd5034 Context Menu - Task and Column initial functionality 2017-05-02 21:06:38 +00:00
Matthew Ross
c0322d9ed9 Add initial context menu service - WIP 2017-05-02 06:43:50 -04:00
kiswa
2e6c63a5c3 Initial functional context menu - WIP 2017-05-01 23:55:55 +00:00
kiswa
9d4151a01f Fix context bug in HTTP interceptor 2017-05-01 23:52:30 +00:00
kiswa
1adca42d8b Remove info logging from API 2017-05-01 23:51:24 +00:00
Matthew Ross
a33b7c95cc Add initial context menu component 2017-04-30 14:30:14 -04:00
Matthew Ross
e1ea13fc1e Handle code overflow 2017-04-30 11:29:37 -04:00
Matthew Ross
59345ce907 Modify styles for checklists 2017-04-30 08:21:26 -04:00
Matthew Ross
c60581e055 Update counts and cleanup SCSS 2017-04-30 08:08:42 -04:00
Matthew Ross
6027b82336 Markdown parsing, and code highlighting! 2017-04-30 08:03:01 -04:00
Matthew Ross
3e4e634ea3 Remove margin from body 2017-04-29 17:24:24 -04:00
Matthew Ross
82e032a531 Update scss-base and tweak styles 2017-04-29 17:13:00 -04:00
Matthew Ross
f1124e669a Codacy cleanup 2017-04-28 19:27:38 -04:00
Matthew Ross
c48a02a285 Change 'let' to 'var' 2017-04-28 19:25:34 -04:00
Matthew Ross
263ca2498b Update app tests and refactor api-http 2017-04-28 19:15:43 -04:00
kiswa
0c7179fea4 Initial add task functionality and README update 2017-04-28 22:13:55 +00:00
Matthew Ross
5e6d0c0378 Add missing package 2017-04-27 19:22:22 -04:00
kiswa
a20fe33e0e Missed merge section 2017-04-27 22:48:18 +00:00
kiswa
a5fe77bef8 Merge 2017-04-27 22:42:07 +00:00
kiswa
e3096aa5c6 Lazy commit - should be several 2017-04-27 22:41:05 +00:00
Matthew Ross
a7569450aa Fix code formatting 2017-03-24 22:08:58 -04:00
kiswa
b5fc471f61 Board display work - pull tasks out to own component 2017-03-25 02:03:24 +00:00
kiswa
efd366b265 update dependencies 2017-03-25 02:02:45 +00:00
Vinicius Dias
543c92e1c1 README.md Markdown corrected (#347) 2017-03-22 09:15:25 -04:00
Matthew Ross
97e0ca4e4c Update counts 2017-03-18 10:39:19 -04:00
Matthew Ross
a1b6b4b13d Update dependencies 2017-03-18 10:35:11 -04:00
kiswa
cde606e76b Build fixed and more board display work 2017-03-18 14:26:36 +00:00
Matthew Ross
9ae9144093 WIP 2017-03-16 06:27:58 -04:00
kiswa
84ce6121f5 Final translations for Settings 2017-03-15 21:36:25 +00:00
kiswa
99329f3eaa Add 'libs' to fix build 2017-03-15 21:36:07 +00:00
Matthew Ross
a975d43734 Fix failing test and JSON typo 2017-03-13 15:17:18 -04:00
Matthew Ross
5ce10e2839 Settings page translations complete 2017-03-13 15:02:12 -04:00
Matthew Ross
65ed1c8d08 Update dependencies 2017-03-13 15:01:27 -04:00
kiswa
847725c5a2 Initial Auto Actions translation work 2017-03-11 12:29:54 +00:00
kiswa
1f29fb90c5 Update counts 2017-03-11 12:29:28 +00:00
kiswa
a99209bfad Translations complete through Settings - Board Admin 2017-03-10 01:46:01 +00:00
kiswa
7ee796149d Update tests to fail build and fix failing tests 2017-03-10 01:45:36 +00:00
kiswa
169ab817fb Update dependencies 2017-03-10 01:45:04 +00:00
Matthew Ross
0f8b1991cc Additional translation work. 2017-03-06 18:50:49 -05:00
kiswa
32ab494e1e Fix tests, more translation, update counts 2017-03-03 22:04:36 +00:00
kiswa
dd8a350825 Initial translation implementation 2017-03-03 02:05:43 +00:00
kiswa
daa1171e81 Update tslint version 2017-03-03 02:04:01 +00:00
kiswa
ac6808ffa7 WIP - Persist collapsed columns and start Task addition 2017-02-26 17:25:39 +00:00
kiswa
f28ef733d6 Update dependencies 2017-02-26 12:57:14 +00:00
kiswa
f8c0c17b94 Modify task layout in columns 2017-02-23 01:25:45 +00:00
kiswa
0a4f3cc143 Update dependencies 2017-02-23 01:25:08 +00:00
kiswa
add4f453a5 Reduce coverage output folder depth 2017-02-23 01:24:37 +00:00
kiswa
6d8bfb20ec Cleanup 2017-02-23 01:23:33 +00:00
Matthew Ross
66e6451fad Fix typo 2017-02-17 06:57:13 -05:00
Matthew Ross
6881de18b7 Update counts 2017-02-17 06:55:46 -05:00
kiswa
376a6a46c6 Add instructions for running single App test 2017-02-17 11:48:12 +00:00
kiswa
f72ed33c9f WIP - Board display and columns (tasks next) 2017-02-17 11:34:39 +00:00
kiswa
d515564b7f Codacy tweak 2017-02-17 11:33:45 +00:00
kiswa
fc0d449f36 Update dependencies 2017-02-17 11:33:23 +00:00
kiswa
99ce275b79 WIP - Board display 2017-02-16 01:04:22 +00:00
kiswa
df1280c6d4 App unit tests up-to-date 2017-02-16 01:03:52 +00:00
kiswa
5ea5d4d28d Add app and styles tasks 2017-02-16 01:03:21 +00:00
kiswa
7966786572 Update dependencies 2017-02-16 01:02:56 +00:00
Matthew Ross
a6967e0a5e WIP - Front end tests 2017-02-14 06:35:19 -05:00
Matthew Ross
54c5d0ae8b Add comment for Codacy 2017-02-10 20:53:03 -05:00
kiswa
6b78eee125 Update dependencies 2017-02-11 01:40:58 +00:00
kiswa
193653f8f6 Front end tests functional again 2017-02-11 01:40:44 +00:00
kiswa
46075eac80 Update counts 2017-02-11 01:35:57 +00:00
kiswa
62618f624e Update counts 2017-02-08 00:26:53 +00:00
kiswa
b9829108e2 Add method to refresh token 2017-02-08 00:25:42 +00:00
kiswa
4b4305c178 Stop creating new JWT on each call 2017-02-08 00:25:04 +00:00
Matthew Ross
eb4254f113 WIP - Consider API without token refresh 2017-02-07 05:45:22 -05:00
Matthew Ross
f7d131521b Merge branch 're-write' of github.com:kiswa/TaskBoard into re-write 2017-02-06 16:51:11 -05:00
kiswa
fb00171bbe Auto-redirect to default board 2017-02-06 21:50:58 +00:00
kiswa
15ce789446 Make board admin links use routerLink 2017-02-06 21:50:40 +00:00
kiswa
c4f9045429 Update dependencies 2017-02-06 21:49:58 +00:00
Matthew Ross
2d02df481e Update dependencies 2017-02-04 07:44:51 -05:00
Matthew Ross
9e752a201b Update for future Bourbon deprecation 2017-02-04 07:44:03 -05:00
kiswa
363d1110b4 Add missed service file 2017-02-03 23:24:14 +00:00
kiswa
5c019bcaf1 Initial boards component work. 2017-02-03 23:23:45 +00:00
kiswa
4d255af99e Revert logging change and remove auto action on board removal 2017-02-03 23:23:20 +00:00
kiswa
5deb398f24 Add image for README 2017-02-03 23:12:38 +00:00
kiswa
4caf00aca2 Add settings section and update counts 2017-02-03 23:11:51 +00:00
kiswa
6aa2f88704 Break build when tsc task fails 2017-02-03 23:08:29 +00:00
kiswa
062de599ee Automatic Actions (and Settings page) complete! 2017-02-03 00:31:18 +00:00
Matthew Ross
5aecff9e56 Update API return and fix failing test 2017-02-02 05:58:02 -05:00
Matthew Ross
c0c06b7553 Add and remove Automatic Actions 2017-02-01 21:36:12 -05:00
Matthew Ross
dfb2f05772 Actually complete Automatic Actions behaviors 2017-02-01 18:08:54 -05:00
Matthew Ross
460d683e49 Update dependencies 2017-02-01 17:58:35 -05:00
Matthew Ross
0cccd0c56a Fix unit tests 2017-02-01 17:58:27 -05:00
kiswa
5a35d71131 Auto Actions front-end behaviors complete 2017-02-01 22:27:54 +00:00
kiswa
b3ddb51619 WIP Automatic Actions updates 2017-01-31 23:58:56 +00:00
kiswa
79ffd00074 Capture Enter key event in inline edit 2017-01-31 23:58:32 +00:00
kiswa
a89d473e62 Add default action attribute to modal 2017-01-31 23:57:57 +00:00
kiswa
cebbdb0a16 Fix multiple selects in modals 2017-01-31 21:49:32 +00:00
kiswa
e9a7d508ba Update API dependencies 2017-01-31 21:48:36 +00:00
Matthew Ross
3e15966bd9 WIP commit for Auto Actions front end 2017-01-30 21:46:24 -05:00
kiswa
c7e4a62238 Update dependencies 2017-01-31 02:26:12 +00:00
Matthew Ross
4ef99299e4 Fix TSLint issues. 2017-01-27 18:41:58 -05:00
kiswa
3009e8a360 Update board list when username is changed 2017-01-27 23:20:42 +00:00
kiswa
ab092e0283 Finalize Board Administration section 2017-01-27 23:19:54 +00:00
kiswa
aa4d2592fa Add SystemJS config properties 2017-01-27 23:18:01 +00:00
kiswa
a4559510f0 Update counts 2017-01-27 00:51:16 +00:00
kiswa
ef6faa7971 Update copyright dates 2017-01-27 00:47:58 +00:00
kiswa
f68bd91386 Update dependencies and change to gulp-istanbul for coverage 2017-01-27 00:47:40 +00:00
kiswa
7cc1e95450 Update API tests to 100% coverage 2017-01-27 00:46:47 +00:00
Matthew Ross
eea4748da7 Fix typo 2017-01-17 06:55:49 -05:00
Matthew Ross
a3ba638af7 Update counts 2017-01-17 06:55:12 -05:00
Matthew Ross
3a20dca7e0 Description not required for a Task 2017-01-17 06:46:40 -05:00
Matthew Ross
a010e35761 Update dependencies 2017-01-17 06:32:22 -05:00
Matthew Ross
4ffea04278 Initial Auto Actions admin setup 2017-01-17 06:29:32 -05:00
Matthew Ross
0dd62749df Hide actions when not allowed 2017-01-17 06:29:06 -05:00
Matthew Ross
dd1a7a0842 Include missed changes 2017-01-14 16:43:13 -05:00
Matthew Ross
3d0015d41f Refactor BeanLoader 2017-01-14 16:36:59 -05:00
Matthew Ross
040dfdeab7 Make LoadCategory a static function. 2017-01-14 15:19:56 -05:00
Matthew Ross
b27fca42de Run all tests in Travis CI 2017-01-14 15:12:04 -05:00
Matthew Ross
1e330811ae Rework BeanLoader with isset checks. 2017-01-14 15:10:47 -05:00
Matthew Ross
2301164f3e Update dependencies 2017-01-14 12:38:47 -05:00
Matthew Ross
6f2cf5c206 Remove duplication in app test code 2017-01-14 12:25:22 -05:00
Matthew Ross
13872686b6 Exclude test that only fails on Travis CI 2017-01-14 10:44:12 -05:00
Matthew Ross
c08c7ae083 One more Travis CI change 2017-01-14 10:32:55 -05:00
Matthew Ross
b097ea0cf7 Remove duplication 2017-01-14 10:22:07 -05:00
Matthew Ross
68bc12e85b Travis CI testing 2017-01-14 10:17:09 -05:00
Matthew Ross
005392d106 Updated test for Travis CI 2017-01-11 20:09:22 -05:00
Matthew Ross
4ac5a63b6a Another Travis CI test 2017-01-11 19:46:23 -05:00
Matthew Ross
67c2913f76 Add var_dump for Travis CI 2017-01-11 18:41:28 -05:00
Matthew Ross
5cdd18f1e0 Formatting 2017-01-11 17:48:18 -05:00
Matthew Ross
7a01529e96 Refactor user update to reduce complexity 2017-01-11 17:44:39 -05:00
Matthew Ross
7fb27afcaa Possible API test fix 2017-01-08 13:16:14 -05:00
Matthew Ross
bc527979d1 Change npm version used in CI 2017-01-08 12:56:41 -05:00
Matthew Ross
22081af564 Update active user after changing password 2017-01-08 12:01:13 -05:00
Matthew Ross
0b6eb28bcf Rename verifyPassword to password_verify for API 2017-01-08 12:00:22 -05:00
Matthew Ross
70edce59ff Fix setting default board on user add/update 2017-01-08 11:59:27 -05:00
Matthew Ross
8866895570 Update deps, change SCSS task 2017-01-08 11:58:12 -05:00
Matthew Ross
824c968c5b Missed one method call 2017-01-05 21:04:29 -05:00
Matthew Ross
a480041f9c Fix function call 2017-01-05 20:59:22 -05:00
Matthew Ross
f0cd7cac9e Refactor comlpex method 2017-01-05 20:53:05 -05:00
Matthew Ross
0dc7921d4e Additional code cleanup for Codacy 2017-01-05 20:25:52 -05:00
Matthew Ross
56f7a593fc Include src/api/vendor/ in clean task 2017-01-05 20:25:20 -05:00
kiswa
4d8afa1807 Code cleanup for Codacy findings 2017-01-06 00:54:51 +00:00
kiswa
6e667fd6ef Update counts 2017-01-04 11:20:25 +00:00
kiswa
3d12faa9bc Update dependencies 2017-01-04 11:18:09 +00:00
kiswa
a665f686ca Actions buttons spacing 2017-01-04 11:17:45 +00:00
kiswa
047858b170 Update app for API changes 2017-01-04 11:16:44 +00:00
kiswa
743ef622f2 Rework API - models removed 2017-01-04 11:15:58 +00:00
Matthew Ross
b051b0050b More code quality cleanup 2016-11-23 12:07:49 -05:00
Matthew Ross
9eb800b816 Add Codacy badge 2016-11-23 11:30:51 -05:00
Matthew Ross
3f439d5fb4 Update for code quality 2016-11-23 11:21:44 -05:00
kiswa
a02e8e6636 WIP - Model updates 2016-11-23 15:06:51 +00:00
kiswa
b7f4ccd929 Update API tests 2016-11-23 15:06:24 +00:00
kiswa
c3c61c3e86 Simplify config 2016-11-23 15:05:43 +00:00
kiswa
cd1c7c43bc Improve bean lookup 2016-11-23 15:05:04 +00:00
kiswa
4a3f7e080b Update dependencies 2016-11-23 15:03:58 +00:00
kiswa
7a7cafe985 Improve tasks 2016-11-23 15:01:23 +00:00
Matthew Ross
9349d91205 Fix API .htaccess 2016-11-23 09:59:26 -05:00
Matthew Ross
da2f09182b Update dependencies 2016-11-23 09:59:00 -05:00
kiswa
ae46db8804 Update counts 2016-11-10 00:01:40 +00:00
kiswa
9a5aa36912 Deep copy when editing board to prevent in-memory changes 2016-11-09 23:58:58 +00:00
kiswa
3cb62e56c0 Set noImplicitAny true for tsc 2016-11-09 23:58:21 +00:00
kiswa
1f3fe32a0a Update styles for inputs 2016-11-05 12:22:52 +00:00
kiswa
70d06b9c5c Fix app tests 2016-11-05 12:06:12 +00:00
kiswa
8fbab86d2d Remove var_dump call 2016-11-05 12:03:25 +00:00
kiswa
de693e85d5 Pass by value when editing board 2016-11-05 11:55:43 +00:00
kiswa
b810591674 Use npm version 3 in CI builds 2016-11-05 11:53:22 +00:00
kiswa
af9ebcfde3 Revert previous 2016-11-05 11:51:01 +00:00
kiswa
48c83befa6 Use older npm for CI builds 2016-11-05 11:44:02 +00:00
kiswa
e087aa2a4d Board edit WIP - Can add to a board now 2016-11-05 11:32:19 +00:00
kiswa
00c80e4b62 Disable warning on scss-base style override 2016-11-05 11:31:23 +00:00
kiswa
8d3e509ec9 Update dependencies 2016-11-05 11:30:45 +00:00
kiswa
f5035d46d5 Update counts 2016-11-05 11:30:33 +00:00
kiswa
7bbffc1f4e Fix user update removing board access 2016-09-29 23:37:01 +00:00
kiswa
9d4d36a7cf Formatting 2016-09-29 23:36:30 +00:00
kiswa
9900e7233c Add note about non-obvious usage 2016-09-29 23:36:08 +00:00
kiswa
5005e7d5ad Remove typings and update dependencies 2016-09-29 23:35:34 +00:00
kiswa
b9db907abf Move tsProject out of task to improve build time during watch task 2016-09-29 23:34:51 +00:00
kiswa
ab3521e5ea Add and test board behaviors when adding user 2016-09-23 21:07:38 +00:00
kiswa
f2a5cc20f6 Update dependencies 2016-09-23 12:42:50 +00:00
kiswa
3cd66d7ef6 Replace unhelpful assertTrue calls and add app settings test 2016-09-23 12:42:26 +00:00
kiswa
3d27489f88 Update counts 2016-09-21 23:32:14 +00:00
kiswa
d6e54e6675 Update app unit tests 2016-09-21 23:30:02 +00:00
kiswa
922f5c6030 Fix issue tracker bug ID not saving 2016-09-21 23:29:26 +00:00
kiswa
86647fe51e Don't require password when editing user 2016-09-21 23:28:52 +00:00
kiswa
2ee00f1312 Update modals (flex display, add autofocus element) 2016-09-21 23:28:25 +00:00
kiswa
d21bfc2a2d Remove unused import 2016-09-21 23:27:07 +00:00
kiswa
47d2ec65af Fix routing on API error and remove temp fix 2016-09-21 23:26:53 +00:00
kiswa
ed3a78981b Update dependencies 2016-09-21 23:26:15 +00:00
kiswa
25de0268b2 Fix user options and adding a user to a board 2016-09-21 23:25:22 +00:00
Matthew Ross
5915d7772e Fix display of user's board access 2016-09-17 10:28:40 -04:00
Matthew Ross
c8d9e38f26 Fix max-height of modal 2016-09-17 10:27:09 -04:00
Matthew Ross
371341f3ae Add inadvertently removed package 2016-09-15 18:48:03 -04:00
Matthew Ross
b731e795c2 Revert to RC6 until ng2-dragula updates 2016-09-15 18:45:29 -04:00
Matthew Ross
b3e2ceb67e Update dependencies 2016-09-15 18:45:09 -04:00
kiswa
98ce544c9e Refactor to update boards on user changes 2016-09-15 22:08:09 +00:00
kiswa
5aa6882e5c Refactor modal property bindings 2016-09-15 22:07:03 +00:00
kiswa
477d5cd9bd Update to Angular 2 2016-09-15 22:03:47 +00:00
kiswa
3c51486f3e Update counts 2016-09-15 22:03:28 +00:00
Matthew Ross
fafafe898e Fix modal left position to center on screen 2016-09-14 20:45:27 -04:00
Matthew Ross
cdbd36f16e Fix modal positioning to work with drag and drop 2016-09-14 20:41:15 -04:00
Matthew Ross
119362374e Update zone.js to 0.6.23 2016-09-14 20:40:29 -04:00
kiswa
bd32b520b2 Add install step to CI 2016-09-14 15:08:47 +00:00
kiswa
4b4e363e3a Revert to RC6 until deps are fixed 2016-09-14 13:18:54 +00:00
Matthew Ross
de2078c5cc Install missing package during CI 2016-09-13 18:01:59 -04:00
Matthew Ross
26f8cc32f6 Add missing parameter 2016-09-13 18:01:39 -04:00
kiswa
8daec83721 Update counts 2016-09-13 21:10:45 +00:00
kiswa
e22e30eccb Add user to board when setting default board 2016-09-13 21:08:39 +00:00
kiswa
960457b82b Update to Angular 2 RC7 2016-09-13 21:07:13 +00:00
Matthew Ross
8bfd66e05b Update dependencies 2016-09-10 09:24:39 -04:00
kiswa
9a0b18d77c Actually fix handling of child relationships in API 2016-09-09 20:55:23 +00:00
kiswa
1dfd7754e6 Use latest ng2-dragula - Fixes CI build 2016-09-09 15:04:07 +00:00
kiswa
7dbd6a3d0f Remove redundant setting 2016-09-09 14:03:41 +00:00
Matthew Ross
92396e96f8 Modal button disables during save 2016-09-08 16:16:39 -04:00
Matthew Ross
5355824e28 Revert systemjs-builder to version that works at home 2016-09-06 20:11:17 -04:00
kiswa
c6e2608360 Fix app unit test 2016-09-02 20:26:21 +00:00
kiswa
05fdc54aa0 Update to Angular 2 RC6 2016-09-02 20:23:19 +00:00
kiswa
84b2bfdde1 Cleanup 2016-09-01 21:45:33 +00:00
kiswa
039799a775 Rework API to correctly handle child objects 2016-09-01 21:40:40 +00:00
kiswa
8994099167 Update counts 2016-08-30 22:01:20 +00:00
kiswa
1d9baa00a8 Refactoring and test updates 2016-08-30 21:59:10 +00:00
kiswa
b994e030eb Bugfixes 2016-08-30 21:58:34 +00:00
kiswa
183a76414b Update failing tests 2016-08-29 12:54:01 +00:00
kiswa
93a1bf4ebb Update CI build to fail if app tests fail 2016-08-29 12:47:09 +00:00
Matthew Ross
62261b7f3b Fix dependencies and remove useless style 2016-08-29 06:35:45 -04:00
kiswa
520ae0ab29 Update counts 2016-08-26 21:43:43 +00:00
kiswa
6f0c27df44 Modal animation and board admin tweaks 2016-08-26 21:43:33 +00:00
kiswa
84bf2b46c2 Use toggles instead of checkboxes 2016-08-26 21:39:38 +00:00
kiswa
271fc5d1ba Include all admins on every board added 2016-08-26 20:52:37 +00:00
kiswa
5683b26d2d Add messages on admin first login 2016-08-26 20:52:13 +00:00
kiswa
11a4eb0cfb Cleanup 2016-08-26 20:50:58 +00:00
Matthew Ross
53e89426d4 100% API coverage again 2016-08-25 20:15:15 -04:00
kiswa
80798ab0a4 Update API tests 2016-08-25 23:00:25 +00:00
kiswa
17632b06c8 Adding a board is functional 2016-08-25 21:27:44 +00:00
kiswa
97d2e4c23a Set site title per page 2016-08-25 21:27:10 +00:00
kiswa
85c5575b36 Fix adding a board 2016-08-25 21:23:29 +00:00
kiswa
b95ec71bc4 Add test counts 2016-08-25 21:20:27 +00:00
kiswa
09dc212e46 Add Issue Tracker model and update tests 2016-08-25 21:19:20 +00:00
kiswa
a6dcab34f2 Update app tests 2016-08-25 10:38:39 +00:00
kiswa
a6f23951a5 Additional models 2016-08-25 10:38:13 +00:00
kiswa
45ecb6fdde Additional board admin work 2016-08-25 10:35:32 +00:00
kiswa
e6186aabf0 Inline edit component 2016-08-25 10:33:29 +00:00
kiswa
335e1ed095 Redirect past login when valid token exists 2016-08-25 10:28:49 +00:00
kiswa
8993fbb26b Add Issue Tracker model to API 2016-08-25 10:28:01 +00:00
kiswa
af3d281bb4 Update dependencies 2016-08-25 10:26:41 +00:00
kiswa
35f435a40c Add code counts 2016-08-25 10:23:57 +00:00
Matthew Ross
76a883b024 Update dependencies 2016-08-22 06:40:17 -04:00
Matthew Ross
cc2574f3db Update dependencies 2016-08-22 06:38:49 -04:00
kiswa
bf580b77e0 Fix unit tests and create and use settings service 2016-08-19 11:10:53 +00:00
kiswa
58f43cc0ae Check for existing username on user add 2016-08-19 11:09:30 +00:00
kiswa
457594a264 Merge branch 're-write' of github.com:kiswa/TaskBoard into re-write 2016-08-17 21:01:18 +00:00
kiswa
2a0d780459 Fix app tests and update/clean tests 2016-08-17 16:25:08 +00:00
kiswa
0577661791 Rename board settings to board admin 2016-08-17 16:24:38 +00:00
Matthew Ross
40b494acc1 Fix style ordering 2016-08-16 18:55:33 -05:00
kiswa
f0ba3956d4 WIP - Close to done with add/edit board modal 2016-08-16 22:02:25 +00:00
kiswa
af2186a1ca Update icon font 2016-08-16 22:01:25 +00:00
kiswa
ba78cb0a37 Update to Angular 2 RC5 2016-08-13 15:09:44 +00:00
kiswa
cac92b839f Update API dependencies 2016-08-12 15:42:54 +00:00
kiswa
30fac9430e WIP - Board administration 2016-08-12 15:15:33 +00:00
kiswa
32bd9246a5 Initial board settings work - new component 2016-08-03 20:46:01 +00:00
kiswa
76c39e8ab6 Update dependencies 2016-08-03 20:45:27 +00:00
Matthew Ross
536f776b83 App unit tests complete with current app code 2016-08-02 14:37:45 -04:00
kiswa
de0c919395 User settings and initial dashboard unit tests 2016-08-02 17:50:36 +00:00
kiswa
c8af0ca903 Set default values 2016-08-02 17:49:44 +00:00
kiswa
1d19687a4e Clean build directory before tsc task 2016-08-02 17:48:41 +00:00
Matthew Ross
ee9ebcc70c User Settings unit tests 2016-08-01 21:07:43 -04:00
Matthew Ross
0b01cb691b UserAdminService unit tests 2016-08-01 16:49:58 -04:00
kiswa
76552cfae5 More app unit tests and changes from testing 2016-08-01 20:29:08 +00:00
kiswa
e4b1d93953 Initial front end unit tests 2016-07-31 20:02:18 +00:00
kiswa
7808ce18cd Modal SCSS and notifications changes 2016-07-31 20:01:13 +00:00
kiswa
e278074cf0 Use modal and update components 2016-07-31 20:00:40 +00:00
kiswa
b096aac522 Rename title for Default Task Color badges 2016-07-31 19:55:32 +00:00
kiswa
33f535d30a Modal finalized 2016-07-31 19:54:30 +00:00
kiswa
3d42003cf3 Cleanup 2016-07-31 19:52:04 +00:00
kiswa
e0a0cb4470 Users controller updates and unit tests 2016-07-31 19:50:59 +00:00
Matthew Ross
8c1c68d792 Cleanup 2016-07-26 20:16:07 -04:00
kiswa
872697a1ae Initial modal component work 2016-07-26 21:22:29 +00:00
kiswa
8ab625753e Change User model to class 2016-07-26 21:19:52 +00:00
kiswa
5246737d07 Add new icons to font and styles 2016-07-26 21:17:52 +00:00
kiswa
5fbf734b57 Update systemjs and systemjs-builder 2016-07-26 21:16:02 +00:00
kiswa
ee9d50f0cc Initial user administration section work 2016-07-25 20:20:27 +00:00
kiswa
98cfad45cb Add endpoint for updating user options 2016-07-25 20:19:50 +00:00
kiswa
02a523baeb Logging of user log in/out actions 2016-07-25 20:18:51 +00:00
kiswa
689af0f7f0 Loading spinner and helper classes 2016-07-25 20:14:43 +00:00
kiswa
476465e53f Update to typings 1.3.2 2016-07-25 20:12:43 +00:00
kiswa
9a4d0d9657 WIP - Initial change UserOptions 2016-07-22 20:21:58 +00:00
kiswa
2dff84b33f Change Email functional, create and add UserOptions model 2016-07-22 20:21:07 +00:00
kiswa
11a06d124e Include UserOptions in AuthService 2016-07-22 20:16:12 +00:00
kiswa
c57f6eb6fd Create UserOptions when a user does not have them 2016-07-22 20:13:38 +00:00
kiswa
f2aff6a960 Return UserOptions with authenticate call 2016-07-22 20:13:06 +00:00
kiswa
76fa6e9623 Remove 'single' group from test 2016-07-22 20:08:43 +00:00
kiswa
00b414370e Use custom options for all call types 2016-07-22 20:07:52 +00:00
kiswa
1553105082 Remove duplicate keywords, add a couple new ones 2016-07-22 20:06:55 +00:00
Matthew Ross
42ec6959c3 WIP - Change email form 2016-07-22 06:28:01 -04:00
kiswa
06c0dcf93d User settings now allows username change 2016-07-21 20:06:53 +00:00
kiswa
fee7a80bca Include username change validation in Users controller 2016-07-21 20:06:08 +00:00
kiswa
ca308ca1ee Move and rename auth-http to api-http 2016-07-21 20:05:37 +00:00
kiswa
c91451a1b3 Update API tests to fix build 2016-07-21 17:11:14 +00:00
Matthew Ross
744133837c Cleanup 2016-07-20 21:10:09 -04:00
Matthew Ross
f60c1a5ec4 Remove console.log 2016-07-20 20:31:15 -04:00
Matthew Ross
823daf740c New Http wrapper, updated components, minor API change
The new Http wrapper automatically handles updates to the JWT during
server communication and redirects to login on unauthenticated calls.
2016-07-20 20:21:15 -04:00
Matthew Ross
c2938c9c38 API tests actually at 100% 2016-07-20 16:50:40 -04:00
kiswa
8647ae9046 Update password works and API tests at 100% 2016-07-20 20:36:02 +00:00
kiswa
f1e1cbd502 Update dependencies 2016-07-20 20:25:43 +00:00
kiswa
6e856d8be8 More work on user settings - needs testing 2016-07-19 10:27:17 +00:00
kiswa
1d0a957c5c Update scss-base to latest version 2016-07-19 10:26:31 +00:00
kiswa
e4797c04fd Update dependencies and fix login SCSS 2016-07-18 11:50:32 +00:00
Matthew Ross
12d8b2b1cb Start separating settings sections into components 2016-07-17 21:19:38 -04:00
Matthew Ross
c0a9bb5c48 Cleanup 2016-07-16 18:13:16 -04:00
Matthew Ross
c55f91811a Add and use new authorization endpoint
The guard for routes now uses this endpoint to validate an existing JWT
when the app is refreshed - instead of automatically redirecting to the
login page.
2016-07-16 18:03:18 -04:00
Matthew Ross
1ad9dfddaf Include First Use section in README 2016-07-16 13:47:54 -04:00
Matthew Ross
050b52e331 Add authenticate endpoint to validate an existing JWT 2016-07-16 13:42:27 -04:00
Matthew Ross
a93f3bb005 More reorganization and initial login/logout functionality 2016-07-16 13:41:28 -04:00
kiswa
68d693307d Reorganize app files, setup initial routing and auth checks
Log in/out is functional with notifications (modified from recipes project).
Shared resources are now organized as such, with barrels to make imports simple.
2016-07-16 12:09:31 +00:00
kiswa
ea7f18ef7b Reset base href and use new vendor.js include 2016-07-15 20:13:34 +00:00
kiswa
6807d72210 Rework Gulp tasks 2016-07-15 20:12:40 +00:00
Matthew Ross
b9c83f19bc Initial router work. 2016-07-13 05:59:19 -04:00
kiswa
07e38517e4 Rename files and mock Board view 2016-07-12 21:14:08 +00:00
kiswa
ba78272e97 Cleanup testing files 2016-07-11 17:34:29 +00:00
Matthew Ross
596e4833fe Response filtering for user endpoints 2016-07-10 19:36:44 -04:00
Matthew Ross
e4a33afaeb Update scss-base 2016-07-10 19:32:17 -04:00
Matthew Ross
c338899b41 Update dependencies, tweak test-api task, and ignore test log file 2016-07-04 21:08:36 -04:00
kiswa
ed014a6856 Mock up login and add constants 2016-06-30 20:30:04 +00:00
kiswa
f2e41f9778 Add Chartist.js to Front End section 2016-06-30 20:29:09 +00:00
kiswa
e809450005 Update dependencies 2016-06-29 14:29:41 +00:00
kiswa
6f9596f332 Tweaks to the dashboard and its components 2016-06-24 20:50:42 +00:00
kiswa
b63c8ba7c0 Move GitHub specific files to .github directory 2016-06-24 20:50:01 +00:00
kiswa
b0cebb4c79 Add burndown chart to mockup 2016-06-23 22:05:01 +00:00
kiswa
c4985ea7af Update dependencies 2016-06-23 22:04:18 +00:00
Matthew Ross
a8ef62d984 Update dependencies and remove temporary fix 2016-06-22 17:15:49 -04:00
Matthew Ross
75c5690280 Response filtering for task endpoints 2016-06-18 13:52:27 -07:00
Matthew Ross
178332d871 Response filtering for comment endpoints 2016-06-17 11:24:49 -07:00
Matthew Ross
9d46b25bc8 Response filtering for column endpoints 2016-06-16 16:53:31 -04:00
Matthew Ross
9d38df9043 Bring test coverage back to 100% (PHP 7 lies) 2016-06-16 11:49:15 -04:00
Matthew Ross
281e5c432a Update CI script for typings install 2016-06-16 10:16:35 -04:00
Matthew Ross
f580185e48 Update dependencies, including Angular2@rc.2 2016-06-16 10:11:23 -04:00
Matthew Ross
d81918cf68 Response filtering for board endpoints 2016-06-16 10:01:00 -04:00
Matthew Ross
133737d4a4 Response filtering for attachment endpoints 2016-06-15 19:42:40 -04:00
Matthew Ross
56f35ff799 Use new router (with temp fixes) 2016-06-14 18:27:11 -04:00
kiswa
e885d8f3e5 Response filtering work and unit tests updates - WIP 2016-06-10 20:33:43 +00:00
kiswa
4926f81877 Match check for creation of admin with JWT 2016-06-10 20:31:12 +00:00
kiswa
e9fa19b083 Add initial calendar to Dashboard mockup 2016-06-10 20:27:05 +00:00
Matthew Ross
2d6b9d6c8b WIP - Will break build 2016-06-09 06:26:40 -04:00
Matthew Ross
16deb35442 Change board users to shared list 2016-06-08 16:52:02 -04:00
kiswa
c9380e2cd9 Merge branch 're-write' of github.com:kiswa/TaskBoard into re-write 2016-06-08 20:09:09 +00:00
kiswa
886d8a8c9b Dashboard mock-up additions 2016-06-08 20:09:05 +00:00
kiswa
8879de20f2 Include timestamp in activity logs 2016-06-08 20:08:45 +00:00
kiswa
92de7c5944 Add core-js to shims task 2016-06-08 20:08:27 +00:00
Matthew Ross
057fa98614 Disable ColorVariable linter 2016-06-07 21:32:22 -04:00
kiswa
b6a9cb9436 Initial dashboard page mockup with charts 2016-06-08 01:14:53 +00:00
kiswa
ec0394ce89 Update systemjs-builder dependency 2016-06-07 16:33:13 +00:00
Matthew Ross
b4815edb01 Update gulp-typescript dependency 2016-06-04 14:06:45 -04:00
Matthew Ross
876e363020 Update API dependencies 2016-06-04 14:04:46 -04:00
Matthew Ross
27c3681983 Update missed logging section 2016-05-31 17:22:14 -05:00
Matthew Ross
aab443b765 Update dependencies 2016-05-31 17:00:24 -05:00
kiswa
6a4156291b Route security and tests complete 2016-05-31 21:46:26 +00:00
kiswa
4da377f426 Continued route security updates and tests 2016-05-28 12:29:36 +00:00
kiswa
f8e88b8e36 Upload coverage results to Dropbox 2016-05-27 13:00:59 +00:00
kiswa
0a4e4b2623 Route security implementation and tests 2016-05-27 11:54:01 +00:00
kiswa
9419931834 Fix unit test 2016-05-25 21:47:39 +00:00
kiswa
929ec17a33 Begin implementing route security and tests 2016-05-25 21:02:11 +00:00
kiswa
15418a67f0 Remove useless route and 'document' route rules 2016-05-25 21:01:31 +00:00
kiswa
06fe8bd276 Add markdown parenthetical 2016-05-25 20:57:07 +00:00
kiswa
b3fc21bd69 Update dependencies 2016-05-25 20:56:05 +00:00
kiswa
ec68921595 Auth controller implemented and 100% test coverage 2016-05-23 20:34:06 +00:00
kiswa
9fd81422f6 Fix stupid typo 2016-05-23 12:49:55 +00:00
Matthew Ross
ca1a89039e WIP commit 2016-05-23 06:40:41 -04:00
kiswa
37690ee5af Various changes WIP 2016-05-22 11:07:16 +00:00
kiswa
464f664edd Add customized icon font and style changes 2016-05-22 11:06:26 +00:00
ZM-git
fedd17af59 Dev (#284)
* Added a default color for each board category to be used over the def…

* board.js fix for previous commit

* Feature for integration with Issue Tracking systems loosely based on TortoiseSVN Bugtraq.
2016-05-16 09:51:58 -04:00
Matthew Ross
6ae3d11a83 Add initial Auth class and update User 2016-05-15 20:53:21 -04:00
Matthew Ross
40bc11a45c Tweak settings mockup 2016-05-13 16:29:57 -04:00
Matthew Ross
3fb468cb16 Use scss-base v1.1.1 2016-05-13 16:29:20 -04:00
Matthew Ross
297a02d7e1 Remove unused code 2016-05-13 16:08:26 -04:00
kiswa
eff5e62121 Mock up Settings page 2016-05-13 20:04:24 +00:00
kiswa
5e18051188 API implementation and tests 2016-05-13 20:03:20 +00:00
Matthew Ross
278adffcd6 API unit tests and updates 2016-05-13 06:29:13 -04:00
kiswa
67e25d707e API unit tests 2016-05-12 20:15:04 +00:00
kiswa
a11b640496 API controller implementation 2016-05-12 10:27:58 +00:00
Matthew Ross
06efbfd45e API controller implementation 2016-05-11 06:20:55 -04:00
kiswa
bedaebff6c Remove static methods from models and update tests 2016-05-10 20:39:08 +00:00
kiswa
18cb96c433 WIP likely to fail build 2016-05-10 13:32:27 +00:00
Matthew Ross
049d3fc70e Implement Attachments controller 2016-05-09 20:06:02 -04:00
Matthew Ross
b3e5b38f9e Fix typo 2016-05-09 18:06:22 -04:00
kiswa
59b80e472a Update API and tests 2016-05-09 22:00:01 +00:00
kiswa
e8a9e5470f Rename 'styles' task to 'scss' 2016-05-09 21:59:21 +00:00
Matthew Ross
d4a1eed8ae WIP - build will fail 2016-05-09 06:33:48 -04:00
Matthew Ross
eaf88269d7 Increase top nav button contrast for a11y
Using https://github.com/Khan/tota11y for a11y recommendations for this
and all future style work.
2016-05-08 19:41:26 -04:00
kiswa
528c79c1aa API WIP commit 2016-05-07 11:55:35 +00:00
kiswa
e46b7e2ca7 Use prefix per Angular 2 styleguide 2016-05-07 11:43:58 +00:00
kiswa
bef6adcf31 Point travis.yml to new phpunit.xml location 2016-05-06 00:57:22 +00:00
kiswa
25c6eaf7a8 File cleanup 2016-05-06 00:42:31 +00:00
kiswa
f06a1a4161 Generate tests.db on test run 2016-05-06 00:31:44 +00:00
kiswa
49b2ae5cf1 API tests at 100% and organized 2016-05-06 00:16:20 +00:00
kiswa
132f6374b2 Use 2 spaces in .yml files 2016-05-05 15:56:24 +00:00
kiswa
534d3499d4 Update to Angular2.rc-1 and bundle app files 2016-05-05 15:48:28 +00:00
kiswa
7d06e5c0c7 Reorganize and update README 2016-05-05 15:37:38 +00:00
Matthew Ross
678c6614d2 API models fully tested 2016-05-02 20:13:43 -04:00
kiswa
e58c48dad0 WIP - will break build 2016-05-02 21:17:55 +00:00
kiswa
34602c95cc Match renamed phpunit.xml in tasks 2016-05-02 18:49:47 +00:00
kiswa
729f31d66b Remove dry run option from test-api task 2016-05-02 18:36:42 +00:00
kiswa
ab24b9f461 API unit test fixes 2016-05-02 18:29:22 +00:00
kiswa
54a2918a38 One more Travis attempt 2016-05-02 18:24:18 +00:00
kiswa
d20d372e07 More CI testing 2016-05-02 17:58:16 +00:00
kiswa
e835b8da3c More CI testing 2016-05-02 17:40:02 +00:00
kiswa
a8289ff2c9 More CI testing 2016-05-02 17:33:54 +00:00
kiswa
1dc2aac653 One more Travis CI test 2016-05-02 17:25:28 +00:00
kiswa
23cdb0c85c Another Travis CI test 2016-05-02 17:16:05 +00:00
kiswa
8138e268f6 Travis changes - may not work 2016-05-02 14:04:38 +00:00
kiswa
7928a4d6da Update dependencies 2016-05-02 14:03:06 +00:00
kiswa
ad6a5ec4a4 Unit test updates and implementation 2016-04-30 02:28:53 +00:00
Matthew Ross
ef90f2a780 Merge pull request #279 from jersou/dev
update Dockerfile to match installation guide
2016-04-29 08:29:31 -04:00
Jerem
1bd9907f04 update Dockerfile to match installation guide 2016-04-29 07:30:43 +02:00
Matthew Ross
579d798176 Update README.md 2016-04-28 10:29:08 -04:00
Matthew Ross
e6865cb93f Update reddit badge link 2016-04-28 10:19:50 -04:00
Matthew Ross
d1789f9850 Update README.md 2016-04-28 10:16:17 -04:00
Matthew Ross
cb6f236383 Update README.md 2016-04-28 10:16:08 -04:00
kiswa
92c63d8b65 Update reddit badge alternate text 2016-04-28 14:15:17 +00:00
kiswa
0944471805 Update reddit badge 2016-04-28 14:11:06 +00:00
Matthew Ross
12f27e5cfa Used wrong link 2016-04-27 20:57:11 -04:00
Matthew Ross
459242e3c6 Last attempt at reddit image 2016-04-27 20:55:53 -04:00
Matthew Ross
9653f560a7 Update reddit badge link 2016-04-27 20:52:36 -04:00
Matthew Ross
7501f02333 Add reddit badge 2016-04-27 20:51:29 -04:00
Matthew Ross
c3a3c11afc API work and unit test updates 2016-04-27 20:07:51 -04:00
Matthew Ross
24e43fb0e8 Add PHP 7.0 to list of supported versions 2016-04-27 20:06:55 -04:00
Matthew Ross
1a360f9a62 Merge branch 're-write' of https://github.com/kiswa/TaskBoard into re-write 2016-04-27 18:12:44 -04:00
kiswa
5a5741e4df API class properties and unit test updates 2016-04-27 19:57:33 +00:00
kiswa
9a68fdfd8c Remove expires section 2016-04-27 18:03:45 +00:00
Matthew Ross
889ebb54ad Merge branch 're-write' of https://github.com/kiswa/TaskBoard into re-write 2016-04-26 16:37:46 -04:00
Matthew Ross
d857fc6794 Tweak top-nav button styles
This assumes the active button is the one without a 'flat' class.
2016-04-26 16:37:33 -04:00
Matthew Ross
e3d7f84bc9 Update dependencies 2016-04-26 16:36:38 -04:00
kiswa
fa225ec6f7 Formatting 2016-04-26 16:57:57 +00:00
kiswa
02c91df745 Include PHP test coverage 2016-04-26 13:05:41 +00:00
Matthew Ross
99d0c272f7 Update Board and remove 'abstract' from implementations 2016-04-25 21:11:10 -04:00
Matthew Ross
6562a149d9 Update BaseModel to include loadFromJson 2016-04-25 20:05:03 -04:00
Matthew Ross
bda10a87ec Modify top-nav styles for board name and version 2016-04-25 18:23:53 -04:00
kiswa
13853c6d9e WIP commit for API board model 2016-04-25 21:37:45 +00:00
Matthew Ross
cbcf132955 Initial Boards API implementation
Created initial Activity model and DbLogger class for logging changes to
the database (to be used in activity displays). Includes initial unit
tests for Boards implementation.
2016-04-23 15:17:37 -04:00
Matthew Ross
7a59216451 Add background color transition to top nav buttons 2016-04-23 14:25:34 -04:00
Matthew Ross
043eb9303d Change default text color 2016-04-23 10:42:56 -04:00
Matthew Ross
6ab896201c Revert gulp clean change 2016-04-23 10:07:27 -04:00
Matthew Ross
16cea15553 Update clean task and remove placeholder file. 2016-04-23 10:04:59 -04:00
kiswa
9bee7c0467 Format API endpoints and update CHANGELOG 2016-04-22 18:25:00 +00:00
kiswa
1d44d2e7af Rename two routes and the DB file 2016-04-22 17:57:32 +00:00
kiswa
87b17f70c0 Add placeholder API models 2016-04-22 17:46:15 +00:00
kiswa
3b48bd03bc API updates and additions
Planned API endpoints and created placeholder classes for their
controllers. Updated unit tests to test existing API code.
2016-04-22 17:09:02 +00:00
kiswa
853edbb61d Remove PHP 5.4 as Slim does not support 2016-04-22 13:02:38 +00:00
kiswa
76721b282f Use more accurate style property 2016-04-22 12:56:37 +00:00
kiswa
5e879c3489 Fixes for proper test runs 2016-04-22 12:56:16 +00:00
Matthew Ross
69a891eb6e Add test task to run all tests 2016-04-21 18:56:22 -04:00
Matthew Ross
51b5e12de1 Initial API test setup 2016-04-21 18:36:48 -04:00
Matthew Ross
f057dca367 Add PHPUnit dependency 2016-04-21 18:20:30 -04:00
Matthew Ross
0b0a4e9d69 Initial API setup 2016-04-21 18:08:59 -04:00
Matthew Ross
72c0c37672 Add front end tool to list 2016-04-21 16:57:56 -04:00
Matthew Ross
a4ac0c0662 Remove unused dependency 2016-04-21 16:53:45 -04:00
kiswa
41312a1ed2 Initial app creation with top nav 2016-04-21 18:51:08 +00:00
kiswa
cf16a9c2b0 Specify NodeJS version 2016-04-21 14:58:52 +00:00
kiswa
a319ebf762 Typo fix 2016-04-21 14:53:31 +00:00
kiswa
794d3df8f3 Change to PHP CI 2016-04-21 14:50:17 +00:00
kiswa
d0423b7170 Add Travis CI configuration 2016-04-21 14:38:11 +00:00
kiswa
8bccffb9b6 Prepare for Travis CI 2016-04-21 14:36:40 +00:00
kiswa
4818f73680 Re-order sections, Add Contributing section 2016-04-21 14:23:48 +00:00
kiswa
68051cdf8a Add build image, fix formatting, mention SCSS linting 2016-04-21 14:18:16 +00:00
kiswa
a1d50413d2 Reset base href 2016-04-21 14:17:25 +00:00
kiswa
9054f24484 Update dependencies 2016-04-21 14:17:03 +00:00
Matthew Ross
1ea052c7b8 Specify which Angular is used 2016-04-21 06:23:11 -04:00
Matthew Ross
5e50591010 Initial SCSS setup 2016-04-21 06:20:39 -04:00
Matthew Ross
fa68d3ec1f Update gulpfile, remove favicons, and add placeholders for directories 2016-04-21 05:56:28 -04:00
Matthew Ross
18aebfc01e Update Composer dependencies
Now includes JWT and PHPMailer for future use.
2016-04-21 05:43:24 -04:00
Matthew Ross
4fd2219097 Update changelog format 2016-04-20 20:56:30 -04:00
Matthew Ross
90970d98be Set format for changelog 2016-04-20 20:55:13 -04:00
Matthew Ross
fb847c752e Rename VERSION to CHANGELOG.md 2016-04-20 20:53:11 -04:00
Matthew Ross
e25114213f Move .htaccess to src 2016-04-20 20:51:38 -04:00
Matthew Ross
9c52d37763 Add link for SQLite 2016-04-20 20:45:00 -04:00
Matthew Ross
2cb0116f1d Create initial app to verify toolchain 2016-04-20 20:38:22 -04:00
Matthew Ross
cf4617da77 Remove unused task 2016-04-20 20:36:34 -04:00
Matthew Ross
42697ab788 Mention other database options 2016-04-20 20:24:19 -04:00
Matthew Ross
51c95669b0 Move "How It's Made" section below "Development" 2016-04-20 20:21:56 -04:00
Matthew Ross
27c1710ebf Toolchain set up complete
Gulp is the task runner with Composer for API dependencies. All initial
dependencies are installed. Ready for work.
2016-04-20 20:20:04 -04:00
Matthew Ross
42daa15063 Initial re-write commit 2016-04-20 17:26:32 -04:00
Matthew Ross
2546120052 Update copyright dates. 2016-01-03 21:15:50 -05:00
Matthew Ross
a835aa7bb8 Merge pull request #242 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2015-12-18 11:05:20 -05:00
The Gitter Badger
7e6d6afc26 Add Gitter badge 2015-12-18 16:02:30 +00:00
kiswa
423fec24f5 Merge branch 'bugfixes' into dev 2015-12-04 13:43:48 -05:00
Matthew Ross
6b96917de3 Merge pull request #237 from kiswa/master
Merge master to demo
2015-12-04 13:37:35 -05:00
Matthew Ross
db0b442267 Merge pull request #236 from kiswa/bugfixes
Merge bugfixes branch
2015-12-04 13:06:40 -05:00
kiswa
8d95a9ed0e Auto actions on moved item now work. Fixes #153. 2015-12-04 13:04:40 -05:00
kiswa
60033fd92b Merge branch 'bugfixes' of https://github.com/kiswa/TaskBoard into bugfixes 2015-12-04 12:44:40 -05:00
kiswa
42e610a22b Add label to user board options for checkbox options. 2015-12-04 12:44:12 -05:00
Matthew Ross
f588f0c036 Update README. 2015-11-08 17:40:27 -05:00
Matthew Ross
09e6761cec Update SLoC counts. 2015-11-08 17:38:42 -05:00
Matthew Ross
f2530601cc Fix user options bugs that caused bad display. Fixes #213 2015-11-08 17:31:05 -05:00
Matthew Ross
4bb79a83f6 Modal dialogs no longer remain open after completing their task. 2015-11-08 16:28:54 -05:00
Matthew Ross
42c56edfc0 Update list of fixes. 2015-11-08 16:14:16 -05:00
Matthew Ross
252de39ac0 Allow setting email to nothing.
Previously, once an email is added to an account, it was not possible to
remove it. Now an empty email address is accepted.
2015-11-08 16:12:32 -05:00
Matthew Ross
27ca92f2fd Add curl requirement to README. Fixes #224. 2015-11-08 16:04:24 -05:00
Matthew Ross
55201fed29 Merge pull request #231 from praveenscience/dev
Update compatibility of arrays with PHP < 5.4
2015-11-05 10:14:25 -05:00
Praveen Kumar
3f6ded3fe8 Updated compatibility of arrays with PHP 5.x.
* Added array() instead of just [].
* Created a new variable for function_call()[] type of array calling.
* The both forms are not supported in PHP 5.x.
2015-10-29 19:55:57 +00:00
kiswa
4deefd8aae Update version for bugfix branch. 2015-10-06 09:44:12 -04:00
kiswa
42c511381e Make optional step more clear. 2015-10-06 09:39:33 -04:00
kiswa
568be4df3b Update Composer 2015-10-06 09:37:12 -04:00
kiswa
ea871e65cc Update install step for accurate location. 2015-10-06 09:30:47 -04:00
kiswa
e83a60b06d Tweak quick-add input size. 2015-07-29 12:39:38 -04:00
kiswa
e6cd183b97 Merge branch 'dev' of https://github.com/kiswa/TaskBoard into dev 2015-07-29 12:33:00 -04:00
kiswa
72dfe4e79b Update Composer deps. 2015-07-29 12:32:41 -04:00
kiswa
b9516cd131 Update Composer. 2015-07-29 12:28:28 -04:00
Matthew Ross
d8efdabcfb Merge pull request #203 from camael24/ndev
Support Vagrant
2015-07-20 21:19:44 -04:00
camael24
1a587bdb70 Support Vagrant 2015-07-20 17:03:34 +02:00
Matthew Ross
eeaea7038e Clear board auto actions on board delete. Fixes #179. 2015-06-27 12:31:15 -04:00
Matthew Ross
deaf648f1d Item quick add now available. Fixes #135. 2015-06-27 12:23:22 -04:00
Matthew Ross
9552462d98 Merge pull request #198 from kiswa/master
Update README.md
2015-06-19 07:52:02 -04:00
Matthew Ross
71928bbc85 Update README.md 2015-06-19 07:51:10 -04:00
Matthew Ross
e1a0d36008 Merge pull request #195 from kiswa/master
Merge master to demo
2015-06-06 13:08:45 -04:00
Matthew Ross
bbbbe871d9 Merge pull request #194 from kiswa/master
Make sure dev matches master
2015-06-06 13:08:09 -04:00
Matthew Ross
fb8f3d7ce8 Merge pull request #193 from kiswa/dev
Update build script for new jQuery version.
2015-06-06 13:04:17 -04:00
Matthew Ross
3f414d4ded Update build script for new jQuery version. 2015-06-06 13:02:24 -04:00
Matthew Ross
73ae7f5aa4 Merge master to demo. 2015-06-06 12:57:40 -04:00
Matthew Ross
b0ac80790c Merge pull request #191 from kiswa/dev
Merge dev into master
2015-06-06 12:38:57 -04:00
Matthew Ross
1891000aa4 Update counts in README. 2015-06-06 12:36:26 -04:00
Matthew Ross
998531a1b4 Fixes #65. Settings now allow disabling animations. 2015-06-06 12:34:34 -04:00
Matthew Ross
f989b2acb3 Fixes #128. Setting to have tasks appear at top of column. 2015-06-05 18:01:37 -04:00
Matthew Ross
ee0e38e5c7 Merge branch 'dev' of https://github.com/kiswa/TaskBoard into dev 2015-06-05 14:44:31 -04:00
Matthew Ross
c18d76c389 Add missing font file. 2015-06-05 14:44:26 -04:00
kiswa
6c004f3a26 Fix last updated date 2015-06-04 08:16:02 -04:00
kiswa
c980df33da Update SLoC counts 2015-06-04 08:13:58 -04:00
Matthew Ross
ebba80b7cf Update Font Awesome to 4.3.0 2015-06-04 06:12:42 -04:00
Matthew Ross
c746e00447 Update noty to v2.3.5 2015-06-04 06:02:32 -04:00
Matthew Ross
f8d60e13fd Update Spectrum.js to 1.7.0 2015-06-04 05:59:25 -04:00
Matthew Ross
af086dc8ca Update jQueryUI to 1.11.4. 2015-06-04 05:54:27 -04:00
Matthew Ross
98939affcf Update jQuery to 1.11.3. 2015-06-04 05:48:14 -04:00
Matthew Ross
e5b04740e0 Fixes #156. Options now save, and are used for displaying 'Assigned To'. 2015-06-03 18:35:42 -04:00
Matthew Ross
adb3e4f30f Fix bug with unassigned tasks and emails. 2015-06-02 20:13:59 -04:00
Matthew Ross
14a50c4a44 Link new options to UI. 2015-05-27 20:42:54 -04:00
Matthew Ross
25a213efd2 Fix typo. 2015-05-27 18:07:16 -04:00
Matthew Ross
47354ece5c Update SLoC counts. 2015-05-27 18:05:53 -04:00
Matthew Ross
5a0df02287 User options now sent from API correctly. 2015-05-27 17:59:30 -04:00
Matthew Ross
4aa39e6056 Update README.md 2015-05-27 16:31:53 -04:00
Matthew Ross
c79c6d989d Merge branch 'dev' of https://github.com/kiswa/TaskBoard into dev 2015-05-27 16:21:41 -04:00
Matthew Ross
683e271f17 Pre-merge work on additional settings. 2015-05-27 16:21:39 -04:00
kiswa
e09e8e5955 Update composer.phar 2015-05-27 15:21:44 -04:00
kiswa
db649d46b1 Merge branch 'woodworker-feature-composer' into dev 2015-05-27 15:13:30 -04:00
kiswa
29f93c0d37 Merge branch 'feature-composer' of https://github.com/woodworker/TaskBoard into woodworker-feature-composer 2015-05-27 15:12:35 -04:00
kiswa
2140e0afac Add CONTRIBUTING.md. Fixes #170 2015-04-28 18:20:21 -04:00
kiswa
b7547c87de Rename readme.md to README.md 2015-04-28 18:15:53 -04:00
Matthew Ross
9fa66ca80c Merge pull request #175 from ZM-git/patch-1
Update boardRoutes.php
2015-04-27 10:03:58 -04:00
ZM-git
1085f26835 Update boardRoutes.php 2015-04-26 22:50:26 +02:00
Martin Holzhauer
927d019f04 Merge remote-tracking branch 'upstream/dev' into feature-composer
Conflicts:
	readme.md
2015-04-22 11:42:28 +02:00
Martin Holzhauer
de77fe4b09 added composer to readme for dev instance 2015-04-21 22:24:40 +02:00
Martin Holzhauer
1ec6e10899 removed autoupdate of composer in build-all 2015-04-21 22:19:50 +02:00
Martin Holzhauer
53a2c0620d fixed build error in settingsBoard.js 2015-04-21 22:19:11 +02:00
Martin Holzhauer
0c184c003c added composer and removed old dependencies 2015-04-21 22:14:56 +02:00
Martin Holzhauer
ee99b705ae added a simple routing script for the php internal webserver to simplify the setup of a development environment 2015-04-21 21:29:36 +02:00
Matthew Ross
55eae2c2bf Merge pull request #168 from woodworker/feature-devrouter
added php internal webserver routing script
2015-04-21 13:26:43 -04:00
Martin Holzhauer
0c4980bf50 added a simple routing script for the php internal webserver to simplify the setup of a development environment 2015-04-21 18:56:01 +02:00
kiswa
629c8ed4b7 Update VERSION to match v0.3.0 changes. 2015-04-08 07:57:48 -04:00
kiswa
44561586b7 Update Slim to 2.6.2 (shows 2.6.1 in code) 2015-04-08 07:52:45 -04:00
kiswa
2f605d9805 Update AngularJS to 1.3.15 2015-04-08 07:48:31 -04:00
kiswa
1e49144037 Update RedBeanPHP to 4.2 2015-04-07 20:03:43 -04:00
kiswa
83dd97d759 Merge 2015-03-27 18:52:48 -04:00
kiswa
8ae5970005 CSS additions 2015-03-27 18:44:52 -04:00
kiswa
833a896914 Remove inline styles (two necessary ones remain). Fixes #151 2015-03-27 18:43:57 -04:00
Matthew Ross
5c82918f4c Merge pull request #150 from iamthemuffinman/dev
Leave space between inputs and buttons
2015-03-26 14:07:42 -04:00
Robert Deusser
6d3f5e4166 Leave space between inputs and buttons 2015-03-26 13:16:18 -04:00
kiswa
81c4ba17f4 Fix bad method name. 2015-03-23 18:23:17 -04:00
kiswa
64854f113f Add email to list of VERSION changes. 2015-03-23 18:16:54 -04:00
kiswa
823504b3c9 Add hostname to email templates and update readme. 2015-03-23 18:12:38 -04:00
kiswa
8b8c4fe617 Update display with new email address setting. 2015-03-23 17:39:12 -04:00
kiswa
a6cb65ccc7 Hide in-work settings display. 2015-03-23 17:38:54 -04:00
kiswa
7c8dc71061 Update .gitignore to actually ignore directory. Remove added file. 2015-03-23 17:36:55 -04:00
kiswa
e965636d7d Merge branch 'dev' of https://github.com/kiswa/TaskBoard into dev 2015-03-23 17:25:44 -04:00
kiswa
bcaba18063 Initial work for new settings. 2015-03-23 17:25:24 -04:00
Matthew Ross
17600e4c7c Merge pull request #146 from Shattenjagger/dev
Implementation of email feature with simple templating

Pending Local Testing - Consider dev unstable until I commit the readme.
2015-03-23 17:19:03 -04:00
Danila Balabaev
f8493f80a3 Minor change 2015-03-23 22:43:24 +03:00
Danila Balabaev
cdec8dddc3 Merge branch 'email_feature' into dev 2015-03-23 20:24:06 +03:00
Danila Balabaev
ec283cb841 Email feature 2015-03-23 20:22:54 +03:00
Danila Balabaev
8d2e8af586 PHPMailer lib 2015-03-23 12:50:44 +03:00
Danila Balabaev
5e87422dd4 GitIgnore modify 2015-03-23 11:57:54 +03:00
kiswa
2ef7e85093 Update default board selector to show only active boards. 2015-03-22 13:15:58 -04:00
kiswa
6a8558d037 Add option to show boards with specific user. Fixes #94. 2015-03-22 13:03:16 -04:00
kiswa
454921c984 Update board active status behaviors.
Added text to Settings page checkboxes. Updated boards controller to
only show active boards in lists. New CSS class for settings page text.
2015-03-22 12:27:49 -04:00
kiswa
730c266bac Fix date for last update of SLoC. 2015-03-20 15:09:22 -04:00
kiswa
aad9580259 Update counts in readme. 2015-03-20 15:07:52 -04:00
kiswa
83029ea24f Update VERSION to list all scheduled changes. Fix sanitize API call. 2015-03-20 15:02:16 -04:00
kiswa
96fe66fea4 Fix #68. Now possible to be logged in from many browsers. 2015-03-20 14:26:53 -04:00
Matthew Ross
c346026ee4 Update readme.md 2015-02-26 14:33:17 -05:00
kiswa
c2f35ad3d8 Update LOC counts. 2015-02-20 13:02:12 -05:00
kiswa
580924e4ca Fix automatic actions category bug issue #130. 2015-02-11 20:12:47 -05:00
kiswa
4d2b213a4d Add additional item to changelog. 2015-02-03 09:26:52 -05:00
kiswa
f261b06c55 Fix for Authorization header on some platforms. See #102 comments. 2015-02-03 08:57:29 -05:00
kiswa
d9e98ec560 Update AngularJS to v1.3.11. 2015-02-02 19:10:31 -05:00
Matthew Ross
933f462284 Merge pull request #121 from kiswa/master
Merge master into dev (one-time fix)
2015-01-29 11:13:26 -05:00
kiswa
ebc9815561 Added active status to Settings board list with filters. Fixes #95. 2015-01-22 17:39:45 -05:00
kiswa
a354ea0909 Add cursor style to activity log close button. 2015-01-22 15:13:26 -05:00
kiswa
91ff7f2418 Minor tweak. 2015-01-22 15:06:35 -05:00
kiswa
254947bb37 Add trace to exception handler. 2015-01-22 14:58:11 -05:00
kiswa
3a79881f86 Revert commits to master. 2015-01-22 14:35:46 -05:00
kiswa
b60fb2748b Add stack trace to exception handler. 2015-01-22 14:28:34 -05:00
kiswa
13b163a0c9 Update RedbeanPHP and Slim Framework. 2015-01-22 13:37:12 -05:00
kiswa
4bf94b55cf Update RedBeanPHP to 4.1.4. 2015-01-12 13:30:18 -05:00
kiswa
ea8baa3d95 Update Slim Framework to Version 2.5.0. 2015-01-12 10:01:49 -05:00
kiswa
c1440198b0 Remove unused code. 2015-01-04 17:22:12 -05:00
kiswa
61e0a7e1db Added ability to sort boards list. Fixes #110. 2015-01-04 17:17:33 -05:00
kiswa
bc6d9417c5 Update to jQuery 1.11.2. 2015-01-04 09:16:03 -05:00
kiswa
c37b721eca Update noty to v2.3.4. 2015-01-04 09:07:19 -05:00
kiswa
7d2c44170e Update to AngularJS 1.2.28. 2015-01-04 08:47:36 -05:00
kiswa
5a5ac92d67 Move to Column context menu styles. Fixes #115. 2014-12-11 18:55:53 -05:00
kiswa
d2501c0a0b Update Bootstrap for missing items. 2014-12-11 18:54:02 -05:00
kiswa
55a59ba798 Update libs. 2014-12-10 19:02:44 -05:00
kiswa
b4fb68dd5e Update .gitignore for tags. 2014-12-09 06:12:48 -05:00
kiswa
b4dc6bbf85 Update VERSION. 2014-12-09 06:01:56 -05:00
kiswa
3ffc0cd84e Textarea resize fixes. Fixes #63. 2014-12-07 14:35:26 -05:00
kiswa
636556a00a Update version for dev branch. 2014-12-07 12:03:46 -05:00
kiswa
0faa69f4c9 Merge branch 'dev' of https://github.com/kiswa/TaskBoard into dev 2014-12-07 10:11:29 -05:00
kiswa
85f8488d44 Updates to board editing. Fixes #114. 2014-12-07 10:10:56 -05:00
Matthew Ross
dd0597e18d Merge pull request #113 from kiswa/master
Merge master into demo
2014-12-05 08:59:58 -05:00
Matthew Ross
2399f2628a Merge pull request #112 from kiswa/dev
Merge dev into master
2014-12-05 08:44:49 -05:00
kiswa
23e43328e1 Fix #109. Editing columns and categories with long names shows editor. 2014-12-05 08:41:21 -05:00
kiswa
ad31872b40 Cleanup CSS for file viewer. 2014-12-01 19:28:33 -05:00
kiswa
ca6dac94af Rework automatic actions. Fixes #106. 2014-12-01 19:12:47 -05:00
Matthew Ross
d297b1b0bd Update VERSION 2014-12-01 08:13:25 -05:00
kiswa
c476f3c15a Removed unused code 2014-11-25 13:57:08 -05:00
kiswa
3e91125d90 Items can be moved to collapsed columns with new context menu item. 2014-11-25 13:54:09 -05:00
Matthew Ross
31e5d94619 Merge pull request #100 from kiswa/master
Merge master into dev
2014-11-17 20:30:04 -05:00
Matthew Ross
fdf4dfc164 Merge pull request #99 from mlasasa/master
Add note about apache AllowOverride setting
2014-11-17 20:28:27 -05:00
mlasasa
4ec7ff5755 Update readme.md 2014-11-17 23:54:37 +01:00
mlasasa
2534ae26f8 Update readme.md 2014-11-17 21:59:04 +01:00
kiswa
07fd61bb7b Modify width of colorpicker in CSS to show 9 colors in each palette row 2014-11-17 13:53:27 -05:00
kiswa
0e02693854 Add default color palette for items. Fixes #97. 2014-11-15 06:47:33 -05:00
kiswa
2252e34ccb Attachments now display username of uploader. Fixes #72. 2014-11-14 08:19:52 -05:00
kiswa
903db75ef7 Tweak Activity Log close button 2014-11-14 08:08:12 -05:00
kiswa
e497eda839 Update HTML formatting 2014-11-13 18:36:27 -05:00
kiswa
ca486921fd Changes to styles and template for File Viewer. Issue #73. 2014-11-12 21:52:18 -05:00
kiswa
e481f22796 Add close icon to Activity Log slide-out. Fixes #88. 2014-11-12 20:50:52 -05:00
kiswa
a671d586f2 Update dev to v0.2.7 2014-11-11 20:21:45 -05:00
Matt
d54bfb9b31 Merge pull request #91 from kiswa/master
Update app display version.
2014-11-11 15:05:31 -05:00
Matt
cac2a8c5dd Merge pull request #90 from kiswa/master
Update app display version.
2014-11-11 15:05:04 -05:00
Matt
4275bd3d31 Update app display version. 2014-11-11 14:57:53 -05:00
Matt
8c9375afca Merge pull request #86 from kiswa/master
Merge master into demo
2014-11-11 08:51:54 -05:00
Matt
fcce7c6496 Merge pull request #85 from kiswa/master
Merge version change from master
2014-11-10 21:34:05 -05:00
Matt
66ed4a8bce Update VERSION 2014-11-10 21:33:02 -05:00
kiswa
e54f488a0d Merge branch 'dev' 2014-11-10 21:30:35 -05:00
kiswa
523a33b83e Merge branch 'master' into dev
Conflicts:
	VERSION
2014-11-10 21:29:42 -05:00
kiswa
89bfe71fd9 Update changelog in VERSION 2014-11-10 21:25:50 -05:00
kiswa
d04db0d715 Update counts in readme 2014-11-10 21:24:02 -05:00
kiswa
09a771f2eb Add Apache requirements to readme. Fixes #82. 2014-11-10 20:12:43 -05:00
kiswa
1c67370fa0 Enable setting user boards on edit. 2014-11-10 20:03:13 -05:00
kiswa
cb9bbce2a9 Update VERSION 2014-11-02 09:33:14 -05:00
kiswa
e15b004a1e Comments are now editable. Resolves #33. Also fixed #31. 2014-11-02 09:29:30 -05:00
kiswa
1875c9682e Redirect to boards when opening login if token exists. Fixes #25. 2014-11-01 15:18:39 -04:00
Matt
ec2fb800cb Update readme.md 2014-10-31 16:51:06 -04:00
kiswa
98faaf34c0 Add users to boards during user creation. Resolves #54. 2014-10-31 16:43:32 -04:00
kiswa
2870109ad5 Reduce calls to update automatic actions display. 2014-10-31 16:42:08 -04:00
kiswa
92b20a6461 Add checkbox to hide items when filtered. Fixes #47. 2014-10-29 06:38:21 -04:00
Matt
b720f53a8c Update VERSION 2014-10-28 08:30:44 -04:00
Matt
01cf708f99 Rollback VERSION update - wrong branch again. 2014-10-28 08:30:30 -04:00
Matt
c3f46dc66b Update VERSION 2014-10-28 08:28:32 -04:00
Matt
21c91b55e7 Merge pull request #67 from kiswa/master
Merge master into demo
2014-10-27 21:29:06 -04:00
Matt
8ea769202d Merge pull request #66 from kiswa/dev
Merge dev into master
2014-10-27 21:25:12 -04:00
kiswa
a6d4c549cd Added ability to use Markdown in comments. Fixes #32. 2014-10-27 21:23:06 -04:00
kiswa
9b09747b05 Finished #36. Modal now autofocus first input. 2014-10-27 05:50:48 -04:00
kiswa
31aaa0581f Partial work on #36. Created directive and works on Settings page. 2014-10-26 21:42:49 -04:00
kiswa
487985cafc Update VERSION 2014-10-26 08:53:45 -04:00
kiswa
b3c0ba8a7d Display version number. Fixes #60. 2014-10-26 08:44:46 -04:00
Matt
92cd3d0f34 Merge pull request #59 from kiswa/dev
Merge dev into master
2014-10-25 17:24:05 -04:00
kiswa
86595373e4 Make title match dev setup when built for prod. 2014-10-25 17:21:27 -04:00
Matt
85693f5278 Merge pull request #57 from kiswa/master
Merge master into demo
2014-10-25 16:38:13 -04:00
Matt
a41addb05b Merge pull request #30 from kiswa/master
Merge master into demo
2014-10-21 08:38:44 -04:00
Matt
60145e9d95 Delete 3b897e367379368d5aa6a7a573314398d9aa4d39 2014-10-20 22:34:19 -04:00
Matt
fa75651af4 Delete taskboard.db.old 2014-10-20 22:34:11 -04:00
Matt
9b5c191a3b Merge pull request #21 from kiswa/master
Fix bug introduced in automatic actions.
2014-10-20 22:28:33 -04:00
kiswa
d59803fdf7 New backup database copy. 2014-10-20 22:16:45 -04:00
kiswa
aa6c098bba Fix demo login. 2014-10-20 21:41:49 -04:00
kiswa
bb86a7a01c Add upload to demo. 2014-10-20 21:15:28 -04:00
kiswa
f634980ba7 Fix login error in demo. 2014-10-20 20:38:11 -04:00
kiswa
345c53a301 Create reset database. 2014-10-20 20:05:08 -04:00
kiswa
39dc7282ac Tweaks for demo. 2014-10-20 20:02:58 -04:00
382 changed files with 71934 additions and 50392 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.git
.cache
.github
.vscode
node_modules
Dockerfile
docker-compose.yml

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

16
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,16 @@
## Contributing
### Reporting Issues
Fill out the template as completely as possible.
The more information you can provide, the easier it is to research your issue.
### Development
Fork the repository and make your changes on the `dev` branch.
Create a pull request against the `dev` branch to merge your changes with
the main repository.
Make sure to include/update unit tests.

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: kiswa
custom: "https://paypal.me/kiswacom"

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

@ -0,0 +1,31 @@
---
name: Bug report
about: Found a bug with TaskBoard?
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Please complete the following information:**
- OS: [e.g. Windows 10]
- Browser [e.g. Chrome, Firefox]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

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

BIN
.github/boards.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

BIN
.github/settings.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 KiB

BIN
.github/tasks.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

54
.gitignore vendored
View File

@ -1,2 +1,52 @@
api/taskboard.db
api/uploads/*
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
.phpunit.result.cache
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db
# Project Specific Files
/src/api/vendor
*.db
*.sqlite
*.zip
/src/config.php

View File

@ -1,15 +0,0 @@
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType text/css "access 1 year"
ExpiresByType text/x-javascript "access 9 months"
ExpiresByType image/x-icon "access 1 year"
ExpiresDefault "access 2 seconds"
</IfModule>

26
.travis.yml Normal file
View File

@ -0,0 +1,26 @@
language: php
php:
- '7.3'
- '7.4'
script:
- npm test
before_script:
- nvm install 13
- nvm use 13
- npm i -g npm@6
- npm i
- touch tests.db
- chmod a+w tests.db
- cd src/api/ && composer update && cd ../../
if: fork = false
after_success:
- echo -e "<?php\n print phpversion();" > version.php
- curl "https://raw.githubusercontent.com/andreafabrizi/Dropbox-Uploader/master/dropbox_uploader.sh" -o dropbox_uploader.sh
- chmod +x dropbox_uploader.sh
- touch ~/.dropbox_uploader
- echo "CONFIGFILE_VERSION=2.0" > ~/.dropbox_uploader
- echo "OAUTH_APP_KEY=$OAUTH_APP_KEY" >> ~/.dropbox_uploader
- echo "OAUTH_APP_SECRET=$OAUTH_APP_SECRET" >> ~/.dropbox_uploader
- echo "OAUTH_REFRESH_TOKEN=$OAUTH_REFRESH_TOKEN" >> ~/.dropbox_uploader
- ./dropbox_uploader.sh upload coverage/api coverage-$(php version.php)/
- ./dropbox_uploader.sh upload coverage/app/lcov-report coverage-$(php version.php)/

19
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9000,
"log": true,
"externalConsole": false,
"pathMappings": {
"/var/www/html/api": "${workspaceRoot}/src/api"
},
"ignore": [
"**/vendor/**/*.php"
]
}
]
}

47
CHANGELOG.md Normal file
View File

@ -0,0 +1,47 @@
## v1.0.2
### New Features
* German translation - Thanks to @ben-so
### Updates
* N/A
### Bug Fixes
* New user creation defaults Security Level to User
* Drag-and-drop automatic actions correctly update UI
* Special characters in emails now handled correctly - Contributed by @ben-so
* Changing card order now updates UI correctly
* Token refresh no longer causes user to be logged out
* Minor fixes for Spanish translations
## v1.0.1
### New Features
* N/A
### Updates
* Made base href dynamic, so there is no need to alter it after 'install'
### Bug Fixes
* N/A
## v1.0.0
### New Features
* Complete re-write
* Conversion script for migrating to new database format (sqlite only)
### Updates
* N/A
### Bug Fixes
* N/A

49
Dockerfile Normal file
View File

@ -0,0 +1,49 @@
# -------------------
# Build Stage 1 (npm)
# -------------------
FROM node:alpine AS appbuild
RUN apk add --update --no-cache p7zip
WORKDIR /usr/src/app
COPY ./package.json ./
RUN npm install
COPY . ./
RUN npm run build:prod
# RUN npm run build
# ------------------------
# Build Stage 2 (composer)
# ------------------------
FROM composer AS apibuild
WORKDIR /app
COPY ./src/api ./
RUN composer install
# --------------------------
# Build Stage 3 (php-apache)
# This build takes the production build from staging builds
# --------------------------
FROM php:7.3-apache
ENV PROJECT /var/www/html
RUN apt-get update && apt-get install -y sqlite3 php7.3-sqlite
RUN a2enmod rewrite expires
# RUN docker-php-ext-install pdo_mysql
# RUN pecl install xdebug && docker-php-ext-enable xdebug
# COPY xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
WORKDIR $PROJECT
COPY --from=appbuild /usr/src/app/dist ./
RUN rm -rf ./api/*
COPY --from=apibuild /app ./api/
RUN chmod 777 ./api
EXPOSE 80

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Matthew Ross
Copyright (c) 2014-2020 Matthew Ross
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

302
README.md Normal file
View File

@ -0,0 +1,302 @@
# REIVILIBRE'S PERSONAL FORK
This fork is being developed for stability and compatibility with Postgres.
# TaskBoard
[![Build Status](https://travis-ci.org/kiswa/TaskBoard.svg)](https://travis-ci.org/kiswa/TaskBoard) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/77952f4ac9b44e9fbebe7758442d356d)](https://www.codacy.com/app/kiswa-com/TaskBoard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=kiswa/TaskBoard&amp;utm_campaign=Badge_Grade) [![Join the chat at https://gitter.im/kiswa/TaskBoard](https://badges.gitter.im/kiswa/TaskBoard.svg)](https://gitter.im/kiswa/TaskBoard?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the discussion at https://reddit.com/r/TaskBoard](https://cdn.rawgit.com/kiswa/TaskBoard/09444718053f7405636ab2205ad0f12413df7a20/reddit.svg)](https://reddit.com/r/TaskBoard)
A [Kanban](http://en.wikipedia.org/wiki/Kanban_board)-inspired app for keeping
track of things that need to get done.
The goal of TaskBoard is to provide a simple and clean interface to a
functional and minimal application for keeping track of tasks.
**It's not trying to be the next Trello or LeanKit.**
## Installation
### Prerequisites
A web server running PHP 7.x with sqlite enabled (it may work on PHP 5.6, but
is not supported). See [PHP Supported Versions](https://www.php.net/supported-versions.php).
The server must have `sqlite3` and `php7-sqlite` installed.
**- OR -**
If you're comfortable changing code, you can use any database [supported by RedBeanPHP](https://redbeanphp.com/index.php?p=/connection).
#### Using PHP-FPM
You are able to use PHP-FPM if you remove (or comment out) the php_value items in the api/.htaccess file, then set them in a `.user.ini` [See Documentation](https://www.php.net/manual/en/configuration.file.per-user.php)
### Install
Installing TaskBoard is as easy as 1, 2, 3!
1. Download [the latest release](https://github.com/kiswa/TaskBoard/releases) since v1.0.0
2. Extract it to your webserver
3. Verify the `api` directory is writable
If you intend to use email features, you will need to edit `api/helpers/mailer.php`.
### Server Config
#### Apache
The directory you create for TaskBoard must have `AllowOverride` set so the
`.htaccess` files work.
You also have to have `mod_rewrite` installed and enabled.
#### Nginx
Example Nginx configuration:
```
server {
server_name ...;
listen 443 ssl http2;
listen [::]:443 ssl http2;
client_max_body_size 100M;
client_body_buffer_size 128k;
# Note: You should disable gzip for SSL traffic.
# See: https://bugs.debian.org/773332
#
# Read up on ssl_ciphers to ensure a secure configuration.
# See: https://bugs.debian.org/765782
ssl_certificate /etc/nginx/tls/....pem;
ssl_certificate_key /etc/nginx/tls/....key;
include snippets/olivier-good-tls.conf;
add_header X-Frame-Options SAME-ORIGIN;
add_header X-Content-Type-Options nosniff;
root /var/www/taskboard/taskboard_{{ taskboard.version }};
index index.php index.html;
autoindex off;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
try_files $uri $uri/ @rewriteApi;
}
location @rewriteApi {
rewrite ^(.*)$ /api/index.php last;
break;
}
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_param HTTP_PROXY "";
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_index index.php;
include snippets/fastcgi-php.conf;
}
location ~ /\.ht {
# deny htaccess
deny all;
}
}
server {
server_name ...;
listen 80;
listen [::]:80;
# permanent redirect preserving method (POSTs not converted to GETs)
return 301 https://$server_name$request_uri;
}
```
You might need to replace `php-fpm.sock` with a different path for your PHP version.
#### IIS
See the [Wiki Page](https://github.com/kiswa/TaskBoard/wiki/TaskBoard-on-IIS)
### First Use
Open a web browser to the location you installed TaskBoard and use `admin` as
the username and password to log into TaskBoard.
Go to the `Settings` page to update your user (username, email, password,
*etc.*) and create your first board!
## Features
### Users & Settings
There are three types of users, and the settings page is slightly different
for each.
* User - View boards assigned to them and update their own settings and options.
* Board Admin - Same as above, with the ability to manage boards they are
added to.
* Admin - Same as above, with the ability to add/remove users and boards.
![Settings Page](./.github/settings.png)
### Boards
Each board may have as many columns as needed. For example, a software project
might have columns like:
* Proposed
* Backlog
* In Work
* Test
* Done
* Archived
Or maybe you want to track a simple todo list with just:
* Todo
* Doing
* Done
It's all up to you! However many columns you have, each column may have tasks
added to it, and tasks can be dragged to other columns as they progress
(or edited and assigned to a new column).
Boards may also have categories for additional organization, *e.g.* `Bug`,
`Enhancement`, `New Feature`.
![Boards Page](./.github/boards.png)
### Tasks
A task only has to have a Title to be added to a board, but there is much more
available. Tasks may be assigned to any user on the board (or left Unassigned),
and include options for Due Date, Color, Points (an optional difficulty
rating), and Category.
TaskBoard uses a [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#table-of-contents)
parser for the Description, allowing for better display of details
(like this readme).
Once a task has been entered, it may have Comments (also supporting Markdown)
or Attachments added to it by viewing the task detail. There is a link to edit
the task, which takes you to a modal much like the original add task dialog.
For admin users, there is also a link to delete the task. This view also shows
the task's activity log on the side of the screen, displaying the complete
history of events related to the task.
![Tasks](./.github/tasks.png)
## Development
Developing on TaskBoard is pretty simple too.
1. Clone the repository and navigate to it `git clone https://github.com/kiswa/TaskBoard && cd TaskBoard/`
2. Run `git checkout dev` to work on the `dev` branch
3. If you don't have it already, install the Angular CLI globally with `npm i -g @angular/cli`
4. Run `npm i` to install dependencies (this also installs the API dependencies)
5. Run `npm run watch` for the build to automatically run after any change
### Building
Run `npm run build` to build. The `dist` directory will be created with the build.
### Unit Tests
Both the API and App are unit tested. To run all tests, use the command
`npm run test`. For only one set, run `npm run test:api` or `npm run test:app`.
To have the app tests run & update as you work, use the command
`npm run test:watch`.
If you want to run a single API test, add the following comment block before
the test function and use the command `npm run test:api-single`.
``` php
/**
* @group single
*/
```
If you want to run a single App test, change the test from
`it('should do something', ...);` to `fit('should do something', ...);` and
only that test will run.
These tests are run by [Travis CI](https://travis-ci.org/) on PRs and commits.
A PR with failing or missing tests will not be merged.
## Contributing
Fork the repository and make your changes on the `dev` branch.
Create a pull request against the `dev` branch to merge your changes with the
main repository.
Make sure to include/update unit tests.
## Feedback
Constructive feedback is appreciated! If you have ideas for improvement, please
[add an issue](https://github.com/kiswa/TaskBoard/issues) or implement it and
submit a pull request.
If you find a bug, please post it on the [Issue Tracker](https://github.com/kiswa/TaskBoard/issues).
## How It's Made
### Front End
* [Angular](https://angular.io/) single-page app (not AngularJS)
* [Bourbon](http://bourbon.io/) and [Neat](http://neat.bourbon.io/) SCSS
library & grid
* [scss-base](https://www.npmjs.com/package/scss-base) for the base styling
* [marked](https://github.com/chjj/marked) Markdown parser
* [Chartist.js](https://gionkunz.github.io/chartist-js/) for all charts
### Back End
* [Slim Framework](http://www.slimframework.com/) and
[RedBeanPHP](http://www.redbeanphp.com/) for a RESTful API
* [PHPMailer](https://github.com/PHPMailer/PHPMailer) for sending emails
* [JWT](https://jwt.io/) authentication
* [SQLite](https://www.sqlite.org/) database
## Lines of Code
Because I like seeing the numbers.
### `src`
Language | Files | Blank | Comment | Code
-----------|--------:|---------:|---------:|---------:
TypeScript | 67 | 977 | 129 | 4103
PHP | 20 | 744 | 37 | 2243
HTML | 21 | 268 | 2 | 1572
SASS | 14 | 299 | 10 | 1347
**SUM:** | **122** | **2288** | **178** | **9265**
Command: `cloc --exclude-dir=vendor,favicons --exclude-ext=json,svg,ini src/`
### `test`
Language | Files | Blank | Comment | Code
-----------|-------:|---------:|---------:|---------:
TypeScript | 38 | 1017 | 8 | 3540
PHP | 11 | 784 | 19 | 2272
**SUM:** | **49** | **1801** | **27** | **5812**
Command: `cloc --exclude-ext=xml test/`

10
VERSION
View File

@ -1,10 +0,0 @@
v0.2.4
Changelog
* Bugfix for attachments downloading with hash instead of file name (#41).
* Bugfix for item positions (#37).
* noty errors are ignored now.
* Favicons added.
* API Error now returns exception message in JSON data.

146
angular.json Normal file
View File

@ -0,0 +1,146 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"TaskBoard": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"aot": true,
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"showCircularDependencies": false,
"polyfills": "src/polyfills.ts",
"assets": [
{
"glob": "**/!(composer*)*.*",
"input": "src/api",
"output": "/api"
},
{
"glob": "config*.php",
"input": "src",
"output": "/"
},
{
"glob": "*.json",
"input": "src/json",
"output": "/strings"
},
{
"glob": "*.*",
"input": "src/favicons",
"output": "favicons"
},
"src/.htaccess",
"src/images",
"src/fonts"
],
"styles": [
"src/scss/main.scss"
],
"scripts": []
},
"configurations": {
"production": {
"aot": true,
"optimization": true,
"outputHashing": "all",
"sourceMap": true,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "TaskBoard:build"
},
"configurations": {
"production": {
"browserTarget": "TaskBoard:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "TaskBoard:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"scripts": [],
"styles": [
"src/scss/main.scss"
],
"assets": [
{
"glob": "**/!(composer*)*.*",
"input": "src/api",
"output": "/api"
},
{
"glob": "*.json",
"input": "src/json",
"output": "/strings"
},
"src/.htaccess",
"src/images",
"src/fonts"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "TaskBoard",
"cli": {
"warnings": {
"typescriptMismatch": false
}
},
"schematics": {
"@schematics/angular:component": {
"prefix": "tb",
"styleext": "scss"
},
"@schematics/angular:directive": {
"prefix": "tb"
}
}
}

View File

@ -1,10 +0,0 @@
ExpiresActive Off
RewriteEngine On
RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
RewriteRule ^(.*)$ - [E=BASE:%1]
RewriteRule ^taskboard.db$ %{ENV:BASE}api.php [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ %{ENV:BASE}api.php [QSA,L]

View File

@ -1,58 +0,0 @@
<?php
require_once('lib/Slim/Slim.php');
require_once('lib/rb.php');
require_once('lib/password.php');
require_once('lib/JWT.php');
require_once('jsonResponse.php');
use Slim\Slim;
Slim::registerAutoloader();
$app = new Slim();
$app->response->headers->set('Content-Type', 'application/json');
$jsonResponse = new JsonResponse();
require_once('helpers.php'); // Must come after $jsonResponse exists.
// Catch Exception if connection to DB failed
function exceptionHandler($exception) {
global $jsonResponse;
header('Content-Type: application/json');
http_response_code(503);
$jsonResponse->message = 'API Error.';
$jsonResponse->data = $exception->getMessage();
echo $jsonResponse->asJson();
};
set_exception_handler('exceptionHandler');
R::setup('sqlite:taskboard.db');
createInitialUser();
$app->notFound(function() use ($app, $jsonResponse) {
$app->response->setStatus(404);
$jsonResponse->message = 'Matching API call Not found.';
$app->response->setBody($jsonResponse->asJson());
});
// TODO: Figure out updating token on activity.
$app->get('/authenticate', function() use($app, $jsonResponse) {
if (validateToken()) {
$jsonResponse->message = 'Token is authenticated.';
// $user = getUser();
// setUserToken($user, (0.5 * 60 * 60) /* Half an hour */);
// R::store($user);
// $jsonResponse->data = $user->token;
}
$app->response->setBody($jsonResponse->asJson());
});
require_once('userRoutes.php');
require_once('boardRoutes.php');
require_once('itemRoutes.php');
$app->run();
R::close();

View File

@ -1,138 +0,0 @@
<?php
// Get the list of active boards
$app->get('/boards', function() use($app, $jsonResponse) {
if (validateToken()) {
$jsonResponse->addBeans(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});
// Create new board.
$app->post('/boards', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$board = R::dispense('board');
loadBoardData($board, $data);
$actor = getUser();
logAction($actor->username . ' added board ' . $board->name, null, $board->export());
$jsonResponse->addBeans(getBoards());
$jsonResponse->addAlert('success', 'New board ' . $board->name . ' created.');
}
$app->response->setBody($jsonResponse->asJson());
});
// Update existing board.
$app->post('/boards/update', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$board = R::load('board', $data->boardId);
if ($board->id) {
$before = $board->export();
loadBoardData($board, $data);
$jsonResponse->addAlert('success', 'Board ' . $board->name . ' edited.');
$actor = getUser();
logAction($actor->username . ' updated board ' . $board->name, $before, $board->export());
}
$jsonResponse->addBeans(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});
// Remove a board.
$app->post('/boards/remove', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$board = R::load('board', $data->boardId);
if ($board->id == $data->boardId) {
$before = $board->export();
foreach($board->sharedUser as $user) {
if ($user->defaultBoard == $data->boardId) {
$user->defaultBoard = null;
R::store($user);
}
}
R::trashAll($board->xownLane);
R::trashAll($board->xownCategory);
R::trash($board);
R::exec('DELETE from board_user WHERE board_id = ?', [$data->boardId]);
$jsonResponse->addAlert('success', 'Removed board ' . $board->name . '.');
$actor = getUser();
logAction($actor->username . ' removed board ' . $board->name, $before, null);
}
$jsonResponse->addBeans(getBoards());
$jsonResponse->users = R::exportAll(getUsers());
}
$app->response->setBody($jsonResponse->asJson());
});
$app->post('/autoactions', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$board = R::load('board', $data->boardId);
if ($board->id) {
$autoAction = R::dispense('autoaction');
$autoAction->triggerId = $data->triggerId;
$autoAction->secondaryId = $data->secondaryId;
$autoAction->actionId = $data->actionId;
$autoAction->color = $data->color;
$autoAction->categoryId = $data->categoryId;
$autoAction->assigneeId = $data->assigneeId;
}
$board->ownAutoaction[] = $autoAction;
R::store($board);
$jsonResponse->addAlert('success', 'Automatic action created.');
$actions = R::findAll('autoaction');
$jsonResponse->addBeans($actions);
}
$app->response->setBody($jsonResponse->asJson());
});
$app->get('/autoactions', function() use($app, $jsonResponse) {
if (validateToken()) {
$actions = R::findAll('autoaction');
$jsonResponse->addBeans($actions);
}
$app->response->setBody($jsonResponse->asJson());
});
$app->post('/autoactions/remove', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$autoAction = R::load('autoaction', $data->actionId);
R::trash($autoAction);
$actions = R::findAll('autoaction');
$jsonResponse->addBeans($actions);
$jsonResponse->addAlert('success', 'Automatic action removed.');
}
$app->response->setBody($jsonResponse->asJson());
});
// Toggle the expand/collapse state of a lane for the current user.
$app->post('/lanes/:laneId/toggle', function($laneId) use($app, $jsonResponse) {
if (validateToken()) {
$user = getUser();
$lane = R::load('lane', $laneId);
$collapsed = R::findOne('collapsed', ' user_id = ? AND lane_id = ? ', [$user->id, $laneId]);
if (null != $collapsed) {
R::trash($collapsed);
$jsonResponse->message = 'Expanded lane ' . $lane->name;
} else {
$collapsed = R::dispense('collapsed');
$collapsed->userId = $user->id;
$collapsed->laneId = $laneId;
R::store($collapsed);
$jsonResponse->message = 'Collapsed lane ' . $lane->name;
}
$jsonResponse->addBeans(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['laneId' => '\d+']); // Numbers only.

View File

@ -1,381 +0,0 @@
<?php
// Patch for when using nginx instead of apache, source: http://php.net/manual/en/function.getallheaders.php#84262
if (!function_exists('getallheaders')) {
function getallheaders() {
$headers = '';
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') {
$headers[str_replace(' ', '-', ucwords(strtolower(
str_replace('_', ' ', substr($name, 5))
)))] = $value;
}
}
return $headers;
}
}
// Log an action. If $itemId is set, it is an item action.
function logAction($comment, $oldValue, $newValue, $itemId=null) {
$activity = R::dispense('activity');
$activity->comment = $comment;
$activity->oldValue = json_encode($oldValue);
$activity->newValue = json_encode($newValue);
$activity->timestamp = time();
$activity->itemId = $itemId;
R::store($activity);
}
// Sets the JWT for the current user and stores in DB for lookup.
function setUserToken($user, $expires) {
$token = JWT::encode(array(
'exp' => time() + $expires,
'uid' => $user->id
), getJwtKey());
// Store the valid token in the user db
$user->token = $token;
R::store($user);
}
// Get the user making the current request.
function getUser() {
global $jsonResponse;
if (isset(getallheaders()['Authorization'])) {
$hash = getallheaders()['Authorization'];
try {
$payload = JWT::decode($hash, getJwtKey());
$user = R::load('user', $payload->uid);
if ($user->id) {
return $user;
}
} catch (Exception $e) {}
}
$jsonResponse->addAlert('error', 'Unable to load user. Please try again.');
return null;
}
// Get all users.
function getUsers($sanitize = true) {
try {
$hash = getallheaders()['Authorization'];
$payload = JWT::decode($hash, getJwtKey());
$users = R::findAll('user');
if ($sanitize) {
foreach($users as &$user) {
sanitize($user);
}
}
return $users;
} catch (Exception $e) {}
$jsonResponse->addAlert('error', 'Unable to load users. Please try again.');
return null;
}
// Add a user to a board.
function addUserToBoard($boardId, $user) {
if ($user->isAdmin) {
$boards = R::findAll('board'); // DO NOT use getBoards here - it sanitizes the users which causes problems.
foreach($boards as $board) {
$board->sharedUser[] = $user;
R::store($board);
}
} else {
$board = R::load('board', $boardId);
if ($board->id) {
$board->sharedUser[] = $user;
R::store($board);
}
}
}
// Get all active boards.
function getBoards() {
$user = getUser();
$boards = R::find('board', ' active = 1 ');
foreach($boards as $board) {
foreach($board->sharedUser as $boardUser) {
sanitize($boardUser);
}
}
$collapsedLanes = R::find('collapsed', ' user_id = ' . $user->id);
foreach($boards as $board) {
foreach($board->xownLane as $lane) {
foreach($collapsedLanes as $collapsed) {
if ($lane->id == $collapsed->lane_id) {
$lane->collapsed = true;
}
}
}
}
if ($user->isAdmin) {
return $boards;
} else {
$filteredBoards = [];
foreach($boards as $board) {
foreach($board->sharedUser as $boardUser) {
if ($user->username == $boardUser->username) {
$filteredBoards[] = $board;
}
}
}
return $filteredBoards;
}
}
// Finds the removed IDs for updating a board.
function getIdsToRemove($boardList, $dataList) {
$retVal = [];
foreach($boardList as $item) {
$remove = true;
foreach($dataList as $newItem) {
if (intval($newItem->id) == $item->id) {
$remove = false;
}
}
if ($remove) {
$retVal[] = $item->id;
}
}
return $retVal;
}
// Load a board bean from provided data.
function loadBoardData($board, $data) {
$board->name = $data->name;
$board->active = true;
$removeIds = getIdsToRemove($board->xownLane, $data->lanes);
foreach($removeIds as $id) {
unset($board->xownLane[$id]);
}
// R::load works like R::dispense if the id is not found.
foreach($data->lanes as $item) {
$lane = R::load('lane', $item->id);
$lane->name = $item->name;
$lane->position = intval($item->position);
if (null == $lane->ownItems) {
$lane->ownItems = [];
}
// New lane, add it to the board
if (!$lane->id) {
$board->xownLane[] = $lane;
}
R::store($lane);
}
$removeIds = getIdsToRemove($board->xownCategory, $data->categories);
foreach($removeIds as $id) {
unset($board->xownCategory[$id]);
}
foreach($data->categories as $item) {
$category = R::load('category', $item->id);
$category->name = $item->name;
// New category, add it to the board.
if (!$category->id) {
$board->xownCategory[] = $category;
}
R::store($category);
}
// Add or remove users as selected.
for($i = 1; $i < count($data->users); $i++) {
$user = R::load('user', $i);
if ($data->users[$i] && $user->id && !in_array($user, $board->sharedUser)) {
$board->sharedUser[] = $user;
} else {
unset($board->sharedUser[$i]);
}
}
// Add all admin users.
foreach(getUsers(false) as $user) {
if ($user->isAdmin && !in_array($user, $board->sharedUser)) {
$board->sharedUser[] = $user;
}
}
R::store($board);
}
// Clean a user bean for return to front-end.
function sanitize($user) {
$user['salt'] = null;
$user['token'] = null;
$user['password'] = null;
}
// Change username if available.
function updateUsername($user, $data) {
global $jsonResponse;
$nameTaken = R::findOne('user', ' username = ?', [$data->newUsername]);
if (null != $user && null == $nameTaken) {
$user->username = $data->newUsername;
$jsonResponse->addAlert('success', 'Username updated.');
} else {
$jsonResponse->addAlert('error', 'Username already in use.');
}
return $user;
}
// Validate a provided JWT.
function validateToken($requireAdmin = false) {
global $jsonResponse, $app;
$retVal = false;
if (checkDbToken()) {
$retVal = true;
} else {
clearDbToken();
$jsonResponse->message = 'Invalid token.';
$app->response->setStatus(401);
}
if ($retVal && $requireAdmin) {
$user = getUser();
if (!$user->isAdmin) {
clearDbToken();
$jsonResponse->message = 'Insufficient user privileges.';
$app->response->setStatus(401);
}
}
return $retVal;
}
// Retrieve user's token from DB and compare to header token.
function checkDbToken() {
$user = getUser();
if (null != $user) {
if (isset(getallheaders()['Authorization'])) {
$hash = getallheaders()['Authorization'];
return $hash == $user->token;
}
}
return false;
}
// Clear a user's token from the DB.
function clearDbToken() {
$payload = null;
try {
$payload = JWT::decode(getallheaders()['Authorization'], getJwtKey());
} catch (Exception $e) {}
if (null != $payload) {
$user = R::load('user', $payload->uid);
if (0 != $user->id) {
$user->token = null;
R::store($user);
}
}
}
// Get the application's JWT key (created on first run).
function getJwtKey() {
$key = R::load('jwt', 1);
if (!$key->id) {
$key->token = password_hash(strval(time()), PASSWORD_BCRYPT);
R::store($key);
}
return $key->token;
}
// If there are no users, create the admin user.
function createInitialUser() {
if (!R::count('user')) {
$admin = R::dispense('user');
$admin->username = 'admin';
$admin->isAdmin = true;
$admin->logins = 0;
$admin->lastLogin = time(); //date('Y-m-d H:i:s');
$admin->defaultBoard = null;
$admin->salt = password_hash($admin->username . time(), PASSWORD_BCRYPT);
$admin->password = password_hash('admin', PASSWORD_BCRYPT, array('salt' => $admin->salt));
R::store($admin);
}
}
// Gets the position for a new item in a lane.
function getNextItemPosition($laneId) {
$retVal = 0;
$lane = R::load('lane', $laneId);
if ($lane->id) {
try {
$retVal = $lane->countOwn('item');
} catch (Exception $e) {
// Ignore, just means there are no items.
}
}
return $retVal;
}
function runAutoActions(&$item) {
$lane = R::load('lane', $item->laneId);
$board = R::load('board', $lane->boardId);
foreach($board->ownAutoaction as $action) {
switch($action->triggerId) {
case 0: // Item moves to lane
if ($item->laneId == $action->secondaryId) {
updateItemFromAction($item, $action);
}
break;
case 1: // Item assigned to user
if ($item->assignee == $action->secondaryId ||
($action->secondaryId == 0 && $item->assignee == null)) {
updateItemFromAction($item, $action);
}
break;
case 2: // Item assigned to category
if ($item->category == $action->secondaryId ||
($action->secondaryId == 0 && $item->category == null)) {
updateItemFromAction($item, $action);
}
break;
}
}
}
function updateItemFromAction(&$item, $action) {
switch($action->actionId) {
case 0: // Set item color
$item->color = $action->color;
break;
case 1: // Set item category
$item->category = $action->categoryId;
break;
case 2: // Set item assignee
$item->assignee = $action->assigneeId;
break;
case 3: // Clear item due date
$item->dueDate = null;
break;
}
R::store($item);
}

View File

@ -1,243 +0,0 @@
<?php
// Create new item
$app->post('/boards/:id/items', function($id) use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$board = R::load('board', $id);
if ($board->id) {
$item = R::dispense('item');
$item->title = $data->title;
$item->description = $data->description;
$item->assignee = $data->assignee;
$item->category = $data->category;
$item->color = $data->color;
$item->dueDate = $data->dueDate;
$item->points = $data->points;
$item->position = getNextItemPosition($data->lane);
$board->xownLane[$data->lane]->xownItem[] = $item;
R::store($board);
runAutoActions($item);
if ($item->id) {
$actor = getUser();
logAction($actor->username . ' added item ' . $item->title . ' to board ' . $board->name,
null, $item->export(), $item->id);
$jsonResponse->addAlert('success', 'New board item created.');
$jsonResponse->addBeans(getBoards());
} else {
$jsonResponse->addAlert('error', 'Failed to create board item.');
}
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['id' => '\d+']); // Numbers only.
//Update existing item
$app->post('/items/:itemId', function($itemId) use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$item = R::load('item', $itemId);
$before = $item->export();
if ($item->id) {
$item->title = $data->title;
$item->description = $data->description;
$item->assignee = $data->assignee;
$item->category = $data->category;
$item->color = $data->color;
$item->dueDate = $data->dueDate;
$item->points = $data->points;
if ($data->lane != $item->lane_id) {
$item->position = getNextItemPosition($data->lane);
$item->lane = R::load('lane', $data->lane);
} else {
$item->position = $data->position;
}
runAutoActions($item);
R::store($item);
logAction($user->username . ' updated item ' . $item->title, $before, $item->export(), $itemId);
$jsonResponse->addAlert('success', 'Updated item ' . $item->title . '.');
$jsonResponse->addBeans(getBoards());
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+']);
// Update item positions
$app->post('/items/positions', function() use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$movedItem = null;
$beforeItem = null;
$afterItem = null;
R::begin();
foreach($data->positions as $posItem) {
$item = R::load('item', $posItem->item);
$before = $item->export();
$oldLane = $item->lane->id;
$item->lane = R::load('lane', $posItem->lane);
if ($oldLane != $item->lane->id) {
$movedItem = $item;
$beforeItem = $before;
$afterItem = $item->export();
}
$item->position = $posItem->position;
runAutoActions($item);
R::store($item);
}
R::commit();
// If an item changed lanes, log the action.
if (null != $movedItem) {
logAction($user->username . ' moved item ' . $movedItem->title . ' to lane ' . $movedItem->lane->name,
$beforeItem, $afterItem, $movedItem->id);
}
$jsonResponse->addBeans(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});
// Add a comment to an item.
$app->post('/items/:itemId/comment', function($itemId) use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$item = R::load('item', $itemId);
if ($item->id) {
$comment = R::dispense('comment');
$comment->text = $data->text;
$comment->userId = $user->id;
$comment->timestamp = time();
$item->ownComment[] = $comment;
R::store($item);
logAction($user->username . ' added a comment to item ' . $item->title, null, $comment, $itemId);
$jsonResponse->addAlert('success', 'Comment added to item ' . $item->title . '.');
$jsonResponse->addBeans(R::load('item', $itemId));
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+']);
// Remove a comment from an item.
$app->post('/items/:itemId/comment/remove', function($itemId) use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$comment = R::load('comment', $data->id);
if ($comment->id) {
$before = $comment->export();
R::trash($comment);
$item = R::load('item', $itemId);
logAction($user->username . ' removed comment from item ' . $item->title . '.', $before, null, $item->id);
$jsonResponse->addAlert('success', 'Comment was deleted.');
$jsonResponse->addBeans(R::load('item', $itemId));
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+']);
// Add an attachment to an item.
$app->post('/items/:itemId/upload', function($itemId) use ($app, $jsonResponse) {
$upload = $_FILES['file'];
if (!file_exists('uploads/')) {
mkdir('uploads', 0777, true);
}
if (validateToken()) {
$file = R::dispense('attachment');
$item = R::load('item', $itemId);
$before = $item->export();
$user = getUser();
$file->filename = sha1($upload['tmp_name']);
$file->name = $upload['name'];
$file->type = $upload['type'];
$file->userId = $user->id;
$file->timestamp = time();
$item->ownAttachment[] = $file;
R::store($item);
move_uploaded_file($upload['tmp_name'], 'uploads/' . $file->filename);
logAction($user->username . ' uploaded attachment ' . $file->name . ' to item ' . $item->name,
$before, $item, $itemId);
$jsonResponse->addAlert('success', $file->name . ' was added.');
$jsonResponse->addBeans($item);
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+']);
// Get an item attachment's information.
$app->get('/items/:itemId/upload/:attachmentId', function($itemId, $attachmentId) use ($app, $jsonResponse) {
if (validateToken()) {
$file = R::load('attachment', $attachmentId);
if ($file->id) {
$file->username = 'unknown';
$user = R::load('user', $file->userId);
if ($user->id) {
$file->username = $user->username;
}
$jsonResponse->addBeans($file);
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+', 'attachmentId' => '\d+']);
// Remove an attachment from an item.
$app->post('/items/:itemId/upload/remove', function($itemId) use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$item = R::load('item', $itemId);
$before = $item->export();
$actor = getUser();
$file = R::load('attachment', $data->fileId);
if ($file->id) {
$filename = $file->name;
$before = $item->export();
unlink('uploads/' . $file->filename);
R::trash($file);
R::store($item);
logAction($actor->username . ' removed attachment ' . $filename . ' from item ' . $item->title,
$before, $item, $itemId);
$jsonResponse->addAlert('success', $filename . ' was deleted.');
$jsonResponse->addBeans($item);
}
}
$app->response->setBody($jsonResponse->asJson());
})->conditions(['itemId' => '\d+']);
// Remove an item.
$app->post('/items/remove', function() use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$item = R::load('item', $data->itemId);
if ($item->id) {
$before = $item->export();
R::trash($item);
$actor = getUser();
logAction($actor->username . ' removed item ' . $item->title, $before, null, $data->itemId);
$jsonResponse->addAlert('success', $item->title . ' was deleted.');
$jsonResponse->addBeans(getBoards());
}
}
$app->response->setBody($jsonResponse->asJson());
});

View File

@ -1,21 +0,0 @@
<?php
// Provides default structure and JSON encoded response for the API.
class JsonResponse {
var $message;
var $data;
var $alerts;
function asJson() {
return json_encode($this);
}
function addBeans($beans) {
if (null == $beans) return array();
$this->data = R::exportAll($beans);
}
function addAlert($type, $text) {
$this->alerts[] = ['type' => $type, 'text' => $text];
}
}

View File

@ -1,265 +0,0 @@
<?php
/**
* JSON Web Token implementation, based on this spec:
* http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Neuman Vong <neuman@twilio.com>
* @author Anant Narayanan <anant@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWT
{
static $methods = array(
'HS256' => array('hash_hmac', 'SHA256'),
'HS512' => array('hash_hmac', 'SHA512'),
'HS384' => array('hash_hmac', 'SHA384'),
'RS256' => array('openssl', 'SHA256'),
);
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param string|Array|null $key The secret key, or map of keys
* @param bool $verify Don't skip verification process
*
* @return object The JWT's payload as a PHP object
* @throws UnexpectedValueException Provided JWT was invalid
* @throws DomainException Algorithm was not provided
*
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
public static function decode($jwt, $key = null, $verify = true)
{
$tks = explode('.', $jwt);
if (count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid segment encoding');
}
if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid segment encoding');
}
$sig = JWT::urlsafeB64Decode($cryptob64);
if ($verify) {
if (empty($header->alg)) {
throw new DomainException('Empty algorithm');
}
if (is_array($key)) {
if(isset($header->kid)) {
$key = $key[$header->kid];
} else {
throw new DomainException('"kid" empty, unable to lookup correct key');
}
}
if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
throw new UnexpectedValueException('Signature verification failed');
}
// Check token expiry time if defined.
if (isset($payload->exp) && time() >= $payload->exp){
throw new UnexpectedValueException('Expired Token');
}
}
return $payload;
}
/**
* Converts and signs a PHP object or array into a JWT string.
*
* @param object|array $payload PHP object or array
* @param string $key The secret key
* @param string $algo The signing algorithm. Supported
* algorithms are 'HS256', 'HS384' and 'HS512'
*
* @return string A signed JWT
* @uses jsonEncode
* @uses urlsafeB64Encode
*/
public static function encode($payload, $key, $algo = 'HS256', $keyId = null)
{
$header = array('typ' => 'JWT', 'alg' => $algo);
if($keyId !== null) {
$header['kid'] = $keyId;
}
$segments = array();
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
$segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
$signing_input = implode('.', $segments);
$signature = JWT::sign($signing_input, $key, $algo);
$segments[] = JWT::urlsafeB64Encode($signature);
return implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource $key The secret key
* @param string $method The signing algorithm. Supported algorithms
* are 'HS256', 'HS384', 'HS512' and 'RS256'
*
* @return string An encrypted message
* @throws DomainException Unsupported algorithm was specified
*/
public static function sign($msg, $key, $method = 'HS256')
{
if (empty(self::$methods[$method])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algo) = self::$methods[$method];
switch($function) {
case 'hash_hmac':
return hash_hmac($algo, $msg, $key, true);
case 'openssl':
$signature = '';
$success = openssl_sign($msg, $signature, $key, $algo);
if(!$success) {
throw new DomainException("OpenSSL unable to sign data");
} else {
return $signature;
}
}
}
/**
* Verify a signature with the mesage, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
* @param string $msg the original message
* @param string $signature
* @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
* @param string $method
* @return bool
* @throws DomainException Invalid Algorithm or OpenSSL failure
*/
public static function verify($msg, $signature, $key, $method = 'HS256') {
if (empty(self::$methods[$method])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algo) = self::$methods[$method];
switch($function) {
case 'openssl':
$success = openssl_verify($msg, $signature, $key, $algo);
if(!$success) {
throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
} else {
return $signature;
}
case 'hash_hmac':
default:
return $signature === hash_hmac($algo, $msg, $key, true);
}
}
/**
* Decode a JSON string into a PHP object.
*
* @param string $input JSON string
*
* @return object Object representation of JSON string
* @throws DomainException Provided string was invalid JSON
*/
public static function jsonDecode($input)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
/* In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you to specify that large ints (like Steam
* Transaction IDs) should be treated as strings, rather than the PHP default behaviour of converting them to floats.
*/
$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
} else {
/* Not all servers will support that, however, so for older versions we must manually detect large ints in the JSON
* string and quote them (thus converting them to strings) before decoding, hence the preg_replace() call.
*/
$max_int_length = strlen((string) PHP_INT_MAX) - 1;
$json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
$obj = json_decode($json_without_bigints);
}
if (function_exists('json_last_error') && $errno = json_last_error()) {
JWT::_handleJsonError($errno);
} else if ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input');
}
return $obj;
}
/**
* Encode a PHP object into a JSON string.
*
* @param object|array $input A PHP object or array
*
* @return string JSON representation of the PHP object or array
* @throws DomainException Provided object could not be encoded to valid JSON
*/
public static function jsonEncode($input)
{
$json = json_encode($input);
if (function_exists('json_last_error') && $errno = json_last_error()) {
JWT::_handleJsonError($errno);
} else if ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input');
}
return $json;
}
/**
* Decode a string with URL-safe Base64.
*
* @param string $input A Base64 encoded string
*
* @return string A decoded string
*/
public static function urlsafeB64Decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* Encode a string with URL-safe Base64.
*
* @param string $input The string you want encoded
*
* @return string The base64 encode of what you passed in
*/
public static function urlsafeB64Encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* Helper method to create a JSON error.
*
* @param int $errno An error number from json_last_error()
*
* @return void
*/
private static function _handleJsonError($errno)
{
$messages = array(
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
);
throw new DomainException(
isset($messages[$errno])
? $messages[$errno]
: 'Unknown JSON error: ' . $errno
);
}
}

View File

@ -1,224 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Environment
*
* This class creates and returns a key/value array of common
* environment variables for the current HTTP request.
*
* This is a singleton class; derived environment variables will
* be common across multiple Slim applications.
*
* This class matches the Rack (Ruby) specification as closely
* as possible. More information available below.
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class Environment implements \ArrayAccess, \IteratorAggregate
{
/**
* @var array
*/
protected $properties;
/**
* @var \Slim\Environment
*/
protected static $environment;
/**
* Get environment instance (singleton)
*
* This creates and/or returns an environment instance (singleton)
* derived from $_SERVER variables. You may override the global server
* variables by using `\Slim\Environment::mock()` instead.
*
* @param bool $refresh Refresh properties using global server variables?
* @return \Slim\Environment
*/
public static function getInstance($refresh = false)
{
if (is_null(self::$environment) || $refresh) {
self::$environment = new self();
}
return self::$environment;
}
/**
* Get mock environment instance
*
* @param array $userSettings
* @return \Slim\Environment
*/
public static function mock($userSettings = array())
{
$defaults = array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_NAME' => '',
'PATH_INFO' => '',
'QUERY_STRING' => '',
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => 80,
'ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'ACCEPT_LANGUAGE' => 'en-US,en;q=0.8',
'ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
'USER_AGENT' => 'Slim Framework',
'REMOTE_ADDR' => '127.0.0.1',
'slim.url_scheme' => 'http',
'slim.input' => '',
'slim.errors' => @fopen('php://stderr', 'w')
);
self::$environment = new self(array_merge($defaults, $userSettings));
return self::$environment;
}
/**
* Constructor (private access)
*
* @param array|null $settings If present, these are used instead of global server variables
*/
private function __construct($settings = null)
{
if ($settings) {
$this->properties = $settings;
} else {
$env = array();
//The HTTP request method
$env['REQUEST_METHOD'] = $_SERVER['REQUEST_METHOD'];
//The IP
$env['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
// Server params
$scriptName = $_SERVER['SCRIPT_NAME']; // <-- "/foo/index.php"
$requestUri = $_SERVER['REQUEST_URI']; // <-- "/foo/bar?test=abc" or "/foo/index.php/bar?test=abc"
$queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; // <-- "test=abc" or ""
// Physical path
if (strpos($requestUri, $scriptName) !== false) {
$physicalPath = $scriptName; // <-- Without rewriting
} else {
$physicalPath = str_replace('\\', '', dirname($scriptName)); // <-- With rewriting
}
$env['SCRIPT_NAME'] = rtrim($physicalPath, '/'); // <-- Remove trailing slashes
// Virtual path
$env['PATH_INFO'] = substr_replace($requestUri, '', 0, strlen($physicalPath)); // <-- Remove physical path
$env['PATH_INFO'] = str_replace('?' . $queryString, '', $env['PATH_INFO']); // <-- Remove query string
$env['PATH_INFO'] = '/' . ltrim($env['PATH_INFO'], '/'); // <-- Ensure leading slash
// Query string (without leading "?")
$env['QUERY_STRING'] = $queryString;
//Name of server host that is running the script
$env['SERVER_NAME'] = $_SERVER['SERVER_NAME'];
//Number of server port that is running the script
$env['SERVER_PORT'] = $_SERVER['SERVER_PORT'];
//HTTP request headers (retains HTTP_ prefix to match $_SERVER)
$headers = \Slim\Http\Headers::extract($_SERVER);
foreach ($headers as $key => $value) {
$env[$key] = $value;
}
//Is the application running under HTTPS or HTTP protocol?
$env['slim.url_scheme'] = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https';
//Input stream (readable one time only; not available for multipart/form-data requests)
$rawInput = @file_get_contents('php://input');
if (!$rawInput) {
$rawInput = '';
}
$env['slim.input'] = $rawInput;
//Error stream
$env['slim.errors'] = @fopen('php://stderr', 'w');
$this->properties = $env;
}
}
/**
* Array Access: Offset Exists
*/
public function offsetExists($offset)
{
return isset($this->properties[$offset]);
}
/**
* Array Access: Offset Get
*/
public function offsetGet($offset)
{
if (isset($this->properties[$offset])) {
return $this->properties[$offset];
} else {
return null;
}
}
/**
* Array Access: Offset Set
*/
public function offsetSet($offset, $value)
{
$this->properties[$offset] = $value;
}
/**
* Array Access: Offset Unset
*/
public function offsetUnset($offset)
{
unset($this->properties[$offset]);
}
/**
* IteratorAggregate
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->properties);
}
}

View File

@ -1,49 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Exception;
/**
* Pass Exception
*
* This Exception will cause the Router::dispatch method
* to skip the current matching route and continue to the next
* matching route. If no subsequent routes are found, a
* HTTP 404 Not Found response will be sent to the client.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Pass extends \Exception
{
}

View File

@ -1,47 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Exception;
/**
* Stop Exception
*
* This Exception is thrown when the Slim application needs to abort
* processing and return control flow to the outer PHP script.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Stop extends \Exception
{
}

View File

@ -1,246 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Helper;
class Set implements \ArrayAccess, \Countable, \IteratorAggregate
{
/**
* Key-value array of arbitrary data
* @var array
*/
protected $data = array();
/**
* Constructor
* @param array $items Pre-populate set with this key-value array
*/
public function __construct($items = array())
{
$this->replace($items);
}
/**
* Normalize data key
*
* Used to transform data key into the necessary
* key format for this set. Used in subclasses
* like \Slim\Http\Headers.
*
* @param string $key The data key
* @return mixed The transformed/normalized data key
*/
protected function normalizeKey($key)
{
return $key;
}
/**
* Set data key to value
* @param string $key The data key
* @param mixed $value The data value
*/
public function set($key, $value)
{
$this->data[$this->normalizeKey($key)] = $value;
}
/**
* Get data value with key
* @param string $key The data key
* @param mixed $default The value to return if data key does not exist
* @return mixed The data value, or the default value
*/
public function get($key, $default = null)
{
if ($this->has($key)) {
$isInvokable = is_object($this->data[$this->normalizeKey($key)]) && method_exists($this->data[$this->normalizeKey($key)], '__invoke');
return $isInvokable ? $this->data[$this->normalizeKey($key)]($this) : $this->data[$this->normalizeKey($key)];
}
return $default;
}
/**
* Add data to set
* @param array $items Key-value array of data to append to this set
*/
public function replace($items)
{
foreach ($items as $key => $value) {
$this->set($key, $value); // Ensure keys are normalized
}
}
/**
* Fetch set data
* @return array This set's key-value data array
*/
public function all()
{
return $this->data;
}
/**
* Fetch set data keys
* @return array This set's key-value data array keys
*/
public function keys()
{
return array_keys($this->data);
}
/**
* Does this set contain a key?
* @param string $key The data key
* @return boolean
*/
public function has($key)
{
return array_key_exists($this->normalizeKey($key), $this->data);
}
/**
* Remove value with key from this set
* @param string $key The data key
*/
public function remove($key)
{
unset($this->data[$this->normalizeKey($key)]);
}
/**
* Property Overloading
*/
public function __get($key)
{
return $this->get($key);
}
public function __set($key, $value)
{
$this->set($key, $value);
}
public function __isset($key)
{
return $this->has($key);
}
public function __unset($key)
{
return $this->remove($key);
}
/**
* Clear all values
*/
public function clear()
{
$this->data = array();
}
/**
* Array Access
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
public function offsetGet($offset)
{
return $this->get($offset);
}
public function offsetSet($offset, $value)
{
$this->set($offset, $value);
}
public function offsetUnset($offset)
{
$this->remove($offset);
}
/**
* Countable
*/
public function count()
{
return count($this->data);
}
/**
* IteratorAggregate
*/
public function getIterator()
{
return new \ArrayIterator($this->data);
}
/**
* Ensure a value or object will remain globally unique
* @param string $key The value or object name
* @param Closure The closure that defines the object
* @return mixed
*/
public function singleton($key, $value)
{
$this->set($key, function ($c) use ($value) {
static $object;
if (null === $object) {
$object = $value($c);
}
return $object;
});
}
/**
* Protect closure from being directly invoked
* @param Closure $callable A closure to keep from being invoked and evaluated
* @return Closure
*/
public function protect(\Closure $callable)
{
return function () use ($callable) {
return $callable;
};
}
}

View File

@ -1,91 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Http;
class Cookies extends \Slim\Helper\Set
{
/**
* Default cookie settings
* @var array
*/
protected $defaults = array(
'value' => '',
'domain' => null,
'path' => null,
'expires' => null,
'secure' => false,
'httponly' => false
);
/**
* Set cookie
*
* The second argument may be a single scalar value, in which case
* it will be merged with the default settings and considered the `value`
* of the merged result.
*
* The second argument may also be an array containing any or all of
* the keys shown in the default settings above. This array will be
* merged with the defaults shown above.
*
* @param string $key Cookie name
* @param mixed $value Cookie settings
*/
public function set($key, $value)
{
if (is_array($value)) {
$cookieSettings = array_replace($this->defaults, $value);
} else {
$cookieSettings = array_replace($this->defaults, array('value' => $value));
}
parent::set($key, $cookieSettings);
}
/**
* Remove cookie
*
* Unlike \Slim\Helper\Set, this will actually *set* a cookie with
* an expiration date in the past. This expiration date will force
* the client-side cache to remove its cookie with the given name
* and settings.
*
* @param string $key Cookie name
* @param array $settings Optional cookie settings
*/
public function remove($key, $settings = array())
{
$settings['value'] = '';
$settings['expires'] = time() - 86400;
$this->set($key, array_replace($this->defaults, $settings));
}
}

View File

@ -1,104 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Http;
/**
* HTTP Headers
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class Headers extends \Slim\Helper\Set
{
/********************************************************************************
* Static interface
*******************************************************************************/
/**
* Special-case HTTP headers that are otherwise unidentifiable as HTTP headers.
* Typically, HTTP headers in the $_SERVER array will be prefixed with
* `HTTP_` or `X_`. These are not so we list them here for later reference.
*
* @var array
*/
protected static $special = array(
'CONTENT_TYPE',
'CONTENT_LENGTH',
'PHP_AUTH_USER',
'PHP_AUTH_PW',
'PHP_AUTH_DIGEST',
'AUTH_TYPE'
);
/**
* Extract HTTP headers from an array of data (e.g. $_SERVER)
* @param array $data
* @return array
*/
public static function extract($data)
{
$results = array();
foreach ($data as $key => $value) {
$key = strtoupper($key);
if (strpos($key, 'X_') === 0 || strpos($key, 'HTTP_') === 0 || in_array($key, static::$special)) {
if ($key === 'HTTP_CONTENT_LENGTH') {
continue;
}
$results[$key] = $value;
}
}
return $results;
}
/********************************************************************************
* Instance interface
*******************************************************************************/
/**
* Transform header name into canonical form
* @param string $key
* @return string
*/
protected function normalizeKey($key)
{
$key = strtolower($key);
$key = str_replace(array('-', '_'), ' ', $key);
$key = preg_replace('#^http #', '', $key);
$key = ucwords($key);
$key = str_replace(' ', '-', $key);
return $key;
}
}

View File

@ -1,617 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Http;
/**
* Slim HTTP Request
*
* This class provides a human-friendly interface to the Slim environment variables;
* environment variables are passed by reference and will be modified directly.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Request
{
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_PATCH = 'PATCH';
const METHOD_DELETE = 'DELETE';
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_OVERRIDE = '_METHOD';
/**
* @var array
*/
protected static $formDataMediaTypes = array('application/x-www-form-urlencoded');
/**
* Application Environment
* @var \Slim\Environment
*/
protected $env;
/**
* HTTP Headers
* @var \Slim\Http\Headers
*/
public $headers;
/**
* HTTP Cookies
* @var \Slim\Helper\Set
*/
public $cookies;
/**
* Constructor
* @param \Slim\Environment $env
*/
public function __construct(\Slim\Environment $env)
{
$this->env = $env;
$this->headers = new \Slim\Http\Headers(\Slim\Http\Headers::extract($env));
$this->cookies = new \Slim\Helper\Set(\Slim\Http\Util::parseCookieHeader($env['HTTP_COOKIE']));
}
/**
* Get HTTP method
* @return string
*/
public function getMethod()
{
return $this->env['REQUEST_METHOD'];
}
/**
* Is this a GET request?
* @return bool
*/
public function isGet()
{
return $this->getMethod() === self::METHOD_GET;
}
/**
* Is this a POST request?
* @return bool
*/
public function isPost()
{
return $this->getMethod() === self::METHOD_POST;
}
/**
* Is this a PUT request?
* @return bool
*/
public function isPut()
{
return $this->getMethod() === self::METHOD_PUT;
}
/**
* Is this a PATCH request?
* @return bool
*/
public function isPatch()
{
return $this->getMethod() === self::METHOD_PATCH;
}
/**
* Is this a DELETE request?
* @return bool
*/
public function isDelete()
{
return $this->getMethod() === self::METHOD_DELETE;
}
/**
* Is this a HEAD request?
* @return bool
*/
public function isHead()
{
return $this->getMethod() === self::METHOD_HEAD;
}
/**
* Is this a OPTIONS request?
* @return bool
*/
public function isOptions()
{
return $this->getMethod() === self::METHOD_OPTIONS;
}
/**
* Is this an AJAX request?
* @return bool
*/
public function isAjax()
{
if ($this->params('isajax')) {
return true;
} elseif (isset($this->headers['X_REQUESTED_WITH']) && $this->headers['X_REQUESTED_WITH'] === 'XMLHttpRequest') {
return true;
} else {
return false;
}
}
/**
* Is this an XHR request? (alias of Slim_Http_Request::isAjax)
* @return bool
*/
public function isXhr()
{
return $this->isAjax();
}
/**
* Fetch GET and POST data
*
* This method returns a union of GET and POST data as a key-value array, or the value
* of the array key if requested; if the array key does not exist, NULL is returned,
* unless there is a default value specified.
*
* @param string $key
* @param mixed $default
* @return array|mixed|null
*/
public function params($key = null, $default = null)
{
$union = array_merge($this->get(), $this->post());
if ($key) {
return isset($union[$key]) ? $union[$key] : $default;
}
return $union;
}
/**
* Fetch GET data
*
* This method returns a key-value array of data sent in the HTTP request query string, or
* the value of the array key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function get($key = null, $default = null)
{
if (!isset($this->env['slim.request.query_hash'])) {
$output = array();
if (function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte'])) {
mb_parse_str($this->env['QUERY_STRING'], $output);
} else {
parse_str($this->env['QUERY_STRING'], $output);
}
$this->env['slim.request.query_hash'] = Util::stripSlashesIfMagicQuotes($output);
}
if ($key) {
if (isset($this->env['slim.request.query_hash'][$key])) {
return $this->env['slim.request.query_hash'][$key];
} else {
return $default;
}
} else {
return $this->env['slim.request.query_hash'];
}
}
/**
* Fetch POST data
*
* This method returns a key-value array of data sent in the HTTP request body, or
* the value of a hash key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
* @throws \RuntimeException If environment input is not available
*/
public function post($key = null, $default = null)
{
if (!isset($this->env['slim.input'])) {
throw new \RuntimeException('Missing slim.input in environment variables');
}
if (!isset($this->env['slim.request.form_hash'])) {
$this->env['slim.request.form_hash'] = array();
if ($this->isFormData() && is_string($this->env['slim.input'])) {
$output = array();
if (function_exists('mb_parse_str') && !isset($this->env['slim.tests.ignore_multibyte'])) {
mb_parse_str($this->env['slim.input'], $output);
} else {
parse_str($this->env['slim.input'], $output);
}
$this->env['slim.request.form_hash'] = Util::stripSlashesIfMagicQuotes($output);
} else {
$this->env['slim.request.form_hash'] = Util::stripSlashesIfMagicQuotes($_POST);
}
}
if ($key) {
if (isset($this->env['slim.request.form_hash'][$key])) {
return $this->env['slim.request.form_hash'][$key];
} else {
return $default;
}
} else {
return $this->env['slim.request.form_hash'];
}
}
/**
* Fetch PUT data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function put($key = null, $default = null)
{
return $this->post($key, $default);
}
/**
* Fetch PATCH data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function patch($key = null, $default = null)
{
return $this->post($key, $default);
}
/**
* Fetch DELETE data (alias for \Slim\Http\Request::post)
* @param string $key
* @param mixed $default Default return value when key does not exist
* @return array|mixed|null
*/
public function delete($key = null, $default = null)
{
return $this->post($key, $default);
}
/**
* Fetch COOKIE data
*
* This method returns a key-value array of Cookie data sent in the HTTP request, or
* the value of a array key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @return array|string|null
*/
public function cookies($key = null)
{
if ($key) {
return $this->cookies->get($key);
}
return $this->cookies;
// if (!isset($this->env['slim.request.cookie_hash'])) {
// $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : '';
// $this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader);
// }
// if ($key) {
// if (isset($this->env['slim.request.cookie_hash'][$key])) {
// return $this->env['slim.request.cookie_hash'][$key];
// } else {
// return null;
// }
// } else {
// return $this->env['slim.request.cookie_hash'];
// }
}
/**
* Does the Request body contain parsed form data?
* @return bool
*/
public function isFormData()
{
$method = isset($this->env['slim.method_override.original_method']) ? $this->env['slim.method_override.original_method'] : $this->getMethod();
return ($method === self::METHOD_POST && is_null($this->getContentType())) || in_array($this->getMediaType(), self::$formDataMediaTypes);
}
/**
* Get Headers
*
* This method returns a key-value array of headers sent in the HTTP request, or
* the value of a hash key if requested; if the array key does not exist, NULL is returned.
*
* @param string $key
* @param mixed $default The default value returned if the requested header is not available
* @return mixed
*/
public function headers($key = null, $default = null)
{
if ($key) {
return $this->headers->get($key, $default);
}
return $this->headers;
// if ($key) {
// $key = strtoupper($key);
// $key = str_replace('-', '_', $key);
// $key = preg_replace('@^HTTP_@', '', $key);
// if (isset($this->env[$key])) {
// return $this->env[$key];
// } else {
// return $default;
// }
// } else {
// $headers = array();
// foreach ($this->env as $key => $value) {
// if (strpos($key, 'slim.') !== 0) {
// $headers[$key] = $value;
// }
// }
//
// return $headers;
// }
}
/**
* Get Body
* @return string
*/
public function getBody()
{
return $this->env['slim.input'];
}
/**
* Get Content Type
* @return string|null
*/
public function getContentType()
{
return $this->headers->get('CONTENT_TYPE');
}
/**
* Get Media Type (type/subtype within Content Type header)
* @return string|null
*/
public function getMediaType()
{
$contentType = $this->getContentType();
if ($contentType) {
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
return strtolower($contentTypeParts[0]);
}
return null;
}
/**
* Get Media Type Params
* @return array
*/
public function getMediaTypeParams()
{
$contentType = $this->getContentType();
$contentTypeParams = array();
if ($contentType) {
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
$contentTypePartsLength = count($contentTypeParts);
for ($i = 1; $i < $contentTypePartsLength; $i++) {
$paramParts = explode('=', $contentTypeParts[$i]);
$contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
}
}
return $contentTypeParams;
}
/**
* Get Content Charset
* @return string|null
*/
public function getContentCharset()
{
$mediaTypeParams = $this->getMediaTypeParams();
if (isset($mediaTypeParams['charset'])) {
return $mediaTypeParams['charset'];
}
return null;
}
/**
* Get Content-Length
* @return int
*/
public function getContentLength()
{
return $this->headers->get('CONTENT_LENGTH', 0);
}
/**
* Get Host
* @return string
*/
public function getHost()
{
if (isset($this->env['HTTP_HOST'])) {
if (strpos($this->env['HTTP_HOST'], ':') !== false) {
$hostParts = explode(':', $this->env['HTTP_HOST']);
return $hostParts[0];
}
return $this->env['HTTP_HOST'];
}
return $this->env['SERVER_NAME'];
}
/**
* Get Host with Port
* @return string
*/
public function getHostWithPort()
{
return sprintf('%s:%s', $this->getHost(), $this->getPort());
}
/**
* Get Port
* @return int
*/
public function getPort()
{
return (int)$this->env['SERVER_PORT'];
}
/**
* Get Scheme (https or http)
* @return string
*/
public function getScheme()
{
return $this->env['slim.url_scheme'];
}
/**
* Get Script Name (physical path)
* @return string
*/
public function getScriptName()
{
return $this->env['SCRIPT_NAME'];
}
/**
* LEGACY: Get Root URI (alias for Slim_Http_Request::getScriptName)
* @return string
*/
public function getRootUri()
{
return $this->getScriptName();
}
/**
* Get Path (physical path + virtual path)
* @return string
*/
public function getPath()
{
return $this->getScriptName() . $this->getPathInfo();
}
/**
* Get Path Info (virtual path)
* @return string
*/
public function getPathInfo()
{
return $this->env['PATH_INFO'];
}
/**
* LEGACY: Get Resource URI (alias for Slim_Http_Request::getPathInfo)
* @return string
*/
public function getResourceUri()
{
return $this->getPathInfo();
}
/**
* Get URL (scheme + host [ + port if non-standard ])
* @return string
*/
public function getUrl()
{
$url = $this->getScheme() . '://' . $this->getHost();
if (($this->getScheme() === 'https' && $this->getPort() !== 443) || ($this->getScheme() === 'http' && $this->getPort() !== 80)) {
$url .= sprintf(':%s', $this->getPort());
}
return $url;
}
/**
* Get IP
* @return string
*/
public function getIp()
{
$keys = array('X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP', 'REMOTE_ADDR');
foreach ($keys as $key) {
if (isset($this->env[$key])) {
return $this->env[$key];
}
}
return $this->env['REMOTE_ADDR'];
}
/**
* Get Referrer
* @return string|null
*/
public function getReferrer()
{
return $this->headers->get('HTTP_REFERER');
}
/**
* Get Referer (for those who can't spell)
* @return string|null
*/
public function getReferer()
{
return $this->getReferrer();
}
/**
* Get User Agent
* @return string|null
*/
public function getUserAgent()
{
return $this->headers->get('HTTP_USER_AGENT');
}
}

View File

@ -1,512 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Http;
/**
* Response
*
* This is a simple abstraction over top an HTTP response. This
* provides methods to set the HTTP status, the HTTP headers,
* and the HTTP body.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Response implements \ArrayAccess, \Countable, \IteratorAggregate
{
/**
* @var int HTTP status code
*/
protected $status;
/**
* @var \Slim\Http\Headers
*/
public $headers;
/**
* @var \Slim\Http\Cookies
*/
public $cookies;
/**
* @var string HTTP response body
*/
protected $body;
/**
* @var int Length of HTTP response body
*/
protected $length;
/**
* @var array HTTP response codes and messages
*/
protected static $messages = array(
//Informational 1xx
100 => '100 Continue',
101 => '101 Switching Protocols',
//Successful 2xx
200 => '200 OK',
201 => '201 Created',
202 => '202 Accepted',
203 => '203 Non-Authoritative Information',
204 => '204 No Content',
205 => '205 Reset Content',
206 => '206 Partial Content',
//Redirection 3xx
300 => '300 Multiple Choices',
301 => '301 Moved Permanently',
302 => '302 Found',
303 => '303 See Other',
304 => '304 Not Modified',
305 => '305 Use Proxy',
306 => '306 (Unused)',
307 => '307 Temporary Redirect',
//Client Error 4xx
400 => '400 Bad Request',
401 => '401 Unauthorized',
402 => '402 Payment Required',
403 => '403 Forbidden',
404 => '404 Not Found',
405 => '405 Method Not Allowed',
406 => '406 Not Acceptable',
407 => '407 Proxy Authentication Required',
408 => '408 Request Timeout',
409 => '409 Conflict',
410 => '410 Gone',
411 => '411 Length Required',
412 => '412 Precondition Failed',
413 => '413 Request Entity Too Large',
414 => '414 Request-URI Too Long',
415 => '415 Unsupported Media Type',
416 => '416 Requested Range Not Satisfiable',
417 => '417 Expectation Failed',
418 => '418 I\'m a teapot',
422 => '422 Unprocessable Entity',
423 => '423 Locked',
//Server Error 5xx
500 => '500 Internal Server Error',
501 => '501 Not Implemented',
502 => '502 Bad Gateway',
503 => '503 Service Unavailable',
504 => '504 Gateway Timeout',
505 => '505 HTTP Version Not Supported'
);
/**
* Constructor
* @param string $body The HTTP response body
* @param int $status The HTTP response status
* @param \Slim\Http\Headers|array $headers The HTTP response headers
*/
public function __construct($body = '', $status = 200, $headers = array())
{
$this->setStatus($status);
$this->headers = new \Slim\Http\Headers(array('Content-Type' => 'text/html'));
$this->headers->replace($headers);
$this->cookies = new \Slim\Http\Cookies();
$this->write($body);
}
public function getStatus()
{
return $this->status;
}
public function setStatus($status)
{
$this->status = (int)$status;
}
/**
* DEPRECATION WARNING! Use `getStatus` or `setStatus` instead.
*
* Get and set status
* @param int|null $status
* @return int
*/
public function status($status = null)
{
if (!is_null($status)) {
$this->status = (int) $status;
}
return $this->status;
}
/**
* DEPRECATION WARNING! Access `headers` property directly.
*
* Get and set header
* @param string $name Header name
* @param string|null $value Header value
* @return string Header value
*/
public function header($name, $value = null)
{
if (!is_null($value)) {
$this->headers->set($name, $value);
}
return $this->headers->get($name);
}
/**
* DEPRECATION WARNING! Access `headers` property directly.
*
* Get headers
* @return \Slim\Http\Headers
*/
public function headers()
{
return $this->headers;
}
public function getBody()
{
return $this->body;
}
public function setBody($content)
{
$this->write($content, true);
}
/**
* DEPRECATION WARNING! use `getBody` or `setBody` instead.
*
* Get and set body
* @param string|null $body Content of HTTP response body
* @return string
*/
public function body($body = null)
{
if (!is_null($body)) {
$this->write($body, true);
}
return $this->body;
}
/**
* Append HTTP response body
* @param string $body Content to append to the current HTTP response body
* @param bool $replace Overwrite existing response body?
* @return string The updated HTTP response body
*/
public function write($body, $replace = false)
{
if ($replace) {
$this->body = $body;
} else {
$this->body .= (string)$body;
}
$this->length = strlen($this->body);
return $this->body;
}
public function getLength()
{
return $this->length;
}
/**
* DEPRECATION WARNING! Use `getLength` or `write` or `body` instead.
*
* Get and set length
* @param int|null $length
* @return int
*/
public function length($length = null)
{
if (!is_null($length)) {
$this->length = (int) $length;
}
return $this->length;
}
/**
* Finalize
*
* This prepares this response and returns an array
* of [status, headers, body]. This array is passed to outer middleware
* if available or directly to the Slim run method.
*
* @return array[int status, array headers, string body]
*/
public function finalize()
{
// Prepare response
if (in_array($this->status, array(204, 304))) {
$this->headers->remove('Content-Type');
$this->headers->remove('Content-Length');
$this->setBody('');
}
return array($this->status, $this->headers, $this->body);
}
/**
* DEPRECATION WARNING! Access `cookies` property directly.
*
* Set cookie
*
* Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
* header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
* response's header is passed by reference to the utility class and is directly modified. By not
* relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
* analyze the raw header before the response is ultimately delivered to the HTTP client.
*
* @param string $name The name of the cookie
* @param string|array $value If string, the value of cookie; if array, properties for
* cookie including: value, expire, path, domain, secure, httponly
*/
public function setCookie($name, $value)
{
// Util::setCookieHeader($this->header, $name, $value);
$this->cookies->set($name, $value);
}
/**
* DEPRECATION WARNING! Access `cookies` property directly.
*
* Delete cookie
*
* Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie`
* header on its own and delegates this responsibility to the `Slim_Http_Util` class. This
* response's header is passed by reference to the utility class and is directly modified. By not
* relying on PHP's native implementation, Slim allows middleware the opportunity to massage or
* analyze the raw header before the response is ultimately delivered to the HTTP client.
*
* This method will set a cookie with the given name that has an expiration time in the past; this will
* prompt the HTTP client to invalidate and remove the client-side cookie. Optionally, you may
* also pass a key/value array as the second argument. If the "domain" key is present in this
* array, only the Cookie with the given name AND domain will be removed. The invalidating cookie
* sent with this response will adopt all properties of the second argument.
*
* @param string $name The name of the cookie
* @param array $settings Properties for cookie including: value, expire, path, domain, secure, httponly
*/
public function deleteCookie($name, $settings = array())
{
$this->cookies->remove($name, $settings);
// Util::deleteCookieHeader($this->header, $name, $value);
}
/**
* Redirect
*
* This method prepares this response to return an HTTP Redirect response
* to the HTTP client.
*
* @param string $url The redirect destination
* @param int $status The redirect HTTP status code
*/
public function redirect ($url, $status = 302)
{
$this->setStatus($status);
$this->headers->set('Location', $url);
}
/**
* Helpers: Empty?
* @return bool
*/
public function isEmpty()
{
return in_array($this->status, array(201, 204, 304));
}
/**
* Helpers: Informational?
* @return bool
*/
public function isInformational()
{
return $this->status >= 100 && $this->status < 200;
}
/**
* Helpers: OK?
* @return bool
*/
public function isOk()
{
return $this->status === 200;
}
/**
* Helpers: Successful?
* @return bool
*/
public function isSuccessful()
{
return $this->status >= 200 && $this->status < 300;
}
/**
* Helpers: Redirect?
* @return bool
*/
public function isRedirect()
{
return in_array($this->status, array(301, 302, 303, 307));
}
/**
* Helpers: Redirection?
* @return bool
*/
public function isRedirection()
{
return $this->status >= 300 && $this->status < 400;
}
/**
* Helpers: Forbidden?
* @return bool
*/
public function isForbidden()
{
return $this->status === 403;
}
/**
* Helpers: Not Found?
* @return bool
*/
public function isNotFound()
{
return $this->status === 404;
}
/**
* Helpers: Client error?
* @return bool
*/
public function isClientError()
{
return $this->status >= 400 && $this->status < 500;
}
/**
* Helpers: Server Error?
* @return bool
*/
public function isServerError()
{
return $this->status >= 500 && $this->status < 600;
}
/**
* DEPRECATION WARNING! ArrayAccess interface will be removed from \Slim\Http\Response.
* Iterate `headers` or `cookies` properties directly.
*/
/**
* Array Access: Offset Exists
*/
public function offsetExists($offset)
{
return isset($this->headers[$offset]);
}
/**
* Array Access: Offset Get
*/
public function offsetGet($offset)
{
return $this->headers[$offset];
}
/**
* Array Access: Offset Set
*/
public function offsetSet($offset, $value)
{
$this->headers[$offset] = $value;
}
/**
* Array Access: Offset Unset
*/
public function offsetUnset($offset)
{
unset($this->headers[$offset]);
}
/**
* DEPRECATION WARNING! Countable interface will be removed from \Slim\Http\Response.
* Call `count` on `headers` or `cookies` properties directly.
*
* Countable: Count
*/
public function count()
{
return count($this->headers);
}
/**
* DEPRECATION WARNING! IteratorAggregate interface will be removed from \Slim\Http\Response.
* Iterate `headers` or `cookies` properties directly.
*
* Get Iterator
*
* This returns the contained `\Slim\Http\Headers` instance which
* is itself iterable.
*
* @return \Slim\Http\Headers
*/
public function getIterator()
{
return $this->headers->getIterator();
}
/**
* Get message for HTTP status code
* @param int $status
* @return string|null
*/
public static function getMessageForCode($status)
{
if (isset(self::$messages[$status])) {
return self::$messages[$status];
} else {
return null;
}
}
}

View File

@ -1,434 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Http;
/**
* Slim HTTP Utilities
*
* This class provides useful methods for handling HTTP requests.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Util
{
/**
* Strip slashes from string or array
*
* This method strips slashes from its input. By default, this method will only
* strip slashes from its input if magic quotes are enabled. Otherwise, you may
* override the magic quotes setting with either TRUE or FALSE as the send argument
* to force this method to strip or not strip slashes from its input.
*
* @param array|string $rawData
* @param bool $overrideStripSlashes
* @return array|string
*/
public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null)
{
$strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes;
if ($strip) {
return self::stripSlashes($rawData);
} else {
return $rawData;
}
}
/**
* Strip slashes from string or array
* @param array|string $rawData
* @return array|string
*/
protected static function stripSlashes($rawData)
{
return is_array($rawData) ? array_map(array('self', 'stripSlashes'), $rawData) : stripslashes($rawData);
}
/**
* Encrypt data
*
* This method will encrypt data using a given key, vector, and cipher.
* By default, this will encrypt data using the RIJNDAEL/AES 256 bit cipher. You
* may override the default cipher and cipher mode by passing your own desired
* cipher and cipher mode as the final key-value array argument.
*
* @param string $data The unencrypted data
* @param string $key The encryption key
* @param string $iv The encryption initialization vector
* @param array $settings Optional key-value array with custom algorithm and mode
* @return string
*/
public static function encrypt($data, $key, $iv, $settings = array())
{
if ($data === '' || !extension_loaded('mcrypt')) {
return $data;
}
//Merge settings with defaults
$defaults = array(
'algorithm' => MCRYPT_RIJNDAEL_256,
'mode' => MCRYPT_MODE_CBC
);
$settings = array_merge($defaults, $settings);
//Get module
$module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
//Validate IV
$ivSize = mcrypt_enc_get_iv_size($module);
if (strlen($iv) > $ivSize) {
$iv = substr($iv, 0, $ivSize);
}
//Validate key
$keySize = mcrypt_enc_get_key_size($module);
if (strlen($key) > $keySize) {
$key = substr($key, 0, $keySize);
}
//Encrypt value
mcrypt_generic_init($module, $key, $iv);
$res = @mcrypt_generic($module, $data);
mcrypt_generic_deinit($module);
return $res;
}
/**
* Decrypt data
*
* This method will decrypt data using a given key, vector, and cipher.
* By default, this will decrypt data using the RIJNDAEL/AES 256 bit cipher. You
* may override the default cipher and cipher mode by passing your own desired
* cipher and cipher mode as the final key-value array argument.
*
* @param string $data The encrypted data
* @param string $key The encryption key
* @param string $iv The encryption initialization vector
* @param array $settings Optional key-value array with custom algorithm and mode
* @return string
*/
public static function decrypt($data, $key, $iv, $settings = array())
{
if ($data === '' || !extension_loaded('mcrypt')) {
return $data;
}
//Merge settings with defaults
$defaults = array(
'algorithm' => MCRYPT_RIJNDAEL_256,
'mode' => MCRYPT_MODE_CBC
);
$settings = array_merge($defaults, $settings);
//Get module
$module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
//Validate IV
$ivSize = mcrypt_enc_get_iv_size($module);
if (strlen($iv) > $ivSize) {
$iv = substr($iv, 0, $ivSize);
}
//Validate key
$keySize = mcrypt_enc_get_key_size($module);
if (strlen($key) > $keySize) {
$key = substr($key, 0, $keySize);
}
//Decrypt value
mcrypt_generic_init($module, $key, $iv);
$decryptedData = @mdecrypt_generic($module, $data);
$res = rtrim($decryptedData, "\0");
mcrypt_generic_deinit($module);
return $res;
}
/**
* Serialize Response cookies into raw HTTP header
* @param \Slim\Http\Headers $headers The Response headers
* @param \Slim\Http\Cookies $cookies The Response cookies
* @param array $config The Slim app settings
*/
public static function serializeCookies(\Slim\Http\Headers &$headers, \Slim\Http\Cookies $cookies, array $config)
{
if ($config['cookies.encrypt']) {
foreach ($cookies as $name => $settings) {
if (is_string($settings['expires'])) {
$expires = strtotime($settings['expires']);
} else {
$expires = (int) $settings['expires'];
}
$settings['value'] = static::encodeSecureCookie(
$settings['value'],
$expires,
$config['cookies.secret_key'],
$config['cookies.cipher'],
$config['cookies.cipher_mode']
);
static::setCookieHeader($headers, $name, $settings);
}
} else {
foreach ($cookies as $name => $settings) {
static::setCookieHeader($headers, $name, $settings);
}
}
}
/**
* Encode secure cookie value
*
* This method will create the secure value of an HTTP cookie. The
* cookie value is encrypted and hashed so that its value is
* secure and checked for integrity when read in subsequent requests.
*
* @param string $value The insecure HTTP cookie value
* @param int $expires The UNIX timestamp at which this cookie will expire
* @param string $secret The secret key used to hash the cookie value
* @param int $algorithm The algorithm to use for encryption
* @param int $mode The algorithm mode to use for encryption
* @return string
*/
public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode)
{
$key = hash_hmac('sha1', (string) $expires, $secret);
$iv = self::getIv($expires, $secret);
$secureString = base64_encode(
self::encrypt(
$value,
$key,
$iv,
array(
'algorithm' => $algorithm,
'mode' => $mode
)
)
);
$verificationString = hash_hmac('sha1', $expires . $value, $key);
return implode('|', array($expires, $secureString, $verificationString));
}
/**
* Decode secure cookie value
*
* This method will decode the secure value of an HTTP cookie. The
* cookie value is encrypted and hashed so that its value is
* secure and checked for integrity when read in subsequent requests.
*
* @param string $value The secure HTTP cookie value
* @param string $secret The secret key used to hash the cookie value
* @param int $algorithm The algorithm to use for encryption
* @param int $mode The algorithm mode to use for encryption
* @return bool|string
*/
public static function decodeSecureCookie($value, $secret, $algorithm, $mode)
{
if ($value) {
$value = explode('|', $value);
if (count($value) === 3 && ((int) $value[0] === 0 || (int) $value[0] > time())) {
$key = hash_hmac('sha1', $value[0], $secret);
$iv = self::getIv($value[0], $secret);
$data = self::decrypt(
base64_decode($value[1]),
$key,
$iv,
array(
'algorithm' => $algorithm,
'mode' => $mode
)
);
$verificationString = hash_hmac('sha1', $value[0] . $data, $key);
if ($verificationString === $value[2]) {
return $data;
}
}
}
return false;
}
/**
* Set HTTP cookie header
*
* This method will construct and set the HTTP `Set-Cookie` header. Slim
* uses this method instead of PHP's native `setcookie` method. This allows
* more control of the HTTP header irrespective of the native implementation's
* dependency on PHP versions.
*
* This method accepts the Slim_Http_Headers object by reference as its
* first argument; this method directly modifies this object instead of
* returning a value.
*
* @param array $header
* @param string $name
* @param string $value
*/
public static function setCookieHeader(&$header, $name, $value)
{
//Build cookie header
if (is_array($value)) {
$domain = '';
$path = '';
$expires = '';
$secure = '';
$httponly = '';
if (isset($value['domain']) && $value['domain']) {
$domain = '; domain=' . $value['domain'];
}
if (isset($value['path']) && $value['path']) {
$path = '; path=' . $value['path'];
}
if (isset($value['expires'])) {
if (is_string($value['expires'])) {
$timestamp = strtotime($value['expires']);
} else {
$timestamp = (int) $value['expires'];
}
if ($timestamp !== 0) {
$expires = '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp);
}
}
if (isset($value['secure']) && $value['secure']) {
$secure = '; secure';
}
if (isset($value['httponly']) && $value['httponly']) {
$httponly = '; HttpOnly';
}
$cookie = sprintf('%s=%s%s', urlencode($name), urlencode((string) $value['value']), $domain . $path . $expires . $secure . $httponly);
} else {
$cookie = sprintf('%s=%s', urlencode($name), urlencode((string) $value));
}
//Set cookie header
if (!isset($header['Set-Cookie']) || $header['Set-Cookie'] === '') {
$header['Set-Cookie'] = $cookie;
} else {
$header['Set-Cookie'] = implode("\n", array($header['Set-Cookie'], $cookie));
}
}
/**
* Delete HTTP cookie header
*
* This method will construct and set the HTTP `Set-Cookie` header to invalidate
* a client-side HTTP cookie. If a cookie with the same name (and, optionally, domain)
* is already set in the HTTP response, it will also be removed. Slim uses this method
* instead of PHP's native `setcookie` method. This allows more control of the HTTP header
* irrespective of PHP's native implementation's dependency on PHP versions.
*
* This method accepts the Slim_Http_Headers object by reference as its
* first argument; this method directly modifies this object instead of
* returning a value.
*
* @param array $header
* @param string $name
* @param array $value
*/
public static function deleteCookieHeader(&$header, $name, $value = array())
{
//Remove affected cookies from current response header
$cookiesOld = array();
$cookiesNew = array();
if (isset($header['Set-Cookie'])) {
$cookiesOld = explode("\n", $header['Set-Cookie']);
}
foreach ($cookiesOld as $c) {
if (isset($value['domain']) && $value['domain']) {
$regex = sprintf('@%s=.*domain=%s@', urlencode($name), preg_quote($value['domain']));
} else {
$regex = sprintf('@%s=@', urlencode($name));
}
if (preg_match($regex, $c) === 0) {
$cookiesNew[] = $c;
}
}
if ($cookiesNew) {
$header['Set-Cookie'] = implode("\n", $cookiesNew);
} else {
unset($header['Set-Cookie']);
}
//Set invalidating cookie to clear client-side cookie
self::setCookieHeader($header, $name, array_merge(array('value' => '', 'path' => null, 'domain' => null, 'expires' => time() - 100), $value));
}
/**
* Parse cookie header
*
* This method will parse the HTTP request's `Cookie` header
* and extract cookies into an associative array.
*
* @param string
* @return array
*/
public static function parseCookieHeader($header)
{
$cookies = array();
$header = rtrim($header, "\r\n");
$headerPieces = preg_split('@\s*[;,]\s*@', $header);
foreach ($headerPieces as $c) {
$cParts = explode('=', $c, 2);
if (count($cParts) === 2) {
$key = urldecode($cParts[0]);
$value = urldecode($cParts[1]);
if (!isset($cookies[$key])) {
$cookies[$key] = $value;
}
}
}
return $cookies;
}
/**
* Generate a random IV
*
* This method will generate a non-predictable IV for use with
* the cookie encryption
*
* @param int $expires The UNIX timestamp at which this cookie will expire
* @param string $secret The secret key used to hash the cookie value
* @return string Hash
*/
private static function getIv($expires, $secret)
{
$data1 = hash_hmac('sha1', 'a'.$expires.'b', $secret);
$data2 = hash_hmac('sha1', 'z'.$expires.'y', $secret);
return pack("h*", $data1.$data2);
}
}

View File

@ -1,349 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Log
*
* This is the primary logger for a Slim application. You may provide
* a Log Writer in conjunction with this Log to write to various output
* destinations (e.g. a file). This class provides this interface:
*
* debug( mixed $object, array $context )
* info( mixed $object, array $context )
* notice( mixed $object, array $context )
* warning( mixed $object, array $context )
* error( mixed $object, array $context )
* critical( mixed $object, array $context )
* alert( mixed $object, array $context )
* emergency( mixed $object, array $context )
* log( mixed $level, mixed $object, array $context )
*
* This class assumes only that your Log Writer has a public `write()` method
* that accepts any object as its one and only argument. The Log Writer
* class may write or send its argument anywhere: a file, STDERR,
* a remote web API, etc. The possibilities are endless.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Log
{
const EMERGENCY = 1;
const ALERT = 2;
const CRITICAL = 3;
const FATAL = 3; //DEPRECATED replace with CRITICAL
const ERROR = 4;
const WARN = 5;
const NOTICE = 6;
const INFO = 7;
const DEBUG = 8;
/**
* @var array
*/
protected static $levels = array(
self::EMERGENCY => 'EMERGENCY',
self::ALERT => 'ALERT',
self::CRITICAL => 'CRITICAL',
self::ERROR => 'ERROR',
self::WARN => 'WARNING',
self::NOTICE => 'NOTICE',
self::INFO => 'INFO',
self::DEBUG => 'DEBUG'
);
/**
* @var mixed
*/
protected $writer;
/**
* @var bool
*/
protected $enabled;
/**
* @var int
*/
protected $level;
/**
* Constructor
* @param mixed $writer
*/
public function __construct($writer)
{
$this->writer = $writer;
$this->enabled = true;
$this->level = self::DEBUG;
}
/**
* Is logging enabled?
* @return bool
*/
public function getEnabled()
{
return $this->enabled;
}
/**
* Enable or disable logging
* @param bool $enabled
*/
public function setEnabled($enabled)
{
if ($enabled) {
$this->enabled = true;
} else {
$this->enabled = false;
}
}
/**
* Set level
* @param int $level
* @throws \InvalidArgumentException If invalid log level specified
*/
public function setLevel($level)
{
if (!isset(self::$levels[$level])) {
throw new \InvalidArgumentException('Invalid log level');
}
$this->level = $level;
}
/**
* Get level
* @return int
*/
public function getLevel()
{
return $this->level;
}
/**
* Set writer
* @param mixed $writer
*/
public function setWriter($writer)
{
$this->writer = $writer;
}
/**
* Get writer
* @return mixed
*/
public function getWriter()
{
return $this->writer;
}
/**
* Is logging enabled?
* @return bool
*/
public function isEnabled()
{
return $this->enabled;
}
/**
* Log debug message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function debug($object, $context = array())
{
return $this->log(self::DEBUG, $object, $context);
}
/**
* Log info message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function info($object, $context = array())
{
return $this->log(self::INFO, $object, $context);
}
/**
* Log notice message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function notice($object, $context = array())
{
return $this->log(self::NOTICE, $object, $context);
}
/**
* Log warning message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function warning($object, $context = array())
{
return $this->log(self::WARN, $object, $context);
}
/**
* DEPRECATED for function warning
* Log warning message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function warn($object, $context = array())
{
return $this->log(self::WARN, $object, $context);
}
/**
* Log error message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function error($object, $context = array())
{
return $this->log(self::ERROR, $object, $context);
}
/**
* Log critical message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function critical($object, $context = array())
{
return $this->log(self::CRITICAL, $object, $context);
}
/**
* DEPRECATED for function critical
* Log fatal message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function fatal($object, $context = array())
{
return $this->log(self::CRITICAL, $object, $context);
}
/**
* Log alert message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function alert($object, $context = array())
{
return $this->log(self::ALERT, $object, $context);
}
/**
* Log emergency message
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
*/
public function emergency($object, $context = array())
{
return $this->log(self::EMERGENCY, $object, $context);
}
/**
* Log message
* @param mixed $level
* @param mixed $object
* @param array $context
* @return mixed|bool What the Logger returns, or false if Logger not set or not enabled
* @throws \InvalidArgumentException If invalid log level
*/
public function log($level, $object, $context = array())
{
if (!isset(self::$levels[$level])) {
throw new \InvalidArgumentException('Invalid log level supplied to function');
} else if ($this->enabled && $this->writer && $level <= $this->level) {
$message = (string)$object;
if (count($context) > 0) {
if (isset($context['exception']) && $context['exception'] instanceof \Exception) {
$message .= ' - ' . $context['exception'];
unset($context['exception']);
}
$message = $this->interpolate($message, $context);
}
return $this->writer->write($message, $level);
} else {
return false;
}
}
/**
* DEPRECATED for function log
* Log message
* @param mixed $object The object to log
* @param int $level The message level
* @return int|bool
*/
protected function write($object, $level)
{
return $this->log($level, $object);
}
/**
* Interpolate log message
* @param mixed $message The log message
* @param array $context An array of placeholder values
* @return string The processed string
*/
protected function interpolate($message, $context = array())
{
$replace = array();
foreach ($context as $key => $value) {
$replace['{' . $key . '}'] = $value;
}
return strtr($message, $replace);
}
}

View File

@ -1,75 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Log Writer
*
* This class is used by Slim_Log to write log messages to a valid, writable
* resource handle (e.g. a file or STDERR).
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class LogWriter
{
/**
* @var resource
*/
protected $resource;
/**
* Constructor
* @param resource $resource
* @throws \InvalidArgumentException If invalid resource
*/
public function __construct($resource)
{
if (!is_resource($resource)) {
throw new \InvalidArgumentException('Cannot create LogWriter. Invalid resource handle.');
}
$this->resource = $resource;
}
/**
* Write message
* @param mixed $message
* @param int $level
* @return int|bool
*/
public function write($message, $level = null)
{
return fwrite($this->resource, (string) $message . PHP_EOL);
}
}

View File

@ -1,114 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Middleware
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
abstract class Middleware
{
/**
* @var \Slim\Slim Reference to the primary application instance
*/
protected $app;
/**
* @var mixed Reference to the next downstream middleware
*/
protected $next;
/**
* Set application
*
* This method injects the primary Slim application instance into
* this middleware.
*
* @param \Slim\Slim $application
*/
final public function setApplication($application)
{
$this->app = $application;
}
/**
* Get application
*
* This method retrieves the application previously injected
* into this middleware.
*
* @return \Slim\Slim
*/
final public function getApplication()
{
return $this->app;
}
/**
* Set next middleware
*
* This method injects the next downstream middleware into
* this middleware so that it may optionally be called
* when appropriate.
*
* @param \Slim|\Slim\Middleware
*/
final public function setNextMiddleware($nextMiddleware)
{
$this->next = $nextMiddleware;
}
/**
* Get next middleware
*
* This method retrieves the next downstream middleware
* previously injected into this middleware.
*
* @return \Slim\Slim|\Slim\Middleware
*/
final public function getNextMiddleware()
{
return $this->next;
}
/**
* Call
*
* Perform actions specific to this middleware and optionally
* call the next downstream middleware.
*/
abstract public function call();
}

View File

@ -1,174 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Middleware;
/**
* Content Types
*
* This is middleware for a Slim application that intercepts
* the HTTP request body and parses it into the appropriate
* PHP data structure if possible; else it returns the HTTP
* request body unchanged. This is particularly useful
* for preparing the HTTP request body for an XML or JSON API.
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class ContentTypes extends \Slim\Middleware
{
/**
* @var array
*/
protected $contentTypes;
/**
* Constructor
* @param array $settings
*/
public function __construct($settings = array())
{
$defaults = array(
'application/json' => array($this, 'parseJson'),
'application/xml' => array($this, 'parseXml'),
'text/xml' => array($this, 'parseXml'),
'text/csv' => array($this, 'parseCsv')
);
$this->contentTypes = array_merge($defaults, $settings);
}
/**
* Call
*/
public function call()
{
$mediaType = $this->app->request()->getMediaType();
if ($mediaType) {
$env = $this->app->environment();
$env['slim.input_original'] = $env['slim.input'];
$env['slim.input'] = $this->parse($env['slim.input'], $mediaType);
}
$this->next->call();
}
/**
* Parse input
*
* This method will attempt to parse the request body
* based on its content type if available.
*
* @param string $input
* @param string $contentType
* @return mixed
*/
protected function parse ($input, $contentType)
{
if (isset($this->contentTypes[$contentType]) && is_callable($this->contentTypes[$contentType])) {
$result = call_user_func($this->contentTypes[$contentType], $input);
if ($result) {
return $result;
}
}
return $input;
}
/**
* Parse JSON
*
* This method converts the raw JSON input
* into an associative array.
*
* @param string $input
* @return array|string
*/
protected function parseJson($input)
{
if (function_exists('json_decode')) {
$result = json_decode($input, true);
if ($result) {
return $result;
}
}
}
/**
* Parse XML
*
* This method creates a SimpleXMLElement
* based upon the XML input. If the SimpleXML
* extension is not available, the raw input
* will be returned unchanged.
*
* @param string $input
* @return \SimpleXMLElement|string
*/
protected function parseXml($input)
{
if (class_exists('SimpleXMLElement')) {
try {
$backup = libxml_disable_entity_loader(true);
$result = new \SimpleXMLElement($input);
libxml_disable_entity_loader($backup);
return $result;
} catch (\Exception $e) {
// Do nothing
}
}
return $input;
}
/**
* Parse CSV
*
* This method parses CSV content into a numeric array
* containing an array of data for each CSV line.
*
* @param string $input
* @return array
*/
protected function parseCsv($input)
{
$temp = fopen('php://memory', 'rw');
fwrite($temp, $input);
fseek($temp, 0);
$res = array();
while (($data = fgetcsv($temp)) !== false) {
$res[] = $data;
}
fclose($temp);
return $res;
}
}

View File

@ -1,212 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Middleware;
/**
* Flash
*
* This is middleware for a Slim application that enables
* Flash messaging between HTTP requests. This allows you
* set Flash messages for the current request, for the next request,
* or to retain messages from the previous request through to
* the next request.
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
* @var array
*/
protected $settings;
/**
* @var array
*/
protected $messages;
/**
* Constructor
* @param array $settings
*/
public function __construct($settings = array())
{
$this->settings = array_merge(array('key' => 'slim.flash'), $settings);
$this->messages = array(
'prev' => array(), //flash messages from prev request (loaded when middleware called)
'next' => array(), //flash messages for next request
'now' => array() //flash messages for current request
);
}
/**
* Call
*/
public function call()
{
//Read flash messaging from previous request if available
$this->loadMessages();
//Prepare flash messaging for current request
$env = $this->app->environment();
$env['slim.flash'] = $this;
$this->next->call();
$this->save();
}
/**
* Now
*
* Specify a flash message for a given key to be shown for the current request
*
* @param string $key
* @param string $value
*/
public function now($key, $value)
{
$this->messages['now'][(string) $key] = $value;
}
/**
* Set
*
* Specify a flash message for a given key to be shown for the next request
*
* @param string $key
* @param string $value
*/
public function set($key, $value)
{
$this->messages['next'][(string) $key] = $value;
}
/**
* Keep
*
* Retain flash messages from the previous request for the next request
*/
public function keep()
{
foreach ($this->messages['prev'] as $key => $val) {
$this->messages['next'][$key] = $val;
}
}
/**
* Save
*/
public function save()
{
$_SESSION[$this->settings['key']] = $this->messages['next'];
}
/**
* Load messages from previous request if available
*/
public function loadMessages()
{
if (isset($_SESSION[$this->settings['key']])) {
$this->messages['prev'] = $_SESSION[$this->settings['key']];
}
}
/**
* Return array of flash messages to be shown for the current request
*
* @return array
*/
public function getMessages()
{
return array_merge($this->messages['prev'], $this->messages['now']);
}
/**
* Array Access: Offset Exists
*/
public function offsetExists($offset)
{
$messages = $this->getMessages();
return isset($messages[$offset]);
}
/**
* Array Access: Offset Get
*/
public function offsetGet($offset)
{
$messages = $this->getMessages();
return isset($messages[$offset]) ? $messages[$offset] : null;
}
/**
* Array Access: Offset Set
*/
public function offsetSet($offset, $value)
{
$this->now($offset, $value);
}
/**
* Array Access: Offset Unset
*/
public function offsetUnset($offset)
{
unset($this->messages['prev'][$offset], $this->messages['now'][$offset]);
}
/**
* Iterator Aggregate: Get Iterator
* @return \ArrayIterator
*/
public function getIterator()
{
$messages = $this->getMessages();
return new \ArrayIterator($messages);
}
/**
* Countable: Count
*/
public function count()
{
return count($this->getMessages());
}
}

View File

@ -1,94 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Middleware;
/**
* HTTP Method Override
*
* This is middleware for a Slim application that allows traditional
* desktop browsers to submit pseudo PUT and DELETE requests by relying
* on a pre-determined request parameter. Without this middleware,
* desktop browsers are only able to submit GET and POST requests.
*
* This middleware is included automatically!
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class MethodOverride extends \Slim\Middleware
{
/**
* @var array
*/
protected $settings;
/**
* Constructor
* @param array $settings
*/
public function __construct($settings = array())
{
$this->settings = array_merge(array('key' => '_METHOD'), $settings);
}
/**
* Call
*
* Implements Slim middleware interface. This method is invoked and passed
* an array of environment variables. This middleware inspects the environment
* variables for the HTTP method override parameter; if found, this middleware
* modifies the environment settings so downstream middleware and/or the Slim
* application will treat the request with the desired HTTP method.
*
* @return array[status, header, body]
*/
public function call()
{
$env = $this->app->environment();
if (isset($env['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
// Header commonly used by Backbone.js and others
$env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
$env['REQUEST_METHOD'] = strtoupper($env['HTTP_X_HTTP_METHOD_OVERRIDE']);
} elseif (isset($env['REQUEST_METHOD']) && $env['REQUEST_METHOD'] === 'POST') {
// HTML Form Override
$req = new \Slim\Http\Request($env);
$method = $req->post($this->settings['key']);
if ($method) {
$env['slim.method_override.original_method'] = $env['REQUEST_METHOD'];
$env['REQUEST_METHOD'] = strtoupper($method);
}
}
$this->next->call();
}
}

View File

@ -1,116 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Middleware;
/**
* Pretty Exceptions
*
* This middleware catches any Exception thrown by the surrounded
* application and displays a developer-friendly diagnostic screen.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class PrettyExceptions extends \Slim\Middleware
{
/**
* @var array
*/
protected $settings;
/**
* Constructor
* @param array $settings
*/
public function __construct($settings = array())
{
$this->settings = $settings;
}
/**
* Call
*/
public function call()
{
try {
$this->next->call();
} catch (\Exception $e) {
$log = $this->app->getLog(); // Force Slim to append log to env if not already
$env = $this->app->environment();
$env['slim.log'] = $log;
$env['slim.log']->error($e);
$this->app->contentType('text/html');
$this->app->response()->status(500);
$this->app->response()->body($this->renderBody($env, $e));
}
}
/**
* Render response body
* @param array $env
* @param \Exception $exception
* @return string
*/
protected function renderBody(&$env, $exception)
{
$title = 'Slim Application Error';
$code = $exception->getCode();
$message = $exception->getMessage();
$file = $exception->getFile();
$line = $exception->getLine();
$trace = str_replace(array('#', '\n'), array('<div>#', '</div>'), $exception->getTraceAsString());
$html = sprintf('<h1>%s</h1>', $title);
$html .= '<p>The application could not run because of the following error:</p>';
$html .= '<h2>Details</h2>';
$html .= sprintf('<div><strong>Type:</strong> %s</div>', get_class($exception));
if ($code) {
$html .= sprintf('<div><strong>Code:</strong> %s</div>', $code);
}
if ($message) {
$html .= sprintf('<div><strong>Message:</strong> %s</div>', $message);
}
if ($file) {
$html .= sprintf('<div><strong>File:</strong> %s</div>', $file);
}
if ($line) {
$html .= sprintf('<div><strong>Line:</strong> %s</div>', $line);
}
if ($trace) {
$html .= '<h2>Trace</h2>';
$html .= sprintf('<pre>%s</pre>', $trace);
}
return sprintf("<html><head><title>%s</title><style>body{margin:0;padding:30px;font:12px/1.5 Helvetica,Arial,Verdana,sans-serif;}h1{margin:0;font-size:48px;font-weight:normal;line-height:48px;}strong{display:inline-block;width:65px;}</style></head><body>%s</body></html>", $title, $html);
}
}

View File

@ -1,210 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim\Middleware;
/**
* Session Cookie
*
* This class provides an HTTP cookie storage mechanism
* for session data. This class avoids using a PHP session
* and instead serializes/unserializes the $_SESSION global
* variable to/from an HTTP cookie.
*
* You should NEVER store sensitive data in a client-side cookie
* in any format, encrypted (with cookies.encrypt) or not. If you
* need to store sensitive user information in a session, you should
* rely on PHP's native session implementation, or use other middleware
* to store session data in a database or alternative server-side cache.
*
* Because this class stores serialized session data in an HTTP cookie,
* you are inherently limited to 4 Kb. If you attempt to store
* more than this amount, serialization will fail.
*
* @package Slim
* @author Josh Lockhart
* @since 1.6.0
*/
class SessionCookie extends \Slim\Middleware
{
/**
* @var array
*/
protected $settings;
/**
* Constructor
*
* @param array $settings
*/
public function __construct($settings = array())
{
$defaults = array(
'expires' => '20 minutes',
'path' => '/',
'domain' => null,
'secure' => false,
'httponly' => false,
'name' => 'slim_session',
);
$this->settings = array_merge($defaults, $settings);
if (is_string($this->settings['expires'])) {
$this->settings['expires'] = strtotime($this->settings['expires']);
}
/**
* Session
*
* We must start a native PHP session to initialize the $_SESSION superglobal.
* However, we won't be using the native session store for persistence, so we
* disable the session cookie and cache limiter. We also set the session
* handler to this class instance to avoid PHP's native session file locking.
*/
ini_set('session.use_cookies', 0);
session_cache_limiter(false);
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
}
/**
* Call
*/
public function call()
{
$this->loadSession();
$this->next->call();
$this->saveSession();
}
/**
* Load session
*/
protected function loadSession()
{
if (session_id() === '') {
session_start();
}
$value = $this->app->getCookie($this->settings['name']);
if ($value) {
try {
$_SESSION = unserialize($value);
} catch (\Exception $e) {
$this->app->getLog()->error('Error unserializing session cookie value! ' . $e->getMessage());
}
} else {
$_SESSION = array();
}
}
/**
* Save session
*/
protected function saveSession()
{
$value = serialize($_SESSION);
if (strlen($value) > 4096) {
$this->app->getLog()->error('WARNING! Slim\Middleware\SessionCookie data size is larger than 4KB. Content save failed.');
} else {
$this->app->setCookie(
$this->settings['name'],
$value,
$this->settings['expires'],
$this->settings['path'],
$this->settings['domain'],
$this->settings['secure'],
$this->settings['httponly']
);
}
// session_destroy();
}
/********************************************************************************
* Session Handler
*******************************************************************************/
/**
* @codeCoverageIgnore
*/
public function open($savePath, $sessionName)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function close()
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function read($id)
{
return '';
}
/**
* @codeCoverageIgnore
*/
public function write($id, $data)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function destroy($id)
{
return true;
}
/**
* @codeCoverageIgnore
*/
public function gc($maxlifetime)
{
return true;
}
}

View File

@ -1,465 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Route
* @package Slim
* @author Josh Lockhart, Thomas Bley
* @since 1.0.0
*/
class Route
{
/**
* @var string The route pattern (e.g. "/books/:id")
*/
protected $pattern;
/**
* @var mixed The route callable
*/
protected $callable;
/**
* @var array Conditions for this route's URL parameters
*/
protected $conditions = array();
/**
* @var array Default conditions applied to all route instances
*/
protected static $defaultConditions = array();
/**
* @var string The name of this route (optional)
*/
protected $name;
/**
* @var array Key-value array of URL parameters
*/
protected $params = array();
/**
* @var array value array of URL parameter names
*/
protected $paramNames = array();
/**
* @var array key array of URL parameter names with + at the end
*/
protected $paramNamesPath = array();
/**
* @var array HTTP methods supported by this Route
*/
protected $methods = array();
/**
* @var array[Callable] Middleware to be run before only this route instance
*/
protected $middleware = array();
/**
* @var bool Whether or not this route should be matched in a case-sensitive manner
*/
protected $caseSensitive;
/**
* Constructor
* @param string $pattern The URL pattern (e.g. "/books/:id")
* @param mixed $callable Anything that returns TRUE for is_callable()
* @param bool $caseSensitive Whether or not this route should be matched in a case-sensitive manner
*/
public function __construct($pattern, $callable, $caseSensitive = true)
{
$this->setPattern($pattern);
$this->setCallable($callable);
$this->setConditions(self::getDefaultConditions());
$this->caseSensitive = $caseSensitive;
}
/**
* Set default route conditions for all instances
* @param array $defaultConditions
*/
public static function setDefaultConditions(array $defaultConditions)
{
self::$defaultConditions = $defaultConditions;
}
/**
* Get default route conditions for all instances
* @return array
*/
public static function getDefaultConditions()
{
return self::$defaultConditions;
}
/**
* Get route pattern
* @return string
*/
public function getPattern()
{
return $this->pattern;
}
/**
* Set route pattern
* @param string $pattern
*/
public function setPattern($pattern)
{
$this->pattern = $pattern;
}
/**
* Get route callable
* @return mixed
*/
public function getCallable()
{
return $this->callable;
}
/**
* Set route callable
* @param mixed $callable
* @throws \InvalidArgumentException If argument is not callable
*/
public function setCallable($callable)
{
$matches = array();
if (is_string($callable) && preg_match('!^([^\:]+)\:([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$!', $callable, $matches)) {
$class = $matches[1];
$method = $matches[2];
$callable = function() use ($class, $method) {
static $obj = null;
if ($obj === null) {
$obj = new $class;
}
return call_user_func_array(array($obj, $method), func_get_args());
};
}
if (!is_callable($callable)) {
throw new \InvalidArgumentException('Route callable must be callable');
}
$this->callable = $callable;
}
/**
* Get route conditions
* @return array
*/
public function getConditions()
{
return $this->conditions;
}
/**
* Set route conditions
* @param array $conditions
*/
public function setConditions(array $conditions)
{
$this->conditions = $conditions;
}
/**
* Get route name
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* Set route name
* @param string $name
*/
public function setName($name)
{
$this->name = (string)$name;
}
/**
* Get route parameters
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Set route parameters
* @param array $params
*/
public function setParams($params)
{
$this->params = $params;
}
/**
* Get route parameter value
* @param string $index Name of URL parameter
* @return string
* @throws \InvalidArgumentException If route parameter does not exist at index
*/
public function getParam($index)
{
if (!isset($this->params[$index])) {
throw new \InvalidArgumentException('Route parameter does not exist at specified index');
}
return $this->params[$index];
}
/**
* Set route parameter value
* @param string $index Name of URL parameter
* @param mixed $value The new parameter value
* @throws \InvalidArgumentException If route parameter does not exist at index
*/
public function setParam($index, $value)
{
if (!isset($this->params[$index])) {
throw new \InvalidArgumentException('Route parameter does not exist at specified index');
}
$this->params[$index] = $value;
}
/**
* Add supported HTTP method(s)
*/
public function setHttpMethods()
{
$args = func_get_args();
$this->methods = $args;
}
/**
* Get supported HTTP methods
* @return array
*/
public function getHttpMethods()
{
return $this->methods;
}
/**
* Append supported HTTP methods
*/
public function appendHttpMethods()
{
$args = func_get_args();
$this->methods = array_merge($this->methods, $args);
}
/**
* Append supported HTTP methods (alias for Route::appendHttpMethods)
* @return \Slim\Route
*/
public function via()
{
$args = func_get_args();
$this->methods = array_merge($this->methods, $args);
return $this;
}
/**
* Detect support for an HTTP method
* @param string $method
* @return bool
*/
public function supportsHttpMethod($method)
{
return in_array($method, $this->methods);
}
/**
* Get middleware
* @return array[Callable]
*/
public function getMiddleware()
{
return $this->middleware;
}
/**
* Set middleware
*
* This method allows middleware to be assigned to a specific Route.
* If the method argument `is_callable` (including callable arrays!),
* we directly append the argument to `$this->middleware`. Else, we
* assume the argument is an array of callables and merge the array
* with `$this->middleware`. Each middleware is checked for is_callable()
* and an InvalidArgumentException is thrown immediately if it isn't.
*
* @param Callable|array[Callable]
* @return \Slim\Route
* @throws \InvalidArgumentException If argument is not callable or not an array of callables.
*/
public function setMiddleware($middleware)
{
if (is_callable($middleware)) {
$this->middleware[] = $middleware;
} elseif (is_array($middleware)) {
foreach ($middleware as $callable) {
if (!is_callable($callable)) {
throw new \InvalidArgumentException('All Route middleware must be callable');
}
}
$this->middleware = array_merge($this->middleware, $middleware);
} else {
throw new \InvalidArgumentException('Route middleware must be callable or an array of callables');
}
return $this;
}
/**
* Matches URI?
*
* Parse this route's pattern, and then compare it to an HTTP resource URI
* This method was modeled after the techniques demonstrated by Dan Sosedoff at:
*
* http://blog.sosedoff.com/2009/09/20/rails-like-php-url-router/
*
* @param string $resourceUri A Request URI
* @return bool
*/
public function matches($resourceUri)
{
//Convert URL params into regex patterns, construct a regex for this route, init params
$patternAsRegex = preg_replace_callback(
'#:([\w]+)\+?#',
array($this, 'matchesCallback'),
str_replace(')', ')?', (string)$this->pattern)
);
if (substr($this->pattern, -1) === '/') {
$patternAsRegex .= '?';
}
$regex = '#^' . $patternAsRegex . '$#';
if ($this->caseSensitive === false) {
$regex .= 'i';
}
//Cache URL params' names and values if this route matches the current HTTP request
if (!preg_match($regex, $resourceUri, $paramValues)) {
return false;
}
foreach ($this->paramNames as $name) {
if (isset($paramValues[$name])) {
if (isset($this->paramNamesPath[$name])) {
$this->params[$name] = explode('/', urldecode($paramValues[$name]));
} else {
$this->params[$name] = urldecode($paramValues[$name]);
}
}
}
return true;
}
/**
* Convert a URL parameter (e.g. ":id", ":id+") into a regular expression
* @param array $m URL parameters
* @return string Regular expression for URL parameter
*/
protected function matchesCallback($m)
{
$this->paramNames[] = $m[1];
if (isset($this->conditions[$m[1]])) {
return '(?P<' . $m[1] . '>' . $this->conditions[$m[1]] . ')';
}
if (substr($m[0], -1) === '+') {
$this->paramNamesPath[$m[1]] = 1;
return '(?P<' . $m[1] . '>.+)';
}
return '(?P<' . $m[1] . '>[^/]+)';
}
/**
* Set route name
* @param string $name The name of the route
* @return \Slim\Route
*/
public function name($name)
{
$this->setName($name);
return $this;
}
/**
* Merge route conditions
* @param array $conditions Key-value array of URL parameter conditions
* @return \Slim\Route
*/
public function conditions(array $conditions)
{
$this->conditions = array_merge($this->conditions, $conditions);
return $this;
}
/**
* Dispatch route
*
* This method invokes the route object's callable. If middleware is
* registered for the route, each callable middleware is invoked in
* the order specified.
*
* @return bool
*/
public function dispatch()
{
foreach ($this->middleware as $mw) {
call_user_func_array($mw, array($this));
}
$return = call_user_func_array($this->getCallable(), array_values($this->getParams()));
return ($return === false) ? false : true;
}
}

View File

@ -1,257 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* Router
*
* This class organizes, iterates, and dispatches \Slim\Route objects.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class Router
{
/**
* @var Route The current route (most recently dispatched)
*/
protected $currentRoute;
/**
* @var array Lookup hash of all route objects
*/
protected $routes;
/**
* @var array Lookup hash of named route objects, keyed by route name (lazy-loaded)
*/
protected $namedRoutes;
/**
* @var array Array of route objects that match the request URI (lazy-loaded)
*/
protected $matchedRoutes;
/**
* @var array Array containing all route groups
*/
protected $routeGroups;
/**
* Constructor
*/
public function __construct()
{
$this->routes = array();
$this->routeGroups = array();
}
/**
* Get Current Route object or the first matched one if matching has been performed
* @return \Slim\Route|null
*/
public function getCurrentRoute()
{
if ($this->currentRoute !== null) {
return $this->currentRoute;
}
if (is_array($this->matchedRoutes) && count($this->matchedRoutes) > 0) {
return $this->matchedRoutes[0];
}
return null;
}
/**
* Return route objects that match the given HTTP method and URI
* @param string $httpMethod The HTTP method to match against
* @param string $resourceUri The resource URI to match against
* @param bool $reload Should matching routes be re-parsed?
* @return array[\Slim\Route]
*/
public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false)
{
if ($reload || is_null($this->matchedRoutes)) {
$this->matchedRoutes = array();
foreach ($this->routes as $route) {
if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) {
continue;
}
if ($route->matches($resourceUri)) {
$this->matchedRoutes[] = $route;
}
}
}
return $this->matchedRoutes;
}
/**
* Add a route object to the router
* @param \Slim\Route $route The Slim Route
*/
public function map(\Slim\Route $route)
{
list($groupPattern, $groupMiddleware) = $this->processGroups();
$route->setPattern($groupPattern . $route->getPattern());
$this->routes[] = $route;
foreach ($groupMiddleware as $middleware) {
$route->setMiddleware($middleware);
}
}
/**
* A helper function for processing the group's pattern and middleware
* @return array Returns an array with the elements: pattern, middlewareArr
*/
protected function processGroups()
{
$pattern = "";
$middleware = array();
foreach ($this->routeGroups as $group) {
$k = key($group);
$pattern .= $k;
if (is_array($group[$k])) {
$middleware = array_merge($middleware, $group[$k]);
}
}
return array($pattern, $middleware);
}
/**
* Add a route group to the array
* @param string $group The group pattern (ie. "/books/:id")
* @param array|null $middleware Optional parameter array of middleware
* @return int The index of the new group
*/
public function pushGroup($group, $middleware = array())
{
return array_push($this->routeGroups, array($group => $middleware));
}
/**
* Removes the last route group from the array
* @return bool True if successful, else False
*/
public function popGroup()
{
return (array_pop($this->routeGroups) !== null);
}
/**
* Get URL for named route
* @param string $name The name of the route
* @param array $params Associative array of URL parameter names and replacement values
* @throws \RuntimeException If named route not found
* @return string The URL for the given route populated with provided replacement values
*/
public function urlFor($name, $params = array())
{
if (!$this->hasNamedRoute($name)) {
throw new \RuntimeException('Named route not found for name: ' . $name);
}
$search = array();
foreach ($params as $key => $value) {
$search[] = '#:' . preg_quote($key, '#') . '\+?(?!\w)#';
}
$pattern = preg_replace($search, $params, $this->getNamedRoute($name)->getPattern());
//Remove remnants of unpopulated, trailing optional pattern segments, escaped special characters
return preg_replace('#\(/?:.+\)|\(|\)|\\\\#', '', $pattern);
}
/**
* Add named route
* @param string $name The route name
* @param \Slim\Route $route The route object
* @throws \RuntimeException If a named route already exists with the same name
*/
public function addNamedRoute($name, \Slim\Route $route)
{
if ($this->hasNamedRoute($name)) {
throw new \RuntimeException('Named route already exists with name: ' . $name);
}
$this->namedRoutes[(string) $name] = $route;
}
/**
* Has named route
* @param string $name The route name
* @return bool
*/
public function hasNamedRoute($name)
{
$this->getNamedRoutes();
return isset($this->namedRoutes[(string) $name]);
}
/**
* Get named route
* @param string $name
* @return \Slim\Route|null
*/
public function getNamedRoute($name)
{
$this->getNamedRoutes();
if ($this->hasNamedRoute($name)) {
return $this->namedRoutes[(string) $name];
} else {
return null;
}
}
/**
* Get named routes
* @return \ArrayIterator
*/
public function getNamedRoutes()
{
if (is_null($this->namedRoutes)) {
$this->namedRoutes = array();
foreach ($this->routes as $route) {
if ($route->getName() !== null) {
$this->addNamedRoute($route->getName(), $route);
}
}
}
return new \ArrayIterator($this->namedRoutes);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,282 +0,0 @@
<?php
/**
* Slim - a micro PHP 5 framework
*
* @author Josh Lockhart <info@slimframework.com>
* @copyright 2011 Josh Lockhart
* @link http://www.slimframework.com
* @license http://www.slimframework.com/license
* @version 2.4.2
* @package Slim
*
* MIT LICENSE
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Slim;
/**
* View
*
* The view is responsible for rendering a template. The view
* should subclass \Slim\View and implement this interface:
*
* public render(string $template);
*
* This method should render the specified template and return
* the resultant string.
*
* @package Slim
* @author Josh Lockhart
* @since 1.0.0
*/
class View
{
/**
* Data available to the view templates
* @var \Slim\Helper\Set
*/
protected $data;
/**
* Path to templates base directory (without trailing slash)
* @var string
*/
protected $templatesDirectory;
/**
* Constructor
*/
public function __construct()
{
$this->data = new \Slim\Helper\Set();
}
/********************************************************************************
* Data methods
*******************************************************************************/
/**
* Does view data have value with key?
* @param string $key
* @return boolean
*/
public function has($key)
{
return $this->data->has($key);
}
/**
* Return view data value with key
* @param string $key
* @return mixed
*/
public function get($key)
{
return $this->data->get($key);
}
/**
* Set view data value with key
* @param string $key
* @param mixed $value
*/
public function set($key, $value)
{
$this->data->set($key, $value);
}
/**
* Set view data value as Closure with key
* @param string $key
* @param mixed $value
*/
public function keep($key, Closure $value)
{
$this->data->keep($key, $value);
}
/**
* Return view data
* @return array
*/
public function all()
{
return $this->data->all();
}
/**
* Replace view data
* @param array $data
*/
public function replace(array $data)
{
$this->data->replace($data);
}
/**
* Clear view data
*/
public function clear()
{
$this->data->clear();
}
/********************************************************************************
* Legacy data methods
*******************************************************************************/
/**
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* Get data from view
*/
public function getData($key = null)
{
if (!is_null($key)) {
return isset($this->data[$key]) ? $this->data[$key] : null;
} else {
return $this->data->all();
}
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* Set data for view
*/
public function setData()
{
$args = func_get_args();
if (count($args) === 1 && is_array($args[0])) {
$this->data->replace($args[0]);
} elseif (count($args) === 2) {
// Ensure original behavior is maintained. DO NOT invoke stored Closures.
if (is_object($args[1]) && method_exists($args[1], '__invoke')) {
$this->data->set($args[0], $this->data->protect($args[1]));
} else {
$this->data->set($args[0], $args[1]);
}
} else {
throw new \InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`');
}
}
/**
* DEPRECATION WARNING! This method will be removed in the next major point release
*
* Append data to view
* @param array $data
*/
public function appendData($data)
{
if (!is_array($data)) {
throw new \InvalidArgumentException('Cannot append view data. Expected array argument.');
}
$this->data->replace($data);
}
/********************************************************************************
* Resolve template paths
*******************************************************************************/
/**
* Set the base directory that contains view templates
* @param string $directory
* @throws \InvalidArgumentException If directory is not a directory
*/
public function setTemplatesDirectory($directory)
{
$this->templatesDirectory = rtrim($directory, DIRECTORY_SEPARATOR);
}
/**
* Get templates base directory
* @return string
*/
public function getTemplatesDirectory()
{
return $this->templatesDirectory;
}
/**
* Get fully qualified path to template file using templates base directory
* @param string $file The template file pathname relative to templates base directory
* @return string
*/
public function getTemplatePathname($file)
{
return $this->templatesDirectory . DIRECTORY_SEPARATOR . ltrim($file, DIRECTORY_SEPARATOR);
}
/********************************************************************************
* Rendering
*******************************************************************************/
/**
* Display template
*
* This method echoes the rendered template to the current output buffer
*
* @param string $template Pathname of template file relative to templates directory
* @param array $data Any additonal data to be passed to the template.
*/
public function display($template, $data = null)
{
echo $this->fetch($template, $data);
}
/**
* Return the contents of a rendered template file
*
* @param string $template The template pathname, relative to the template base directory
* @param array $data Any additonal data to be passed to the template.
* @return string The rendered template
*/
public function fetch($template, $data = null)
{
return $this->render($template, $data);
}
/**
* Render a template file
*
* NOTE: This method should be overridden by custom view subclasses
*
* @param string $template The template pathname, relative to the template base directory
* @param array $data Any additonal data to be passed to the template.
* @return string The rendered template
* @throws \RuntimeException If resolved template pathname is not a valid file
*/
protected function render($template, $data = null)
{
$templatePathname = $this->getTemplatePathname($template);
if (!is_file($templatePathname)) {
throw new \RuntimeException("View cannot render `$template` because the template does not exist");
}
$data = array_merge($this->data->all(), (array) $data);
extract($data);
ob_start();
require $templatePathname;
return ob_get_clean();
}
}

View File

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

View File

@ -1,279 +0,0 @@
<?php
/**
* A Compatibility library with PHP 5.5's simplified password hashing API.
*
* @author Anthony Ferrara <ircmaxell@php.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2012 The Authors
*/
namespace {
if (!defined('PASSWORD_DEFAULT')) {
define('PASSWORD_BCRYPT', 1);
define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
/**
* Hash the password using the specified algorithm
*
* @param string $password The password to hash
* @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* @param array $options The options for the algorithm to use
*
* @return string|false The hashed password, or false on error.
*/
function password_hash($password, $algo, array $options = array()) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
return null;
}
if (!is_string($password)) {
trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
return null;
}
if (!is_int($algo)) {
trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
return null;
}
$resultLength = 0;
switch ($algo) {
case PASSWORD_BCRYPT:
// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
$cost = 10;
if (isset($options['cost'])) {
$cost = $options['cost'];
if ($cost < 4 || $cost > 31) {
trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
return null;
}
}
// The length of salt to generate
$raw_salt_len = 16;
// The length required in the final serialization
$required_salt_len = 22;
$hash_format = sprintf("$2y$%02d$", $cost);
// The expected length of the final crypt() output
$resultLength = 60;
break;
default:
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
return null;
}
$salt_requires_encoding = false;
if (isset($options['salt'])) {
switch (gettype($options['salt'])) {
case 'NULL':
case 'boolean':
case 'integer':
case 'double':
case 'string':
$salt = (string) $options['salt'];
break;
case 'object':
if (method_exists($options['salt'], '__tostring')) {
$salt = (string) $options['salt'];
break;
}
case 'array':
case 'resource':
default:
trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
return null;
}
if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
return null;
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
$salt_requires_encoding = true;
}
} else {
$buffer = '';
$buffer_valid = false;
if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
$buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
$buffer = openssl_random_pseudo_bytes($raw_salt_len);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && @is_readable('/dev/urandom')) {
$f = fopen('/dev/urandom', 'r');
$read = PasswordCompat\binary\_strlen($buffer);
while ($read < $raw_salt_len) {
$buffer .= fread($f, $raw_salt_len - $read);
$read = PasswordCompat\binary\_strlen($buffer);
}
fclose($f);
if ($read >= $raw_salt_len) {
$buffer_valid = true;
}
}
if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
$bl = PasswordCompat\binary\_strlen($buffer);
for ($i = 0; $i < $raw_salt_len; $i++) {
if ($i < $bl) {
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
} else {
$buffer .= chr(mt_rand(0, 255));
}
}
}
$salt = $buffer;
$salt_requires_encoding = true;
}
if ($salt_requires_encoding) {
// encode string with the Base64 variant used by crypt
$base64_digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
$bcrypt64_digits =
'./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$base64_string = base64_encode($salt);
$salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
}
$salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);
$hash = $hash_format . $salt;
$ret = crypt($password, $hash);
if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
return false;
}
return $ret;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => 10,
* ),
* )
*
* @param string $hash The password hash to extract info from
*
* @return array The array of information about the hash.
*/
function password_get_info($hash) {
$return = array(
'algo' => 0,
'algoName' => 'unknown',
'options' => array(),
);
if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
$return['algo'] = PASSWORD_BCRYPT;
$return['algoName'] = 'bcrypt';
list($cost) = sscanf($hash, "$2y$%d$");
$return['options']['cost'] = $cost;
}
return $return;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* @param string $hash The hash to test
* @param int $algo The algorithm used for new password hashes
* @param array $options The options array passed to password_hash
*
* @return boolean True if the password needs to be rehashed.
*/
function password_needs_rehash($hash, $algo, array $options = array()) {
$info = password_get_info($hash);
if ($info['algo'] != $algo) {
return true;
}
switch ($algo) {
case PASSWORD_BCRYPT:
$cost = isset($options['cost']) ? $options['cost'] : 10;
if ($cost != $info['options']['cost']) {
return true;
}
break;
}
return false;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* @param string $password The password to verify
* @param string $hash The hash to verify against
*
* @return boolean If the password matches the hash
*/
function password_verify($password, $hash) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
return false;
}
$ret = crypt($password, $hash);
if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
return false;
}
$status = 0;
for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
$status |= (ord($ret[$i]) ^ ord($hash[$i]));
}
return $status === 0;
}
}
}
namespace PasswordCompat\binary {
/**
* Count the number of bytes in a string
*
* We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
* In this case, strlen() will count the number of *characters* based on the internal encoding. A
* sequence of bytes might be regarded as a single multibyte character.
*
* @param string $binary_string The input string
*
* @internal
* @return int The number of bytes
*/
function _strlen($binary_string) {
if (function_exists('mb_strlen')) {
return mb_strlen($binary_string, '8bit');
}
return strlen($binary_string);
}
/**
* Get a substring based on byte limits
*
* @see _strlen()
*
* @param string $binary_string The input string
* @param int $start
* @param int $length
*
* @internal
* @return string The substring
*/
function _substr($binary_string, $start, $length) {
if (function_exists('mb_substr')) {
return mb_substr($binary_string, $start, $length, '8bit');
}
return substr($binary_string, $start, $length);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,220 +0,0 @@
<?php
// Validate a user and store token (and return in response).
$app->post('/login', function() use ($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
$expires = ($data->rememberme)
? (2 * 7 * 24 * 60 * 60) /* Two weeks */
: (1.5 * 60 * 60) /* One and a half hours */;
$lookup = R::findOne('user', ' username = ? ', [$data->username]);
$jsonResponse->message = 'Invalid username or password.';
$app->response->setStatus(401);
if (null != $lookup) {
$hash = password_hash($data->password, PASSWORD_BCRYPT, array('salt' => $lookup->salt));
if ($lookup->password == $hash) {
if ($lookup->logins == 0 && $lookup->username == 'admin') {
$jsonResponse->addAlert('warning', "This is your first login, don't forget to change your password.");
$jsonResponse->addAlert('success', 'Go to Settings to add your first board.');
}
setUserToken($lookup, $expires);
$lookup->logins = $lookup->logins + 1;
$lookup->lastLogin = time();
R::store($lookup);
logAction($lookup->username . ' logged in.', null, null);
$jsonResponse->message = 'Login successful.';
$jsonResponse->data = $lookup->token;
$app->response->setStatus(200);
}
}
$app->response->setBody($jsonResponse->asJson());
});
// Log out a user by clearing tokens.
$app->get('/logout', function() use ($app, $jsonResponse) {
if (validateToken()) {
clearDbToken();
$jsonResponse->message = 'Logout complete.';
$actor = getUser();
logAction($actor->username . ' logged out.', null, null);
}
$app->response->setStatus(200); // Doesn't matter if the token was no good.
$app->response->setBody($jsonResponse->asJson());
});
// Update current user's password.
$app->post('/updatepassword', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
if (null != $user) {
$checkPass = password_hash($data->currentPass, PASSWORD_BCRYPT, array('salt' => $user->salt));
if ($user->password == $checkPass) {
$before = $user->export();
$user->password = password_hash($data->newPass, PASSWORD_BCRYPT, array('salt' => $user->salt));
R::store($user);
logAction($user->username . ' changed their password.', $before, $user->export());
$jsonResponse->addAlert('success', 'Password changed.');
} else {
$jsonResponse->addAlert('error', 'Invalid current password. Password not changed.');
}
}
}
$app->response->setBody($jsonResponse->asJson());
});
// Update current user's username if not taken.
$app->post('/updateusername', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$before = $user->export();
$user = updateUsername($user, $data);
R::store($user);
if ($jsonResponse->alerts[0]['type'] == 'success') {
logAction($before['username'] . ' changed username to ' . $user->username, $before, $user->export());
}
$jsonResponse->addBeans(getUsers());
}
$app->response->setBody($jsonResponse->asJson());
});
// Update current user's default board.
$app->post('/updateboard', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
$before = $user->export();
$user->defaultBoard = $data->defaultBoard;
R::store($user);
logAction($user->username . ' changed their default board.', $before, $user->export());
$jsonResponse->addBeans(getUsers());
}
$app->response->setBody($jsonResponse->asJson());
});
// Get all user actions
$app->get('/actions', function() use($app, $jsonResponse) {
if (validateToken(true)) {
$actions = R::findAll('activity', ' order by timestamp desc ');
$jsonResponse->addBeans($actions);
}
$app->response->setBody($jsonResponse->asJson());
});
// Get current user
$app->get('/users/current', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken()) {
$user = getUser();
if (null != $user) {
$jsonResponse->data = [
'userId' => $user->id,
'username' => $user->username,
'isAdmin' => $user->isAdmin,
'defaultBoard' => $user->defaultBoard
];
}
}
$app->response->setBody($jsonResponse->asJson());
});
// Get all users
$app->get('/users', function() use($app, $jsonResponse) {
if (validateToken()) {
$jsonResponse->addBeans(getUsers());
}
$app->response->setBody($jsonResponse->asJson());
});
// Create a new user
$app->post('/users', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$nameTaken = R::findOne('user', ' username = ?', [$data->username]);
if (null != $nameTaken) {
$jsonResponse->addAlert('error', 'Username already in use.');
} else {
$user = R::dispense('user');
$user->username = $data->username;
$user->isAdmin = $data->isAdmin;
$user->defaultBoard = $data->defaultBoard;
$user->salt = password_hash($data->username . time(), PASSWORD_BCRYPT);
$user->password = password_hash($data->password, PASSWORD_BCRYPT, array('salt' => $user->salt));
R::store($user);
addUserToBoard($data->defaultBoard, $user);
$actor = getUser();
logAction($actor->username . ' added user ' . $user->username, null, $user->export());
$jsonResponse->addAlert('success', 'New user ' . $user->username . ' created.');
}
$jsonResponse->addBeans(getUsers());
$jsonResponse->boards = R::exportAll(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});
// Update a user
$app->post('/users/update', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$user = R::load('user', $data->userId);
$actor = getUser();
if ($user->id && $actor->isAdmin) {
$before = $user->export();
if ($data->newUsername != $user->username) {
$user = updateUsername($user, $data);
}
if ($data->password != '' && $data->password != null) {
$user->password = password_hash($data->password, PASSWORD_BCRYPT, array('salt' => $user->salt));
}
$user->isAdmin = $data->isAdmin;
$user->defaultBoard = $data->defaultBoard;
R::store($user);
addUserToBoard($data->defaultBoard, $user);
logAction($actor->username . ' updated user ' . $user->username, $before, $user->export());
$jsonResponse->addAlert('success', 'User updated.');
}
$jsonResponse->addBeans(getUsers());
$jsonResponse->boards = R::exportAll(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});
// Remove a user.
$app->post('/users/remove', function() use($app, $jsonResponse) {
$data = json_decode($app->environment['slim.input']);
if (validateToken(true)) {
$user = R::load('user', $data->userId);
$actor = getUser();
if ($user->id == $data->userId && $actor->isAdmin) {
$before = $user->export();
R::trash($user);
R::exec('DELETE from board_user WHERE user_id = ?', [$data->userId]);
logAction($actor->username . ' removed user ' . $before['username'], $before, null);
$jsonResponse->addAlert('success', 'Removed user ' . $user->username . '.');
}
$jsonResponse->addBeans(getUsers());
$jsonResponse->boards = R::exportAll(getBoards());
}
$app->response->setBody($jsonResponse->asJson());
});

View File

@ -1,22 +0,0 @@
# Dockerfile for Taskboard with nginx and sqlite.
FROM ubuntu:trusty
MAINTAINER Alex van den Hoogen <alex.van.den.hoogen@geodan.nl>
RUN apt-get update && \
apt-get install -yq --no-install-recommends git wget nginx php5-fpm php5-sqlite sqlite3 ca-certificates pwgen && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN echo "cgi.fix_pathinfo = 0;" >> /etc/php5/fpm/php.ini && \
echo "daemon off;" >> /etc/nginx/nginx.conf && \
mkdir -p /var/www
RUN git clone https://github.com/kiswa/TaskBoard.git /var/www && \
chmod 777 $(find /var/www -type d)
ADD nginx.conf /etc/nginx/sites-available/default
EXPOSE 80
CMD service php5-fpm start && nginx

View File

@ -1,35 +0,0 @@
#!/bin/bash
echo Building...
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
echo ' Compiling lib JS files...'
./minify-libs
echo ' Minifying lib CSS files...'
cd ../lib/css/
cat bootstrap.min.css > combined.css
cat font-awesome.min.css >> combined.css
cat jquery-ui.min.css >> combined.css
cat spectrum.css >> combined.css
curl -X POST -s --data-urlencode 'input@combined.css' http://cssminifier.com/raw > combined.min.css
rm combined.css
cd ../../build/
echo ' Compiling app JS files...'
./minify-app
echo ' Minifying app CSS files...'
cd ../css/
curl -X POST -s --data-urlencode 'input@styles.css' http://cssminifier.com/raw > styles.min.css
cd ../build/
echo ' Updating index.html...'
cd ../
if [ -f indexProd.html ]; then
mv index.html indexDev.html
mv indexProd.html index.html
fi
cd build/
echo Build Complete

View File

@ -1,22 +0,0 @@
#!/bin/bash
echo Cleaning...
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
echo ' Cleaning CSS files...'
rm -f ../lib/css/combined.min.css
rm -f ../css/styles.min.css
echo ' Cleaning JS files...'
rm -f ../js/app.min.js
rm -f ../lib/libs.min.js
echo ' Restoring index.html...'
cd ../
if [ -f indexDev.html ]; then
mv index.html indexProd.html
mv indexDev.html index.html
fi
cd build/
echo Clean Complete

Binary file not shown.

View File

@ -1,3 +0,0 @@
#!/bin/bash
cd ../js/
java -jar ../build/compiler.jar -W QUIET --js app.js controllers/*.js services/*.js directives/*.js --js_output_file app.min.js

View File

@ -1,3 +0,0 @@
#!/bin/bash
cd ../lib/
java -jar ../build/compiler.jar -W QUIET --js jquery-1.11.1.min.js jquery-ui.min.js jquery.noty.min.js bootstrap.min.js angular.min.js angular-route.min.js angular-sanitize.min.js ng-context-menu.min.js marked.min.js spectrum.js prefixfree.min.js --js_output_file libs.min.js

View File

@ -1,34 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /var/www;
index index.php index.html;
# Make site accessible from http://localhost/
server_name localhost;
location / {
try_files $uri $uri/ /index.php?$args;
}
location /api {
if (!-e $request_filename) {
rewrite ^(.*)$ /api/api.php last; break;
}
}
location /api/taskboard.db {
rewrite ^(.*)$ /api/api.php last; break;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
}
}

View File

@ -1,654 +0,0 @@
/*
Contents:
#General - Styles that apply throughout.
#Login - Login page specific styles.
#PageTop - Styles for the top bar across pages (includes board navigation).
#SiteNav - Site navigation section styles.
#BoardLayout - Styles specific to the board layout.
#ViewItem - Styles for item view modal.
#SettingsPage - Styles specific to the settings page.
#FilesPage - Styles for the file viewer page.
#3rdParty - Styles for 3rd party widgets (noty, calendar, etc.)
*/
/* #General */
html, body {
height: 100%;width: 100%;
}
body {
background: url(../images/bg.png);
font-family: "Pontano Sans", sans-serif;
font-size: 12pt;
}
th, h1, h2, h3, h4, h5, h6, #board-nav a {
font-family: "Raleway";
font-weight: 500;
}
th {
font-size: 91%;
}
a, button {
transition: background-color 0.3s, color 0.3s;
}
a:hover.fa {
text-decoration: none;
}
.green {
color: green;
}
.red {
color: darkred;
}
.dateNear {
text-shadow: 0px 0px 3px rgba(255, 163, 84, 1);
}
.datePast {
text-shadow: 0px 0px 3px rgba(255, 81, 28, 1);
}
.fa-edit, .fa-trash-o {
cursor: pointer;
}
.pull-up {
margin-top: -10px;
}
#wrapper {
display: table;
table-layout: fixed;
border-spacing: 5px;
width: 100%;
height: 100%;
overflow-x: scroll;
margin: 0;
padding: 0;
}
/* #Login */
.form-signin {
max-width: 350px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
font-weight: normal;
text-align: center;
}
.form-signin .form-signin-heading {
margin-top: .5em;
}
.form-signin .form-control {
position: relative;
height: auto;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
text-align: center;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
text-align: center;
}
.login-container {
background: #fff;
border: 1px solid #ccc;
width: 350px;
height: 275px;
margin: 2em auto;
position: relative;
}
.login-error {
text-align: center;
font-weight: bold;
margin-bottom: -2.1em;
}
.login-container::before, .login-container::after {
content: "";
width: 100%; height: 100%;
position: absolute;
top: 0; left: 0;
background: #ededed;
border: 1px solid #ccc;
z-index: -1;
transform: rotateZ(4deg);
}
.login-container::after {
top: 4px;
z-index: -2;
background: #efefef;
transform: rotateZ(-4deg);
}
/* #PageTop */
#header {
display: table-row;
margin: 0;
}
#header h2 {
border-bottom: 1px solid rgba(51,51,51,.2);
margin-top: 5px;
padding-bottom: 10px;
}
#board-nav {
margin-top: -10px;
overflow: hidden;
}
#board-nav p {
margin-bottom: 2px;
margin-left: 3px;
}
#board-nav .form-control {
height: auto;
padding: 3px 6px;
margin-right: 7px;
}
#board-nav :last-child.form-control {
margin-right: 0;
}
/* #SiteNav*/
.nav {
margin-top: -.7em;
padding: 0;
}
.nav>li.active>a {
cursor: default;
}
.nav>li>a {
border: 1px solid #ccc;
background-color: #eee;
}
.nav>li>a:hover,.nav>li>a:focus {
background-color: #fff;
}
.content {
display: table-row;
height: 100%;
}
/* #BoardLayout */
#board {
overflow-x: auto;
width: 100%;
height: 100%;
/*box-shadow: 0px 0px 7px 0px rgba(51,51,51,.3);
padding: 5px;
border: 1px solid rgba(51,51,51,.3);
background-color: #f8f8f8;
border-radius: 5px;*/
}
#boardColumns {
display: table;
width: 100%;
height: 100%;
}
.boardColumn {
display: table-cell;
overflow: auto;
min-width: 260px;
max-width: 300px;
height: 100%;
margin: 5px;
border: 1px solid rgba(51,51,51,.3);
box-shadow: 0px 0px 3px 0px rgba(51,51,51,.2);
border-radius: 3px;
background-color: #fff;
}
.boardColumn > h3 {
margin: 0;
padding: 5px;
background-color: #e0edf2;
border-bottom: 1px solid rgba(51,51,51,.05);
}
.itemContainer {
height: 94%;
overflow: auto;
}
.filtered {
opacity: 0.4;
}
.boardColumn.collapsed {
min-width: 0;
width: 1.9em;
padding: 0;
margin: 0;
background-color: #e0edf2;
}
.boardColumn.collapsed > h3 {
border: none;
width: 1.5em;
white-space: nowrap;
transform: rotate(90deg) translateX(-30px);
transform-origin: left bottom;
}
.shrink {
float: right;
margin-top: 5px;
cursor: pointer;
}
.expand {
display: none;
cursor: pointer;
transform: translateY(-3px) translateX(-10px);
}
.boardColumn.collapsed .shrink {
display: none;
}
.boardColumn.collapsed .expand {
display: inline-block;
margin-left: 1em;
}
.boardColumn.collapsed .itemContainer {
display: none;
}
.boardColumn.collapsed .badge {
transform: rotate(-90deg) translateX(4px);
}
.boardItem {
background: lightyellow;
padding: 3px;
margin: 5px;
border: 1px solid rgba(51,51,51,.1);
border-radius: 3px;
box-shadow: 1px 1px 2px -1px rgba(51,51,51,.2);
}
.itemHeader {
position: relative;
}
.itemHeader h4 {
border-bottom: 1px dotted rgba(51,51,51,.3);
cursor: move;
padding-bottom: 3px;
margin: 0;
padding-right: 25px;
}
.itemHeader .badge {
position: absolute;
right: 0;
top: 0;
margin: 0;
font-family: "Pontano Sans";
border-radius: 7px;
cursor: default;
}
.boardItem .category {
font-weight: 700;
text-align: right;
display: inline-block;
width: 100%;
padding-right: 3px;
}
.boardItem .description {
margin: 3px;
max-height: 12em;
overflow: auto;
}
.boardItem .assignee {
font-size: .8em;
font-family: Raleway;
}
.boardItem p:last-child {
margin-bottom: 0;
}
.addItem {
margin: 5px;
text-align: right;
}
.draggable-placeholder {
height: 80px;
box-shadow: inset 0px 0px 10px 0px rgba(51,51,51,.25);
border-radius: 5px;
margin: 5px;
}
span.fa {
margin-right: 5px;
}
.lane-placeholder {
width: 100%;
height: 1.2em;
box-shadow: inset 0px 0px 10px 0px rgba(0,0,0,.25);
list-style: none;
}
/* #ViewItem */
.itemViewModal .modal-dialog {
width: 700px;
}
.itemViewModal .modal-content, .view-item-details {
overflow: hidden;
}
.itemViewModal h4 {
padding-top: 1em;
}
.due-date {
font-weight: bold;
font-size: 1.1em;
margin: .9em;
}
.view-item-details {
padding-bottom: .5em;
font-size: 91%;
border: 1px solid rgba(51,51,51,.1);
border-radius: 5px;
}
.view-item-details .description {
margin: 1em;
max-height: 500px;
overflow: auto;
}
.view-item-actions {
overflow: hidden;
padding: .5em 0;
}
.view-item-actions a {
display: block;
}
.alternate {
background-color: rgba(51,51,51,.04);
}
.view-item-attachments .list-group {
margin-bottom: 0;
}
.view-item-comment-form {
overflow: hidden;
}
.detail {
display: block;
font-size: 80%;
margin-left: 1em;
}
p.detail {
margin-top: -10px;
margin-bottom: 0;
}
.links {
margin-top: -2em;
}
.links a {
margin-left: 3px;
}
#add-comment {
margin-top: 5px;
}
.sidebar {
position: fixed;
top: 30px;
left: 0;
width: 240px;
height: 85%;
background: #fff;
border-radius: 0;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
transition: all 0.3s ease;
}
.sidebar .modal-header {
position: fixed;
width: 238px;
}
.sidebar .sidebar-content {
margin-top: 2.55em;
overflow: auto;
height: 93.6%;
}
.sidebar.toggle {
left:-240px;
}
.sidebar .fa {
cursor: pointer;
}
.sidebar .showtab {
border-radius: 0;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
border: 1px solid rgba(0,0,0,0.2);
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
position: fixed;
top: 30px;
left: 0;
width: auto;
}
.showtab.toggle {
display:none;
}
.sidebar .action {
margin: 5px;
}
/* #SettingsPage */
.fa-arrows-v {
cursor: move;
padding: 0 6px 0 5px;
margin: 0 3px 0 0;
border-right: 1px solid #ccc;
}
#widget-container {
/*background-color: #f8f8f8;
box-shadow: 0px 0px 7px 0px rgba(51,51,51,.3);
border-radius: 5px;*/
display: table-cell;
}
.min-padding { padding: 5px; }
.settings-widget {
border: 1px solid rgba(51,51,51,.2);
border-radius: 5px;
background-color: #fff;
box-shadow: 0px 0px 3px 0px rgba(51,51,51,.2);
}
.widget-header, .modal-header {
margin: 0 0 5px 0;
padding: 7px;
border-radius: 5px 5px 0 0;
background-color: #e0edf2;
border-bottom: 1px solid rgba(51,51,51,.05);
}
.widget-content {
margin: 5px;
cursor: auto;
}
.widget-content h4 {
clear: both;
}
.widget-content .list-group-item {
padding: 3px 7px;
background: transparent;
min-width: 125px;
}
.list-group-item.small {
padding: 3px;
}
td > .list-group {
margin: 0;
padding: 0;
}
fieldset > .list-group {
margin-bottom: 10px;
}
#change-password, #default-board, #change-username {
width: 48%;
float: left;
margin-bottom: 1em;
}
#default-board, #change-username {
margin-left: 4%;
margin-bottom: .6em;
}
.badge { margin: 0 3px; }
.half-width {
width: 50%;
float: left;
padding-right: 10px;
margin-right: 0;
}
.half-width > ul {
margin-right: 1em;
}
.list-group-item > a {
margin-right: 5px;
margin-top: 3px;
vertical-align: top;
}
input.custom-inline {
height: auto;
padding: 0 6px;
margin-top: -3px;
}
/* Activity Log slide out */
.slide-menu {
position: fixed;
width: 240px;
height: 100%;
top: 90px;
z-index: 10000;
right: -240px;
transition: all 0.3s ease;
}
.slide-menu-open {
right: 0;
}
.slide-menu > .settings-widget {
height: 85%;
}
.slide-menu .widget-header {
position: fixed;
width: 240px;
}
.slide-menu .widget-content {
margin-top: 3em;
height: 92%;
overflow: auto;
}
.action {
border: 1px solid rgba(51,51,51,.2);
border-radius: 5px;
box-shadow: inset 0px 0px 5px 0px rgba(51,51,51,.2);
background-color: rgba(51,51,51,.005);
padding: 5px;
margin: 0;
margin-bottom: 5px;
}
.action .timestamp {
margin-bottom: -5px;
font-size: .8em;
}
/* #FilesPage */
.files-widget {
border: 1px solid rgba(51,51,51,.2);
border-radius: 5px;
background-color: #fff;
box-shadow: 0px 0px 3px 0px rgba(51,51,51,.2);
height: 100%;
}
.files-widget .small.pull-right {
margin-top: .3em;
}
#iframe-wrapper {
width: 100%;
height: 100%;
}
#iframe-wrapper iframe {
display: block;
border: 1px solid rgba(51,51,51,.3);
width: 100%;
height: 100%;
min-height: 500px;
}
/* #3rdParty */
/* noty notifications */
.noty_bar {
color: #fff;
font-weight: bold;
text-shadow: 0 0 2px #333;
}
.noty_type_error {
background-color: #d9534f;
border-color: #d43f3a;
}
.noty_type_warning {
background-color: #f0ad4e;
border-color: #eea236;
}
.noty_type_information {
background-color: #5bc0de;
border-color: #46b8da;
}
.noty_type_success {
background-color: #5cb85c;
border-color: #4cae4c;
}
.noty_type_notification {
background-color: #428bca;
border-color: #357ebd;
}
/* spectrum color picker */
.sp-replacer {
display: block;
height: 34px;
padding: 6px 12px;
background-color: #fff;
border: solid 1px #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}
.sp-disabled {
background-color: #eee;
}
.sp-preview {
width: 100%;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: rgba(51,51,51,.3);
}
.sp-container {
border: 1px solid #ccc;
background-color: #fff;
border-radius: 4px;
}
.sp-cf:before, .sp-cf:after {
display: none !important;
}
/* jQueryUI Datepicker */
.ui-datepicker {
border: 1px solid rgba(51,51,51,.5);
background-color: #fff;
border-radius: 5px;
}
.ui-datepicker .ui-datepicker-header {
border-bottom: 1px dotted rgba(51,51,51,.5);
}
.ui-datepicker-week-end {
background-color: rgba(51,51,51,.05);
}
.ui-state-highlight {
background-color: rgba(51,51,51,.1);
}
.ui-state-active {
background-color: rgba(51,51,51,.2);
}
.ui-icon-circle-triangle-e,.ui-icon-circle-triangle-w {
text-indent: 0 !important;
cursor: pointer;
}
.ui-icon-circle-triangle-e {
margin-left: -1.2em !important;
}
td a.ui-state-hover {
background-color: rgba(51,51,51,.15);
}

9
docker-compose.yml Normal file
View File

@ -0,0 +1,9 @@
version: "3"
services:
taskboard:
image: "taskboard"
build:
context: .
ports:
- "8081:80"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="icons/mstile-70x70.png"/>
<square150x150logo src="icons/mstile-150x150.png"/>
<square310x310logo src="icons/mstile-310x310.png"/>
<wide310x150logo src="icons/mstile-310x150.png"/>
<TileColor>#2b5797</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,82 +0,0 @@
<!doctype html>
<html lang="en" data-ng-app="TaskBoard">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Matthew Ross">
<meta name="description" content="A Kanban-inspired app for keeping track of things that need to get done.">
<title>TaskBoard {{ display.smallText }}</title>
<link rel="stylesheet" href="lib/css/bootstrap.min.css">
<link rel="stylesheet" href="lib/css/font-awesome.min.css">
<link rel="stylesheet" href="lib/css/jquery-ui.min.css">
<link rel="stylesheet" href="lib/css/jquery-ui.structure.min.css">
<link rel="stylesheet" href="lib/css/spectrum.css">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Pontano+Sans|Raleway:500" data-noprefix>
<link rel="stylesheet" href="css/styles.css">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!--favicons -->
<link rel="shortcut icon" href="favicons/favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="114x114" href="favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="72x72" href="favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="144x144" href="favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="60x60" href="favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="120x120" href="favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="76x76" href="favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="152x152" href="favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="favicons/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="favicons/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="favicons/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="favicons/favicon-32x32.png" sizes="32x32">
<meta name="msapplication-TileColor" content="#2b5797">
<meta name="msapplication-TileImage" content="favicons/mstile-144x144.png">
<meta name="msapplication-config" content="favicons/browserconfig.xml">
</head>
<body>
<div id="wrapper" data-ng-view data-ng-cloak>
</div>
<script src="lib/jquery-1.11.1.min.js"></script>
<script src="lib/jquery-ui.min.js"></script>
<script src="lib/jquery.noty.min.js"></script>
<script src="lib/bootstrap.min.js"></script>
<script src="lib/angular.js"></script>
<script src="lib/angular-route.js"></script>
<script src="lib/angular-sanitize.js"></script>
<script src="lib/ng-context-menu.min.js"></script>
<script src="lib/marked.min.js"></script>
<script src="lib/prefixfree.min.js"></script>
<script src="lib/spectrum.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/boards.js"></script>
<script src="js/controllers/boardsItemForm.js"></script>
<script src="js/controllers/boardsItemView.js"></script>
<script src="js/controllers/files.js"></script>
<script src="js/controllers/settings.js"></script>
<script src="js/controllers/settingsAutoActions.js"></script>
<script src="js/controllers/settingsUser.js"></script>
<script src="js/controllers/settingsUserForm.js"></script>
<script src="js/controllers/settingsBoard.js"></script>
<script src="js/controllers/settingsBoardForm.js"></script>
<script src="js/controllers/login.js"></script>
<script src="js/controllers/header.js"></script>
<script src="js/services/token.js"></script>
<script src="js/services/user.js"></script>
<script src="js/services/board.js"></script>
<script src="js/services/auth.js"></script>
<script src="js/services/alerts.js"></script>
<script src="js/directives/includeReplace.js"></script>
<script src="js/directives/clickToEdit.js"></script>
<script src="js/directives/onLoadCallback.js"></script>
<script src="js/directives/fileUpload.js"></script>
</body>
</html>

View File

@ -1,47 +0,0 @@
<!doctype html>
<html lang="en" data-ng-app="TaskBoard">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Matthew Ross">
<meta name="description" content="A Kanban-inspired app for keeping track of things that need to get done.">
<title>TaskBoard</title>
<link rel="stylesheet" href="lib/css/combined.min.css">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Pontano+Sans|Raleway:500" data-noprefix>
<link rel="stylesheet" href="css/styles.min.css">
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<!--favicons -->
<link rel="shortcut icon" href="favicons/favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="114x114" href="favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="72x72" href="favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="144x144" href="favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="60x60" href="favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="120x120" href="favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="76x76" href="favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="152x152" href="favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="favicons/favicon-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="favicons/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="favicons/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="favicons/favicon-32x32.png" sizes="32x32">
<meta name="msapplication-TileColor" content="#2b5797">
<meta name="msapplication-TileImage" content="favicons/mstile-144x144.png">
<meta name="msapplication-config" content="favicons/browserconfig.xml">
</head>
<body>
<div id="wrapper" data-ng-view data-ng-cloak>
</div>
<script src="lib/libs.min.js"></script>
<script src="js/app.min.js"></script>
</body>
</html>

View File

@ -1,77 +0,0 @@
var taskBoardServices = angular.module('TaskBoardServices', []);
var taskBoardControllers = angular.module('TaskBoardControllers', []);
var taskBoardDirectives = angular.module('TaskBoardDirectives', []);
var taskBoard = angular.module('TaskBoard',
['ngRoute', 'ngSanitize',
'ng-context-menu',
'TaskBoardServices',
'TaskBoardControllers',
'TaskBoardDirectives']);
taskBoard.config(['$routeProvider', '$httpProvider',
function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
controller: 'LoginCtrl',
templateUrl: 'partials/login.html'
}).when('/boards', {
controller: 'BoardCtrl',
templateUrl: 'partials/boardSelect.html',
authRequired: true
}).when('/boards/:boardId', {
controller: 'BoardCtrl',
templateUrl: 'partials/board.html',
authRequired: true,
resolve: {
validation: ['$q', '$route', function($q, $route) {
var deferred = $q.defer(),
id = parseInt($route.current.params.boardId);
if (isNaN(id)) {
deferred.reject('INVALID BOARD ID');
} else {
deferred.resolve();
}
return deferred.promise;
}]
}
}).when('/settings', {
controller: 'SettingsCtrl',
templateUrl: 'partials/settings.html',
authRequired: true
}).when('/files/:fileId', {
controller: 'FilesCtrl',
templateUrl: 'partials/files.html',
authRequired: true
}).otherwise({
redirectTo: '/'
});
// Inject the auth token with each API call.
$httpProvider.interceptors.push('TokenInterceptor');
}]);
// Custom handlers for route authentication and rejection of invalid board id
taskBoard.run(['$rootScope', '$location', '$window', 'AuthenticationService',
function($rootScope, $location, $window, AuthenticationService) {
$rootScope.$on('$routeChangeStart', function(event, nextRoute, currentRoute) {
// Redirect to default path if authentication is required but not present.
if (nextRoute !== null && nextRoute.authRequired !== null &&
nextRoute.authRequired && !AuthenticationService.isAuthenticated &&
!$window.localStorage.token) {
$location.path('/');
}
});
$rootScope.$on('$routeChangeSuccess', function(event, route, previousRoute) {
if (route.controller === 'LoginCtrl' && previousRoute && previousRoute.originalPath !== '') {
AuthenticationService.attemptedRoute = previousRoute;
}
});
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
// Custom rejection from /boards/:boardId route
if (rejection === 'INVALID BOARD ID') {
$location.path('/boards');
}
});
}]);

View File

@ -1,250 +0,0 @@
taskBoardControllers.controller('BoardCtrl',
['$scope', '$routeParams', '$location', '$interval', '$window',
'UserService', 'BoardService', 'AlertService', 'AuthenticationService',
function ($scope, $routeParams, $location, $interval, $window,
UserService, BoardService, AlertService, AuthenticationService) {
// This is here because the BoardCtrl is the default redirect from login.
// If the user was trying to go somewhere else first, they are redirected now.
if (AuthenticationService.attemptedRoute) {
var tmp = AuthenticationService.attemptedRoute,
path = tmp.originalPath;
tmp.keys.forEach(function(key) {
path = path.replace(':' + key.name, tmp.params[key.name]);
});
AuthenticationService.attemptedRoute = null;
$location.path(path);
}
$scope.alerts = AlertService;
$scope.marked = function(text) {
if (text) {
return $window.marked(text);
} else {
return "<p>No Description</p>";
}
};
$scope.boardId = $routeParams.boardId;
$scope.filter = {
user: null,
category: null
};
$scope.filterChanged = function() {
$scope.currentBoard.ownLane.forEach(function (lane) {
if (lane.ownItem) {
lane.ownItem.forEach(function (item) {
item.filtered = false;
if ($scope.filter.user !== null) {
if ($scope.filter.user != item.assignee) {
item.filtered = true;
}
}
if ($scope.filter.category !== null) {
if ($scope.filter.category != item.category) {
item.filtered = true;
}
}
});
}
});
};
$scope.selectBoard = function() {
$location.path('boards/' + $scope.boardNames.current);
};
$scope.openEditItem = function() {
$scope.itemFormData.loadItem($scope.contextItem);
$('.itemModal').modal('show');
};
$scope.openAddItem = function() {
$scope.itemFormData.reset($scope.contextLaneId);
$('.itemModal').modal('show');
};
$scope.removeItem = function() {
$scope.openItem($scope.contextItem, false);
$scope.deleteItem();
};
$scope.contextItem = {}; // Needs to exist prior to onContextMenu call.
$scope.onContextMenu = function(laneId, item) {
$scope.contextItem = item;
$scope.contextLaneId = laneId;
};
// This is called every 250ms until the boards are loaded.
// Once loaded, the repetitive calling is canceled.
// If a default is found the user is redirected to that board.
var checkDefaultBoard = function() {
if ($scope.boardId) {
$interval.cancel($scope.interval);
}
if ($scope.boardsLoaded && !$scope.boardId && $scope.currentUser && parseInt($scope.currentUser.defaultBoard)) {
$interval.cancel($scope.interval);
$location.path('boards/' + $scope.currentUser.defaultBoard);
}
};
$scope.interval = $interval(checkDefaultBoard, 250);
$scope.$on('$destroy', function () { $interval.cancel($scope.interval); });
$scope.boards = [];
$scope.boardsLoaded = false;
$scope.boardNames = [];
$scope.userNames = [];
$scope.laneNames = [];
$scope.categories = [];
$scope.currentBoard = {
loading: true,
name: 'Kanban Board App'
};
var pendingResponse = false,
updateCounter = 0;
$scope.loadBoards = function() {
// Don't update the boards if an update is pending.
if (pendingResponse || updateCounter) {
return;
}
pendingResponse = true;
BoardService.getBoards()
.success(function(data) {
pendingResponse = false;
$scope.updateBoards(data);
});
};
$scope.loadBoards();
$scope.boardInterval = $interval($scope.loadBoards, 10000);
$scope.$on('$destroy', function () { $interval.cancel($scope.boardInterval); });
$scope.updateBoards = function(data) {
// Don't update the boards if a position update is pending.
if (0 !== updateCounter) {
return;
}
$scope.boards = data.data;
$scope.boardsLoaded = true;
var boardFound = false;
if ($scope.boards) {
$scope.boardNames = [];
$scope.boards.forEach(function(board) {
// Add each board's name to the list.
$scope.boardNames.push({id: board.id, name: board.name});
// If the board is the current board, process and assign it.
if (board.id == $scope.boardId) {
board.sharedUser.unshift({ id: 0, username: 'Unassigned' });
board.sharedUser.forEach(function(user) {
$scope.userNames[user.id] = user.username;
});
board.ownLane.forEach(function(lane) {
$scope.laneNames[lane.id] = lane.name;
if (lane.ownItem) {
lane.ownItem.forEach(function(item) {
var date = new Date(item.due_date),
diff = date - Date.now();
if (diff < 0) {
item.datePast = true;
} else if (diff < (1000 * 60 * 60 * 24 * 3)) { // Three days
item.dateNear = true;
}
item.position = parseInt(item.position);
});
}
});
if (board.ownCategory) {
board.ownCategory.unshift({ id: 0, name: 'Uncategorized' });
board.ownCategory.forEach(function(category) {
$scope.categories[category.id] = category.name;
});
}
$scope.currentBoard = board;
$scope.boardNames.current = board.id;
boardFound = true;
}
});
}
if (boardFound) {
$scope.filterChanged(); // Make sure any filters are still applied.
$scope.currentBoard.loading = false;
} else {
$scope.currentBoard.error = true;
}
};
$scope.toggleLane = function(lane) {
lane.collapsed = !lane.collapsed;
updateCounter++;
BoardService.toggleLane(lane.id)
.success(function(data) {
updateCounter--;
$scope.updateBoards(data);
});
};
// This is not the Angular way.
$scope.updateSortables = function() {
var that = this.$parent;
$('.itemContainer').sortable({
connectWith: '.itemContainer',
placeholder: 'draggable-placeholder',
items: 'div:not(.addItem, .itemHeader, .description)',
change: function(event, ui) {
var parent = $(ui).parent(),
addItem = parent.find('.addItem');
addItem.detach();
parent.append(addItem);
},
stop: function(event, ui) {
var lanes = $.find('.boardColumn'),
positionArray = [];
$(lanes).each(function() {
var laneId = $(this).attr('data-lane-id');
$(this).find('.boardItem').each(function(index) {
var itemId = $(this).attr('data-item-id');
positionArray.push({
item: itemId,
lane: laneId,
position: index
});
});
});
that.updatePositions(positionArray);
}
});
};
$scope.updatePositions = function(positionArray) {
updateCounter++;
BoardService.updateItemPositions(positionArray)
.success(function(data) {
updateCounter--;
$scope.updateBoards(data);
});
};
$scope.currentUser = {};
$scope.userLoaded = false;
$scope.updateCurrentUser = function() {
UserService.currentUser()
.success(function(data) {
$scope.userLoaded = true;
$scope.currentUser = data.data;
});
};
$scope.updateCurrentUser();
}]);

View File

@ -1,129 +0,0 @@
taskBoardControllers.controller('ItemFormBoardCtrl',
['$scope', 'BoardService',
function ($scope, BoardService) {
var defaultColor = '#ffffe0';
$scope.itemFormData = {
isSaving: false,
isAdd: true,
itemId: 0,
title: '',
titleError: false,
description: '',
assignee: 0,
category: 0,
lane: 0,
color: defaultColor,
dueDate: null,
points: null,
pointsError: false,
reset: function(laneId) {
$('.popover-dismiss').popover({html:true});
this.isSaving = false;
this.isAdd = true;
this.itemId = 0;
this.title = '';
this.titleError = false;
this.description = '';
this.assignee = 0;
this.category = 0;
this.lane = laneId || 0;
this.color = defaultColor;
this.spectrum(); // Reset the color plugin to default as well.
this.dueDate = null;
this.points = null;
this.pointsError = false;
var that = this;
$('.itemModal').on('hidden.bs.modal', function (e) {
that.reset();
});
},
loadItem: function(item) {
this.reset(item.lane_id);
this.isAdd = false;
this.itemId = item.id;
this.title = item.title;
this.description = item.description;
this.assignee = item.assignee === "0" ? 0 : item.assignee;
this.category = item.category === "0" ? 0 : item.category;
this.color = item.color;
this.spectrum(this.color);
this.dueDate = item.due_date;
this.points = parseInt(item.points);
this.position = item.position;
},
// Uses jQuery to close the modal and reset colorpicker.
cancel: function() {
$('.itemModal').modal('hide');
$('#spectrum').spectrum('hide');
$('#spectrum').spectrum('enable');
},
// Uses jQuery to set the colorpicker.
spectrum: function(color) {
color = color || defaultColor;
$('#spectrum').spectrum({
color: color,
allowEmpty: false,
localStorageKey: 'taskboard.colorPalette',
showPalette: true,
palette: [[]],
showSelectionPalette: true,
showButtons: false,
showInput: true,
preferredFormat: 'hex3',
appendTo: '#addEdit'
});
},
// Uses jQuery to set the datepicker.
datePicker: function() {
$('#datepicker').datepicker();
}
};
$scope.$parent.itemFormData = $scope.itemFormData;
$scope.submitItem = function (itemFormData) {
itemFormData.isSaving = true;
$('#spectrum').spectrum('disable');
itemFormData.titleError = false;
if (itemFormData.title === '') {
$scope.alerts.showAlert({
type:'error',
text: 'Title is required to add a new item.'
});
itemFormData.isSaving = false;
itemFormData.titleError = true;
return;
}
itemFormData.pointsError = false;
if (itemFormData.points === undefined) { // It is undefined if invalid
$scope.alerts.showAlert({
type:'error',
text: 'Points must be greater than or equal to zero (or left empty).'
});
itemFormData.isSaving = false;
itemFormData.pointsError = true;
return;
}
if (itemFormData.isAdd) {
BoardService.addItem(itemFormData, $scope.currentBoard.id)
.success(function(data) {
isSuccess(data);
});
} else {
BoardService.updateItem(itemFormData)
.success(function(data) {
isSuccess(data);
});
}
};
var isSuccess = function(data) {
$scope.alerts.showAlerts(data.alerts);
if (data.alerts[0].type == 'success') {
$scope.updateBoards(data);
$scope.itemFormData.cancel();
}
};
}]);

View File

@ -1,212 +0,0 @@
taskBoardControllers.controller('ItemViewBoardCtrl',
['$scope', 'BoardService',
function ($scope, BoardService) {
$scope.viewItem = {};
$scope.toggle = {
sidebar: false
};
$scope.fileReset = false;
$scope.comments = {
options: [
{id: 0, text:'Oldest First'},
{id: 1, text: 'Newest First'}
],
sorting: 0
};
// Takes an array of timestamps and converts them to display dates.
var convertDates = function(timestampArray) {
if (undefined === timestampArray) {
return;
}
var date = new Date();
timestampArray.forEach(function(item) {
date.setTime(item.timestamp * 1000);
item.date = date.toLocaleString();
});
},
updateItem = function(item) {
$scope.viewItem = item;
$scope.viewItem.laneName = $scope.laneNames[item.lane_id];
};
$scope.openItem = function(item, openModal) {
if (undefined === openModal) {
openModal = true;
}
updateItem(item);
$scope.viewItem.disabled = false;
if (undefined === $scope.viewItem.ownComment) {
$scope.viewItem.ownComment = [];
}
if (undefined === $scope.viewItem.ownAttachment) {
$scope.viewItem.ownAttachment = [];
}
convertDates($scope.viewItem.ownComment);
convertDates($scope.viewItem.ownAttachment);
convertDates($scope.viewItem.ownActivity);
$scope.fileReset = true;
if (openModal) {
$('.itemViewModal').modal({ show: true, keyboard:false });
}
};
$scope.$parent.openItem = $scope.openItem;
$scope.editItem = function() {
$scope.itemFormData.loadItem($scope.viewItem);
$('.itemViewModal').modal('hide');
$('.itemModal').modal('show');
}
$scope.deleteItem = function() {
noty({
text: 'Deleting an item cannot be undone.<br>Continue?',
layout: 'center',
type: 'information',
modal: true,
buttons: [
{
addClass: 'btn btn-default',
text: 'Ok',
onClick: function($noty) {
$noty.close();
$scope.viewItem.disabled = true;
BoardService.removeItem($scope.viewItem.id)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
if (data.alerts[0].type == 'success') {
$scope.updateBoards(data);
$('.itemViewModal').modal('hide');
}
});
}
},
{
addClass: 'btn btn-info',
text: 'Cancel',
onClick: function($noty) {
$noty.close();
}
}
]
});
};
$scope.$parent.deleteItem = $scope.deleteItem;
$scope.deleteComment = function(commentId) {
noty({
text: 'Deleting a comment cannot be undone.<br>Continue?',
layout: 'center',
type: 'information',
modal: true,
buttons: [
{
addClass: 'btn btn-default',
text: 'Ok',
onClick: function($noty) {
$noty.close();
BoardService.removeItemComment($scope.viewItem.id, commentId)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
if (data.alerts[0].type == 'success') {
updateItem(data.data[0]);
}
$scope.loadBoards();
});
}
},
{
addClass: 'btn btn-info',
text: 'Cancel',
onClick: function($noty) {
$noty.close();
}
}
]
});
};
$scope.attachmentDeleting = [];
$scope.deleteAttachment = function(fileId) {
noty({
text: 'Deleting an attachment cannot be undone.<br>Continue?',
layout: 'center',
type: 'information',
modal: true,
buttons: [
{
addClass: 'btn btn-default',
text: 'Ok',
onClick: function($noty) {
$noty.close();
$scope.attachmentDeleting[fileId] = true;
BoardService.removeItemAttachment($scope.viewItem.id, fileId)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
if (data.alerts[0].type == 'success') {
updateItem(data.data[0]);
}
$scope.loadBoards();
});
}
},
{
addClass: 'btn btn-info',
text: 'Cancel',
onClick: function($noty) {
$noty.close();
}
}
]
});
};
$scope.addItemComment = function(comment) {
if (comment === "" || undefined === comment) {
$scope.alerts.showAlert({ type: 'error', text:'Comment cannot be empty.' });
return;
}
$scope.viewItem.disabled = true;
BoardService.addItemComment($scope.viewItem.id, comment)
.success(function(data) {
updateItem(data.data[0]);
$scope.loadBoards();
$scope.viewItem.disabled = false;
});
// Reset input
$scope.comment.text = "";
};
$scope.addItemAttachment = function() {
if ($scope.itemUpload === undefined || $scope.itemUpload.name === '') {
$scope.alerts.showAlert({ type: 'error', text: 'Select a file before uploading.' });
return;
}
$scope.viewItem.disabled = true;
BoardService.addItemAttachment($scope.viewItem.id, $scope.itemUpload)
.success(function(data) {
updateItem(data.data[0]);
$scope.loadBoards();
$scope.viewItem.disabled = false;
// Reset input
$scope.fileReset = true;
})
.error(function(data) {
$scope.viewItem.disabled = false;
$scope.fileReset = true;
console.log(data);
$scope.alerts.showAlert({ type: 'error', text: 'There was an error with your attachment. ' +
'The file may be too large.' });
});
};
}]);

View File

@ -1,27 +0,0 @@
taskBoardControllers.controller('FilesCtrl',
['$scope', '$routeParams', '$http', '$window',
'BoardService',
function ($scope, $routeParams, $http, $window, BoardService) {
$scope.file = {
id: $routeParams.fileId,
loading: true
};
$http.get('api/items/1/upload/' + $scope.file.id)
.success(function(data) {
$scope.file.loading = false;
if (data.data) {
$scope.file.data = data.data[0];
var date = new Date();
date.setTime($scope.file.data.timestamp * 1000);
$scope.file.data.date = date.toLocaleString();
$scope.file.data.filename = 'api/uploads/' + $scope.file.data.filename;
} else {
$scope.file.error = true;
}
})
.error(function(data) {
$scope.file.loading = false;
$scope.file.error = true;
});
}]);

View File

@ -1,48 +0,0 @@
taskBoardControllers.controller('HeaderCtrl',
['$scope', '$window', '$location', 'UserService', 'AuthenticationService', 'AlertService',
function ($scope, $window, $location, UserService, AuthenticationService, AlertService) {
UserService.validateToken()
.error($scope.logout);
$scope.display = {
username: '',
smallText: ' - Settings'
};
$scope.$parent.display = $scope.display;
$scope.$watch('currentUser', function(newValue, oldValue) {
if (undefined !== newValue && undefined !== newValue.username) {
$scope.display.username = '(' + newValue.username + ')';
}
});
$scope.page = {
boards: false,
settings: true
};
if ($location.path().indexOf('boards') > -1) {
$scope.page.boards = true;
$scope.page.settings = false;
$scope.$watch('currentBoard.name', function(newValue, oldValue) {
$scope.display.smallText = ' - ' + newValue;
});
}
if ($location.path().indexOf('files') > -1) {
$scope.page.boards = false;
$scope.page.settings = false;
$scope.display.smallText = ' - File Viewer';
}
try {
$.noty.closeAll(); // Clear any alerts on page load.
} catch(e) {}
$scope.logout = function() {
UserService.logOut()
.then(function(data) {
AuthenticationService.reset();
delete $window.localStorage.token;
$location.path('/');
});
};
}]);

View File

@ -1,38 +0,0 @@
taskBoardControllers.controller('LoginCtrl',
['$rootScope', '$scope', '$location', '$window', 'UserService', 'AuthenticationService', 'AlertService',
function ($rootScope, $scope, $location, $window, UserService, AuthenticationService, AlertService) {
$scope.formdata = {
username: '',
password: '',
rememberme: false
};
$scope.isSaving = false;
// Uses jQuery to handle clearing of any open modals.
$scope.clear = function() {
$('[name~=Modal]').modal('hide');
$('.modal-backdrop').hide();
};
$scope.logIn = function (formdata) {
$scope.errors = [];
$scope.isSaving = true;
UserService.logIn(formdata.username, formdata.password, formdata.rememberme)
.success(function(data) {
if (null !== data.alerts) {
AlertService.showAlerts(data.alerts);
}
AuthenticationService.isAuthenticated = true;
$window.localStorage.token = data.data;
$location.path('/boards');
}).error(function(data, status) {
$scope.isSaving = false;
$scope.errors.push(data.message);
if (status === 503) {
$scope.errors[0] = $scope.errors[0] + ' Ensure api directory is writable.';
}
});
};
}
]);

View File

@ -1,83 +0,0 @@
taskBoardControllers.controller('SettingsCtrl',
['$scope', 'UserService', 'AlertService',
function ($scope, UserService, AlertService) {
$scope.alerts = AlertService;
$scope.users = [];
$scope.boards = [];
$scope.boardNames = [];
$scope.boardLookup = {};
$scope.currentUser = {};
$scope.slide = {
open: false
};
$scope.loadingCurrentUser = true;
$scope.loadingBoards = true;
$scope.loadingUsers = true;
$scope.loadCurrentUser = function() {
UserService.currentUser()
.success(function(data) {
$scope.currentUser = data.data;
$scope.loadingCurrentUser = false;
});
};
$scope.loadCurrentUser();
$scope.updateBoardsList = function(data) {
if (undefined === data) {
return;
}
$scope.loadingBoards = false;
if (null === data) {
$scope.boards = [];
return;
}
$scope.boards = data;
var boardNames = [];
data.forEach(function(board) {
boardNames.push({ 'id': board.id, 'name':board.name });
});
$scope.boardNames = boardNames;
for (var i = 0, len = boardNames.length; i < len; i++) {
$scope.boardLookup[boardNames[i].id] = boardNames[i].name;
}
$scope.updateActions();
};
$scope.updateUsers = function(data) {
if (undefined === data || null === data) {
return;
}
$scope.users = data;
$scope.loadingUsers = false;
$scope.updateActions();
};
$scope.actions = [];
$scope.actionsLoading = true;
$scope.updateActions = function() {
if ('1' !== $scope.currentUser.isAdmin) {
return;
}
UserService.actions()
.success(function(data) {
$scope.actions = data.data;
if ($scope.actions) {
var date = new Date();
$scope.actions.forEach(function(action) {
date.setTime(action.timestamp * 1000);
action.date = date.toLocaleString();
});
}
$scope.actionsLoading = false;
});
};
$scope.updateActions();
}]);

View File

@ -1,286 +0,0 @@
taskBoardControllers.controller('AutomaticActionsCtrl',
['$scope', '$interval', 'BoardService',
function ($scope, $interval, BoardService) {
$scope.loadingActions = true;
$scope.actions = [];
$scope.secondarySelection = [];
$scope.boardCategories = [{ id: 0, name: 'Uncategorized' }];
$scope.userList = [{ id: 0, name: 'Unassigned', username: 'Unassigned' }];
$scope.actionData = {
isSaving: false,
board: null,
trigger: 0,
triggerWord: '',
secondary: null,
action: 0,
color: null,
category: null,
assignee: null
};
$scope.actionDeleting = [];
$scope.actionTypes = [
{ id: 0, action: 'Set item color' },
{ id: 1, action: 'Set item category'},
{ id: 2, action: 'Set item assignee' },
{ id: 3, action: 'Clear item due date' }
];
$scope.actionOptions = {
triggers: [
{
id: 0,
trigger: 'Item moves to column'
},
{
id: 1,
trigger: 'Item assigned to user'
},
{
id: 2,
trigger: 'Item set to category'
}
]
};
var getBoardData = function(boardId) {
if (null === boardId || undefined === boardId)
{
return;
}
var boardData;
$scope.boards.forEach(function(board) {
if (board.id === boardId) {
boardData = board;
}
});
return boardData;
},
getCategories = function(boardData) {
var categories = [{ id: '0', name: 'Uncategorized' }];
if (boardData) {
boardData.ownCategory.forEach(function(category) {
categories.push(category);
});
}
return categories;
},
getUsers = function(boardData) {
var userList = [{ id: '0', name: 'Unassigned', username: 'Unassigned' }];
if (boardData) {
boardData.sharedUser.forEach(function(user) {
userList.push({ id: user.id, name: user.username });
});
}
return userList;
},
getSecondaryText = function(action) {
var text = ': ',
actionBoard = getBoardData(action.board_id),
boardCategories = getCategories(actionBoard),
userList = getUsers(actionBoard);
switch(parseInt(action.trigger_id)) {
case 0: // Lane
actionBoard.ownLane.forEach(function(lane) {
if (lane.id === action.secondary_id) {
text += lane.name;
}
});
break;
case 1: // User
userList.forEach(function(user) {
if (user.id === action.secondary_id) {
text += user.name;
}
});
break;
case 2: // Category
boardCategories.forEach(function(category) {
if (category.id === action.secondary_id) {
text += category.name;
}
});
break;
}
return text;
},
getActionText = function(action) {
var text = '',
actionBoard = getBoardData(action.board_id),
boardCategories = getCategories(actionBoard),
userList = getUsers(actionBoard);
switch(parseInt(action.action_id)) {
case 0: // Color
text = ': ' + action.color;
break;
case 1: // Category
boardCategories.forEach(function(category) {
if (category.id === action.category_id) {
text = ': ' + category.name;
}
});
break;
case 2: // Assignee
userList.forEach(function(user) {
if (user.id === action.assignee_id) {
text = ': ' + user.name;
}
});
break;
}
return text;
},
updateAutoActions = function(actions) {
if (!actions) {
return;
}
var mappedActions = [];
actions.forEach(function(action) {
mappedActions.push({
id: action.id,
board: $scope.boardLookup[action.board_id],
trigger: $scope.actionOptions.triggers[action.trigger_id].trigger + getSecondaryText(action),
action: $scope.actionTypes[action.action_id].action + getActionText(action)
});
});
$scope.actions = mappedActions;
};
$scope.loadActions = function() {
BoardService.getAutoActions()
.success(function(data) {
updateAutoActions(data.data);
$scope.loadingActions = false;
$scope.loadActions();
});
};
// Wait until boards are loaded to load the actions.
$scope.$watch('loadingBoards', function() {
if (!$scope.loadingBoards) {
$scope.loadActions();
}
});
$scope.addAction = function() {
if ($scope.actionData.secondary === null ||
($scope.actionData.action !== 3 &&
($scope.actionData.color === null && $scope.actionData.category === null &&
$scope.actionData.assignee === null))) {
$scope.alerts.showAlert({
type: 'error',
text: 'One or more required fields are not entered. Automatic Action not added.'
});
return;
}
$scope.actionData.isSaving = true;
BoardService.addAutoAction($scope.actionData)
.success(function(data) {
$scope.actionData.isSaving = false;
$scope.alerts.showAlerts(data.alerts);
if (data.alerts[0].type == 'success') {
updateAutoActions(data.data);
}
});
};
$scope.removeAction = function(actionId) {
$scope.actionDeleting[actionId] = true;
BoardService.removeAutoAction(actionId)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
updateAutoActions(data.data);
});
};
$scope.updateSecondary = function() {
$scope.secondarySelection = [];
$scope.actionData.secondary = null;
$scope.actionData.action = 0;
var boardData = getBoardData($scope.actionData.board);
$scope.boardCategories = getCategories(boardData);
$scope.userList = getUsers(boardData);
if (boardData) {
switch($scope.actionData.trigger) {
case 0:
$scope.secondarySelection = boardData.ownLane;
break;
case 1:
$scope.secondarySelection = $scope.userList;
break;
case 2:
$scope.secondarySelection = $scope.boardCategories;
break;
}
}
};
$scope.getTriggerWord = function() {
if ($scope.actionData.trigger !== null) {
var word = $scope.actionOptions.triggers[$scope.actionData.trigger].trigger.split(" ").pop();
$scope.actionData.triggerWord = word.charAt(0).toUpperCase() + word.slice(1);
}
$scope.updateSecondary();
};
$scope.getTriggerWord();
$scope.resetActionSecondary = function() {
$scope.actionData.color = null;
$scope.actionData.category = null;
$scope.actionData.assignee = null;
};
var defaultColor = '#ffffe0';
$scope.spectrum = function(color) {
color = color || defaultColor;
$('#spectrum').spectrum({
color: color,
allowEmpty: false,
localStorageKey: 'taskboard.colorPalette',
showPalette: true,
palette: [[]],
showSelectionPalette: true,
showButtons: false,
showInput: true,
preferredFormat: 'hex3',
disabled: $scope.actionData.board === null
});
};
$scope.updateColorpicker = function() {
if (null !== $scope.actionData.board) {
$('#spectrum').spectrum('enable');
$scope.actionData.color = $('#spectrum').spectrum('option', 'color');
return;
}
$('#spectrum').spectrum('disable');
$scope.actionData.color = null;
};
// Check every 250ms to see if a board has been chosen.
var updateIfBoardChosen = function() {
if ($scope.actionData.board !== null) {
$interval.cancel($scope.interval); // Stop checking once it has.
$scope.getTriggerWord();
}
};
$scope.interval = $interval(updateIfBoardChosen, 250);
$scope.$on('$destroy', function () { $interval.cancel($scope.interval); });
}]);

View File

@ -1,70 +0,0 @@
taskBoardControllers.controller('BoardSettingsCtrl',
['$scope', '$interval', 'BoardService',
function ($scope, $interval, BoardService) {
var pendingResponse = false,
retryCount = 3,
loadBoards = function() {
if (pendingResponse) {
return;
}
pendingResponse = true;
BoardService.getBoards()
.success(function(data) {
$scope.updateBoardsList(data.data);
pendingResponse = false;
retryCount = 3;
}).error(function(data) {
if (retryCount--) {
pendingResponse = false;
return;
}
$interval.cancel($scope.interval);
$scope.$parent.loadingBoards = false;
});
};
loadBoards();
$scope.interval = $interval(loadBoards, 5000);
$scope.$on('$destroy', function () { $interval.cancel($scope.interval); });
$scope.isDeleting = [];
$scope.removeBoard = function(boardId) {
noty({
text: 'Deleting a board cannot be undone.<br>Continue?',
layout: 'center',
type: 'information',
modal: true,
buttons: [
{
addClass: 'btn btn-default',
text: 'Ok',
onClick: function($noty) {
$noty.close();
$scope.boards.forEach(function(board) {
if (board.id == boardId) {
$scope.isDeleting[boardId] = true;
}
});
BoardService.removeBoard(boardId)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.boards = data.data;
$scope.updateUsers(data.users);
});
}
},
{
addClass: 'btn btn-info',
text: 'Cancel',
onClick: function($noty) {
$noty.close();
}
}
]
});
};
}]);

View File

@ -1,215 +0,0 @@
taskBoardControllers.controller('BoardFormSettingsCtrl',
['$scope', 'BoardService',
function ($scope, BoardService) {
$scope.boardFormData = {
boardId: 0,
isAdd: true,
name: '',
lanes: [],
laneName: '',
categories: [],
categoryName: '',
users: [],
nameError: false,
lanesError: false,
categoriesError: false,
isSaving: false,
updateLanesSorting: function() {
var that = this;
$('.lanes').sortable({
placeholder: 'lane-placeholder',
stop: function(event, ui) {
that.lanes.length = 0;
$(ui.item).parent().children().each(function(index){
that.lanes.push({
id: $(this).find('.hidden').text(),
name: $(this).find('.item-text').text(),
position: index
});
});
$scope.$apply();
}
});
},
setBoard: function(board) {
this.reset();
this.isAdd = false;
this.boardId = board.id;
this.name = board.name;
var that = this;
if (undefined !== board.ownLane) {
board.ownLane.forEach(function(lane) {
that.lanes.push({
id: lane.id,
name: lane.name,
position: lane.position
});
});
}
if (undefined !== board.ownCategory) {
board.ownCategory.forEach(function(cat) {
that.categories.push({
id: cat.id,
name: cat.name
});
});
}
if (undefined !== board.sharedUser) {
board.sharedUser.forEach(function(user) {
that.users[user.id] = true;
});
}
this.updateLanesSorting();
},
addLane: function() {
this.lanesError = false;
if (this.laneName === '') {
this.setAlert(false, true, false, 'Lane name cannot be empty.');
return;
}
var that = this;
this.lanes.forEach(function(lane) {
if (that.laneName == lane.name) {
this.setAlert(false, true, false, 'That lane name has already been added.');
}
});
// Add the new lane (if no error) and reset the input.
if (!this.lanesError) {
this.lanes.push({
id: 0,
name: this.laneName,
position: this.lanes.length
});
}
this.laneName = '';
this.updateLanesSorting();
},
removeLane: function(lane) {
if (this.isSaving) { return; }
this.lanes.splice(this.lanes.indexOf(lane), 1);
var pos = 0;
this.lanes.forEach(function(lane) {
lane.position = pos;
pos++;
});
},
addCategory: function() {
this.categoriesError = false;
if (this.categoryName === '') {
this.setAlert(false, false, true, 'Category name cannot be empty.');
return;
}
var that = this;
this.categories.forEach(function(category) {
if (that.categoryName == category) {
this.setAlert(false, false, true, 'That category name has already been added.');
}
});
// Add the new category (if no error) and reset the input.
if (!this.categoriesError) {
this.categories.push({
id: 0,
name: this.categoryName
});
}
this.categoryName = '';
},
removeCategory: function(category) {
if (this.isSaving) { return; }
this.categories.splice(this.categories.indexOf(category), 1);
},
setForSaving: function() {
this.nameError = false;
this.lanesError = false;
this.categoriesError = false;
this.isSaving = true;
},
setAlert: function(name, lane, cat, message) {
this.nameError = name;
this.lanesError = lane;
this.categoriesError = cat;
this.isSaving = false;
$scope.alerts.showAlert({ 'type': 'error', 'text': message });
},
reset: function() {
this.boardId = 0;
this.isAdd = true;
this.name = '';
this.lanes = [];
this.laneName = '';
this.categories = [];
this.categoryName = '';
this.users = [];
this.nameError = false;
this.lanesError = false;
this.categoriesError = false;
this.isSaving = false;
},
// Uses jQuery to close modal and reset form data.
cancel: function() {
$('.boardModal').modal('hide');
var that = this;
$('.boardModal').on('hidden.bs.modal', function (e) {
that.reset();
});
}
};
$scope.$parent.boardFormData = $scope.boardFormData;
$scope.addBoard = function(boardFormData) {
boardFormData.setForSaving();
if (!checkFormInputs(boardFormData)) {
return;
}
BoardService.addBoard(boardFormData)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateBoardsList(data.data);
boardFormData.reset();
if (data.alerts[0].type == 'success') {
$('.boardModal').modal('hide');
}
});
};
$scope.editBoard = function(boardFormData) {
boardFormData.setForSaving();
if (!checkFormInputs(boardFormData)) {
return;
}
BoardService.editBoard(boardFormData)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateBoardsList(data.data);
boardFormData.reset();
if (data.alerts[0].type == 'success') {
$('.boardModal').modal('hide');
}
});
};
var checkFormInputs = function(boardFormData) {
if ('' === boardFormData.name) {
boardFormData.setAlert(true, false, false, 'Board name cannot be empty.');
return false;
}
if (0 === boardFormData.lanes.length) {
boardFormData.setAlert(false, true, false, 'At least one lane is required.');
return false;
}
return true;
};
}]);

View File

@ -1,165 +0,0 @@
taskBoardControllers.controller('UserSettingsCtrl',
['$scope', '$interval', 'UserService',
function ($scope, $interval, UserService) {
var pendingResponse = false,
retryCount = 3,
loadUsers = function() {
if (pendingResponse) {
return;
}
pendingResponse = true;
UserService.getUsers()
.success(function(data) {
$scope.updateUsers(data.data);
pendingResponse = false;
retryCount = 3;
})
.error(function() {
if (retryCount--) {
pendingResponse = false;
return;
}
$interval.cancel($scope.interval);
$scope.$parent.loadingUsers = false;
});
};
loadUsers();
$scope.interval = $interval(loadUsers, 5000);
$scope.$on('$destroy', function () { $interval.cancel($scope.interval); });
$scope.passwordFormData = {
currentPass: '',
newPass: '',
verifyPass: '',
newPassError: false,
currentPassError: false,
isSaving: false,
setForSaving: function() {
this.newPassError = false;
this.currentPassError = false;
this.isSaving = true;
},
setAlert: function(newError, oldError, message) {
this.newPassError = newError;
this.currentPassError = oldError;
this.isSaving = false;
$scope.alerts.showAlert({ 'type': 'error', 'text': message });
},
reset: function() {
this.currentPass = '';
this.newPass = '';
this.verifyPass = '';
this.newPassError = false;
this.currentPassError = false;
this.isSaving = false;
}
};
$scope.changePassword = function(passwordFormData) {
passwordFormData.setForSaving();
if (passwordFormData.currentPass === '') {
passwordFormData.setAlert(false, true, 'Current password cannot be blank.');
} else if (passwordFormData.newPass === '' || passwordFormData.verifyPass === '') {
passwordFormData.setAlert(true, false, 'New password cannot be blank.');
} else {
if(passwordFormData.newPass == passwordFormData.verifyPass) {
UserService.changePassword(passwordFormData.currentPass, passwordFormData.newPass)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
passwordFormData.isSaving = false;
passwordFormData.currentPass = '';
if (data.alerts[0].text == "Password changed.") {
passwordFormData.reset();
} else {
passwordFormData.currentPassError = true;
}
});
} else {
passwordFormData.setAlert(true, false, 'New passwords do not match.');
}
}
};
$scope.usernameFormData = {
newUsername: '',
usernameError: false,
isSaving: false,
setAlert: function(message) {
this.isSaving = false;
this.usernameError = true;
$scope.alerts.showAlert({ 'type': 'error', 'text': message });
},
reset: function() {
this.newUsername = '';
this.usernameError = false;
this.isSaving = false;
}
};
$scope.changeUsername = function(newUsernameFormData) {
$scope.usernameFormData.isSaving = true;
if (newUsernameFormData.newUsername === '') {
newUsernameFormData.setAlert('Username cannot be blank.');
newUsernameFormData.isSaving = false;
} else {
UserService.changeUsername(newUsernameFormData.newUsername)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateUsers(data.data);
$scope.loadCurrentUser();
newUsernameFormData.isSaving = false;
newUsernameFormData.newUsername = '';
});
}
};
$scope.updatingDefaultBoard = false;
$scope.setDefaultBoard = function() {
$scope.updatingDefaultBoard = true;
UserService.changeDefaultBoard($scope.currentUser.defaultBoard)
.success(function(data) {
$scope.updatingDefaultBoard = false;
$scope.updateUsers(data.data);
$scope.alerts.showAlert({ 'type': 'success', 'text': 'Default board changed.' });
});
};
$scope.isDeleting = [];
$scope.removeUser = function(userId) {
noty({
text: 'Deleting a user cannot be undone.<br>Continue?',
layout: 'center',
type: 'information',
modal: true,
buttons: [
{
addClass: 'btn btn-default',
text: 'Ok',
onClick: function($noty) {
$noty.close();
$scope.isDeleting[userId] = true;
UserService.removeUser(userId)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateUsers(data.data);
$scope.updateBoardsList(data.boards);
});
}
},
{
addClass: 'btn btn-info',
text: 'Cancel',
onClick: function($noty) {
$noty.close();
}
}
]
});
};
}]);

View File

@ -1,112 +0,0 @@
taskBoardControllers.controller('UserFormSettingsCtrl',
['$scope', 'UserService',
function ($scope, UserService) {
$scope.userFormData = {
userId: 0,
isAdd: true,
username: '',
password: '',
verifyPass: '',
defaultBoard: null,
isAdmin: false,
passError: false,
usernameError: false,
isSaving: false,
setUser: function(user) {
this.reset();
this.isAdd = false;
this.userId = user.id;
this.username = user.username;
this.defaultBoard = user.default_board;
this.isAdmin = user.is_admin == '1';
},
reset: function() {
$('.popover-dismiss').popover();
this.userId = 0;
this.isAdd = true;
this.username = '';
this.password = '';
this.verifyPass = '';
this.defaultBoard = null;
this.isAdmin = false;
this.passError = false;
this.usernameError = false;
this.isSaving = false;
},
cancel: function() {
$('.userModal').modal('hide');
var that = this;
$('.userModal').on('hidden.bs.modal', function (e) {
that.reset();
});
},
setForSaving: function() {
this.isSaving = true;
this.usernameError = false;
this.passError = false;
},
setAlert: function(user, pass, message) {
this.isSaving = false;
this.usernameError = user;
this.passError = pass;
$scope.alerts.showAlert({ 'type': 'error', 'text': message });
}
};
$scope.$parent.userFormData = $scope.userFormData;
$scope.editUser = function(userFormData) {
userFormData.setForSaving();
if (userFormData.username === '') {
userFormData.setAlert(true, false, 'Username cannot be blank.');
} else {
userFormData.isSaving = true;
if(userFormData.password == userFormData.verifyPass) {
UserService.editUser(userFormData)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateUsers(data.data);
$scope.updateBoardsList(data.boards);
if (data.alerts[0].type == 'success') {
$('.userModal').modal('hide');
userFormData.password = '';
userFormData.verifyPass = '';
}
});
} else {
userFormData.setAlert(false, true, 'Passwords do not match.');
}
}
};
$scope.addUser = function(userFormData) {
userFormData.setForSaving();
if (userFormData.username === '') {
userFormData.setAlert(true, false, 'Username cannot be blank.');
} else if (userFormData.password === '' || userFormData.verifyPass === '') {
userFormData.setAlert(false, true, 'Password cannot be blank.');
} else {
userFormData.isSaving = true;
if(userFormData.password == userFormData.verifyPass) {
UserService.addUser(userFormData)
.success(function(data) {
$scope.alerts.showAlerts(data.alerts);
$scope.updateUsers(data.data);
$scope.updateBoardsList(data.boards);
userFormData.reset();
if (data.alerts[0].type == 'success') {
$('.userModal').modal('hide');
}
});
} else {
userFormData.setAlert(false, true, 'Passwords do not match.');
}
}
};
}]);

View File

@ -1,36 +0,0 @@
taskBoardDirectives.directive('clickToEdit', function() {
return {
restrict: 'A',
replace: true,
templateUrl: 'partials/clickToEdit.html',
scope: {
value: '=clickToEdit'
},
controller: ['$scope', function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editableValue = $scope.value;
$scope.view.editorEnabled = true;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.view.editorEnabled = false;
};
$scope.checkKeypress = function(e) {
if (e.which === 13) { // Enter key
$scope.save();
e.preventDefault();
} else if (e.which === 27) { // Escape key
$scope.view.editorEnabled = false;
e.preventDefault();
}
};
}]
};
});

View File

@ -1,24 +0,0 @@
taskBoardDirectives.directive('fileUpload', ['$parse', function($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var fileModel = $parse(attrs.fileUpload),
resetModel = $parse(attrs.resetFlag);
// When the resetFlag attribute value changes, reset the input.
scope.$watch(resetModel, function(val) {
if (val) {
element[0].value = '';
resetModel.assign(scope, false);
}
});
// Bind the file to the model on change event.
element.bind('change', function() {
scope.$apply(function() {
fileModel.assign(scope, element[0].files[0]);
});
});
}
};
}]);

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