├── .dockerignore ├── .gitignore ├── .gitremotes ├── .htaccess ├── CHANGELOG.md ├── CHANGELOG.seblucas.md ├── DESIGN.md ├── LICENSE ├── README.md ├── SECURITY.md ├── TRANSLATIONS.md ├── build.xml ├── checkconfig.php ├── composer.json ├── composer.lock ├── config ├── .gitignore ├── config.php ├── default.php ├── loader.php ├── local.php.example ├── test.php └── tooltips.php ├── docker-compose-dev.yaml ├── docker-compose-lsio.yaml ├── docker-compose.yaml ├── docker ├── Dockerfile.alpine ├── Dockerfile.linuxserver └── Dockerfile.webdevops ├── favicon.ico ├── images ├── allbook.png ├── author.png ├── bookcover.png ├── custom.png ├── icons │ ├── icon114.png │ ├── icon144.png │ ├── icon57.png │ └── icon72.png ├── language.png ├── publisher.png ├── rating.png ├── recent.png ├── serie.png └── tag.png ├── index.php ├── lang ├── Localization_af.json ├── Localization_bg.json ├── Localization_ca.json ├── Localization_cs.json ├── Localization_da.json ├── Localization_de.json ├── Localization_el.json ├── Localization_en.json ├── Localization_es.json ├── Localization_eu.json ├── Localization_fr.json ├── Localization_gl.json ├── Localization_hr.json ├── Localization_ht.json ├── Localization_hu.json ├── Localization_id.json ├── Localization_it.json ├── Localization_ja.json ├── Localization_ko.json ├── Localization_nb.json ├── Localization_nl.json ├── Localization_pl.json ├── Localization_pt_BR.json ├── Localization_pt_PT.json ├── Localization_ro.json ├── Localization_ru.json ├── Localization_sl.json ├── Localization_sr.json ├── Localization_sv.json ├── Localization_tr.json ├── Localization_uk.json ├── Localization_zh_CN.json └── Localization_zh_TW.json ├── login.html ├── package.json ├── phpstan-baseline.neon ├── phpstan.neon.dist ├── phpunit.xml.dist ├── resources ├── cache.graphql.php ├── dot-php │ ├── .hgtags │ ├── LICENSE │ ├── README.md │ ├── composer.json │ └── doT.php ├── js-cookie │ ├── README.md │ └── js.cookie.js ├── monocle │ ├── README.md │ ├── scripts │ │ ├── monocore.js │ │ └── monoctrl.js │ └── styles │ │ ├── monocore.css │ │ └── monoctrl.css ├── openapi.json ├── schema.graphql └── simonpioli │ └── sortelements │ ├── README.md │ └── jquery.sortElements.js ├── router.php ├── src ├── .gitignore ├── Calibre │ ├── Annotation.php │ ├── Author.php │ ├── Base.php │ ├── BaseList.php │ ├── Book.php │ ├── BookList.php │ ├── Category.php │ ├── Cover.php │ ├── CustomColumn.php │ ├── CustomColumnType.php │ ├── CustomColumnTypeBool.php │ ├── CustomColumnTypeComment.php │ ├── CustomColumnTypeDate.php │ ├── CustomColumnTypeEnumeration.php │ ├── CustomColumnTypeFloat.php │ ├── CustomColumnTypeInteger.php │ ├── CustomColumnTypeRating.php │ ├── CustomColumnTypeSeries.php │ ├── CustomColumnTypeText.php │ ├── Data.php │ ├── Database.php │ ├── Filter.php │ ├── Format.php │ ├── Identifier.php │ ├── Language.php │ ├── Metadata.php │ ├── Note.php │ ├── Preference.php │ ├── Publisher.php │ ├── Rating.php │ ├── Resource.php │ ├── Serie.php │ ├── Tag.php │ ├── User.php │ └── VirtualLibrary.php ├── Framework │ ├── Adapter │ │ ├── AdapterInterface.php │ │ └── CustomAdapter.php │ ├── Framework.php │ └── FrameworkTodo.php ├── Handlers │ ├── AdminHandler.php │ ├── BaseHandler.php │ ├── CalResHandler.php │ ├── CheckHandler.php │ ├── CorsHandler.php │ ├── EpubFsHandler.php │ ├── ErrorHandler.php │ ├── FeedHandler.php │ ├── FetchHandler.php │ ├── GraphQLHandler.php │ ├── HandlerManager.php │ ├── HasRouteTrait.php │ ├── HtmlHandler.php │ ├── JsonHandler.php │ ├── LoaderHandler.php │ ├── MailHandler.php │ ├── OpdsHandler.php │ ├── PageHandler.php │ ├── QueueBasedHandler.php │ ├── ReadHandler.php │ ├── RestApiHandler.php │ ├── TableHandler.php │ ├── TestHandler.php │ ├── ZipFsHandler.php │ └── ZipperHandler.php ├── Input │ ├── Config.php │ ├── HasContextInterface.php │ ├── HasContextTrait.php │ ├── ProxyRequest.php │ ├── Request.php │ ├── RequestContext.php │ ├── Route.php │ └── Session.php ├── Language │ ├── Normalizer.php │ ├── Slugger.php │ └── Translation.php ├── Middleware │ ├── BaseMiddleware.php │ └── TestMiddleware.php ├── Model │ ├── Entry.php │ ├── EntryBook.php │ ├── Link.php │ ├── LinkAcquisition.php │ ├── LinkFacet.php │ ├── LinkFeed.php │ ├── LinkImage.php │ ├── LinkNavigation.php │ └── LinkResource.php ├── Output │ ├── BaseRenderer.php │ ├── DotPHPTemplate.php │ ├── EPubReader.php │ ├── FileResponse.php │ ├── Format.php │ ├── GraphQLExecutor.php │ ├── HtmlRenderer.php │ ├── JsonRenderer.php │ ├── KiwilanOPDS.php │ ├── Mail.php │ ├── OpdsRenderer.php │ ├── OpenApi.php │ ├── Response.php │ ├── RestApiProvider.php │ ├── TwigTemplate.php │ └── Zipper.php ├── Pages │ ├── Page.php │ ├── PageAbout.php │ ├── PageAllAuthors.php │ ├── PageAllAuthorsLetter.php │ ├── PageAllBooks.php │ ├── PageAllBooksLetter.php │ ├── PageAllBooksYear.php │ ├── PageAllCustoms.php │ ├── PageAllFormats.php │ ├── PageAllIdentifiers.php │ ├── PageAllLanguages.php │ ├── PageAllPublishers.php │ ├── PageAllPublishersLetter.php │ ├── PageAllRating.php │ ├── PageAllSeries.php │ ├── PageAllSeriesLetter.php │ ├── PageAllTags.php │ ├── PageAllTagsLetter.php │ ├── PageAllVirtualLibraries.php │ ├── PageAuthorDetail.php │ ├── PageBookDetail.php │ ├── PageCustomDetail.php │ ├── PageCustomize.php │ ├── PageFilter.php │ ├── PageFormatDetail.php │ ├── PageId.php │ ├── PageIdentifierDetail.php │ ├── PageIndex.php │ ├── PageLanguageDetail.php │ ├── PagePublisherDetail.php │ ├── PageQueryResult.php │ ├── PageQueryScope.php │ ├── PageRatingDetail.php │ ├── PageRecentBooks.php │ ├── PageSerieDetail.php │ ├── PageTagDetail.php │ └── PageWithDetail.php ├── Routing │ ├── FastRouter.php │ ├── RouteLoader.php │ ├── RouterInterface.php │ ├── Routing.php │ ├── UriGenerator.php │ ├── url_cached_routes.php │ ├── url_fastroute_cache.php │ ├── url_generating_routes.php │ ├── url_generating_routes.php.meta │ ├── url_generating_routes.php.meta.json │ ├── url_matching_routes.php │ ├── url_matching_routes.php.meta │ └── url_matching_routes.php.meta.json └── functions.php ├── styles ├── cops-monocle.css └── cops-monocle.js ├── templates ├── about.html ├── admin.html ├── bootstrap │ ├── bookdetail.html │ ├── file.html │ ├── footer.html │ ├── header.html │ ├── main.html │ ├── page.html │ ├── scripts │ │ └── cops.js │ ├── styles │ │ ├── style-base.css │ │ └── style-default.css │ └── suggestion.html ├── bootstrap2 │ ├── bookdetail.html │ ├── file.html │ ├── footer.html │ ├── header.html │ ├── main.html │ ├── page.html │ ├── scripts │ │ └── cops.js │ ├── styles │ │ ├── style-base.css │ │ ├── style-default.css │ │ ├── style-kindle.css │ │ └── typeahead.css │ └── suggestion.html ├── bootstrap5 │ ├── bookdetail.html │ ├── file.html │ ├── footer.html │ ├── header.html │ ├── main.html │ ├── page.html │ ├── scripts │ │ └── cops.js │ ├── styles │ │ ├── bootstrap5.css │ │ ├── style-base.css │ │ ├── style-default.css │ │ └── typeahead.css │ └── suggestion.html ├── checkconfig.html ├── default │ ├── bookdetail.html │ ├── file.html │ ├── footer.html │ ├── header.html │ ├── main.html │ ├── page.html │ ├── styles │ │ ├── fa-solid.min.css │ │ ├── fontawesome.min.css │ │ ├── style-base.css │ │ ├── style-default.css │ │ ├── style-eink.css │ │ ├── style-iphone.css │ │ ├── style-iphone7.css │ │ ├── style-kindle.css │ │ └── typeahead.css │ ├── suggestion.html │ └── webfonts │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.svg │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ └── fa-solid-900.woff2 ├── epubjs-reader.html ├── epubreader.html ├── error.html ├── graphql.html ├── notfound.html ├── restapi.html ├── tables.html └── twigged │ ├── README.md │ ├── about.html │ ├── authordetail.html │ ├── base.html │ ├── bookdetail.html │ ├── booklist.html │ ├── customdetail.html │ ├── customize.html │ ├── extra_info.html │ ├── extra_series.html │ ├── filters.html │ ├── identifierdetail.html │ ├── index.html │ ├── languagedetail.html │ ├── loader │ ├── README.md │ ├── actions.html │ ├── databases.html │ └── index.html │ ├── mainlist.html │ ├── navlist.html │ ├── page.html │ ├── publisherdetail.html │ ├── ratingdetail.html │ ├── recent.html │ ├── scripts │ └── cops.js │ ├── seriedetail.html │ ├── styles │ ├── style-base.css │ ├── style-default.css │ └── typeahead.css │ ├── suggestion.html │ ├── tagdetail.html │ └── var_dump.json.twig ├── tests ├── .gitignore ├── BaseWithCustomColumns │ └── metadata.db ├── BaseWithOneBook │ └── metadata.db ├── BaseWithSomeBooks │ ├── .calnotes │ │ ├── notes.db │ │ └── resources │ │ │ ├── 7c │ │ │ ├── xxh64-7c301792c52eebf7 │ │ │ └── xxh64-7c301792c52eebf7.metadata │ │ │ ├── c0 │ │ │ ├── xxh64-c062fb16e3568fdc │ │ │ └── xxh64-c062fb16e3568fdc.metadata │ │ │ └── d7 │ │ │ ├── xxh64-d7e814ba6a1b41d1 │ │ │ └── xxh64-d7e814ba6a1b41d1.metadata │ ├── Lewis Carroll │ │ └── Alice's Adventures in Wonderland (17) │ │ │ ├── Alice's Adventures in Wonderland - Lewis Carroll.epub │ │ │ ├── cover.jpg │ │ │ ├── data │ │ │ ├── hello.txt │ │ │ └── sub folder │ │ │ │ └── copied file.txt │ │ │ └── metadata.opf │ ├── Sun Wu │ │ └── Sun Zi Bing Fa (19) │ │ │ ├── Sun Zi Bing Fa - Sun Wu.epub │ │ │ ├── cover.jpg │ │ │ └── metadata.opf │ ├── hierarchical_type2.sql │ ├── metadata.db │ └── users.db ├── BookListTest.php ├── BookTest.php ├── BrowserKitTest.php ├── ConfigTest.php ├── EpubFsTest.php ├── EpubReaderTest.php ├── FetchHandlerTest.php ├── FilterTest.php ├── FrameworkTest.php ├── FrameworkTodoTest.php ├── GraphQLHandlerTest.php ├── HierarchyTest.php ├── HtmlRendererTest.php ├── JsonRendererTest.php ├── KiwilanTest.php ├── MailTest.php ├── NoteResourceTest.php ├── OPDSValidator.jar ├── OpdsRendererTest.php ├── PageMultiDatabaseTest.php ├── PageTest.php ├── ResponseTest.php ├── RestApiTest.php ├── RouteTest.php ├── Routing │ ├── FastRouterTest.php │ ├── RoutingTest.php │ └── UriGeneratorTest.php ├── SessionTest.php ├── TypeAheadSearchTest.php ├── UserTest.php ├── WebDriverTest.php ├── WebDriverTestCase.php ├── ZipperTest.php ├── baseTest.php ├── composer.kiwilan.json ├── customColumnsTest.php ├── graphql │ ├── author.query.graphql │ ├── author.query.json │ ├── author.result.json │ ├── authors.query.graphql │ ├── authors.query.json │ ├── authors.result.json │ ├── book.query.graphql │ ├── book.query.json │ ├── book.result.json │ ├── books.query.graphql │ ├── books.query.json │ ├── books.result.json │ ├── customColumn.query.graphql │ ├── customColumn.query.json │ ├── customColumn.result.json │ ├── customColumns.query.graphql │ ├── customColumns.query.json │ ├── customColumns.result.json │ ├── data.query.graphql │ ├── data.query.json │ ├── data.result.json │ ├── datas.query.graphql │ ├── datas.query.json │ ├── datas.result.json │ ├── format.query.graphql │ ├── format.query.json │ ├── format.result.json │ ├── formats.query.graphql │ ├── formats.query.json │ ├── formats.result.json │ ├── getAuthor.3.result.json │ ├── getAuthors.l.2.result.json │ ├── getNode.authors.3.result.json │ ├── getNode.books.17.result.json │ ├── getNode.datas.20.result.json │ ├── getNode.oops.42.result.json │ ├── identifier.query.graphql │ ├── identifier.query.json │ ├── identifier.result.json │ ├── identifiers.query.graphql │ ├── identifiers.query.json │ ├── identifiers.result.json │ ├── language.query.graphql │ ├── language.query.json │ ├── language.result.json │ ├── languages.query.graphql │ ├── languages.query.json │ ├── languages.result.json │ ├── node.query.graphql │ ├── node.query.json │ ├── node.result.json │ ├── nodelist.query.graphql │ ├── nodelist.query.json │ ├── nodelist.result.json │ ├── preference.query.graphql │ ├── preference.query.json │ ├── preference.result.json │ ├── preferences.query.graphql │ ├── preferences.query.json │ ├── preferences.result.json │ ├── publisher.query.graphql │ ├── publisher.query.json │ ├── publisher.result.json │ ├── publishers.query.graphql │ ├── publishers.query.json │ ├── publishers.result.json │ ├── rating.query.graphql │ ├── rating.query.json │ ├── rating.result.json │ ├── ratings.query.graphql │ ├── ratings.query.json │ ├── ratings.result.json │ ├── search.query.graphql │ ├── search.query.json │ ├── search.result.json │ ├── serie.query.graphql │ ├── serie.query.json │ ├── serie.result.json │ ├── series.query.graphql │ ├── series.query.json │ ├── series.result.json │ ├── tag.query.graphql │ ├── tag.query.json │ ├── tag.result.json │ ├── tags.query.graphql │ ├── tags.query.json │ ├── tags.result.json │ └── test.query.graphql ├── jing.jar ├── opds-relax-ng │ ├── atom.rng │ ├── opds_catalog_1_1.rng │ ├── opds_catalog_1_2.rng │ └── opensearchdescription.rng ├── res │ ├── atom.rnc │ ├── opds_v1.0.rnc │ ├── opds_v1.1.rnc │ └── opds_v1.2.rnc ├── restapi │ ├── check-more.html │ ├── check.html │ ├── page-about.json │ ├── page-author-id.json │ ├── page-author.json │ ├── page-authors-letter.json │ ├── page-authors-letters.json │ ├── page-authors.json │ ├── page-book-id.json │ ├── page-book.json │ ├── page-books-letter.json │ ├── page-books-letters.json │ ├── page-books-year.json │ ├── page-books-years.json │ ├── page-books.json │ ├── page-custom.json │ ├── page-customize.json │ ├── page-customtype.json │ ├── page-filter.json │ ├── page-format.json │ ├── page-formats.json │ ├── page-identifier-id.json │ ├── page-identifier.json │ ├── page-identifiers.json │ ├── page-index.json │ ├── page-language-id.json │ ├── page-language.json │ ├── page-languages.json │ ├── page-libraries.json │ ├── page-publisher-id.json │ ├── page-publisher.json │ ├── page-publishers-letter.json │ ├── page-publishers-letters.json │ ├── page-publishers.json │ ├── page-query-scope.json │ ├── page-query.json │ ├── page-rating-id.json │ ├── page-rating.json │ ├── page-ratings.json │ ├── page-recent.json │ ├── page-search.json │ ├── page-serie-id.json │ ├── page-serie.json │ ├── page-series-letter.json │ ├── page-series-letters.json │ ├── page-series.json │ ├── page-tag-id.json │ ├── page-tag.json │ ├── page-tags-letter.json │ ├── page-tags-letters.json │ ├── page-tags.json │ ├── page-typeahead.json │ ├── restapi-annotation.json │ ├── restapi-annotations-book.json │ ├── restapi-annotations.json │ ├── restapi-customtypes.json │ ├── restapi-database-table.json │ ├── restapi-database.json │ ├── restapi-databases.json │ ├── restapi-handler.json │ ├── restapi-metadata-element-name.json │ ├── restapi-metadata-element.json │ ├── restapi-metadata.json │ ├── restapi-note.json │ ├── restapi-notes-type-id.json │ ├── restapi-notes-type.json │ ├── restapi-notes.json │ ├── restapi-openapi.json │ ├── restapi-path.html │ ├── restapi-path.json │ ├── restapi-preference.json │ ├── restapi-preferences.json │ ├── restapi-route.json │ ├── restapi-user-details.json │ └── restapi-user.json └── schema │ ├── opds │ ├── feed-metadata.schema.json │ ├── feed.schema.json │ ├── properties.schema.json │ └── publication.schema.json │ └── readium │ ├── contributor-object.schema.json │ ├── contributor.schema.json │ ├── extensions │ ├── epub │ │ ├── metadata.schema.json │ │ └── properties.schema.json │ └── presentation │ │ ├── metadata.schema.json │ │ └── properties.schema.json │ ├── language-map.schema.json │ ├── link.schema.json │ ├── metadata.schema.json │ ├── subcollection.schema.json │ ├── subject-object.schema.json │ └── subject.schema.json ├── tools ├── multi-platform.sh └── selenium.sh ├── util.js ├── web.config └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | *.zip 3 | config/local.php 4 | tests/ 5 | # keep the test database in the image here 6 | !tests/BaseWithSomeBooks/ 7 | tests/BaseWithSomeBooks/.calnotes/backup/ 8 | tests/BaseWithSomeBooks/.calnotes/retired/ 9 | tests/BaseWithSomeBooks/.caltrash/ 10 | tests/BaseWithSomeBooks/metadata_db_prefs_backup.json 11 | vendor/ 12 | # remove docker files 13 | docker/ 14 | docker-compose*.yaml 15 | # remove development files 16 | .vscode/ 17 | .sonarcloud.properties 18 | .sonarlint/ 19 | .yarn/ 20 | #!.yarn/releases 21 | #!.yarn/plugins 22 | .pnp.* 23 | # remove unused directories 24 | tools/ 25 | clover.xml 26 | coverage/ 27 | *.cache 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/* 2 | /vendor/ 3 | /clover.xml 4 | /tests/text.* 5 | /*.phar 6 | /cops.zip 7 | /cops-*.zip 8 | /cops.sublime-* 9 | /config_local.php 10 | /config_local.*.php 11 | .yarn/* 12 | #!.yarn/releases 13 | #!.yarn/plugins 14 | .yarnrc.yml 15 | .pnp.* 16 | .editorconfig 17 | .gitattributes 18 | *.cache 19 | .devcontainer 20 | /cache/*.php 21 | rector.php 22 | .vscode/ 23 | .sonarcloud.properties 24 | .sonarlint/ 25 | -------------------------------------------------------------------------------- /.gitremotes: -------------------------------------------------------------------------------- 1 | [remote "upstream"] 2 | url = https://github.com/seblucas/cops.git 3 | fetch = +refs/heads/*:refs/remotes/upstream/* 4 | [remote "tryme"] 5 | url = https://github.com/SenorSmartyPants/cops.git 6 | fetch = +refs/heads/*:refs/remotes/tryme/* 7 | [remote "peltos"] 8 | url = https://github.com/peltos/cops.git 9 | fetch = +refs/heads/*:refs/remotes/peltos/* 10 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 3.5.x | :white_check_mark: | 11 | | 3.4.x | :x: | 12 | | < 3.x | :x: | 13 | 14 | ## Reporting a Vulnerability 15 | 16 | Please report any security vulnerability via https://github.com/mikespub-org/seblucas-cops/security/advisories 17 | -------------------------------------------------------------------------------- /TRANSLATIONS.md: -------------------------------------------------------------------------------- 1 | # The software COPS 2 | COPS aka Calibre OPDS (and HTML) PHP Server. COPS is a light alternative to Calibre content server / Calibre2OPDS 3 | * COPS Github: https://github.com/mikespub-org/seblucas-cops 4 | 5 | ## Translating COPS 6 | Translate COPS to your language using free Gitlocalize platform: 7 | * COPS Gitlocalize: https://gitlocalize.com/repo/9640 8 | 9 | ## Languages and translators 10 | No software to install, just join in Gitlocalize and translate 11 | * You can request to be a translator or proofreader 12 | * New languages can be requested if missing 13 | 14 | ## Help and support 15 | * Translation Wiki page: https://github.com/mikespub-org/seblucas-cops/wiki/Update-translations 16 | * Translation Forum: https://www.mobileread.com/forums/showthread.php?t=255504 17 | 18 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | local.php 2 | local.*.php 3 | -------------------------------------------------------------------------------- /config/local.php.example: -------------------------------------------------------------------------------- 1 | "smtp.free.fr", 13 | "smtp.username" => "", 14 | "smtp.password" => "", 15 | "smtp.secure" => "", 16 | "address.from" => "cops@slucas.fr", 17 | ]; 18 | 19 | // from here on, we assume that all global $config variables have been loaded 20 | Config::load($config); 21 | 22 | // initialize framework with routes etc. for tests 23 | Framework::getInstance()->getContext(); 24 | -------------------------------------------------------------------------------- /docker-compose-dev.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | dev: 3 | image: mikespub/seblucas-cops:dev 4 | build: 5 | context: . 6 | dockerfile: docker/Dockerfile.alpine 7 | target: dev-envs 8 | platforms: 9 | - "linux/amd64" 10 | - "linux/arm64" 11 | #environment: 12 | # - PORT=80 13 | ports: 14 | - '8080:80' 15 | volumes: 16 | - ./config/local.php:/app/config/local.php 17 | - ./src:/app/src 18 | - ./tests:/app/tests 19 | -------------------------------------------------------------------------------- /docker-compose-lsio.yaml: -------------------------------------------------------------------------------- 1 | # Use latest linuxserver/cops image from linuxserver.io: 2 | # docker compose -f docker-compose-lsio.yaml up -d 3 | # See https://github.com/linuxserver/docker-cops for setup and usage 4 | services: 5 | cops: 6 | image: lscr.io/linuxserver/cops:latest 7 | #image: linuxserver/cops 8 | container_name: cops 9 | environment: 10 | - PUID=1000 11 | - PGID=1000 12 | - TZ=Etc/UTC 13 | volumes: 14 | - ./tests/config:/config 15 | - ./tests/BaseWithSomeBooks:/books 16 | ports: 17 | - 8080:80 18 | restart: unless-stopped 19 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | image: mikespub/seblucas-cops:web 4 | build: 5 | context: . 6 | dockerfile: docker/Dockerfile.alpine 7 | target: build 8 | platforms: 9 | - "linux/amd64" 10 | - "linux/arm64" 11 | tags: 12 | - "mikespub/seblucas-cops:latest" 13 | #environment: 14 | # - PORT=80 15 | ports: 16 | - "8080:80" 17 | volumes: 18 | - ./config/local.php:/app/config/local.php 19 | -------------------------------------------------------------------------------- /docker/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM alpine:3.21 AS build 2 | RUN apk add --no-cache --upgrade \ 3 | php83 \ 4 | php83-dom \ 5 | php83-gd \ 6 | php83-intl \ 7 | php83-pdo_sqlite \ 8 | php83-xml \ 9 | composer \ 10 | php83-tokenizer \ 11 | php83-xmlwriter 12 | 13 | WORKDIR /app 14 | COPY . /app 15 | RUN composer install --no-dev -o -d /app 16 | 17 | ENV PORT=80 18 | EXPOSE $PORT 19 | 20 | CMD ["sh", "-c", "php -S 0.0.0.0:$PORT -t /app"] 21 | 22 | FROM build AS dev-envs 23 | RUN composer update -o -d /app 24 | #RUN apk --no-cache add nodejs yarn 25 | #RUN yarn 26 | -------------------------------------------------------------------------------- /docker/Dockerfile.webdevops: -------------------------------------------------------------------------------- 1 | FROM webdevops/php-nginx:8.3-alpine AS build 2 | COPY . /app 3 | RUN composer install --no-dev -o -d /app 4 | 5 | FROM build AS dev-envs 6 | RUN composer update -o -d /app 7 | #RUN apk --no-cache add nodejs yarn 8 | #RUN yarn 9 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/favicon.ico -------------------------------------------------------------------------------- /images/allbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/allbook.png -------------------------------------------------------------------------------- /images/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/author.png -------------------------------------------------------------------------------- /images/bookcover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/bookcover.png -------------------------------------------------------------------------------- /images/custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/custom.png -------------------------------------------------------------------------------- /images/icons/icon114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/icons/icon114.png -------------------------------------------------------------------------------- /images/icons/icon144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/icons/icon144.png -------------------------------------------------------------------------------- /images/icons/icon57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/icons/icon57.png -------------------------------------------------------------------------------- /images/icons/icon72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/icons/icon72.png -------------------------------------------------------------------------------- /images/language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/language.png -------------------------------------------------------------------------------- /images/publisher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/publisher.png -------------------------------------------------------------------------------- /images/rating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/rating.png -------------------------------------------------------------------------------- /images/recent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/recent.png -------------------------------------------------------------------------------- /images/serie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/serie.png -------------------------------------------------------------------------------- /images/tag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/images/tag.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 9 | * @author mikespub 10 | */ 11 | 12 | use SebLucas\Cops\Framework\Framework; 13 | 14 | require_once __DIR__ . '/config/config.php'; // NOSONAR 15 | 16 | Framework::run(); 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seblucas-cops", 3 | "packageManager": "yarn@4.9.1", 4 | "dependencies": { 5 | "@mikespub/epubjs-reader": "^2025.4.8", 6 | "bootstrap": "^3.4.1", 7 | "corejs-typeahead": "^1.3.4", 8 | "datatables.net": "^2.3.1", 9 | "datatables.net-bs5": "^2.3.1", 10 | "dot": "^1.1.3", 11 | "graphiql": "^4.0.0", 12 | "graphql": "^16.11.0", 13 | "jquery": "^3.7.1", 14 | "js-cookie": "^3.0.5", 15 | "lru-fast": "^0.2.2", 16 | "magnific-popup": "^1.2.0", 17 | "normalize.css": "^8.0.1", 18 | "react": "^19.1.0", 19 | "react-dom": "^19.1.0", 20 | "swagger-ui-dist": "^5.21.0", 21 | "twig": "^1.17.1" 22 | }, 23 | "license": "GPL-2.0-or-later" 24 | } 25 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | # baseline re-generated with level 4 to keep some issues visible on higher levels 2 | # at the time this file was updated there are 0 errors reported for levels 5 to 6 3 | includes: 4 | - phpstan-baseline.neon 5 | 6 | parameters: 7 | level: 6 8 | treatPhpDocTypesAsCertain: false 9 | paths: 10 | - . 11 | bootstrapFiles: 12 | - vendor/autoload.php 13 | excludePaths: 14 | - vendor/* 15 | - tests/WebDriverTestCase.php 16 | - tests/WebDriverTest.php 17 | - src/Framework/Adapter/LaravelAdapter.php 18 | - src/Framework/Adapter/SlimAdapter.php 19 | - src/Framework/Adapter/SymfonyAdapter.php 20 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ./tests/ 13 | ./tests/WebDriverTest.php 14 | 15 | 16 | 17 | 18 | ./ 19 | 20 | 21 | ./resources 22 | ./tests 23 | ./vendor 24 | ./config 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/dot-php/.hgtags: -------------------------------------------------------------------------------- 1 | 9a405bbdd1a9eb1ba72eecfc67115d3bd3efdcbb 1.0.0 2 | -------------------------------------------------------------------------------- /resources/dot-php/README.md: -------------------------------------------------------------------------------- 1 | doT-php 2 | ======= 3 | 4 | PHP rendering engine for [doT.js (The fastest + concise javascript template engine for nodejs and browsers)](https://github.com/olado/doT). 5 | 6 | 7 | How to use it 8 | ------------- 9 | 10 | ```php 11 | // Load the library 12 | require_once('resources/doT-php/doT.php'); 13 | 14 | // Load the template 15 | $page = file_get_contents('templates/page.html'); 16 | 17 | // instanciate the object 18 | $template = new doT(); 19 | 20 | // Compile your templace in a PHP function ($dot) 21 | $dot = $template->template($page); 22 | 23 | // the data is simple PHP array 24 | $data = array('title' => 'My custom title'); 25 | 26 | // Write the HTML 27 | echo $dot($data); 28 | ``` 29 | 30 | 31 | Warning 32 | ------- 33 | 34 | It's far from complete. I needed it just to provide a server side rendering engine 35 | for another project ([COPS](https://github.com/seblucas/cops)). 36 | 37 | So the code provided works perfectly for the templates of COPS and was never tested 38 | elsewhere, doT's unit test were also never tested. 39 | 40 | That being said, You can fork, enhance it and send me some pull request, I'll 41 | happily merge them. 42 | -------------------------------------------------------------------------------- /resources/dot-php/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seblucas/dot-php", 3 | "type": "library", 4 | "description": "PHP rendering engine for doT.js (The fastest + concise javascript template engine for nodejs and browsers)", 5 | "keywords": ["doT", "template", "engine", "rendering"], 6 | "homepage": "https://github.com/seblucas/doT-php", 7 | "license": "GPL-2.0-or-later", 8 | "authors": [ 9 | { 10 | "name": "Sébastien Lucas", 11 | "email": "sebastien@slucas.fr", 12 | "homepage": "https://blog.slucas.fr/", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.0" 18 | }, 19 | "autoload": { 20 | "classmap": ["doT.php"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/simonpioli/sortelements/README.md: -------------------------------------------------------------------------------- 1 | sort 2 | --- 3 | 4 | The sort plugin is a very basic low-level element sorting function that allows you to sort DOM elements with a custom comparator (similar to `Array.prototype.sort`). 5 | 6 | Assuming the following markup: 7 | 8 | 13 | 14 | You could sort the items alphabetically like so: 15 | 16 | $('li').sortElements(function(a, b){ 17 | return $(a).text() > $(b).text() ? 1 : -1; 18 | }); 19 | 20 | That would result in: 21 | 22 | 27 | 28 | It also let's you specify what element will be sorted. The current collection's elements will be those referred to as `a` and `b` on each call of the comparator, but you might not want those elements to be the ones to move. E.g. you might want it to be a parent. For example, when sorting a table column, you would sort by the `` elements, but the elements you actually want to move within the DOM are the `` (each ``'s parent): 29 | 30 | $('td').sortElements(myComparator, function(){ 31 | // Return a reference to the desired element: 32 | return this.parentNode; 33 | }); 34 | 35 | See more info here: [https://j11y.io/javascript/sorting-elements-with-jquery/](https://j11y.io/javascript/sorting-elements-with-jquery/). -------------------------------------------------------------------------------- /router.php: -------------------------------------------------------------------------------- 1 | 11 | * @author mikespub 12 | */ 13 | 14 | if (php_sapi_name() !== 'cli-server') { 15 | echo 'This router is for the php development server only'; 16 | return; 17 | } 18 | 19 | // check if the requested path actually exists 20 | $path = parse_url((string) $_SERVER['REQUEST_URI'], PHP_URL_PATH); 21 | // parse_url() does not decode URL-encoded characters in the path 22 | $path = rawurldecode((string) $path); 23 | if (!empty($path) && file_exists(__DIR__ . $path) && !is_dir(__DIR__ . $path)) { 24 | return false; 25 | } 26 | // route to the right PHP endpoint if needed 27 | $script = rawurldecode((string) $_SERVER['SCRIPT_NAME']); 28 | if (str_contains($path, $script . '/') && file_exists(__DIR__ . $script)) { 29 | return false; 30 | } 31 | 32 | // set environment vars for the front controller 33 | $_SERVER['SCRIPT_NAME'] = '/index.php'; 34 | // parse_url() does not decode URL-encoded characters in the path 35 | $_SERVER['PATH_INFO'] ??= parse_url((string) $_SERVER['REQUEST_URI'], PHP_URL_PATH); 36 | 37 | // use index.php as front controller 38 | include __DIR__ . '/index.php'; // NOSONAR 39 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore adapters for other frameworks 2 | Framework/Adapter/LaravelAdapter.php 3 | Framework/Adapter/SlimAdapter.php 4 | Framework/Adapter/SymfonyAdapter.php 5 | -------------------------------------------------------------------------------- /src/Calibre/Metadata.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Calibre; 12 | 13 | use SebLucas\EPubMeta\Metadata as EPubMetadata; 14 | 15 | /** 16 | * Calibre metadata.opf files are based on EPUB 2.0 , 17 | * not EPUB 3.x 18 | */ 19 | class Metadata extends EPubMetadata 20 | { 21 | public const ROUTE_DETAIL = "restapi-metadata"; 22 | public const ROUTE_ELEMENT = "restapi-metadata-element"; 23 | public const ROUTE_ELEMENT_NAME = "restapi-metadata-element-name"; 24 | 25 | /** 26 | * Summary of getInstanceByBookId 27 | * @param int $bookId 28 | * @param ?int $database 29 | * @return Metadata|false 30 | */ 31 | public static function getInstanceByBookId($bookId, $database = null) 32 | { 33 | $book = Book::getBookById($bookId, $database); 34 | if (empty($book)) { 35 | return false; 36 | } 37 | $file = realpath($book->path . '/metadata.opf'); 38 | return self::fromFile($file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Framework/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Calibre\Resource; 14 | use SebLucas\Cops\Output\FileResponse; 15 | use SebLucas\Cops\Output\Response; 16 | 17 | /** 18 | * Handle calres:// resources for Calibre notes 19 | * URL format: index.php/calres/{db}/{alg}/{digest} with {hash} = {alg}:{digest} 20 | */ 21 | class CalResHandler extends BaseHandler 22 | { 23 | public const HANDLER = "calres"; 24 | public const PREFIX = "/calres"; 25 | public const PARAMLIST = ["db", "alg", "digest"]; 26 | 27 | public static function getRoutes() 28 | { 29 | return [ 30 | "calres" => ["/calres/{db:\d+}/{alg}/{digest}"], 31 | ]; 32 | } 33 | 34 | public function handle($request) 35 | { 36 | $database = $request->getId('db'); 37 | $alg = $request->get('alg'); 38 | $digest = $request->get('digest'); 39 | 40 | $hash = $alg . ':' . $digest; 41 | 42 | // create empty file response to start with!? 43 | $response = new FileResponse(); 44 | 45 | $result = Resource::sendImageResource($hash, $response, null, intval($database)); 46 | if (is_null($result)) { 47 | return Response::notFound($request); 48 | } 49 | if ($result->isNotModified($request)) { 50 | return $result->setNotModified(); 51 | } 52 | return $result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Handlers/CorsHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | /** 14 | * Summary of CorsHandler 15 | */ 16 | class CorsHandler extends BaseHandler 17 | { 18 | public const HANDLER = "cors"; 19 | public const PREFIX = ""; 20 | public const PARAMLIST = []; 21 | 22 | public static function getRoutes() 23 | { 24 | return [ 25 | // @todo add cors options after the last handler or use middleware or... 26 | //'cors' => ['/{path:.*}', ['_handler' => 'TODO'], ['OPTIONS']], 27 | ]; 28 | } 29 | 30 | public function handle($request) 31 | { 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Handlers/EpubFsHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Handlers\TestHandler; 14 | use SebLucas\Cops\Output\EPubReader; 15 | use SebLucas\Cops\Output\Response; 16 | use Exception; 17 | 18 | /** 19 | * Handle Epub filesystem for monocle epub reader 20 | * URL format: index.php/epubfs?data={idData}&comp={component} 21 | */ 22 | class EpubFsHandler extends BaseHandler 23 | { 24 | public const HANDLER = "epubfs"; 25 | public const PREFIX = "/epubfs"; 26 | public const PARAMLIST = ["db", "data", "comp"]; 27 | 28 | public static function getRoutes() 29 | { 30 | // support custom pattern for route placeholders - see nikic/fast-route 31 | return [ 32 | "epubfs" => ["/epubfs/{db:\d+}/{data:\d+}/{comp:.+}"], 33 | ]; 34 | } 35 | 36 | public function handle($request) 37 | { 38 | if (php_sapi_name() === 'cli' && $request->getHandler() !== TestHandler::class) { 39 | return; 40 | } 41 | 42 | //$database = $request->getId('db'); 43 | $idData = $request->getId('data'); 44 | if (empty($idData)) { 45 | return Response::notFound($request); 46 | } 47 | $component = $request->get('comp', null); 48 | if (empty($component)) { 49 | return Response::notFound($request); 50 | } 51 | $database = $request->database(); 52 | 53 | // create empty response to start with!? 54 | $response = new Response(); 55 | 56 | $reader = new EPubReader($request, $response); 57 | 58 | try { 59 | return $reader->sendContent($idData, $component, $database); 60 | 61 | } catch (Exception $e) { 62 | error_log($e); 63 | return Response::sendError($request, $e->getMessage()); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Handlers/ErrorHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Output\Response; 14 | 15 | /** 16 | * Summary of ErrorHandler 17 | */ 18 | class ErrorHandler extends BaseHandler 19 | { 20 | public const HANDLER = "error"; 21 | 22 | public static function getRoutes() 23 | { 24 | return []; 25 | } 26 | 27 | public function handle($request) 28 | { 29 | if ($request->getHandler() == HtmlHandler::class && $request->path() != '' && !$request->isJson()) { 30 | $error = "Invalid request path '" . $request->path() . "'"; 31 | $ref = $request->server('HTTP_REFERER'); 32 | if ($ref) { 33 | $error .= ' from ' . $ref; 34 | } 35 | return Response::sendError($request, $error, ["db" => $request->database()]); 36 | } 37 | if ($request->getHandler() == TestHandler::class) { 38 | return new Response(); 39 | } 40 | return Response::notFound($request); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Handlers/JsonHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Output\JsonRenderer; 14 | use SebLucas\Cops\Output\Response; 15 | use SebLucas\Cops\Pages\PageId; 16 | use InvalidArgumentException; 17 | use Throwable; 18 | 19 | /** 20 | * Handle JSON ajax requests 21 | * URL format: index.php?page={page}&... 22 | * with Accept: application/json or X-Requested-With: XMLHttpRequest 23 | */ 24 | class JsonHandler extends PageHandler 25 | { 26 | public const HANDLER = "json"; 27 | 28 | public static function getRoutes() 29 | { 30 | // same routes as HtmlHandler - see util.js 31 | //return parent::getRoutes(); 32 | return []; 33 | } 34 | 35 | public function handle($request) 36 | { 37 | $page = $request->get('page'); 38 | if (in_array($page, [PageId::CUSTOMIZE, PageId::FILTER])) { 39 | $session = $this->getContext()->getSession(); 40 | $session->start(); 41 | $request->setSession($session); 42 | } 43 | $response = new Response(Response::MIME_TYPE_JSON); 44 | 45 | $json = new JsonRenderer($request, $response); 46 | 47 | try { 48 | return $response->setContent(json_encode($json->getJson($request))); 49 | } catch (InvalidArgumentException $e) { 50 | return Response::notFound($request, $e->getMessage()); 51 | } catch (Throwable $e) { 52 | error_log($e); 53 | return Response::sendError($request, $e->getMessage()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Handlers/MailHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Handlers\TestHandler; 14 | use SebLucas\Cops\Output\Mail; 15 | use SebLucas\Cops\Output\Response; 16 | 17 | /** 18 | * Send books by email 19 | * URL format: index.php/mail (POST data and email) 20 | */ 21 | class MailHandler extends BaseHandler 22 | { 23 | public const HANDLER = "mail"; 24 | public const PREFIX = "/mail"; 25 | 26 | public static function getRoutes() 27 | { 28 | return [ 29 | "mail" => ["/mail", [], ["POST"]], 30 | ]; 31 | } 32 | 33 | public function handle($request) 34 | { 35 | // set request handler to 'TestHandler' class to run preSend() but not actually Send() 36 | $dryRun = ($request->getHandler() === TestHandler::class) ? true : false; 37 | 38 | $mailer = new Mail(); 39 | 40 | if ($error = $mailer->checkConfiguration()) { 41 | return Response::sendError($request, $error); 42 | } 43 | 44 | $idData = (int) $request->post("data"); 45 | $emailDest = $request->post("email"); 46 | if ($error = $mailer->checkRequest($idData, $emailDest)) { 47 | return Response::sendError($request, $error); 48 | } 49 | 50 | if ($error = $mailer->sendMail($idData, $emailDest, $request, $dryRun)) { 51 | $response = new Response('text/plain'); 52 | return $response->setContent(localize("mail.messagenotsent") . $error); 53 | } 54 | 55 | $response = new Response('text/plain'); 56 | return $response->setContent(localize("mail.messagesent")); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Handlers/QueueBasedHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Input\RequestContext; 14 | use SebLucas\Cops\Input\Request; 15 | use SebLucas\Cops\Output\Response; 16 | 17 | /** 18 | * Basic middleware dispatcher (FIFO queue) 19 | * This is mainly useful for request middleware, not response middleware, 20 | * since most COPS request handlers already finish sending the response 21 | * @see https://www.php-fig.org/psr/psr-15/meta/#queue-based-request-handler 22 | */ 23 | class QueueBasedHandler extends BaseHandler 24 | { 25 | /** @var BaseHandler */ 26 | protected $handler; 27 | /** @var array */ 28 | protected $queue = []; 29 | 30 | /** 31 | * Set final request handler 32 | * @param RequestContext $context 33 | * @param BaseHandler $handler 34 | */ 35 | public function __construct($context, $handler) 36 | { 37 | $this->setContext($context); 38 | $this->handler = $handler; 39 | } 40 | 41 | /** 42 | * Add middleware to queue (FIFO) 43 | * @param mixed $middleware 44 | * @return void 45 | */ 46 | public function add($middleware) 47 | { 48 | $this->queue[] = $middleware; 49 | } 50 | 51 | /** 52 | * Process next middleware in queue or call final handler 53 | * @param Request $request 54 | * @return Response 55 | */ 56 | public function handle($request) 57 | { 58 | if (empty($this->queue)) { 59 | // return Response if it's not already sent 60 | return $this->handler->handle($request); 61 | } 62 | // @todo support other __invoke, callable etc. middleware 63 | $middleware = array_shift($this->queue); 64 | return $middleware->process($request, $this); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Handlers/ReadHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Input\Config; 14 | use SebLucas\Cops\Output\EPubReader; 15 | use SebLucas\Cops\Output\Response; 16 | use Exception; 17 | 18 | /** 19 | * Handle epub reader with monocle 20 | * URL format: index.php/read?data={idData}&version={version} 21 | */ 22 | class ReadHandler extends BaseHandler 23 | { 24 | public const HANDLER = "read"; 25 | public const PREFIX = "/read"; 26 | public const PARAMLIST = ["db", "data", "title"]; 27 | 28 | public static function getRoutes() 29 | { 30 | return [ 31 | "read-title" => ["/read/{db:\d+}/{data:\d+}/{title}"], 32 | "read" => ["/read/{db:\d+}/{data:\d+}"], 33 | ]; 34 | } 35 | 36 | public function handle($request) 37 | { 38 | $idData = $request->getId('data'); 39 | if (empty($idData)) { 40 | return Response::notFound($request); 41 | } 42 | $version = $request->get('version', Config::get('epub_reader', 'monocle')); 43 | $database = $request->database(); 44 | 45 | $response = new Response(Response::MIME_TYPE_HTML); 46 | 47 | $reader = new EPubReader($request, $response); 48 | 49 | try { 50 | return $response->setContent($reader->getReader($idData, $version, $database)); 51 | } catch (Exception $e) { 52 | error_log($e); 53 | return Response::sendError($request, $e->getMessage()); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Handlers/TableHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | use SebLucas\Cops\Input\Route; 14 | use SebLucas\Cops\Output\Format; 15 | use SebLucas\Cops\Output\Response; 16 | 17 | /** 18 | * Handle datatables 19 | */ 20 | class TableHandler extends BaseHandler 21 | { 22 | public const HANDLER = "tables"; 23 | public const PREFIX = "/tables"; 24 | public const PARAMLIST = ["db", "name", "id"]; 25 | 26 | public static string $template = "templates/tables.html"; 27 | 28 | public static function getRoutes() 29 | { 30 | return [ 31 | //"tables-db-name-id" => ["/tables/{db:\d+}/{name:\w+}/{id}"], 32 | //"tables-db-name" => ["/tables/{db:\d+}/{name:\w+}"], 33 | //"tables-db" => ["/tables/{db:\d+}"], 34 | "tables" => ["/tables"], 35 | ]; 36 | } 37 | 38 | public function handle($request) 39 | { 40 | $data = ['link' => RestApiHandler::getBaseUrl()]; 41 | $data['thead'] = 'RouteDescription'; 42 | $data['tbody'] = ''; 43 | foreach (Route::getRoutes() as $name => $route) { 44 | $path = reset($route); 45 | if (str_contains($path, '{')) { 46 | continue; 47 | } 48 | $data['tbody'] .= '' . $path . ''; 49 | } 50 | $data['tfoot'] = $data['thead']; 51 | 52 | $response = new Response(Response::MIME_TYPE_HTML); 53 | return $response->setContent(Format::template($data, self::$template)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Handlers/TestHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Handlers; 12 | 13 | /** 14 | * Summary of TestHandler 15 | */ 16 | class TestHandler extends BaseHandler 17 | { 18 | public const HANDLER = "phpunit"; 19 | public const PREFIX = "/test"; 20 | public const PARAMLIST = []; 21 | 22 | public static function getRoutes() 23 | { 24 | return [ 25 | "test" => ["/test"], 26 | //"other" => ["/{path:.*}"], 27 | ]; 28 | } 29 | 30 | public function handle($request) 31 | { 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Input/HasContextInterface.php: -------------------------------------------------------------------------------- 1 | context = $context; 20 | } 21 | 22 | /** 23 | * Summary of getContext 24 | * @return RequestContext 25 | */ 26 | public function getContext() 27 | { 28 | return $this->context; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Language/Slugger.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Language; 12 | 13 | use Symfony\Component\String\Slugger\AsciiSlugger; 14 | 15 | class Slugger extends AsciiSlugger 16 | { 17 | // use slug() 18 | } 19 | -------------------------------------------------------------------------------- /src/Middleware/BaseMiddleware.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Middleware; 12 | 13 | use SebLucas\Cops\Handlers\BaseHandler; 14 | use SebLucas\Cops\Input\HasContextInterface; 15 | use SebLucas\Cops\Input\HasContextTrait; 16 | use SebLucas\Cops\Input\Request; 17 | use SebLucas\Cops\Output\Response; 18 | 19 | /** 20 | * Summary of BaseMiddleware 21 | */ 22 | abstract class BaseMiddleware implements HasContextInterface 23 | { 24 | use HasContextTrait; 25 | 26 | /** 27 | * @param Request $request 28 | * @param BaseHandler $handler 29 | * @return Response|void 30 | */ 31 | abstract public function process($request, $handler); 32 | } 33 | -------------------------------------------------------------------------------- /src/Middleware/TestMiddleware.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Middleware; 12 | 13 | use SebLucas\Cops\Handlers\BaseHandler; 14 | use SebLucas\Cops\Input\Request; 15 | use SebLucas\Cops\Output\Response; 16 | 17 | /** 18 | * Summary of BaseMiddleware 19 | */ 20 | class TestMiddleware extends BaseMiddleware 21 | { 22 | public function __construct() 23 | { 24 | // ... 25 | } 26 | 27 | /** 28 | * @param Request $request 29 | * @param BaseHandler $handler 30 | * @return Response|void 31 | */ 32 | public function process($request, $handler) 33 | { 34 | // do something with $request before $handler 35 | $request->set('hello', 'world'); 36 | $response = $handler->handle($request); 37 | if ($response instanceof Response && !$response->isSent()) { 38 | // do something with $response after $handler 39 | $response->setContent($response->getContent() . "\nGoodbye!"); 40 | } 41 | return $response; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Model/EntryBook.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | use SebLucas\Cops\Calibre\Book; 14 | 15 | class EntryBook extends Entry 16 | { 17 | public Book $book; 18 | 19 | /** 20 | * EntryBook constructor. 21 | * @param string $title 22 | * @param string $id 23 | * @param string $content 24 | * @param string $contentType 25 | * @param array $linkArray 26 | * @param Book $book 27 | */ 28 | public function __construct($title, $id, $content, $contentType, $linkArray, $book) 29 | { 30 | parent::__construct($title, $id, $content, $contentType, $linkArray, $book->getDatabaseId()); 31 | $this->book = $book; 32 | $this->localUpdated = $book->timestamp; 33 | } 34 | 35 | /** 36 | * Summary of hasAcquisitionLink 37 | * @return bool 38 | */ 39 | public function hasAcquisitionLink() 40 | { 41 | foreach ($this->linkArray as $link) { 42 | if ($link instanceof LinkAcquisition) { 43 | return true; 44 | } 45 | } 46 | return false; 47 | } 48 | 49 | /** 50 | * Summary of isValidForOPDS 51 | * @return bool 52 | */ 53 | public function isValidForOPDS() 54 | { 55 | // check that we have at least 1 valid acquisition link for this book - see #28 56 | return $this->hasAcquisitionLink(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Model/Link.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | class Link 14 | { 15 | public string|\Closure $href; 16 | public string $type; 17 | /** @var ?string */ 18 | public $rel; 19 | /** @var ?string */ 20 | public $title; 21 | 22 | /** 23 | * Summary of __construct 24 | * @param string|\Closure $href uri or closure including the endpoint 25 | * @param string $type link type in the OPDS catalog 26 | * @param ?string $rel relation in the OPDS catalog 27 | * @param ?string $title title in the OPDS catalog and elsewhere 28 | */ 29 | public function __construct($href, $type, $rel = null, $title = null) 30 | { 31 | $this->href = $href; 32 | $this->type = $type; 33 | $this->rel = $rel; 34 | $this->title = $title; 35 | } 36 | 37 | /** 38 | * Summary of getUri 39 | * @return string 40 | */ 41 | public function getUri() 42 | { 43 | if ($this->href instanceof \Closure) { 44 | $this->href = ($this->href)(); 45 | } 46 | // Link()->href includes the endpoint here 47 | return $this->href; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Model/LinkAcquisition.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | /** 14 | * From https://drafts.opds.io/opds-2.0#53-acquisition-links 15 | * In OPDS 2.0, the concept of an Acquision Link is not as central as in OPDS 1.x 16 | * since publications can also be accessed through a manifest. 17 | * That said, for publications that are strictly accessible through a download 18 | * or require specific interactions, the concept remains. 19 | */ 20 | class LinkAcquisition extends LinkResource 21 | { 22 | public const OPDS_ACQUISITION_TYPE = "http://opds-spec.org/acquisition"; 23 | } 24 | -------------------------------------------------------------------------------- /src/Model/LinkFacet.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | /** 14 | * From https://specs.opds.io/opds-1.2#4-facets 15 | * An Acquisition Feed MAY offer multiple links to reorder the Publications listed 16 | * in the feed or limit them to a subset. This specification defines one new relation 17 | * to identify such links as Facets: 18 | * http://opds-spec.org/facet: An Acquisition Feed with a subset or an alternate order 19 | * of the Publications listed. 20 | * 21 | * Links using this relation MUST only appear in Acquisition Feeds. 22 | */ 23 | class LinkFacet extends LinkFeed 24 | { 25 | public const LINK_RELATION = "http://opds-spec.org/facet"; 26 | 27 | /** @var ?string */ 28 | public $facetGroup; 29 | public bool $activeFacet; 30 | /** @var ?int */ 31 | public $threadCount; 32 | 33 | /** 34 | * Summary of __construct 35 | * @param string|\Closure $href uri or closure including the endpoint 36 | * @param ?string $title title in the OPDS catalog 37 | * @param ?string $facetGroup facetGroup this facet belongs to 38 | * @param bool $activeFacet is the facet currently active 39 | * @param ?int $threadCount number of items expected 40 | */ 41 | public function __construct($href, $title = null, $facetGroup = null, $activeFacet = false, $threadCount = null) 42 | { 43 | parent::__construct($href, static::LINK_RELATION, $title); 44 | $this->facetGroup = $facetGroup; 45 | $this->activeFacet = $activeFacet; 46 | $this->threadCount = $threadCount; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Model/LinkFeed.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | /** 14 | * From https://specs.opds.io/opds-1.2#23-acquisition-feeds 15 | * An Acquisition Feed is an OPDS Catalog Feed Document that collects OPDS Catalog Entries 16 | * into a single, ordered set. The simplest complete OPDS Catalog would be a single Acquisition 17 | * Feed listing all of the available OPDS Catalog Entries from that provider. In more complex 18 | * OPDS Catalogs, Acquisition Feeds are used to present and organize sets of related OPDS 19 | * Catalog Entries for browsing and discovery by clients and aggregators. 20 | * 21 | * Links to Acquisition Feeds MUST use the "type" attribute 22 | * "application/atom+xml;profile=opds-catalog;kind=acquisition" 23 | */ 24 | class LinkFeed extends Link 25 | { 26 | public const OPDS_NAVIGATION_FEED = "application/atom+xml;profile=opds-catalog;kind=navigation"; 27 | public const OPDS_ACQUISITION_FEED = "application/atom+xml;profile=opds-catalog;kind=acquisition"; 28 | 29 | public const LINK_TYPE = self::OPDS_ACQUISITION_FEED; 30 | 31 | /** 32 | * Summary of __construct 33 | * @param string|\Closure $href uri or closure including the endpoint 34 | * @param ?string $rel relation in the OPDS catalog 35 | * @param ?string $title title in the OPDS catalog and elsewhere 36 | */ 37 | public function __construct($href, $rel = null, $title = null) 38 | { 39 | parent::__construct($href, static::LINK_TYPE, $rel, $title); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Model/LinkNavigation.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Model; 12 | 13 | /** 14 | * From https://specs.opds.io/opds-1.2#22-navigation-feeds 15 | * A Navigation Feed is an OPDS Catalog Feed Document whose Atom Entries serve to create 16 | * a suggested hierarchy for presentation and browsing. A Navigation Feed MUST NOT contain 17 | * OPDS Catalog Entries but instead contains Atom Entries that link to other Navigation or 18 | * Acquisition Feeds or other Resources. 19 | * 20 | * Links to Navigation Feeds MUST use the "type" attribute 21 | * "application/atom+xml;profile=opds-catalog;kind=navigation" 22 | */ 23 | class LinkNavigation extends LinkFeed 24 | { 25 | public const LINK_TYPE = parent::OPDS_NAVIGATION_FEED; 26 | } 27 | -------------------------------------------------------------------------------- /src/Pages/PageAbout.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Input\Config; 14 | 15 | class PageAbout extends Page 16 | { 17 | /** 18 | * Summary of initializeContent 19 | * @return void 20 | */ 21 | public function initializeContent() 22 | { 23 | $this->idPage = PageId::ABOUT_ID; 24 | $this->title = localize("about.title"); 25 | } 26 | 27 | /** 28 | * Summary of getContent 29 | * @return string 30 | */ 31 | public function getContent() 32 | { 33 | return preg_replace("/\About COPS\<\/h1\>/", "

About COPS " . Config::VERSION . "

", file_get_contents('templates/about.html')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Pages/PageAllAuthors.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Author; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | 16 | class PageAllAuthors extends Page 17 | { 18 | protected $className = Author::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->getEntries(); 27 | $this->idPage = Author::PAGE_ID; 28 | $this->title = localize("authors.title"); 29 | } 30 | 31 | /** 32 | * Summary of getEntries 33 | * @return void 34 | */ 35 | public function getEntries() 36 | { 37 | $baselist = new BaseList($this->className, $this->request); 38 | if ($this->request->option("author_split_first_letter") == 1 || $this->request->get('letter')) { 39 | $this->entryArray = $baselist->getCountByFirstLetter(); 40 | $this->sorted = $baselist->orderBy; 41 | } else { 42 | $this->entryArray = $baselist->getRequestEntries($this->n); 43 | $this->totalNumber = $baselist->countRequestEntries(); 44 | $this->sorted = $baselist->orderBy; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Pages/PageAllAuthorsLetter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Author; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllAuthorsLetter extends Page 18 | { 19 | protected $className = Author::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the first letter - override here 28 | $this->idGet = $this->request->get('letter', null, '/^[\p{L}\p{N}]$/u'); 29 | $this->getEntries(); 30 | $this->idPage = Author::getEntryIdByLetter($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByLetter.letter"), str_format(localize("authorword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("authors.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Author::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $baselist = new BaseList($this->className, $this->request); 48 | $this->entryArray = $baselist->getEntriesByFirstLetter($this->idGet, $this->n); 49 | $this->totalNumber = $baselist->countEntriesByFirstLetter($this->idGet); 50 | $this->sorted = $baselist->orderBy; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Pages/PageAllBooks.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Book; 14 | use SebLucas\Cops\Calibre\BookList; 15 | 16 | class PageAllBooks extends Page 17 | { 18 | protected $className = Book::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->getEntries(); 27 | $this->idPage = Book::PAGE_ID; 28 | $this->title = localize("allbooks.title"); 29 | } 30 | 31 | /** 32 | * Summary of getEntries 33 | * @return void 34 | */ 35 | public function getEntries() 36 | { 37 | $booklist = new BookList($this->request); 38 | $idlist = $this->request->get('idlist'); 39 | if (!empty($idlist)) { 40 | // [$this->entryArray, $this->totalNumber] = $booklist->getAllBooks($this->n); 41 | if (!is_array($idlist)) { 42 | $idlist = explode(',', string: $idlist); 43 | } 44 | $idlist = array_map('intval', $idlist); 45 | // sort entryArray by order in idlist here 46 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByIdList($idlist); 47 | $this->sorted = $booklist->orderBy ?? "id"; 48 | } elseif ($this->request->option("titles_split_first_letter") == 1 || $this->request->get('letter')) { 49 | $this->entryArray = $booklist->getCountByFirstLetter(); 50 | $this->sorted = $booklist->orderBy ?? "letter"; 51 | } elseif (!empty($this->request->option("titles_split_publication_year")) || $this->request->get('year')) { 52 | $this->entryArray = $booklist->getCountByPubYear(); 53 | $this->sorted = $booklist->orderBy ?? "year"; 54 | } else { 55 | [$this->entryArray, $this->totalNumber] = $booklist->getAllBooks($this->n); 56 | $this->sorted = $booklist->orderBy ?? Book::SQL_SORT; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Pages/PageAllBooksLetter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Book; 14 | use SebLucas\Cops\Calibre\BookList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllBooksLetter extends Page 18 | { 19 | protected $className = Book::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the first letter - override here 28 | $this->idGet = $this->request->get('letter', null, '/^[\p{L}\p{N}]$/u'); 29 | $this->getEntries(); 30 | $this->idPage = Book::getEntryIdByLetter($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByLetter.letter"), str_format(localize("bookword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("allbooks.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Book::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $booklist = new BookList($this->request); 48 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByFirstLetter($this->idGet, $this->n); 49 | $this->sorted = $booklist->orderBy ?? Book::SQL_SORT; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Pages/PageAllBooksYear.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Book; 14 | use SebLucas\Cops\Calibre\BookList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllBooksYear extends Page 18 | { 19 | protected $className = Book::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the year - override here 28 | $this->idGet = $this->request->getId('year'); 29 | $this->getEntries(); 30 | $this->idPage = Book::getEntryIdByYear($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByYear.year"), str_format(localize("bookword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("allbooks.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Book::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $booklist = new BookList($this->request); 48 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByPubYear($this->idGet, $this->n); 49 | $this->sorted = $booklist->orderBy ?? Book::SQL_SORT; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Pages/PageAllFormats.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BaseList; 14 | use SebLucas\Cops\Calibre\Format; 15 | use SebLucas\Cops\Input\Config; 16 | 17 | class PageAllFormats extends Page 18 | { 19 | protected $className = Format::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | $this->getEntries(); 28 | $this->idPage = Format::PAGE_ID; 29 | $this->title = localize("formats.title"); 30 | } 31 | 32 | /** 33 | * Summary of getEntries 34 | * @return void 35 | */ 36 | public function getEntries() 37 | { 38 | $baselist = new BaseList($this->className, $this->request); 39 | $this->entryArray = $baselist->getRequestEntries($this->n); 40 | $this->totalNumber = $baselist->countDistinctEntries(); 41 | $this->sorted = $baselist->orderBy; 42 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("format", Config::get('show_not_set_filter'))) { 43 | array_push($this->entryArray, $baselist->getWithoutEntry()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Pages/PageAllIdentifiers.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BaseList; 14 | use SebLucas\Cops\Calibre\Identifier; 15 | use SebLucas\Cops\Input\Config; 16 | 17 | class PageAllIdentifiers extends Page 18 | { 19 | protected $className = Identifier::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | $this->getEntries(); 28 | $this->idPage = Identifier::PAGE_ID; 29 | $this->title = localize("identifiers.title"); 30 | } 31 | 32 | /** 33 | * Summary of getEntries 34 | * @return void 35 | */ 36 | public function getEntries() 37 | { 38 | $baselist = new BaseList($this->className, $this->request); 39 | $this->entryArray = $baselist->getRequestEntries($this->n); 40 | $this->totalNumber = $baselist->countDistinctEntries(); 41 | $this->sorted = $baselist->orderBy; 42 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("identifier", Config::get('show_not_set_filter'))) { 43 | array_push($this->entryArray, $baselist->getWithoutEntry()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Pages/PageAllLanguages.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Language; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | 16 | class PageAllLanguages extends Page 17 | { 18 | protected $className = Language::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->getEntries(); 27 | $this->idPage = Language::PAGE_ID; 28 | $this->title = localize("languages.title"); 29 | } 30 | 31 | /** 32 | * Summary of getEntries 33 | * @return void 34 | */ 35 | public function getEntries() 36 | { 37 | $baselist = new BaseList($this->className, $this->request); 38 | $this->entryArray = $baselist->getRequestEntries($this->n); 39 | $this->totalNumber = $baselist->countRequestEntries(); 40 | $this->sorted = $baselist->orderBy; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Pages/PageAllPublishers.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Publisher; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | 16 | class PageAllPublishers extends Page 17 | { 18 | protected $className = Publisher::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->getEntries(); 27 | $this->idPage = Publisher::PAGE_ID; 28 | $this->title = localize("publishers.title"); 29 | } 30 | 31 | /** 32 | * Summary of getEntries 33 | * @return void 34 | */ 35 | public function getEntries() 36 | { 37 | $baselist = new BaseList($this->className, $this->request); 38 | if ($this->request->option("publisher_split_first_letter") == 1 || $this->request->get('letter')) { 39 | $this->entryArray = $baselist->getCountByFirstLetter(); 40 | $this->sorted = $baselist->orderBy; 41 | return; 42 | } 43 | $this->entryArray = $baselist->getRequestEntries($this->n); 44 | $this->totalNumber = $baselist->countRequestEntries(); 45 | $this->sorted = $baselist->orderBy; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Pages/PageAllPublishersLetter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Publisher; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllPublishersLetter extends Page 18 | { 19 | protected $className = Publisher::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the first letter - override here 28 | $this->idGet = $this->request->get('letter', null, '/^[\p{L}\p{N}]$/u'); 29 | $this->getEntries(); 30 | $this->idPage = Publisher::getEntryIdByLetter($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByLetter.letter"), str_format(localize("publisherword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("publishers.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Publisher::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $baselist = new BaseList($this->className, $this->request); 48 | $this->entryArray = $baselist->getEntriesByFirstLetter($this->idGet, $this->n); 49 | $this->totalNumber = $baselist->countEntriesByFirstLetter($this->idGet); 50 | $this->sorted = $baselist->orderBy; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Pages/PageAllRating.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BaseList; 14 | use SebLucas\Cops\Calibre\Rating; 15 | use SebLucas\Cops\Input\Config; 16 | 17 | class PageAllRating extends Page 18 | { 19 | protected $className = Rating::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | $this->getEntries(); 28 | $this->idPage = Rating::PAGE_ID; 29 | $this->title = localize("ratings.title"); 30 | } 31 | 32 | /** 33 | * Summary of getEntries 34 | * @return void 35 | */ 36 | public function getEntries() 37 | { 38 | $baselist = new BaseList($this->className, $this->request); 39 | $this->entryArray = $baselist->getRequestEntries($this->n); 40 | $this->totalNumber = $baselist->countRequestEntries(); 41 | $this->sorted = $baselist->orderBy; 42 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("rating", Config::get('show_not_set_filter'))) { 43 | array_push($this->entryArray, $baselist->getWithoutEntry()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Pages/PageAllSeries.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BaseList; 14 | use SebLucas\Cops\Calibre\Serie; 15 | use SebLucas\Cops\Input\Config; 16 | 17 | class PageAllSeries extends Page 18 | { 19 | protected $className = Serie::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | $this->getEntries(); 28 | $this->idPage = Serie::PAGE_ID; 29 | $this->title = localize("series.title"); 30 | } 31 | 32 | /** 33 | * Summary of getEntries 34 | * @return void 35 | */ 36 | public function getEntries() 37 | { 38 | $baselist = new BaseList($this->className, $this->request); 39 | if ($this->request->option("series_split_first_letter") == 1 || $this->request->get('letter')) { 40 | $this->entryArray = $baselist->getCountByFirstLetter(); 41 | $this->sorted = $baselist->orderBy; 42 | if (in_array("series", Config::get('show_not_set_filter'))) { 43 | array_push($this->entryArray, $baselist->getWithoutEntry()); 44 | } 45 | return; 46 | } 47 | if ($baselist->hasChildCategories()) { 48 | // use tag_browser_series view here, to get the full hierarchy? 49 | $this->entryArray = $baselist->browseAllEntries($this->n, $this->request->get('tree')); 50 | } else { 51 | $this->entryArray = $baselist->getRequestEntries($this->n); 52 | } 53 | $this->totalNumber = $baselist->countRequestEntries(); 54 | $this->sorted = $baselist->orderBy; 55 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("series", Config::get('show_not_set_filter'))) { 56 | array_push($this->entryArray, $baselist->getWithoutEntry()); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Pages/PageAllSeriesLetter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Serie; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllSeriesLetter extends Page 18 | { 19 | protected $className = Serie::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the first letter - override here 28 | $this->idGet = $this->request->get('letter', null, '/^[\p{L}\p{N}]$/u'); 29 | $this->getEntries(); 30 | $this->idPage = Serie::getEntryIdByLetter($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByLetter.letter"), str_format(localize("seriesword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("series.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Serie::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $baselist = new BaseList($this->className, $this->request); 48 | $this->entryArray = $baselist->getEntriesByFirstLetter($this->idGet, $this->n); 49 | $this->totalNumber = $baselist->countEntriesByFirstLetter($this->idGet); 50 | $this->sorted = $baselist->orderBy; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Pages/PageAllTags.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BaseList; 14 | use SebLucas\Cops\Calibre\Tag; 15 | use SebLucas\Cops\Input\Config; 16 | 17 | class PageAllTags extends Page 18 | { 19 | protected $className = Tag::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | $this->getEntries(); 28 | $this->idPage = Tag::PAGE_ID; 29 | $this->title = localize("tags.title"); 30 | } 31 | 32 | /** 33 | * Summary of getEntries 34 | * @return void 35 | */ 36 | public function getEntries() 37 | { 38 | $baselist = new BaseList($this->className, $this->request); 39 | if ($this->request->option("tag_split_first_letter") == 1 || $this->request->get('letter')) { 40 | $this->entryArray = $baselist->getCountByFirstLetter(); 41 | $this->sorted = $baselist->orderBy; 42 | if (in_array("tag", Config::get('show_not_set_filter'))) { 43 | array_push($this->entryArray, $baselist->getWithoutEntry()); 44 | } 45 | return; 46 | } 47 | $this->sorted = $this->request->getSorted("sort"); 48 | if ($baselist->hasChildCategories()) { 49 | // use tag_browser_tags view here, to get the full hierarchy? 50 | $this->entryArray = $baselist->browseAllEntries($this->n, $this->request->get('tree')); 51 | } else { 52 | $this->entryArray = $baselist->getRequestEntries($this->n); 53 | } 54 | $this->totalNumber = $baselist->countRequestEntries(); 55 | $this->sorted = $baselist->orderBy; 56 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("tag", Config::get('show_not_set_filter'))) { 57 | array_push($this->entryArray, $baselist->getWithoutEntry()); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Pages/PageAllTagsLetter.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Tag; 14 | use SebLucas\Cops\Calibre\BaseList; 15 | use SebLucas\Cops\Input\Route; 16 | 17 | class PageAllTagsLetter extends Page 18 | { 19 | protected $className = Tag::class; 20 | 21 | /** 22 | * Summary of initializeContent 23 | * @return void 24 | */ 25 | public function initializeContent() 26 | { 27 | // this would be the first letter - override here 28 | $this->idGet = $this->request->get('letter', null, '/^[\p{L}\p{N}]$/u'); 29 | $this->getEntries(); 30 | $this->idPage = Tag::getEntryIdByLetter($this->idGet); 31 | $count = $this->totalNumber; 32 | if ($count == -1) { 33 | $count = count($this->entryArray); 34 | } 35 | $this->title = str_format(localize("splitByLetter.letter"), str_format(localize("tagword", $count), $count), $this->idGet); 36 | $this->parentTitle = ""; // localize("tags.title"); 37 | $filterParams = $this->request->getFilterParams(); 38 | $this->parentUri = $this->getRoute(Tag::ROUTE_ALL, $filterParams); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @return void 44 | */ 45 | public function getEntries() 46 | { 47 | $baselist = new BaseList($this->className, $this->request); 48 | $this->entryArray = $baselist->getEntriesByFirstLetter($this->idGet, $this->n); 49 | $this->totalNumber = $baselist->countEntriesByFirstLetter($this->idGet); 50 | $this->sorted = $baselist->orderBy; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Pages/PageAllVirtualLibraries.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\VirtualLibrary; 14 | use SebLucas\Cops\Input\Config; 15 | 16 | class PageAllVirtualLibraries extends Page 17 | { 18 | protected $className = VirtualLibrary::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->getEntries(); 27 | $this->idPage = VirtualLibrary::PAGE_ID; 28 | $this->title = localize("libraries.title"); 29 | } 30 | 31 | /** 32 | * Summary of getEntries 33 | * @return void 34 | */ 35 | public function getEntries() 36 | { 37 | $this->entryArray = VirtualLibrary::getEntries($this->getDatabaseId(), $this->handler); 38 | $this->totalNumber = VirtualLibrary::countEntries($this->getDatabaseId()); 39 | $this->sorted = null; 40 | if ((!$this->isPaginated() || $this->n == $this->getMaxPage()) && in_array("libraries", Config::get('show_not_set_filter'))) { 41 | array_push($this->entryArray, VirtualLibrary::getWithoutEntry($this->getDatabaseId(), $this->handler)); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Pages/PageBookDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\Book; 14 | use InvalidArgumentException; 15 | 16 | class PageBookDetail extends Page 17 | { 18 | protected $className = Book::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | $this->book = Book::getBookById($this->idGet, $this->getDatabaseId()); 27 | if (is_null($this->book)) { 28 | throw new InvalidArgumentException('Invalid Book'); 29 | } 30 | $this->book->setHandler($this->handler); 31 | $this->idPage = $this->book->getEntryId(); 32 | $this->title = $this->book->getTitle(); 33 | $this->currentUri = $this->book->getUri(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Pages/PageFormatDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | use SebLucas\Cops\Calibre\Format; 15 | 16 | /** 17 | * This shows the books with a particular format, e.g. epub, pdf, ... 18 | */ 19 | class PageFormatDetail extends PageWithDetail 20 | { 21 | protected $className = Format::class; 22 | 23 | public function initializeContent() 24 | { 25 | // this would be the identifier - override here 26 | $this->idGet = $this->request->get('id', null, '/^\w+$/'); 27 | /** @var Format $instance */ 28 | $instance = Format::getInstanceById($this->idGet, $this->getDatabaseId()); 29 | $instance->setHandler($this->handler); 30 | if ($this->request->get('filter')) { 31 | $this->filterParams = [Format::URL_PARAM => $this->idGet]; 32 | $this->getFilters($instance); 33 | } else { 34 | $this->getEntries($instance); 35 | } 36 | $this->setInstance($instance); 37 | } 38 | 39 | /** 40 | * Summary of getEntries 41 | * @param Format $instance 42 | * @return void 43 | */ 44 | public function getEntries($instance = null) 45 | { 46 | $booklist = new BookList($this->request); 47 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByInstance($instance, $this->n); 48 | $this->sorted = $booklist->orderBy ?? "sort"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Pages/PageIdentifierDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | use SebLucas\Cops\Calibre\Identifier; 15 | 16 | /** 17 | * This shows the books with a particular identifier type, e.g. amazon, isbn, url, ... 18 | */ 19 | class PageIdentifierDetail extends PageWithDetail 20 | { 21 | protected $className = Identifier::class; 22 | 23 | public function initializeContent() 24 | { 25 | // this would be the identifier - override here 26 | $this->idGet = $this->request->get('id', null, '/^\w+$/'); 27 | /** @var Identifier $instance */ 28 | $instance = Identifier::getInstanceById($this->idGet, $this->getDatabaseId()); 29 | $instance->setHandler($this->handler); 30 | if ($this->request->get('filter')) { 31 | $this->filterParams = [Identifier::URL_PARAM => $this->idGet]; 32 | $this->getFilters($instance); 33 | } else { 34 | $this->getEntries($instance); 35 | } 36 | $this->setInstance($instance); 37 | } 38 | 39 | /** 40 | * Summary of getEntries 41 | * @param Identifier $instance 42 | * @return void 43 | */ 44 | public function getEntries($instance = null) 45 | { 46 | $booklist = new BookList($this->request); 47 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByInstance($instance, $this->n); 48 | $this->sorted = $booklist->orderBy ?? "sort"; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Pages/PageLanguageDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | use SebLucas\Cops\Calibre\Language; 15 | 16 | class PageLanguageDetail extends PageWithDetail 17 | { 18 | protected $className = Language::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | /** @var Language $instance */ 27 | $instance = Language::getInstanceById($this->idGet, $this->getDatabaseId()); 28 | $instance->setHandler($this->handler); 29 | if ($this->request->get('filter')) { 30 | $this->filterParams = [Language::URL_PARAM => $this->idGet]; 31 | $this->getFilters($instance); 32 | } else { 33 | $this->getEntries($instance); 34 | } 35 | $this->setInstance($instance); 36 | } 37 | 38 | /** 39 | * Summary of getEntries 40 | * @param Language $instance 41 | * @return void 42 | */ 43 | public function getEntries($instance = null) 44 | { 45 | $booklist = new BookList($this->request); 46 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByInstance($instance, $this->n); 47 | $this->sorted = $booklist->orderBy ?? "sort"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Pages/PagePublisherDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | use SebLucas\Cops\Calibre\Publisher; 15 | 16 | class PagePublisherDetail extends PageWithDetail 17 | { 18 | protected $className = Publisher::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | /** @var Publisher $instance */ 27 | $instance = Publisher::getInstanceById($this->idGet, $this->getDatabaseId()); 28 | $instance->setHandler($this->handler); 29 | if ($this->request->get('filter')) { 30 | $this->filterParams = [Publisher::URL_PARAM => $this->idGet]; 31 | $this->getFilters($instance); 32 | } elseif ($this->request->get('extra')) { 33 | // show extra information without books 34 | $this->getExtra($instance); 35 | } else { 36 | $this->getEntries($instance); 37 | } 38 | $this->setInstance($instance); 39 | } 40 | 41 | /** 42 | * Summary of getEntries 43 | * @param Publisher $instance 44 | * @return void 45 | */ 46 | public function getEntries($instance = null) 47 | { 48 | $booklist = new BookList($this->request); 49 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByInstance($instance, $this->n); 50 | $this->sorted = $booklist->orderBy ?? "sort"; 51 | $this->getExtra($instance); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Pages/PageQueryScope.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | enum PageQueryScope: string 14 | { 15 | case AUTHOR = "author"; 16 | case BOOK = "book"; 17 | case FORMAT = "format"; 18 | case IDENTIFIER = "identifier"; 19 | case LANGUAGE = "language"; 20 | case PUBLISHER = "publisher"; 21 | case RATING = "rating"; 22 | case SERIES = "series"; 23 | case TAG = "tag"; 24 | case LIBRARIES = "libraries"; 25 | 26 | /** 27 | * Summary of in_array 28 | * @param array $values 29 | * @return bool 30 | */ 31 | public function in_array($values): bool 32 | { 33 | return in_array($this->value, $values); 34 | } 35 | 36 | public function result(): string 37 | { 38 | return localize("search.result.{$this->value}"); 39 | } 40 | 41 | public function title(): string 42 | { 43 | return match ($this) { 44 | self::AUTHOR => localize("authors.title"), 45 | self::BOOK => localize("bookword.title"), 46 | self::FORMAT => localize("formats.title"), 47 | self::IDENTIFIER => localize("identifiers.title"), 48 | self::LANGUAGE => localize("languages.title"), 49 | self::PUBLISHER => localize("publishers.title"), 50 | self::RATING => localize("ratings.title"), 51 | self::SERIES => localize("series.title"), 52 | self::TAG => localize("tags.title"), 53 | self::LIBRARIES => localize("libraries.title"), 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Pages/PageRatingDetail.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | use SebLucas\Cops\Calibre\Rating; 15 | 16 | class PageRatingDetail extends PageWithDetail 17 | { 18 | protected $className = Rating::class; 19 | 20 | /** 21 | * Summary of initializeContent 22 | * @return void 23 | */ 24 | public function initializeContent() 25 | { 26 | /** @var Rating $instance */ 27 | $instance = Rating::getInstanceById($this->idGet, $this->getDatabaseId()); 28 | $instance->setHandler($this->handler); 29 | if ($this->request->get('filter')) { 30 | $this->filterParams = [Rating::URL_PARAM => $this->idGet]; 31 | $this->getFilters($instance); 32 | } else { 33 | $this->getEntries($instance); 34 | } 35 | $this->setInstance($instance); 36 | } 37 | 38 | /** 39 | * Summary of getEntries 40 | * @param Rating $instance 41 | * @return void 42 | */ 43 | public function getEntries($instance = null) 44 | { 45 | $booklist = new BookList($this->request); 46 | [$this->entryArray, $this->totalNumber] = $booklist->getBooksByInstance($instance, $this->n); 47 | $this->sorted = $booklist->orderBy ?? "sort"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Pages/PageRecentBooks.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | namespace SebLucas\Cops\Pages; 12 | 13 | use SebLucas\Cops\Calibre\BookList; 14 | 15 | class PageRecentBooks extends Page 16 | { 17 | //protected $className = Book::class; 18 | 19 | /** 20 | * Summary of initializeContent 21 | * @return void 22 | */ 23 | public function initializeContent() 24 | { 25 | $this->getEntries(); 26 | $this->idPage = PageId::ALL_RECENT_BOOKS_ID; 27 | $this->title = localize("recent.title"); 28 | } 29 | 30 | /** 31 | * Summary of getEntries 32 | * @return void 33 | */ 34 | public function getEntries() 35 | { 36 | $booklist = new BookList($this->request); 37 | $this->entryArray = $booklist->getAllRecentBooks(); 38 | $this->sorted = $booklist->orderBy ?? "timestamp desc"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Routing/RouterInterface.php: -------------------------------------------------------------------------------- 1 | array of path params or null if not found 22 | */ 23 | public function match($path, $method = null); 24 | 25 | /** 26 | * Generate URL path for route name and params 27 | * @param string $name 28 | * @param array $params 29 | * @throws \Throwable 30 | * @return string 31 | */ 32 | public function generate($name, $params); 33 | 34 | /** 35 | * Get internal router for handler routes (cached) 36 | * @param bool $refresh 37 | * @return mixed 38 | */ 39 | public function getRouter($refresh = false); 40 | 41 | /** 42 | * Add multiple routes at once 43 | * @param array> $routes Array of routes with [path, params, methods, options] 44 | * @return void 45 | */ 46 | public function addRoutes(array $routes): void; 47 | 48 | /** 49 | * Add single route - mainly for testing 50 | * @param string|array $methods HTTP methods (GET, POST etc.) 51 | * @param string $path Route pattern with optional {param} placeholders 52 | * @param array $params Fixed parameters including handler 53 | * @param array $options Extra options including route name 54 | * @return void 55 | */ 56 | public function addRoute(string|array $methods, string $path, array $params, array $options = []): void; 57 | } 58 | -------------------------------------------------------------------------------- /src/Routing/url_generating_routes.php.meta: -------------------------------------------------------------------------------- 1 | a:0:{} -------------------------------------------------------------------------------- /src/Routing/url_generating_routes.php.meta.json: -------------------------------------------------------------------------------- 1 | {"resources":[]} -------------------------------------------------------------------------------- /src/Routing/url_matching_routes.php.meta: -------------------------------------------------------------------------------- 1 | a:0:{} -------------------------------------------------------------------------------- /src/Routing/url_matching_routes.php.meta.json: -------------------------------------------------------------------------------- 1 | {"resources":[]} -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 8 | * @author mikespub 9 | */ 10 | 11 | use SebLucas\Cops\Input\Config; 12 | use SebLucas\Cops\Language\Translation; 13 | use SebLucas\Cops\Output\Format; 14 | 15 | if (!function_exists('str_format')) { 16 | /** 17 | * Summary of str_format 18 | * @param string $format 19 | * @param array $args 20 | * @return string 21 | */ 22 | function str_format($format, ...$args) 23 | { 24 | return Format::str_format($format, ...$args); 25 | } 26 | } 27 | 28 | if (!function_exists('localize')) { 29 | $translator = new Translation($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null); 30 | Config::set('_translator_', $translator); 31 | 32 | /** 33 | * Summary of localize 34 | * @param string $phrase 35 | * @param int $count 36 | * @param bool $reset 37 | * @return string 38 | */ 39 | function localize($phrase, $count = -1, $reset = false) 40 | { 41 | return Config::get('_translator_')->localize($phrase, $count, $reset); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 |
2 |

About COPS

3 |

This Fork

4 |

See CHANGELOG on Github for details: https://github.com/mikespub-org/seblucas-cops

5 | 6 |

Authors

7 |

COPS is developed and maintained by Sébastien Lucas.

8 | 9 |

See full history on Github to check all authors.

10 | 11 |

COPS use some external libraries, check README for the details.

12 |

Copyright

13 |

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 or later as published by the Free Software Foundation.

14 | 15 |

The complete content of license is provided in file LICENSE within distribution and also available online.

16 |

Contact

17 |

For more info please visit COPS Home Page

18 | 19 |

You can also check COPS's topic on MobileRead forum.

20 | 21 |

DISCLAIMER : COPS is an open source software free to install everywhere. So if you have questions about any books available with any installation of COPS, please ask the owner of the website and not COPS's maintainer.

22 |

Thanks

23 |

Thanks a lot to Kovid Goyal for Calibre.

24 | 25 |

And many thanks to all those who helped test COPS.

26 |
27 | -------------------------------------------------------------------------------- /templates/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | COPS - {{=it.title}} 5 | 6 | 7 | 8 |

{{=it.title}}

9 |

{{=it.content}}

10 |

Link: {{=it.home}}

11 | 12 | -------------------------------------------------------------------------------- /templates/bootstrap/footer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/bootstrap/footer.html -------------------------------------------------------------------------------- /templates/bootstrap/page.html: -------------------------------------------------------------------------------- 1 | {{#def.header}} 2 | {{#def.main}} 3 | {{#def.footer}} 4 | -------------------------------------------------------------------------------- /templates/bootstrap/scripts/cops.js: -------------------------------------------------------------------------------- 1 | function postRefresh() 2 | { 3 | $('[data-toggle="tooltip"]').tooltip(); 4 | } -------------------------------------------------------------------------------- /templates/bootstrap/styles/style-base.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap/styles/style-default.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap/suggestion.html: -------------------------------------------------------------------------------- 1 |

{{=it.title}}

-------------------------------------------------------------------------------- /templates/bootstrap2/footer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/bootstrap2/footer.html -------------------------------------------------------------------------------- /templates/bootstrap2/page.html: -------------------------------------------------------------------------------- 1 | {{#def.header}} 2 | {{#def.main}} 3 | {{#def.footer}} 4 | -------------------------------------------------------------------------------- /templates/bootstrap2/scripts/cops.js: -------------------------------------------------------------------------------- 1 | function postRefresh() 2 | { 3 | $('[data-toggle="tooltip"]').tooltip(); 4 | hash = window.location.hash.replace("#", ""); 5 | var elmnt = document.getElementById(hash); 6 | if (elmnt) elmnt.scrollIntoView(); 7 | } -------------------------------------------------------------------------------- /templates/bootstrap2/styles/style-base.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap2/styles/style-default.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap2/suggestion.html: -------------------------------------------------------------------------------- 1 |

{{=it.title}}

-------------------------------------------------------------------------------- /templates/bootstrap5/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/bootstrap5/page.html: -------------------------------------------------------------------------------- 1 | {{#def.header}} 2 | 3 | {{#def.footer}} 4 | {{#def.main}} 5 | -------------------------------------------------------------------------------- /templates/bootstrap5/scripts/cops.js: -------------------------------------------------------------------------------- 1 | // Refactored to replace ES6+ features with ES5-compatible syntax 2 | function postRefresh() 3 | { 4 | var tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); 5 | var tooltipList = Array.prototype.map.call(tooltipTriggerList, function(tooltipTriggerEl) { 6 | return new bootstrap.Tooltip(tooltipTriggerEl); 7 | }); 8 | hash = window.location.hash.replace("#", ""); 9 | var elmnt = document.getElementById(hash); 10 | if (elmnt) { 11 | elmnt.scrollIntoView(); 12 | } 13 | $(".tt-hint").attr("name","searchTypeahead"); 14 | 15 | /* Add submenu class to body to give extra spacing */ 16 | if ($("#controls-menu").length > 0) { 17 | document.body.classList.add("submenu"); 18 | } else { 19 | if (document.body.classList.contains("submenu")) { 20 | document.body.classList.remove("submenu"); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /templates/bootstrap5/styles/style-base.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap5/styles/style-default.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/bootstrap5/suggestion.html: -------------------------------------------------------------------------------- 1 |

{{=it.title}}

-------------------------------------------------------------------------------- /templates/default/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{? it.isPaginated == 1}} 9 |
10 | {{? it.prevLink != ""}}
{{?}} 11 |

{{=it.currentPage}} / {{=it.maxPage}}

12 | {{? it.nextLink != ""}}
{{?}} 13 |
14 | {{?}} 15 |
-------------------------------------------------------------------------------- /templates/default/page.html: -------------------------------------------------------------------------------- 1 |
2 | {{#def.header}} 3 | {{#def.main}} 4 | {{#def.footer}} 5 |
-------------------------------------------------------------------------------- /templates/default/styles/fa-solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:Font Awesome\ 5 Free;font-weight:900} -------------------------------------------------------------------------------- /templates/default/styles/style-default.css: -------------------------------------------------------------------------------- 1 | @import url("style-base.css"); 2 | 3 | 4 | /* ============================================================================= 5 | Main container stuff goes here and other globals 6 | ========================================================================== */ 7 | .container { 8 | border-radius:10px; 9 | } 10 | 11 | /* ============================================================================= 12 | Header stuff goes here 13 | ========================================================================== */ 14 | header { 15 | box-shadow:inset 0px -5px 8px #000000; 16 | border-radius: 10px 10px 0px 0px; 17 | } 18 | 19 | .hicon{ 20 | text-shadow: 2px 2px 2px black; 21 | } 22 | 23 | .submit { 24 | -webkit-border-radius: 0; 25 | -moz-border-radius: 0; 26 | border-radius: 0; 27 | -webkit-box-shadow: none; 28 | -moz-box-shadow: none; 29 | box-shadow: none; 30 | } 31 | 32 | header h1{ 33 | display: table-cell; 34 | vertical-align: middle; 35 | text-align: center; 36 | line-height: 100%; 37 | } 38 | 39 | header a{ 40 | color: white; 41 | } 42 | 43 | .download a { 44 | border-radius: 6px; 45 | box-shadow:0 0 10px #000; 46 | background: radial-gradient(#666, black); 47 | } 48 | 49 | footer 50 | { 51 | box-shadow:inset 0px 5px 8px #000000; 52 | border-radius: 0px 0px 10px 10px; 53 | } 54 | 55 | 56 | /* ============================================================================= 57 | Mediaquerie stuff goes here 58 | ========================================================================== */ 59 | /* 768px and greater */ 60 | @media only screen and (min-width: 768px) { 61 | .container { box-shadow:0 0 20px #000; } 62 | } -------------------------------------------------------------------------------- /templates/default/styles/style-eink.css: -------------------------------------------------------------------------------- 1 | @import url("style-base.css"); 2 | 3 | a { 4 | color: black; 5 | font-weight: bold; 6 | } 7 | .frontpage a:hover, .books:hover { background-color: white;} 8 | 9 | #filter ul { 10 | font-variant: normal; 11 | } 12 | 13 | li, .container { 14 | background-color: white; 15 | } 16 | 17 | header { 18 | color: black; 19 | text-transform: none; 20 | border-bottom: 2px dashed gray; 21 | font-variant: small-caps; 22 | letter-spacing: 2px; 23 | } 24 | 25 | #tool input[type=text], .twitter-typeahead 26 | { 27 | font-variant: normal; 28 | } 29 | 30 | .hicon, .footcenter, .submit, .fullclickpopup{ 31 | color:black; 32 | } 33 | 34 | .frontpage h2 { 35 | text-align: center; 36 | letter-spacing: 2px; 37 | color: black; 38 | font-family: "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif; 39 | } 40 | 41 | .frontpage h4 { 42 | padding: 5px 0; 43 | text-align: center; 44 | color: gray; 45 | font-size: 13px; 46 | text-decoration: overline; 47 | } 48 | 49 | .download a { 50 | background: black; 51 | color: white; 52 | } 53 | 54 | 55 | .tt-dropdown-menu { 56 | color: black; 57 | background-color: white; 58 | border: 1px solid #ccc; 59 | } 60 | .tt-suggestion.tt-is-under-cursor { 61 | color: black; 62 | background-color: white; 63 | } -------------------------------------------------------------------------------- /templates/default/suggestion.html: -------------------------------------------------------------------------------- 1 |

{{=it.title}}

-------------------------------------------------------------------------------- /templates/default/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/default/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /templates/default/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/default/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /templates/default/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/default/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /templates/default/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/templates/default/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /templates/epubjs-reader.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{=it.title}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | COPS - Error 5 | 6 | 7 |

Error

8 | 9 |

COPS: {{=it.error}}

10 |

Please report this issue at https://github.com/mikespub-org/seblucas-cops/issues if needed

11 |

Link: Home

12 | 13 | -------------------------------------------------------------------------------- /templates/notfound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | COPS - Not Found 5 | 6 | 7 |

Not Found

8 | 9 |

COPS: {{=it.error}}

10 |

Link: Home

11 | 12 | -------------------------------------------------------------------------------- /templates/restapi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | COPS - SwaggerUI 11 | 12 | 13 | 14 |
15 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/twigged/about.html: -------------------------------------------------------------------------------- 1 | {# about.html is used for it.page == "about" #} 2 | {% extends 'base.html' %} 3 | 4 | {% block main %} 5 |
6 | {{it.fullhtml|raw}} 7 |
8 | {% endblock main %} -------------------------------------------------------------------------------- /templates/twigged/authordetail.html: -------------------------------------------------------------------------------- 1 | {# authordetail.html is (not) used for it.page == "author" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/customdetail.html: -------------------------------------------------------------------------------- 1 | {# customdetail.html is (not) used for it.page == "custom" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/extra_info.html: -------------------------------------------------------------------------------- 1 | {# extra_info.html is included in booklist.html if it.extra.title is not empty #} 2 | 3 |
4 |
5 |
6 |
7 |
8 | {{it.extra.title}} 9 |
10 |
11 | {% if it.extra.link %} 12 |

{{it.c.i18n.linkTitle}}: {{it.extra.link}}

13 | {% endif %} 14 | {% if it.extra.content %} 15 | {{it.extra.content|raw}} 16 | {% endif %} 17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /templates/twigged/extra_series.html: -------------------------------------------------------------------------------- 1 | {# extra_series.html is included in booklist.html if it.extra.series is not empty #} 2 | 3 |
4 |
5 |
6 |
7 |
8 | {{it.c.i18n.seriesTitle}} 9 |
10 |
11 |
    12 | {% for series in it.extra.series %} 13 |
  • {{series.title}} ({{series.number}})
  • 14 | {% endfor %} 15 |
16 |
17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /templates/twigged/identifierdetail.html: -------------------------------------------------------------------------------- 1 | {# identifierdetail.html is (not) used for it.page == "identifier" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/languagedetail.html: -------------------------------------------------------------------------------- 1 | {# languagedetail.html is (not) used for it.page == "language" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/loader/README.md: -------------------------------------------------------------------------------- 1 | ## Replacing templates for epub-loader 2 | 3 | If you want to override the standard templates of epub-loader, you can place 4 | them here (or anywhere else really), and update $gConfig['template_dir'] in 5 | `config/loader.php` 6 | 7 | Every action typically has its own template that extends [index.html](index.html) 8 | and defines its own 'content' block. 9 | 10 | Check the [epub-loader templates](https://github.com/mikespub-org/epub-loader/tree/main/templates) 11 | folder for specific action templates you want to re-use. Unlike the 'twigged' templates in COPS 12 | itself, you don't need to use it.* for every variable. 13 | 14 | Note: they're also here under vendor/mikespub/epub-loader/templates if you installed the package. 15 | -------------------------------------------------------------------------------- /templates/twigged/loader/actions.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} 2 | 3 | {% block content %} 4 |
5 | Select action 6 |
7 | {% if groups %} 8 |
    9 | {% for group, actionList in groups %} 10 | {% if group != "Internal" %} 11 |
  • 12 | {{group}} 13 |
      14 | {% for action, actionInfo in actionList %} 15 |
    • 16 | {% if action in actions|keys %} 17 | {{actionInfo}} 18 | {% else %} 19 | {{actionInfo}} 20 | {% endif %} 21 |
    • 22 | {% endfor %} 23 |
    24 |
  • 25 | {% endif %} 26 | {% endfor %} 27 |
28 | {% else %} 29 |
    30 | {% for action, actionInfo in actions %} 31 |
  • 32 | {{actionInfo}} 33 |
  • 34 | {% endfor %} 35 |
36 | {% endif %} 37 | {% endblock content %} 38 | -------------------------------------------------------------------------------- /templates/twigged/loader/databases.html: -------------------------------------------------------------------------------- 1 | {% extends 'index.html' %} 2 | 3 | {% block content %} 4 |
5 | Select database 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% for dbNum, dbConfig in databases %} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 |
Db numDb nameAuthorsBooksSeriesNotesActionDb PathEpub pathNb Files
{{dbNum}}{{dbConfig.name}}{{dbConfig.authors}}{{dbConfig.books}}{{dbConfig.series}}{{dbConfig['notes_db.notes']}}{{actionTitle}}{{dbConfig.db_path}}{{dbConfig.epub_path}}{{dbConfig.count}}
35 |

Stats Updated: {{statsUpdated}}

36 | {% endblock content %} 37 | -------------------------------------------------------------------------------- /templates/twigged/navlist.html: -------------------------------------------------------------------------------- 1 | {# navlist.html is used for it.containsBook == 0 #} 2 | {% extends 'mainlist.html' %} 3 | 4 | {% block sortlinks %} 5 | {% if it.sortoptions ?? false %} 6 | 7 | 19 | {% endif %} 20 | {% endblock sortlinks %} 21 | 22 | {% block content %} 23 |
24 |
25 | {% for entry in it.entries %} 26 |
27 | {% block entry %} 28 | 36 | {% endblock entry %} 37 |
38 | {% endfor %} 39 |
40 |
41 | {% endblock content %} 42 | -------------------------------------------------------------------------------- /templates/twigged/page.html: -------------------------------------------------------------------------------- 1 | {# page.html is rendered by Twig and it decides which template to include #} 2 | {% if it.page == "book" %} 3 | {% include 'bookdetail.html' %} 4 | {% elseif it.page == "about" %} 5 | {% include 'about.html' %} 6 | {% elseif it.page == "customize" %} 7 | {% include 'customize.html' %} 8 | {% elseif it.isFilterPage %} 9 | {% include 'filters.html' %} 10 | {% elseif it.containsBook == 0 %} 11 | {% include 'navlist.html' %} 12 | {% elseif it.page == "recent" %} 13 | {% include 'recent.html' %} 14 | {% else %} 15 | {% include 'booklist.html' %} 16 | {% endif %} -------------------------------------------------------------------------------- /templates/twigged/publisherdetail.html: -------------------------------------------------------------------------------- 1 | {# publisherdetail.html is (not) used for it.page == "publisher" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/ratingdetail.html: -------------------------------------------------------------------------------- 1 | {# ratingdetail.html is (not) used for it.page == "rating" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/recent.html: -------------------------------------------------------------------------------- 1 | {# recent.html is (not) used for it.page == "recent" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/scripts/cops.js: -------------------------------------------------------------------------------- 1 | function postRefresh() 2 | { 3 | $('[data-toggle="tooltip"]').tooltip(); 4 | hash = window.location.hash.replace("#", ""); 5 | var elmnt = document.getElementById(hash); 6 | if (elmnt) elmnt.scrollIntoView(); 7 | } 8 | 9 | // Refactored to replace ES6+ features with ES5-compatible syntax 10 | function initiateTwig(url, theme, templates, version) { 11 | templates = typeof templates !== 'undefined' ? templates : 'templates'; 12 | Twig.extendFunction("str_format", str_format); 13 | Twig.extendFunction("asset", asset); 14 | 15 | var template = Twig.twig({ 16 | id: 'page', 17 | href: templates + '/' + theme + '/page.html?v=' + version, 18 | async: false 19 | }); 20 | 21 | templatePage = function (data) { 22 | return Twig.twig({ref: 'page'}).render({it: data}); 23 | }; 24 | 25 | $.when($.getJSON(url)).done(function(data){ 26 | currentData = data; 27 | 28 | updatePage (currentData); 29 | cache.put (url, currentData); 30 | if (isPushStateEnabled) { 31 | window.history.replaceState(url, "", window.location); 32 | } 33 | handleLinks (); 34 | }).fail(function (error) { 35 | if (error.responseText) { 36 | document.write (error.responseText); 37 | } else { 38 | document.write ('Error loading templates from directory "' + encodeURI(templates + '/' + theme) + '/" for url: ' + encodeURI(url)); 39 | } 40 | console.log('getJSON failed: ' + JSON.stringify(error, null, 4)); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /templates/twigged/seriedetail.html: -------------------------------------------------------------------------------- 1 | {# seriedetail.html is (not) used for it.page == "serie" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/styles/style-base.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/twigged/styles/style-default.css: -------------------------------------------------------------------------------- 1 | .panel-body { padding: 5px; } 2 | 3 | .bottomright {position:absolute; bottom:0; margin-bottom:25px; right: 20px;} -------------------------------------------------------------------------------- /templates/twigged/suggestion.html: -------------------------------------------------------------------------------- 1 |

{{it.title}}

-------------------------------------------------------------------------------- /templates/twigged/tagdetail.html: -------------------------------------------------------------------------------- 1 | {# tagdetail.html is (not) used for it.page == "tag" #} 2 | {% extends 'booklist.html' %} 3 | -------------------------------------------------------------------------------- /templates/twigged/var_dump.json.twig: -------------------------------------------------------------------------------- 1 | {# dump all template variables as json for template development or debugging #} 2 | {{ _context|json_encode()|raw }} -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | config 3 | BaseWithSomeBooks/.calnotes/backup/ 4 | BaseWithSomeBooks/.calnotes/retired/ 5 | BaseWithSomeBooks/.caltrash/ 6 | BaseWithSomeBooks/metadata_db_prefs_backup.json 7 | -------------------------------------------------------------------------------- /tests/BaseWithCustomColumns/metadata.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithCustomColumns/metadata.db -------------------------------------------------------------------------------- /tests/BaseWithOneBook/metadata.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithOneBook/metadata.db -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/notes.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/.calnotes/notes.db -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/7c/xxh64-7c301792c52eebf7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/.calnotes/resources/7c/xxh64-7c301792c52eebf7 -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/7c/xxh64-7c301792c52eebf7.metadata: -------------------------------------------------------------------------------- 1 | {"name": "330px-LewisCarrollSelfPhoto.jpg"} -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/c0/xxh64-c062fb16e3568fdc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/.calnotes/resources/c0/xxh64-c062fb16e3568fdc -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/c0/xxh64-c062fb16e3568fdc.metadata: -------------------------------------------------------------------------------- 1 | {"name": "image.jpg"} -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/d7/xxh64-d7e814ba6a1b41d1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/.calnotes/resources/d7/xxh64-d7e814ba6a1b41d1 -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/.calnotes/resources/d7/xxh64-d7e814ba6a1b41d1.metadata: -------------------------------------------------------------------------------- 1 | {"name": "image-1.jpg"} -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/Alice's Adventures in Wonderland - Lewis Carroll.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/Alice's Adventures in Wonderland - Lewis Carroll.epub -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/cover.jpg -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/data/hello.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Lewis Carroll/Alice's Adventures in Wonderland (17)/data/sub folder/copied file.txt: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Sun Wu/Sun Zi Bing Fa (19)/Sun Zi Bing Fa - Sun Wu.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/Sun Wu/Sun Zi Bing Fa (19)/Sun Zi Bing Fa - Sun Wu.epub -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/Sun Wu/Sun Zi Bing Fa (19)/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/Sun Wu/Sun Zi Bing Fa (19)/cover.jpg -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/hierarchical_type2.sql: -------------------------------------------------------------------------------- 1 | -- SQLite 2 | insert into custom_column_2 values (4, "Tree"); 3 | insert into custom_column_2 values (5, "Tree.More"); 4 | update custom_column_2 set value="Tree.Tag1" where id=1; 5 | update custom_column_2 set value="Tree.More.Tag2" where id=2; 6 | update custom_column_2 set value="Tree.More.Tag3" where id=3; 7 | -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/metadata.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/metadata.db -------------------------------------------------------------------------------- /tests/BaseWithSomeBooks/users.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/BaseWithSomeBooks/users.db -------------------------------------------------------------------------------- /tests/OPDSValidator.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/OPDSValidator.jar -------------------------------------------------------------------------------- /tests/graphql/author.query.graphql: -------------------------------------------------------------------------------- 1 | query getAuthor($id: ID) { 2 | author(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/author.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getAuthor", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getAuthor($id: ID) {\n author(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/author.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "author": { 4 | "id": "cops:authors:1", 5 | "title": "Arthur Conan Doyle" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/authors.query.graphql: -------------------------------------------------------------------------------- 1 | query getAuthors { 2 | authors { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/authors.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getAuthors", 3 | "variables": [], 4 | "query": "query getAuthors {\n authors {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/authors.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "authors": [ 4 | { 5 | "id": "cops:authors:3", 6 | "title": "Lewis Carroll" 7 | }, 8 | { 9 | "id": "cops:authors:1", 10 | "title": "Arthur Conan Doyle" 11 | }, 12 | { 13 | "id": "cops:authors:5", 14 | "title": "Alexandre Dumas" 15 | }, 16 | { 17 | "id": "cops:authors:2", 18 | "title": "Jack London" 19 | }, 20 | { 21 | "id": "cops:authors:4", 22 | "title": "H. G. Wells" 23 | }, 24 | { 25 | "id": "cops:authors:7", 26 | "title": "Émile Zola" 27 | }, 28 | { 29 | "id": "cops:authors:9", 30 | "title": "孙武" 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /tests/graphql/book.query.graphql: -------------------------------------------------------------------------------- 1 | query getBook($id: ID) { 2 | book(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/book.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getBook", 3 | "variables": { 4 | "id": 17 5 | }, 6 | "query": "query getBook($id: ID) {\n book(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/book.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "book": { 4 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9", 5 | "title": "Alice's Adventures in Wonderland" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/books.query.graphql: -------------------------------------------------------------------------------- 1 | query getBooks { 2 | books { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/books.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getBooks", 3 | "variables": [], 4 | "query": "query getBooks {\n books {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/customColumn.query.graphql: -------------------------------------------------------------------------------- 1 | query getCustomColumn($id: ID) { 2 | customColumn(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/customColumn.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getCustomColumn", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getCustomColumn($id: ID) {\n customColumn(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/customColumn.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "customColumn": null 4 | } 5 | } -------------------------------------------------------------------------------- /tests/graphql/customColumns.query.graphql: -------------------------------------------------------------------------------- 1 | query getCustomColumns { 2 | customColumns { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/customColumns.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getCustomColumns", 3 | "variables": [], 4 | "query": "query getCustomColumns {\n customColumns {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/customColumns.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "customColumns": null 4 | } 5 | } -------------------------------------------------------------------------------- /tests/graphql/data.query.graphql: -------------------------------------------------------------------------------- 1 | query getData($id: ID) { 2 | data(id: $id) { 3 | id 4 | name 5 | format 6 | } 7 | } -------------------------------------------------------------------------------- /tests/graphql/data.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getData", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getData($id: ID) {\n data(id: $id) {\n id\n name\n format\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/data.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "data": { 4 | "id": "1", 5 | "name": "The Return of Sherlock Holmes - Arthur Conan Doyle", 6 | "format": "EPUB" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /tests/graphql/datas.query.graphql: -------------------------------------------------------------------------------- 1 | query getDatas($bookId: ID) { 2 | datas(bookId: $bookId) { 3 | id 4 | name 5 | format 6 | } 7 | } -------------------------------------------------------------------------------- /tests/graphql/datas.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getDatas", 3 | "variables": { 4 | "bookId": 17 5 | }, 6 | "query": "query getDatas($bookId: ID) {\n datas(bookId: $bookId) {\n id\n name\n format\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/datas.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "datas": [ 4 | { 5 | "id": "17", 6 | "name": "Alice's Adventures in Wonderland - Lewis Carroll", 7 | "format": "MOBI" 8 | }, 9 | { 10 | "id": "19", 11 | "name": "Alice's Adventures in Wonderland - Lewis Carroll", 12 | "format": "PDF" 13 | }, 14 | { 15 | "id": "20", 16 | "name": "Alice's Adventures in Wonderland - Lewis Carroll", 17 | "format": "EPUB" 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /tests/graphql/format.query.graphql: -------------------------------------------------------------------------------- 1 | query getFormat($id: ID) { 2 | format(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/format.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getFormat", 3 | "variables": { 4 | "id": "EPUB" 5 | }, 6 | "query": "query getFormat($id: ID) {\n format(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/format.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "format": { 4 | "id": "cops:formats:EPUB", 5 | "title": "EPUB" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/formats.query.graphql: -------------------------------------------------------------------------------- 1 | query getFormats { 2 | formats { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/formats.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getFormats", 3 | "variables": [], 4 | "query": "query getFormats {\n formats {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/formats.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "formats": [ 4 | { 5 | "id": "cops:formats:EPUB", 6 | "title": "EPUB" 7 | }, 8 | { 9 | "id": "cops:formats:MOBI", 10 | "title": "MOBI" 11 | }, 12 | { 13 | "id": "cops:formats:PDF", 14 | "title": "PDF" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /tests/graphql/getAuthors.l.2.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "authors": [ 4 | { 5 | "id": "cops:authors:7", 6 | "title": "Émile Zola", 7 | "numberOfElement": "1", 8 | "books": [ 9 | { 10 | "id": "urn:uuid:08d43a34-fb89-446f-94ef-16bac6d8aa28", 11 | "title": "La curée", 12 | "languages": "French" 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /tests/graphql/getNode.authors.3.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "node": { 4 | "__typename": "Entry", 5 | "id": "cops:authors:3", 6 | "title": "Lewis Carroll", 7 | "content": "No books", 8 | "books": [ 9 | { 10 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9", 11 | "title": "Alice's Adventures in Wonderland" 12 | }, 13 | { 14 | "id": "urn:uuid:95029a0f-c537-4d8d-b4d0-b478ad4de645", 15 | "title": "Through the Looking Glass (And What Alice Found There)" 16 | } 17 | ] 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/graphql/getNode.books.17.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "node": { 4 | "__typename": "EntryBook", 5 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9", 6 | "title": "Alice's Adventures in Wonderland", 7 | "navlink": "vendor/bin/index.php/restapi/books/17/Lewis_Carroll/Alice_s_Adventures_in_Wonderland", 8 | "authors": [ 9 | { 10 | "id": "cops:authors:3", 11 | "title": "Lewis Carroll" 12 | } 13 | ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/graphql/getNode.datas.20.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "node": { 4 | "__typename": "Data", 5 | "id": "20", 6 | "name": "Alice's Adventures in Wonderland - Lewis Carroll", 7 | "format": "EPUB", 8 | "book": { 9 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9", 10 | "title": "Alice's Adventures in Wonderland" 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /tests/graphql/getNode.oops.42.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [ 3 | { 4 | "message": "Internal server error", 5 | "locations": [ 6 | { 7 | "line": 85, 8 | "column": 3 9 | } 10 | ], 11 | "path": [ 12 | "node" 13 | ], 14 | "extensions": { 15 | "debugMessage": "Invalid global identifier type" 16 | } 17 | } 18 | ], 19 | "data": { 20 | "node": null 21 | } 22 | } -------------------------------------------------------------------------------- /tests/graphql/identifier.query.graphql: -------------------------------------------------------------------------------- 1 | query getIdentifier($id: ID) { 2 | identifier(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/identifier.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getIdentifier", 3 | "variables": { 4 | "id": "isbn" 5 | }, 6 | "query": "query getIdentifier($id: ID) {\n identifier(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/identifier.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "identifier": { 4 | "id": "cops:identifiers:isbn", 5 | "title": "ISBN" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/identifiers.query.graphql: -------------------------------------------------------------------------------- 1 | query getIdentifiers { 2 | identifiers { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/identifiers.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getIdentifiers", 3 | "variables": [], 4 | "query": "query getIdentifiers {\n identifiers {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/identifiers.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "identifiers": [ 4 | { 5 | "id": "cops:identifiers:amazon", 6 | "title": "Amazon" 7 | }, 8 | { 9 | "id": "cops:identifiers:google", 10 | "title": "Google Books" 11 | }, 12 | { 13 | "id": "cops:identifiers:isbn", 14 | "title": "ISBN" 15 | }, 16 | { 17 | "id": "cops:identifiers:olid", 18 | "title": "OpenLibrary" 19 | }, 20 | { 21 | "id": "cops:identifiers:uri", 22 | "title": "uri" 23 | }, 24 | { 25 | "id": "cops:identifiers:urn", 26 | "title": "urn" 27 | }, 28 | { 29 | "id": "cops:identifiers:wd", 30 | "title": "Wikidata" 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /tests/graphql/language.query.graphql: -------------------------------------------------------------------------------- 1 | query getLanguage($id: ID) { 2 | language(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/language.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getLanguage", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getLanguage($id: ID) {\n language(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/language.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "language": { 4 | "id": "cops:languages:1", 5 | "title": "English" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/languages.query.graphql: -------------------------------------------------------------------------------- 1 | query getLanguages { 2 | languages { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/languages.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getLanguages", 3 | "variables": [], 4 | "query": "query getLanguages {\n languages {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/languages.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "languages": [ 4 | { 5 | "id": "cops:languages:1", 6 | "title": "English" 7 | }, 8 | { 9 | "id": "cops:languages:2", 10 | "title": "French" 11 | }, 12 | { 13 | "id": "cops:languages:3", 14 | "title": "Chinese" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /tests/graphql/node.query.graphql: -------------------------------------------------------------------------------- 1 | query getNode($id: ID!) { 2 | node(id: $id) { 3 | __typename 4 | id 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/node.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getNode", 3 | "variables": { 4 | "id": "/books/17" 5 | }, 6 | "query": "query getNode($id: ID!) {\n node(id: $id) {\n __typename\n id\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/node.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "node": { 4 | "__typename": "EntryBook", 5 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/nodelist.query.graphql: -------------------------------------------------------------------------------- 1 | query getNodelist($idlist: [ID!]!) { 2 | nodelist(idlist: $idlist) { 3 | __typename 4 | id 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/nodelist.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getNodelist", 3 | "variables": { 4 | "idlist": [ 5 | "/authors/3", 6 | "/books/17", 7 | "/datas/20", 8 | "/oops/42" 9 | ] 10 | }, 11 | "query": "query getNodelist($idlist: [ID!]!) {\n nodelist(idlist: $idlist) {\n __typename\n id\n }\n}" 12 | } -------------------------------------------------------------------------------- /tests/graphql/nodelist.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": [ 3 | { 4 | "message": "Internal server error", 5 | "locations": [ 6 | { 7 | "line": 2, 8 | "column": 3 9 | } 10 | ], 11 | "path": [ 12 | "nodelist", 13 | 3 14 | ], 15 | "extensions": { 16 | "debugMessage": "Invalid global identifier type" 17 | } 18 | } 19 | ], 20 | "data": { 21 | "nodelist": [ 22 | { 23 | "__typename": "Entry", 24 | "id": "cops:authors:3" 25 | }, 26 | { 27 | "__typename": "EntryBook", 28 | "id": "urn:uuid:d74fec58-06bc-4ba8-b8b4-24a91a58e6f9" 29 | }, 30 | { 31 | "__typename": "Data", 32 | "id": "20" 33 | }, 34 | null 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /tests/graphql/preference.query.graphql: -------------------------------------------------------------------------------- 1 | query getPreference($id: ID) { 2 | preference(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/preference.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getPreference", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getPreference($id: ID) {\n preference(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/preference.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "preference": null 4 | } 5 | } -------------------------------------------------------------------------------- /tests/graphql/preferences.query.graphql: -------------------------------------------------------------------------------- 1 | query getPreferences { 2 | preferences { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/preferences.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getPreferences", 3 | "variables": [], 4 | "query": "query getPreferences {\n preferences {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/preferences.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "preferences": null 4 | } 5 | } -------------------------------------------------------------------------------- /tests/graphql/publisher.query.graphql: -------------------------------------------------------------------------------- 1 | query getPublisher($id: ID) { 2 | publisher(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/publisher.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getPublisher", 3 | "variables": { 4 | "id": 2 5 | }, 6 | "query": "query getPublisher($id: ID) {\n publisher(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/publisher.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "publisher": { 4 | "id": "cops:publishers:2", 5 | "title": "Macmillan and Co. London" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/publishers.query.graphql: -------------------------------------------------------------------------------- 1 | query getPublishers { 2 | publishers { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/publishers.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getPublishers", 3 | "variables": [], 4 | "query": "query getPublishers {\n publishers {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/publishers.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "publishers": [ 4 | { 5 | "id": "cops:publishers:3", 6 | "title": "D. Appleton and Company" 7 | }, 8 | { 9 | "id": "cops:publishers:9", 10 | "title": "Georges Charpentier & Eugène Fasquelle" 11 | }, 12 | { 13 | "id": "cops:publishers:2", 14 | "title": "Macmillan and Co. London" 15 | }, 16 | { 17 | "id": "cops:publishers:4", 18 | "title": "Macmillan Publishers USA" 19 | }, 20 | { 21 | "id": "cops:publishers:5", 22 | "title": "Pierson's Magazine" 23 | }, 24 | { 25 | "id": "cops:publishers:6", 26 | "title": "Strand Magazine" 27 | }, 28 | { 29 | "id": "cops:publishers:10", 30 | "title": "青苹果数据中心" 31 | } 32 | ] 33 | } 34 | } -------------------------------------------------------------------------------- /tests/graphql/rating.query.graphql: -------------------------------------------------------------------------------- 1 | query getRating($id: ID) { 2 | rating(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/rating.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getRating", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getRating($id: ID) {\n rating(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/rating.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "rating": { 4 | "id": "cops:rating:1", 5 | "title": "5 stars" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/ratings.query.graphql: -------------------------------------------------------------------------------- 1 | query getRatings { 2 | ratings { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/ratings.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getRatings", 3 | "variables": [], 4 | "query": "query getRatings {\n ratings {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/ratings.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "ratings": [ 4 | { 5 | "id": "cops:rating:3", 6 | "title": "2 stars" 7 | }, 8 | { 9 | "id": "cops:rating:2", 10 | "title": "4 stars" 11 | }, 12 | { 13 | "id": "cops:rating:1", 14 | "title": "5 stars" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /tests/graphql/search.query.graphql: -------------------------------------------------------------------------------- 1 | query getSearch($query: String!, $scope: String) { 2 | search(query: $query, scope: $scope) { 3 | __typename 4 | ... on Entry { 5 | id 6 | title 7 | content 8 | numberOfElement 9 | } 10 | ... on EntryBook { 11 | id 12 | title 13 | authors { 14 | id 15 | title 16 | } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/graphql/search.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getSearch", 3 | "variables": { 4 | "query": "car" 5 | }, 6 | "query": "query getSearch($query: String!, $scope: String) {\n search(query: $query, scope: $scope) {\n __typename\n ... on Entry {\n id\n title\n content\n numberOfElement\n }\n ... on EntryBook {\n id\n title\n authors {\n id\n title\n }\n }\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/search.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "search": [ 4 | { 5 | "__typename": "Entry", 6 | "id": "db:query::book", 7 | "title": "Search result for *car* in books", 8 | "content": "1 book", 9 | "numberOfElement": "1" 10 | }, 11 | { 12 | "__typename": "EntryBook", 13 | "id": "urn:uuid:f3a4534e-d7bc-415a-8887-ba2b2810c980", 14 | "title": "A Study in Scarlet", 15 | "authors": [ 16 | { 17 | "id": "cops:authors:1", 18 | "title": "Arthur Conan Doyle" 19 | } 20 | ] 21 | }, 22 | { 23 | "__typename": "Entry", 24 | "id": "db:query::author", 25 | "title": "Search result for *car* in authors", 26 | "content": "1 author", 27 | "numberOfElement": "1" 28 | }, 29 | { 30 | "__typename": "Entry", 31 | "id": "cops:authors:3", 32 | "title": "Lewis Carroll", 33 | "content": "2 books", 34 | "numberOfElement": "2" 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /tests/graphql/serie.query.graphql: -------------------------------------------------------------------------------- 1 | query getSerie($id: ID) { 2 | serie(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/serie.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getSerie", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getSerie($id: ID) {\n serie(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/serie.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "serie": { 4 | "id": "cops:series:1", 5 | "title": "Sherlock Holmes" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/series.query.graphql: -------------------------------------------------------------------------------- 1 | query getSeries { 2 | series { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/series.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getSeries", 3 | "variables": [], 4 | "query": "query getSeries {\n series {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/series.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "series": [ 4 | { 5 | "id": "cops:series:3", 6 | "title": "D'Artagnan Romances" 7 | }, 8 | { 9 | "id": "cops:series:2", 10 | "title": "Professor Challenger" 11 | }, 12 | { 13 | "id": "cops:series:1", 14 | "title": "Sherlock Holmes" 15 | }, 16 | { 17 | "id": "cops:series:5", 18 | "title": "Série des Rougon-Macquart" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/graphql/tag.query.graphql: -------------------------------------------------------------------------------- 1 | query getTag($id: ID) { 2 | tag(id: $id) { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/tag.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getTag", 3 | "variables": { 4 | "id": 1 5 | }, 6 | "query": "query getTag($id: ID) {\n tag(id: $id) {\n id\n title\n }\n}" 7 | } -------------------------------------------------------------------------------- /tests/graphql/tag.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "tag": { 4 | "id": "cops:tags:1", 5 | "title": "Fiction" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /tests/graphql/tags.query.graphql: -------------------------------------------------------------------------------- 1 | query getTags { 2 | tags { 3 | id 4 | title 5 | } 6 | } -------------------------------------------------------------------------------- /tests/graphql/tags.query.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "getTags", 3 | "variables": [], 4 | "query": "query getTags {\n tags {\n id\n title\n }\n}" 5 | } -------------------------------------------------------------------------------- /tests/graphql/tags.result.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "tags": [ 4 | { 5 | "id": "cops:tags:4", 6 | "title": "Action & Adventure" 7 | }, 8 | { 9 | "id": "cops:tags:5", 10 | "title": "Fantasy" 11 | }, 12 | { 13 | "id": "cops:tags:1", 14 | "title": "Fiction" 15 | }, 16 | { 17 | "id": "cops:tags:10", 18 | "title": "Historical" 19 | }, 20 | { 21 | "id": "cops:tags:6", 22 | "title": "Juvenile" 23 | }, 24 | { 25 | "id": "cops:tags:12", 26 | "title": "Littérature" 27 | }, 28 | { 29 | "id": "cops:tags:3", 30 | "title": "Mystery & Detective" 31 | }, 32 | { 33 | "id": "cops:tags:9", 34 | "title": "Romance" 35 | }, 36 | { 37 | "id": "cops:tags:7", 38 | "title": "Science Fiction" 39 | }, 40 | { 41 | "id": "cops:tags:2", 42 | "title": "Short Stories" 43 | }, 44 | { 45 | "id": "cops:tags:8", 46 | "title": "War & Military" 47 | } 48 | ] 49 | } 50 | } -------------------------------------------------------------------------------- /tests/jing.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/jing.jar -------------------------------------------------------------------------------- /tests/restapi/check-more.html: -------------------------------------------------------------------------------- 1 | \SebLucas\Cops\Input\Request::__set_state(array( 2 | 'urlParams' => 3 | array ( 4 | '_route' => 'check-more', 5 | 'more' => 'more', 6 | ), 7 | 'serverParams' => 8 | array ( 9 | 'HTTP_REDACTED' => true, 10 | ), 11 | 'queryParams' => 12 | array ( 13 | ), 14 | 'postParams' => 15 | array ( 16 | ), 17 | 'cookieParams' => 18 | array ( 19 | ), 20 | 'fileParams' => 21 | array ( 22 | ), 23 | 'parsed' => true, 24 | 'content' => NULL, 25 | 'locale' => 'en', 26 | 'session' => NULL, 27 | 'invalid' => false, 28 | )) -------------------------------------------------------------------------------- /tests/restapi/page-authors-letter.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "1 author starting with C", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "1 author starting with C", 11 | "multipleDatabase": 0, 12 | "page": "authors_letter", 13 | "entries": [ 14 | { 15 | "class": "Authors", 16 | "title": "Lewis Carroll", 17 | "content": "2 books", 18 | "navlink": "vendor/bin/index.php/restapi/authors/3/Lewis_Carroll", 19 | "number": 2 20 | } 21 | ], 22 | "entriesCount": 1, 23 | "isPaginated": 0, 24 | "sorted": "sort", 25 | "sortedBy": "sort", 26 | "sortedDir": "asc", 27 | "containsBook": 0, 28 | "filterurl": false, 29 | "sortoptions": [], 30 | "filters": false, 31 | "abouturl": "vendor/bin/index.php/restapi/about", 32 | "customizeurl": "vendor/bin/index.php/restapi/customize", 33 | "homeurl": "vendor/bin/index.php", 34 | "parenturl": "vendor/bin/index.php/restapi/authors", 35 | "hierarchy": false, 36 | "extra": false, 37 | "assets": "vendor/bin/vendor/npm-asset", 38 | "download": false, 39 | "locale": "en", 40 | "template": "bootstrap2" 41 | } -------------------------------------------------------------------------------- /tests/restapi/page-customtype.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Type1", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Type1", 11 | "multipleDatabase": 0, 12 | "page": "customtype", 13 | "entries": [ 14 | { 15 | "class": "Type1", 16 | "title": "other", 17 | "content": "1 book", 18 | "navlink": "vendor/bin/index.php/restapi/custom/3/2", 19 | "number": 1 20 | }, 21 | { 22 | "class": "Type1", 23 | "title": "text", 24 | "content": "2 books", 25 | "navlink": "vendor/bin/index.php/restapi/custom/3/1", 26 | "number": 2 27 | }, 28 | { 29 | "class": "Type1", 30 | "title": "Not Set", 31 | "content": "13 books", 32 | "navlink": "vendor/bin/index.php/restapi/custom/3/not_set", 33 | "number": 13 34 | } 35 | ], 36 | "entriesCount": 3, 37 | "isPaginated": 0, 38 | "sorted": "value", 39 | "sortedBy": "value", 40 | "sortedDir": "asc", 41 | "containsBook": 0, 42 | "filterurl": false, 43 | "sortoptions": [], 44 | "filters": false, 45 | "abouturl": "vendor/bin/index.php/restapi/about", 46 | "customizeurl": "vendor/bin/index.php/restapi/customize", 47 | "homeurl": "vendor/bin/index.php", 48 | "parenturl": "vendor/bin/index.php", 49 | "hierarchy": false, 50 | "extra": false, 51 | "assets": "vendor/bin/vendor/npm-asset", 52 | "download": false, 53 | "locale": "en", 54 | "template": "bootstrap2" 55 | } -------------------------------------------------------------------------------- /tests/restapi/page-formats.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Formats", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Formats", 11 | "multipleDatabase": 0, 12 | "page": "formats", 13 | "entries": [ 14 | { 15 | "class": "Format", 16 | "title": "EPUB", 17 | "content": "16 books", 18 | "navlink": "vendor/bin/index.php/restapi/formats/EPUB", 19 | "number": 16 20 | }, 21 | { 22 | "class": "Format", 23 | "title": "MOBI", 24 | "content": "1 book", 25 | "navlink": "vendor/bin/index.php/restapi/formats/MOBI", 26 | "number": 1 27 | }, 28 | { 29 | "class": "Format", 30 | "title": "PDF", 31 | "content": "1 book", 32 | "navlink": "vendor/bin/index.php/restapi/formats/PDF", 33 | "number": 1 34 | }, 35 | { 36 | "class": "Format", 37 | "title": "No format", 38 | "content": "No books", 39 | "navlink": "vendor/bin/index.php/restapi/formats/0", 40 | "number": 0 41 | } 42 | ], 43 | "entriesCount": 4, 44 | "isPaginated": 0, 45 | "sorted": "format", 46 | "sortedBy": "format", 47 | "sortedDir": "desc", 48 | "containsBook": 0, 49 | "filterurl": false, 50 | "sortoptions": [], 51 | "filters": false, 52 | "abouturl": "vendor/bin/index.php/restapi/about", 53 | "customizeurl": "vendor/bin/index.php/restapi/customize", 54 | "homeurl": "vendor/bin/index.php", 55 | "parenturl": "vendor/bin/index.php", 56 | "hierarchy": false, 57 | "extra": false, 58 | "assets": "vendor/bin/vendor/npm-asset", 59 | "download": false, 60 | "locale": "en", 61 | "template": "bootstrap2" 62 | } -------------------------------------------------------------------------------- /tests/restapi/page-languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Languages", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Languages", 11 | "multipleDatabase": 0, 12 | "page": "languages", 13 | "entries": [ 14 | { 15 | "class": "Languages", 16 | "title": "English", 17 | "content": "14 books", 18 | "navlink": "vendor/bin/index.php/restapi/languages/1/English", 19 | "number": 14 20 | }, 21 | { 22 | "class": "Languages", 23 | "title": "French", 24 | "content": "1 book", 25 | "navlink": "vendor/bin/index.php/restapi/languages/2/French", 26 | "number": 1 27 | }, 28 | { 29 | "class": "Languages", 30 | "title": "Chinese", 31 | "content": "1 book", 32 | "navlink": "vendor/bin/index.php/restapi/languages/3/Chinese", 33 | "number": 1 34 | } 35 | ], 36 | "entriesCount": 3, 37 | "isPaginated": 0, 38 | "sorted": "lang_code", 39 | "sortedBy": "lang_code", 40 | "sortedDir": "asc", 41 | "containsBook": 0, 42 | "filterurl": false, 43 | "sortoptions": [], 44 | "filters": false, 45 | "abouturl": "vendor/bin/index.php/restapi/about", 46 | "customizeurl": "vendor/bin/index.php/restapi/customize", 47 | "homeurl": "vendor/bin/index.php", 48 | "parenturl": "vendor/bin/index.php", 49 | "hierarchy": false, 50 | "extra": false, 51 | "assets": "vendor/bin/vendor/npm-asset", 52 | "download": false, 53 | "locale": "en", 54 | "template": "bootstrap2" 55 | } -------------------------------------------------------------------------------- /tests/restapi/page-libraries.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Virtual libraries", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Virtual libraries", 11 | "multipleDatabase": 0, 12 | "page": "libraries", 13 | "entries": [ 14 | { 15 | "class": "VirtualLibrary", 16 | "title": "Action Fiction from this Century (TODO)", 17 | "content": "No books", 18 | "navlink": "vendor/bin/index.php/restapi/index?vl=1.Action_Fiction_from_this_Century_TODO", 19 | "number": 0 20 | }, 21 | { 22 | "class": "VirtualLibrary", 23 | "title": "Short Stories in English", 24 | "content": "No books", 25 | "navlink": "vendor/bin/index.php/restapi/index?vl=2.Short_Stories_in_English", 26 | "number": 0 27 | }, 28 | { 29 | "class": "VirtualLibrary", 30 | "title": "No virtual libraries", 31 | "content": "16 books", 32 | "navlink": "vendor/bin/index.php/restapi/index?vl=0.No_virtual_libraries", 33 | "number": 16 34 | } 35 | ], 36 | "entriesCount": 3, 37 | "isPaginated": 0, 38 | "sorted": "", 39 | "sortedBy": "", 40 | "sortedDir": "", 41 | "containsBook": 0, 42 | "filterurl": false, 43 | "sortoptions": [], 44 | "filters": false, 45 | "abouturl": "vendor/bin/index.php/restapi/about", 46 | "customizeurl": "vendor/bin/index.php/restapi/customize", 47 | "homeurl": "vendor/bin/index.php", 48 | "parenturl": "vendor/bin/index.php", 49 | "hierarchy": false, 50 | "extra": false, 51 | "assets": "vendor/bin/vendor/npm-asset", 52 | "download": false, 53 | "locale": "en", 54 | "template": "bootstrap2" 55 | } -------------------------------------------------------------------------------- /tests/restapi/page-publishers-letter.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "2 publishers starting with M", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "2 publishers starting with M", 11 | "multipleDatabase": 0, 12 | "page": "publishers_letter", 13 | "entries": [ 14 | { 15 | "class": "Publishers", 16 | "title": "Macmillan and Co. London", 17 | "content": "2 books", 18 | "navlink": "vendor/bin/index.php/restapi/publishers/2/Macmillan_and_Co_London", 19 | "number": 2 20 | }, 21 | { 22 | "class": "Publishers", 23 | "title": "Macmillan Publishers USA", 24 | "content": "1 book", 25 | "navlink": "vendor/bin/index.php/restapi/publishers/4/Macmillan_Publishers_USA", 26 | "number": 1 27 | } 28 | ], 29 | "entriesCount": 2, 30 | "isPaginated": 0, 31 | "sorted": "name", 32 | "sortedBy": "name", 33 | "sortedDir": "asc", 34 | "containsBook": 0, 35 | "filterurl": false, 36 | "sortoptions": [], 37 | "filters": false, 38 | "abouturl": "vendor/bin/index.php/restapi/about", 39 | "customizeurl": "vendor/bin/index.php/restapi/customize", 40 | "homeurl": "vendor/bin/index.php", 41 | "parenturl": "vendor/bin/index.php/restapi/publishers", 42 | "hierarchy": false, 43 | "extra": false, 44 | "assets": "vendor/bin/vendor/npm-asset", 45 | "download": false, 46 | "locale": "en", 47 | "template": "bootstrap2" 48 | } -------------------------------------------------------------------------------- /tests/restapi/page-query-scope.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Search result for *car* in authors", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Search result for *car* in authors", 11 | "multipleDatabase": 0, 12 | "page": "query", 13 | "entries": [ 14 | { 15 | "class": "Authors", 16 | "title": "Lewis Carroll", 17 | "content": "2 books", 18 | "navlink": "vendor/bin/index.php/restapi/authors/3/Lewis_Carroll", 19 | "number": 2 20 | } 21 | ], 22 | "entriesCount": 1, 23 | "isPaginated": 0, 24 | "sorted": "sort", 25 | "sortedBy": "sort", 26 | "sortedDir": "asc", 27 | "containsBook": 0, 28 | "filterurl": false, 29 | "sortoptions": [], 30 | "filters": false, 31 | "abouturl": "vendor/bin/index.php/restapi/about", 32 | "customizeurl": "vendor/bin/index.php/restapi/customize", 33 | "homeurl": "vendor/bin/index.php", 34 | "parenturl": "vendor/bin/index.php", 35 | "hierarchy": false, 36 | "extra": false, 37 | "assets": "vendor/bin/vendor/npm-asset", 38 | "download": false, 39 | "locale": "en", 40 | "template": "bootstrap2" 41 | } -------------------------------------------------------------------------------- /tests/restapi/page-query.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Search result for *car*", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Search result for *car*", 11 | "multipleDatabase": 0, 12 | "page": "query", 13 | "entries": [ 14 | { 15 | "class": "tt-header", 16 | "title": "Search result for *car* in books", 17 | "content": "1 book", 18 | "navlink": "vendor/bin/index.php/restapi/search/car/book", 19 | "number": 1 20 | }, 21 | { 22 | "class": "tt-header", 23 | "title": "Search result for *car* in authors", 24 | "content": "1 author", 25 | "navlink": "vendor/bin/index.php/restapi/search/car/author", 26 | "number": 1 27 | } 28 | ], 29 | "entriesCount": 2, 30 | "isPaginated": 0, 31 | "sorted": "sort", 32 | "sortedBy": "sort", 33 | "sortedDir": "asc", 34 | "containsBook": 0, 35 | "filterurl": false, 36 | "sortoptions": [], 37 | "filters": false, 38 | "abouturl": "vendor/bin/index.php/restapi/about", 39 | "customizeurl": "vendor/bin/index.php/restapi/customize", 40 | "homeurl": "vendor/bin/index.php", 41 | "parenturl": "vendor/bin/index.php", 42 | "hierarchy": false, 43 | "extra": false, 44 | "assets": "vendor/bin/vendor/npm-asset", 45 | "download": false, 46 | "locale": "en", 47 | "template": "bootstrap2" 48 | } -------------------------------------------------------------------------------- /tests/restapi/page-ratings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ratings", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Ratings", 11 | "multipleDatabase": 0, 12 | "page": "ratings", 13 | "entries": [ 14 | { 15 | "class": "Ratings", 16 | "title": "2 stars", 17 | "content": "1 book", 18 | "navlink": "vendor/bin/index.php/restapi/ratings/3/2_stars", 19 | "number": 1 20 | }, 21 | { 22 | "class": "Ratings", 23 | "title": "4 stars", 24 | "content": "2 books", 25 | "navlink": "vendor/bin/index.php/restapi/ratings/2/4_stars", 26 | "number": 2 27 | }, 28 | { 29 | "class": "Ratings", 30 | "title": "5 stars", 31 | "content": "4 books", 32 | "navlink": "vendor/bin/index.php/restapi/ratings/1/5_stars", 33 | "number": 4 34 | }, 35 | { 36 | "class": "Ratings", 37 | "title": "No star", 38 | "content": "9 books", 39 | "navlink": "vendor/bin/index.php/restapi/ratings/0/No_star", 40 | "number": 9 41 | } 42 | ], 43 | "entriesCount": 4, 44 | "isPaginated": 0, 45 | "sorted": "rating", 46 | "sortedBy": "rating", 47 | "sortedDir": "desc", 48 | "containsBook": 0, 49 | "filterurl": false, 50 | "sortoptions": [], 51 | "filters": false, 52 | "abouturl": "vendor/bin/index.php/restapi/about", 53 | "customizeurl": "vendor/bin/index.php/restapi/customize", 54 | "homeurl": "vendor/bin/index.php", 55 | "parenturl": "vendor/bin/index.php", 56 | "hierarchy": false, 57 | "extra": false, 58 | "assets": "vendor/bin/vendor/npm-asset", 59 | "download": false, 60 | "locale": "en", 61 | "template": "bootstrap2" 62 | } -------------------------------------------------------------------------------- /tests/restapi/page-series-letter.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "2 series starting with S", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "2 series starting with S", 11 | "multipleDatabase": 0, 12 | "page": "series_letter", 13 | "entries": [ 14 | { 15 | "class": "Series", 16 | "title": "Sherlock Holmes", 17 | "content": "7 books", 18 | "navlink": "vendor/bin/index.php/restapi/series/1/Sherlock_Holmes", 19 | "number": 7 20 | }, 21 | { 22 | "class": "Series", 23 | "title": "Série des Rougon-Macquart", 24 | "content": "1 book", 25 | "navlink": "vendor/bin/index.php/restapi/series/5/Serie_des_Rougon_Macquart", 26 | "number": 1 27 | } 28 | ], 29 | "entriesCount": 2, 30 | "isPaginated": 0, 31 | "sorted": "sort", 32 | "sortedBy": "sort", 33 | "sortedDir": "asc", 34 | "containsBook": 0, 35 | "filterurl": false, 36 | "sortoptions": [], 37 | "filters": false, 38 | "abouturl": "vendor/bin/index.php/restapi/about", 39 | "customizeurl": "vendor/bin/index.php/restapi/customize", 40 | "homeurl": "vendor/bin/index.php", 41 | "parenturl": "vendor/bin/index.php/restapi/series", 42 | "hierarchy": false, 43 | "extra": false, 44 | "assets": "vendor/bin/vendor/npm-asset", 45 | "download": false, 46 | "locale": "en", 47 | "template": "bootstrap2" 48 | } -------------------------------------------------------------------------------- /tests/restapi/page-series-letters.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Series", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "Series", 11 | "multipleDatabase": 0, 12 | "page": "series", 13 | "entries": [ 14 | { 15 | "class": "Letter", 16 | "title": "D", 17 | "content": "1 series", 18 | "navlink": "vendor/bin/index.php/restapi/series/letter/D", 19 | "number": 1 20 | }, 21 | { 22 | "class": "Letter", 23 | "title": "P", 24 | "content": "1 series", 25 | "navlink": "vendor/bin/index.php/restapi/series/letter/P", 26 | "number": 1 27 | }, 28 | { 29 | "class": "Letter", 30 | "title": "S", 31 | "content": "2 series", 32 | "navlink": "vendor/bin/index.php/restapi/series/letter/S", 33 | "number": 2 34 | }, 35 | { 36 | "class": "Series", 37 | "title": "No series", 38 | "content": "5 books", 39 | "navlink": "vendor/bin/index.php/restapi/series/0/No_series", 40 | "number": 5 41 | } 42 | ], 43 | "entriesCount": 4, 44 | "isPaginated": 0, 45 | "sorted": "groupid", 46 | "sortedBy": "groupid", 47 | "sortedDir": "asc", 48 | "containsBook": 0, 49 | "filterurl": false, 50 | "sortoptions": [], 51 | "filters": false, 52 | "abouturl": "vendor/bin/index.php/restapi/about", 53 | "customizeurl": "vendor/bin/index.php/restapi/customize", 54 | "homeurl": "vendor/bin/index.php", 55 | "parenturl": "vendor/bin/index.php", 56 | "hierarchy": false, 57 | "extra": false, 58 | "assets": "vendor/bin/vendor/npm-asset", 59 | "download": false, 60 | "locale": "en", 61 | "template": "bootstrap2" 62 | } -------------------------------------------------------------------------------- /tests/restapi/page-tags-letter.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "2 tags starting with F", 3 | "parentTitle": "", 4 | "baseurl": "vendor/bin/index.php", 5 | "isFilterPage": false, 6 | "databaseId": "", 7 | "databaseName": "", 8 | "libraryId": "", 9 | "libraryName": "COPS", 10 | "fullTitle": "2 tags starting with F", 11 | "multipleDatabase": 0, 12 | "page": "tags_letter", 13 | "entries": [ 14 | { 15 | "class": "Tags", 16 | "title": "Fantasy", 17 | "content": "2 books", 18 | "navlink": "vendor/bin/index.php/restapi/tags/5/Fantasy", 19 | "number": 2 20 | }, 21 | { 22 | "class": "Tags", 23 | "title": "Fiction", 24 | "content": "14 books", 25 | "navlink": "vendor/bin/index.php/restapi/tags/1/Fiction", 26 | "number": 14 27 | } 28 | ], 29 | "entriesCount": 2, 30 | "isPaginated": 0, 31 | "sorted": "name", 32 | "sortedBy": "name", 33 | "sortedDir": "asc", 34 | "containsBook": 0, 35 | "filterurl": false, 36 | "sortoptions": [], 37 | "filters": false, 38 | "abouturl": "vendor/bin/index.php/restapi/about", 39 | "customizeurl": "vendor/bin/index.php/restapi/customize", 40 | "homeurl": "vendor/bin/index.php", 41 | "parenturl": "vendor/bin/index.php/restapi/tags", 42 | "hierarchy": false, 43 | "extra": false, 44 | "assets": "vendor/bin/vendor/npm-asset", 45 | "download": false, 46 | "locale": "en", 47 | "template": "bootstrap2" 48 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-annotation.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "(17) Bookmark About #1", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "id": 1, 6 | "name": "About #1", 7 | "link": null, 8 | "count": null, 9 | "limitSelf": true, 10 | "book": 17, 11 | "format": "EPUB", 12 | "userType": "local", 13 | "user": "viewer", 14 | "timestamp": 1710158075.128396, 15 | "type": "bookmark", 16 | "data": { 17 | "title": "About #1", 18 | "pos_type": "epubcfi", 19 | "pos": "epubcfi(/6/2/4/2/6/2:38)", 20 | "timestamp": "2024-03-11T11:54:35.128396+00:00", 21 | "type": "bookmark" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-annotations-book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Annotations for 17", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "entries": [ 6 | { 7 | "class": "Annotation", 8 | "title": "(17) Bookmark About #1", 9 | "navlink": "vendor/bin/index.php/restapi/annotations/17/1" 10 | }, 11 | { 12 | "class": "Annotation", 13 | "title": "(17) Highlight 5HHGuoCOtpA-umaIbBuc0Q", 14 | "navlink": "vendor/bin/index.php/restapi/annotations/17/2" 15 | }, 16 | { 17 | "class": "Annotation", 18 | "title": "(17) Highlight KFGNXVB2Heb53ghsAjVqvQ", 19 | "navlink": "vendor/bin/index.php/restapi/annotations/17/3" 20 | }, 21 | { 22 | "class": "Annotation", 23 | "title": "(17) Highlight iuGTy-WIXWDinBuszppGwA", 24 | "navlink": "vendor/bin/index.php/restapi/annotations/17/4" 25 | }, 26 | { 27 | "class": "Annotation", 28 | "title": "(17) Highlight 1ZUnS8Bf4uZGH0hS9JOKGQ", 29 | "navlink": "vendor/bin/index.php/restapi/annotations/17/5" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-annotations.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Annotations", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "entries": [ 6 | { 7 | "class": "Annotations", 8 | "title": "Annotations for 17", 9 | "navlink": "vendor/bin/index.php/restapi/annotations/17", 10 | "number": 5 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-customtypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Custom Columns", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "entries": [ 5 | { 6 | "id": 1, 7 | "label": "type4", 8 | "name": "Type4", 9 | "datatype": "series", 10 | "display": "{}", 11 | "is_multiple": 0, 12 | "normalized": 1, 13 | "navlink": "vendor/bin/index.php/restapi/custom/1" 14 | }, 15 | { 16 | "id": 2, 17 | "label": "type2", 18 | "name": "Type2", 19 | "datatype": "text", 20 | "display": "{\"is_names\": false}", 21 | "is_multiple": 1, 22 | "normalized": 1, 23 | "navlink": "vendor/bin/index.php/restapi/custom/2" 24 | }, 25 | { 26 | "id": 3, 27 | "label": "type1", 28 | "name": "Type1", 29 | "datatype": "text", 30 | "display": "{\"use_decorations\": 0}", 31 | "is_multiple": 0, 32 | "normalized": 1, 33 | "navlink": "vendor/bin/index.php/restapi/custom/3" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-database-table.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Database Table books", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "entries": [], 5 | "error": "Invalid api key" 6 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-database.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Database Types", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "entries": [ 5 | { 6 | "class": "Metadata", 7 | "title": "Tables", 8 | "navlink": "vendor/bin/index.php/restapi/databases/0?type=table" 9 | }, 10 | { 11 | "class": "Metadata", 12 | "title": "Views", 13 | "navlink": "vendor/bin/index.php/restapi/databases/0?type=view" 14 | } 15 | ], 16 | "version": 26 17 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-databases.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Databases", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "entries": [ 5 | { 6 | "class": "Database", 7 | "title": "", 8 | "id": 0, 9 | "navlink": "vendor/bin/index.php/restapi/databases/0" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-metadata-element.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Metadata for 17", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "element": "dc:title", 6 | "entries": [ 7 | "Alice's Adventures in Wonderland" 8 | ] 9 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-note.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Note for authors #3", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "id": 1, 6 | "item": 3, 7 | "colname": "authors", 8 | "doc": "
\n

This is a note for Lewis Carroll

\n

Wikipedia

\n

", 9 | "mtime": 1708880895.654, 10 | "size": 227, 11 | "resources": { 12 | "xxh64:7c301792c52eebf7": { 13 | "hash": "xxh64:7c301792c52eebf7", 14 | "name": "330px-LewisCarrollSelfPhoto.jpg", 15 | "url": "vendor/bin/index.php/calres/0/xxh64/7c301792c52eebf7", 16 | "size": 37341, 17 | "mtime": 1737754398 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-notes-type-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Note for authors #3", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "id": 1, 6 | "item": 3, 7 | "colname": "authors", 8 | "doc": "
\n

This is a note for Lewis Carroll

\n

Wikipedia

\n

", 9 | "mtime": 1708880895.654, 10 | "size": 227, 11 | "resources": { 12 | "xxh64:7c301792c52eebf7": { 13 | "hash": "xxh64:7c301792c52eebf7", 14 | "name": "330px-LewisCarrollSelfPhoto.jpg", 15 | "url": "vendor/bin/index.php/calres/0/xxh64/7c301792c52eebf7", 16 | "size": 37341, 17 | "mtime": 1737754398 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-notes-type.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Notes for authors", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "entries": [ 6 | { 7 | "class": "Notes", 8 | "title": "Lewis Carroll", 9 | "id": 3, 10 | "navlink": "vendor/bin/index.php/restapi/notes/authors/3/Lewis_Carroll", 11 | "size": 227, 12 | "timestamp": 1708880895.654 13 | }, 14 | { 15 | "class": "Notes", 16 | "title": "H. G. Wells", 17 | "id": 4, 18 | "navlink": "vendor/bin/index.php/restapi/notes/authors/4/H_G_Wells", 19 | "size": 21741, 20 | "timestamp": 1741359641.1851127 21 | }, 22 | { 23 | "class": "Notes", 24 | "title": "Alexandre Dumas", 25 | "id": 5, 26 | "navlink": "vendor/bin/index.php/restapi/notes/authors/5/Alexandre_Dumas", 27 | "size": 28518, 28 | "timestamp": 1741359668.481976 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-notes.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Notes", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "entries": [ 6 | { 7 | "class": "Notes Type", 8 | "title": "authors", 9 | "navlink": "vendor/bin/index.php/restapi/notes/authors", 10 | "number": 3 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-path.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikespub-org/seblucas-cops/d143fceca81d22a544d2da50e713668f2fb1c1f2/tests/restapi/restapi-path.html -------------------------------------------------------------------------------- /tests/restapi/restapi-path.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /tests/restapi/restapi-preference.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Preference for virtual_libraries", 3 | "baseurl": "vendor/bin/index.php/restapi", 4 | "databaseId": null, 5 | "id": 56, 6 | "key": "virtual_libraries", 7 | "val": { 8 | "Action Fiction from this Century (TODO)": "(tags:\"Fiction\" and pubdate:>=2000) and tags:\"=Action & Adventure\"", 9 | "Short Stories in English": "tags:\"=Short Stories\" and languages:\"=eng\" and formats:\"=EPUB\"" 10 | } 11 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-user-details.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "Invalid username" 3 | } -------------------------------------------------------------------------------- /tests/restapi/restapi-user.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": "Invalid username" 3 | } -------------------------------------------------------------------------------- /tests/schema/opds/feed-metadata.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://drafts.opds.io/schema/feed-metadata.schema.json", 4 | "title": "OPDS Metadata", 5 | "type": "object", 6 | "properties": { 7 | "identifier": { 8 | "type": "string", 9 | "format": "uri" 10 | }, 11 | "@type": { 12 | "type": "string", 13 | "format": "uri" 14 | }, 15 | "title": { 16 | "type": [ 17 | "string", 18 | "array", 19 | "object" 20 | ] 21 | }, 22 | "subtitle": { 23 | "type": [ 24 | "string", 25 | "array", 26 | "object" 27 | ] 28 | }, 29 | "modified": { 30 | "type": "string", 31 | "format": "date-time" 32 | }, 33 | "description": { 34 | "type": "string" 35 | }, 36 | "itemsPerPage": { 37 | "type": "integer", 38 | "exclusiveMinimum": 0 39 | }, 40 | "currentPage": { 41 | "type": "integer", 42 | "exclusiveMinimum": 0 43 | }, 44 | "numberOfItems": { 45 | "type": "integer", 46 | "minimum": 0 47 | } 48 | }, 49 | "required": [ 50 | "title" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /tests/schema/readium/contributor-object.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://readium.org/webpub-manifest/schema/contributor-object.schema.json", 4 | "title": "Contributor Object", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "anyOf": [ 9 | { 10 | "type": "string" 11 | }, 12 | { 13 | "description": "The language in a language map must be a valid BCP 47 tag.", 14 | "type": "object", 15 | "patternProperties": { 16 | "^((?(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang))|((?([A-Za-z]{2,3}(-(?[A-Za-z]{3}(-[A-Za-z]{3}){0,2}))?)|[A-Za-z]{4}|[A-Za-z]{5,8})(-(?