Compare commits

...

741 Commits

Author SHA1 Message Date
Timothy Warren 77ffd46cd8 Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-12-02 15:30:04 -05:00
Timothy Warren 59ba0f49c7 Update clear thumbnails script to work with lots of files 2019-12-02 15:29:24 -05:00
Timothy Warren 44ec41b0f7 Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master There was a failure building this commit Details
2019-10-08 20:25:24 -04:00
Timothy Warren fec671e3cd Catch errors when mapping MAL ids on sync
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-10-08 19:59:47 -04:00
Timothy Warren 848f667626 Misc bugfixes, especially for Anime without a MAL id.
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-10-07 20:10:27 -04:00
Timothy Warren 361ebcbbe4 Update CI stuff
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-16 14:40:15 -04:00
Timothy Warren 6f2a59ae95 Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-08-16 10:39:50 -04:00
Timothy Warren d10e930a6b Remove php 7.4 version in travis config that doesn't exist yet
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-16 10:35:59 -04:00
Timothy Warren c6b74e2775 Minor code cleanup, add newer php version for travis tests
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-16 10:31:31 -04:00
Timothy Warren f3b42ae056 Update base request builder to use the correct user agent
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-10 10:42:02 -04:00
Timothy Warren 9140ebaa19 Add first GraphQL files for Kitsu for future implementation
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-10 10:10:09 -04:00
Timothy Warren 27160bda9a Improve 404 checks for detail pages
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-08-10 10:09:07 -04:00
Timothy Warren 3fae7fe9d6 Update detail pages to use one column for text 2019-08-10 10:07:28 -04:00
Timothy Warren b5ec89de34 Simplify _.show and _.hide useage
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-07-15 16:05:29 -04:00
Timothy Warren 8eca9c9d04 Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-07-15 14:31:17 -04:00
Timothy Warren dfce7f649e Fix scroll to top on list item update
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-07-12 23:12:05 -04:00
Timothy Warren b4a5e8ce77 Remove now unused css file
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-07-12 16:27:39 -04:00
Timothy Warren 1f2accf4ec Add 'automatic' dark theme, based on browser 'prefers-color-scheme: dark' media query
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-07-12 15:56:24 -04:00
Timothy Warren 6e950613e6 Update js dependencies 2019-07-12 13:33:40 -04:00
Timothy Warren 9214b0c87b Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master There was a failure building this commit Details
2019-07-12 13:28:37 -04:00
Timothy Warren 73488d8244 Clean up commands a little bit 2019-07-11 19:03:35 -04:00
Timothy Warren c93629dea2 Show fewer sync errors by filtering common data disparity issues 2019-07-11 16:38:21 -04:00
Timothy Warren 5bf8277376 Fix syncing manga to anilist when you have to create a new list item 2019-07-11 15:24:34 -04:00
Timothy Warren 51bf392d1b Make Anilist missing username error more reliable, allow editing anilist username in settings panel 2019-07-11 10:28:09 -04:00
Timothy Warren dd6e99877a Collection "All Tab", and filtering. Resolves #6, #7 2019-07-10 13:32:05 -04:00
Timothy Warren 4de92a3591 No more genre-related database errors, and other collection improvements 2019-07-10 10:20:37 -04:00
Timothy Warren 2943f716b9 Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-05-08 16:09:29 -04:00
Timothy Warren 62781355b1 Tweak display of descriptions on detail pages
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-05-08 16:08:51 -04:00
Timothy Warren 9e6ce8171a Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-05-08 14:18:49 -04:00
Timothy Warren aa1e6675c2 Use larger cover images for edit forms
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-05-08 14:18:18 -04:00
Timothy Warren 9f585bf1b4 Edit form style tweaks 2019-05-08 14:17:57 -04:00
Timothy Warren ca03e96edd Merge remote-tracking branch 'origin/develop'
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-05-08 13:19:32 -04:00
Timothy Warren 76b23c7646 Fix thumbnail generation command
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-05-08 13:19:03 -04:00
Timothy Warren 82c8fa8661 Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2019-05-08 12:40:32 -04:00
Timothy Warren 74ab8bd8b9 Button and Select style tweaks 2019-05-08 11:14:11 -04:00
Timothy Warren 62e7cc7bed Add polyfill for older browsers, so Opera 12 works 2019-05-08 08:57:15 -04:00
Timothy Warren 1d9537126b Update js sourcemaps 2019-05-08 08:56:26 -04:00
Timothy Warren 2c915188a8 Use static closures in bootstrap 2019-05-08 08:55:58 -04:00
Timothy Warren 765fc9de42 Update css/js dependencies 2019-05-08 08:53:34 -04:00
Timothy Warren b944e1f250 Style tweaks. Fixes #16. 2019-05-08 08:50:57 -04:00
Timothy Warren 92243189ee Fix some edge cases 2019-04-01 16:17:40 -04:00
Timothy Warren 4c896349b9 Remove XML tests 2019-03-12 09:47:59 -04:00
Timothy Warren f3f2879c54 Remove XML codec class 2019-03-12 09:43:17 -04:00
Timothy Warren b70ba1da6f Consistent spacing around auth checks
timw4mail/HummingBirdAnimeClient/PR-21 This commit looks good Details
2019-01-29 16:01:31 -05:00
Timothy Warren 28146ad909 Add a per-controller-method check for authorization for private routes
timw4mail/HummingBirdAnimeClient/PR-21 This commit looks good Details
2019-01-29 15:12:31 -05:00
Timothy Warren 0348d0db00 Cleanup redundant methods in Collection model 2019-01-28 14:31:48 -05:00
Timothy Warren 84ca0a9481 Fix error on attempt to insert a duplicate series
timw4mail/HummingBirdAnimeClient/PR-21 This commit looks good Details
2019-01-22 10:21:58 -05:00
Timothy Warren aec9a2f2b8 Hide missing table error on noninitialized collection, see #20
timw4mail/HummingBirdAnimeClient/PR-21 This commit looks good Details
2019-01-08 15:52:53 -05:00
Timothy Warren 42ec5faa4a Update phinx.yml file for new version of Phinx, see #20
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
timw4mail/HummingBirdAnimeClient/PR-21 This commit looks good Details
2019-01-07 14:31:17 -05:00
Timothy Warren 1c3b478d49 Cleanup database logic a bit 2019-01-07 14:29:15 -05:00
Timothy Warren 5d752f6ee3 Small code cleanup
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2019-01-07 09:08:00 -05:00
Timothy Warren 0503cad15f Simplify/clean up some base classes
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-12-21 15:52:34 -05:00
Timothy Warren 49dc661de1 Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2018-12-13 14:51:01 -05:00
Timothy Warren 59403b9cb5 Make syncing slightly more robust
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-12-12 15:31:59 -05:00
Timothy Warren 4d26acea5b Use the same API client instance across the codebase
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-12-07 10:24:42 -05:00
Timothy Warren a38c9712e6 Don't show media tabs with no media on character page, make stats on user page more resiliant 2018-12-07 10:22:16 -05:00
Timothy Warren b871a4fac2 Remove default API client timeouts, fix time on anime calculation
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-12-06 16:21:02 -05:00
Timothy Warren aacf7ece65 Remove CSS sourcemaps...because they're pointless
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-12-06 13:44:31 -05:00
Timothy Warren 826cb0c1cb Add dark theme with setting toggle
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-12-06 13:04:54 -05:00
Timothy Warren 94e61e35a8 Update misspelled method, somehow resolves #19
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-11-29 11:46:06 -05:00
Timothy Warren f09716b040 Some API client cleanup
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-11-29 11:00:50 -05:00
Timothy Warren 921febaeb4 Fix error in list sync
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-11-27 15:37:16 -05:00
Timothy Warren e3d6ac20ea Update cache dependency
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
timw4mail/HummingBirdAnimeClient/PR-14 This commit looks good Details
2018-11-27 14:57:27 -05:00
Timothy Warren f3c85da8cc Misc code cleanup
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-11-09 10:38:35 -05:00
Timothy Warren 92c5b2baf7 Purge the few inline styles
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-11-08 14:18:24 -05:00
Timothy Warren 9ad74ed887 Remove data transformation from media detail pages, and into the proper transformers
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-11-08 12:15:30 -05:00
Timothy Warren 27977a0c8a Move data transformation out of controllers, and into transformers
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-11-08 11:36:42 -05:00
Timothy Warren 11475187fc Fix generic user page route, minor code cleanup
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-11-07 14:29:21 -05:00
Timothy Warren f88f1578a8 Merge remote-tracking branch 'origin/master' into develop
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-11-05 13:25:42 -05:00
Timothy Warren b9e8ddfc9a Fix collection query
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2018-11-05 13:25:18 -05:00
Timothy Warren 99c94963e6 Merge branch 'develop' of timw4mail/HummingBirdAnimeClient into master
timw4mail/HummingBirdAnimeClient/master This commit looks good Details
2018-11-05 13:15:58 -05:00
Timothy Warren 556e184ce5 More styling tweaks
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-05 11:22:35 -05:00
Timothy Warren c4f759e5d8 Make tables responsive
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-05 11:04:19 -05:00
Timothy Warren 61b7a799b9 Fix broken snapshot test
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-05 10:42:51 -05:00
Timothy Warren 38cd9c74d9 Sort streaming links by service
timw4mail/HummingBirdAnimeClient/PR-12 There was a failure building this commit Details
2018-11-05 10:40:29 -05:00
Timothy Warren 67e068f053 Some visual tweaks
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-05 09:56:38 -05:00
Timothy Warren 24edf55f44 Responsive updates for smaller screen sizes
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-05 09:47:05 -05:00
Timothy Warren 6770c133fb Update picture helper, move anilist oauth calls to the settings controller
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-02 12:58:19 -04:00
Timothy Warren 7a4816d34d Make all the css classes and ids kebob case
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-02 10:48:20 -04:00
Timothy Warren d09908cb1d Update controller test
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
timw4mail/HummingBirdAnimeClient/PR-12 This commit looks good Details
2018-11-01 22:16:45 -04:00
Timothy Warren cd2dcf2873 Various refactoring, better webp image handling 2018-11-01 22:15:20 -04:00
Timothy Warren c2d51b2b7e Make Controllers more specialized 2018-11-01 22:12:41 -04:00
Timothy Warren 067c9b4035 Lots of visual updates 2018-11-01 22:01:09 -04:00
Timothy Warren 3244db3438 More page style tweaks
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-30 13:05:49 -04:00
Timothy Warren fa27abb954 Add tabs to character page sections 2018-10-30 11:42:32 -04:00
Timothy Warren 96820a6418 Fix css for character images on user page
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-30 09:43:54 -04:00
Timothy Warren cf1c55782f Small code consistency update
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-29 15:48:54 -04:00
Timothy Warren bd4cfaafe1 Add staff section on Manga detail pages
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-29 15:17:48 -04:00
Timothy Warren 679c369427 Fix staff section on Anime detail pages, center unusually sized images instead of stretching them
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-29 14:43:06 -04:00
Timothy Warren 0fa54d9f7f Attempt to fix tests again
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-29 10:07:20 -04:00
Timothy Warren 015929b48d Update test snapshots
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-29 09:41:50 -04:00
Timothy Warren 2a6929c6ff Update JsonAPI helper to better handle input data without mangling
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-29 09:39:56 -04:00
Timothy Warren 50b65d66e1 Ugly Progress Commit
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
* Update Person pages to have series organized by character for Voice
Acting
* Miscellaneous style updates
* Add placeholder images for items missing images
2018-10-26 13:08:45 -04:00
Timothy Warren 14be365a16 Refactor some silly switches
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-19 10:40:11 -04:00
Timothy Warren db686dbd8d Fix broken tests 2018-10-19 10:39:38 -04:00
Timothy Warren 16f62ceb8d Miscellaneous page improvements, including additional data and sorting
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-19 09:30:27 -04:00
Timothy Warren 28164f72da Remove need for www subdomain for streaming service mapping
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-17 14:33:16 -04:00
Timothy Warren f243dd4583 Replace switch statement with array mapping
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-17 14:20:07 -04:00
Timothy Warren fccc794528 Fix tests, and category list for Manga detail page
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-16 14:32:52 -04:00
Timothy Warren 12dfb491bc Remove a reference to genres from an older version of the Kitsu API
timw4mail/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-16 14:22:47 -04:00
Timothy Warren 82520afa0e Account for missing genres in anime collection
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-11 16:40:51 -04:00
Timothy Warren 779d7263d0 Update docs a bit
aviat/HummingBirdAnimeClient/develop This commit looks good Details
timw4mail/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-11 13:25:53 -04:00
Timothy Warren 762bdba724 Update shell script for Jenkins
aviat/HummingBirdAnimeClient/develop This commit looks good Details
2018-10-11 13:06:58 -04:00
Timothy Warren 592e9d2c86 Closer to working Jenkins
aviat/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-11 12:50:29 -04:00
Timothy Warren ef33664f6f Try again, for Jenkins
aviat/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-11 12:22:44 -04:00
Timothy Warren 0d84cdb1c1 Maybe third time is a charm for Jenkins?
aviat/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-11 12:08:57 -04:00
Timothy Warren 562e88dc43 Attempt2 with Jenkins
aviat/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-11 11:54:27 -04:00
Timothy Warren 8100f6fd71 Let's do CI with Jenkins
aviat/HummingBirdAnimeClient/develop There was a failure building this commit Details
2018-10-11 11:11:24 -04:00
Timothy Warren 07cae83e15 Default to secure (https) urls 2018-10-11 09:53:14 -04:00
Timothy Warren f7cfcbbced Add console command to re-generate list thumbnails 2018-10-10 16:04:58 -04:00
Timothy Warren ca6954936f Attempt to fix ssl detection 2018-10-10 15:58:28 -04:00
Timothy Warren 4eb258bb2f Fix broken url generator test 2018-10-10 14:26:44 -04:00
Timothy Warren 54b4e11335 Always set the url protocol for the url generator 2018-10-10 14:21:46 -04:00
Timothy Warren aaafebec99 Fix javascript minification 2018-10-10 12:57:11 -04:00
Timothy Warren 5258b215d4 Fix tests 2018-10-09 18:26:42 -04:00
Timothy Warren ee3f3e1743 Update config.toml.example file 2018-10-09 18:21:06 -04:00
Timothy Warren 5b4e1df740 Update README 2018-10-09 18:15:03 -04:00
Timothy Warren fec30b7e36 Full Anilist settings page OAuth flow, ability to run app without manually editing config files. See #7. Resolves #5 2018-10-09 18:10:20 -04:00
Timothy Warren 7a2bb1ba05 Cleanup styles of settings page, cleanup syncing command a bit 2018-10-09 10:11:42 -04:00
Timothy Warren bb9e7b8d49 Fix issue with cache settings 2018-10-08 16:47:40 -04:00
Timothy Warren e4110f089e More settings, now with tabs 2018-10-08 16:38:08 -04:00
Timothy Warren 88b68b847c Settings control panel saves to admin-override.toml in the app/config directory, resolves #7 2018-10-08 15:45:46 -04:00
Timothy Warren b625f22d6a Update gitignore 2018-10-08 15:44:03 -04:00
Timothy Warren f1a54d8782 More webp images, fix login 2018-10-05 22:36:54 -04:00
Timothy Warren 616ae1ea82 Ugly Progress Commit
* Cache and resize images - not just cache them
* Convert to webp on cache
* Show webp images if available
* Settings Form Generation (doesn't yet save)
2018-10-05 21:32:15 -04:00
Timothy Warren 0102a0b3ee Add command to check Kitsu's MAL id mappings 2018-10-05 14:40:30 -04:00
Timothy Warren 789af166ae Update code to use simpler config 2018-10-05 14:32:05 -04:00
Timothy Warren 3098fef20c Merge config.toml and route_config.toml 2018-10-05 14:27:07 -04:00
Timothy Warren 4d29b43123 Fix some api mapping issues for #5 2018-10-01 13:03:48 -04:00
Timothy Warren 5632c0c815 Update header comments to version 4.1 2018-10-01 11:35:51 -04:00
Timothy Warren 6f5db162a0 More work on #5 2018-10-01 10:50:22 -04:00
Timothy Warren dde6c328f4 Fix tests 2018-09-27 16:48:12 -04:00
Timothy Warren e3b4b9dd32 Ugly progress commit 2018-09-27 16:45:12 -04:00
Timothy Warren 9821c17de3 Fix tests 2018-09-26 22:43:04 -04:00
Timothy Warren 2d3295757d Lots of Anilist integration, see #5 2018-09-26 22:31:04 -04:00
Timothy Warren ede69b6099 Rebuild scripts and css 2018-09-20 16:12:28 -04:00
Timothy Warren ca5bfafe88 Anilist CRUD operations for Anime! See #5 2018-09-20 16:08:46 -04:00
Timothy Warren d8db517fa5 Update test snapshots 2018-09-20 11:53:12 -04:00
Timothy Warren 77f2ffa93f Progress with simultaneous updates to Anilist for Anime 2018-09-20 10:41:28 -04:00
Timothy Warren 664a9ec14a JS style updates 2018-09-19 14:11:35 -04:00
Timothy Warren 4284c38e9c Let's do ES modules for browsers that support them 2018-09-14 11:56:48 -04:00
Timothy Warren b19f8965e0 Optimize streaming service logos 2018-09-12 14:20:51 -04:00
Timothy Warren 0fefece3e3 Check config object shape on page load 2018-08-24 14:37:53 -04:00
Timothy Warren 5fe48dfd57 Add Config 'Type', to keep config settings somewhat in check 2018-08-24 14:36:58 -04:00
Timothy Warren 45b545d32d Cleanup config a bit 2018-08-24 14:23:01 -04:00
Timothy Warren 0cb09dc4b8 Fix stupid type error 2018-08-22 13:51:58 -04:00
Timothy Warren 282ba45960 Update file header comments 2018-08-22 13:48:27 -04:00
Timothy Warren 348fe6e724 Eradicate MAL integration 2018-08-22 13:43:04 -04:00
Timothy Warren 153230580e Cleanup javascript into fewer files, add show/description link to search pages 2018-08-22 12:54:06 -04:00
Timothy Warren ca3fa85df3 Update service worker so it only caches images 2018-08-21 17:11:03 -04:00
Timothy Warren 1fa5695a9f Anime Collection improvements
* Allow editing title and alternate title
* Show list of genres on list view of collection
2018-08-21 17:09:42 -04:00
Timothy Warren 807c701edd Getting started with some service workers 2018-08-20 16:24:33 -04:00
Timothy Warren 282022dbd2 Fix test failure 2018-08-20 13:41:25 -04:00
Timothy Warren ba8663b22a Various cleanup, some work on #7 2018-08-20 13:01:16 -04:00
Timothy Warren ce44761420 Remove php js minifier script, in favor of commited js files 2018-08-20 12:58:56 -04:00
Timothy Warren ff97cc1cb2 Add basic check for folder permissions for quicker troubleshooting 2018-08-16 12:10:24 -04:00
Timothy Warren b3474ddff2 More progress on #5 2018-08-15 16:19:07 -04:00
Timothy Warren 64a9f41a64 Making API requests to Anilist, see #5 2018-08-15 14:05:28 -04:00
Timothy Warren 1ab47ca03a More prep for Anilist integration 2018-08-15 08:51:37 -04:00
Timothy Warren 6df059fe75 Merge remote-tracking branch 'origin/develop' 2018-08-14 11:37:43 -04:00
Timothy Warren 79b8c09a9b Remove lines and shading from add forms 2018-08-14 11:36:26 -04:00
Timothy Warren 24ea198dad Adjust layout of edit pages 2018-08-13 15:13:20 -04:00
Timothy Warren 7c7a2d0824 Update test snapshots 2018-08-10 20:11:22 -04:00
Timothy Warren 0ff848c614 More refactoring work, some groundwork for Anilist integration 2018-08-10 20:10:19 -04:00
Timothy Warren 787687abf8 Update some styles 2018-08-10 20:09:28 -04:00
Timothy Warren b4a843e699 Fix test snapshots 2018-08-10 11:01:30 -04:00
Timothy Warren 1dc1370115 Update some types
* Remove empty values from types for serialization, so that empty values
are not sent with API requests
* Allow use of explicit setters for more complex types
2018-08-09 11:34:02 -04:00
Timothy Warren 6f7d94641e Better error handling for incrementing watched count on Anime list 2018-08-09 11:31:15 -04:00
Timothy Warren 3d51a81347 Remove references to MAL syncing, resolves #4 2018-08-09 11:16:44 -04:00
Timothy Warren 6e58844286 More tabs for collections, see issue #2 2018-08-09 11:14:57 -04:00
Timothy Warren 28e4f22d7c First go at tabs for collection 2018-08-08 17:04:35 -04:00
Timothy Warren 7a1967b404 Fix PHP 7.1 test 2018-08-08 13:08:10 -04:00
Timothy Warren 3b3156e78a More refactoring, fix snapshot tests 2018-08-08 13:05:38 -04:00
Timothy Warren d29945eb96 Fix tests 2018-08-08 11:18:57 -04:00
Timothy Warren f04cc7d1d5 Some minor code cleanliness refactoring 2018-08-08 10:12:45 -04:00
Timothy Warren e50ce3fa5b Merge remote-tracking branch 'origin/master' into develop 2018-06-15 08:47:16 -04:00
Timothy Warren e0e72eeef8 Give a better error message on failing to parse an XML API response 2018-06-15 08:46:28 -04:00
Timothy Warren 204313abbf Use more efficient method of combining large sets of data from Kitsu 2018-04-11 09:26:14 -04:00
Timothy Warren c4332d0ccd Merge branch 'master' of timw4mail/HummingBirdAnimeClient into develop 2018-04-05 23:03:43 -04:00
Timothy Warren b078690fdf Add more missing streaming logos 2018-04-05 23:00:58 -04:00
Timothy Warren bc98b977f4 Merge remote-tracking branch 'origin/develop' 2018-04-05 21:56:02 -04:00
Timothy Warren 116b4de204 Update wiki and CI links due to move from Gitlab 2018-04-05 21:20:43 -04:00
Timothy Warren 1df419cde5 Add Hidive to streaming service mapping 2018-04-05 08:56:01 -04:00
Timothy Warren 3c5bac98a1 Add Hidive to streaming service mapping 2018-04-05 08:39:49 -04:00
Timothy Warren 98cabd32af Merge remote-tracking branch 'origin/develop' 2018-02-02 09:53:22 -05:00
Timothy Warren fa2d79c77e Miscellaneous style updates 2018-02-02 09:50:58 -05:00
Timothy Warren 70832f8b63 Minor refactor of Commands 2018-01-31 15:44:48 -05:00
Timothy Warren ad92e51258 Merge remote-tracking branch 'origin/develop' 2018-01-31 11:36:54 -05:00
Timothy Warren 79f6ae8db7 Update test snapshot 2018-01-31 11:00:10 -05:00
Timothy Warren e6b534078f Add trailer videos to anime detail pages 2018-01-31 10:55:20 -05:00
Timothy Warren a6dc8caaa0 Use template literals instead of mustache templates 2018-01-30 16:57:13 -05:00
Timothy Warren 23a6241e2c Merge remote-tracking branch 'origin/develop' 2018-01-30 14:28:07 -05:00
Timothy Warren fe383249e4 Fix issue with anime detail pages 2018-01-30 14:02:28 -05:00
Timothy Warren 322833ff13 Merge remote-tracking branch 'origin/develop' 2018-01-25 19:31:34 -05:00
Timothy Warren dc9d7ab4e1 Lots of style fixes and minor logic tweaks 2018-01-18 16:21:45 -05:00
Timothy Warren c67fbff9ed Update README for requirements 2018-01-16 15:05:17 -05:00
Timothy Warren 32a5ab6492 Remove php 7.0 test 2018-01-16 15:04:25 -05:00
Timothy Warren 9f61123479 Make character layout more closely match anime/manga pages 2018-01-16 14:58:30 -05:00
Timothy Warren 55cabc5840 Code style improvements 2018-01-16 14:58:07 -05:00
Timothy Warren 0f705750e7 Remove strict types from Artax client implementation 2018-01-15 14:49:17 -05:00
Timothy Warren 4a0792e4fe Update copyright year 2018-01-15 14:43:15 -05:00
Timothy Warren f5d8d4f18a Merge branch 'master' into 'develop'
Correct index of id for kitsu sync

See merge request timw4mail/HummingBirdAnimeClient!22
2018-01-11 09:49:43 -05:00
Timothy Warren 7a3ad6ebc6 Correct index of id for kitsu sync 2018-01-10 16:43:49 -05:00
Timothy Warren bd61bd734b Simplify syncing script 2018-01-10 16:34:25 -05:00
Timothy Warren f205ba8cff Merge remote-tracking branch 'origin/master' into develop 2018-01-10 16:28:37 -05:00
Timothy Warren fb8db698a6 Fix some documentation generation issues 2018-01-10 16:24:00 -05:00
Timothy Warren d1987fc710 Handle syncing errors more consistently 2018-01-10 16:18:06 -05:00
Timothy Warren 98283e27d8 Fix bug with sync, remove some code duplication 2017-12-13 11:38:21 -05:00
Timothy Warren 19a032b874 Merge branch 'master' into 'develop'
Merge branch 'develop' into 'master'

See merge request timw4mail/HummingBirdAnimeClient!21
2017-12-11 13:24:30 -05:00
Timothy Warren d3cbe06cb0 Update Artax, and update other code to work with new version 2017-12-08 22:32:00 -05:00
Timothy Warren 1634e98799 Miscellaneous code style changes 2017-12-06 14:40:13 -05:00
Timothy Warren 2bdefd1aa1 Merge branch 'develop' into 'master'
Update master from develop

See merge request timw4mail/HummingBirdAnimeClient!20
2017-12-06 12:36:23 -05:00
Timothy Warren 1ea3a59b6a Correct some errors in creating and deleting anime collection items 2017-12-06 12:30:12 -05:00
Timothy Warren 815bf835d3 Update dependencies 2017-12-06 11:48:15 -05:00
Timothy Warren b5c7d430f4 Make sure to add git... 2017-12-06 11:20:12 -05:00
Timothy Warren 72e68e18ac Don't try to run update, maybe? 2017-12-06 11:16:47 -05:00
Timothy Warren d08ffd1d17 Another attempt to fix gitlab ci builds 2017-12-06 11:12:51 -05:00
Timothy Warren a6e63b07f0 Try, try, again 2017-12-06 11:10:50 -05:00
Timothy Warren 7703187cd9 Attempt to fix gitlab ci build 2017-12-06 11:07:23 -05:00
Timothy Warren 30062ab65d Attempt to test PHP 7.2 2017-12-06 10:58:26 -05:00
Timothy Warren a2f6805417 Merge remote-tracking branch 'origin/master' into develop 2017-12-04 16:07:46 -05:00
Timothy Warren d13ad92dd1 Fix MAL sync issue for anime 2017-12-04 16:06:27 -05:00
Timothy Warren f02a94b862 Fix js minification url 2017-12-04 15:57:13 -05:00
Timothy Warren cce0e714dc Attempt to fix builds 3 2017-10-19 18:38:56 -04:00
Timothy Warren 42117a1386 Attempt to fix builds 2 2017-10-19 18:34:11 -04:00
Timothy Warren b1cae947a7 Attempt to fix builds 2017-10-19 18:31:28 -04:00
Timothy Warren c76f23bffd Merge branch 'master' into develop 2017-10-18 20:00:00 -04:00
Timothy Warren d51ee20abf Fix about section of user page 2017-10-18 19:59:29 -04:00
Timothy Warren 40d3ebcc6f Merge branch 'master' into develop 2017-10-18 19:42:38 -04:00
Timothy Warren 79c6f21a2f Merge branch 'master' of git.timshomepage.net:timw4mail/HummingBirdAnimeClient 2017-10-18 19:42:01 -04:00
Timothy Warren 534079b6e7 Add proper logging to console commands 2017-10-18 19:28:57 -04:00
Timothy Warren fab01c3e89 Fix an issue with Kitsu <-> MAL sync 2017-10-18 19:28:22 -04:00
Timothy Warren c0d912a1c4 Revert former change so that kitsu anime lists are properly pulled for sync 2017-09-15 16:51:47 -04:00
Timothy Warren 23552d62b1 Adding missing method for manga collection 2017-09-15 15:05:35 -04:00
Timothy Warren 4b248eb8cf Better handle empty lists on sync, resolves #29 2017-09-15 15:04:57 -04:00
Timothy Warren f9a667b20b Tweak handling of empty list sections 2017-09-14 17:33:24 -04:00
Timothy Warren 0f94c0b479 Fix an issue fetching anime for the add item call 2017-09-14 17:32:40 -04:00
Timothy Warren d798a057c7 Rough start of Manga collection...need to set up proper structure for manga collection items 2017-09-14 16:18:13 -04:00
Timothy Warren 2d19809ad3 Refactor a bit to prepare for manga collection 2017-09-14 15:32:53 -04:00
Timothy Warren d632cc11e4 Remove PHP 7.2 test that doesn't work 2017-09-12 12:52:56 -04:00
Timothy Warren 318c582fe9 Test with php 7.2 2017-09-12 12:38:26 -04:00
Timothy Warren 26aab3eef6 Fix tests broken by api change fixes 2017-09-12 12:33:57 -04:00
Timothy Warren da2fa371e5 Update some api calls based on api changes 2017-09-12 12:18:31 -04:00
Timothy Warren e808f751e1 Add overlay during update request on list pages, resolves #31 2017-07-12 16:40:56 -04:00
Timothy Warren 8bfc9fcc6e Attempt to re-authenticate when access token expires 2017-06-19 15:31:24 -04:00
Timothy Warren 5ef0ccf9a7 Some minor refactoring 2017-06-19 13:49:28 -04:00
Timothy Warren 90f81fdf7f Update htaccess so images can load 2017-04-28 13:20:59 -04:00
Timothy Warren dd07a441b2 Update readme with another folder that needs to be writable 2017-04-28 13:20:34 -04:00
Timothy Warren 6caa031c86 Further filter titles, showing only the canonical title if it is really long 2017-04-26 10:09:14 -04:00
Timothy Warren e632843bab Merge branch 'develop' into 'master'
Merge develop into master

See merge request !18
2017-04-24 09:28:40 -04:00
Timothy Warren d8b985c993 Kitsu <-> MAL manga list item comparison, resolves #18 2017-04-19 16:48:53 -04:00
Timothy Warren 510ae24dca kitsu <-> mal comparison for anime, see #18 2017-04-19 16:15:39 -04:00
Timothy Warren 06213df29f Miscellaneous code cleanup 2017-04-17 16:13:36 -04:00
Timothy Warren c81e12bb89 More main menu tweaking 2017-04-17 14:49:33 -04:00
Timothy Warren 54c544113b Tweak the main menu a bit 2017-04-17 12:45:29 -04:00
Timothy Warren e714599fad Update dependencies, and set a more locked-down content security policy 2017-04-13 15:08:28 -04:00
Timothy Warren 1df71121eb Use proxy and cached images on user info page 2017-04-13 14:25:39 -04:00
Timothy Warren d62710bdf8 Update manga transformer tests 2017-04-13 11:54:58 -04:00
Timothy Warren 029073a4ea Get images from proxy or cache for Manga views, and Add views 2017-04-13 11:44:03 -04:00
Timothy Warren 1c06748232 All anime images now pull from proxy or cache 2017-04-13 11:26:28 -04:00
Timothy Warren 2d9c5b3093 Css tweaks, and start caching kitsu images 2017-04-13 11:15:16 -04:00
Timothy Warren 33099df6ea Update .gitignore 2017-04-11 13:28:05 -04:00
Timothy Warren 59446649f6 Fix changing a list item's status with no score or progress on MAL 2017-04-11 09:28:07 -04:00
Timothy Warren 4bd62ce881 Fix various edge cases 2017-04-10 15:31:35 -04:00
Timothy Warren e6cfe2b6e9 Use a more appropriate function for substring filtering 2017-04-07 16:58:08 -04:00
Timothy Warren 555de3d17b Tweak handling of alternate titles, to ensure the +1 button is always usable 2017-04-07 16:44:27 -04:00
Timothy Warren ed4f9152d4 Replace 0 with - 2017-04-07 13:57:14 -04:00
Timothy Warren 3f81068182 Update doc generation 2017-04-06 21:27:47 -04:00
Timothy Warren e5e4323486 Make sure Cast heading only shows up if there are actual cast entries 2017-04-06 21:27:03 -04:00
Timothy Warren 5357bfb122 Make sure rating parameter sent to Kitsu is greater than 0 2017-04-06 14:53:38 -04:00
Timothy Warren b444648a3d Use snapshots library with tests to simplify testcases 2017-04-06 11:59:53 -04:00
Timothy Warren e0516e4cc0 Simplify css and javascript minification 2017-04-06 11:45:25 -04:00
Timothy Warren f412aaad27 Update postcss 2017-04-06 10:01:09 -04:00
Timothy Warren 05b8136a1e Add a better API timeout message emoticon 2017-04-05 13:08:16 -04:00
Timothy Warren 853a349a4b Add staff to character pages 2017-04-05 13:02:48 -04:00
Timothy Warren ea2b4fe148 Catch API timeouts 2017-04-05 13:01:51 -04:00
Timothy Warren d435438b20 Merge remote-tracking branch 'origin/develop' 2017-04-03 16:53:25 -04:00
Timothy Warren 255895097a Remove some dead code 2017-04-03 16:53:04 -04:00
Timothy Warren 06a92a5a8e Fix collection images, resolves #26 2017-04-03 16:49:40 -04:00
Timothy Warren 388c0a274d Fix creating missing Kitsu items 2017-04-03 15:46:16 -04:00
Timothy Warren d91665e3ae More basic tests, see #16 2017-04-03 14:46:29 -04:00
Timothy Warren 650ddc781f Merge remote-tracking branch 'origin/develop' 2017-03-31 17:02:26 -04:00
Timothy Warren 9f41603152 small tweak to user page 2017-03-31 17:01:53 -04:00
Timothy Warren 06bc655a59 Details and user page updates, resolves #27 2017-03-31 16:36:22 -04:00
Timothy Warren ca402bd826 Add favorite characters to user page, see #27 2017-03-31 14:15:29 -04:00
Timothy Warren 82c86b7b47 Update detail pages 2017-03-31 13:37:53 -04:00
Timothy Warren 8223786bb5 Remove duplicated function 2017-03-30 16:57:58 -04:00
Timothy Warren aa901550a1 Update header comments 2017-03-30 16:49:48 -04:00
Timothy Warren f8f499520e Allow over-riding the default lists in the user config 2017-03-30 16:47:02 -04:00
Timothy Warren ffd034220c Fix config typo 2017-03-30 16:18:59 -04:00
Timothy Warren 67819156ed Simplify routing code a bit 2017-03-30 16:16:40 -04:00
Timothy Warren e7ef11c423 Add rereading info to manga list 2017-03-30 14:50:25 -04:00
Timothy Warren a767c75014 Make sure rating is only updated if it is numeric 2017-03-29 16:09:22 -04:00
Timothy Warren 9dfc99f49b More tests 2017-03-29 15:14:30 -04:00
Timothy Warren 88c65dd0df Add and delete manga simulaneously from kitsu and mal 2017-03-29 14:25:03 -04:00
Timothy Warren 528deae789 Allow manga +1 button to update both kitsu and mal 2017-03-29 14:00:57 -04:00
Timothy Warren be252836be Fix tests 2017-03-29 13:42:40 -04:00
Timothy Warren 3bb4f32bdb Simultaneously update kitsu and MAL manga list item 2017-03-29 13:29:03 -04:00
Timothy Warren 08a882bbb6 Create missing manga items on kitsu and mal with sync command 2017-03-29 12:32:36 -04:00
Timothy Warren a892d875fd Update sync lists command to create Kitsu items that are missing compared to MAL 2017-03-28 16:52:27 -04:00
Timothy Warren 4f528ca2c8 Minor api model refactoring 2017-03-28 14:36:23 -04:00
Timothy Warren 754a5e7b98 Reorgnize order of Kitsu model methods 2017-03-28 14:34:33 -04:00
Timothy Warren 161c2b3bc1 Update dependencies, use ParallelAPIRequest 2017-03-28 11:01:38 -04:00
Timothy Warren e740b9e314 Merge branch 'develop' into 'master'
Develop

See merge request !17
2017-03-28 10:43:51 -04:00
Timothy Warren 493ac3ca03 Code cleanup and fix 'On Hold' title on all section of anime list 2017-03-27 10:09:45 -04:00
Timothy Warren f59827f95f List characters on manga pages 2017-03-24 10:59:07 -04:00
Timothy Warren bc8822e725 Update all the page titles 2017-03-24 09:58:27 -04:00
Timothy Warren da336876b6 Fix title of anime pages 2017-03-24 09:10:30 -04:00
Timothy Warren f7119a5b0f Show custom 404 pages for missing anime and characters 2017-03-24 09:08:39 -04:00
Timothy Warren f3df8f1588 Remove code coverage ignore annotations 2017-03-24 08:49:39 -04:00
Timothy Warren e7aba2a256 Get rid of whoops 2017-03-23 11:21:13 -04:00
Timothy Warren d6b7075229 Fix 'all' view with missing sections 2017-03-22 16:53:46 -04:00
Timothy Warren 7caa6d254f Merge branch 'master' into 'develop'
Master

Closes #24

See merge request !16
2017-03-22 13:48:01 -04:00
Timothy Warren 71b692c791 Merge branch 'develop' into 'master'
Update favicon with blue version, resolves #24

See merge request !15
2017-03-22 13:17:27 -04:00
Timothy Warren 54cd406187 Update favicon with blue version, resolves #24 2017-03-22 13:12:29 -04:00
Timothy Warren 0d49a3fc93 Merge branch 'master' of git.timshomepage.net:timw4mail/HummingBirdAnimeClient 2017-03-22 12:49:18 -04:00
Timothy Warren 6f643454ef Update streaming link handling, and add daisuki and viewster 2017-03-22 12:29:07 -04:00
Timothy Warren 1fd2dd1146 Fix syntax error, prime manga cache too. See #19 2017-03-22 12:28:19 -04:00
Timothy Warren 857d484b61 Fix php 7.1 build? 2017-03-22 11:43:20 -04:00
Timothy Warren 535de1cf50 Add command to prime cache, see #19 2017-03-22 11:41:25 -04:00
Timothy Warren e84b837dce Fix issue with updating anime item 2017-03-22 11:15:40 -04:00
Timothy Warren a58d654d1d Remove unused mappings 2017-03-22 11:14:59 -04:00
Timothy Warren 40a899066e All anime api calls are now using paginated requests, see #23 2017-03-22 11:13:50 -04:00
Timothy Warren 185416f09d Miscellaneous code cleanup 2017-03-20 19:08:33 -04:00
Timothy Warren 66236129a6 Move link to user profile page 2017-03-20 13:16:01 -04:00
Timothy Warren 0187140e8e Show characters on anime details page 2017-03-20 13:14:01 -04:00
Timothy Warren 39e083d17c Minor model refactoring 2017-03-14 14:28:08 -04:00
Timothy Warren b38d5811a4 More work on user profile page 2017-03-10 12:50:48 -05:00
Timothy Warren c895dfcfbe use readable cache keys 2017-03-10 12:50:29 -05:00
Timothy Warren 4653ce2f42 More work on profile page 2017-03-08 16:21:01 -05:00
Timothy Warren 5605384b6a Fix tests and start on profile page 2017-03-08 13:46:50 -05:00
Timothy Warren 3e68fec704 Add basic character pages 2017-03-08 12:55:49 -05:00
Timothy Warren d7c47f3c1f Update all the header comments with the correct repository url 2017-03-07 20:53:58 -05:00
Timothy Warren 34604d1b7b Fix issues with sync-lists command, add more docblocks 2017-03-07 20:49:31 -05:00
Timothy Warren b6dc104947 Fix 'All' section on Manga page 2017-03-07 18:41:51 -05:00
Timothy Warren 26d339e546 Add back 'All' menu item for anime 2017-03-07 17:51:08 -05:00
Timothy Warren 663258df0d Update gitignore, and make sure cache directory for js minifier exists 2017-03-07 17:48:35 -05:00
Timothy Warren 451c7904d8 Make sure to actually add streaming logos to repo 2017-03-07 15:22:45 -05:00
Timothy Warren 15b6d492f1 Make sure mapping is accurately named
\!
2017-03-03 11:51:53 -05:00
Timothy Warren e4f6db8229 Update README 2017-03-03 11:33:42 -05:00
Timothy Warren c7a75dd2b4 Minor refactor of bootstrap setup 2017-03-03 11:33:32 -05:00
Timothy Warren 5c382db49c Move AnimeWatchingStatus and MangaReadingStatus enums to the same namespace 2017-03-02 11:12:19 -05:00
Timothy Warren df249fd423 Remove accidentially created Java file 2017-03-01 22:11:37 -05:00
Timothy Warren 88f6a3fc4f Rework the rest of the mappings 2017-03-01 22:07:51 -05:00
Timothy Warren 9c7ed16538 Update Manga mappings and enums 2017-03-01 21:52:30 -05:00
Timothy Warren 1f86d4460c Add new mapping class for Anime watching statuses 2017-03-01 20:51:40 -05:00
Timothy Warren 7faaa06079 Merge branch 'develop' into 'master'
Develop

See merge request !14
2017-02-28 16:58:53 -05:00
Timothy Warren 2223243a45 Rename on packagist 2017-02-28 16:47:39 -05:00
Timothy Warren dc879d0511 Attempt to fix travis ci after switch to phpdbg 2017-02-28 14:24:32 -05:00
Timothy Warren f1e5a0a237 Actually install dev dependencies for gitlab ci 2017-02-28 13:52:39 -05:00
Timothy Warren 9ec3e0cd94 Try, try, again 2017-02-28 13:44:41 -05:00
Timothy Warren 7d03a69196 Add phpunit as a dev dependency 2017-02-28 13:39:49 -05:00
Timothy Warren c82898b16c Properly setup test coverage 2017-02-28 13:21:37 -05:00
Timothy Warren 92a1b147fc Simplify gitlab ci setup 2017-02-28 13:17:06 -05:00
Timothy Warren d15710abfa More PHPStan fixes 2017-02-22 15:08:29 -05:00
Timothy Warren d620e44114 PHPStan fixes 2017-02-22 14:46:35 -05:00
Timothy Warren e6dde8c42a Move some README info to the wiki 2017-02-21 15:56:19 -05:00
Timothy Warren 7889849fe2 Update method references in Manga controller 2017-02-21 15:37:29 -05:00
Timothy Warren adecbe1e5c Update method references in Collection controller 2017-02-21 15:36:34 -05:00
Timothy Warren 774f9de80f Simplify database config example 2017-02-21 14:56:10 -05:00
Timothy Warren 59cac2cbb3 Remove old/unused config options from example file 2017-02-21 14:41:59 -05:00
Timothy Warren b5e2b9a8a3 Make sure anime detail pages don't distort images 2017-02-21 12:24:34 -05:00
Timothy Warren 8f9a77c1eb Will teh Gitlab build be triggered? 2017-02-20 15:13:16 -05:00
Timothy Warren 96fce9a6d5 Use new ParallelAPIRequest class 2017-02-20 13:37:08 -05:00
Timothy Warren fdd7f91835 More code style fixes 2017-02-17 11:37:22 -05:00
Timothy Warren 9951053b4f Lots of style fixes, with more to come 2017-02-17 10:55:17 -05:00
Timothy Warren 1f58d7a419 Slightly reorganize model hierarchy 2017-02-17 08:39:27 -05:00
Timothy Warren 58549b7018 Various tweaking 2017-02-17 08:25:19 -05:00
Timothy Warren 856aaec7ca Reference svg logos as image files, not raw html 2017-02-16 14:30:39 -05:00
Timothy Warren 1f6100c367 Yet more snake case to camel case 2017-02-16 14:30:06 -05:00
Timothy Warren 23d78c3b28 More javascript style fixes 2017-02-16 13:22:26 -05:00
Timothy Warren 672aa287dc JS style fixes 2017-02-16 11:47:54 -05:00
Timothy Warren 4def692123 Make sure header comments are actually updated for all code files 2017-02-16 11:09:37 -05:00
Timothy Warren 086b5a9f2d Js snake case to camel case 2017-02-15 16:58:08 -05:00
Timothy Warren dfdc06d048 Rename test base class 2017-02-15 16:40:18 -05:00
Timothy Warren 365da603fa And more snake case to camel case 2017-02-15 16:30:14 -05:00
Timothy Warren b902464446 Update header comments 2017-02-15 16:13:32 -05:00
Timothy Warren 71610b241c Snake case to camel case 2017-02-15 16:11:52 -05:00
Timothy Warren 8c31a3b6d6 More snake case to camel case 2017-02-15 15:56:10 -05:00
Timothy Warren 7648d31e53 More code styles fixes 2017-02-15 15:35:41 -05:00
Timothy Warren 24bba26c38 Javascript style fixes 2017-02-15 14:08:15 -05:00
Timothy Warren e6fb7d4cc4 Snake case to camel case 2017-02-15 14:07:22 -05:00
Timothy Warren 7c33a6ef70 Update headers 2017-02-15 13:08:17 -05:00
Timothy Warren 6652ed7354 More style fixes 2017-02-15 13:07:36 -05:00
Timothy Warren a04643ea10 Fix more code style issues 2017-02-15 11:57:29 -05:00
Timothy Warren 30b398c946 Properly namespace all the tests 2017-02-15 11:49:38 -05:00
Timothy Warren e5db6bebe8 Snake case to camel case 2017-02-15 11:30:16 -05:00
Timothy Warren 506cce02cf Remove some unused code 2017-02-15 11:18:55 -05:00
Timothy Warren 325d391c2e Add yarn lock file 2017-02-15 11:05:03 -05:00
Timothy Warren 3e4e76cf0f Try, try again 2017-02-15 10:23:07 -05:00
Timothy Warren 8a789e0786 Slim build config 2017-02-15 10:12:18 -05:00
Timothy Warren ed13df885b Ignore stupid xsl requirement 2017-02-15 10:05:06 -05:00
Timothy Warren e3cc53e23d Install xsl because of the stupid dev dependency 2017-02-15 09:57:08 -05:00
Timothy Warren 1a8042191f Will xdebug work? 2017-02-15 09:53:46 -05:00
Timothy Warren 77c314dd0f Will xdebug work? 2017-02-15 09:50:13 -05:00
Timothy Warren 5e1600d35d xdebug try again 2017-02-15 09:47:52 -05:00
Timothy Warren c77afcb580 Try to use xdebug another way 2017-02-15 09:42:20 -05:00
Timothy Warren 3778a8e6de Make sure to try to install the correct packages 2017-02-15 09:36:51 -05:00
Timothy Warren b4c8cafde7 Remove unneeded bashism 2017-02-15 09:32:31 -05:00
Timothy Warren 874a1cdfee Maybe this will work better? 2017-02-15 09:30:27 -05:00
Timothy Warren fa889ad7fc Maybe stages will help? 2017-02-15 09:26:49 -05:00
Timothy Warren c733a892fd Attempt to use alpine php image 2017-02-15 09:21:08 -05:00
Timothy Warren d88220d9f7 Attempt testing hhvm with a different docker image 2017-02-15 08:55:51 -05:00
Timothy Warren a29feae442 Merge branch 'develop' into 'master'
Develop

See merge request !13
2017-02-14 16:39:37 -05:00
Timothy Warren 28584be828 Add hummingbird favicon 2017-02-14 16:23:18 -05:00
Timothy Warren 0d553b7dd4 Get sync-lists command to create missing entries on MAL 2017-02-14 15:29:13 -05:00
Timothy Warren 9c1dc50e65 Uncomment rewatching stuff 2017-02-13 13:33:01 -05:00
Timothy Warren cfa23b8066 Fix mapping from Kitsu to MAL for updating a list item 2017-02-13 12:42:05 -05:00
Timothy Warren 8ddd2f5fd5 Attempt to fix hhvm tests 2017-02-10 16:33:42 -05:00
Timothy Warren fb1af97bae Fix config mapping for BaseCommand 2017-02-10 16:12:02 -05:00
Timothy Warren 0b04c22b58 Split user config from application config 2017-02-10 15:50:07 -05:00
Timothy Warren e2aa61f580 Fix update requests broken by Artax conversion 2017-02-09 20:10:13 -05:00
Timothy Warren c9f74dd863 Remove 'fix' for issue caused by php.ini setting 2017-02-09 13:45:40 -05:00
Timothy Warren c5b51054df Replace Guzzle with Artax 2017-02-09 13:44:56 -05:00
Timothy Warren 0e9c257ab7 Another ugly progress commit
- Eradicated Guzzle from main codebase
- All API requests now use Artax
- Refactor code to use function and constant imports
- And more!
2017-02-08 15:48:20 -05:00
Timothy Warren 5aafbc9cb2 Start of work to replace Guzzle with Artax 2017-02-08 00:44:57 -05:00
Timothy Warren 5f0f830aea Update headers and some whitespace 2017-02-07 13:27:41 -05:00
Timothy Warren 20540963ff Create Request Builder wrapper around Artax 2017-02-07 13:11:42 -05:00
Timothy Warren 76c9adbc43 Update PHPUnit 2017-02-07 09:13:13 -05:00
Timothy Warren 5c5b1cd318 Only translate fields that are passed in 2017-02-07 09:12:44 -05:00
Timothy Warren f41cbf70e3 Merge branch 'develop' into 'master'
Develop

See merge request !12
2017-02-06 11:51:58 -05:00
Timothy Warren 6d735ebab5 Update test config to allow hhvm failures 2017-02-06 11:35:21 -05:00
Timothy Warren b4de2cfe01 Update deprecated test 2017-02-06 11:00:18 -05:00
Timothy Warren 0c70bcb948 Update changelog 2017-02-06 10:57:38 -05:00
Timothy Warren 3dbbcd218a Update README 2017-02-06 10:56:27 -05:00
Timothy Warren 093abdb14b Update dependencies 2017-02-06 10:42:41 -05:00
Timothy Warren a48c3e5f8e Actually update MAL if enabled 2017-02-04 15:18:34 -05:00
Timothy Warren 47bfe810fc Able to create list items on MAL 2017-02-01 09:53:02 -05:00
Timothy Warren 82e0078dbf Delete outdated test data 2017-01-31 12:55:28 -05:00
Timothy Warren c38b4b7f45 Misc updates 2017-01-31 12:52:43 -05:00
Timothy Warren da4163bc6a More test coverage for transformers 2017-01-31 12:51:14 -05:00
Timothy Warren f697e3e18e Add license 2017-01-27 16:34:03 -05:00
Timothy Warren e2e51a0025 Add coverage button to readme 2017-01-27 15:56:40 -05:00
Timothy Warren ad4b71ac03 More test coverage, attempt to get Gitlab to see test coverage 2017-01-27 15:41:52 -05:00
Timothy Warren 627c33356c Merge branch 'develop' into 'master'
Develop

See merge request !10
2017-01-27 13:03:53 -05:00
Timothy Warren 17372bafd2 Attempt to show code coverage 2017-01-27 12:42:57 -05:00
Timothy Warren ef6d0f82fd Ugly progress commit 2017-01-27 12:35:28 -05:00
Timothy Warren b800dcb407 Add back zlib to the docker build 2017-01-27 12:26:32 -05:00
Timothy Warren 28fcdd1e2d Add back dependency for xsl in docker build 2017-01-27 12:17:02 -05:00
Timothy Warren df09e27dee Attempt simpler php setup, with xdebug 2017-01-27 12:09:05 -05:00
Timothy Warren 57b103c136 Another attempt at hhvm testing 2017-01-27 11:38:18 -05:00
Timothy Warren 780b1ae837 Attempt to fix hhvm pipeline 2017-01-27 11:21:25 -05:00
Timothy Warren 3f90e93af1 Get xml parsing working predictably 2017-01-27 09:43:42 -05:00
Timothy Warren 192618b890 Make sure to pass the correct arguments to the cache hash method 2017-01-26 13:06:35 -05:00
Timothy Warren 04e8a9e514 Refactor KitsuModel, add more docblocks 2017-01-26 13:03:38 -05:00
Timothy Warren 005014a9c7 Fix broken test 2017-01-26 13:02:18 -05:00
Timothy Warren b8f75e14ec Fix display of streaming links in cover and list views 2017-01-25 13:37:39 -05:00
Timothy Warren 833cd0f7e3 Merge branch 'develop' into 'master'
Develop

See merge request !9
2017-01-25 12:16:50 -05:00
Timothy Warren 1818bf105d Fix issue with selected list highlighting, fixes #20 2017-01-25 12:13:37 -05:00
Timothy Warren 5f6c1f9f48 Fix cache clear command 2017-01-19 12:49:18 -05:00
Timothy Warren 2505df6501 Merge branch 'develop' 2017-01-17 12:52:02 -05:00
Timothy Warren 94bfdebf46 Update commands 2017-01-17 12:47:02 -05:00
Timothy Warren 2c106f607c Update cache dependency 2017-01-17 12:46:47 -05:00
Timothy Warren 95f768aed3 Cache manga list 2017-01-16 14:14:45 -05:00
Timothy Warren 5c34c92453 Fix failing test 2017-01-16 14:05:42 -05:00
Timothy Warren 07c4682711 Update views for collection, remove old json import 2017-01-16 14:03:30 -05:00
Timothy Warren 3dac007390 Fix anime collection 2017-01-16 13:49:51 -05:00
Timothy Warren 747dc66d67 Update README to be more accurate 2017-01-16 12:42:30 -05:00
Timothy Warren 27895a27be Merge branch 'develop' 2017-01-16 11:27:49 -05:00
Timothy Warren a266c68f9b Restore cache clearing functionality 2017-01-16 11:26:19 -05:00
Timothy Warren d79370deb9 Streaming links, caching, and more MAL integration 2017-01-13 16:53:56 -05:00
Timothy Warren 68bee55f6a Cache API errors at the dispatcher level, so a more appropriate error page can be displayed 2017-01-13 16:52:12 -05:00
Timothy Warren 29d8207c63 Update search to bring in My anime list id for future integration 2017-01-13 16:51:31 -05:00
Timothy Warren abfa97d99c Update views to show streaming links 2017-01-13 16:49:46 -05:00
Timothy Warren 76c5e50b0e Update transformer tests 2017-01-13 16:48:08 -05:00
Timothy Warren 270f9ab167 Really ugly progress commit 2017-01-12 15:41:20 -05:00
Timothy Warren f10b0f5284 Update Changelog and Readme 2017-01-12 11:31:49 -05:00
Timothy Warren adbed10173 Add test data files 2017-01-11 22:27:36 -05:00
Timothy Warren 2e3cc1837b Update config files 2017-01-11 22:26:43 -05:00
Timothy Warren 951187783b Third time's a charm for updating the header comment? 2017-01-11 19:37:14 -05:00
Timothy Warren e6e9c9424b Start of integration with My Anime List 2017-01-11 19:35:51 -05:00
Timothy Warren 3f34ecee28 Update header comments, with proper newlines 2017-01-11 10:34:24 -05:00
Timothy Warren 0644c40b1c Merge branch 'develop' into 'master'
Replace Hummingbird with Kitsu

See merge request !8
2017-01-11 10:32:10 -05:00
Timothy Warren 036396a382 Update header comments 2017-01-11 10:31:17 -05:00
Timothy Warren 7be02bb292 Merge branch 'master' into 'develop'
Hummingbird to Kitsu

See merge request !7
2017-01-11 10:25:43 -05:00
Timothy Warren 8d559dc664 Fix unit tests 2017-01-10 21:13:44 -05:00
Timothy Warren a5dd9f0650 All basic API functionality:
* Anime List Item:
	* Creation
	* Updating
	* Retreiving
	* Deletion

* Manga List Item:
	* Creation
	* Updating
	* Retreiving
	* Deletion

* Anime detail page
* Manga detail page
2017-01-10 12:35:46 -05:00
Timothy Warren 5fc70bb4fe Fix some javascript issues 2017-01-09 21:38:42 -05:00
Timothy Warren 08b4227b34 Anime and Manga editing, incrementing, and deletion 2017-01-09 20:36:48 -05:00
Timothy Warren 187812576c Update header comments 2017-01-06 23:34:56 -05:00
Timothy Warren 8412588940 Episode incrementing and update work for anime 2017-01-06 21:39:01 -05:00
Timothy Warren 4c75701c0d Better handling of alternate titles, Airing Status and genres for anime list views 2017-01-05 22:24:45 -05:00
Timothy Warren 9eda005399 Authentication, show edit forms for Anime 2017-01-05 13:41:32 -05:00
Timothy Warren e6c96bed21 Update postcss to actually output compatible css 2017-01-04 13:51:04 -05:00
Timothy Warren 9c8df03c36 Update css to fit blocks within poster images 2017-01-04 13:40:46 -05:00
Timothy Warren 609ba57078 Manga lists and detail pages 2017-01-04 13:16:58 -05:00
Timothy Warren 772aeae20f Start of changes for Manga list 2017-01-03 21:06:49 -05:00
Timothy Warren 9bd5d62ca7 Remove some old code to better make way for kitsu/MAL api integration 2017-01-03 20:29:43 -05:00
Timothy Warren a92acd203d Another ugly progress commit
Sort of working:
* Get anime list by status
* Get anime description pages
2016-12-22 21:36:23 -05:00
Timothy Warren 0c910bff1a Pull stuff from the Kitsu API 2016-12-21 12:46:20 -05:00
Timothy Warren a41acb28f6 Ugly progress commit 2016-12-20 12:58:37 -05:00
Timothy Warren 14181c9c51 Start of API integration 2016-12-20 12:55:43 -05:00
Timothy Warren 276c492355 Remove Hummingbird stuff 2016-12-16 21:52:59 -05:00
Timothy Warren fe91235436 Fix hhvm tests take 2 2016-11-03 11:39:24 -04:00
Timothy Warren 08c793b912 Fix hhvm tests 2016-11-03 11:30:22 -04:00
Timothy Warren bc0452aa2f Update CI tools to exclude old PHP versions 2016-11-01 09:10:11 -04:00
Timothy Warren 27f66cfea3 Update headers and namespaces 2016-10-20 22:32:17 -04:00
Timothy Warren 5e2a68dc84 Update EVERYTHING 2016-10-20 22:09:36 -04:00
Timothy Warren 08b61d08e9 Make sure to use the version of phpunit I actually install 2016-08-30 12:02:21 -04:00
Timothy Warren c3ec94c063 Attempt to fix travis build 2016-08-30 11:51:35 -04:00
Timothy Warren 9ec66803c8 Update Robofile to work properly 2016-08-30 11:45:17 -04:00
Timothy Warren afbde14116 Code style fixes to satisfy phpcs 2016-08-30 10:57:41 -04:00
Timothy Warren 52b1959338 Update header comments 2016-08-30 10:01:18 -04:00
Timothy Warren 4607d146a4 Move tests to tests/ directory 2016-08-29 17:09:56 -04:00
Timothy Warren bf01e41e32 Move src files to root of src/ 2016-08-29 16:36:13 -04:00
Timothy Warren 756db06540 Build/doc generation updates 2016-08-29 15:50:59 -04:00
Timothy Warren d6f9ceb5c7 Fix tests broken due to changes in container 2016-08-29 15:36:36 -04:00
Timothy Warren f1a6f99fc4 Convert Dependency injection bootstrap file to use factory functions, rather than direct instances 2016-08-29 14:51:32 -04:00
Timothy Warren 3335093e87 Move Ion namespace into composer dependency 2016-08-09 11:08:45 -04:00
Timothy Warren 1d6e347b78 Another attempt at hhvm setup 2016-08-03 18:43:09 -04:00
Timothy Warren f163ea41d6 actually use the correct composer command to install phpunit for hhvm 2016-08-03 18:25:28 -04:00
Timothy Warren d30e90937b try a different docker container for running hhvm tests 2016-08-03 18:09:55 -04:00
Timothy Warren 30834be3a8 Attempt hhvm testing on gitlab ci, adjust acceptable failures on travis 2016-08-03 14:30:36 -04:00
Timothy Warren 282fb44603 Don't install dev dependencies in test environments 2016-08-03 14:14:38 -04:00
Timothy Warren aac478a455 Another attempt to fix gitlab ci build 2016-08-03 14:08:02 -04:00
Timothy Warren b14413af3f Attempt to fix gitlab ci build 2016-08-03 13:47:14 -04:00
Timothy Warren cc65cc9cf1 Set up mutation testing for unit tests 2016-08-01 14:38:23 -04:00
Timothy Warren 1ccce00e46 Add dev dependencies, augment gitignore 2016-08-01 13:10:00 -04:00
Timothy Warren 7f1bcc841a Refactor cache to remove dependency on container 2016-08-01 13:02:26 -04:00
Timothy Warren ba6ada32f9 Move cache class to IOn namespace, use safer json for serialization in cache drivers 2016-07-28 10:44:13 -04:00
Timothy Warren 3d66fa9c11 Finish moving get_cached_image method to Util class 2016-07-27 14:32:37 -04:00
Timothy Warren 2a3a64f0eb Fix travis ci tests 2016-07-27 13:35:30 -04:00
Timothy Warren 1b93adefa3 Refactor out some Interdependency between Ion and AnimeClient namespaces 2016-07-27 13:18:52 -04:00
Timothy Warren 756b5b5136 Update the correct file to change config for gitlab ci 2016-07-25 12:29:42 -04:00
Timothy Warren ac76994a70 Attempt moving config file in a different way 2016-07-25 12:18:51 -04:00
Timothy Warren 1d2936df92 Fix typo in test path 2016-07-25 12:04:56 -04:00
Timothy Warren fec9de0e3d Add redis config file for gitlab ci tests 2016-07-25 11:58:43 -04:00
Timothy Warren 5a69da4466 Update Redis tests to work with gitlab ci 2016-07-22 17:48:13 -04:00
Timothy Warren 4772c6df95 Update Redis cache driver to use PHP-only library, removing the dependence on an extension: 2016-07-22 17:22:00 -04:00
Timothy Warren 0632ebba66 Remove redundant mbstring extesion from build setup 2016-07-19 10:45:16 -04:00
Timothy Warren b61f0b5e6c Attempt 2 to install gd in gitlab ci tests 2016-07-19 10:25:54 -04:00
Timothy Warren d54d180090 Fix failing test by installing gd in gitlab ci test 2016-07-19 10:17:53 -04:00
Timothy Warren 23fa88bb33 Set default timezone to prevent stupid test errors 2016-07-18 13:06:37 -04:00
Timothy Warren c0c90eb565 Make tests skip redis integration if the extension is not installed 2016-07-18 12:59:34 -04:00
Timothy Warren 1f80491968 Another attempt at getting gitlab ci to run 2016-07-18 12:47:51 -04:00
Timothy Warren 691387b7c9 Attempt tests without redis for now 2016-07-18 10:16:21 -04:00
Timothy Warren 229387a972 Gitlab CI take 3 2016-07-18 10:07:50 -04:00
Timothy Warren 8ba6e83032 Make sure docker sh script doesn't have CRLF line endings 2016-07-18 09:58:23 -04:00
Timothy Warren 3f7711dd20 Gitlab CI take two 2016-07-18 09:55:06 -04:00
Timothy Warren 9fc3d60835 First attempt at setting up gitlab ci 2016-07-18 09:47:34 -04:00
Timothy Warren c9e0b333bf Merge branch 'develop' 2016-07-15 11:35:31 -04:00
Timothy Warren 6da1924f53 Minor example file tweaks, add smooth scrolling to browsers that support it 2016-06-07 11:36:02 -04:00
Timothy Warren 1afb45522d Remove redundant cache loading 2016-04-22 10:52:42 -04:00
Timothy Warren e0b58a29ea Shrink api clearing button 2016-04-22 10:47:18 -04:00
Timothy Warren 675ccac14d Add button to clear api cache 2016-04-21 11:14:21 -04:00
Timothy Warren 47b8a83d86 Update changelog and add additional tests 2016-04-19 14:51:58 -04:00
Timothy Warren 1bc1d2fa31 Update README for version 3 2016-04-19 13:24:21 -04:00
Timothy Warren 1844f89e63 Small miscellaneous cleanup 2016-04-19 13:23:49 -04:00
Timothy Warren 741d9d0805 Add batch image thumbnail creation, see #6, #14 2016-04-19 13:02:50 -04:00
Timothy Warren 05391eceab Resolves #10, adds ability to delete from anime collection 2016-04-14 19:10:03 -04:00
Timothy Warren cf7b645d54 Update sonarqube version 2016-04-14 18:03:34 -04:00
Timothy Warren 3710f42505 Add detail view to anime list 2016-04-14 17:51:00 -04:00
Timothy Warren 01d25f2817 Add ability to delete items from manga list. See #10 2016-04-14 17:00:34 -04:00
Timothy Warren d17549c0fb Add ability to delete items from anime list. References #10 2016-04-14 15:16:13 -04:00
Timothy Warren 79f71eee09 Re-add cache to manga controller so cache can be invalidated on update 2016-04-14 11:05:16 -04:00
Timothy Warren de2f4fca1e Add tests for Cache Manager class 2016-04-12 14:05:13 -04:00
Timothy Warren aa2e6a5418 Add missing update to base API Model 2016-04-12 13:41:50 -04:00
Timothy Warren 29041a4667 Update manga model to cache the one api response. 2016-04-12 13:41:03 -04:00
Timothy Warren a7063e9a49 Add NullDriver for cache layer, for the sake of testing, or config without a cache 2016-04-12 12:05:42 -04:00
Timothy Warren 35b42d5234 Miscellaneous cleanup 2016-04-08 18:05:52 -04:00
Timothy Warren d8e67e914f Remove allowed failures for PHP 5.5 & 5.6 2016-04-08 15:21:10 -04:00
Timothy Warren 6817082816 One last attempt to get redis to work with travis CI 2016-04-08 15:17:45 -04:00
Timothy Warren 5caa85d8f1 Another attempt to get redis working 2016-04-08 15:06:16 -04:00
Timothy Warren d895c44c0c Update travis config to test redis 2016-04-08 14:56:09 -04:00
Timothy Warren 86065d16df Fix anime collection selection template to match db schema 2016-04-08 14:45:11 -04:00
Timothy Warren ea1b63265b Add Redis Cache driver 2016-04-08 14:25:45 -04:00
Timothy Warren 52c1cff5e8 Update first migration to allow empty notes on collection 2016-04-08 13:39:37 -04:00
Timothy Warren 5fee288016 Add changelog 2016-04-08 13:22:29 -04:00
Timothy Warren c08880c19d Remove zepto 2016-04-08 13:22:10 -04:00
Timothy Warren 1c08959ed3 Set up package.json for myth css processing 2016-04-08 11:57:16 -04:00
Timothy Warren 4e58950ae2 Remove target=_blank from links 2016-04-08 11:56:17 -04:00
Timothy Warren c7b4ddf71e Add caching to Manga views 2016-04-07 13:11:45 -04:00
Timothy Warren 3d19f93001 Add tests for SQL based api cache 2016-04-07 12:34:57 -04:00
Timothy Warren 5db1d8b494 Remove json 'cache' files from anime model 2016-04-07 12:32:32 -04:00
Timothy Warren 181af86899 Fix tests 2016-04-06 14:58:19 -04:00
Timothy Warren 352ebb4105 Add some naive cache invalidation to update methods 2016-04-06 12:11:07 -04:00
Timothy Warren 3127e06a47 Start of caching implementation 2016-04-05 13:19:35 -04:00
Timothy Warren 44e4aaa732 Fix search methods to work with new Request library 2016-04-05 12:06:07 -04:00
Timothy Warren 125dc96de5 Add migration for sql cache backend 2016-04-05 12:03:56 -04:00
Timothy Warren 5aa8a70b0b Start of interface for caching backend 2016-04-01 17:35:53 -04:00
Timothy Warren 510211cfd0 Fix broken tests 2016-03-29 11:40:27 -04:00
Timothy Warren 26c6df74e4 Fix issue with cover not being hidden on last episode 2016-03-29 11:30:51 -04:00
Timothy Warren d8c89c9deb Update url generation to use new router 2016-03-07 14:37:49 -05:00
Timothy Warren dbaadc4c2a Fix most of the broken tests 2016-03-03 16:53:17 -05:00
Timothy Warren 2da5935d1f Further refactor handling of request variables, routing works again 2016-02-17 11:36:37 -05:00
Timothy Warren 9d00fc140c Get HTML output working again, still refactoring router 2016-02-17 10:29:05 -05:00
Timothy Warren 28adcaf95a Start integration of PSR 7 Request/Response 2016-02-16 16:28:44 -05:00
Timothy Warren a5f59092bb Merge remote-tracking branch 'origin/master' into develop 2016-02-16 12:26:54 -05:00
Timothy Warren 598d987b37 Update some javascript documentation, and add show/hide methods 2016-02-16 12:07:01 -05:00
Timothy Warren eb8090b350 Minor style fixes, and fix double message issue 2016-02-10 17:56:46 -05:00
Timothy Warren 38abb27eb3 Minor style fixes, and fix double message issue 2016-02-10 17:50:07 -05:00
Timothy Warren b6307eb88b Start of migration from php to toml config, see #11 2016-02-10 17:30:45 -05:00
Timothy Warren b2522acd08 Merge branch 'develop' into 'master'
Version 2.2

Pull Request tracking changes leading up to version 2.2

See merge request !1
2016-02-10 12:29:39 -05:00
Timothy Warren ba4597973e Better front-end tests 2016-02-10 12:25:13 -05:00
Timothy Warren 63b26a104d Update README 2016-02-09 21:42:56 -05:00
Timothy Warren b346bdf337 Update tests relating to issue #9 2016-02-09 21:03:26 -05:00
Timothy Warren c12bff6027 Polyfill classList api for browsers lacking support 2016-02-09 20:57:40 -05:00
Timothy Warren 825e0c1a90 Fixes issue #9 2016-02-09 20:20:54 -05:00
Timothy Warren db69b13d87 Partially fix #9 -- API calls fail on 'Plan to Watch' section 2016-02-09 20:12:39 -05:00
Timothy Warren 918b8fd18c Combine javascript library files into one base file 2016-02-09 20:07:01 -05:00
Timothy Warren 170acbaffd Update README with some server setup details, resolves #7 2016-02-09 16:45:22 -05:00
Timothy Warren 2effae1bd5 Remove last dependencies on zepto 2016-02-08 20:21:41 -05:00
Timothy Warren 41e6f6e57a Remove zepto ajax calls 2016-02-08 13:37:44 -05:00
Timothy Warren 5c188f3513 Further improve minifiers, add start of front-end tests 2016-02-08 11:32:39 -05:00
Timothy Warren 3019e9f62e Fix #8, make minfiers output correctly 2016-02-08 10:57:44 -05:00
Timothy Warren dcf5bebb9b Add basic htaccess file for apache 2016-02-05 14:14:02 -05:00
Timothy Warren fc02a68691 Some temporary fixes for tempramental minifiers 2016-02-04 21:57:14 -05:00
Timothy Warren b1549440e0 Make build.xml file more phing friendly 2016-02-03 21:24:10 -05:00
Timothy Warren af29c68ec9 Rewrite minifiers into cleaner classes. Resolves #5 2016-02-03 14:57:00 -05:00
Timothy Warren f3a44e6f33 Minor spacing fixes 2016-02-02 21:38:38 -05:00
Timothy Warren 6555aac2fb Minor code quality fixes for Scrutinizer 2016-02-02 21:28:32 -05:00
Timothy Warren 93b885dc0d Fix manga editing for sections other than 'Reading' 2016-02-02 14:13:49 -05:00
Timothy Warren d746de34ea Add form for manga 2016-02-02 11:34:03 -05:00
Timothy Warren 49bd468aa4 Ugly progress commit 2016-02-01 09:49:18 -05:00
Timothy Warren f007ad987d Add missing table sorting lib 2016-01-20 20:14:32 -05:00
Timothy Warren daf4b71bbb Add proper table sorting and add some security headers 2016-01-20 13:01:41 -05:00
Timothy Warren e59ead5a84 Another attempt at code coverage for codeclimate 2016-01-12 12:47:48 -05:00
Timothy Warren 2e3306708e Code coverage for codeclimate 2016-01-12 12:33:45 -05:00
Timothy Warren c59288b5f9 Merge pull request #13 from timw4mail/develop
Sync with dev
2016-01-11 15:44:57 -05:00
Timothy Warren 6ff4ee2746 Change logger methods to be inline with interface, fix Manga Model tests 2016-01-11 15:31:53 -05:00
Timothy Warren 9b03f102f3 Update whoops to 2.0 2016-01-11 14:57:43 -05:00
Timothy Warren 816a309f18 Remove errorhandler, and replace with logger 2016-01-11 14:39:53 -05:00
Timothy Warren b85ddb9464 Update collection to use flash messages and more intelligent redirects 2016-01-11 13:33:56 -05:00
Timothy Warren 3918ce4eb7 Add more test coverage, and update build.xml 2016-01-11 10:42:34 -05:00
Timothy Warren 3bd5c7d218 Further refine Dispatcher 2016-01-08 16:39:18 -05:00
Timothy Warren 9c73ab928c Refactor Dispatcher 2016-01-08 15:54:21 -05:00
Timothy Warren 016e0988e9 Fix line endings in view classes 2016-01-08 15:53:50 -05:00
Timothy Warren 97dfa89b6d Actually fix view tests 2016-01-08 11:40:24 -05:00
Timothy Warren f5549934fe Fix view tests 2016-01-08 11:19:56 -05:00
Timothy Warren 27ac7e8063 Fix http verb for update route, add correct http codes for http errors 2016-01-07 20:48:18 -05:00
Timothy Warren 275b0eea40 Update codebase to use new Json class 2016-01-07 13:45:43 -05:00
Timothy Warren fa4940f22d Add missing classes 2016-01-06 17:08:44 -05:00
Timothy Warren bfe46fbbd1 Fix PHP 5.5 build 2016-01-06 17:06:30 -05:00
Timothy Warren 38faaebb5f Remove unnamespaced constants, and improve some tests 2016-01-06 15:44:40 -05:00
Timothy Warren 3c124456d0 Simplify routing 2016-01-06 11:08:56 -05:00
Timothy Warren b4489311c9 Fix testss 2016-01-05 10:05:14 -05:00
Timothy Warren 7a9fe42d83 Merge pull request #12 from timw4mail/scrutinizer-patch-2
Scrutinizer Auto-Fixes
2016-01-05 10:02:18 -05:00
Scrutinizer Auto-Fixer dd0e15137a Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2016-01-05 14:55:08 +00:00
Timothy Warren 205c7ac76d Update header comments, add start of manga editing functionality 2016-01-04 16:58:33 -05:00
Timothy Warren 6455541210 Merge pull request #11 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2016-01-04 11:16:15 -05:00
Scrutinizer Auto-Fixer 7cdd79a116 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2016-01-04 16:15:25 +00:00
Timothy Warren 6e4e8edd9d Add full edit form to anime list 2016-01-04 10:53:03 -05:00
Timothy Warren 1251486aa3 Update composer.json 2015-12-16 10:12:31 -05:00
Timothy Warren 1b8ed53afb Start of delete functionality for anime collection 2015-12-15 15:55:30 -05:00
Timothy Warren 5e048977a3 Merge pull request #10 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-12-09 15:13:24 -05:00
Scrutinizer Auto-Fixer 355aff2951 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-12-09 19:59:54 +00:00
Timothy Warren 3c41682d73 Some more minor code-style fixes 2015-12-09 14:54:11 -05:00
Timothy Warren 77709c068a Some code style fixes 2015-12-08 16:39:49 -05:00
Timothy Warren d48cafa54d update travis build file 2015-12-08 14:58:43 -05:00
Timothy Warren f386766841 Fix collection functionality 2015-12-08 14:52:59 -05:00
Timothy Warren 52397bbd61 Update README and composer 2015-11-18 16:03:40 -05:00
Timothy Warren 99b429433c Skip erroring tests on travis 2015-11-18 10:58:12 -05:00
Timothy Warren c23c63cb15 Fix some minor formating issues 2015-11-18 10:54:06 -05:00
Timothy Warren fff46421bf Update minor documention issues 2015-11-18 10:48:05 -05:00
Timothy Warren 61f1963db8 Try mocking out get_cached_image method 2015-11-18 10:41:00 -05:00
Timothy Warren 253f191113 More test coverage 2015-11-18 10:31:42 -05:00
Timothy Warren 6622014bd1 Improve some test coverage 2015-11-17 16:45:41 -05:00
Timothy Warren bd6b5e2b54 Remove loose functions file 2015-11-16 19:30:04 -05:00
Timothy Warren 4ed6200d9f Fix manga list updating 2015-11-16 15:57:37 -05:00
Timothy Warren c009a96a15 Merge pull request #9 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-16 11:40:26 -05:00
Timothy Warren ae88282b13 Update header comments 2015-11-16 11:40:01 -05:00
Scrutinizer Auto-Fixer 38b2f34527 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-16 15:33:30 +00:00
Timothy Warren afced2339a Poor style progress update commit 2015-11-13 16:31:01 -05:00
Timothy Warren 83cd815750 Update some config and metadata 2015-11-13 11:34:30 -05:00
Timothy Warren ca2e72d3f0 Update 404 view 2015-11-13 11:33:47 -05:00
Timothy Warren 3c4ba096c6 Make updating of anime list work 2015-11-13 11:33:27 -05:00
Timothy Warren 1891aafef5 Update js minifier to be more robust, with better error handling 2015-11-13 11:32:12 -05:00
Timothy Warren aee2fa7120 Fix various code style nuances 2015-11-11 15:28:51 -05:00
Timothy Warren c55f91a79f Fix some sonarqube issues 2015-11-11 14:53:09 -05:00
Timothy Warren 8f95bfe7e0 Merge pull request #8 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-09 15:55:54 -05:00
Scrutinizer Auto-Fixer db33f46547 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-09 20:08:08 +00:00
Timothy Warren 68cb36b193 Update config and header for new auth class 2015-11-09 11:50:24 -05:00
Timothy Warren ff28b40c9e Fix ArrayType class 2015-11-09 11:49:51 -05:00
Timothy Warren e6b4fe59a3 More quality fixes 2015-11-09 11:10:15 -05:00
Timothy Warren 0cd30e811d No coverage for scrutinizer 2015-11-05 11:30:51 -05:00
Timothy Warren d3541da789 Fix some more code style issues 2015-11-05 11:26:03 -05:00
Timothy Warren cdb4406e14 Some more style fixes 2015-11-05 10:41:46 -05:00
Timothy Warren 6ef8caca00 Some code style fixes 2015-11-04 16:53:22 -05:00
Timothy Warren beb127c06c Some minor refactoring 2015-11-04 16:36:54 -05:00
Timothy Warren 9b38242f9d Merge pull request #7 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-11-04 16:33:18 -05:00
Scrutinizer Auto-Fixer d63d33d245 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-11-04 21:31:03 +00:00
Timothy Warren 30f18106cb Update metadata and build information files 2015-11-04 16:12:46 -05:00
Timothy Warren fdd0da8d93 Fix tests broken by missing fix to Anime Collection Model 2015-10-21 15:46:50 -04:00
Timothy Warren 4ad6178bf0 More test coverage 2015-10-21 15:43:51 -04:00
Timothy Warren 32d20a9234 Fix issue where cache file doesn't exist, add tests for Menu Helper 2015-10-21 11:57:58 -04:00
Timothy Warren def424da72 Fix default redirect and tests 2015-10-20 16:41:51 -04:00
Timothy Warren c99d4ee53d Fix the rest of the menu urls 2015-10-20 15:59:51 -04:00
Timothy Warren 935076cc63 Remove another vistigal controller method 2015-10-19 15:19:02 -04:00
Timothy Warren 766fad6bb2 Remove risky tests, update .gitignore 2015-10-19 15:13:18 -04:00
Timothy Warren 672b781425 Remove some vestigal methods from base controller 2015-10-19 13:58:59 -04:00
Timothy Warren 95ecc9e9b8 Fix spacing style 2015-10-19 13:26:50 -04:00
Timothy Warren 8625f20b74 Fix tests for PHP 5.5 2015-10-19 13:02:10 -04:00
Timothy Warren 944a0c9c2a More test coverage 2015-10-19 12:50:46 -04:00
Timothy Warren f22015635e Scrutinizer fixes 2015-10-16 12:53:55 -04:00
Timothy Warren 23122964d2 Better testing for ArrayType and Config classes 2015-10-15 22:00:09 -04:00
Timothy Warren 67080a098f Add partial test for config delete 2015-10-15 10:23:00 -04:00
Timothy Warren 5bf46e0840 Fix origin value in API Model tests 2015-10-15 09:49:38 -04:00
Timothy Warren 454679626c Fix html view test for PHP < 7 2015-10-15 09:28:10 -04:00
Timothy Warren e2e27c2311 More test coverage 2015-10-15 09:25:30 -04:00
Timothy Warren 3cf753a707 Update lots of docblocks 2015-10-14 09:20:52 -04:00
Timothy Warren 9ed18ce131 Fix documentation issues 2015-10-12 14:27:20 -04:00
Timothy Warren 6b1f39525d Remove some dead code 2015-10-12 14:11:00 -04:00
Timothy Warren 725915060b Merge pull request #6 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-12 11:00:09 -04:00
Scrutinizer Auto-Fixer 2b6b8bff43 Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-10 02:35:39 +00:00
Timothy Warren f49e4fe3d8 Rearrange some namespaces and add more docblocks 2015-10-09 22:29:59 -04:00
Timothy Warren 9cf958b1ed Merge pull request #5 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-09 15:04:55 -04:00
Scrutinizer Auto-Fixer 25981afeef Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-09 18:55:15 +00:00
Timothy Warren 3de32f52af Basic Menu generation 2015-10-09 14:34:55 -04:00
Timothy Warren 15707167f1 More scrutinizer fixes 2015-10-06 13:38:59 -04:00
Timothy Warren c86d72f3e2 Merge pull request #4 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-10-06 13:37:48 -04:00
Scrutinizer Auto-Fixer edd393793a Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-06 17:35:42 +00:00
Timothy Warren f9b3d64eca Fix more scrutinizer issues 2015-10-06 12:15:19 -04:00
Timothy Warren 83f1a9afd9 Fix failing test 2015-10-06 11:41:21 -04:00
Timothy Warren d53524ed86 Code style improvements 2015-10-06 11:38:20 -04:00
Timothy Warren aedabd6eda Scrutinizer fixes 2015-10-06 10:44:33 -04:00
Timothy Warren af95ac941f Merge pull request #3 from timw4mail/scrutinizer-patch-1
Spacing and docblock fixes
2015-10-06 10:28:33 -04:00
Scrutinizer Auto-Fixer 7c5a73e73b Scrutinizer Auto-Fixes
This commit consists of patches automatically generated for this project on https://scrutinizer-ci.com
2015-10-06 14:24:48 +00:00
Timothy Warren 5e5434d057 Miscellaneous updates, prep for menu generator 2015-10-05 16:54:25 -04:00
Timothy Warren 9f123822b1 Update Router 2015-10-01 16:30:46 -04:00
Timothy Warren 651b9c4483 Update some meta files 2015-10-01 16:21:09 -04:00
Timothy Warren e71e76dbc6 fix test 2015-10-01 16:07:40 -04:00
Timothy Warren 7917c39065 Lots of miscellaneous improvements 2015-10-01 16:02:51 -04:00
Timothy Warren e8a9982f9a Fix views to match transformed data 2015-10-01 16:01:23 -04:00
Timothy Warren e634a22134 Fix broken test 2015-09-28 15:11:45 -04:00
Timothy Warren a7ae1ac3a6 Use Anime transformer class 2015-09-28 14:41:45 -04:00
Timothy Warren 5624d9b44e Transformers and Enums 2015-09-25 13:41:12 -04:00
Timothy Warren 92d9124bb7 Update manga model to use Zipper transformer 2015-09-21 09:48:15 -04:00
Timothy Warren 8fb6dae119 More tests for Ion 2015-09-18 22:55:40 -04:00
Timothy Warren 5f6119c86b Fix failing tests for PHP < 5.6 2015-09-18 13:06:22 -04:00
Timothy Warren 602759b471 Decouple and generalise 2015-09-17 23:11:18 -04:00
Timothy Warren c788cf5d87 Start of refactoring routing to be more convention based 2015-09-16 12:25:35 -04:00
Timothy Warren 9193938dee More namespace refactoring 2015-09-15 13:19:29 -04:00
Timothy Warren b1c6039630 Namespace refactoring 2015-09-14 19:54:34 -04:00
Timothy Warren 67799fcdfa fix a few variable changes, remove old code from app folder 2015-09-14 16:14:02 -04:00
Timothy Warren dfe0b3a6cf Pass the tests! 2015-09-14 15:49:20 -04:00
Timothy Warren cee211621c Some progress toward better structure through refactoring 2015-09-14 10:54:50 -04:00
Timothy Warren 8904054212 Update default config, add phpci config file 2015-07-20 16:13:00 -04:00
Timothy Warren 6450a76351 Update readme with new instructions for collection 2015-07-06 14:35:24 -04:00
Timothy Warren 54371bf76a Miscellaneous rework, and adding/editing of collection items when logged in 2015-07-02 14:04:04 -04:00
Timothy Warren c33575e3d8 More dependency injection, and code coverage 2015-06-30 13:03:20 -04:00
Timothy Warren ef4e9b3e85 Merge pull request #1 from timw4mail/scrutinizer-patch-1
Scrutinizer Auto-Fixes
2015-06-29 10:36:28 -04:00
381 changed files with 34160 additions and 5316 deletions

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = false
charset = utf-8
indent_style = tab
trim_trailing_whitespace = true
[*.{cpp,c,h,hpp,cxx}]
insert_final_newline = true
# Yaml files
[*.{yml,yaml}]
indent_style = space
indent_size = 4

158
.gitignore vendored
View File

@ -1,10 +1,148 @@
vendor
app/cache/*
public/images/*
public/js/cache/*
composer.lock
*.sqlite
*.db
*.sqlite3
docs/*
coverage/*
# Created by https://www.gitignore.io/api/macos,jetbrains+all
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### JetBrains+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
*.iml
modules.xml
.idea/misc.xml
*.ipr
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.gitignore.io/api/macos,jetbrains+all
.codelite
.phing_targets
.sonar/
*.phprj
*.workspace
vendor
**/cache/**
**/logs/**
**/coverage/**
**/docs/**
**/node_modules/**
composer.lock
*.sqlite
*.db
*.sqlite3
apidocs/**
tests/test_data/sessions/*
cache.properties
build/**
!build/*.txt
!build/*.xml
!build/*.php
app/config/*.toml
!app/config/*.toml.example
phinx.yml
Caddyfile
build/humbuglog.txt
public/images/anime/**
public/images/avatars/**
public/images/manga/**
public/images/characters/**
public/images/people/**
public/mal_mappings.json

6
.htaccess Normal file
View File

@ -0,0 +1,6 @@
#Rewrite index.php out of the app urls
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]

View File

@ -1,20 +1,26 @@
language: php
install:
- composer install
- composer install --ignore-platform-reqs
php:
- 5.4
- 5.5
- 5.6
- 7
- hhvm
- 7.1
- 7.2
- 7.3
- nightly
script:
- mkdir -p build/logs
- phpunit --coverage-clover=coverage.clover
- phpdbg -qrr -- vendor/bin/phpunit -c build
after_script:
- wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
- CODECLIMATE_REPO_TOKEN=2cbddcebcb9256b3402867282e119dbe61de0b31039325356af3c7d72ed6d058 vendor/bin/test-reporter
matrix:
allow_failures:
- php: nightly
- php: hhvm
addons:
code_climate:
repo_token: 2cbddcebcb9256b3402867282e119dbe61de0b31039325356af3c7d72ed6d058

26
CHANGELOG.md Normal file
View File

@ -0,0 +1,26 @@
# Changelog
## Version 4.1
* Removed MAL integration, added Anilist Integration
* Now uses WebP cache images when the browser supports it
* Replaces JS minifier with pre-minified scripts (Removes the need for one caching folder, too)
* Updated console command to sync Kitsu and Anilist data (Kitsu can sync MAL, and MAL's API broke, so MAL sync was removed)
* Added page to update settings without having to edit config files
* Defaulted to secure (HTTPS) urls
* Updated Character pages to show voice actors
* Added People pages, showing which works they contributed to, and in what role
## Version 4
* Updated to use Kitsu API after discontinuation of Hummingbird
* Added streaming links to list entries from the Kitsu API
* Added simple integration with MyAnimeList, so an update can cross-post to both Kitsu and MyAnimeList (anime and manga)
* Added console command to sync Kitsu and MyAnimeList data
* Added character pages
## Version 3
* Converted user configuration to toml files
* Added a caching layer for api calls, which resets upon updates from the
app.
* Added a bulk thumbnail generator script
* Removed json file "cache" from the app folder

53
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,53 @@
pipeline {
agent none
stages {
stage('PHP 7.1') {
agent {
docker {
image 'php:7.1-alpine'
args '-u root --privileged'
}
}
steps {
sh 'chmod +x ./build/docker_install.sh'
sh 'sh build/docker_install.sh'
sh 'apk add --no-cache php7-phpdbg'
sh 'curl -sS https://getcomposer.org/installer | php'
sh 'php composer.phar install --ignore-platform-reqs'
sh 'phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never'
}
}
stage('PHP 7.2') {
agent {
docker {
image 'php:7.2-alpine'
args '-u root --privileged'
}
}
steps {
sh 'chmod +x ./build/docker_install.sh'
sh 'sh build/docker_install.sh'
sh 'apk add --no-cache php7-phpdbg'
sh 'curl -sS https://getcomposer.org/installer | php'
sh 'php composer.phar install --ignore-platform-reqs'
sh 'phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never'
}
}
stage('PHP 7.3') {
agent {
docker {
image 'php:7.3-alpine'
args '-u root --privileged'
}
}
steps {
sh 'chmod +x ./build/docker_install.sh'
sh 'sh build/docker_install.sh'
sh 'apk add --no-cache php7-phpdbg'
sh 'curl -sS https://getcomposer.org/installer | php'
sh 'php composer.phar install --ignore-platform-reqs'
sh 'phpdbg -qrr -- ./vendor/bin/phpunit --coverage-text --colors=never'
}
}
}
}

21
LICENSE Normal file
View File

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

View File

@ -1,10 +1,11 @@
# Hummingbird Anime Client
A self-hosted client that allows custom formatting of data from the hummingbird api
Update your anime/manga list on Kitsu.io and MyAnimeList.net
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
[![Build Status](https://travis-ci.org/timw4mail/HummingBirdAnimeClient.svg?branch=master)](https://travis-ci.org/timw4mail/HummingBirdAnimeClient)
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=aviat/HummingBirdAnimeClient/develop)](https://jenkins.timshomepage.net/job/aviat/HummingBirdAnimeClient/develop)
[[Hosted Example](https://anime.timshomepage.net)]
[[Hosted Example](https://list.timshomepage.net)]
## Features
@ -13,43 +14,47 @@ A self-hosted client that allows custom formatting of data from the hummingbird
* Plan to Watch
* On Hold
* Dropped
* Completed
* All of the above
* Completed
* Combined View
* Manga List views (Each with list and cover views):
* Reading
* Plan to Read
* On Hold
* Dropped
* Completed
* All of the above
* Combined View
* Anime collection view (segmented by media type):
* Cover Images
* Table List
### Requirements
* PHP 5.4+
* PDO SQLite (For collection tab)
* GD
* PHP 7.1+
* PDO SQLite or PDO PostgreSQL (For collection tab)
* GD extension for caching images
### Highly Recommended
* Redis or Memcached for caching
### Installation
1. Install dependencies via composer: `composer install`
2. Change the `WHOSE` constant declaration in `index.php` to your name
3. Configure settings in `app/config/config.php` to your liking
1. Install via git, then install dependencies via composer: `composer install`
2. Duplicate `app/config/config.toml.example` file as `app/config/config.toml`
3. Configure settings in `app/config/config.toml` to your liking
4. Create the following directories if they don't exist, and make sure they are world writable
* app/cache
* public/images/manga
* app/config
* app/logs
* public/images/avatars
* public/images/anime
* public/js/cache
* public/images/characters
* public/images/manga
5. Make sure the `console` script is executable
6. Additional settings are on the settings page once you log in.
#### Anime Collection Additional Installation
* Run `php /vendor/bin/phinx migrate -e development` to create the database tables
* For importing anime:
1. Find the anime you are looking for on the hummingbird search api page: `https://hummingbird.me/api/v1/search/anime?query=`
2. Create an `import.json` file in the root of the app, with an array of objects from the search page that you want to import
3. Go to the anime collection tab, and the import will be run
### Server Setup
See the [wiki](https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient/wiki)
for more in-depth information

305
RoboFile.php Normal file
View File

@ -0,0 +1,305 @@
<?php declare(strict_types=1);
use Robo\Tasks;
if ( ! function_exists('glob_recursive'))
{
// Does not support flag GLOB_BRACE
function glob_recursive($pattern, $flags = 0)
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir)
{
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}
/**
* This is project's console commands configuration for Robo task runner.
*
* @see http://robo.li/
*/
class RoboFile extends Tasks {
/**
* Directories used by analysis tools
*
* @var array
*/
protected $taskDirs = [
'build/logs',
'build/pdepend',
'build/phpdox',
];
/**
* Directories to remove with the clean task
*
* @var array
*/
protected $cleanDirs = [
'coverage',
'docs',
'phpdoc',
'build/logs',
'build/phpdox',
'build/pdepend'
];
/**
* Do static analysis tasks
*/
public function analyze()
{
$this->prepare();
$this->lint();
$this->phploc(TRUE);
$this->phpcs(TRUE);
$this->dependencyReport();
$this->phpcpdReport();
}
/**
* Run all tests, generate coverage, generate docs, generate code statistics
*/
public function build()
{
$this->analyze();
$this->coverage();
$this->docs();
}
/**
* Cleanup temporary files
*/
public function clean()
{
$cleanFiles = [
'build/humbug.json',
'build/humbug-log.txt',
];
array_map(function ($file) {
@unlink($file);
}, $cleanFiles);
// So the task doesn't complain,
// make any 'missing' dirs to cleanup
array_map(function ($dir) {
if ( ! is_dir($dir))
{
`mkdir -p {$dir}`;
}
}, $this->cleanDirs);
$this->_cleanDir($this->cleanDirs);
$this->_deleteDir($this->cleanDirs);
}
/**
* Run unit tests and generate coverage reports
*/
public function coverage()
{
$this->_run(['phpdbg -qrr -- vendor/bin/phpunit -c build']);
}
/**
* Generate documentation with phpdox
*/
public function docs()
{
$cmd_parts = [
'vendor/bin/phpdox',
];
$this->_run($cmd_parts, ' && ');
}
/**
* Verify that source files are valid
*/
public function lint()
{
$files = $this->getAllSourceFiles();
$chunks = array_chunk($files, 12);
foreach($chunks as $chunk)
{
$this->parallelLint($chunk);
}
}
/**
* Run the phpcs tool
*
* @param bool $report - if true, generates reports instead of direct output
*/
public function phpcs($report = FALSE)
{
$report_cmd_parts = [
'vendor/bin/phpcs',
'--standard=./build/phpcs.xml',
'--report-checkstyle=./build/logs/phpcs.xml',
];
$normal_cmd_parts = [
'vendor/bin/phpcs',
'--standard=./build/phpcs.xml',
];
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
$this->_run($cmd_parts);
}
/**
* Run the phploc tool
*
* @param bool $report - if true, generates reports instead of direct output
*/
public function phploc($report = FALSE)
{
// Command for generating reports
$report_cmd_parts = [
'vendor/bin/phploc',
'--count-tests',
'--log-csv=build/logs/phploc.csv',
'--log-xml=build/logs/phploc.xml',
'src',
'tests'
];
// Command for generating direct output
$normal_cmd_parts = [
'vendor/bin/phploc',
'--count-tests',
'src',
'tests'
];
$cmd_parts = ($report) ? $report_cmd_parts : $normal_cmd_parts;
$this->_run($cmd_parts);
}
/**
* Create temporary directories
*/
public function prepare()
{
array_map([$this, '_mkdir'], $this->taskDirs);
}
/**
* Lint php files and run unit tests
*/
public function test()
{
$this->lint();
$this->_run(['phpunit']);
}
/**
* Watches for file updates, and automatically runs appropriate actions
*/
public function watch()
{
$this->taskWatch()
->monitor('composer.json', function() {
$this->taskComposerUpdate()->run();
})
->monitor('src', function () {
$this->taskExec('test')->run();
})
->monitor('tests', function () {
$this->taskExec('test')->run();
})
->run();
}
/**
* Create pdepend reports
*/
protected function dependencyReport()
{
$cmd_parts = [
'vendor/bin/pdepend',
'--jdepend-xml=build/logs/jdepend.xml',
'--jdepend-chart=build/pdepend/dependencies.svg',
'--overview-pyramid=build/pdepend/overview-pyramid.svg',
'src'
];
$this->_run($cmd_parts);
}
/**
* Get the total list of source files, including tests
*
* @return array
*/
protected function getAllSourceFiles()
{
$files = array_merge(
glob_recursive('build/*.php'),
glob_recursive('src/*.php'),
glob_recursive('src/**/*.php'),
glob_recursive('tests/*.php'),
glob_recursive('tests/**/*.php'),
glob('*.php')
);
sort($files);
return $files;
}
/**
* Run php's linter in one parallel task for the passed chunk
*
* @param array $chunk
*/
protected function parallelLint(array $chunk)
{
$task = $this->taskParallelExec()
->timeout(5)
->printed(FALSE);
foreach($chunk as $file)
{
$task = $task->process("php -l {$file}");
}
$task->run();
}
/**
* Generate copy paste detector report
*/
protected function phpcpdReport()
{
$cmd_parts = [
'vendor/bin/phpcpd',
'--log-pmd build/logs/pmd-cpd.xml',
'src'
];
$this->_run($cmd_parts);
}
/**
* Shortcut for joining an array of command arguments
* and then running it
*
* @param array $cmd_parts - command arguments
* @param string $join_on - what to join the command arguments with
*/
protected function _run(array $cmd_parts, $join_on = ' ')
{
$this->taskExec(implode($join_on, $cmd_parts))->run();
}
}

View File

@ -0,0 +1,59 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2017 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
use function Aviat\AnimeClient\loadToml;
// ----------------------------------------------------------------------------
// Lower level configuration
//
// You shouldn't generally need to change anything below this line
// ----------------------------------------------------------------------------
$APP_DIR = realpath(__DIR__ . '/../');
$ROOT_DIR = realpath("{$APP_DIR}/../");
$tomlConfig = loadToml(__DIR__);
return array_merge($tomlConfig, [
'asset_dir' => "{$ROOT_DIR}/public",
'base_config_dir' => __DIR__,
'config_dir' => "{$APP_DIR}/config",
// No config defaults
'kitsu_username' => 'timw4mail',
'whose_list' => 'Someone',
'cache' => [
'connection' => [],
'driver' => 'null',
],
'secure_urls' => TRUE,
// Routing defaults
'asset_path' => '/public',
'default_list' => 'anime', //anime|manga
'default_anime_list_path' => 'watching', // watching|plan_to_watch|on_hold|dropped|completed|all
'default_manga_list_path' => 'reading', // reading|plan_to_read|on_hold|dropped|completed|all
'default_view_type' => 'cover_view', // cover_view|list_view
// Template file path
'view_path' => "{$APP_DIR}/views",
// Cache paths
'data_cache_path' => "{$APP_DIR}/cache",
'img_cache_path' => "{$ROOT_DIR}/public/images",
// Included config files
'routes' => require 'routes.php',
]);

19
app/appConf/menus.toml Normal file
View File

@ -0,0 +1,19 @@
[anime_list]
route_prefix = "/anime"
[anime_list.items]
watching = '/watching'
plan_to_watch = '/plan_to_watch'
on_hold = '/on_hold'
dropped = '/dropped'
completed = '/completed'
all = '/all'
[manga_list]
route_prefix = "/manga"
[manga_list.items]
reading = '/reading'
plan_to_read = '/plan_to_read'
on_hold = '/on_hold'
dropped = '/dropped'
completed = '/completed'
all = '/all'

306
app/appConf/routes.php Normal file
View File

@ -0,0 +1,306 @@
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
use const Aviat\AnimeClient\{
ALPHA_SLUG_PATTERN,
NUM_PATTERN,
SLUG_PATTERN,
DEFAULT_CONTROLLER_METHOD,
DEFAULT_CONTROLLER
};
// -------------------------------------------------------------------------
// Routing Config
//
// Maps paths to controllers and methods
// -------------------------------------------------------------------------
$routes = [
// ---------------------------------------------------------------------
// Anime List Routes
// ---------------------------------------------------------------------
'anime.add.get' => [
'path' => '/anime/add',
'action' => 'addForm',
],
'anime.add.post' => [
'path' => '/anime/add',
'action' => 'add',
'verb' => 'post',
],
'anime.details' => [
'path' => '/anime/details/{id}',
'action' => 'details',
'tokens' => [
'id' => SLUG_PATTERN,
],
],
'anime.delete' => [
'path' => '/anime/delete',
'action' => 'delete',
'verb' => 'post',
],
// ---------------------------------------------------------------------
// Manga Routes
// ---------------------------------------------------------------------
'manga.search' => [
'path' => '/manga/search',
'action' => 'search',
],
'manga.add.get' => [
'path' => '/manga/add',
'action' => 'addForm',
],
'manga.add.post' => [
'path' => '/manga/add',
'action' => 'add',
'verb' => 'post',
],
'manga.delete' => [
'path' => '/manga/delete',
'action' => 'delete',
'verb' => 'post',
],
'manga.details' => [
'path' => '/manga/details/{id}',
'action' => 'details',
'tokens' => [
'id' => SLUG_PATTERN,
],
],
// ---------------------------------------------------------------------
// Anime Collection Routes
// ---------------------------------------------------------------------
'anime.collection.search' => [
'path' => '/anime-collection/search',
'action' => 'search',
],
'anime.collection.add.get' => [
'path' => '/anime-collection/add',
'action' => 'form',
],
'anime.collection.edit.get' => [
'path' => '/anime-collection/edit/{id}',
'action' => 'form',
'tokens' => [
'id' => NUM_PATTERN,
],
],
'anime.collection.add.post' => [
'path' => '/anime-collection/add',
'action' => 'add',
'verb' => 'post',
],
'anime.collection.edit.post' => [
'path' => '/anime-collection/edit',
'action' => 'edit',
'verb' => 'post',
],
'anime.collection.view' => [
'path' => '/anime-collection/view{/view}',
'action' => 'view',
'tokens' => [
'view' => ALPHA_SLUG_PATTERN,
],
],
'anime.collection.delete' => [
'path' => '/anime-collection/delete',
'action' => 'delete',
'verb' => 'post',
],
'anime.collection.redirect' => [
'path' => '/anime-collection',
],
'anime.collection.redirect2' => [
'path' => '/anime-collection/',
],
// ---------------------------------------------------------------------
// Manga Collection Routes
// ---------------------------------------------------------------------
'manga.collection.search' => [
'path' => '/manga-collection/search',
'action' => 'search',
],
'manga.collection.add.get' => [
'path' => '/manga-collection/add',
'action' => 'form',
],
'manga.collection.edit.get' => [
'path' => '/manga-collection/edit/{id}',
'action' => 'form',
'tokens' => [
'id' => NUM_PATTERN,
],
],
'manga.collection.add.post' => [
'path' => '/manga-collection/add',
'action' => 'add',
'verb' => 'post',
],
'manga.collection.edit.post' => [
'path' => '/manga-collection/edit',
'action' => 'edit',
'verb' => 'post',
],
'manga.collection.view' => [
'path' => '/manga-collection/view{/view}',
'tokens' => [
'view' => ALPHA_SLUG_PATTERN,
],
],
'manga.collection.delete' => [
'path' => '/manga-collection/delete',
'action' => 'delete',
'verb' => 'post',
],
// ---------------------------------------------------------------------
// Other Routes
// ---------------------------------------------------------------------
'character' => [
'path' => '/character/{slug}',
'tokens' => [
'slug' => SLUG_PATTERN
]
],
'person' => [
'path' => '/people/{id}',
'tokens' => [
'id' => SLUG_PATTERN
]
],
'default_user_info' => [
'path' => '/me',
'action' => 'me',
'controller' => 'user',
],
'user_info' => [
'path' => '/user/{username}',
'controller' => 'user',
'action' => 'about',
'tokens' => [
'username' => '.*?'
]
],
// ---------------------------------------------------------------------
// Default / Shared routes
// ---------------------------------------------------------------------
'anilist-redirect' => [
'path' => '/anilist-redirect',
'action' => 'anilistRedirect',
'controller' => 'settings',
],
'anilist-callback' => [
'path' => '/anilist-oauth',
'action' => 'anilistCallback',
'controller' => 'settings',
],
'image_proxy' => [
'path' => '/public/images/{type}/{file}',
'action' => 'cache',
'controller' => 'images',
'tokens' => [
'type' => SLUG_PATTERN,
'file' => '[a-z0-9\-]+\.[a-z]{3,4}'
]
],
'cache_purge' => [
'path' => '/cache_purge',
'action' => 'clearCache',
],
'settings' => [
'path' => '/settings',
],
'settings-post' => [
'path' => '/settings/update',
'action' => 'update',
'verb' => 'post',
],
'login' => [
'path' => '/login',
'action' => 'login',
],
'login.post' => [
'path' => '/login',
'action' => 'loginAction',
'verb' => 'post',
],
'logout' => [
'path' => '/logout',
'action' => 'logout',
],
'increment' => [
'path' => '/{controller}/increment',
'action' => 'increment',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'update' => [
'path' => '/{controller}/update',
'action' => 'update',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'update.post' => [
'path' => '/{controller}/update_form',
'action' => 'formUpdate',
'verb' => 'post',
'tokens' => [
'controller' => ALPHA_SLUG_PATTERN,
],
],
'edit' => [
'path' => '/{controller}/edit/{id}/{status}',
'action' => 'edit',
'tokens' => [
'id' => SLUG_PATTERN,
'status' => '([a-zA-Z\-_]|%20)+',
],
],
'list' => [
'path' => '/{controller}/{type}{/view}',
'tokens' => [
'type' => ALPHA_SLUG_PATTERN,
'view' => ALPHA_SLUG_PATTERN,
],
],
'index_redirect' => [
'path' => '/',
'action' => 'redirectToDefaultRoute',
],
];
$defaultMap = [
'action' => DEFAULT_CONTROLLER_METHOD,
'controller' => DEFAULT_CONTROLLER,
'params' => [],
'verb' => 'get',
];
foreach ($routes as &$route)
{
foreach($defaultMap as $key => $val)
{
if ( ! array_key_exists($key, $route))
{
$route[$key] = $val;
}
}
}
return $routes;

View File

@ -1,81 +0,0 @@
<?php
/**
* Base API Model
*/
namespace AnimeClient;
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;
/**
* Base model for api interaction
*/
class BaseApiModel extends BaseModel {
/**
* Base url for making api requests
* @var string
*/
protected $base_url = '';
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->cookieJar = new CookieJar();
$this->client = new Client([
'base_url' => $this->base_url,
'defaults' => [
'cookies' => $this->cookieJar,
'headers' => [
'User-Agent' => $_SERVER['HTTP_USER_AGENT'],
'Accept-Encoding' => 'application/json'
],
'timeout' => 5,
'connect_timeout' => 5
]
]);
}
/**
* Attempt login via the api
*
* @codeCoverageIgnore
* @param string $username
* @param string $password
* @return bool
*/
public function authenticate($username, $password)
{
$result = $this->client->post('https://hummingbird.me/api/v1/users/authenticate', [
'body' => [
'username' => $username,
'password' => $password
]
]);
if ($result->getStatusCode() === 201)
{
$_SESSION['hummingbird_anime_token'] = $result->json();
return TRUE;
}
return FALSE;
}
}
// End of BaseApiModel.php

View File

@ -1,254 +0,0 @@
<?php
/**
* Base Controller
*/
namespace AnimeClient;
use Aura\Web\WebFactory;
/**
* Base class for controllers, defines output methods
*/
class BaseController {
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Request object
* @var object $request
*/
protected $request;
/**
* Response object
* @var object $response
*/
protected $response;
/**
* The api model for the current controller
* @var object
*/
protected $model;
/**
* Common data to be sent to views
* @var array
*/
protected $base_data = [];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
$this->config = $config;
list($request, $response) = $web;
$this->request = $request;
$this->response = $response;
}
public function __destruct()
{
$this->output();
}
/**
* Get the string output of a partial template
*
* @param string $template
* @param array|object $data
* @return string
*/
public function load_partial($template, $data=[])
{
if (isset($this->base_data))
{
$data = array_merge($this->base_data, $data);
}
global $router, $defaultHandler;
$route = $router->get_route();
$data['route_path'] = ($route) ? $router->get_route()->path : "";
$defaultHandler->addDataTable('Template Data', $data);
$template_path = _dir(APP_DIR, 'views', "{$template}.php");
if ( ! is_file($template_path))
{
throw new Exception("Invalid template : {$path}");
}
ob_start();
extract($data);
include _dir(APP_DIR, 'views', 'header.php');
include $template_path;
include _dir(APP_DIR, 'views', 'footer.php');
$buffer = ob_get_contents();
ob_end_clean();
return $buffer;
}
/**
* Output a template to HTML, using the provided data
*
* @param string $template
* @param array|object $data
* @return void
*/
public function outputHTML($template, $data=[])
{
$buffer = $this->load_partial($template, $data);
$this->response->content->setType('text/html');
$this->response->content->set($buffer);
}
/**
* Output json with the proper content type
*
* @param mixed $data
* @return void
*/
public function outputJSON($data)
{
if ( ! is_string($data))
{
$data = json_encode($data);
}
$this->response->content->setType('application/json');
$this->response->content->set($data);
}
/**
* Redirect to the selected page
*
* @param string $url
* @param int $code
* @return void
*/
public function redirect($url, $code, $type="anime")
{
$url = full_url($url, $type);
$codes = [
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other'
];
header("HTTP/1.1 {$code} {$codes[$code]}");
header("Location: {$url}");
}
/**
* Add a message box to the page
*
* @param string $type
* @param string $message
* @return string
*/
public function show_message($type, $message)
{
return $this->load_partial('message', [
'stat_class' => $type,
'message' => $message
]);
}
/**
* Clear the api session
*
* @return void
*/
public function logout()
{
session_destroy();
$this->response->redirect->seeOther(full_url(''));
}
/**
* Show the login form
*
* @param string $status
* @return void
*/
public function login($status="")
{
$message = "";
if ($status != "")
{
$message = $this->show_message('error', $status);
}
$this->outputHTML('login', [
'title' => 'Api login',
'message' => $message
]);
}
/**
* Attempt to log in with the api
*
* @return void
*/
public function login_action()
{
if (
$this->model->authenticate(
$this->config->hummingbird_username,
$this->request->post->get('password')
)
)
{
$this->response->redirect->afterPost(full_url('', $this->base_data['url_type']));
return;
}
$this->login("Invalid username or password.");
}
/**
* Send the appropriate response
*
* @return void
*/
private function output()
{
// send status
@header($this->response->status->get(), true, $this->response->status->getCode());
// headers
foreach($this->response->headers->get() as $label => $value)
{
@header("{$label}: {$value}");
}
// cookies
foreach($this->response->cookies->get() as $name => $cookie)
{
@setcookie(
$name,
$cookie['value'],
$cookie['expire'],
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
// send the actual response
echo $this->response->content->get();
}
}
// End of BaseController.php

View File

@ -1,32 +0,0 @@
<?php
/**
* Base DB model
*/
namespace AnimeClient;
/**
* Base model for database interaction
*/
class BaseDBModel extends BaseModel {
/**
* The query builder object
* @var object $db
*/
protected $db;
/**
* The database connection information array
* @var array $db_config
*/
protected $db_config;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->db_config = $this->config->database;
}
}
// End of BaseDBModel.php

View File

@ -1,106 +0,0 @@
<?php
/**
* Base for base models
*/
namespace AnimeClient;
use abeautifulsite\SimpleImage;
/**
* Common base for all Models
*/
class BaseModel {
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Constructor
*/
public function __construct()
{
global $config;
$this->config = $config;
}
/**
* Get the path of the cached version of the image. Create the cached image
* if the file does not already exist
*
* @codeCoverageIgnore
* @param string $api_path - The original image url
* @param string $series_slug - The part of the url with the series name, becomes the image name
* @param string $type - Anime or Manga, controls cache path
* @return string - the frontend path for the cached image
*/
public function get_cached_image($api_path, $series_slug, $type="anime")
{
$api_path = str_replace("jjpg", "jpg", $api_path);
$path_parts = explode('?', basename($api_path));
$path = current($path_parts);
$ext_parts = explode('.', $path);
$ext = end($ext_parts);
// Workaround for some broken extensions
if ($ext == "jjpg") $ext = "jpg";
// Failsafe for weird urls
if (strlen($ext) > 3) return $api_path;
$cached_image = "{$series_slug}.{$ext}";
$cached_path = "{$this->config->img_cache_path}/{$type}/{$cached_image}";
// Cache the file if it doesn't already exist
if ( ! file_exists($cached_path))
{
if (ini_get('allow_url_fopen'))
{
copy($api_path, $cached_path);
}
elseif (function_exists('curl_init'))
{
$ch = curl_init($api_path);
$fp = fopen($cached_path, 'wb');
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_HEADER => 0
]);
curl_exec($ch);
curl_close($ch);
fclose($ch);
}
else
{
throw new Exception("Couldn't cache images because they couldn't be downloaded.");
}
// Resize the image
if ($type == 'anime')
{
$resize_width = 220;
$resize_height = 319;
$this->_resize($cached_path, $resize_width, $resize_height);
}
}
return "/public/images/{$type}/{$cached_image}";
}
/**
* Resize an image
*
* @codeCoverageIgnore
* @param string $path
* @param string $width
* @param string $height
*/
private function _resize($path, $width, $height)
{
$img = new SimpleImage($path);
$img->resize($width,$height)->save();
}
}
// End of BaseModel.php

View File

@ -1,56 +0,0 @@
<?php
namespace AnimeClient;
/**
* Wrapper for configuration values
*/
class Config {
/**
* Config object
*
* @var array
*/
protected $config = [];
/**
* Constructor
*
* @param array $config_files
*/
public function __construct(Array $config_files=[])
{
// @codeCoverageIgnoreStart
if (empty($config_files))
{
require_once _dir(CONF_DIR, 'config.php'); // $config
require_once _dir(CONF_DIR, 'base_config.php'); // $base_config
}
else // @codeCoverageIgnoreEnd
{
$config = $config_files['config'];
$base_config = $config_files['base_config'];
}
$this->config = array_merge($config, $base_config);
}
/**
* Getter for config values
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (isset($this->config[$key]))
{
return $this->config[$key];
}
return NULL;
}
}
// End of config.php

View File

@ -1,175 +0,0 @@
<?php
/**
* Routing logic
*/
namespace AnimeClient;
/**
* Basic routing/ dispatch
*/
class Router {
/**
* The route-matching object
* @var object $router
*/
protected $router;
/**
* The global configuration object
* @var object $config
*/
protected $config;
/**
* Array containing request and response objects
* @var array $web
*/
protected $web;
/**
* Constructor
*
* @param
*/
public function __construct(Config $config, \Aura\Router\Router $router, \Aura\Web\Request $request, \Aura\Web\Response $response)
{
$this->config = $config;
$this->router = $router;
$this->web = [$request, $response];
$this->_setup_routes();
}
/**
* Get the current route object, if one matches
*
* @return object
*/
public function get_route()
{
global $defaultHandler;
$raw_route = $_SERVER['REQUEST_URI'];
$route_path = str_replace([$this->config->anime_path, $this->config->manga_path], '', $raw_route);
$route_path = "/" . trim($route_path, '/');
$defaultHandler->addDataTable('Route Info', [
'route_path' => $route_path
]);
$route = $this->router->match($route_path, $_SERVER);
return $route;
}
/**
* Handle the current route
*
* @param [object] $route
* @return void
*/
public function dispatch($route = NULL)
{
global $defaultHandler;
if (is_null($route))
{
$route = $this->get_route();
}
if ( ! $route)
{
$failure = $this->router->getFailedRoute();
$defaultHandler->addDataTable('failed_route', (array)$failure);
$controller_name = 'BaseController';
$action_method = 'outputHTML';
$params = [
'template' => '404',
'data' => [
'title' => 'Page Not Found'
]
];
}
else
{
list($controller_name, $action_method) = $route->params['action'];
$params = (isset($route->params['params'])) ? $route->params['params'] : [];
if ( ! empty($route->tokens))
{
foreach($route->tokens as $key => $v)
{
if (array_key_exists($key, $route->params))
{
$params[$key] = $route->params[$key];
}
}
}
}
$controller = new $controller_name($this->config, $this->web);
// Run the appropriate controller method
$defaultHandler->addDataTable('controller_args', $params);
call_user_func_array([$controller, $action_method], $params);
}
/**
* Select controller based on the current url, and apply its relevent routes
*
* @return void
*/
private function _setup_routes()
{
$route_map = [
'anime' => '\\AnimeClient\\AnimeController',
'manga' => '\\AnimeClient\\MangaController',
];
$route_type = "anime";
if ($this->config->manga_host !== "" && strpos($_SERVER['HTTP_HOST'], $this->config->manga_host) !== FALSE)
{
$route_type = "manga";
}
else if ($this->config->manga_path !== "" && strpos($_SERVER['REQUEST_URI'], $this->config->manga_path) !== FALSE)
{
$route_type = "manga";
}
$routes = $this->config->routes;
// Add routes
foreach(['common', $route_type] as $key)
{
foreach($routes[$key] as $name => &$route)
{
$path = $route['path'];
unset($route['path']);
// Prepend the controller to the route parameters
array_unshift($route['action'], $route_map[$route_type]);
// Select the appropriate router method based on the http verb
$add = (array_key_exists('verb', $route)) ? "add" . ucfirst(strtolower($route['verb'])) : "addGet";
if ( ! array_key_exists('tokens', $route))
{
$this->router->$add($name, $path)->addValues($route);
}
else
{
$tokens = $route['tokens'];
unset($route['tokens']);
$this->router->$add($name, $path)
->addValues($route)
->addTokens($tokens);
}
}
}
}
}
// End of Router.php

View File

@ -1,134 +0,0 @@
<?php
/**
* Global functions
*/
/**
* Check if the user is currently logged in
*
* @return bool
*/
function is_logged_in()
{
return array_key_exists('hummingbird_anime_token', $_SESSION);
}
/**
* HTML selection helper function
*
* @param string $a - First item to compare
* @param string $b - Second item to compare
* @return string
*/
function is_selected($a, $b)
{
return ($a === $b) ? 'selected' : '';
}
/**
* Inverse of selected helper function
*
* @param string $a - First item to compare
* @param string $b - Second item to compare
* @return string
*/
function is_not_selected($a, $b)
{
return ($a !== $b) ? 'selected' : '';
}
/**
* Get the base url for css/js/images
*
* @return string
*/
function asset_url(/*...*/)
{
global $config;
$args = func_get_args();
$base_url = rtrim($config->asset_path, '/');
array_unshift($args, $base_url);
return implode("/", $args);
}
/**
* Get the base url from the config
*
* @param string $type - (optional) The controller
# @param object $config - (optional) Config
* @return string
*/
function base_url($type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
$path = ($config_path !== '') ? $config_path : "";
return implode("/", ['/', $host, $path]);
}
/**
* Generate full url path from the route path based on config
*
* @param string $path - (optional) The route path
* @param string $type - (optional) The controller (anime or manga), defaults to anime
# @param object $config - (optional) Config
* @return string
*/
function full_url($path="", $type="anime", $config=NULL)
{
if (is_null($config)) global $config;
$config_path = trim($config->{"{$type}_path"}, "/");
$config_host = $config->{"{$type}_host"};
$config_default_route = $config->{"default_{$type}_path"};
// Remove beginning/trailing slashes
$config_path = trim($config_path, '/');
$path = trim($path, '/');
// Remove any optional parameters from the route
$path = preg_replace('`{/.*?}`i', '', $path);
// Set the appropriate HTTP host
$host = ($config_host !== '') ? $config_host : $_SERVER['HTTP_HOST'];
// Set the default view
if ($path === '')
{
$path .= trim($config_default_route, '/');
if ($config->default_to_list_view) $path .= '/list';
}
// Set an leading folder
if ($config_path !== '')
{
$path = "{$config_path}/{$path}";
}
return "//{$host}/{$path}";
}
/**
* Get the last segment of the current url
*
* @return string
*/
function last_segment()
{
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', $path);
return end($segments);
}
// End of functions.php

View File

@ -1,43 +0,0 @@
<?php
/**
* Functions that need to be included before config
*/
/**
* Joins paths together. Variadic to take an
* arbitrary number of arguments
*
* @return string
*/
function _dir()
{
return implode(DIRECTORY_SEPARATOR, func_get_args());
}
/**
* Set up autoloaders
*
* @codeCoverageIgnore
* @return void
*/
function _setup_autoloaders()
{
require _dir(ROOT_DIR, '/vendor/autoload.php');
spl_autoload_register(function ($class) {
$class_parts = explode('\\', $class);
$class = end($class_parts);
$dirs = ["base", "controllers", "models"];
foreach($dirs as $dir)
{
$file = _dir(APP_DIR, $dir, "{$class}.php");
if (file_exists($file))
{
require_once $file;
return;
}
}
});
}

View File

@ -1,57 +1,195 @@
<?php
<?php declare(strict_types=1);
/**
* Hummingbird Anime List Client
*
* An API client for Kitsu to manage anime and manga watch lists
*
* PHP version 7.1
*
* @package HummingbirdAnimeClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2018 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.1
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
*/
namespace AnimeClient;
namespace Aviat\AnimeClient;
use \Whoops\Handler\PrettyPageHandler;
use \Whoops\Handler\JsonResponseHandler;
use \Aura\Web\WebFactory;
use \Aura\Router\RouterFactory;
use \GuzzleHttp\Client;
use \GuzzleHttp\Cookie\CookieJar;
use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\{
Anilist,
Kitsu,
Kitsu\KitsuRequestBuilder
};
use Aviat\AnimeClient\Model;
use Aviat\Banker\Pool;
use Aviat\Ion\Config;
use Aviat\Ion\Di\Container;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Zend\Diactoros\{Response, ServerRequestFactory};
// -----------------------------------------------------------------------------
// Setup error handling
// Setup DI container
// -----------------------------------------------------------------------------
$whoops = new \Whoops\Run();
return static function ($configArray = []) {
$container = new Container();
// Set up default handler for general errors
$defaultHandler = new PrettyPageHandler();
$whoops->pushHandler($defaultHandler);
// -------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------
// Set up json handler for ajax errors
$jsonHandler = new JsonResponseHandler();
$jsonHandler->onlyForAjaxRequests(true);
$whoops->pushHandler($jsonHandler);
$appLogger = new Logger('animeclient');
$appLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
$anilistRequestLogger = new Logger('anilist-request');
$anilistRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/anilist_request.log', Logger::NOTICE));
$kitsuRequestLogger = new Logger('kitsu-request');
$kitsuRequestLogger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/kitsu_request.log', Logger::NOTICE));
$container->setLogger($appLogger);
$container->setLogger($anilistRequestLogger, 'anilist-request');
$container->setLogger($kitsuRequestLogger, 'kitsu-request');
$whoops->register();
// -------------------------------------------------------------------------
// Injected Objects
// -------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Injected Objects
// -----------------------------------------------------------------------------
// Create Config Object
$container->set('config', static function() use ($configArray) {
return new Config($configArray);
});
// Create Config Object
$config = new Config();
require _dir(BASE_DIR, '/functions.php');
// Create Cache Object
$container->set('cache', static function($container) {
$logger = $container->getLogger();
$config = $container->get('config')->get('cache');
return new Pool($config, $logger);
});
// Create Aura Router Object
$router_factory = new RouterFactory();
$aura_router = $router_factory->newInstance();
// Create List Cache
// Create Request/Response Objects
$web_factory = new WebFactory([
'_GET' => $_GET,
'_POST' => $_POST,
'_COOKIE' => $_COOKIE,
'_SERVER' => $_SERVER,
'_FILES' => $_FILES
]);
$request = $web_factory->newRequest();
$response = $web_factory->newResponse();
// Create Aura Router Object
$container->set('aura-router', static function() {
return new RouterContainer;
});
// -----------------------------------------------------------------------------
// Router
// -----------------------------------------------------------------------------
$router = new Router($config, $aura_router, $request, $response);
$router->dispatch();
// Create Html helper Object
$container->set('html-helper', static function($container) {
$htmlHelper = (new HelperLocatorFactory)->newInstance();
$htmlHelper->set('menu', static function() use ($container) {
$menuHelper = new Helper\Menu();
$menuHelper->setContainer($container);
return $menuHelper;
});
$htmlHelper->set('field', static function() use ($container) {
$formHelper = new Helper\Form();
$formHelper->setContainer($container);
return $formHelper;
});
$htmlHelper->set('picture', static function() use ($container) {
$pictureHelper = new Helper\Picture();
$pictureHelper->setContainer($container);
return $pictureHelper;
});
return $htmlHelper;
});
// Create Request/Response Objects
$container->set('request', static function() {
return ServerRequestFactory::fromGlobals(
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
});
$container->set('response', static function() {
return new Response;
});
// Create session Object
$container->set('session', static function() {
return (new SessionFactory())->newInstance($_COOKIE);
});
// Miscellaneous helper methods
$container->set('util', static function($container) {
return new Util($container);
});
// Models
$container->set('kitsu-model', static function($container) {
$requestBuilder = new KitsuRequestBuilder();
$requestBuilder->setLogger($container->getLogger('kitsu-request'));
$listItem = new Kitsu\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new Kitsu\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
$cache = $container->get('cache');
$model->setCache($cache);
return $model;
});
$container->set('anilist-model', static function($container) {
$requestBuilder = new Anilist\AnilistRequestBuilder();
$requestBuilder->setLogger($container->getLogger('anilist-request'));
$listItem = new Anilist\ListItem();
$listItem->setContainer($container);
$listItem->setRequestBuilder($requestBuilder);
$model = new Anilist\Model($listItem);
$model->setContainer($container);
$model->setRequestBuilder($requestBuilder);
return $model;
});
$container->set('api-model', static function($container) {
return new Model\API($container);
});
$container->set('anime-model', static function($container) {
return new Model\Anime($container);
});
$container->set('manga-model', static function($container) {
return new Model\Manga($container);
});
$container->set('anime-collection-model', static function($container) {
return new Model\AnimeCollection($container);
});
$container->set('manga-collection-model', static function($container) {
return new Model\MangaCollection($container);
});
$container->set('settings-model', static function($container) {
$model = new Model\Settings($container->get('config'));
$model->setContainer($container);
return $model;
});
// Miscellaneous Classes
$container->set('auth', static function($container) {
return new Kitsu\Auth($container);
});
$container->set('url-generator', static function($container) {
return new UrlGenerator($container);
});
// -------------------------------------------------------------------------
// Dispatcher
// -------------------------------------------------------------------------
$container->set('dispatcher', static function($container) {
return new Dispatcher($container);
});
return $container;
};
// End of bootstrap.php

View File

@ -1,15 +0,0 @@
<?php
// ----------------------------------------------------------------------------
// Lower level configuration
//
// You shouldn't generally need to change anything below this line
// ----------------------------------------------------------------------------
$base_config = [
// Cache paths
'data_cache_path' => _dir(APP_DIR, 'cache'),
'img_cache_path' => _dir(ROOT_DIR, 'public/images'),
// Included config files
'routes' => require _dir(CONF_DIR, 'routes.php'),
'database' => require _dir(CONF_DIR, 'database.php'),
];

View File

@ -0,0 +1,22 @@
################################################################################
# Cache Setup #
################################################################################
# See https://git.timshomepage.net/aviat/banker for more information
# Available drivers are apcu, memcache, memcached, redis or null
# Null cache driver means no caching
driver = "redis"
[connection]
# Host or socket to connect to
host = "127.0.0.1"
# Connection port
#port = 6379
# Connection password
#password = ""
# Database number
database = 2

View File

@ -1,40 +0,0 @@
<?php
$config = [
// ----------------------------------------------------------------------------
// Username for anime and manga lists
// ----------------------------------------------------------------------------
'hummingbird_username' => 'timw4mail',
// ----------------------------------------------------------------------------
// General config
// ----------------------------------------------------------------------------
// do you wish to show the anime collection tab?
'show_anime_collection' => TRUE,
// path to public directory
'asset_path' => '//' . $_SERVER['HTTP_HOST'] . '/public',
// path to public directory on the server
'asset_dir' => __DIR__ . '/../../public',
// ----------------------------------------------------------------------------
// Routing
//
// Route by path, or route by domain. To route by path, set the _host suffixed
// options to an empty string. To route by host, set the _path suffixed options
// to an empty string
// ----------------------------------------------------------------------------
'anime_host' => 'anime.timshomepage.net',
'manga_host' => 'manga.timshomepage.net',
'anime_path' => '',
'manga_path' => '',
// Default pages for anime/manga
'default_anime_path' => '/watching',
'default_manga_path' => '/all',
// Default to list view?
'default_to_list_view' => FALSE,
];

View File

@ -0,0 +1,36 @@
################################################################################
# Main User Configuration #
################################################################################
# Username for anime and manga lists
kitsu_username = "johnsmith"
# Whose list is it?
whose_list = "Someone"
# do you wish to show the anime collection?
show_anime_collection = true
# do you wish to show the manga collection?
show_manga_collection = false
################################################################################
# Default views and paths
################################################################################
# Which list should be the default?
default_list = "anime" # anime or manga
# Default pages for anime/manga
default_anime_list_path = "watching" # watching|plan_to_watch|on_hold|dropped|completed|all
default_manga_list_path = "reading" # reading|plan_to_read|on_hold|dropped|completed|all
################################################################################
# Not on Settings Page
#
# These settings are not available to change on the settings page
################################################################################
# Use HTTPs for URLs
# It is not recommended to change this setting
secure_urls = true

View File

@ -1,13 +0,0 @@
<?php
return [
'collection' => [
'type' => 'sqlite',
'host' => '',
'user' => '',
'pass' => '',
'port' => '',
'name' => 'default',
'database' => '',
'file' => __DIR__ . '/../../anime_collection.sqlite',
]
];

View File

@ -0,0 +1,11 @@
################################################################################
# Database Configuration #
################################################################################
type = "sqlite"
host = ""
user = ""
pass = ""
port = ""
database = ""
file = "anime_collection.sqlite3"

View File

@ -1,60 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/* $config = */require 'config.php';
$config = (object)$config;
// Should we use myth to preprocess?
$use_myth = FALSE;
/*
|--------------------------------------------------------------------------
| CSS Folder
|--------------------------------------------------------------------------
|
| The folder where css files exist, in relation to the document root
|
*/
$css_root = $config->asset_dir. '/css/';
/*
|--------------------------------------------------------------------------
| Path from
|--------------------------------------------------------------------------
|
| Path fragment to rewrite in css files
|
*/
$path_from = '';
/*
|--------------------------------------------------------------------------
| Path to
|--------------------------------------------------------------------------
|
| The path fragment replacement for the css files
|
*/
$path_to = '';
/*
|--------------------------------------------------------------------------
| JS Folder
|--------------------------------------------------------------------------
|
| The folder where javascript files exist, in relation to the document root
|
*/
$js_root = $config->asset_dir. '/js/';

View File

@ -1,36 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/**
* This is the config array for css files to concatenate and minify
*/
return [
/*-----
Css
-----*/
/*
For each group create an array like so
'my_group' => array(
'path/to/css/file1.css',
'path/to/css/file2.css'
),
*/
'base' => [
'marx.css',
'base.css'
]
];
// End of css_groups.php

View File

@ -1,40 +0,0 @@
<?php
/**
* Easy Min
*
* Simple minification for better website performance
*
* @author Timothy J. Warren
* @copyright Copyright (c) 2012
* @link https://github.com/aviat4ion/Easy-Min
* @license http://philsturgeon.co.uk/code/dbad-license
*/
// --------------------------------------------------------------------------
/**
* This is the config array for javascript files to concatenate and minify
*/
return [
/*
For each group create an array like so
'my_group' => array(
'path/to/js/file1.js',
'path/to/js/file2.js'
),
*/
'table' => [
'lib/jquery.min.js',
'lib/table_sorter/jquery.tablesorter.min.js',
'sort_tables.js'
],
'edit' => [
'lib/jquery.min.js',
'show_message.js',
'anime_edit.js',
'manga_edit.js'
]
];
// End of js_groups.php

View File

@ -1,188 +0,0 @@
<?php
return [
// Routes on all controllers
'common' => [
'update' => [
'path' => '/update',
'action' => ['update'],
'verb' => 'post'
],
'login_form' => [
'path' => '/login',
'action' => ['login'],
'verb' => 'get'
],
'login_action' => [
'path' => '/login',
'action' => ['login_action'],
'verb' => 'post'
],
'logout' => [
'path' => '/logout',
'action' => ['logout']
],
],
// Routes on anime controller
'anime' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301'
]
],
'all' => [
'path' => '/all{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Anime List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'watching' => [
'path' => '/watching{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'currently-watching',
'title' => WHOSE . " Anime List &middot; Watching"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_watch' => [
'path' => '/plan_to_watch{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'plan-to-watch',
'title' => WHOSE . " Anime List &middot; Plan to Watch"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'on-hold',
'title' => WHOSE . " Anime List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'dropped',
'title' => WHOSE . " Anime List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed{/view}',
'action' => ['anime_list'],
'params' => [
'type' => 'completed',
'title' => WHOSE . " Anime List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'collection' => [
'path' => '/collection{/view}',
'action' => ['collection'],
'params' => [],
'tokens' => [
'view' => '[a-z_]+'
]
]
],
'manga' => [
'index' => [
'path' => '/',
'action' => ['redirect'],
'params' => [
'url' => '', // Determined by config
'code' => '301',
'type' => 'manga'
]
],
'all' => [
'path' => '/all{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'all',
'title' => WHOSE . " Manga List &middot; All"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'reading' => [
'path' => '/reading{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Reading',
'title' => WHOSE . " Manga List &middot; Reading"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'plan_to_read' => [
'path' => '/plan_to_read{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Plan to Read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'on_hold' => [
'path' => '/on_hold{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'On Hold',
'title' => WHOSE . " Manga List &middot; On Hold"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'dropped' => [
'path' => '/dropped{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Dropped',
'title' => WHOSE . " Manga List &middot; Dropped"
],
'tokens' => [
'view' => '[a-z_]+'
]
],
'completed' => [
'path' => '/completed{/view}',
'action' => ['manga_list'],
'params' => [
'type' => 'Completed',
'title' => WHOSE . " Manga List &middot; Completed"
],
'tokens' => [
'view' => '[a-z_]+'
]
]
]
];

View File

@ -1,121 +0,0 @@
<?php
/**
* Anime Controller
*/
namespace AnimeClient;
/**
* Controller for Anime-related pages
*/
class AnimeController extends BaseController {
/**
* The anime list model
* @var object $model
*/
protected $model;
/**
* The anime collection model
* @var object $collection_model
*/
private $collection_model;
/**
* Data to ve sent to all routes in this controller
* @var array $base_data
*/
protected $base_data;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Watching' => '/watching{/view}',
'Plan to Watch' => '/plan_to_watch{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'Collection' => '/collection{/view}',
'All' => '/all{/view}'
];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
parent::__construct($config, $web);
if ($this->config->show_anime_collection === FALSE)
{
unset($this->nav_routes['Collection']);
}
$this->model = new AnimeModel();
$this->collection_model = new AnimeCollectionModel();
$this->base_data = [
'message' => '',
'url_type' => 'anime',
'other_type' => 'manga',
'nav_routes' => $this->nav_routes,
];
}
/**
* Show a portion, or all of the anime list
*
* @param string $type - The section of the list
* @param string $title - The title of the page
* @return void
*/
public function anime_list($type, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($type != 'all')
? $this->model->get_list($type)
: $this->model->get_all_lists();
$this->outputHTML('anime/' . $view_map[$view], [
'title' => $title,
'sections' => $data
]);
}
/**
* Show the anime collection page
*
* @return void
*/
public function collection($view)
{
$view_map = [
'' => 'collection',
'list' => 'collection_list'
];
$data = $this->collection_model->get_collection();
$this->outputHTML('anime/' . $view_map[$view], [
'title' => WHOSE . " Anime Collection",
'sections' => $data
]);
}
/**
* Update an anime item
*
* @return bool
*/
public function update()
{
print_r($this->model->update($this->request->post->get()));
}
}
// End of AnimeController.php

View File

@ -1,87 +0,0 @@
<?php
/**
* Manga Controller
*/
namespace AnimeClient;
/**
* Controller for manga list
*/
class MangaController extends BaseController {
/**
* The manga model
* @var object $model
*/
protected $model;
/**
* Data to ve sent to all routes in this controller
* @var array $base_data
*/
protected $base_data;
/**
* Route mapping for main navigation
* @var array $nav_routes
*/
private $nav_routes = [
'Reading' => '/reading{/view}',
'Plan to Read' => '/plan_to_read{/view}',
'On Hold' => '/on_hold{/view}',
'Dropped' => '/dropped{/view}',
'Completed' => '/completed{/view}',
'All' => '/all{/view}'
];
/**
* Constructor
*/
public function __construct(Config $config, Array $web)
{
parent::__construct($config, $web);
$this->model = new MangaModel();
$this->base_data = [
'url_type' => 'manga',
'other_type' => 'anime',
'nav_routes' => $this->nav_routes
];
}
/**
* Update an anime item
*
* @return bool
*/
public function update()
{
$this->outputJSON($this->model->update($this->request->post->get()));
}
/**
* Get a section of the manga list
*
* @param string $status
* @param string $title
* @param string $view
* @return void
*/
public function manga_list($status, $title, $view)
{
$view_map = [
'' => 'cover',
'list' => 'list'
];
$data = ($status !== 'all')
? [$status => $this->model->get_list($status)]
: $this->model->get_all_lists();
$this->outputHTML('manga/' . $view_map[$view], [
'title' => $title,
'sections' => $data
]);
}
}
// End of MangaController.php

View File

@ -1,206 +0,0 @@
<?php
/**
* Anime Collection DB Model
*/
namespace AnimeClient;
/**
* Model for getting anime collection data
*/
class AnimeCollectionModel extends BaseDBModel {
/**
* Anime API Model
* @var object $anime_model
*/
private $anime_model;
/**
* Whether the database is valid for querying
* @var bool
*/
private $valid_database = FALSE;
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
$this->db = \Query($this->db_config['collection']);
$this->anime_model = new AnimeModel();
// Is database valid? If not, set a flag so the
// app can be run without a valid database
$db_file = file_get_contents($this->db_config['collection']['file']);
$this->valid_database = (strpos($db_file, 'SQLite format 3') === 0);
// Do an import if an import file exists
$this->json_import();
}
/**
* Get collection from the database, and organize by media type
*
* @return array
*/
public function get_collection()
{
$raw_collection = $this->_get_collection();
$collection = [];
foreach($raw_collection as $row)
{
if (array_key_exists($row['media'], $collection))
{
$collection[$row['media']][] = $row;
}
else
{
$collection[$row['media']] = [$row];
}
}
return $collection;
}
/**
* Get full collection from the database
*
* @return array
*/
private function _get_collection()
{
if ( ! $this->valid_database) return [];
$query = $this->db->select('hummingbird_id, slug, title, alternate_title, show_type, age_rating, episode_count, episode_length, cover_image, notes, media.type as media')
->from('anime_set a')
->join('media', 'media.id=a.media_id', 'inner')
->order_by('media')
->order_by('title')
->get();
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
/**
* Import anime into collection from a json file
*
* @return void
*/
private function json_import()
{
if ( ! file_exists('import.json')) return;
if ( ! $this->valid_database) return;
$anime = json_decode(file_get_contents("import.json"));
foreach($anime as $item)
{
$this->db->set([
'hummingbird_id' => $item->id,
'slug' => $item->slug,
'title' => $item->title,
'alternate_title' => $item->alternate_title,
'show_type' => $item->show_type,
'age_rating' => $item->age_rating,
'cover_image' => $this->get_cached_image($item->cover_image, $item->slug, 'anime'),
'episode_count' => $item->episode_count,
'episode_length' => $item->episode_length
])->insert('anime_set');
}
// Delete the import file
unlink('import.json');
// Update genre info
$this->update_genres();
}
/**
* Update genre information
*
* @return void
*/
private function update_genres()
{
$genres = [];
$flipped_genres = [];
$links = [];
// Get existing genres
$query = $this->db->select('id, genre')
->from('genres')
->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $genre)
{
$genres[$genre['id']] = $genre['genre'];
}
// Get existing link table entries
$query = $this->db->select('hummingbird_id, genre_id')
->from('genre_anime_set_link')
->get();
foreach($query->fetchAll(PDO::FETCH_ASSOC) as $link)
{
if (array_key_exists($link['hummingbird_id'], $links))
{
$links[$link['hummingbird_id']][] = $link['genre_id'];
}
else
{
$links[$link['hummingbird_id']] = [$link['genre_id']];
}
}
// Get the anime collection
$collection = $this->_get_collection();
foreach($collection as $anime)
{
// Get api information
$api = $this->anime_model->get_anime($anime['hummingbird_id']);
foreach($api['genres'] as $genre)
{
// Add genres that don't currently exist
if ( ! in_array($genre['name'], $genres))
{
$this->db->set('genre', $genre['name'])
->insert('genres');
$genres[] = $genre['name'];
}
// Update link table
// Get id of genre to put in link table
$flipped_genres = array_flip($genres);
$insert_array = [
'hummingbird_id' => $anime['hummingbird_id'],
'genre_id' => $flipped_genres[$genre['name']]
];
if (array_key_exists($anime['hummingbird_id'], $links))
{
if ( ! in_array($flipped_genres[$genre['name']], $links[$anime['hummingbird_id']]))
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
else
{
$this->db->set($insert_array)->insert('genre_anime_set_link');
}
}
}
}
}
// End of AnimeCollectionModel.php

View File

@ -1,245 +0,0 @@
<?php
/**
* Anime API Model
*/
namespace AnimeClient;
/**
* Model for handling requests dealing with the anime list
*/
class AnimeModel extends BaseApiModel {
/**
* The base url for api requests
* @var string $base_url
*/
protected $base_url = "https://hummingbird.me/api/v1/";
/**
* Constructor
*/
public function __construct()
{
parent::__construct();
}
/**
* Update the selected anime
*
* @param array $data
* @return array
*/
public function update($data)
{
$data['auth_token'] = $_SESSION['hummingbird_anime_token'];
$result = $this->client->post("libraries/{$data['id']}", [
'body' => $data
]);
return $result->json();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$output = [
'Watching' => [],
'Plan to Watch' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$data = $this->_get_list();
foreach($data as $datum)
{
switch($datum['status'])
{
case "completed":
$output['Completed'][] = $datum;
break;
case "plan-to-watch":
$output['Plan to Watch'][] = $datum;
break;
case "dropped":
$output['Dropped'][] = $datum;
break;
case "on-hold":
$output['On Hold'][] = $datum;
break;
case "currently-watching":
$output['Watching'][] = $datum;
break;
}
}
// Sort anime by name
foreach($output as &$status_list)
{
$this->sort_by_name($status_list);
}
return $output;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$map = [
'currently-watching' => 'Watching',
'plan-to-watch' => 'Plan to Watch',
'on-hold' => 'On Hold',
'dropped' => 'Dropped',
'completed' => 'Completed',
];
$data = $this->_get_list($status);
$this->sort_by_name($data);
$output = [];
$output[$map[$status]] = $data;
return $output;
}
/**
* Get information about an anime from its id
*
* @param string $anime_id
* @return array
*/
public function get_anime($anime_id)
{
$config = [
'query' => [
'id' => $anime_id
]
];
$response = $this->client->get("anime/{$anime_id}", $config);
return $response->json();
}
/**
* Search for anime by name
*
* @param string $name
* @return array
*/
public function search($name)
{
global $defaultHandler;
$config = [
'query' => [
'query' => $name
]
];
$response = $this->client->get('search/anime', $config);
$defaultHandler->addDataTable('anime_search_response', (array)$response);
if ($response->getStatusCode() != 200)
{
throw new Exception($response->getEffectiveUrl());
}
return $response->json();
}
/**
* Actually retreive the data from the api
*
* @param string $status - Status to filter by
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = "{$this->config->data_cache_path}/anime-{$status}.json";
$config = [
'allow_redirects' => FALSE
];
if ($status != "all")
{
$config['query']['status'] = $status;
}
$response = $this->client->get("users/{$this->config->hummingbird_username}/library", $config);
$defaultHandler->addDataTable('anime_list_response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$output = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
$output = $response->json();
$output_json = json_encode($output);
if (( ! file_exists($cache_file)) || file_get_contents($cache_file) !== $output_json)
{
// Attempt to create the cache folder if it doesn't exist
if ( ! is_dir($this->config->data_cache_path))
{
mkdir($this->config->data_cache_path);
}
// Cache the call in case of downtime
file_put_contents($cache_file, json_encode($output));
}
}
foreach($output as &$row)
{
$row['anime']['cover_image'] = $this->get_cached_image($row['anime']['cover_image'], $row['anime']['slug'], 'anime');
}
return $output;
}
/**
* Sort the list by title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['anime']['title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
// End of AnimeModel.php

View File

@ -1,193 +0,0 @@
<?php
/**
* Manga API Model
*/
namespace AnimeClient;
/**
* Model for handling requests dealing with the manga list
*/
class MangaModel extends BaseApiModel {
/**
* The base url for api requests
* @var string
*/
protected $base_url = "https://hummingbird.me/";
/**
* Update the selected manga
*
* @param array $data
* @return array
*/
public function update($data)
{
$id = $data['id'];
unset($data['id']);
$result = $this->client->put("manga_library_entries/{$id}", [
'cookies' => ['token' => $_SESSION['hummingbird_anime_token']],
'json' => ['manga_library_entry' => $data]
]);
return $result->json();
}
/**
* Get the full set of anime lists
*
* @return array
*/
public function get_all_lists()
{
$data = $this->_get_list();
foreach ($data as $key => &$val)
{
$this->sort_by_name($val);
}
return $data;
}
/**
* Get a category out of the full list
*
* @param string $status
* @return array
*/
public function get_list($status)
{
$data = $this->_get_list($status);
$this->sort_by_name($data);
return $data;
}
/**
* Massage the list of manga entries into something more usable
*
* @param string $status
* @return array
*/
private function _get_list($status="all")
{
global $defaultHandler;
$cache_file = _dir($this->config->data_cache_path, 'manga.json');
$config = [
'query' => [
'user_id' => $this->config->hummingbird_username
],
'allow_redirects' => FALSE
];
$response = $this->client->get('manga_library_entries', $config);
$defaultHandler->addDataTable('response', (array)$response);
if ($response->getStatusCode() != 200)
{
if ( ! file_exists($cache_file))
{
throw new Exception($response->getEffectiveUrl());
}
else
{
$raw_data = json_decode(file_get_contents($cache_file), TRUE);
}
}
else
{
// Reorganize data to be more usable
$raw_data = $response->json();
// Attempt to create the cache dir if it doesn't exist
if ( ! is_dir($this->config->data_cache_path))
{
mkdir($this->config->data_cache_path);
}
// Cache data in case of downtime
file_put_contents($cache_file, json_encode($raw_data));
}
// Bail out early if there isn't any manga data
if (empty($raw_data)) return [];
$data = [
'Reading' => [],
'Plan to Read' => [],
'On Hold' => [],
'Dropped' => [],
'Completed' => [],
];
$manga_data = [];
// Massage the two lists into one
foreach($raw_data['manga'] as $manga)
{
$manga_data[$manga['id']] = $manga;
}
// Filter data by status
foreach($raw_data['manga_library_entries'] as &$entry)
{
$entry['manga'] = $manga_data[$entry['manga_id']];
// Cache poster images
$entry['manga']['poster_image'] = $this->get_cached_image($entry['manga']['poster_image'], $entry['manga_id'], 'manga');
switch($entry['status'])
{
case "Plan to Read":
$data['Plan to Read'][] = $entry;
break;
case "Dropped":
$data['Dropped'][] = $entry;
break;
case "On Hold":
$data['On Hold'][] = $entry;
break;
case "Currently Reading":
$data['Reading'][] = $entry;
break;
case "Completed":
default:
$data['Completed'][] = $entry;
break;
}
}
//file_put_contents(_dir($this->config->data_cache_path, "manga-processed.json"), json_encode($data, JSON_PRETTY_PRINT));
return (array_key_exists($status, $data)) ? $data[$status] : $data;
}
/**
* Sort the manga entries by their title
*
* @param array $array
* @return void
*/
private function sort_by_name(&$array)
{
$sort = array();
foreach($array as $key => $item)
{
$sort[$key] = $item['manga']['romaji_title'];
}
array_multisort($sort, SORT_ASC, $array);
}
}
// End of MangaModel.php

View File

@ -1,7 +1,6 @@
<body>
<main>
<h1>404</h1>
<h2>Page Not Found</h2>
</main>
</body>
</html>
<main>
<h1>404</h1>
<h2><?= $message ?></h2>
<pre>(╯°□°)╯︵ ┻━┻
┬─┬ノ( º _ ºノ)</pre>
</main>

39
app/views/anime/add.php Normal file
View File

@ -0,0 +1,39 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Add Anime to your List</h2>
<form action="<?= $action_url ?>" method="post">
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for anime by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td><label for="status">Watching Status</label></td>
<td>
<select name="status" id="status">
<?php foreach($status_list as $status_key => $status_title): ?>
<option value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" name="type" value="anime" />
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -1,28 +0,0 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<section class="status">
<h2><?= $name ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<img src="<?= $item['cover_image'] ?>" />
<div class="name">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<br />({$item['alternate_title']})" : ""; ?>
</div>
<div class="table">
<div class="row">
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age_rating"><?= $item['age_rating'] ?></div>
</div>
</div>
</article>
</a>
<?php endforeach ?>
</section>
</section>
<?php endforeach ?>
</main>

View File

@ -1,43 +0,0 @@
<main>
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr>
<td class="align_left">
<a href="https://hummingbird.me/anime/<?= $item['slug'] ?>">
<?= $item['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['alternate_title'] ?></td>
<td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<td class="align_left"><?= $item['notes'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<br />
<?php endforeach ?>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="/public/js/table_sorter/jquery.tablesorter.min.js"></script>
<script>
$(function() {
$('table').tablesorter();
});
</script>

View File

@ -0,0 +1,91 @@
<article
class="media"
data-kitsu-id="<?= $item['id'] ?>"
data-mal-id="<?= $item['mal_id'] ?>"
>
<?php if ($auth->isAuthenticated()): ?>
<button title="Increment episode count" class="plus-one" hidden>+1 Episode</button>
<?php endif ?>
<?= $helper->picture("images/anime/{$item['anime']['id']}.webp") ?>
<div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
<span class="canonical"><?= $item['anime']['title'] ?></span>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br/>
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($item['private'] || $item['rewatching']): ?>
<div class="row">
<?php foreach (['private', 'rewatching'] as $attr): ?>
<?php if ($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['rewatched'] > 0): ?>
<div class="row">
<div>Rewatched <?= $item['rewatched'] ?> time(s)</div>
</div>
<?php endif ?>
<?php if (count($item['anime']['streaming_links']) > 0): ?>
<div class="row">
<?php foreach ($item['anime']['streaming_links'] as $link): ?>
<div class="cover-streaming-link">
<?php if ($link['meta']['link']): ?>
<a href="<?= $link['link'] ?>"
title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
'alt' => "{$link['meta']['name']} logo",
]); ?>
</a>
<?php else: ?>
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 20,
'height' => 20,
'alt' => "{$link['meta']['name']} logo",
]); ?>
<?php endif ?>
</div>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($auth->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed" title="Edit information about this anime" href="<?=
$url->generate('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
]);
?>">Edit</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
<div class="completion">Episodes:
<span class="completed_number"><?= $item['episodes']['watched'] ?></span> /
<span class="total_number"><?= $item['episodes']['total'] ?></span>
</div>
</div>
<div class="row">
<div class="media_type"><?= $escape->html($item['anime']['show_type']) ?></div>
<div class="airing-status"><?= $escape->html($item['airing']['status']) ?></div>
<div class="age-rating"><?= $escape->html($item['anime']['age_rating']) ?></div>
</div>
</div>
</article>

View File

@ -1,40 +1,30 @@
<main>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<?php if (empty($items)): ?>
<section class="status">
<h2><?= $name ?></h2>
<h2><?= $escape->html($name) ?></h2>
<h3>There's nothing here!</h3>
</section>
<?php else: ?>
<section class="status">
<h2><?= $escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<article class="media" id="a-<?= $item['anime']['id'] ?>">
<?php if (is_logged_in()): ?>
<button class="plus_one" hidden>+1 Episode</button>
<?php endif ?>
<img src="<?= $item['anime']['cover_image'] ?>" />
<div class="name">
<a href="<?= $item['anime']['url'] ?>">
<?= $item['anime']['title'] ?>
<?= ($item['anime']['alternate_title'] != "") ? "<br />({$item['anime']['alternate_title']})" : ""; ?>
</a>
</div>
<div class="table">
<div class="row">
<div class="user_rating">Rating: <?= ($item['rating']['value'] > 0) ? (int)($item['rating']['value'] * 2) : " - " ?> / 10</div>
<div class="completion">Episodes:
<span class="completed_number"><?= $item['episodes_watched'] ?></span> /
<span class="total_number"><?= ($item['anime']['episode_count'] != 0) ? $item['anime']['episode_count'] : "-" ?></span>
</div>
</div>
<div class="row">
<div class="media_type"><?= $item['anime']['show_type'] ?></div>
<div class="airing_status"><?= $item['anime']['status'] ?></div>
<div class="age_rating"><?= $item['anime']['age_rating'] ?></div>
</div>
</div>
</article>
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
<?php include __DIR__ . '/cover-item.php' ?>
<?php endforeach ?>
</section>
</section>
<?php endif ?>
<?php endforeach ?>
</main>
<?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
<?php endif ?>
</main>

179
app/views/anime/details.php Normal file
View File

@ -0,0 +1,179 @@
<?php use function Aviat\AnimeClient\getLocalImg; ?>
<main class="details fixed">
<section class="flex">
<aside class="info">
<?= $helper->picture("images/anime/{$data['id']}-original.webp") ?>
<br />
<table class="media-details">
<tr>
<td class="align-right">Airing Status</td>
<td><?= $data['status'] ?></td>
</tr>
<tr>
<td>Show Type</td>
<td><?= $data['show_type'] ?></td>
</tr>
<tr>
<td>Episode Count</td>
<td><?= $data['episode_count'] ?? '-' ?></td>
</tr>
<?php if ( ! empty($data['episode_length'])): ?>
<tr>
<td>Episode Length</td>
<td><?= $data['episode_length'] ?> minutes</td>
</tr>
<?php endif ?>
<?php if ( ! empty($data['age_rating'])): ?>
<tr>
<td>Age Rating</td>
<td><abbr title="<?= $data['age_rating_guide'] ?>"><?= $data['age_rating'] ?></abbr>
</td>
</tr>
<?php endif ?>
<tr>
<td>Genres</td>
<td>
<?= implode(', ', $data['genres']) ?>
</td>
</tr>
</table>
<br />
</aside>
<article class="text">
<h2 class="toph"><a rel="external" href="<?= $data['url'] ?>"><?= $data['title'] ?></a></h2>
<?php foreach ($data['titles'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
<br />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
</div>
<?php if (count($data['streaming_links']) > 0): ?>
<hr />
<h4>Streaming on:</h4>
<table class="full-width invisible streaming-links">
<thead>
<tr>
<th class="align-left">Service</th>
<th>Subtitles</th>
<th>Dubs</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['streaming_links'] as $link): ?>
<tr>
<td class="align-left">
<?php if ($link['meta']['link'] !== FALSE): ?>
<a
href="<?= $link['link'] ?>"
title="Stream '<?= $data['title'] ?>' on <?= $link['meta']['name'] ?>"
>
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]); ?>
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
</a>
<?php else: ?>
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]); ?>
&nbsp;&nbsp;<?= $link['meta']['name'] ?>
<?php endif ?>
</td>
<td><?= implode(', ', $link['subs']) ?></td>
<td><?= implode(', ', $link['dubs']) ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php if ( ! empty($data['trailer_id'])): ?>
<div class="responsive-iframe">
<h4>Trailer</h4>
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/<?= $data['trailer_id'] ?>"
frameborder="0"
allow="autoplay; encrypted-media"
allowfullscreen
></iframe>
</div>
<?php endif ?>
</article>
</section>
<?php if (count($data['characters']) > 0): ?>
<section>
<h2>Characters</h2>
<div class="tabs">
<?php $i = 0 ?>
<?php foreach ($data['characters'] as $role => $list): ?>
<input
type="radio" name="character-types"
id="character-types-<?= $i ?>" <?= ($i === 0) ? 'checked' : '' ?> />
<label for="character-types-<?= $i ?>"><?= ucfirst($role) ?></label>
<section class="content media-wrap flex flex-wrap flex-justify-start">
<?php foreach ($list as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="<?= $role === 'supporting' ? 'small-' : '' ?>character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
<div class="name">
<?= $helper->a($link, $char['name']); ?>
</div>
<a href="<?= $link ?>">
<?= $helper->picture("images/characters/{$id}.webp") ?>
</a>
</article>
<?php endif ?>
<?php endforeach ?>
</section>
<?php $i++; ?>
<?php endforeach ?>
</div>
</section>
<?php endif ?>
<?php if (count($data['staff']) > 0): ?>
<section>
<h2>Staff</h2>
<div class="vertical-tabs">
<?php $i = 0; ?>
<?php foreach ($data['staff'] as $role => $people): ?>
<div class="tab">
<input type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $pid => $person): ?>
<article class='character small-person'>
<?php $link = $url->generate('person', ['id' => $person['id']]) ?>
<div class="name">
<a href="<?= $link ?>">
<?= $person['name'] ?>
</a>
</div>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($person['image']['original'] ?? NULL)) ?>
</a>
</article>
<?php endforeach ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
</div>
</section>
<?php endif ?>
</main>

View File

@ -1,8 +1,112 @@
<body>
<?php include 'nav.php' ?>
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Edit Anime List Item</h2>
<form action="<?= $action ?>" method="post">
<table class="invisible form">
<thead>
<tr>
<th>
<h3><?= $escape->html($item['anime']['title']) ?></h3>
<?php foreach($item['anime']['titles'] as $title): ?>
<h4><?= $escape->html($title) ?></h4>
<?php endforeach ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="9">
<?= $helper->picture("images/anime/{$item['anime']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
</td>
</tr>
<tr>
<td><label for="private">Is Private?</label></td>
<td>
<input type="checkbox" name="private" id="private"
<?php if($item['private']): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="watching_status">Watching Status</label></td>
<td>
<select name="watching_status" id="watching_status">
<?php foreach($statuses as $status_key => $status_title): ?>
<option <?php if($item['watching_status'] === $status_key): ?>selected="selected"<?php endif ?>
value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="series_rating">Rating</label></td>
<td>
<input type="number" min="0" max="10" maxlength="2" name="user_rating" id="series_rating" value="<?= $item['user_rating'] ?>" id="series_rating" size="2" /> / 10
</td>
</tr>
<tr>
<td><label for="episodes_watched">Episodes Watched</label></td>
<td>
<input type="number" min="0" size="4" maxlength="4" value="<?= $item['episodes']['watched'] ?>" name="episodes_watched" id="episodes_watched" />
<?php if($item['episodes']['total'] > 0): ?>
/ <?= $item['episodes']['total'] ?>
<?php endif ?>
</td>
</tr>
<tr>
<td><label for="rewatching_flag">Rewatching?</label></td>
<td>
<input type="checkbox" name="rewatching" id="rewatching_flag"
<?php if($item['rewatching'] === TRUE): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="rewatched">Rewatch Count</label></td>
<td>
<input type="number" min="0" id="rewatched" name="rewatched" value="<?= $item['rewatched'] ?>" />
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td>
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<?php if ( ! empty($item['mal_id'])): ?>
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
<?php endif ?>
<input type="hidden" value="true" name="edit" />
<button type="submit">Submit</button>
</td>
</tr>
</tbody>
</table>
</form>
<form class="js-delete" action="<?= $url->generate('anime.delete') ?>" method="post">
<fieldset>
<legend>Danger Zone</legend>
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<?php if (!empty($item['mal_id'])): ?>
<input type="hidden" value="<?= $item['mal_id'] ?? '' ?>" name="mal_id" />
<?php endif ?>
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</main>
</body>
</html>
<?php endif ?>

View File

@ -1,36 +1,110 @@
<main>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Alternate Title</th>
<th>Airing Status</th>
<th>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="a-<?= $item['anime']['id'] ?>">
<td class="align_left">
<a href="<?= $item['anime']['url'] ?>">
<?= $item['anime']['title'] ?>
</a>
</td>
<td class="align_left"><?= $item['anime']['alternate_title'] ?></td>
<td class="align_left"><?= $item['anime']['status'] ?></td>
<td><?= (int)($item['rating']['value'] * 2) ?> / 10 </td>
<td><?= $item['anime']['show_type'] ?></td>
<td>Episodes: <?= $item['episodes_watched'] ?> / <?= $item['anime']['episode_count'] ?></td>
<td><?= $item['anime']['age_rating'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php if (empty($items)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<table class='media-wrap'>
<thead>
<tr>
<?php if($auth->isAuthenticated()): ?>
<td class="no-border">&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th>Airing Status</th>
<th>Score</th>
<th>Type</th>
<th>Progress</th>
<th>Rated</th>
<th colspan="2">Attributes</th>
<th>Notes</th>
<th>Genres</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->isAuthenticated()) continue; ?>
<tr id="a-<?= $item['id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $url->generate('edit', [
'controller' => 'anime',
'id' => $item['id'],
'status' => $item['watching_status']
]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left justify">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]) ?>">
<?= $item['anime']['title'] ?>
</a>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br/><?= $title ?>
<?php endforeach ?>
</td>
<td><?= $item['airing']['status'] ?></td>
<td><?= $item['user_rating'] ?> / 10 </td>
<td><?= $item['anime']['show_type'] ?></td>
<td id="<?= $item['anime']['slug'] ?>">
Episodes: <br />
<span class="completed_number"><?= $item['episodes']['watched'] ?></span>&nbsp;/&nbsp;<span class="total_number"><?= $item['episodes']['total'] ?></span>
</td>
<td><?= $item['anime']['age_rating'] ?></td>
<td>
<ul>
<?php if ($item['rewatched'] > 0): ?>
<li>Rewatched <?= $item['rewatched'] ?> time(s)</li>
<?php endif ?>
<?php foreach(['private','rewatching'] as $attr): ?>
<?php if($item[$attr]): ?>
<li><?= ucfirst($attr); ?></li>
<?php endif ?>
<?php endforeach ?>
</ul>
</td>
<td>
<?php foreach($item['anime']['streaming_links'] as $link): ?>
<?php if ($link['meta']['link'] !== FALSE): ?>
<a href="<?= $link['link'] ?>" title="Stream '<?= $item['anime']['title'] ?>' on <?= $link['meta']['name'] ?>">
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]); ?>
</a>
<?php else: ?>
<?= $helper->picture("images/{$link['meta']['image']}", 'svg', [
'class' => 'streaming-logo',
'width' => 50,
'height' => 50,
'alt' => "{$link['meta']['name']} logo",
]); ?>
<?php endif ?>
<?php endforeach ?>
</td>
<td>
<p><?= $escape->html($item['notes']) ?></p>
</td>
<td class="align-left">
<?php sort($item['anime']->genres) ?>
<?= implode(', ', $item['anime']->genres) ?>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<script src="<?= asset_url('js.php?g=table') ?>"></script>
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>

3
app/views/blank.php Normal file
View File

@ -0,0 +1,3 @@
<main>
<h1><?= $title ?></h1>
</main>

View File

@ -0,0 +1,224 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="character-page details fixed">
<section class="flex flex-no-wrap">
<aside>
<?= $helper->picture("images/characters/{$data['id']}-original.webp") ?>
</aside>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
<?php foreach ($data['names'] as $name): ?>
<h3><?= $name ?></h3>
<?php endforeach ?>
<?php if ( ! empty($data['otherNames'])): ?>
<h4>Also Known As:</h4>
<ul>
<?php foreach ($data['otherNames'] as $name): ?>
<li><h5><?= $name ?></h5></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<br />
<hr />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['description']) ?></p>
</div>
</div>
</section>
<?php if ( ! (empty($data['media']['anime']) || empty($data['media']['manga']))): ?>
<h3>Media</h3>
<div class="tabs">
<?php if ( ! empty($data['media']['anime'])): ?>
<input checked="checked" type="radio" id="media-anime" name="media-tabs" />
<label for="media-anime">Anime</label>
<section class="media-wrap content">
<?php foreach ($data['media']['anime'] as $id => $anime): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $anime['attributes']['slug']]);
$titles = Kitsu::filterTitles($anime['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/anime/{$id}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php endif ?>
<?php if ( ! empty($data['media']['manga'])): ?>
<input type="radio" id="media-manga" name="media-tabs" />
<label for="media-manga">Manga</label>
<section class="media-wrap content">
<?php foreach ($data['media']['manga'] as $id => $manga): ?>
<article class="media">
<?php
$link = $url->generate('manga.details', ['id' => $manga['attributes']['slug']]);
$titles = Kitsu::filterTitles($manga['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/manga/{$id}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php endif ?>
</div>
<?php endif ?>
<section>
<?php if (count($data['castings']) > 0): ?>
<h3>Castings</h3>
<?php
$vas = $data['castings']['Voice Actor'];
unset($data['castings']['Voice Actor']);
ksort($vas)
?>
<?php foreach ($data['castings'] as $role => $entries): ?>
<h4><?= $role ?></h4>
<?php foreach ($entries as $language => $casting): ?>
<h5><?= $language ?></h5>
<table class="min-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td>
<article class="character">
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'], TRUE)) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td>
<section class="align-left media-wrap">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php endforeach ?>
<?php endforeach ?>
<?php if ( ! empty($vas)): ?>
<h4>Voice Actors</h4>
<div class="tabs">
<?php $i = 0; ?>
<?php foreach ($vas as $language => $casting): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="character-va<?= $i ?>"
name="character-vas"
/>
<label for="character-va<?= $i ?>"><?= $language ?></label>
<section class="content">
<table class="borderless max-table">
<tr>
<th>Cast Member</th>
<th>Series</th>
</tr>
<?php foreach ($casting as $cid => $c): ?>
<tr>
<td>
<article class="character">
<?php
$link = $url->generate('person', ['id' => $c['person']['id']]);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($c['person']['image'])) ?>
<div class="name">
<?= $c['person']['name'] ?>
</div>
</a>
</article>
</td>
<td width="75%">
<section class="align-left media-wrap-flex">
<?php foreach ($c['series'] as $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['attributes']['slug']]);
$titles = Kitsu::filterTitles($series['attributes']);
?>
<a href="<?= $link ?>">
<?= $helper->picture(getLocalImg($series['attributes']['posterImage']['small'], TRUE)) ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach ?>
</table>
</section>
<?php $i++ ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php endif ?>
</section>
</main>

View File

@ -0,0 +1,42 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Add <?= ucfirst($collection_type) ?> to your Collection</h2>
<form action="<?= $action_url ?>" method="post">
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for <?= $collection_type ?> by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" name="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td><label for="media_id">Media</label></td>
<td>
<select name="media_id" id="media_id">
<?php foreach($media_items as $id => $name): ?>
<option value="<?= $id ?>"><?= $name ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td><textarea id="notes" name="notes"></textarea></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -0,0 +1,26 @@
<article class="media" id="a-<?= $item['hummingbird_id'] ?>">
<?= $helper->picture("images/anime/{$item['hummingbird_id']}.webp") ?>
<div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?>
<?= ($item['alternate_title'] != "") ? "<small><br />{$item['alternate_title']}</small>" : ""; ?>
</a>
</div>
<div class="table">
<?php if ($auth->isAuthenticated()): ?>
<div class="row">
<span class="edit">
<a class="bracketed"
href="<?= $url->generate($collection_type . '.collection.edit.get', [
'id' => $item['hummingbird_id']
]) ?>">Edit</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div class="completion">Episodes: <?= $item['episode_count'] ?></div>
<div class="media_type"><?= $item['show_type'] ?></div>
<div class="age-rating"><?= $item['age_rating'] ?></div>
</div>
</div>
</article>

View File

@ -0,0 +1,40 @@
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<div class="tabs">
<?php $i = 0; ?>
<?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>" name="collection-tabs" />
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height">
<section class="media-wrap">
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?>
<?php endforeach ?>
</section>
</div>
<?php $i++; ?>
<?php endforeach ?>
<!-- All Tab -->
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' />
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class='content full-height'>
<?php foreach ($sections as $name => $items): ?>
<h3><?= $name ?></h3>
<section class="media-wrap">
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/cover-item.php'; ?>
<?php endforeach ?>
</section>
<?php endforeach ?>
</div>
</div>
<?php endif ?>
</main>

View File

@ -0,0 +1,69 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Edit Anime Collection Item</h2>
<form action="<?= $action_url ?>" method="post">
<table class="invisible form">
<tbody>
<tr>
<td rowspan="6" class="align-center">
<?= $helper->picture("images/anime/{$item['hummingbird_id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
</td>
</tr>
<tr>
<td class="align-right"><label for="title">Title</label></td>
<td class="align-left">
<input type="text" id="title" name="title" value="<?= $item['title'] ?>" />
</td>
</tr>
<tr>
<td class="align-right"><label for="alternate_title">Alternate Title</label></td>
<td class="align-left">
<input type="text" id="alternate_title" name="alternate_title" value="<?= $item['alternate_title'] ?>"/>
</td>
</tr>
<tr>
<td class="align-right"><label for="media_id">Media</label></td>
<td class="align-left">
<select name="media_id" id="media_id">
<?php foreach($media_items as $id => $name): ?>
<option <?= $item['media_id'] === $id ? 'selected="selected"' : '' ?> value="<?= $id ?>"><?= $name ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td><textarea id="notes" name="notes"><?= $escape->html($item['notes']) ?></textarea></td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<?php if($action === 'Edit'): ?>
<input type="hidden" name="hummingbird_id" value="<?= $item['hummingbird_id'] ?>" />
<?php endif ?>
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
<form class="js-delete" action="<?= $url->generate($collection_type . '.collection.delete') ?>" method="post">
<fieldset>
<legend>Danger Zone</legend>
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['hummingbird_id'] ?>" name="hummingbird_id" />
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</form>
</main>
<?php endif ?>

View File

@ -0,0 +1,20 @@
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>
<a class="bracketed"
href="<?= $url->generate($collection_type . '.collection.edit.get', ['id' => $item['hummingbird_id']]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left">
<a href="<?= $url->generate('anime.details', ['id' => $item['slug']]) ?>">
<?= $item['title'] ?>
</a>
<?= ! empty($item['alternate_title']) ? ' <br /><small> ' . $item['alternate_title'] . '</small>' : '' ?>
</td>
<td><?= $item['episode_count'] ?></td>
<td><?= $item['episode_length'] ?></td>
<td><?= $item['show_type'] ?></td>
<td><?= $item['age_rating'] ?></td>
<td class="align-left"><?= implode(', ', $item['genres']) ?></td>
<td class="align-left"><?= $item['notes'] ?></td>
</tr>

View File

@ -0,0 +1,74 @@
<main>
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate($collection_type . '.collection.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php $i = 0; ?>
<div class="tabs">
<?php foreach ($sections as $name => $items): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="collection-tab-<?= $i ?>"
name="collection-tabs"/>
<label for="collection-tab-<?= $i ?>"><h2><?= $name ?></h2></label>
<div class="content full-height">
<table class="full-width media-wrap">
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>Actions</td>
<?php endif ?>
<th>Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Genres</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/list-item.php' ?>
<?php endforeach ?>
</tbody>
</table>
</div>
<?php $i++ ?>
<?php endforeach ?>
<!-- All -->
<input type='radio' id='collection-tab-<?= $i ?>' name='collection-tabs' />
<label for='collection-tab-<?= $i ?>'><h2>All</h2></label>
<div class="content full-height">
<?php foreach ($sections as $name => $items): ?>
<h3><?= $name ?></h3>
<table class="full-width media-wrap">
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>Actions</td>
<?php endif ?>
<th>Title</th>
<th>Episode Count</th>
<th>Episode Length</th>
<th>Show Type</th>
<th>Age Rating</th>
<th>Genres</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<?php include __DIR__ . '/list-item.php' ?>
<?php endforeach ?>
</tbody>
</table>
<?php endforeach; ?>
</div>
</div>
<?php endif ?>
</main>
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>

5
app/views/error.php Normal file
View File

@ -0,0 +1,5 @@
<main>
<h1><?= $title ?></h1>
<h2><?= $message ?></h2>
<div><?= $long_message ?></div>
</main>

View File

@ -1,2 +1,22 @@
<section id="loading-shadow" hidden="hidden">
<div class="loading-wrapper">
<div class="loading-content">
<h3>Updating List Item...</h3>
<div class="cssload-loader">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
</div>
</div>
</section>
<script nomodule="nomodule" src="https://polyfill.io/v3/polyfill.min.js?features=es5%2CObject.assign"></script>
<?php if ($auth->isAuthenticated()): ?>
<script nomodule='nomodule' async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts-authed.min.js') ?>"></script>
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index-authed.js') ?>"></script>
<?php else: ?>
<script nomodule="nomodule" async="async" defer="defer" src="<?= $urlGenerator->assetUrl('js/scripts.min.js') ?>"></script>
<script type="module" src="<?= $urlGenerator->assetUrl('js/src/index.js') ?>"></script>
<?php endif ?>
</body>
</html>
</html>

View File

@ -1,35 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title><?= $title ?></title>
<meta charset="utf-8" />
<link rel="stylesheet" href="<?= asset_url('css.php?g=base') ?>" />
<script>
var BASE_URL = "<?= base_url($url_type) ?>";
var CONTROLLER = "<?= $url_type ?>";
</script>
<meta http-equiv="cache-control" content="no-store" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=1" />
<?php if ($config->get('theme') !== 'auto'): ?>
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/app.min.css') ?>" />
<?php elseif ($config->get('theme') === 'auto'): ?>
<link rel="stylesheet" href="<?= $urlGenerator->assetUrl('css/dark-auto.min.css') ?>" />
<?php endif ?>
<link rel="<?= $config->get('theme') === 'dark' ? '' : 'alternate ' ?>stylesheet" title="Dark Theme" href="<?= $urlGenerator->assetUrl('css/dark.min.css') ?>" />
<link rel="icon" href="<?= $urlGenerator->assetUrl('images/icons/favicon.ico') ?>" />
<link rel="apple-touch-icon" sizes="57x57" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-57x57.png') ?>">
<link rel="apple-touch-icon" sizes="60x60" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-60x60.png') ?>">
<link rel="apple-touch-icon" sizes="72x72" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-72x72.png') ?>">
<link rel="apple-touch-icon" sizes="76x76" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-76x76.png') ?>">
<link rel="apple-touch-icon" sizes="114x114" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-114x114.png') ?>">
<link rel="apple-touch-icon" sizes="120x120" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-120x120.png') ?>">
<link rel="apple-touch-icon" sizes="144x144" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-144x144.png') ?>">
<link rel="apple-touch-icon" sizes="152x152" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-152x152.png') ?>">
<link rel="apple-touch-icon" sizes="180x180" href="<?= $urlGenerator->assetUrl('images/icons/apple-icon-180x180.png') ?>">
<link rel="icon" type="image/png" sizes="192x192" href="<?= $urlGenerator->assetUrl('images/icons/android-icon-192x192.png') ?>">
<link rel="icon" type="image/png" sizes="32x32" href="<?= $urlGenerator->assetUrl('images/icons/favicon-32x32.png') ?>">
<link rel="icon" type="image/png" sizes="96x96" href="<?= $urlGenerator->assetUrl('images/icons/favicon-96x96.png') ?>">
<link rel="icon" type="image/png" sizes="16x16" href="<?= $urlGenerator->assetUrl('images/icons/favicon-16x16.png') ?>">
</head>
<body class="<?= $url_type ?> list">
<h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1"><?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> [<a href="<?= full_url("", $other_type) ?>"><?= ucfirst($other_type) ?> List</a>]</span>
<span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?>
[<a href="<?= full_url("/logout", $url_type) ?>">Logout</a>]
<?php else: ?>
[<a href="<?= full_url("/login", $url_type) ?>"><?= WHOSE ?> Login</a>]
<?php endif ?>
</span>
</h1>
<nav>
<ul>
<?php foreach($nav_routes as $title => $nav_path): ?>
<li class="<?= is_selected($nav_path, $route_path) ?>"><a href="<?= full_url($nav_path, $url_type) ?>"><?= $title ?></a></li>
<?php endforeach ?>
</ul>
<br />
<ul>
<li class="<?= is_not_selected('list', last_segment()) ?>"><a href="<?= full_url($route_path, $url_type) ?>">Cover View</a></li>
<li class="<?= is_selected('list', last_segment()) ?>"><a href="<?= full_url("{$route_path}/list", $url_type) ?>">List View</a></li>
</ul>
</nav>
<br />
<body class="<?= $escape->attr($url_type) ?> list">
<?php include 'setup-check.php' ?>
<header>
<?php
include 'main-menu.php';
if(isset($message) && is_array($message))
{
foreach($message as $m)
{
$message = $m['message'];
$message_type = $m['message_type'];
include 'message.php';
}
}
?>
</header>

View File

@ -1,17 +1,16 @@
<main>
<h2><?= $config->get('whose_list'); ?>'s Login</h2>
<?= $message ?>
<aside>
<form method="post" action="<?= full_url('/login', $url_type) ?>">
<dl>
<dt><label for="username">Username: </label></dt>
<dd><input type="text" id="username" name="username" required="required" /></dd>
<dt><label for="password">Password: </label></dt>
<dd><input type="password" id="password" name="password" required="required" /></dd>
<dt>&nbsp;</dt>
<dd><input type="submit" value="Login" /></dd>
</dl>
</form>
</aside>
<form method="post" action="<?= $url->generate('login.post') ?>">
<table class="form invisible">
<tr>
<td><label for="password">Password: </label></td>
<td><input type="password" id="password" name="password" required="required" /></td>
</tr>
<tr>
<td>&nbsp;</td>
<td><button type="submit">Login</button></td>
</tr>
</table>
</form>
</main>

88
app/views/main-menu.php Normal file
View File

@ -0,0 +1,88 @@
<?php declare(strict_types=1);
namespace Aviat\AnimeClient;
$whose = $config->get('whose_list') . "'s ";
$lastSegment = $urlGenerator->lastSegment();
$extraSegment = $lastSegment === 'list' ? '/list' : '';
$hasAnime = stripos($_SERVER['REQUEST_URI'], 'anime') !== FALSE;
$hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
?>
<div id="main-nav" class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1">
<?php if(strpos($route_path, 'collection') === FALSE): ?>
<?= $helper->a(
$urlGenerator->defaultUrl($url_type),
$whose . ucfirst($url_type) . ' List'
) ?>
<?php if($config->get("show_{$url_type}_collection")): ?>
[<?= $helper->a(
$url->generate("{$url_type}.collection.view") . $extraSegment,
ucfirst($url_type) . ' Collection'
) ?>]
<?php endif ?>
<?php if($config->get("show_{$other_type}_collection")): ?>
[<?= $helper->a(
$url->generate("{$other_type}.collection.view") . $extraSegment,
ucfirst($other_type) . ' Collection'
) ?>]
<?php endif ?>
[<?= $helper->a(
$urlGenerator->defaultUrl($other_type) . $extraSegment,
ucfirst($other_type) . ' List'
) ?>]
<?php else: ?>
<?= $whose . ucfirst($url_type) . ' Collection' ?>
<?php if($config->get("show_{$other_type}_collection")): ?>
[<?= $helper->a(
$url->generate("{$other_type}.collection.view") . $extraSegment,
ucfirst($other_type) . ' Collection'
) ?>]
<?php endif ?>
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
<?php endif ?>
<?php if ($auth->isAuthenticated() && $config->get(['cache', 'driver']) !== 'null'): ?>
<span class="flex-no-wrap small-font">
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
</span>
<?php endif ?>
</span>
<span class="flex-no-wrap small-font">[<?= $helper->a(
$url->generate('default_user_info'),
'About '. $config->get('whose_list')
) ?>]</span>
<?php if ($auth->isAuthenticated()): ?>
<span class="flex-no-wrap small-font">
<?= $helper->a(
$url->generate('settings'),
'Settings',
['class' => 'bracketed']
) ?>
</span>
<span class="flex-no-wrap small-font">
<?= $helper->a(
$url->generate('logout'),
'Logout',
['class' => 'bracketed']
) ?>
</span>
<?php else: ?>
<span class="flex-no-wrap small-font">
[<?= $helper->a($url->generate('login'), "{$whose} Login") ?>]
</span>
<?php endif ?>
</div>
<nav>
<?php if ($container->get('util')->isViewPage() && ($hasAnime || $hasManga)): ?>
<?= $helper->menu($menu_name) ?>
<br />
<ul>
<li class="<?= Util::isNotSelected('list', $lastSegment) ?>"><a href="<?= $urlGenerator->url($route_path) ?>">Cover View</a></li>
<li class="<?= Util::isSelected('list', $lastSegment) ?>"><a href="<?= $urlGenerator->url("{$route_path}/list") ?>">List View</a></li>
</ul>
<?php endif ?>
</nav>

39
app/views/manga/add.php Normal file
View File

@ -0,0 +1,39 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>Add Manga to your List</h2>
<form action="<?= $action_url ?>" method="post">
<section>
<div class="cssload-loader" hidden="hidden">
<div class="cssload-inner cssload-one"></div>
<div class="cssload-inner cssload-two"></div>
<div class="cssload-inner cssload-three"></div>
</div>
<label for="search">Search for manga by name:&nbsp;&nbsp;&nbsp;&nbsp;<input type="search" id="search" /></label>
<section id="series-list" class="media-wrap">
</section>
</section>
<br />
<table class="invisible form">
<tbody>
<tr>
<td><label for="status">Reading Status</label></td>
<td>
<select name="status" id="status">
<?php foreach($status_list as $status_key => $status_title): ?>
<option value="<?= $status_key ?>"><?= $status_title ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" name="type" value="manga" />
<button type="submit">Save</button>
</td>
</tr>
</tbody>
</table>
</form>
</main>
<?php endif ?>

View File

@ -1,49 +1,94 @@
<main>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<?php if (empty($items)): ?>
<section class="status">
<h2><?= $name ?></h2>
<h2><?= $escape->html($name) ?></h2>
<h3>There's nothing here!</h3>
</section>
<?php else: ?>
<section class="status">
<h2><?= $escape->html($name) ?></h2>
<section class="media-wrap">
<?php foreach($items as $item): ?>
<article class="media" id="manga-<?= $item['id'] ?>">
<?php if (is_logged_in()): ?>
<div class="edit_buttons" hidden>
<button class="plus_one_chapter">+1 Chapter</button>
<button class="plus_one_volume">+1 Volume</button>
<article class="media" data-kitsu-id="<?= $item['id'] ?>" data-mal-id="<?= $item['mal_id'] ?>">
<?php if ($auth->isAuthenticated()): ?>
<div class="edit-buttons" hidden>
<button class="plus-one-chapter">+1 Chapter</button>
<?php /* <button class="plus-one-volume">+1 Volume</button> */ ?>
</div>
<?php endif ?>
<img src="<?= $item['manga']['poster_image'] ?>" />
<?= $helper->picture("images/manga/{$item['manga']['id']}.webp") ?>
<div class="name">
<a href="https://hummingbird.me/manga/<?= $item['manga_id'] ?>">
<?= $item['manga']['romaji_title'] ?>
<?= (isset($item['manga']['english_title'])) ? "<br />({$item['manga']['english_title']})" : ""; ?>
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $escape->html($item['manga']['title']) ?>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">
<?php if ($auth->isAuthenticated()): ?>
<div class="row">
<div class="user_rating">Rating: <?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</div>
<span class="edit">
<a class="bracketed"
title="Edit information about this manga"
href="<?= $url->generate('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">
Edit
</a>
</span>
</div>
<?php endif ?>
<div class="row">
<div><?= $item['manga']['type'] ?></div>
<div class="user-rating">Rating: <?= $item['user_rating'] ?> / 10</div>
</div>
<?php if ($item['rereading']): ?>
<div class="row">
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<span class="item-<?= $attr ?>"><?= ucfirst($attr) ?></span>
<?php endif ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if ($item['reread'] > 0): ?>
<div class="row">
<div>Reread <?= $item['reread'] ?> time(s)</div>
</div>
<?php endif ?>
<div class="row">
<div class="chapter_completion">
Chapters: <span class="chapters_read"><?= $item['chapters_read'] ?></span> /
<span class="chapter_count"><?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></span>
Chapters: <span class="chapters_read"><?= $item['chapters']['read'] ?></span> /
<span class="chapter_count"><?= $item['chapters']['total'] ?></span>
</div>
</div>
<div class="row">
<?php /* </div>
<div class="row"> */ ?>
<div class="volume_completion">
Volumes: <span class="volumes_read"><?= $item['volumes_read'] ?></span> /
<span class="volume_count"><?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></span>
Volumes: <span class="volume_count"><?= $item['volumes']['total'] ?></span>
</div>
</div>
</div>
<?php /*<div class="medium_metadata">
<div class="media_type"><?= $item['manga']['manga_type'] ?></div>
</div> */ ?>
</article>
<?php endforeach ?>
</section>
</section>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<?php if (is_logged_in()): ?>
<script src="<?= asset_url('js.php?g=edit') ?>"></script>
<?php endif ?>

104
app/views/manga/details.php Normal file
View File

@ -0,0 +1,104 @@
<main class="details fixed">
<section class="flex flex-no-wrap">
<aside class="info">
<?= $helper->picture("images/manga/{$data['id']}-original.webp", 'jpg', ['class' => 'cover']) ?>
<br />
<table class="media-details">
<tr>
<td>Manga Type</td>
<td><?= ucfirst($data['manga_type']) ?></td>
</tr>
<tr>
<td>Volume Count</td>
<td><?= $data['volume_count'] ?></td>
</tr>
<tr>
<td>Chapter Count</td>
<td><?= $data['chapter_count'] ?></td>
</tr>
<tr>
<td>Genres</td>
<td>
<?= implode(', ', $data['genres']); ?>
</td>
</tr>
</table>
<br />
</aside>
<article class="text">
<h2 class="toph"><a rel="external" href="<?= $data['url'] ?>"><?= $data['title'] ?></a></h2>
<?php foreach ($data['titles'] as $title): ?>
<h3><?= $title ?></h3>
<?php endforeach ?>
<br />
<div class="description">
<p><?= str_replace("\n", '</p><p>', $data['synopsis']) ?></p>
</div>
</article>
</section>
<?php if (count($data['characters']) > 0): ?>
<h2>Characters</h2>
<div class="tabs">
<?php $i = 0 ?>
<?php foreach ($data['characters'] as $role => $list): ?>
<input
type="radio" name="character-role-tabs"
id="character-tabs<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="character-tabs<?= $i ?>"><?= ucfirst($role) ?></label>
<section class="content media-wrap flex flex-wrap flex-justify-start">
<?php foreach ($list as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="<?= $role === 'supporting' ? 'small-' : '' ?>character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
<div class="name">
<?= $helper->a($link, $char['name']); ?>
</div>
<a href="<?= $link ?>">
<?= $helper->picture("images/characters/{$id}.webp") ?>
</a>
</article>
<?php endif ?>
<?php endforeach ?>
</section>
<?php $i++ ?>
<?php endforeach ?>
</div>
<?php endif ?>
<?php if (count($data['staff']) > 0): ?>
<h2>Staff</h2>
<div class="vertical-tabs">
<?php $i = 0 ?>
<?php foreach ($data['staff'] as $role => $people): ?>
<div class="tab">
<input
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $pid => $person): ?>
<article class='character person'>
<?php $link = $url->generate('person', ['id' => $pid]) ?>
<div class="name">
<a href="<?= $link ?>">
<?= $person['name'] ?>
</a>
</div>
<a href="<?= $link ?>">
<?= $helper->picture("images/people/{$pid}.webp") ?>
</a>
</article>
<?php endforeach ?>
</section>
</div>
<?php $i++ ?>
<?php endforeach ?>
</div>
<?php endif ?>
</main>

111
app/views/manga/edit.php Normal file
View File

@ -0,0 +1,111 @@
<?php if ($auth->isAuthenticated()): ?>
<main>
<h2>
Edit Manga List Item
</h2>
<form action="<?= $action ?>" method="post">
<table class="invisible form">
<thead>
<tr>
<th>
<h3><?= $escape->html($item['manga']['title']) ?></h3>
<?php foreach ($item['manga']['titles'] as $title): ?>
<h4><?= $escape->html($title) ?></h4>
<?php endforeach ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="9">
<?= $helper->picture("images/manga/{$item['manga']['id']}-original.webp", "jpg", [], ["width" => "390"]) ?>
</td>
</tr>
<tr>
<td><label for="status">Reading Status</label></td>
<td>
<select name="status" id="status">
<?php foreach ($status_list as $val => $status): ?>
<option <?php if ($item['reading_status'] === $val): ?>selected="selected"<?php endif ?>
value="<?= $val ?>"><?= $status ?></option>
<?php endforeach ?>
</select>
</td>
</tr>
<tr>
<td><label for="series_rating">Rating</label></td>
<td>
<input type="number" min="0" max="10" maxlength="2" name="new_rating"
value="<?= $item['user_rating'] ?>" id="series_rating" size="2"/> / 10
</td>
</tr>
<tr>
<td><label for="chapters_read">Chapters Read</label></td>
<td>
<input type="number" min="0" name="chapters_read" id="chapters_read"
value="<?= $item['chapters']['read'] ?>"/> / <?= $item['chapters']['total'] ?>
</td>
</tr>
<tr>
<td><label for="volumes_read">Volumes Read</label></td>
<td>
<?php /*<input type="number" disabled="disabled" min="0" name="volumes_read" id="volumes_read" value="" /> */ ?>
- / <?= $item['volumes']['total'] ?>
</td>
</tr>
<tr>
<td><label for="rereading_flag">Rereading?</label></td>
<td>
<input type="checkbox" name="rereading" id="rereading_flag"
<?php if ($item['rereading'] === TRUE): ?>checked="checked"<?php endif ?>
/>
</td>
</tr>
<tr>
<td><label for="reread_count">Reread Count</label></td>
<td>
<input type="number" min="0" id="reread_count" name="reread_count"
value="<?= $item['reread'] ?>"/>
</td>
</tr>
<tr>
<td><label for="notes">Notes</label></td>
<td>
<textarea name="notes" id="notes"><?= $escape->html($item['notes']) ?></textarea>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
<input type="hidden" value="<?= $item['manga']['slug'] ?>" name="manga_id"/>
<input type="hidden" value="<?= $item['user_rating'] ?>" name="old_rating"/>
<input type="hidden" value="true" name="edit"/>
<button type="submit">Submit</button>
</td>
</tr>
</tbody>
</table>
</form>
<fieldset>
<legend>Danger Zone</legend>
<form class="js-delete" action="<?= $url->generate('manga.delete') ?>" method="post">
<table class="form invisible">
<tbody>
<tr>
<td class="danger">
<strong>Permanently</strong> remove this list item and <strong>all</strong> its data?
</td>
<td>
<input type="hidden" value="<?= $item['id'] ?>" name="id"/>
<input type="hidden" value="<?= $item['mal_id'] ?>" name="mal_id"/>
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>
</tbody>
</table>
</form>
</fieldset>
</main>
<?php endif ?>

View File

@ -1,33 +1,78 @@
<main>
<main class="media-list">
<?php if ($auth->isAuthenticated()): ?>
<a class="bracketed" href="<?= $url->generate('manga.add.get') ?>">Add Item</a>
<?php endif ?>
<?php if (empty($sections)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<br />
<label>Filter: <input type='text' class='media-filter' /></label>
<br />
<?php foreach ($sections as $name => $items): ?>
<h2><?= $name ?></h2>
<table>
<thead>
<tr>
<th>Title</th>
<th>Rating</th>
<th>Chapters</th>
<th>Volumes</th>
<th>Type</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="manga-<?= $item['manga']['id'] ?>">
<td class="align_left">
<a href="https://hummingbird.me/manga/<?= $item['manga']['id'] ?>">
<?= $item['manga']['romaji_title'] ?>
</a>
<?= (array_key_exists('english_title', $item['manga'])) ? " &middot; " . $item['manga']['english_title'] : "" ?>
</td>
<td><?= ($item['rating'] > 0) ? (int)($item['rating'] * 2) : '-' ?> / 10</td>
<td><?= $item['chapters_read'] ?> / <?= ($item['manga']['chapter_count'] > 0) ? $item['manga']['chapter_count'] : "-" ?></td>
<td><?= $item['volumes_read'] ?> / <?= ($item['manga']['volume_count'] > 0) ? $item['manga']['volume_count'] : "-" ?></td>
<td><?= $item['manga']['manga_type'] ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php if (empty($items)): ?>
<h3>There's nothing here!</h3>
<?php else: ?>
<table class='media-wrap'>
<thead>
<tr>
<?php if ($auth->isAuthenticated()): ?>
<td>&nbsp;</td>
<?php endif ?>
<th>Title</th>
<th>Rating</th>
<th>Completed Chapters</th>
<th># of Volumes</th>
<th>Attributes</th>
<th>Type</th>
<th>Genres</th>
</tr>
</thead>
<tbody>
<?php foreach($items as $item): ?>
<tr id="manga-<?= $item['id'] ?>">
<?php if($auth->isAuthenticated()): ?>
<td>
<a class="bracketed" href="<?= $url->generate('edit', [
'controller' => 'manga',
'id' => $item['id'],
'status' => $name
]) ?>">Edit</a>
</td>
<?php endif ?>
<td class="align-left">
<a href="<?= $url->generate('manga.details', ['id' => $item['manga']['slug']]) ?>">
<?= $item['manga']['title'] ?>
</a>
<?php foreach($item['manga']['titles'] as $title): ?>
<br /><?= $title ?>
<?php endforeach ?>
</td>
<td><?= $item['user_rating'] ?> / 10</td>
<td><?= $item['chapters']['read'] ?> / <?= $item['chapters']['total'] ?></td>
<td><?= $item['volumes']['total'] ?></td>
<td>
<ul>
<?php if ($item['reread'] > 0): ?>
<li>Reread <?= $item['reread'] ?> time(s)</li>
<?php endif ?>
<?php foreach(['rereading'] as $attr): ?>
<?php if($item[$attr]): ?>
<li><?= ucfirst($attr); ?></li>
<?php endif ?>
<?php endforeach ?>
</ul>
</td>
<td><?= $item['manga']['type'] ?></td>
<td class="align-left">
<?= implode(', ', $item['manga']['genres']) ?>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
</main>
<script src="<?= asset_url('js.php?g=table') ?>"></script>
<script defer="defer" src="<?= $urlGenerator->assetUrl('js/tables.min.js') ?>"></script>

View File

@ -1,5 +1,5 @@
<div class="message <?= $stat_class ?>">
<div class="message <?= $escape->attr($message_type) ?>">
<span class="icon"></span>
<?= $message ?>
<span class="close" onclick="this.parentElement.style.display='none'">x</span>
<?= $escape->html($message) ?>
<span class="close"></span>
</div>

View File

@ -0,0 +1,67 @@
<?php
use function Aviat\AnimeClient\getLocalImg;
use Aviat\AnimeClient\API\Kitsu;
?>
<h3>Voice Acting Roles</h3>
<div class="tabs">
<?php $i = 0; ?>
<?php foreach($data['characters'] as $role => $characterList): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" name="character-type-tabs" id="character-type-<?= $i ?>" />
<label for="character-type-<?= $i ?>"><h5><?= ucfirst($role) ?></h5></label>
<section class="content">
<table class="borderless max-table">
<tr>
<th>Character</th>
<th>Series</th>
</tr>
<?php foreach ($characterList as $cid => $character): ?>
<tr>
<td>
<article class="character">
<?php
$link = $url->generate('character', ['slug' => $character['character']['slug']]);
?>
<a href="<?= $link ?>">
<?php $imgPath = ($character['character']['image'] === NULL)
? 'images/characters/empty.png'
: getLocalImg($character['character']['image']['original']);
echo $helper->picture($imgPath);
?>
<div class="name">
<?= $character['character']['canonicalName'] ?>
</div>
</a>
</article>
</td>
<td>
<section class="align-left media-wrap">
<?php foreach ($character['media'] as $sid => $series): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $series['slug']]);
$titles = Kitsu::filterTitles($series);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/anime/{$sid}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
</td>
</tr>
<?php endforeach; ?>
</table>
</section>
<?php $i++ ?>
<?php endforeach ?>
</div>

View File

@ -0,0 +1,67 @@
<?php
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="details fixed">
<section class="flex flex-no-wrap">
<div>
<?= $helper->picture("images/people/{$data['id']}-original.webp", 'jpg', ['class' => 'cover' ]) ?>
</div>
<div>
<h2 class="toph"><?= $data['name'] ?></h2>
</div>
</section>
<?php if ( ! empty($data['staff'])): ?>
<section>
<h3>Castings</h3>
<div class="vertical-tabs">
<?php $i = 0 ?>
<?php foreach ($data['staff'] as $role => $entries): ?>
<div class="tab">
<input
type="radio" name="staff-roles" id="staff-role<?= $i ?>" <?= $i === 0 ? 'checked' : '' ?> />
<label for="staff-role<?= $i ?>"><?= $role ?></label>
<?php foreach ($entries as $type => $casting): ?>
<?php if ($type === 'characters') continue; ?>
<?php if ( ! (empty($entries['manga']) || empty($entries['anime']))): ?>
<h4><?= ucfirst($type) ?></h4>
<?php endif ?>
<section class="content">
<?php foreach ($casting as $sid => $series): ?>
<article class="media">
<?php
$mediaType = in_array($type, ['anime', 'manga'], TRUE) ? $type : 'anime';
$link = $url->generate("{$mediaType}.details", ['id' => $series['slug']]);
$titles = Kitsu::filterTitles($series);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/{$type}/{$sid}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br />
<small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach; ?>
</section>
<?php endforeach ?>
</div>
<?php $i++ ?>
<?php endforeach ?>
</div>
</section>
<?php endif ?>
<?php if ( ! (empty($data['characters']['main']) || empty($data['characters']['supporting']))): ?>
<section>
<?php include 'character-mapping.php' ?>
</section>
<?php endif ?>
</main>

View File

@ -0,0 +1,24 @@
<?php
// Higher scoped variables:
// $fields
// $hiddenFields
// $nestedPrefix
?>
<?php foreach ($fields as $name => $field): ?>
<?php $fieldname = ($section === 'config' || $nestedPrefix !== 'config') ? "{$nestedPrefix}[{$name}]" : "{$nestedPrefix}[{$section}][{$name}]"; ?>
<?php if ($field['type'] === 'subfield'): ?>
<section>
<h4><?= $field['title'] ?></h4>
<?php include_once '_form.php'; ?>
</section>
<?php elseif ( ! empty($field['display'])): ?>
<article>
<label for="<?= $fieldname ?>"><?= $field['title'] ?></label><br />
<small><?= $field['description'] ?></small><br />
<?= $helper->field($fieldname, $field); ?>
</article>
<?php else: ?>
<?php $hiddenFields[] = $helper->field($fieldname, $field); ?>
<?php endif ?>
<?php endforeach ?>

View File

@ -0,0 +1,66 @@
<?php
if ( ! $auth->isAuthenticated())
{
echo '<h1>Not Authorized</h1>';
return;
}
$sectionMapping = [
'anilist' => 'Anilist API Integration',
'config' => 'General Settings',
'cache' => 'Caching',
'database' => 'Collection Database Settings',
];
$hiddenFields = [];
$nestedPrefix = 'config';
?>
<form action="<?= $url->generate('settings-post') ?>" method="POST">
<main class='settings form'>
<button type="submit">Save Changes</button>
<div class="tabs">
<?php $i = 0; ?>
<?php foreach ($form as $section => $fields): ?>
<input <?= $i === 0 ? 'checked="checked"' : '' ?> type="radio" id="settings-tab<?= $i ?>"
name="settings-tabs"
/>
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
<section class="content">
<?php require __DIR__ . '/_form.php' ?>
<?php if ($section === 'anilist'): ?>
<hr />
<?php $auth = $anilistModel->checkAuth(); ?>
<?php if (array_key_exists('errors', $auth)): ?>
<p class="static-message error">Not Authorized.</p>
<?= $helper->a(
$url->generate('anilist-redirect'),
'Link Anilist Account'
) ?>
<?php else: ?>
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
<p class="static-message info">
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
</p>
<?= $helper->a(
$url->generate('anilist-redirect'),
'Update Access Token'
) ?>
<?php endif ?>
<?php endif ?>
</section>
<?php $i++; ?>
<?php endforeach ?>
</div>
<br />
<?php foreach ($hiddenFields as $field): ?>
<?= $field->__toString() ?>
<?php endforeach ?>
<button type="submit">Save Changes</button>
</main>
</form>

27
app/views/setup-check.php Normal file
View File

@ -0,0 +1,27 @@
<?php
$setupErrors = \Aviat\AnimeClient\checkFolderPermissions($container->get('config'));
?>
<?php if ( ! empty($setupErrors)): ?>
<aside class="message error">
<h1>Issues with server setup:</h1>
<?php if (array_key_exists('missing', $setupErrors)): ?>
<h3>The following folders need to be created, and writable.</h3>
<ul>
<?php foreach ($setupErrors['missing'] as $error): ?>
<li><?= $error ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
<?php if (array_key_exists('writable', $setupErrors)): ?>
<h3>The following folders are not writable by the server.</h3>
<ul>
<?php foreach($setupErrors['writable'] as $error): ?>
<li><?= $error ?></li>
<?php endforeach ?>
</ul>
<?php endif ?>
</aside>
<?php endif ?>

136
app/views/user/details.php Normal file
View File

@ -0,0 +1,136 @@
<?php
use Aviat\AnimeClient\API\Kitsu;
?>
<main class="user-page details">
<h2 class="toph">
<?= $helper->a(
"https://kitsu.io/users/{$data['slug']}",
$data['name'], [
'title' => 'View profile on Kitsu'
])
?>
</h2>
<p><?= $escape->html($data['about']) ?></p>
<section class="flex flex-no-wrap">
<aside class="info">
<center>
<?= $helper->img($urlGenerator->assetUrl($data['avatar']), ['alt' => '']); ?>
</center>
<br />
<table class="media-details">
<tr>
<td>Location</td>
<td><?= $data['location'] ?></td>
</tr>
<tr>
<td>Website</td>
<td><?= $helper->a($data['website'], $data['website']) ?></td>
</tr>
<?php if ( ! empty($data['waifu'])): ?>
<tr>
<td><?= $escape->html($data['waifu']['label']) ?></td>
<td>
<?php
$character = $data['waifu']['character'];
echo $helper->a(
$url->generate('character', ['slug' => $character['slug']]),
$character['canonicalName']
);
?>
</td>
</tr>
<?php endif ?>
</table>
<h3>User Stats</h3><br />
<table class="media-details">
<?php foreach($data['stats'] as $label => $stat): ?>
<tr>
<td><?= $label ?></td>
<td><?= $stat ?></td>
</tr>
<?php endforeach ?>
</table>
</aside>
<article>
<?php if ( ! empty($data['favorites'])): ?>
<h3>Favorites</h3>
<div class="tabs">
<?php $i = 0 ?>
<?php if ( ! empty($data['favorites']['characters'])): ?>
<input type="radio" name="user-favorites" id="user-fav-chars" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-chars">Characters</label>
<section class="content full-width media-wrap">
<?php foreach($data['favorites']['characters'] as $id => $char): ?>
<?php if ( ! empty($char['image']['original'])): ?>
<article class="character">
<?php $link = $url->generate('character', ['slug' => $char['slug']]) ?>
<div class="name"><?= $helper->a($link, $char['canonicalName']); ?></div>
<a href="<?= $link ?>">
<?= $helper->picture("images/characters/{$char['id']}.webp") ?>
</a>
</article>
<?php endif ?>
<?php endforeach ?>
</section>
<?php $i++; ?>
<?php endif ?>
<?php if ( ! empty($data['favorites']['anime'])): ?>
<input type="radio" name="user-favorites" id="user-fav-anime" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-anime">Anime</label>
<section class="content full-width media-wrap">
<?php foreach($data['favorites']['anime'] as $anime): ?>
<article class="media">
<?php
$link = $url->generate('anime.details', ['id' => $anime['slug']]);
$titles = Kitsu::filterTitles($anime);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/anime/{$anime['id']}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php $i++; ?>
<?php endif ?>
<?php if ( ! empty($data['favorites']['manga'])): ?>
<input type="radio" name="user-favorites" id="user-fav-manga" <?= $i === 0 ? 'checked' : '' ?> />
<label for="user-fav-manga">Manga</label>
<section class="content full-width media-wrap">
<?php foreach($data['favorites']['manga'] as $manga): ?>
<article class="media">
<?php
$link = $url->generate('manga.details', ['id' => $manga['slug']]);
$titles = Kitsu::filterTitles($manga);
?>
<a href="<?= $link ?>">
<?= $helper->picture("images/manga/{$manga['id']}.webp") ?>
</a>
<div class="name">
<a href="<?= $link ?>">
<?= array_shift($titles) ?>
<?php foreach ($titles as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
</article>
<?php endforeach ?>
</section>
<?php $i++; ?>
<?php endif ?>
</div>
<?php endif ?>
</article>
</section>
</main>

View File

@ -0,0 +1,28 @@
<documentation title="Closing comments instead of PHP closing tag">
<standard>
<![CDATA[
The PHP closing tag on a PHP document ?> is optional to the PHP parser. However, if used, any whitespace following the closing tag, whether introduced by the developer, user, or an FTP application, can cause unwanted output, PHP errors, or if the latter are suppressed, blank pages. For this reason, all PHP files should OMIT the closing PHP tag, and instead use a comment block to mark the end of file and it's location relative to the application root. This allows you to still identify a file as being complete and not truncated.
]]>
</standard>
<code_comparison>
<code title="Examples of valid closing comments">
<![CDATA[
<?php
echo "Here's my code!";
/* End of file myfile.php */
/* Location: ./system/modules/mymodule/myfile.php */
]]>
</code>
<code title="Examples of invalid closing comments">
<![CDATA[
<?php
echo "Here's my code!";
?>
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,7 @@
<documentation title="Unicode (UTF-8) encoding without BOM">
<standard>
<![CDATA[
Files should be saved with Unicode (UTF-8) encoding. The BOM should not be used. Unlike UTF-16 and UTF-32, there's no byte order to indicate in a UTF-8 encoded file, and the BOM can have a negative side effect in PHP of sending output, preventing the application from being able to set its own headers. Unix line endings should be used (LF).
]]>
</standard>
</documentation>

View File

@ -0,0 +1,31 @@
<documentation title="Constructor Names">
<standard>
<![CDATA[
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
]]>
</standard>
<code_comparison>
<code title="Examples of valid constructor name">
<![CDATA[
class Super_class
{
function Super_class()
{
echo 'Some code here !';
}
}
]]>
</code>
<code title="Examples of invalid constructor name">
<![CDATA[
class Super_class
{
function __constructor()
{
echo 'Some code here !';
}
}
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,21 @@
<documentation title="Class names">
<standard>
<![CDATA[
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
]]>
</standard>
<code_comparison>
<code title="Examples of valid class names">
<![CDATA[
class Super_class
]]>
</code>
<code title="Examples of invalid class names">
<![CDATA[
class SuperClass // words not separated with underscores and words next to the first one start with an upper case
class superclass // words not separated with underscores
class Super_Class // words next to the first one start with an upper case
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,21 @@
<documentation title="File names">
<standard>
<![CDATA[
To be able to find which class is contained in a file, file names should be case-insensitively equal to class names. Some operating systems and tools are case-insensitive, though other are. So, file names should be in lower case to avoid any trouble.
]]>
</standard>
<code_comparison>
<code title="Examples of valid file names">
<![CDATA[
super_class.php
]]>
</code>
<code title="Examples of invalid file names">
<![CDATA[
superclass.php // words not separated with underscores
SuperClass.php // not in lower case and words not separated with underscores
Super_class.php // not in lower case
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,27 @@
<documentation title="Function and Method Names">
<standard>
<![CDATA[
Class names should always start with an uppercase letter. Multiple words should be separated with an underscore, and not CamelCased. All other class methods should be entirely lowercased and named to clearly indicate their function, preferably including a verb. Try to avoid overly long and verbose names.
Methods and variables that are only accessed internally by your class, such as utility and helper functions that your public methods use for code abstraction, should be prefixed with an underscore.
]]>
</standard>
<code_comparison>
<code title="Examples of valid method names">
<![CDATA[
function get_file_properties() // descriptive, underscore separator, and all lowercase letters
private function _get_file_properties()
]]>
</code>
<code title="Examples of invalid method names">
<![CDATA[
function fileproperties() // not descriptive and needs underscore separator
function fileProperties() // not descriptive and uses CamelCase
function getfileproperties() // Better! But still missing underscore separator
function getFileProperties() // uses CamelCase
function get_the_file_properties_from_the_file() // wordy
private function get_the_file_properties() // not prefixed with an underscor, though private
function _get_the_file_properties() // prefixed with an underscor, though public
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,31 @@
<documentation title="Variable names">
<standard>
<![CDATA[
Namely, variables should contain only lowercase letters, use underscore separators, and be reasonably named to indicate their purpose and contents. Very short, non-word variables should only be used as iterators in for() loops.
Methods and variables that are only accessed internally by your class, such as utility and helper functions that your public methods use for code abstraction, should be prefixed with an underscore.
]]>
</standard>
<code_comparison>
<code title="Examples of valid variable names">
<![CDATA[
for ($j = 0; $j < 10; $j++)
$str
$buffer
$group_id
$last_city
private $_internal_data;
]]>
</code>
<code title="Examples of invalid variable names">
<![CDATA[
$j = 'foo'; // single letter variables should only be used in for() loops
$Str // contains uppercase letters
$bufferedText // uses CamelCasing, and could be shortened without losing semantic meaning
$groupid // multiple words, needs underscore separator
$name_of_last_city_used // too long
private $internal_data; // not prefixed with an underscor, though private
$_public_attribute; // prefixed with an underscor, though public
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,40 @@
<documentation title="Strict comparison operators">
<standard>
<![CDATA[
Some PHP functions return FALSE on failure, but may also have a valid return value of "" or 0, which would evaluate to FALSE in loose comparisons. Be explicit by comparing the variable type when using these return values in conditionals to ensure the return value is indeed what you expect, and not a value that has an equivalent loose-type evaluation.
Use the same stringency in returning and checking your own variables. Use === and !== as necessary.
]]>
</standard>
<code_comparison>
<code title="Valid strict comparison">
<![CDATA[
if (strpos($str, 'foo') === FALSE) {
echo 'Do something.';
}
function build_string($str = "")
{
if ($str === "") {
echo 'Buid string.';
}
}
]]>
</code>
<code title="Invalid loose comparison">
<![CDATA[
// If 'foo' is at the beginning of the string, strpos will return a 0,
// resulting in this conditional evaluating as TRUE
if (strpos($str, 'foo') == FALSE) {
echo 'Do something.';
}
function build_string($str = "")
{
if ($str == "") { // uh-oh! What if FALSE or the integer 0 is passed as an argument?
echo 'Buid string.';
}
}
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,28 @@
<documentation title="Double-quoted strings">
<standard>
<![CDATA[
Always use single quoted strings unless you need variables parsed, and in cases where you do need variables parsed, use braces to prevent greedy token parsing. You may also use double-quoted strings if the string contains single quotes, so you do not have to use escape characters.
]]>
</standard>
<code_comparison>
<code title="Examples of invalid double-quoted strings">
<![CDATA[
"My String" // no variable parsing, so no use for double quotes
"My string $foo" // needs braces
'SELECT foo FROM bar WHERE baz = \'bag\'' // ugly
'\r\n' // it isn't wrong, but it won't be interpreted as a new line feed
]]>
</code>
<code title="Examples of valid double-quoted strings">
<![CDATA[
'My String'
"My string {$foo}" // variables in strings may be enclosed with braces in 2 ways
"My string ${foo}"
"My string {$foo['bar']}" // variables in strings may be an array entry
"My string {$foo->bar}" // variables in strings may be an object attribute
"SELECT foo FROM bar WHERE baz = 'bag'"
"\n" // not specified in Code Igniter coding standard, but it should be allowed
]]>
</code>
</code_comparison>
</documentation>

View File

@ -0,0 +1,187 @@
<?php
/**
* CodeIgniter_Sniffs_Commenting_InlineCommentSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Commenting_InlineCommentSniff.
*
* Ensure the use of single line comments within code (i.e //)
* and blank lines between large comment blocks and code.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Commenting;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class InlineCommentSniff implements Sniff
{
/**
* @var int Limit defining long comments.
* Long comments count $longCommentLimit or more lines.
*/
public $longCommentLimit = 5;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_COMMENT
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// keep testing only if it's about the first comment of the block
$previousCommentPtr = $phpcsFile->findPrevious($tokens[$stackPtr]['code'], $stackPtr - 1);
if ($tokens[$previousCommentPtr]['line'] !== $tokens[$stackPtr]['line'] - 1) {
if (TRUE !== $this->_checkCommentStyle($phpcsFile, $stackPtr)) {
return;
}
$commentLines = $this->_getCommentBlock($phpcsFile, $stackPtr);
if (count($commentLines) >= $this->longCommentLimit) {
$this->_checkBlankLinesAroundLongComment($phpcsFile, $commentLines);
}
}
}//end process()
/**
* Add error to $phpcsFile, if comment pointed by $stackPtr doesn't start
* with '//'.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* that has to be a comment.
*
* @return bool TRUE if the content of the token pointed by $stackPtr starts
* with //, FALSE if an error was added to $phpcsFile.
*/
private function _checkCommentStyle(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content']{0} === '#') {
$error = 'Perl-style comments are not allowed; use "// Comment" or DocBlock comments instead';
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
return FALSE;
} else if (substr($tokens[$stackPtr]['content'], 0, 2) === '/*'
|| $tokens[$stackPtr]['content']{0} === '*'
) {
$error = 'Multi lines comments are not allowed; use "// Comment" DocBlock comments instead';
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
return FALSE;
} else if (substr($tokens[$stackPtr]['content'], 0, 2) !== '//') {
$error = 'Use single line or DocBlock comments within code';
$phpcsFile->addError($error, $stackPtr, 'WrongStyle');
return FALSE;
}
return TRUE;
}//_checkCommentStyle()
/**
* Gather into an array all comment lines to which $stackPtr belongs.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr Pointer to the first comment line.
*
* @return type array Pointers to tokens making up the comment block.
*/
private function _getCommentBlock(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$commentLines = array($stackPtr);
$nextComment = $stackPtr;
$lastLine = $tokens[$stackPtr]['line'];
while (($nextComment = $phpcsFile->findNext($tokens[$stackPtr]['code'], ($nextComment + 1), null, false)) !== false) {
if (($tokens[$nextComment]['line'] - 1) !== $lastLine) {
// Not part of the block.
break;
}
$lastLine = $tokens[$nextComment]['line'];
$commentLines[] = $nextComment;
}
return $commentLines;
}//_getCommentBlock()
/**
* Add errors to $phpcsFile, if $commentLines isn't enclosed with blank lines.
*
* @param File $phpcsFile The current file being scanned.
* @param array $commentLines Lines of the comment block being checked.
*
* @return bool TRUE if $commentLines is enclosed with at least a blank line
* before and after, FALSE otherwise.
*/
private function _checkBlankLinesAroundLongComment(File $phpcsFile, array $commentLines)
{
$hasBlankLinesAround = TRUE;
$tokens = $phpcsFile->getTokens();
// check blank line before the long comment
$firstCommentPtr = reset($commentLines);
$firstPreviousSpacePtr = $firstCommentPtr - 1;
while (T_WHITESPACE === $tokens[$firstPreviousSpacePtr]['code'] && $firstPreviousSpacePtr > 0) {
$firstPreviousSpacePtr--;
}
if ($tokens[$firstPreviousSpacePtr]['line'] >= $tokens[$firstCommentPtr]['line'] - 1) {
$error = "Please add a blank line before comments counting more than {$this->longCommentLimit} lines.";
$phpcsFile->addError($error, $firstCommentPtr, 'LongCommentWithoutSpacing');
$hasBlankLinesAround = FALSE;
}
// check blank line after the long comment
$lastCommentPtr = end($commentLines);
$lastNextSpacePtr = $lastCommentPtr + 1;
while (T_WHITESPACE === $tokens[$lastNextSpacePtr]['code'] && $lastNextSpacePtr < count($tokens)) {
$lastNextSpacePtr++;
}
if ($tokens[$lastNextSpacePtr]['line'] <= $tokens[$lastCommentPtr]['line'] + 1) {
$error = "Please add a blank line after comments counting more than {$this->longCommentLimit} lines.";
$phpcsFile->addError($error, $lastCommentPtr, 'LongCommentWithoutSpacing');
$hasBlankLinesAround = FALSE;
}
return $hasBlankLinesAround;
}//end _checkBlanksAroundLongComment()
}//end class
?>

View File

@ -0,0 +1,98 @@
<?php
/**
* CodeIgniter_Sniffs_Files_ByteOrderMarkSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Files_ByteOrderMarkSniff.
*
* Ensures that no BOM appears at the beginning of file.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Files;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class ByteOrderMarkSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array( T_OPEN_TAG );
}//end register()
/**
* List of supported BOM definitions.
*
* Use encoding names as keys and hex BOM representations as values.
*
* @return array
*/
protected function getBomDefinitions()
{
return array(
'UTF-8' => 'efbbbf',
'UTF-16 (BE)' => 'feff',
'UTF-16 (LE)' => 'fffe',
'UTF-32 (BE)' => '0000feff',
'UTF-32 (LE)' => 'fffe0000'
);
}//end getBomDefinitions()
/**
* Process tokens.
*
* Actually, only proceed when we're at index 0, this should be the only case
* that will contain BOM. Then check if BOM definition matches what
* we've found as file's inline HTML. Inline HTML could be longer than just BOM
* so make sure you test as much as needed.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr )
{
// We are only interested if this is the first open tag.
if ($stackPtr !== 0) {
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
return;
}
}
$tokens = $phpcsFile->getTokens();
$fileStartString = $tokens[0]['content'];
foreach ($this->getBomDefinitions() as $bomName => $expectedBomHex) {
$bomByteLength = strlen($expectedBomHex) / 2;
$fileStartHex = bin2hex(substr($fileStartString, 0, $bomByteLength));
if ($fileStartHex === $expectedBomHex) {
$error = "File contains a $bomName byte order mark (BOM).";
$phpcsFile->addError($error, $stackPtr, 123);
break;
}
}
}//end process()
}

View File

@ -0,0 +1,222 @@
<?php
/**
* CodeIgniter_Sniffs_Files_Utf8EncodingSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Files_Utf8EncodingSniff.
*
* Ensures that PHP files are encoded with Unicode (UTF-8) encoding.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Files;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class Utf8EncodingSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_OPEN_TAG
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// We are only interested if this is the first open tag.
if ($stackPtr !== 0) {
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
return;
}
}
$file_path = $phpcsFile->getFilename();
$file_name = basename($file_path);
$file_content = file_get_contents($file_path);
if (false === mb_check_encoding($file_content, 'UTF-8')) {
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding.';
$phpcsFile->addError($error, 0);
}
if ( ! self::_checkUtf8W3c($file_content)) {
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding, but it did not successfully pass the W3C test.';
$phpcsFile->addError($error, 0);
}
if ( ! self::_checkUtf8Rfc3629($file_content)) {
$error = 'File "' . $file_name . '" should be saved with Unicode (UTF-8) encoding, but it did not meet RFC3629 requirements.';
$phpcsFile->addError($error, 0);
}
}//end process()
/**
* Checks that the string $content contains only valid UTF-8 chars
* using W3C's method.
* Returns true if $content contains only UTF-8 chars, false otherwise.
*
* @param string $content String to check.
*
* @return bool true if $content contains only UTF-8 chars, false otherwise.
*
* @see http://w3.org/International/questions/qa-forms-utf-8.html
*/
private static function _checkUtf8W3c($content)
{
$content_chunks=self::mb_chunk_split($content, 4096, '');
foreach($content_chunks as $content_chunk)
{
$preg_result= preg_match(
'%^(?:
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)*$%xs',
$content_chunk
);
if($preg_result!==1)
{
return false;
}
}
return true;
}//end _checkUtf8W3c()
/**
* Checks that the string $content contains only valid UTF-8 chars
* using the method described in RFC 3629.
* Returns true if $content contains only UTF-8 chars, false otherwise.
*
* @param string $content String to check.
*
* @return bool true if $content contains only UTF-8 chars, false otherwise.
*
* @see http://www.php.net/manual/en/function.mb-detect-encoding.php#85294
*/
private static function _checkUtf8Rfc3629($content)
{
$len = strlen($content);
for ($i = 0; $i < $len; $i++) {
$c = ord($content[$i]);
if ($c > 128) {
if (($c >= 254)) {
return false;
} elseif ($c >= 252) {
$bits=6;
} elseif ($c >= 248) {
$bits=5;
} elseif ($c >= 240) {
$bytes = 4;
} elseif ($c >= 224) {
$bytes = 3;
} elseif ($c >= 192) {
$bytes = 2;
} else {
return false;
} if (($i + $bytes) > $len) {
return false;
} while ($bytes > 1) {
$i++;
$b = ord($content[$i]);
if ($b < 128 || $b > 191) {
return false;
}
$bytes--;
}
}
}
return true;
}//_checkUtf8Rfc3629()
/**
* Splits a string to chunks of given size
* This helps to avoid segmentation fault errors when large text is given
* Returns array of strings after splitting
*
* @param string $str String to split.
* @param int $len number of characters per chunk
*
* @return array string array after splitting
*
* @see http://php.net/manual/en/function.chunk-split.php
*/
private static function mb_chunk_split($str, $len, $glue)
{
if (empty($str)) return false;
$array = self::mbStringToArray ($str);
$n = -1;
$new = Array();
foreach ($array as $char) {
$n++;
if ($n < $len) $new []= $char;
elseif ($n == $len) {
$new []= $glue . $char;
$n = 0;
}
}
return $new;
}//mb_chunk_split
/**
* Supporting function for mb_chunk_split
*
* @param string $str
*
* @return array
*
* @see http://php.net/manual/en/function.chunk-split.php
*/
private static function mbStringToArray ($str)
{
if (empty($str)) return false;
$len = mb_strlen($str);
$array = array();
for ($i = 0; $i < $len; $i++) {
$array[] = mb_substr($str, $i, 1);
}
return $array;
}
}//end class
?>

View File

@ -0,0 +1,81 @@
<?php
/**
* CodeIgniter_Sniffs_Operators_LogicalOperatorAndSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Operators_LogicalOperatorAndSniff.
*
* Ensures that the logical operator 'AND' is in upper case and suggest the use of its symbolic equivalent.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Operators;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class LogicalOperatorAndSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for: symbolic and literal operators and.
*
* @return array
*/
public function register()
{
return array(
T_LOGICAL_AND,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$operator_token = $tokens[$stackPtr];
$operator_string = $operator_token['content'];
$operator_code = $operator_token['code'];
if ($operator_string !== strtoupper($operator_string)) {
$error_message = 'Logical operator should be in upper case;'
. ' use "' . strtoupper($operator_string)
. '" instead of "' . $operator_string . '"';
$phpcsFile->addError($error_message, $stackPtr, 'LowercaseLogicalOperator');
}
$warning_message = 'The symbolic form "&&" is preferred over the literal form "AND"';
$phpcsFile->addWarning($warning_message, $stackPtr, 'UseOfLiteralAndOperator');
}//end process()
}//end class
?>

View File

@ -0,0 +1,81 @@
<?php
/**
* CodeIgniter_Sniffs_Operators_StrictComparisonOperatorSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Operators_StrictComparisonOperatorSniff.
*
* Ensures that only strict comparison operators are used instead of
* equal and not equal operators.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Operators;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class StrictComparisonOperatorSniff implements Sniff
{
private static $_replacements = array(
T_IS_EQUAL => '===',
T_IS_NOT_EQUAL => '!==',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_IS_EQUAL,
T_IS_NOT_EQUAL,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$operator_token = $tokens[$stackPtr];
$operator_string = $operator_token['content'];
$operator_code = $operator_token['code'];
$error_message = '"==" and "!=" are prohibited; use "'
. self::$_replacements[$operator_code] . '" instead of "'
. $operator_string . '".';
$phpcsFile->addError($error_message, $stackPtr, 'NonStrictComparisonUsed');
}//end process()
}//end class
?>

View File

@ -0,0 +1,84 @@
<?php
/**
* CodeIgniter_Sniffs_Operators_UppercaseLogicalOperatorOrSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Operators_UppercaseLogicalOperatorOrSniff.
*
* Ensures that the logical operator 'OR' is in upper cases and its symbolic equivalent.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Operators;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class UppercaseLogicalOperatorOrSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for: literal and symbolic operators or.
*
* @return array
*/
public function register()
{
return array(
T_BOOLEAN_OR,
T_LOGICAL_OR,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$operator_token = $tokens[$stackPtr];
$operator_string = $operator_token['content'];
$operator_code = $operator_token['code'];
if ($operator_code == T_BOOLEAN_OR) {
$error_message = 'Logical operator "' . $operator_string
. '" is prohibited; use "OR" instead';
$phpcsFile->addError($error_message, $stackPtr, 'UseOf||InsteadOfOR');
}
// it is literal, if it is not symbolic
else if ($operator_string !== strtoupper($operator_string)) {
$error_message = 'Logical operator should be in upper case;'
. ' use "' . strtoupper($operator_string)
. '" instead of "' . $operator_string . '"';
$phpcsFile->addError($error_message, $stackPtr, 'UseOfLowercaseOr');
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,465 @@
<?php
/**
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Strings;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use Exception;
/**
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
*
* Ensures that double-quoted strings are used only to parse variables,
* to avoid escape characters before single quotes or for chars that need
* to be interpreted like \r, \n or \t.
* If a double-quoted string contain both single and double quotes
* but no variable, then a warning is raised to encourage the use of
* single-quoted strings.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class VariableUsageSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
/*
return array(
T_DOUBLE_QUOTED_STRING,
T_CONSTANT_ENCAPSED_STRING,
);
*/
return array();
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$string = $tokens[$stackPtr]['content'];
// makes sure that it is about a double quote string,
// since variables are not parsed out of double quoted string
$openDblQtStr = substr($string, 0, 1);
if (0 === strcmp($openDblQtStr, '"')) {
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $string);
} else if (0 === strcmp($openDblQtStr, "'")) {
$this->processSingleQuotedString($phpcsFile, $stackPtr, $string);
}
}//end process()
/**
* Processes this test, when the token encountered is a double-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $dblQtString The double-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $dblQtString)
{
$variableFound = FALSE;
$strTokens = token_get_all('<?php '.$dblQtString);
$strPtr = 1; // skip php opening tag added by ourselves
$requireDblQuotes = FALSE;
while ($strPtr < count($strTokens)) {
$strToken = $strTokens[$strPtr];
if (is_array($strToken)) {
if (in_array($strToken[0], array(T_DOLLAR_OPEN_CURLY_BRACES, T_CURLY_OPEN))) {
$strPtr++;
try {
$this->_parseVariable($strTokens, $strPtr);
} catch (Exception $err) {
$error = 'There is no variable, object nor array between curly braces. Please use the escape char for $ or {.';
$phpcsFile->addError($error, $stackPtr, 234);
}
$variableFound = TRUE;
if ('}' !== $strTokens[$strPtr]) {
$error = 'There is no matching closing curly brace.';
$phpcsFile->addError($error, $stackPtr, 345);
}
// don't move forward, since it will be done in the main loop
// $strPtr++;
} else if (T_VARIABLE === $strToken[0]) {
$variableFound = TRUE;
$error = "Variable {$strToken[1]} in double-quoted strings should be enclosed with curly braces. Please consider {{$strToken[1]}}";
$phpcsFile->addError($error, $stackPtr, 456);
}
}
$strPtr++;
}
return $variableFound;
}//end processDoubleQuotedString()
/**
* Processes this test, when the token encountered is a single-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $sglQtString The single-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $sglQtString)
{
$variableFound = FALSE;
$strTokens = token_get_all('<?php '.$sglQtString);
$strPtr = 1; // skip php opening tag added by ourselves
while ($strPtr < count($strTokens)) {
$strToken = $strTokens[$strPtr];
if (is_array($strToken)) {
if (T_VARIABLE === $strToken[0]) {
$error = "Variables like {$strToken[1]} should be in double-quoted strings only.";
$phpcsFile->addError($error, $stackPtr);
}
}
$strPtr++;
}
return $variableFound;
}//end processSingleQuotedString()
/**
* Grammar rule to parse the use of a variable. Please notice that it
* doesn't manage the leading $.
*
* _parseVariable ::= <variable>
* | <variable>_parseObjectAttribute()
* | <variable>_parseArrayIndexes()
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return array The attribute name associated to index 'var', an array with
* indexes 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
*/
private function _parseVariable ($strTokens, &$strPtr)
{
if ( ! in_array($strTokens[$strPtr][0], array(T_VARIABLE, T_STRING_VARNAME))) {
throw new Exception ('Expected variable name.');
}
$var = $strTokens[$strPtr][1];
$strPtr++;
$startStrPtr = $strPtr;
try {
$attr = $this->_parseObjectAttribute($strTokens, $strPtr);
return array ('obj' => $var, 'attr' => $attr);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
try {
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
return array ('arr' => $var, 'idx' => $idx);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
return array ('var' => $var);
}//end _parseVariable()
/**
* Grammar rule to parse the use of an object attribute.
*
* _parseObjectAttribute ::= -><attribute>
* | -><attribute>_parseObjectAttribute()
* | -><attribute>_parseArrayIndexes()
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return mixed The attribute name as a string, an array with indexes
* 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
*/
private function _parseObjectAttribute ($strTokens, &$strPtr)
{
if (T_OBJECT_OPERATOR !== $strTokens[$strPtr][0]) {
throw new Exception ('Expected ->.');
}
$strPtr++;
if (T_STRING !== $strTokens[$strPtr][0]) {
throw new Exception ('Expected an object attribute.');
}
$attr = $strTokens[$strPtr][1];
$strPtr++;
$startStrPtr = $strPtr;
try {
$sub_attr = $this->_parseObjectAttribute($strTokens, $strPtr);
return array ('obj' => $attr, 'attr' => $sub_attr);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
try {
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
return array ('arr' => $attr, 'idx' => $idx);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
return $attr;
}//end _parseObjectAttribute()
/**
* Grammar rule to parse the use of one or more array indexes.
*
* _parseArrayIndexes ::= _parseArrayIndex()+
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return array Indexes in the same order as in the string.
*/
private function _parseArrayIndexes ($strTokens, &$strPtr)
{
$indexes = array($this->_parseArrayIndex($strTokens, $strPtr));
try {
while (1) {
$startStrPtr = $strPtr;
$indexes [] = $this->_parseArrayIndex($strTokens, $strPtr);
}
} catch (Exception $err) {
if (0 !== ($strPtr - $startStrPtr)) {
throw $err;
}
return $indexes;
}
}//end _parseArrayIndexes()
/**
* Grammar rule to parse the use of array index.
*
* _parseArrayIndex ::= [<index>]
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return string Index between the 2 square brackets
*/
private function _parseArrayIndex ($strTokens, &$strPtr)
{
if ('[' !== $strTokens[$strPtr]) {
throw new Exception ('Expected [.');
}
$strPtr++;
if (! in_array($strTokens[$strPtr][0], array(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER))) {
throw new Exception ('Expected an array index.');
}
$index = $strTokens[$strPtr][1];
$strPtr++;
if (']' !== $strTokens[$strPtr]) {
throw new Exception ('Expected ].');
}
$strPtr++;
return $index;
}//end _parseArrayIndex()
}//end class
/**
* CodeIgniter_Sniffs_Strings_VariableUsageSniff.
*
* Ensures that variables parsed in double-quoted strings are enclosed with
* braces to prevent greedy token parsing.
* Single-quoted strings don't parse variables, so there is no risk of greedy
* token parsing.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DoubleQuoteUsageSniff extends VariableUsageSniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_DOUBLE_QUOTED_STRING,
T_CONSTANT_ENCAPSED_STRING,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// no variable are in the string from here
$tokens = $phpcsFile->getTokens();
$qtString = $tokens[$stackPtr]['content'];
// makes sure that it is about a double quote string,
// since variables are not parsed out of double quoted string
$open_qt_str = substr($qtString, 0, 1);
// clean the enclosing quotes
$qtString = substr($qtString, 1, strlen($qtString) - 1 - 1);
if (0 === strcmp($open_qt_str, '"')) {
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $qtString);
} else if (0 === strcmp($open_qt_str, "'")) {
$this->processSingleQuotedString($phpcsFile, $stackPtr, $qtString);
}
}//end process()
/**
* Processes this test, when the token encountered is a double-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $qtString The double-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $qtString)
{
// so there should be at least a single quote or a special char
// if there are the 2 kinds of quote and no special char, then add a warning
$has_variable = parent::processDoubleQuotedString($phpcsFile, $stackPtr, '"'.$qtString.'"');
$has_specific_sequence = $this->_hasSpecificSequence($qtString);
$dbl_qt_at = strpos($qtString, '"');
$smpl_qt_at = strpos($qtString, "'");
if (false === $has_variable && false === $has_specific_sequence
&& false === $smpl_qt_at
) {
$error = 'Single-quoted strings should be used unless it contains variables, special chars like \n or single quotes.';
$phpcsFile->addError($error, $stackPtr, 111);
} else if (false !== $smpl_qt_at && false !== $dbl_qt_at
&& false === $has_variable && false === $has_specific_sequence
) {
$warning = 'It is encouraged to use a single-quoted string, since it doesn\'t contain any variable nor special char though it mixes single and double quotes.';
$phpcsFile->addWarning($warning, $stackPtr, 222);
}
}//end processDoubleQuotedString()
/**
* Processes this test, when the token encountered is a single-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $qtString The single-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $qtString)
{
// if there is single quotes without additional double quotes,
// then user is allowed to use double quote to avoid having to
// escape single quotes. Don't add the warning, if an error was
// already added, because a variable was found in a single-quoted
// string.
$has_variable = parent::processSingleQuotedString($phpcsFile, $stackPtr, "'".$qtString."'");
$dbl_qt_at = strpos($qtString, '"');
$smpl_qt_at = strpos($qtString, "'");
if (false === $has_variable && false !== $smpl_qt_at && false === $dbl_qt_at) {
$warning = 'You may also use double-quoted strings if the string contains single quotes, so you do not have to use escape characters.';
$phpcsFile->addWarning($warning, $stackPtr, 333);
}
}//end processSingleQuotedString()
/**
* Return TRUE, if a sequence of chars that is parsed in a specific way
* in double-quoted strings is found, FALSE otherwise.
*
* @param string $string String in which sequence of special chars will
* be researched.
*
* @return TRUE, if a sequence of chars that is parsed in a specific way
* in double-quoted strings is found, FALSE otherwise.
*
* @link http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.double
*/
private function _hasSpecificSequence($string)
{
$hasSpecificSequence = FALSE;
$specialMeaningStrs = array('\n', '\r', '\t', '\v', '\f');
foreach ($specialMeaningStrs as $splStr) {
if (FALSE !== strpos($string, $splStr)) {
$hasSpecificSequence = TRUE;
}
}
$specialMeaningPtrns = array('\[0-7]{1,3}', '\x[0-9A-Fa-f]{1,2}');
foreach ($specialMeaningPtrns as $splPtrn) {
if (1 === preg_match("/{$splPtrn}/", $string)) {
$hasSpecificSequence = TRUE;
}
}
return $hasSpecificSequence;
}//end _hasSpecificSequence()
}//end class
?>

View File

@ -0,0 +1,87 @@
<?php
/**
* CodeIgniter_Sniffs_WhiteSpace_DisallowSpaceIndentSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@gmail.com>
* @copyright 2011 Thomas ERNEST
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_WhiteSpace_DisallowSpaceIndentSniff.
*
* Ensures the use of tabs for indentation.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@gmail.com>
* @copyright 2011 Thomas ERNEST
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class DisallowSpaceIndentSniff implements Sniff
{
/**
* A list of tokenizers this sniff supports.
*
* @var array
*/
public $supportedTokenizers = array(
'PHP',
'JS',
'CSS',
);
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(T_WHITESPACE);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile All the tokens found in the document.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Make sure this is whitespace used for indentation.
$line = $tokens[$stackPtr]['line'];
if ($stackPtr > 0 && $tokens[($stackPtr - 1)]['line'] === $line) {
return;
}
if (strpos($tokens[$stackPtr]['content'], " ") !== false) {
$error = 'Tabs must be used to indent lines; spaces are not allowed for code indentation';
$phpcsFile->addError($error, $stackPtr, 'SpacesUsedForIndentation');
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,95 @@
<?php
/**
* CodeIgniter_Sniffs_WhiteSpace_DisallowWitheSpaceAroundPhpTagsSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_WhiteSpace_DisallowWitheSpaceAroundPhpTagsSniff.
*
* Ensures that no whitespace precedes the opening PHP tag
* or follows the closing PHP tag.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class DisallowWitheSpaceAroundPhpTagsSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_OPEN_TAG,
T_CLOSE_TAG
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$php_tag_token = $tokens[$stackPtr];
$php_tag_code = $php_tag_token['code'];
if (T_OPEN_TAG === $php_tag_code) {
// opening php tag should be the first token.
// any whitespace beofre an opening php tag is tokenized
// as T_INLINE_HTML, so no need to check the content of the token.
$isFirst = 0 === $stackPtr;
if ( ! $isFirst) {
$error = 'Any char before the opening PHP tag is prohibited. Please remove newline or indentation before the opening PHP tag.';
$phpcsFile->addError($error, $stackPtr);
}
} else {
// if (T_CLOSE_TAG === $php_tag_code)
// closing php tag should be the last token
// and it must not contain any whitespace.
$php_tag_string = $php_tag_token['content'];
$isLast = count($tokens) - 1 === $stackPtr;
// both of the two closing php tags contains 2 chars exactly.
$containsEndTagOnly = strlen($php_tag_string) > 2;
if ( ! $isLast || ! $containsEndTagOnly ) {
$error = 'Any char after the closing PHP tag is prohibited. Please removes newline or spaces after the closing PHP tag.';
$phpcsFile->addError($error, $stackPtr);
}
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,82 @@
<?php
/**
* CodeIgniter_Sniffs_WhiteSpace_ElseOnNewLineSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_WhiteSpace_ElseOnNewLineSniff.
*
* Ensures that control structures else and elseif stand on new lines.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class ElseOnNewLineSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_ELSE,
T_ELSEIF,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$else_token = $tokens[$stackPtr];
$previous_non_blank_token_ptr = $phpcsFile->findPrevious(array(T_WHITESPACE), $stackPtr - 1, null, true);
if (false === $previous_non_blank_token_ptr) {
// else is no preceded with any symbol, but it is not the responsibility of this sniff.
return;
}
$previous_non_blank_token = $tokens[$previous_non_blank_token_ptr];
if ($previous_non_blank_token['line'] === $else_token['line']) {
$error = '"' . $else_token['content'] . '" should be on a new line.';
$phpcsFile->addError($error, $stackPtr, 123423);
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,75 @@
<?php
/**
* CodeIgniter_Sniffs_WhiteSpace_LogicalNotSpacingSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_WhiteSpace_LogicalNotSpacingSniff.
*
* Ensures that at exactly a space precedes and follows the logical operator !.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class LogicalNotSpacingSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_BOOLEAN_NOT,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$operator_token = $tokens[$stackPtr];
$previous_token = $tokens[$stackPtr - 1];
$next_token = $tokens[$stackPtr + 1];
if (T_WHITESPACE !== $previous_token['code'] || T_WHITESPACE !== $next_token['code']) {
$error = 'Logical operator ! should always be preceded and followed with a whitespace.';
$phpcsFile->addError($error, $stackPtr, 'badNot');
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,104 @@
<?php
/**
* CodeIgniter_Sniffs_Files_AbstractClosingCommentSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Files_AbstractClosingCommentSniff.
*
* Defines some methods used by
* CodeIgniter_Sniffs_Files_ClosingFileCommentSniff
* and CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Files;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class AbstractClosingCommentSniff implements Sniff
{
/**
* As an abstract class, this sniff is not associated to any token.
*/
public function register()
{
return array();
}
/**
* As an abstract class, this sniff is not dedicated to process a token.
*/
public function process(File $phpcsFile, $stackPtr)
{
$error = __CLASS__.'::'.__METHOD__.' is abstract. Please develop this method in a child class.';
throw new PHP_CodeSniffer_Exception($error);
}
/**
* Returns the comment without its delimiter(s) as well as leading
* and traling whitespaces.
*
* It removes the first #, the two first / (i.e. //) or the first /*
* and last \*\/. If a comment starts with /**, then the last * will remain
* as well as whitespaces between this star and the comment content.
*
* @param string $comment Comment containing either comment delimiter(s) and
* trailing or leading whitspaces to clean.
*
* @return string Comment without comment delimiter(s) and whitespaces.
*/
protected static function _getCommentContent ($comment)
{
if (self::_stringStartsWith($comment, '#')) {
$comment = substr($comment, 1);
} else if (self::_stringStartsWith($comment, '//')) {
$comment = substr($comment, 2);
} else if (self::_stringStartsWith($comment, '/*')) {
$comment = substr($comment, 2, strlen($comment) - 2 - 2);
}
$comment = trim($comment);
return $comment;
}//_getCommentContent()
/**
* Binary safe string comparison between $needle and
* the beginning of $haystack. Returns true if $haystack starts with
* $needle, false otherwise.
*
* @param string $haystack The string to search in.
* @param string $needle The string to search for.
*
* @return bool true if $haystack starts with $needle, false otherwise.
*/
protected static function _stringStartsWith ($haystack, $needle)
{
$startsWith = false;
if (strlen($needle) <= strlen($haystack)) {
$haystackBeginning = substr($haystack, 0, strlen($needle));
if (0 === strcmp($haystackBeginning, $needle)) {
$startsWith = true;
}
}
return $startsWith;
}//_stringStartsWith()
}//end class
?>

View File

@ -0,0 +1,109 @@
<?php
/**
* CodeIgniter_Sniffs_Files_ClosingFileCommentSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Files_ClosingFileCommentSniff.
*
* Ensures that a comment containing the file name is available at the end of file.
* Only other comments and whitespaces are allowed to follow this specific comment.
*
* It may be all kind of comment like multi-line and inline C-style comments as
* well as PERL-style comments. Any number of white may separate comment delimiters
* from comment content. However, content has to be equal to template
* "End of file <file_name>". Comparison between content and template is
* case-sensitive.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Files;
use PHP_CodeSniffer\Files\File;
class ClosingFileCommentSniff extends AbstractClosingCommentSniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_OPEN_TAG,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// We are only interested if this is the first open tag.
if ($stackPtr !== 0) {
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
return;
}
}
$fullFilename = $phpcsFile->getFilename();
$filename = basename($fullFilename);
$commentTemplate = "End of file $filename";
$tokens = $phpcsFile->getTokens();
$currentToken = count($tokens) - 1;
$hasClosingFileComment = false;
$isNotAWhitespaceOrAComment = false;
while ($currentToken >= 0
&& ! $isNotAWhitespaceOrAComment
&& ! $hasClosingFileComment
) {
$token = $tokens[$currentToken];
$tokenCode = $token['code'];
if (T_COMMENT === $tokenCode) {
$commentString = self::_getCommentContent($token['content']);
if (0 === strcmp($commentString, $commentTemplate)) {
$hasClosingFileComment = true;
}
} else if (T_WHITESPACE === $tokenCode) {
// Whitespaces are allowed between the closing file comment,
// other comments and end of file
} else {
$isNotAWhitespaceOrAComment = true;
}
$currentToken--;
}
if ( ! $hasClosingFileComment) {
$error = 'No comment block marks the end of file instead of the closing PHP tag. Please add a comment block containing only "' . $commentTemplate . '".';
$phpcsFile->addError($error, $currentToken);
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,182 @@
<?php
/**
* CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff.
*
* Ensures that a comment containing the file location exists at the end of file.
* Only other comments and whitespaces are allowed between this comment and
* the end of file.
*
* It may be all kind of comment like multi-line and inline C-style comments as
* well as PERL-style comments. Any number of white may separate comment delimiters
* from comment content. However, content has to be equal to template
* "Location: <file_path_relative_to_application_root>".
* Comparison between content and template is case-sensitive.
*
* There are several ways to configure the application root. In order of priority :
* - Configuration variable ci_application_root.
* - Rule property applicationRoot.
* - Default value '/application/'
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2006 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Files;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Common;
class ClosingLocationCommentSniff extends AbstractClosingCommentSniff
{
public $applicationRoot = '/application/';
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_OPEN_TAG
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// We are only interested if this is the first open tag.
if ($stackPtr !== 0) {
if ($phpcsFile->findPrevious(T_OPEN_TAG, ($stackPtr - 1)) !== false) {
return;
}
}
$filePath = $phpcsFile->getFilename();
$tokens = $phpcsFile->getTokens();
// removes the application root from the beginning of the file path
$locationPath = self::_getLocationPath($filePath, $this->_getAppRoot());
// add an error, if application root doesn't exist in current file path
if (false === $locationPath) {
$error = 'Unable to find "' . $this->_getAppRoot() . '" in file path "' . $filePath . '". Please set your project\'s application root.';
$phpcsFile->addError($error, count($tokens) - 1);
return;
}
// generates the expected comment
$commentTemplate = "Location: $locationPath";
$currentToken = count($tokens) - 1;
$hasClosingLocationComment = false;
$isNotAWhitespaceOrAComment = false;
while ($currentToken >= 0
&& ! $isNotAWhitespaceOrAComment
&& ! $hasClosingLocationComment
) {
$token = $tokens[$currentToken];
$tokenCode = $token['code'];
if (T_COMMENT === $tokenCode) {
$commentString = self::_getCommentContent($token['content']);
if (0 === strcmp($commentString, $commentTemplate)) {
$hasClosingLocationComment = true;
}
} else if (T_WHITESPACE === $tokenCode) {
// Whitespaces are allowed between the closing file comment,
//other comments and end of file
} else {
$isNotAWhitespaceOrAComment = true;
}
$currentToken--;
}
if ( ! $hasClosingLocationComment) {
$error = 'No comment block marks the end of file instead of the closing PHP tag. Please add a comment block containing only "' . $commentTemplate . '".';
$phpcsFile->addError($error, $currentToken);
}
}//end process()
/**
* Returns the relative path from $appRoot to $filePath, or false if
* $appRoot cannot be found in $filePath, because $appRoot is not a parent
* of $filePath.
*
* @param string $filePath Full path to the file being proceed.
* @param string $appRoot Partial or full path to the CodeIgniter
* application root of the file being proceed. It must not contain the
* full path to the application root, but at least the name of the
* application root. Parent directory of the application root are allowed
* but not mandatory.
*
* @return string|bool The relative path from $appRoot to $filePath, or
* false if $appRoot cannot be found in $filePath.
*/
private static function _getLocationPath ($filePath, $appRoot)
{
// removes the path to application root
// from the beginning of the file path
$appRootAt = strpos($filePath, $appRoot);
if (false === $appRootAt) {
return false;
}
$localPath = substr($filePath, $appRootAt + strlen($appRoot));
// ensures the location path to be a relative path starting with "./".
if ( ! self::_stringStartsWith($localPath, './')) {
$localPath = './' . $localPath;
} else if ( ! self::_stringStartsWith($localPath, '.')
&& self::_stringStartsWith($localPath, '/')
) {
$localPath = '.' . $localPath;
}
return $localPath;
}//end _getLocationPath()
/**
* Returns the application root that should be used first.
*
* There are several ways to configure the application root.
* In order of priority :
* - Configuration variable ci_application_root.
* - Rule property applicationRoot.
* - Default value '/application/'
*
* @return string Path to your project application root.
*/
private function _getAppRoot()
{
$appRoot = Common::getConfigData('ci_application_root');
if (null === $appRoot) {
$appRoot = $this->applicationRoot;
}
return $appRoot;
}//end _getAppRoot()
}//end class
?>

View File

@ -0,0 +1,142 @@
<?php
/**
* CodeIgniter_Sniffs_NamingConventions_ConstructorNameSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@gmail.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\NamingConventions;
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
use PHP_CodeSniffer\Files\File;
/**
* CodeIgniter_Sniffs_NamingConventions_ConstructorNameSniff.
*
* Favor PHP 4 constructor syntax, which uses "function ClassName()".
* Avoid PHP 5 constructor syntax, which uses "function __construct()".
*
* @todo Try to avoid overly long and verbose names.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@gmail.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ConstructorNameSniff extends AbstractScopeSniff
{
public $php5Constructors = '1';
/**
* Constructs the test with the tokens it wishes to listen for.
*
* @return void
*/
public function __construct()
{
parent::__construct(array(T_CLASS, T_INTERFACE), array(T_FUNCTION), true);
}//end __construct()
/**
* Processes this test when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $currScope A pointer to the start of the scope.
*
* @return void
*/
protected function processTokenWithinScope(
File $phpcsFile,
$stackPtr,
$currScope
) {
$methodName = $phpcsFile->getDeclarationName($stackPtr);
$className = $phpcsFile->getDeclarationName($currScope);
$isPhp4Constructor = strcasecmp($methodName, $className) === 0;
$isPhp5Constructor = strcasecmp($methodName, '__construct') === 0;
if ($this->php5Constructors != '0') {
if ($isPhp4Constructor) {
$error = "PHP4 style constructors are not allowed; use \"__construct\" instead";
$phpcsFile->addError($error, $stackPtr);
}
} else {
if ($isPhp5Constructor) {
$error = "PHP5 style constructors are not allowed; use \"$className\" instead";
$phpcsFile->addError($error, $stackPtr);
}
}
if ( ! $isPhp4Constructor && ! $isPhp5Constructor ) {
return;
}
$tokens = $phpcsFile->getTokens();
$parentClassName = $phpcsFile->findExtendedClassName($currScope);
$wrongConstructor = '';
// prepares the error message and wrong constructor
if ($this->php5Constructors != '0') {
$error = 'PHP4 style calls to parent constructors are not allowed.';
$error = "$error Please use \"parent::__construct\" instead.";
if (false !== $parentClassName) {
$wrongConstructor = $parentClassName;
}
// Else $wrongConstructor will be empty
// and the test expression will always be false.
// It doesn't check that no parent method should be called
// when no parent class is defined.
} else {
$error = 'PHP5 style calls to parent constructors are not allowed.';
if (false !== $parentClassName) {
$error = "$error Please use \"parent::$parentClassName\" instead.";
}
$wrongConstructor = '__construct';
}
// looks for the use of a wrong constructor.
$endFunctionIndex = $tokens[$stackPtr]['scope_closer'];
$doubleColonIndex = $phpcsFile->findNext(
array(T_DOUBLE_COLON),
$stackPtr,
$endFunctionIndex
);
while ($doubleColonIndex) {
if ($tokens[($doubleColonIndex + 1)]['code'] === T_STRING
&& $tokens[($doubleColonIndex + 1)]['content'] === $wrongConstructor
) {
$phpcsFile->addError($error, ($doubleColonIndex + 1));
}
$doubleColonIndex = $phpcsFile->findNext(
array(T_DOUBLE_COLON),
$doubleColonIndex + 1,
$endFunctionIndex
);
}
}//end processTokenWithinScope()
protected function processTokenOutsideScope(File $phpcsFile, $stackPtr)
{
// TODO: Implement processTokenOutsideScope() method.
}
}//end class
?>

View File

@ -0,0 +1,84 @@
<?php
/**
* CodeIgniter_Sniffs_NamingConventions_ValidClassNameSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_NamingConventions_ValidClassNameSniff.
*
* Ensures that class and interface names have their first letter uppercase
* and that words are separated with an underscore, and not CamelCased.
*
* @todo Try to avoid overly long and verbose names in using property rule and
* configuration variable to set limits. Have a look at
* CodeIgniter_Sniffs_NamingConventions_ValidMethodNameSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\NamingConventions;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class ValidClassNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_CLASS,
T_INTERFACE,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
// get the class name
$className = trim($phpcsFile->getDeclarationName($stackPtr));
// compute the expected class name
// [^_] means "something different from _", but not "nothing or something different from _"
$lcClassNameChunk = preg_replace('/([^_])([A-Z])/', '${1}_${2}', $className);
$expectedClassName
= strtoupper($className[0]) . strtolower(substr($lcClassNameChunk,1));
// ensures that the current class name
// and the expected class name are identical
if (0 !== strcmp($className, $expectedClassName)) {
$error = 'Class names should always have their first letter uppercase. Multiple words should be separated with an underscore, and not CamelCased. Please consider ' . $expectedClassName . ' instead of ' . $className . '.';
$phpcsFile->addError($error, $stackPtr);
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,84 @@
<?php
/**
* CodeIgniter_Sniffs_NamingConventions_ValidFileNameSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_NamingConventions_ValidFileNameSniff.
*
* Tests that the file name matchs the name of the class that it contains in lower case.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\NamingConventions;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
class ValidFileNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_CLASS,
T_INTERFACE,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// computes the expected filename based on the name of the class or interface that it contains.
$decNamePtr = $phpcsFile->findNext(T_STRING, $stackPtr);
$decName = $tokens[$decNamePtr]['content'];
$expectedFileName = strtolower($decName);
// extracts filename without extension from its path.
$fullPath = $phpcsFile->getFilename();
$fileNameAndExt = basename($fullPath);
$fileName = substr($fileNameAndExt, 0, strrpos($fileNameAndExt, '.'));
if ($expectedFileName !== $fileName) {
$errorTemplate = 'Filename "%s" doesn\'t match the name of the %s that it contains "%s" in lower case. "%s" was expected.';
$errorMessage = sprintf(
$errorTemplate,
$fileName,
strtolower($tokens[$stackPtr]['content']), // class or interface
$decName,
$expectedFileName
);
$phpcsFile->addError($errorMessage, 0);
}
}//end process()
}//end class
?>

View File

@ -0,0 +1,161 @@
<?php
/**
* CodeIgniter_Sniffs_NamingConventions_ValidMethodNameSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_NamingConventions_ValidMethodNameSniff.
*
* Ensures that class methods and functions areentirely lowercased and that
* words are separated with an underscore, and not CamelCased.
* Ensures that private class methods are prefixed with an underscore and that
* all other methods are not prefixed with an underscored.
* Ensures that names longer than 50 chars are prohibited. Likewise names longer
* than 35 chars raise a warning.
*
* @todo Use a rule property or a configuration variable to allow users to set
* their own maximum lengths for function and method names. Have a look at
* CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff and application root.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\NamingConventions;
use PHP_CodeSniffer\Sniffs\AbstactScopeSniff;
use PHP_CodeSniffer\Files\File;
class ValidMethodNameSniff extends AbstractScopeSniff
{
/**
* A list of all PHP magic methods.
*
* @var array
*/
protected static $magicMethods = array(
'construct',
'destruct',
'call',
'callStatic',
'get',
'set',
'isset',
'unset',
'sleep',
'wakeup',
'toString',
'set_state',
'clone',
);
/**
* Defines which token(s) in which scope(s) will be proceed.
*/
public function __construct()
{
parent::__construct(array(T_CLASS, T_INTERFACE), array(T_FUNCTION), true);
}//end __construct()
/**
* Processes the tokens within the scope.
*
* @param File $phpcsFile The file being processed.
* @param int $stackPtr The position where this token was
* found.
* @param int $currScope The position of the current scope.
*
* @return void
*/
protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope)
{
$methodName = $phpcsFile->getDeclarationName($stackPtr);
if ($methodName === null) {
// Ignore closures.
return;
}
$className = $phpcsFile->getDeclarationName($currScope);
// Is this a magic method i.e. is prefixed with "__".
if (0 === strcmp(substr($methodName, 0, 2), '__')) {
$magicPart = substr($methodName, 2);
if (in_array($magicPart, self::$magicMethods) === false) {
$error = "Method name \"$className::$methodName\" is invalid; only PHP magic methods should be prefixed with a double underscore";
$phpcsFile->addError($error, $stackPtr);
}
return;
}
// PHP4 constructors are allowed to break our rules.
if ($methodName === $className) {
return;
}
// PHP4 destructors are allowed to break our rules.
if ($methodName === '_'.$className) {
return;
}
if (0 !== strcmp($methodName, strtolower($methodName))) {
$uscrdMethodName = preg_replace('/([A-Z])/', '_${1}', $methodName);
$expectedMethodName = strtolower($uscrdMethodName);
$error = "Class methods should be entirely lowercased. Please consider \"$expectedMethodName\" instead of \"$methodName\".";
$phpcsFile->addError($error, $stackPtr);
return;
}
$methodProps = $phpcsFile->getMethodProperties($stackPtr);
$scope = $methodProps['scope'];
$scopeSpecified = $methodProps['scope_specified'];
// If it's a private method, it must have an underscore on the front.
if ($scope === 'private' && $methodName{0} !== '_') {
$error = "Private method name \"$className::$methodName\" must be prefixed with an underscore";
$phpcsFile->addError($error, $stackPtr);
return;
}
// If it's not a private method, it must not have an underscore on the front.
if ($scope !== 'private' && $methodName{0} === '_') {
if (true === $scopeSpecified) {
$error = "Public method name \"$className::$methodName\" must not be prefixed with an underscore";
} else {
$error = ucfirst($scope)." method name \"$className::$methodName\" must not be prefixed with an underscore";
}
$phpcsFile->addError($error, $stackPtr);
return;
}
// If name is too verbose,
// then either an error or a warning is displayed.
$error_limit = 50;
$warning_limit = 35;
if (strlen($methodName) > $error_limit) {
$error = "Overly long and verbose names are prohibited. Please find a name shorter than $error_limit chars.";
$phpcsFile->addError($error, $stackPtr);
return;
} else if (strlen($methodName) > $warning_limit) {
$warning = "Try to avoid overly long and verbose names in finding a name shorter than $warning_limit chars.";
$phpcsFile->addWarning($warning, $stackPtr);
}
}//end processTokenWithinScope()
}//end class
?>

View File

@ -0,0 +1,562 @@
<?php
/**
* CodeIgniter_Sniffs_NamingConventions_ValidVariableNameSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
/**
* CodeIgniter_Sniffs_NamingConventions_ValidVariableNameSniff.
*
* Ensures that variable names contain only lowercase letters,
* use underscore separators.
* Ensures that class attribute names are prefixed with an underscore,
* only when they are private.
* Ensure that variable names are longer than 3 chars except those declared
* in for loops.
*
* @todo Try to avoid overly long and verbose names in using property rule and
* configuration variable to set limits. Have a look at
* CodeIgniter_Sniffs_NamingConventions_ValidMethodNameSniff.
* @todo Use a property rule or a configuration variable to allow users to set
* minimum variable name length. Have a look at
* CodeIgniter_Sniffs_Files_ClosingLocationCommentSniff and application root.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baoabz.com>
* @copyright 2010 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\NamingConventions;
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
use PHP_CodeSniffer\Files\File;
class ValidVariableNameSniff extends AbstractVariableSniff
{
/**
* Processes class member variables.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
protected function processMemberVar(File $phpcsFile, $stackPtr)
{
// get variable name and properties
$tokens = $phpcsFile->getTokens();
$varTk = $tokens[$stackPtr];
$varName = substr($varTk['content'], 1);
$varProps = $phpcsFile->getMemberProperties($stackPtr);
// check(s)
if ( ! $this->checkLowerCase($phpcsFile, $stackPtr, $varName) ) {
return;
}
if ( ! $this->checkVisibilityPrefix($phpcsFile, $stackPtr, $varName, $varProps)) {
return;
}
if ( ! $this->checkLength($phpcsFile, $stackPtr, $varName)) {
return;
}
}//end processMemberVar()
/**
* Processes normal variables.
*
* @param File $phpcsFile The file where this token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void
*/
protected function processVariable(File $phpcsFile, $stackPtr)
{
// get variable name
$tokens = $phpcsFile->getTokens();
$varTk = $tokens[$stackPtr];
$varName = substr($varTk['content'], 1);
// skip the current object variable, i.e. $this
if (0 === strcmp($varName, 'this')) {
return;
}
// check(s)
if ( ! $this->checkLowerCase($phpcsFile, $stackPtr, $varName)) {
return;
}
if ( ! $this->checkLength($phpcsFile, $stackPtr, $varName)) {
return;
}
}//end processVariable()
/**
* Processes variables in double quoted strings.
*
* @param File $phpcsFile The file where this token was found.
* @param int $stackPtr The position where the token was found.
*
* @return void
*/
protected function processVariableInString(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$stringTk = $tokens[$stackPtr];
$stringString = $stringTk['content'];
$varAt = self::_getVariablePosition($stringString, 0);
while (false !== $varAt) {
// get variable name
$matches = array();
preg_match('/^\$\{?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}?/', substr($stringString, $varAt), $matches);
$varName = $matches[1];
// check(s)
if ( ! $this->checkLowerCase($phpcsFile, $stackPtr, $varName)) {
return;
}
if ( ! $this->checkLength($phpcsFile, $stackPtr, $varName)) {
return;
}
// prepare checking next variable
$varAt = self::_getVariablePosition($stringString, $varAt + 1);
}
}//end processVariableInString()
/**
* Checks that the variable name is all in lower case, else it add an error
* to $phpcsFile. Returns true if variable name is all in lower case, false
* otherwise.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $varName The name of the variable to
* procced without $, { nor }.
*
* @return bool true if variable name is all in lower case, false otherwise.
*/
protected function checkLowerCase(File $phpcsFile, $stackPtr, $varName)
{
$isInLowerCase = true;
if (0 !== strcmp($varName, strtolower($varName))) {
// get the expected variable name
$varNameWithUnderscores = preg_replace('/([A-Z])/', '_${1}', $varName);
$expectedVarName = strtolower(ltrim($varNameWithUnderscores, '_'));
// adapts the error message to the error case
if (strlen($varNameWithUnderscores) > strlen($varName)) {
$error = 'Variables should not use CamelCasing or start with a Capital.';
} else {
$error = 'Variables should be entirely lowercased.';
}
$error = $error . 'Please consider "' . $expectedVarName
. '" instead of "' . $varName . '".';
// adds the error and changes return value
$phpcsFile->addError($error, $stackPtr);
$isInLowerCase = false;
}
return $isInLowerCase;
}//end checkLowerCase()
/**
* Checks that an underscore is used at the beginning of a variable only if
* it is about a private variable. If it isn't a private variable, then it
* must not be prefixed with an underscore. Returns true if $varName is
* properly prefixed according to the variable visibility provided in
* $varProps, false otherwise.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $varName The name of the variable to
* procced without $, { nor }.
* @param array $varProps Member variable properties like
* its visibility.
*
* @return bool true if variable name is prefixed with an underscore only
* when it is about a private variable, false otherwise.
*/
protected function checkVisibilityPrefix(File $phpcsFile, $stackPtr, $varName, $varProps)
{
$isVisibilityPrefixRight = true;
$scope = $varProps['scope'];
// If it's a private variable, it must have an underscore on the front.
if ($scope === 'private' && $varName{0} !== '_') {
$error = "Private variable name \"$varName\" must be prefixed with an underscore";
$phpcsFile->addError($error, $stackPtr);
$isVisibilityPrefixRight = false;
} else if ($scope !== 'private' && $varName{0} === '_') {
// If it's not a private variable,
// then it must not start with an underscore.
if (isset ($scopeSpecified) && true === $scopeSpecified) {
$error = "Public variable name \"$varName\" must not be prefixed with an underscore";
} else {
$error = ucfirst($scope) . " variable name \"$varName\" must not be prefixed with an underscore";
}
$phpcsFile->addError($error, $stackPtr);
$isVisibilityPrefixRight = false;
}
return $isVisibilityPrefixRight;
}//end checkVisibilityPrefix()
/**
* Checks that variable name length is not too short. Returns true, if it
* meets minimum length requirement, false otherwise.
*
* A variable name is too short if it is shorter than the minimal
* length and it isn't in the list of allowed short names nor declared in a
* for loop (in which it would be nested).
* The minimal length is defined in the function. It is 3 chars now.
* The list of allowed short names is defined in the function.
* It is case-sensitive. It contains only 'ci' now.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $varName The name of the variable to
* procced without $, { nor }.
*
* @return bool false if variable name $varName is shorter than the minimal
* length and it isn't in the list of allowed short names nor declared in a
* for loop (in which it would be nested), otherwise true.
*/
protected function checkLength(File $phpcsFile, $stackPtr, $varName)
{
$minLength = 3;
$allowedShortName = array('ci');
$isLengthRight = true;
// cleans variable name
$varName = ltrim($varName, '_');
if (strlen($varName) <= $minLength) {
// skips adding an error, if it is a specific variable name
if (in_array($varName, $allowedShortName)) {
return $isLengthRight;
}
// skips adding an error, if the variable is in a for loop
if (false !== self::_isInForLoop($phpcsFile, $stackPtr, $varName)) {
return $isLengthRight;
}
// adds the error message finally
$error = 'Very short'
. (
$minLength > 0 ?
' (i.e. less than ' . ($minLength + 1) . ' chars)'
: ''
)
. ', non-word variables like "' . $varName
. '" should only be used as iterators in for() loops.';
$phpcsFile->addError($error, $stackPtr);
$isLengthRight = false;
}
return $isLengthRight;
}//end checkLength()
/**
* Returns the position of closest previous T_FOR, if token associated with
* $stackPtr in $phpcsFile is in a for loop, otherwise false.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $varName The name of the variable to
* procced without $, { nor }.
*
* @return int|bool Position of T_FOR if token associated with $stackPtr in
* $phpcsFile is in the head of a for loop, otherwise false.
*/
private static function _isInForLoop(File $phpcsFile, $stackPtr, $varName)
{
$keepLookingFromPtr = $stackPtr;
while (false !== $keepLookingFromPtr) {
// looks if it is in (head or body) of a for loop
$forPtr = self::_isInForLoopHead($phpcsFile, $keepLookingFromPtr);
if (false === $forPtr) {
$forPtr = self::_isInForLoopBody($phpcsFile, $keepLookingFromPtr);
}
// checks if it is declared in here and prepares next step
if (false !== $forPtr) {
if (false !== self::_isDeclaredInForLoop($phpcsFile, $forPtr, $varName)) {
return $forPtr;
}
$keepLookingFromPtr = $forPtr;
} else {
$keepLookingFromPtr = false;
}
}
return false;
}//end _isInForLoop()
/**
* Returns the position of closest previous T_FOR, if token associated with
* $stackPtr in $phpcsFile is in the head of a for loop, otherwise false.
* The head is the code placed between parenthesis next to the key word
* 'for' : for (<loop_head>) {<loop_body>}.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int|bool Position of T_FOR if token associated with $stackPtr in
* $phpcsFile is in the head of a for loop, otherwise false.
*/
private static function _isInForLoopHead(File $phpcsFile, $stackPtr)
{
$isInForLoop = false;
$tokens = $phpcsFile->getTokens();
$currentTk = $tokens[$stackPtr];
if (array_key_exists('nested_parenthesis', $currentTk)) {
$nestedParenthesis = $currentTk['nested_parenthesis'];
foreach ( $nestedParenthesis as $openParPtr => $closeParPtr) {
$nonWhitspacePtr = $phpcsFile->findPrevious(
array(T_WHITESPACE),
$openParPtr - 1,
null,
true,
null,
true
);
if (false !== $nonWhitspacePtr) {
$isFor = T_FOR === $tokens[$nonWhitspacePtr]['code'];
if ($isFor) {
$isInForLoop = $nonWhitspacePtr;
break;
}
}
}
}
return $isInForLoop;
}//end _isInForLoopHead()
/**
* Returns the position of closest previous T_FOR, if token associated with
* $stackPtr in $phpcsFile is in the body of a for loop, otherwise false.
* The body are the instructions placed after parenthesis of a 'for'
* declaration, enclosed with curly brackets usually.
* 'for' : for (<loop_head>) {<loop_body>}.
*
* @param File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return int|bool Position of T_FOR if token associated with $stackPtr in
* $phpcsFile is in the body of a for loop, otherwise false.
*/
private static function _isInForLoopBody(File $phpcsFile, $stackPtr)
{
$isInForLoop = false;
$tokens = $phpcsFile->getTokens();
// get englobing hierarchy
$parentPtrAndCode = $tokens[$stackPtr]['conditions'];
krsort($parentPtrAndCode);
// looks for a for loop having a body not enclosed with curly brackets,
// which involves that its body contains only one instruction.
if (is_array($parentPtrAndCode) && ! empty($parentPtrAndCode)) {
$parentCode = reset($parentPtrAndCode);
$parentPtr = key($parentPtrAndCode);
$openBracketPtr = $tokens[$parentPtr]['scope_opener'];
} else {
$parentCode = 0;
$parentPtr = 0;
$openBracketPtr = 0;
}
$openResearchScopePtr = $stackPtr;
// recursive search, since a for statement may englobe other inline
// control statement or may be near to function calls, etc...
while (false !== $openResearchScopePtr) {
$closeParPtr = $phpcsFile->findPrevious(
array(T_CLOSE_PARENTHESIS),
$openResearchScopePtr,
null,
false,
null,
true
);
// is there a closing parenthesis with a control statement before
// the previous instruction ?
if (false !== $closeParPtr) {
// is there no opening curly bracket specific to
// set of instructions, between the closing parenthesis
// and the current token ?
if ($openBracketPtr < $closeParPtr) {
// starts the search from the token before the closing
// parenthesis, if it isn't a for statement
$openResearchScopePtr = $closeParPtr - 1;
// is this parenthesis associated with a for statement ?
$closeParenthesisTk = $tokens[$closeParPtr];
if (array_key_exists('parenthesis_owner', $closeParenthesisTk)) {
$mayBeForPtr = $closeParenthesisTk['parenthesis_owner'];
$mayBeForTk = $tokens[$mayBeForPtr];
if (T_FOR === $mayBeForTk['code']) {
return $mayBeForPtr;
}
}
} else {
// if it is about a for loop, don't go further
// and detect it after one more loop execution, do it now
if (T_FOR === $parentCode) {
return $parentPtr;
}
// starts the search from the token before the one
// englobing the current statement
$openResearchScopePtr = $parentPtr - 1;
// re-initialize variables about the englobing structure
if (is_array($parentPtrAndCode)) {
$parentCode = next($parentPtrAndCode);
$parentPtr = key($parentPtrAndCode);
$openBracketPtr = $tokens[$parentPtr]['scope_opener'];
}
}
} else {
$openResearchScopePtr = false;
}
}
// looks for a for loop having a body enclosed with curly brackets
foreach ($parentPtrAndCode as $parentPtr => $parentCode) {
if (T_FOR === $parentCode) {
return $parentPtr;
}
}
return false;
}//end _isInForLoopBody()
/**
* Returns true if a variable declared in the head of the for loop pointed
* by $forPtr in file $phpcsFile has the name $varName.
*
* @param File $phpcsFile The current file being processed.
* @param int $forPtr The position of the 'for' token
* in the stack passed in $tokens.
* @param string $varName The name of the variable to
* procced without $, { nor }.
*
* @return int|bool true if a variable declared in the head of the for loop
* pointed by $forPtr in file $phpcsFile has the name $varName.
*/
private static function _isDeclaredInForLoop(File $phpcsFile, $forPtr, $varName)
{
$isDeclaredInFor = false;
$tokens = $phpcsFile->getTokens();
$forVarPtrs = self::_getVarDeclaredInFor($phpcsFile, $forPtr);
foreach ($forVarPtrs as $forVarPtr) {
$forVarTk = $tokens[$forVarPtr];
// get variable name
$matches = array();
preg_match('/^\$\{?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}?/', $forVarTk['content'], $matches);
$forVarName = $matches[1];
if (0 === strcmp($forVarName, $varName)) {
$isDeclaredInFor = $forVarPtr;
break;
}
}
return $isDeclaredInFor;
}//end _isDeclaredInForLoop()
/**
* Returns list of pointers to variables declared in for loop associated to
* $forPtr in file $phpcsFile.
*
* All pointers in the result list are pointing to token with code
* T_VARIABLE. An exception is raised, if $forPtr doesn't point a token with
* code T_FOR.
*
* @param File $phpcsFile The current file being processed.
* @param int $forPtr The position of the current token
* in the stack passed in $tokens.
*
* @return array List of pointers to variables declared in for loop $forPtr.
*/
private static function _getVarDeclaredInFor(File $phpcsFile, $forPtr)
{
$tokens = $phpcsFile->getTokens();
$forTk = $tokens[$forPtr];
if (T_FOR !== $forTk['code']) {
throw new PHP_CodeSniffer_Exception('$forPtr must be of type T_FOR');
}
$openParPtr = $forTk['parenthesis_opener'];
$openParenthesisTk = $tokens[$openParPtr];
$endOfDeclPtr = $phpcsFile->findNext(array(T_SEMICOLON), $openParPtr);
$forVarPtrs = array();
$varPtr = $phpcsFile->findNext(
array(T_VARIABLE),
$openParPtr + 1,
$endOfDeclPtr
);
while (false !== $varPtr) {
$forVarPtrs [] = $varPtr;
$varPtr = $phpcsFile->findNext(
array(T_VARIABLE),
$varPtr + 1,
$endOfDeclPtr
);
}
return $forVarPtrs;
}//end _getVarDeclaredInFor()
/**
* Returns the position of first occurrence of a PHP variable starting with
* $ in $haystack from $offset.
*
* @param string $haystack The string to search in.
* @param int $offset The optional offset parameter allows you to
* specify which character in haystack to start
* searching. The returned position is still
* relative to the beginning of haystack.
*
* @return mixed The position as an integer
* or the boolean false, if no variable is found.
*/
private static function _getVariablePosition($haystack, $offset = 0)
{
$var_starts_at = strpos($haystack, '$', $offset);
$is_a_var = false;
while (false !== $var_starts_at && ! $is_a_var) {
// makes sure that $ is used for a variable and not as a symbol,
// if $ is protected with the escape char, then it is a symbol.
if (0 !== strcmp($haystack[$var_starts_at - 1], '\\')) {
if (0 === strcmp($haystack[$var_starts_at + 1], '{')) {
// there is an opening brace in the right place
// so it looks for the closing brace in the right place
$hsChunk2 = substr($haystack, $var_starts_at + 2);
if (1 === preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*\}/', $hsChunk2)) {
$is_a_var = true;
}
} else {
$hsChunk1 = substr($haystack, $var_starts_at + 1);
if (1 === preg_match('/^[a-zA-Z_\x7f-\xff]/', $hsChunk1)) {
// $ is used for a variable and not as a symbol,
// since what follows $ matchs the definition of
// a variable label for PHP.
$is_a_var = true;
}
}
}
// update $var_starts_at for the next variable
// only if no variable was found, since it is returned otherwise.
if ( ! $is_a_var) {
$var_starts_at = strpos($haystack, '$', $var_starts_at + 1);
}
}
if ($is_a_var) {
return $var_starts_at;
} else {
return false;
}
}//end _getVariablePosition()
}//end class
?>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ruleset name="CodeIgniter">
<description>CodeIgniter coding standard as described at http://codeigniter.com/user_guide/general/styleguide.html.</description>
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n"/>
</properties>
</rule>
<!-- PHP files should OMIT the closing PHP tag -->
<rule ref="Zend.Files.ClosingTag"/>
<!-- Always use full PHP opening tags -->
<rule ref="Generic.PHP.DisallowShortOpenTag"/>
<!-- Constants should always be fully uppercase -->
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
<!-- TRUE, FALSE, and NULL keywords should always be fully uppercase -->
<rule ref="Generic.PHP.UpperCaseConstant"/>
<!-- One statement per line -->
<rule ref="Generic.Formatting.DisallowMultipleStatements"/>
<!-- Classes and functions should be commented -->
<rule ref="PEAR.Commenting.ClassComment"/>
<rule ref="PEAR.Commenting.FunctionComment"/>
<rule ref="Squiz.Commenting.FunctionCommentThrowTag"/>
<!-- Use warnings for docblock comments for files and variables, since nothing is cleary explained -->
<rule ref="PEAR.Commenting.FileComment">
<properties>
<property name="error" value="false"/>
</properties>
</rule>
<rule ref="Squiz.Commenting.VariableComment">
<properties>
<property name="error" value="false"/>
</properties>
</rule>
<!-- Use Allman style indenting. With the exception of Class declarations,
braces are always placed on a line by themselves, and indented at the same level as the control statement that "owns" them. -->
<rule ref="Generic.Functions.OpeningFunctionBraceBsdAllman"/>
<rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>
<rule ref="Generic.Functions.FunctionCallArgumentSpacing"/>
<!-- @todo Please see PHP_CodeSniffer_Standards_CodeIgniter_CodeIgniterCodingStandard for more details about what there is to do -->
</ruleset>

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