diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..49c7ed05
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,16 @@
+### .env.example
+### The following environment variables are useful for Unit Test websocket server
+### In a local development environment, copy this file to .env.development to ensure that Unit Test websocket server will run locally
+### and will use the local API instance to run tests.
+### Set the same value for WS_PORT in the Unit Test frontend project folder.
+### For more information, see https://github.com/Liturgical-Calendar/LiturgicalCalendarAPI/blob/development/LitCalTestServer.php
+
+WS_PORT=8080
+API_PROTOCOL=http
+API_HOST=localhost
+API_PORT=8000 # will not determine on which port the API will launch, only on which port the Unit Test server will look for the API
+# When production, the API will add `/api/{version}` to the path
+# where {version} is the version of the API that is automatically detected
+# based on the current folder name (dev, v3, v4...).
+# When development, no path will be added.
+APP_ENV=development # development | production
diff --git a/.gitignore b/.gitignore
index 49b97714..ee52a0b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ ValidateTestsAgainstSchema.php
.envrc
debuginfo.php
testYaml.php
+.env
+.env.*
+!.env.example
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index b147c72b..9b716c76 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -44,6 +44,15 @@
"dependsOn": [
"launch-browser"
]
+ },
+ {
+ "label": "litcal-tests-websockets",
+ "type": "shell",
+ "command": "php LitCalTestServer.php",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
}
]
}
diff --git a/LitCalTestServer.php b/LitCalTestServer.php
index 26e948b5..ff98013a 100644
--- a/LitCalTestServer.php
+++ b/LitCalTestServer.php
@@ -10,9 +10,25 @@
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use LiturgicalCalendar\Api\Health;
+use Dotenv\Dotenv;
-$apiVersion = basename(__DIR__);
-define('API_BASE_PATH', "https://litcal.johnromanodorazio.com/api/{$apiVersion}");
+$dotenv = Dotenv::createImmutable(__DIR__, ['.env', '.env.local', '.env.development', '.env.production'], false);
+$dotenv->ifPresent(['API_PROTOCOL', 'API_HOST'])->notEmpty();
+$dotenv->ifPresent(['API_PORT'])->isInteger();
+$dotenv->ifPresent(['APP_ENV'])->notEmpty()->allowedValues(['development', 'production']);
+$dotenv->ifPresent(['WS_PROTOCOL', 'WS_HOST'])->notEmpty();
+$dotenv->ifPresent(['WS_PORT'])->isInteger();
+$dotenv->safeLoad();
+$API_PROTOCOL = $_ENV['API_PROTOCOL'] ?? 'https';
+$API_HOST = $_ENV['API_HOST'] ?? 'litcal.johnromanodorazio.com';
+$API_PORT = $_ENV['API_PORT'] ?? 443;
+
+if (isset($_ENV['APP_ENV']) && $_ENV['APP_ENV'] === 'development') {
+ define('API_BASE_PATH', "{$API_PROTOCOL}://{$API_HOST}:{$API_PORT}");
+} else {
+ $apiVersion = basename(__DIR__);
+ define('API_BASE_PATH', "{$API_PROTOCOL}://{$API_HOST}/api/{$apiVersion}");
+}
$server = IoServer::factory(
new HttpServer(
@@ -20,7 +36,7 @@
new Health()
)
),
- 8080
+ $_ENV['WS_PORT'] ?? 8080
);
$server->run();
diff --git a/README.md b/README.md
index fb834cfc..20e0131f 100644
--- a/README.md
+++ b/README.md
@@ -42,8 +42,8 @@ Some characteristics of this API:
There are a few proof of concept example applications for usage of the API at https://litcal.johnromanodorazio.com/usage.php, which demonstrate generating an HTML representation of the Liturgical Calendar.
* The [first example](https://litcal.johnromanodorazio.com/examples/php/) uses cURL in PHP to make a request to the endpoint and handle the results.
-* The [second example](https://litcal.johnromanodorazio.com/examples/javascript/) uses AJAX in Javascript to make the request to the endpoint and handle the results.
-* The [third example](https://litcal.johnromanodorazio.com/examples/fullcalendar/examples/month-view.html) makes use of the [FullCalendar javascript framework](https://github.com/fullcalendar/fullcalendar) to display the results from the AJAX request in a nicely formatted calendar view.
+* The [second example](https://litcal.johnromanodorazio.com/examples/javascript/) uses `fetch` in Javascript to make the request to the endpoint and handle the results.
+* The [third example](https://litcal.johnromanodorazio.com/examples/fullcalendar/examples/month-view.html) makes use of the [FullCalendar javascript framework](https://github.com/fullcalendar/fullcalendar) to display the results from the `fetch` request in a nicely formatted calendar view.
* The [fourth example](https://litcal.johnromanodorazio.com/examples/fullcalendar/examples/messages.html) is the same as the third except that it outputs the Messages first and the [FullCalendar](https://github.com/fullcalendar/fullcalendar) calendar view after.
All of these examples request `JSON` as the data exchange format generated by the endpoint. Any application could use the endpoint in a similar manner: an Android App, a plugin for a Desktop Publishing App...
@@ -59,6 +59,13 @@ _(See [usage.php#calSubscription](https://litcal.johnromanodorazio.com/usage.php
# Testing locally
+System requirements:
+* PHP >= 8.1
+* PHP modules installed and enabled: `intl` * `zip` * `gettext` * `calendar` * `yaml`
+* System language packs for all the supported languages
+
+## Using PHP's builtin server
+
To test the API locally, you can use PHP's builtin server. However, you will need to spawn at least a couple of workers, since some routes will make a request internally to another route. For example, a request to the `/calendar` route will make a request internally to the `/calendars` route.
Spawn at least two workers:
@@ -66,8 +73,20 @@ Spawn at least two workers:
PHP_CLI_SERVER_WORKERS=2 php -S localhost:8000
```
+## Using a docker container
+
For convenience when using VSCode, a `tasks.json` has been defined so that you can simply type CTRL+SHIFT+B (CMD+SHIFT+B on MacOS) to start the PHP builtin server and open the browser.
+To further simplify your setup, without having to worry about getting all the system requirements in place, you can also launch the API in a docker container using the repo `Dockerfile`:
+
+```bash
+# If you haven't cloned the repo locally, you can build directly from the remote repo (replace `{branch}` with the branch or tag from which you want to build):
+docker build -t liturgy-api:{branch} https://github.com/Liturgical-Calendar/LiturgicalCalendarAPI.git#{branch}
+# If instead you have cloned the repo locally, you can build from the local repo (replace `{branch}` with the branch or tag that you have checked out locally):
+docker build -t liturgy-api:{branch} .
+docker run -p 8000:8000 -d liturgy-api:{branch}
+```
+
# Translations
diff --git a/composer.json b/composer.json
index b2a6b7e3..3d84a5e3 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,8 @@
"php": ">=8.1",
"swaggest/json-schema": "~0.12",
"cboden/ratchet": "~0.4",
- "sabre/vobject": "^4.5.1"
+ "sabre/vobject": "^4.5.1",
+ "vlucas/phpdotenv": "^5.6"
},
"require-dev": {
"squizlabs/php_codesniffer": "*"
diff --git a/composer.lock b/composer.lock
index 00fbfe6e..5c4ef388 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "69e62a87a18e3801a1cc4e8746f9e117",
+ "content-hash": "0c3aa268dce27d9cedc97a8d8fa860c2",
"packages": [
{
"name": "cboden/ratchet",
@@ -116,6 +116,68 @@
},
"time": "2023-08-08T05:53:35+00:00"
},
+ {
+ "name": "graham-campbell/result-type",
+ "version": "v1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
+ "reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
+ "reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\ResultType\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "An Implementation Of The Result Type",
+ "keywords": [
+ "Graham Campbell",
+ "GrahamCampbell",
+ "Result Type",
+ "Result-Type",
+ "result"
+ ],
+ "support": {
+ "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:45:45+00:00"
+ },
{
"name": "guzzlehttp/psr7",
"version": "2.7.0",
@@ -280,6 +342,81 @@
},
"time": "2016-09-17T00:15:18+00:00"
},
+ {
+ "name": "phpoption/phpoption",
+ "version": "1.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/schmittjoh/php-option.git",
+ "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
+ "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpOption\\": "src/PhpOption/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Johannes M. Schmitt",
+ "email": "schmittjoh@gmail.com",
+ "homepage": "https://github.com/schmittjoh"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "Option Type for PHP",
+ "keywords": [
+ "language",
+ "option",
+ "php",
+ "type"
+ ],
+ "support": {
+ "issues": "https://github.com/schmittjoh/php-option/issues",
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:41:07+00:00"
+ },
{
"name": "psr/http-factory",
"version": "1.1.0",
@@ -1286,12 +1423,12 @@
},
"type": "library",
"extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
"branch-alias": {
"dev-main": "3.5-dev"
- },
- "thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -1411,6 +1548,85 @@
],
"time": "2024-11-13T18:58:10+00:00"
},
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "symfony/polyfill-mbstring",
"version": "v1.31.0",
@@ -1491,6 +1707,86 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "symfony/polyfill-php83",
"version": "v1.31.0",
@@ -1511,8 +1807,8 @@
"type": "library",
"extra": {
"thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
}
},
"autoload": {
@@ -1649,6 +1945,90 @@
}
],
"time": "2024-11-13T15:31:34+00:00"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v5.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
+ "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "graham-campbell/result-type": "^1.1.3",
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.3",
+ "symfony/polyfill-ctype": "^1.24",
+ "symfony/polyfill-mbstring": "^1.24",
+ "symfony/polyfill-php80": "^1.24"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-filter": "*",
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the boolean validator."
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "5.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dotenv\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com",
+ "homepage": "https://github.com/vlucas"
+ }
+ ],
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "support": {
+ "issues": "https://github.com/vlucas/phpdotenv/issues",
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:52:34+00:00"
}
],
"packages-dev": [
@@ -1735,12 +2115,12 @@
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": [],
+ "stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.1"
},
- "platform-dev": [],
+ "platform-dev": {},
"plugin-api-version": "2.6.0"
}
diff --git a/jsondata/schemas/DiocesanCalendar.json b/jsondata/schemas/DiocesanCalendar.json
index 2aa296c7..3aa431e8 100644
--- a/jsondata/schemas/DiocesanCalendar.json
+++ b/jsondata/schemas/DiocesanCalendar.json
@@ -35,7 +35,8 @@
"type": "string"
},
"group": {
- "type": "string"}
+ "type": "string"
+ }
},
"required": [
"diocese_id",
diff --git a/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/Calgary.json b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/Calgary.json
new file mode 100644
index 00000000..b8501304
--- /dev/null
+++ b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/Calgary.json
@@ -0,0 +1,32 @@
+{
+ "litcal": [
+ {
+ "festivity": {
+ "event_key": "DedicationofSaintMarysCathedral",
+ "color": [
+ "white"
+ ],
+ "grade": 6,
+ "common": [
+ "Dedication of a Church"
+ ],
+ "day": 11,
+ "month": 12
+ },
+ "metadata": {
+ "since_year": 1970,
+ "form_rownum": 1
+ }
+ }
+ ],
+ "metadata": {
+ "locales": [
+ "fr_CA",
+ "en_CA"
+ ],
+ "nation": "CA",
+ "diocese_id": "calgar_ca",
+ "diocese_name": "Calgary",
+ "timezone": "America\/Edmonton"
+ }
+}
diff --git a/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/en_CA.json b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/en_CA.json
new file mode 100644
index 00000000..86163d19
--- /dev/null
+++ b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/en_CA.json
@@ -0,0 +1,3 @@
+{
+ "DedicationofSaintMarysCathedral": "Dedication of Saint Mary's Cathedral"
+}
diff --git a/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/fr_CA.json b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/fr_CA.json
new file mode 100644
index 00000000..d82c00b3
--- /dev/null
+++ b/jsondata/sourcedata/calendars/dioceses/CA/calgar_ca/i18n/fr_CA.json
@@ -0,0 +1,3 @@
+{
+ "DedicationofSaintMarysCathedral": ""
+}
diff --git a/src/Enum/JsonData.php b/src/Enum/JsonData.php
index 49d38e14..529d89df 100644
--- a/src/Enum/JsonData.php
+++ b/src/Enum/JsonData.php
@@ -158,4 +158,10 @@ class JsonData
* Evaluates to 'jsondata/sourcedata/calendars/dioceses/{nation}/{diocese}/i18n/{locale}.json'.
*/
public const DIOCESAN_CALENDARS_I18N_FILE = JsonData::DIOCESAN_CALENDARS_I18N_FOLDER . '/{locale}.json';
+
+ /**
+ * The file containing the data for the world dioceses of the Latin Rite.
+ * Evaluates to 'jsondata/world_dioceses.json'.
+ */
+ public const WORLD_DIOCESES_LATIN_RITE = JsonData::FOLDER . '/world_dioceses.json';
}
diff --git a/src/Params/RegionalDataParams.php b/src/Params/RegionalDataParams.php
index 6ef85335..a6d159ff 100644
--- a/src/Params/RegionalDataParams.php
+++ b/src/Params/RegionalDataParams.php
@@ -39,6 +39,7 @@ class RegionalDataParams
public ?string $key = null;
public ?string $locale = null;
public ?object $payload = null;
+ public ?string $i18nRequest = null;
public function __construct()
{
@@ -127,7 +128,11 @@ private function checkNationalCalendarConditions(object $data): string
$validLangs = $currentNation[0]->locales;
if (property_exists($data, 'locale')) {
$data->locale = \Locale::canonicalize($data->locale);
- if (in_array($data->locale, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests that don't need to validate against existing locales
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($data->locale, $validLangs, true)
+ ) {
$this->locale = $data->locale;
} else {
$message = "Invalid value {$data->locale} for param `locale`, valid values for nation {$data->key} are: "
@@ -136,7 +141,11 @@ private function checkNationalCalendarConditions(object $data): string
}
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$value = \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
- if (in_array($value, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests that don't need to validate against existing locales
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($value, $validLangs, true)
+ ) {
$this->locale = $value;
} else {
$message = "Invalid value {$value} for Accept-Language header, valid values for nation {$data->key} are: "
@@ -182,11 +191,17 @@ private function checkDiocesanCalendarConditions(object $data): string
);
}
- $currentDiocese = array_values(array_filter($this->calendars->diocesan_calendars, fn ($el) => $el->calendar_id === $data->key))[0];
- $validLangs = $currentDiocese->locales;
+ if (RegionalData::$Core->getRequestMethod() !== RequestMethod::PUT) {
+ $currentDiocese = array_values(array_filter($this->calendars->diocesan_calendars, fn ($el) => $el->calendar_id === $data->key))[0];
+ $validLangs = $currentDiocese->locales;
+ }
if (property_exists($data, 'locale')) {
$data->locale = \Locale::canonicalize($data->locale);
- if (in_array($data->locale, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests that don't need to validate against existing locales
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($data->locale, $validLangs, true)
+ ) {
$this->locale = $data->locale;
} else {
$message = "Invalid value {$data->locale} for param `locale`, valid values for {$currentDiocese->diocese} are: "
@@ -195,7 +210,11 @@ private function checkDiocesanCalendarConditions(object $data): string
}
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$value = \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
- if (in_array($value, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests which don't require a check against valid langs
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($value, $validLangs, true) // otherwise check against valid langs
+ ) {
$this->locale = $value;
} else {
$message = "Invalid value {$value} for Accept-Language header, valid values for {$currentDiocese->diocese} are: "
@@ -203,8 +222,7 @@ private function checkDiocesanCalendarConditions(object $data): string
RegionalData::produceErrorResponse(StatusCode::BAD_REQUEST, $message);
}
} else {
- // if no locale was requested, just use the first valid locale
- $this->locale = $validLangs[0];
+ RegionalData::produceErrorResponse(StatusCode::BAD_REQUEST, "`locale` param or `Accept-Language` header required for Diocesan calendar data when request method is PUT");
}
return $data->key;
@@ -230,7 +248,7 @@ private function checkDiocesanCalendarConditions(object $data): string
private function checkWiderRegionCalendarConditions(object $data)
{
if (
- false === in_array($data->key, $this->calendars->wider_regions_keys)
+ false === in_array($data->key, $this->calendars->wider_regions_keys, true)
&& RegionalData::$Core->getRequestMethod() !== RequestMethod::PUT
) {
$validVals = implode(', ', $this->calendars->wider_regions_keys);
@@ -243,7 +261,11 @@ private function checkWiderRegionCalendarConditions(object $data)
$validLangs = $currentWiderRegion->locales;
if (property_exists($data, 'locale')) {
$data->locale = \Locale::canonicalize($data->locale);
- if (in_array($data->locale, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests that don't need to validate against existing locales
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($data->locale, $validLangs, true)
+ ) {
$this->locale = $data->locale;
} else {
$message = "Invalid value {$data->locale} for param `locale`, valid values for wider region {$currentWiderRegion->name} are: "
@@ -252,7 +274,11 @@ private function checkWiderRegionCalendarConditions(object $data)
}
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$value = \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
- if (in_array($value, $validLangs)) {
+ if (
+ RegionalData::$Core->getRequestMethod() === RequestMethod::PUT // short circuit for PUT requests that don't need to validate against existing locales
+ || null !== $this->i18nRequest // short circuit for i18n requests
+ || in_array($value, $validLangs, true)
+ ) {
$this->locale = $value;
} else {
$message = "Invalid value {$value} for Accept-Language header, valid values for wider region {$currentWiderRegion->name} are: "
@@ -296,11 +322,10 @@ private function validatePayload(object $payload): bool
case 'PUT':
if (
false === property_exists($payload, 'litcal')
- || false === property_exists($payload, 'diocese')
- || false === property_exists($payload, 'nation')
+ || false === property_exists($payload, 'metadata')
|| false === property_exists($payload->metadata, 'locales')
) {
- $message = "Cannot create Diocesan calendar data when the payload does not have required properties `litcal`, `diocese` or `nation`. Payload was:\n" . json_encode($payload, JSON_PRETTY_PRINT);
+ $message = "Cannot create Diocesan calendar data when the payload does not have required properties `litcal`, `metadata`, or `metadata.locales`. Payload was:\n" . json_encode($payload, JSON_PRETTY_PRINT);
RegionalData::produceErrorResponse(StatusCode::BAD_REQUEST, $message);
}
break;
@@ -342,10 +367,16 @@ public function setData(object $data): bool
if (false === in_array($data->category, self::EXPECTED_CATEGORIES)) {
RegionalData::produceErrorResponse(
StatusCode::BAD_REQUEST,
- "Unexpected value '{$data->category}' for param `category`, acceptable values are: " . implode(', ', array_values(self::EXPECTED_CATEGORIES))
+ "Unexpected value '{$data->category}' for param `category`, acceptable values are: " . implode(', ', array_keys(self::EXPECTED_CATEGORIES))
);
}
+ if (in_array(RegionalData::$Core->getRequestMethod(), [RequestMethod::GET, RequestMethod::POST], true)) {
+ if (property_exists($data, 'i18n')) {
+ $this->i18nRequest = $data->i18n;
+ }
+ }
+
$this->category = $data->category;
switch ($data->category) {
case 'NATIONALCALENDAR':
@@ -361,7 +392,7 @@ public function setData(object $data): bool
$this->key = null;
}
- if (in_array(RegionalData::$Core->getRequestMethod(), [RequestMethod::PUT,RequestMethod::PATCH], true)) {
+ if (in_array(RegionalData::$Core->getRequestMethod(), [RequestMethod::PUT, RequestMethod::PATCH], true)) {
if (false === property_exists($data, 'payload') || false === $data->payload instanceof \stdClass) {
RegionalData::produceErrorResponse(StatusCode::BAD_REQUEST, "Cannot create or update Calendar data without a payload");
}
diff --git a/src/Paths/RegionalData.php b/src/Paths/RegionalData.php
index 9bf8c2e6..9f752faf 100644
--- a/src/Paths/RegionalData.php
+++ b/src/Paths/RegionalData.php
@@ -13,6 +13,7 @@
use LiturgicalCalendar\Api\Enum\LitSchema;
use LiturgicalCalendar\Api\Enum\RequestContentType;
use LiturgicalCalendar\Api\Params\RegionalDataParams;
+use PHP_CodeSniffer\Tokenizers\JS;
/**
* RegionalData
@@ -94,6 +95,8 @@ private function handleRequestMethod()
*/
private function getRegionalCalendar(): void
{
+ $i18nDataFile = null;
+ $calendarDataFile = null;
switch ($this->params->category) {
case "DIOCESANCALENDAR":
$dioceseEntry = array_values(array_filter($this->CalendarsMetadata->diocesan_calendars, function ($el) {
@@ -102,21 +105,44 @@ private function getRegionalCalendar(): void
if (empty($dioceseEntry)) {
self::produceErrorResponse(StatusCode::NOT_FOUND, "The requested resource {$this->params->key} was not found in the index");
}
- $calendarDataFile = strtr(JsonData::DIOCESAN_CALENDARS_FILE, [
- '{nation}' => $dioceseEntry[0]->nation,
- '{diocese}' => $this->params->key,
- '{diocese_name}' => $dioceseEntry[0]->diocese
- ]);
+
+ if (property_exists($this->params, 'i18nRequest') && null !== $this->params->i18nRequest) {
+ $i18nDataFile = strtr(JsonData::DIOCESAN_CALENDARS_I18N_FILE, [
+ '{nation}' => $dioceseEntry[0]->nation,
+ '{diocese}' => $this->params->key,
+ '{locale}' => $this->params->i18nRequest
+ ]);
+ } else {
+ $calendarDataFile = strtr(JsonData::DIOCESAN_CALENDARS_FILE, [
+ '{nation}' => $dioceseEntry[0]->nation,
+ '{diocese}' => $this->params->key,
+ '{diocese_name}' => $dioceseEntry[0]->diocese
+ ]);
+ }
break;
case "WIDERREGIONCALENDAR":
- $calendarDataFile = strtr(JsonData::WIDER_REGIONS_FILE, [
- '{wider_region}' => $this->params->key
- ]);
+ if (property_exists($this->params, 'i18nRequest') && null !== $this->params->i18nRequest) {
+ $i18nDataFile = strtr(JsonData::WIDER_REGIONS_I18N_FILE, [
+ '{wider_region}' => $this->params->key,
+ '{locale}' => $this->params->i18nRequest
+ ]);
+ } else {
+ $calendarDataFile = strtr(JsonData::WIDER_REGIONS_FILE, [
+ '{wider_region}' => $this->params->key
+ ]);
+ }
break;
case "NATIONALCALENDAR":
- $calendarDataFile = strtr(JsonData::NATIONAL_CALENDARS_FILE, [
- '{nation}' => $this->params->key
- ]);
+ if (property_exists($this->params, 'i18nRequest') && null !== $this->params->i18nRequest) {
+ $i18nDataFile = strtr(JsonData::NATIONAL_CALENDARS_I18N_FILE, [
+ '{nation}' => $this->params->key,
+ '{locale}' => $this->params->i18nRequest
+ ]);
+ } else {
+ $calendarDataFile = strtr(JsonData::NATIONAL_CALENDARS_FILE, [
+ '{nation}' => $this->params->key
+ ]);
+ }
break;
default:
self::produceErrorResponse(
@@ -126,11 +152,21 @@ private function getRegionalCalendar(): void
);
}
- if (file_exists($calendarDataFile)) {
+ // If a simple i18n data request was made, we only return the i18n data
+ if (null !== $i18nDataFile) {
+ if (file_exists($i18nDataFile)) {
+ self::produceResponse(file_get_contents($i18nDataFile));
+ } else {
+ self::produceErrorResponse(StatusCode::NOT_FOUND, "RegionalData::getRegionalCalendar: file $i18nDataFile does not exist");
+ }
+ }
+
+ // Else if a calendar data request was made, we return the calendar data with the requested locale
+ if (null !== $calendarDataFile && file_exists($calendarDataFile)) {
$CalendarData = json_decode(file_get_contents($calendarDataFile));
if (null === $this->params->locale) {
$this->params->locale = $CalendarData->metadata->locales[0];
- } elseif (false === in_array($this->params->locale, $CalendarData->metadata->locales)) {
+ } elseif (false === in_array($this->params->locale, $CalendarData->metadata->locales, true)) {
self::produceErrorResponse(
StatusCode::BAD_REQUEST,
"Invalid value `{$this->params->locale}` for param `locale`. Valid values for current requested Wider region calendar data `{$this->params->key}` are: "
@@ -167,13 +203,29 @@ private function getRegionalCalendar(): void
}
}
} else {
- self::produceErrorResponse(StatusCode::NOT_FOUND, "file $CalendarDataI18nFile does not exist");
+ self::produceErrorResponse(StatusCode::NOT_FOUND, "RegionalData::getRegionalCalendar: file $CalendarDataI18nFile does not exist");
}
self::produceResponse(json_encode($CalendarData));
} else {
- self::produceErrorResponse(StatusCode::NOT_FOUND, "file $calendarDataFile does not exist");
+ self::produceErrorResponse(StatusCode::NOT_FOUND, "RegionalData::getRegionalCalendar: file $calendarDataFile does not exist");
+ }
+ }
+
+ /**
+ private static function getCountryIsoByDioceseId($data, $targetDioceseId) {
+ foreach ($data as $country) {
+ foreach ($country['dioceses'] as $diocese) {
+ if ($diocese['diocese_id'] === $targetDioceseId) {
+ return [
+ 'country_iso' => $country['country_iso'],
+ 'diocese_name' => $diocese['diocese_name']
+ ];
+ }
+ }
}
+ return null; // Return null if no match is found
}
+ */
/**
* Handle PUT requests to create or update a regional calendar data resource.
@@ -190,56 +242,94 @@ private function getRegionalCalendar(): void
private function createRegionalCalendar(): void
{
$response = new \stdClass();
- $updateData = new \stdClass();
- switch ($this->params->category) {
- case 'DIOCESANCALENDAR':
- $nationType = gettype($this->params->payload->nation);
- $dioceseType = gettype($this->params->payload->diocese);
- if ($nationType !== 'string' || $dioceseType !== 'string') {
- self::produceErrorResponse(StatusCode::BAD_REQUEST, "Params `nation` and `key` in payload are expected to be of type string, instead `nation` was of type `{$nationType}` and `key` was of type `{$dioceseType}`");
- }
- $updateData->nation = strip_tags($this->params->payload->nation);
- $updateData->diocese = strip_tags($this->params->payload->diocese);
- if (false === $this->params->payload instanceof \stdClass) {
- $calType = gettype($this->params->payload);
- self::produceErrorResponse(StatusCode::BAD_REQUEST, "`caldata` param in payload expected to be serialized object, instead it was of type `{$calType}` after unserialization");
- }
- if (property_exists($this->params->payload, 'group')) {
- $groupType = gettype($this->params->payload->group);
- if ($groupType !== 'string') {
- self::produceErrorResponse(StatusCode::BAD_REQUEST, "Param `group` in payload is expected to be of type `string`, instead it was of type `{$groupType}`");
- }
- $updateData->group = strip_tags($this->params->payload->group);
- }
+ if (false === $this->params->payload instanceof \stdClass) {
+ $calType = gettype($this->params->payload);
+ self::produceErrorResponse(StatusCode::BAD_REQUEST, "`payload` param expected to be serialized object, instead it was of type `{$calType}` after unserialization");
+ }
- // make sure we have all the necessary folders in place
- if (!file_exists(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->nation)) {
- mkdir(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->nation, 0755, true);
- }
- if (!file_exists(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->nation . '/' . $this->params->payload->diocese)) {
- mkdir(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->nation . '/' . $this->params->payload->diocese, 0755, true);
- }
- $diocesanCalendarI18nFolder = strtr(JsonData::DIOCESAN_CALENDARS_I18N_FOLDER, [
- '{nation}' => $this->params->payload->nation,
- '{diocese}' => $this->params->payload->diocese
- ]);
- if (!file_exists($diocesanCalendarI18nFolder)) {
- mkdir($diocesanCalendarI18nFolder, 0755, true);
- }
+ $test = $this->validateDataAgainstSchema($this->params->payload, LitSchema::DIOCESAN);
+ if ($test === true) {
- $test = $this->validateDataAgainstSchema($this->params->payload, LitSchema::DIOCESAN);
- if ($test === true) {
- $calendarData = json_encode($this->params->payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
- file_put_contents(
- $updateData->path . "/{$updateData->diocese}.json",
- $calendarData . PHP_EOL
- );
- $response->success = "Calendar data created or updated for Diocese \"{$updateData->diocese}\" (Nation: \"$updateData->nation\")";
- self::produceResponse(json_encode($response));
- } else {
- self::produceErrorResponse(StatusCode::UNPROCESSABLE_CONTENT, $test);
- }
- break;
+ // make sure we have all the necessary folders in place
+ if (!file_exists(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->metadata->nation)) {
+ mkdir(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->metadata->nation, 0755, true);
+ }
+ if (!file_exists(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->metadata->nation . '/' . $this->params->payload->metadata->diocese_id)) {
+ mkdir(JsonData::DIOCESAN_CALENDARS_FOLDER . $this->params->payload->metadata->nation . '/' . $this->params->payload->metadata->diocese_id, 0755, true);
+ }
+ $diocesanCalendarI18nFolder = strtr(JsonData::DIOCESAN_CALENDARS_I18N_FOLDER, [
+ '{nation}' => $this->params->payload->metadata->nation,
+ '{diocese}' => $this->params->payload->metadata->diocese_id
+ ]);
+ if (!file_exists($diocesanCalendarI18nFolder)) {
+ mkdir($diocesanCalendarI18nFolder, 0755, true);
+ }
+
+ $diocesanCalendarFile = strtr(
+ JsonData::DIOCESAN_CALENDARS_FILE,
+ [
+ '{nation}' => $this->params->payload->metadata->nation,
+ '{diocese}' => $this->params->payload->metadata->diocese_id,
+ '{diocese_name}' => $this->params->payload->metadata->diocese_name
+ ]
+ );
+
+ $diocesanCalendarI18nFile = strtr(
+ JsonData::DIOCESAN_CALENDARS_I18N_FILE,
+ [
+ '{nation}' => $this->params->payload->metadata->nation,
+ '{diocese}' => $this->params->payload->metadata->diocese_id,
+ '{locale}' => $this->params->locale
+ ]
+ );
+
+ $litCalEventsI18n = array_reduce($this->params->payload->litcal, function ($carry, $item) {
+ $carry[$item->festivity->event_key] = $item->festivity->name;
+ unset($item->festivity->name);
+ return $carry;
+ }, []);
+
+ $litCalEventsI18nOtherLocales = array_reduce(array_keys($litCalEventsI18n), function ($carry, $key) {
+ $carry[$key] = '';
+ return $carry;
+ }, []);
+
+ $otherLocales = array_values(array_filter($this->params->payload->metadata->locales, function ($el) {
+ return $el !== $this->params->locale;
+ }));
+
+ $calendarData = json_encode($this->params->payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ file_put_contents(
+ $diocesanCalendarFile,
+ $calendarData . PHP_EOL
+ );
+
+ $calendarI18nData = json_encode($litCalEventsI18n, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ file_put_contents(
+ $diocesanCalendarI18nFile,
+ $calendarI18nData . PHP_EOL
+ );
+
+ $calendarI18nDataOtherLocales = json_encode($litCalEventsI18nOtherLocales, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+ foreach($otherLocales as $locale) {
+ $diocesanCalendarI18nFileOtherLocales = strtr(
+ JsonData::DIOCESAN_CALENDARS_I18N_FILE,
+ [
+ '{nation}' => $this->params->payload->metadata->nation,
+ '{diocese}' => $this->params->payload->metadata->diocese_id,
+ '{locale}' => $locale
+ ]
+ );
+ file_put_contents(
+ $diocesanCalendarI18nFileOtherLocales,
+ $calendarI18nDataOtherLocales . PHP_EOL
+ );
+ }
+
+ $response->success = "Calendar data created or updated for Diocese \"{$this->params->payload->metadata->diocese_name}\" (Nation: \"{$this->params->payload->metadata->nation}\")";
+ self::produceResponse(json_encode($response));
+ } else {
+ self::produceErrorResponse(StatusCode::UNPROCESSABLE_CONTENT, $test);
}
}
@@ -637,7 +727,7 @@ private static function retrievePayloadFromPostPutPatchRequest(object $data): ?o
$payload = (object)$_POST;
break;
default:
- if (in_array(self::$Core->getRequestMethod(), [RequestMethod::PUT, RequestMethod::PATCH])) {
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::PUT, RequestMethod::PATCH], true)) {
// the payload MUST be in the body of the request, either JSON encoded or YAML encoded
self::produceErrorResponse(
StatusCode::BAD_REQUEST,
@@ -668,13 +758,19 @@ private static function setDataFromPath(array $requestPathParts): object
$data->category = RegionalDataParams::EXPECTED_CATEGORIES[$requestPathParts[0]];
$data->key = $requestPathParts[1];
- if (in_array(self::$Core->getRequestMethod(), [RequestMethod::POST, RequestMethod::PUT, RequestMethod::PATCH])) {
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::GET, RequestMethod::POST], true)) {
+ if (isset($requestPathParts[2])) {
+ $data->i18n = $requestPathParts[2];
+ }
+ }
+
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::POST, RequestMethod::PUT, RequestMethod::PATCH], true)) {
$data = RegionalData::retrievePayloadFromPostPutPatchRequest($data);
- } elseif (
- self::$Core->getRequestMethod() === RequestMethod::GET
- && isset($_GET['locale'])
- ) {
- $data->locale = \Locale::canonicalize($_GET['locale']);
+ }
+ elseif (self::$Core->getRequestMethod() === RequestMethod::GET) {
+ if (isset($_GET['locale'])) {
+ $data->locale = \Locale::canonicalize($_GET['locale']);
+ }
}
return $data;
}
@@ -688,10 +784,18 @@ private static function setDataFromPath(array $requestPathParts): object
*/
private static function validateRequestPath(array $requestPathParts): void
{
- if (count($requestPathParts) !== 2) {
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::GET, RequestMethod::POST], true)) {
+ if (count($requestPathParts) < 2 || count($requestPathParts) > 3) {
+ self::produceErrorResponse(
+ StatusCode::BAD_REQUEST,
+ "Expected at least two and at most three path params for GET and POST requests, received " . count($requestPathParts)
+ );
+ }
+ }
+ else if (count($requestPathParts) !== 2) {
self::produceErrorResponse(
StatusCode::BAD_REQUEST,
- "Expected two and exactly two path params, received " . count($requestPathParts)
+ "Expected two and exactly two path params for PATCH and DELETE requests, received " . count($requestPathParts)
);
}
@@ -733,8 +837,14 @@ private function handleRequestParams(array $requestPathParts = []): void
default:
$data = (object)$_REQUEST;
}
- if (null === $data || !property_exists($data, 'payload')) {
- self::produceErrorResponse(StatusCode::BAD_REQUEST, "No payload received. Must receive payload in body of request, in JSON or YAML format, with properties `key` and `caldata`");
+ if (
+ null === $data
+ || !property_exists($data, 'payload')
+ || !property_exists($data->payload, 'litcal')
+ || !property_exists($data->payload, 'metadata')
+ || !property_exists($data->payload->metadata, 'diocese_id')
+ ) {
+ self::produceErrorResponse(StatusCode::BAD_REQUEST, "Invalid payload in request. Must receive payload in body of request, in JSON or YAML format, with properties `payload`, `payload.litcal`, `payload.metadata`, and `payload.metadata.diocese_id`");
}
if (false === count($requestPathParts)) {
self::produceErrorResponse(StatusCode::BAD_REQUEST, "No request path received. Must receive request path with path param `category`");
@@ -745,6 +855,7 @@ private function handleRequestParams(array $requestPathParts = []): void
break;
case 'diocese':
$data->category = 'DIOCESANCALENDAR';
+ $data->key = $data->payload->metadata->diocese_id;
break;
case 'widerregion':
$data->category = 'WIDERREGIONCALENDAR';
@@ -838,7 +949,7 @@ public static function produceErrorResponse(int $statusCode, string $description
*/
private static function produceResponse(string $jsonEncodedResponse): void
{
- if (in_array(self::$Core->getRequestMethod(), ['PUT','PATCH'])) {
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::PUT, RequestMethod::PATCH], true)) {
header($_SERVER[ "SERVER_PROTOCOL" ] . " 201 Created", true, 201);
}
switch (self::$Core->getResponseContentType()) {
@@ -869,13 +980,13 @@ private static function produceResponse(string $jsonEncodedResponse): void
public function init(array $requestPathParts = [])
{
self::$Core->init();
- if (self::$Core->getRequestMethod() === RequestMethod::GET || self::$Core->getRequestMethod() === RequestMethod::OPTIONS) {
+ if (in_array(self::$Core->getRequestMethod(), [RequestMethod::GET, RequestMethod::OPTIONS], true)) {
self::$Core->validateAcceptHeader(true);
} else {
self::$Core->validateAcceptHeader(false);
}
if (self::$Core->getRequestMethod() === RequestMethod::OPTIONS) {
- return;
+ die();
}
self::$Core->setResponseContentTypeHeader();
$this->handleRequestParams($requestPathParts);
diff --git a/src/Router.php b/src/Router.php
index 045ddbee..b0d90541 100644
--- a/src/Router.php
+++ b/src/Router.php
@@ -2,6 +2,7 @@
namespace LiturgicalCalendar\Api;
+use GuzzleHttp\Psr7\Request;
use LiturgicalCalendar\Api\Enum\RequestMethod;
use LiturgicalCalendar\Api\Enum\RequestContentType;
use LiturgicalCalendar\Api\Enum\AcceptHeader;
@@ -120,9 +121,12 @@ public static function route(): void
case 'calendar':
$LitCalEngine = new Calendar();
// Calendar::$Core will not exist until the Calendar class is instantiated!
- //Calendar::$Core->setAllowedOrigins(self::$allowedOrigins);
- Calendar::$Core->setAllowedRequestMethods([ RequestMethod::GET, RequestMethod::POST, RequestMethod::OPTIONS ]);
- Calendar::$Core->setAllowedRequestContentTypes([ RequestContentType::JSON, RequestContentType::FORMDATA ]);
+ Calendar::$Core->setAllowedRequestMethods([
+ RequestMethod::GET,
+ RequestMethod::POST,
+ RequestMethod::OPTIONS
+ ]);
+ Calendar::$Core->setAllowedRequestContentTypes([ RequestContentType::JSON, RequestContentType::YAML, RequestContentType::FORMDATA ]);
Calendar::$Core->setAllowedAcceptHeaders([ AcceptHeader::JSON, AcceptHeader::XML, AcceptHeader::ICS, AcceptHeader::YAML ]);
$LitCalEngine->setAllowedReturnTypes([ ReturnType::JSON, ReturnType::XML, ReturnType::ICS, ReturnType::YAML ]);
$LitCalEngine->setCacheDuration(CacheDuration::MONTH);
@@ -148,13 +152,17 @@ public static function route(): void
) {
Tests::$Core->setAllowedOrigins(self::$allowedOrigins);
}
- Tests::$Core->setAllowedRequestContentTypes([ RequestContentType::JSON ]);
+ Tests::$Core->setAllowedRequestContentTypes([ RequestContentType::JSON, RequestContentType::YAML ]);
Tests::$Core->setAllowedAcceptHeaders([ AcceptHeader::JSON, AcceptHeader::YAML ]);
Tests::handleRequest();
break;
case 'events':
$Events = new Events();
- Events::$Core->setAllowedRequestMethods([ RequestMethod::GET, RequestMethod::POST, RequestMethod::OPTIONS ]);
+ Events::$Core->setAllowedRequestMethods([
+ RequestMethod::GET,
+ RequestMethod::POST,
+ RequestMethod::OPTIONS
+ ]);
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
if (
in_array($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], [ RequestMethod::PUT, RequestMethod::PATCH, RequestMethod::DELETE ], true)
diff --git a/src/Utilities.php b/src/Utilities.php
index 8897b596..58ab917e 100644
--- a/src/Utilities.php
+++ b/src/Utilities.php
@@ -27,15 +27,22 @@ class Utilities
'feasts_keys',
'memorials',
'memorials_keys',
+ 'suppressed_events',
+ 'suppressed_events_keys',
+ 'reinstated_events',
+ 'reinstated_events_keys',
'request_headers',
'color',
'color_lcl',
'common'
];
private static string $LAST_ARRAY_KEY = '';
+ /**
+ * All snake_case keys are automatically transformed to their PascalCase equivalent.
+ * If any key needs a specific case transformation other than the automatic snake_case to PascalCase, add it to this array.
+ */
private const TRANSFORM_KEYS = [
- "litcal" => "LitCal",
- "has_vesper_ii" => "HasVesperII"
+ "litcal" => "LitCal"
];
public static string $HASH_REQUEST = '';
@@ -75,36 +82,41 @@ public static function convertArray2XML(array $data, ?\SimpleXMLElement &$xml):
//self::debugWrite( "proceeding to convert array value of <$key> to xml sequence..." );
self::convertArray2XML($value, $new_object);
} else {
- // XML elements cannot have numerical names, they must have text
+ // XML elements cannot have numerical names, they must have text
if (is_numeric($key)) {
- //self::debugWrite( "key <$key> is numerical, have to deal with this..." );
if (self::$LAST_ARRAY_KEY === 'messages') {
- //self::debugWrite( "key <$key> seems to belong to the Messages array: will create a corresponding element with attribute 'idx'" );
$el = $xml->addChild('Message', htmlspecialchars($value));
$el->addAttribute("idx", $key);
- } elseif (in_array(self::$LAST_ARRAY_KEY, ['solemnities_keys','feasts_keys','memorials_keys'])) {
+ } elseif (in_array(self::$LAST_ARRAY_KEY, ['solemnities_keys','feasts_keys','memorials_keys','suppressed_events_keys','reinstated_events_keys'])) {
$el = $xml->addChild('Key', $value);
$el->addAttribute("idx", $key);
} else {
- //self::debugWrite( "key <$key> does not seem to belong to the Messages array: will create a corresponding