diff --git a/.wp-env.json b/.wp-env.json index 52202242a..aeee73820 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -1,30 +1,35 @@ { - "phpVersion": "7.4", - "core": "Wordpress/Wordpress#6.5", - "plugins": [ - ".", - "https://downloads.wordpress.org/plugin/wp-crontrol.zip", - "https://downloads.wordpress.org/plugin/wordpress-importer.zip", - "https://downloads.wordpress.org/plugin/query-monitor.zip", - "https://downloads.wordpress.org/plugin/wp-mail-logging.zip", - "https://downloads.wordpress.org/plugin/jsm-show-post-meta.zip", - "https://downloads.wordpress.org/plugin/disable-welcome-messages-and-tips.zip" - ], - "port": 1000, - "testsPort": 1001, - "config": { - "WP_DEBUG": true, - "WP_DEBUG_LOG": true, - "WP_DEBUG_DISPLAY": false - }, - "themes": [ - "flegfleg/kasimir-theme" - ], - "env": { - "tests": { - "mappings": { - "wp-content/plugins/commonsbooking": "." - } - } - } + "phpVersion" : "7.4", + "core" : "Wordpress/Wordpress#6.5", + "plugins" : [ + ".", + "https://downloads.wordpress.org/plugin/wp-crontrol.zip", + "https://downloads.wordpress.org/plugin/wordpress-importer.zip", + "https://downloads.wordpress.org/plugin/query-monitor.zip", + "https://downloads.wordpress.org/plugin/wp-mail-logging.zip", + "https://downloads.wordpress.org/plugin/jsm-show-post-meta.zip", + "https://downloads.wordpress.org/plugin/disable-welcome-messages-and-tips.zip" + ], + "port" : 1000, + "testsPort" : 1001, + "config" : { + "WP_DEBUG" : true, + "WP_DEBUG_LOG" : true, + "WP_DEBUG_DISPLAY" : false + }, + "themes" : [ + "flegfleg/kasimir-theme" + ], + "env" : { + "tests" : { + "mappings" : { + "wp-content/plugins/commonsbooking" : "." + }, + "config" : { + "WP_DEBUG" : true, + "WP_DEBUG_LOG" : true, + "WP_DEBUG_DISPLAY" : false + } + } + } } diff --git a/bin/install-wp-cli.sh b/bin/install-wp-cli.sh deleted file mode 100755 index 799269577..000000000 --- a/bin/install-wp-cli.sh +++ /dev/null @@ -1,15 +0,0 @@ -# install-wp-cli.sh -# -# Felipe Elia and 10up contributors -# -# The following code is a derivative work of the code from the ElasticPress project, -# which is licensed GPLv2. This code therefore is also licensed under the terms -# of the GNU Public License, version 2.' - -#!/usr/bin/env bash - -echo "Installing WP-CLI in $1" - -./bin/wp-env-cli $1 curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -./bin/wp-env-cli $1 chmod +x wp-cli.phar -./bin/wp-env-cli $1 mv wp-cli.phar /usr/local/bin/wp diff --git a/bin/setup-cypress-env.sh b/bin/setup-cypress-env.sh index 659139a8d..ef78b8005 100755 --- a/bin/setup-cypress-env.sh +++ b/bin/setup-cypress-env.sh @@ -1,15 +1,8 @@ -# setup-cypress-env.sh -# Felipe Elia and 10up contributors -# -# The following code is a derivative work of the code from the ElasticPress project, -# which is licensed GPLv2. This code therefore is also licensed under the terms -# of the GNU Public License, version 2.' - #!/bin/bash # Install our example posts from a WP export file -./bin/wp-env-cli tests-wordpress "wp --allow-root import /var/www/html/wp-content/plugins/commonsbooking/tests/cypress/wordpress-files/content-example.xml --authors=create" +wp-env run tests-cli wp import /var/www/html/wp-content/plugins/commonsbooking/tests/cypress/wordpress-files/content-example.xml --authors=create # Switch to Kasimir theme -./bin/wp-env-cli tests-wordpress "wp --allow-root theme activate kasimir-theme" +wp-env run tests-cli wp theme activate kasimir-theme # Create subscriber with username "subscriber" and password "password" -./bin/wp-env-cli tests-wordpress "wp --allow-root user create subscriber sub@sub.de --role=subscriber --user_pass=password" \ No newline at end of file +wp-env run tests-cli wp user create subscriber sub@sub.de --role=subscriber --user_pass=password \ No newline at end of file diff --git a/bin/wp-env-cli b/bin/wp-env-cli deleted file mode 100755 index f5ff7e12f..000000000 --- a/bin/wp-env-cli +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node - -const path = require('path'); -const { spawn } = require('child_process'); -const { readConfig } = require('@wordpress/env/lib/config'); - -function spawnCommandDirectly({ container, command, dockerComposeConfigPath }) { - const composeCommand = [ - '-f', - dockerComposeConfigPath, - 'exec -T', - container, - ...command.split(' '), // The command will fail if passed as a complete string. - ]; - - return new Promise((resolve, reject) => { - const childProc = spawn('docker-compose', composeCommand, { - stdio: 'inherit', - shell: true, - }); - childProc.on('error', reject); - childProc.on('exit', (code) => { - // Code 130 is set if the user tries to exit with ctrl-c before using - // ctrl-d (so it is not an error which should fail the script.) - if (code === 0 || code === 130) { - resolve(); - } else { - reject(code); - } - }); - }); -} - -const run = async () => { - const configPath = path.resolve('.wp-env.json'); - const { dockerComposeConfigPath } = await readConfig(configPath); - - const container = process.argv[2]; - const command = process.argv.splice(3).join(' '); - - try { - await spawnCommandDirectly({ - container, - command, - dockerComposeConfigPath, - }); - } catch (errorCode) { - process.exit(errorCode); - } -}; - -run(); diff --git a/languages/commonsbooking-de_DE.po b/languages/commonsbooking-de_DE.po index d65e200ea..680bd4e89 100644 --- a/languages/commonsbooking-de_DE.po +++ b/languages/commonsbooking-de_DE.po @@ -1409,15 +1409,15 @@ msgstr "Artikel-Kategorie" msgid "Location Category" msgstr "Standort-Kategorie" -#: src/Repository/BookingCodes.php:304 +#: src/Repository/BookingCodes.php:328 msgid "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings." msgstr "Es konnten keine Buchungscodes erstellt werden, da keine Buchungscodes zur Auswahl standen. Bitte lege einige Buchungscodes in den CommonsBooking-Einstellungen fest." -#: src/Repository/BookingCodes.php:311 +#: src/Repository/BookingCodes.php:335 msgid "No booking codes could be created because the location of the timeframe could not be found." msgstr "Es konnten keine Buchungscodes erstellt werden, da der Standort nicht gefunden wurde." -#: src/Repository/BookingCodes.php:317 +#: src/Repository/BookingCodes.php:341 msgid "No booking codes could be created because the item of the timeframe could not be found." msgstr "Es konnten keine Buchungscodes erstellt werden, da der Artikel nicht gefunden wurde." diff --git a/languages/commonsbooking.pot b/languages/commonsbooking.pot index 832837217..9335a8c6a 100644 --- a/languages/commonsbooking.pot +++ b/languages/commonsbooking.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-06-05T11:41:25+00:00\n" +"POT-Creation-Date: 2024-06-17T20:26:25+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.10.0\n" "X-Domain: commonsbooking\n" @@ -1599,15 +1599,15 @@ msgstr "" msgid "CommonsBooking Bookings" msgstr "" -#: src/Repository/BookingCodes.php:304 +#: src/Repository/BookingCodes.php:328 msgid "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings." msgstr "" -#: src/Repository/BookingCodes.php:311 +#: src/Repository/BookingCodes.php:335 msgid "No booking codes could be created because the location of the timeframe could not be found." msgstr "" -#: src/Repository/BookingCodes.php:317 +#: src/Repository/BookingCodes.php:341 msgid "No booking codes could be created because the item of the timeframe could not be found." msgstr "" diff --git a/package-lock.json b/package-lock.json index 247f70469..41b52c16c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,18 +10,18 @@ "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@commonsbooking/frontend": "^0.1.0-beta.6", - "feiertagejs": "^1.4.0", + "feiertagejs": "^1.4.1", "leaflet": "^1.7.1", "leaflet-easybutton": "^2.4.0", "leaflet-spin": "^1.1.2", "leaflet.markercluster": "^1.5.0", "shufflejs": "^6.1.1", "spin.js": "^2.3.2", - "vue": "^3.4.27" + "vue": "^3.4.29" }, "devDependencies": { "@babel/preset-env": "^7.23.2", - "@wordpress/env": "^5.0.0", + "@wordpress/env": "^10.0.0", "commons-api": "git+https://github.com/wielebenwir/commons-api.git", "cypress": "^13.9.0", "editorconfig": "^2.0.0", @@ -471,9 +471,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2021,12 +2021,12 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", - "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", + "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/shared": "3.4.27", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.29", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" @@ -2044,24 +2044,24 @@ } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", - "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", + "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", "dependencies": { - "@vue/compiler-core": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-core": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", - "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", - "dependencies": { - "@babel/parser": "^7.24.4", - "@vue/compiler-core": "3.4.27", - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", + "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", + "dependencies": { + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.29", + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -2069,57 +2069,58 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", - "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", + "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-dom": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/reactivity": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", - "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", + "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", "dependencies": { - "@vue/shared": "3.4.27" + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", - "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", + "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", "dependencies": { - "@vue/reactivity": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/reactivity": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", - "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", + "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", "dependencies": { - "@vue/runtime-core": "3.4.27", - "@vue/shared": "3.4.27", + "@vue/reactivity": "3.4.29", + "@vue/runtime-core": "3.4.29", + "@vue/shared": "3.4.29", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", - "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", + "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", "dependencies": { - "@vue/compiler-ssr": "3.4.27", - "@vue/shared": "3.4.27" + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { - "vue": "3.4.27" + "vue": "3.4.29" } }, "node_modules/@vue/shared": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", - "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==" }, "node_modules/@vueuse/core": { "version": "10.6.1", @@ -2205,14 +2206,14 @@ } }, "node_modules/@wordpress/env": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-5.16.0.tgz", - "integrity": "sha512-zx6UO8PuJBrQ34cfeedK1HlGHLFaj7oWzTo9tTt+noB79Ttqc4+a0lYwDqBLLJhlHU+cWgcyOP2lB6TboXH0xA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.0.0.tgz", + "integrity": "sha512-GpbYT7P61U6PWOqpyPmTBGLswBoyOCUtGF2/0qCsbN7vC8oekm+iNpYT2JS0ETSC1/8esxxKlYVvLxXwTF9MPg==", "dev": true, "dependencies": { "chalk": "^4.0.0", "copy-dir": "^1.3.0", - "docker-compose": "^0.22.2", + "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", "inquirer": "^7.1.0", @@ -2225,6 +2226,10 @@ }, "bin": { "wp-env": "bin/wp-env" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" } }, "node_modules/@wordpress/env/node_modules/ansi-styles": { @@ -3838,14 +3843,29 @@ } }, "node_modules/docker-compose": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz", - "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==", + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.8.tgz", + "integrity": "sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw==", "dev": true, + "dependencies": { + "yaml": "^2.2.2" + }, "engines": { "node": ">= 6.0.0" } }, + "node_modules/docker-compose/node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -4429,9 +4449,9 @@ } }, "node_modules/feiertagejs": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/feiertagejs/-/feiertagejs-1.4.0.tgz", - "integrity": "sha512-04oxk2oksavDs1eYewhqrJlnocWvI8a2i3S2oBZybjwRPTZv1A08sFnUnqExbp1JiVgukVaPZ3aqyVqmCJX7mw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/feiertagejs/-/feiertagejs-1.4.1.tgz", + "integrity": "sha512-+tXhTi6KG9rEw9iDR0TZGA5/lChAjwXuVYXiuHbzAOlfdmCo3MUMPYeL7GW4qjxS5KwsWqfi877JmzXZyhiimA==", "engines": { "node": ">=8.0.0" } @@ -12110,15 +12130,15 @@ "dev": true }, "node_modules/vue": { - "version": "3.4.27", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", - "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", - "dependencies": { - "@vue/compiler-dom": "3.4.27", - "@vue/compiler-sfc": "3.4.27", - "@vue/runtime-dom": "3.4.27", - "@vue/server-renderer": "3.4.27", - "@vue/shared": "3.4.27" + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", + "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-sfc": "3.4.29", + "@vue/runtime-dom": "3.4.29", + "@vue/server-renderer": "3.4.29", + "@vue/shared": "3.4.29" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index a2d7f4ff7..6670861f0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.23.2", - "@wordpress/env": "^5.0.0", + "@wordpress/env": "^10.0.0", "commons-api": "git+https://github.com/wielebenwir/commons-api.git", "cypress": "^13.9.0", "editorconfig": "^2.0.0", @@ -30,8 +30,7 @@ "scripts": { "start": "composer install --ignore-platform-reqs && npm install && npm run dist", "env": "wp-env", - "env:install-tests-cli": "./bin/install-wp-cli.sh tests-wordpress", - "env:start": "wp-env start && npm run env:install-tests-cli", + "env:start": "wp-env start", "env:stop": "wp-env stop", "cypress:setup": "./bin/setup-cypress-env.sh", "cypress:open": "cypress open --config-file tests/cypress/cypress.config.js", @@ -40,13 +39,13 @@ }, "dependencies": { "@commonsbooking/frontend": "^0.1.0-beta.6", - "feiertagejs": "^1.4.0", + "feiertagejs": "^1.4.1", "leaflet": "^1.7.1", "leaflet-easybutton": "^2.4.0", "leaflet-spin": "^1.1.2", "leaflet.markercluster": "^1.5.0", "shufflejs": "^6.1.1", "spin.js": "^2.3.2", - "vue": "^3.4.27" + "vue": "^3.4.29" } } diff --git a/src/Helper/NominatimGeoCodeService.php b/src/Helper/NominatimGeoCodeService.php index 99edb77dc..6657ac103 100644 --- a/src/Helper/NominatimGeoCodeService.php +++ b/src/Helper/NominatimGeoCodeService.php @@ -28,7 +28,7 @@ public function getAddressData( $addressString ): ?Location { throw new \Exception("Could not get address data because of missing curl extension."); } - $defaultUserAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'; + $defaultUserAgent = 'CommonsBooking v.' . COMMONSBOOKING_VERSION . " Contact: mail@commonsbooking.org"; $client = new Client( null, diff --git a/src/Migration/Migration.php b/src/Migration/Migration.php index ad002a978..a4e2e7764 100644 --- a/src/Migration/Migration.php +++ b/src/Migration/Migration.php @@ -580,17 +580,13 @@ public static function migrateBooking( $booking ): bool { * @return mixed */ public static function migrateBookingCode( $bookingCode ) { - $cb2LocationId = CB1::getCB2LocationId( $bookingCode['location_id'] ); $cb2ItemId = CB1::getCB2ItemId( $bookingCode['item_id'] ); - $cb2TimeframeId = CB1::getCB2TimeframeId( $bookingCode['timeframe_id'] ); $date = $bookingCode['booking_date']; $code = $bookingCode['bookingcode']; $bookingCode = new BookingCode( $date, $cb2ItemId, - $cb2LocationId, - $cb2TimeframeId, $code ); diff --git a/src/Model/BookingCode.php b/src/Model/BookingCode.php index 8121bb4ff..93a8d6e75 100644 --- a/src/Model/BookingCode.php +++ b/src/Model/BookingCode.php @@ -23,46 +23,30 @@ class BookingCode { * Datestring in the format Y-m-d * @var string */ - protected $date; + private $date; /** * Item ID * @var int */ - protected $item; - - /** - * Location ID - * @var int - */ - protected $location; - - /** - * Timeframe ID - * @var int - */ - protected $timeframe; + private $item; /** * Code string * @var string */ - protected $code; + private $code; /** * BookingCode constructor. * * @param $date * @param $item - * @param $location - * @param $timeframe * @param $code */ - public function __construct( $date, $item, $location, $timeframe, $code ) { + public function __construct( $date, $item, $code ) { $this->date = $date; $this->item = $item; - $this->location = $location; - $this->timeframe = $timeframe; $this->code = $code; } @@ -110,44 +94,6 @@ public function setItem( $item ): BookingCode { return $this; } - /** - * @return int - */ - public function getLocation(): int { - return $this->location; - } - - /** - * @deprecated will be deleted in the next version. This Type should be immutable, use constructor to create a new instance - * @param mixed $location - * - * @return BookingCode - */ - public function setLocation( $location ): BookingCode { - $this->location = $location; - - return $this; - } - - /** - * @return int - */ - public function getTimeframe(): int { - return $this->timeframe; - } - - /** - * @deprecated will be deleted in the next version. This Type should be immutable, use constructor to create a new instance - * @param mixed $timeframe - * - * @return BookingCode - */ - public function setTimeframe( $timeframe ): BookingCode { - $this->timeframe = $timeframe; - - return $this; - } - /** * @return string */ @@ -165,6 +111,5 @@ public function setCode( $code ): BookingCode { $this->code = $code; return $this; - } - + } } diff --git a/src/Model/Day.php b/src/Model/Day.php index 6cfd8f61a..3f8b307e7 100644 --- a/src/Model/Day.php +++ b/src/Model/Day.php @@ -475,6 +475,21 @@ protected function mapRestrictions( array &$slots ) { } } + public function getStartTimestamp(): int { + $dt = new DateTime( $this->getDate() ); + $dt->modify( 'midnight' ); + + return $dt->getTimestamp(); + } + + public function getEndTimestamp(): int { + $dt = new DateTime( $this->getDate() ); + $dt->modify( '23:59:59' ); + + return $dt->getTimestamp(); + } + + /** * Remove empty and merge connected slots. * diff --git a/src/Plugin.php b/src/Plugin.php index 480495f1d..7cb243acf 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -864,14 +864,12 @@ function () { /** * Adds bookingcode actions. * They: - * 1. Delete booking codes when a booking is deleted. - * 2. Hook appropriate function to button that downloads the booking codes in the backend. + * - Hook appropriate function to button that downloads the booking codes in the backend. * @see \CommonsBooking\View\BookingCodes::renderTable() - * 3. Hook appropriate function to button that sends out emails with booking codes to the station. + * - Hook appropriate function to button that sends out emails with booking codes to the station. * @see \CommonsBooking\View\BookingCodes::renderDirectEmailRow() */ public function initBookingcodes() { - add_action( 'before_delete_post', array( BookingCodes::class, 'deleteBookingCodes' ), 10 ); add_action( 'admin_action_cb_download-bookingscodes-csv', array( View\BookingCodes::class, 'renderCSV' ), 10, 0 ); add_action( 'admin_action_cb_email-bookingcodes', array(View\BookingCodes::class, 'emailCodes'), 10, 0); } diff --git a/src/Repository/BookingCodes.php b/src/Repository/BookingCodes.php index 68e848b51..15aadfdfd 100644 --- a/src/Repository/BookingCodes.php +++ b/src/Repository/BookingCodes.php @@ -16,7 +16,7 @@ /** * This class generates booking codes for a timeframe. - * Currently, booking codes can only be created for timeframes with an end date and where one slot fills the whole day. + * Currently, booking codes can only be created where one slot fills the whole day. */ class BookingCodes { @@ -75,11 +75,12 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e //check, if we have enough codes for the timeframe or if we need to generate more //we only need to check, if we have an open-ended timeframe - //we check, if the end date of the last generated code is before the end date of the requested time period - if ( ! $timeframe->getRawEndDate() && self::getLastCode( $timeframe ) - && strtotime( self::getLastCode( $timeframe )->getDate() ) < strtotime( $endDate ) - ) { - $startGenerationPeriod = new \DateTime( self::getLastCode($timeframe)->getDate() ); + // NOTE: there used to be a check if generation is necessary by checking the date of the last code. However, + // as the codes are never deleted anymore, it is possible that they get fragmented with date gaps and the + // check is not trivial anymore. Is is easier and safer to always try to generate codes. It is no serious + // performance issue as getCodes() is only used in admin pages on particular admin actions. + if ( ! $timeframe->getRawEndDate() ) { + $startGenerationPeriod = new \DateTime( $startDate ); $endGenerationPeriod = new \DateTime( $endDate ); // set $endGenerationPeriod's time > 00:00:00 such that $endDate is included in DatePeriod iteration // and code is generated for $endDate @@ -98,23 +99,23 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e $sql = $wpdb->prepare( "SELECT * FROM $table_name - WHERE timeframe = %d + WHERE item = %d AND date BETWEEN %s AND %s - ORDER BY item ASC ,date ASC + ORDER BY item ASC, date ASC, timeframe ASC, location ASC ", - $timeframeId, + $timeframe->getItem()->ID, $startDate, $endDate ); $bookingCodes = $wpdb->get_results($sql); + self::backwardCompatibilityFilter( $bookingCodes, $timeframeId, $timeframe->getLocation()->ID ); // for backward compatibility: delete line in future cb + $codes = []; foreach ( $bookingCodes as $bookingCode ) { $bookingCodeObject = new BookingCode( $bookingCode->date, $bookingCode->item, - $bookingCode->location, - $bookingCode->timeframe, $bookingCode->code ); $codes[] = $bookingCodeObject; @@ -127,11 +128,95 @@ public static function getCodes( int $timeframeId, int $startDate = null, int $e } /** - * Returns booking code by timeframe, location, item and date. + * Filter an array of BookingCode|s such that it contains only one BookingCode per date. + * Function is only needed when new cb version handles database entries created by old cb version. + * The filtering can be omitted in future cb versions when backward compatibility with old cb is dropped. + * + * @param $bookingCodes[] array of BookingCode|s. It is assumed that they all have the same itemId. + * @param int $preferredTimeframeId timeframeId to prefer when filtering + * @param int $preferredLocationId locationId to prefer when filtering + * + * @return $bookingCodes[] array of BookingCode|s (only one code per day) + */ + private static function backwardCompatibilityFilter(&$bookingCodes, $preferredTimeframeId, $preferredLocationId) { + $filteredCodes = []; + $codesByDate = []; + + // Group booking codes by date + foreach ( $bookingCodes as $code ) { + $date = $code->date; + if (! isset($codesByDate[$date]) ) { + $codesByDate[$date] = []; + } + $codesByDate[$date][] = $code; + } + + // For each date, filter out codes to ensure only one entry per date + foreach ( $codesByDate as $date => $codes ) { + if ( count($codes) > 1 ) { + // Keep entries matching $preferredTimeframeId and $preferredLocationId + $preferredCodes = array_filter( $codes, function($code) use ($preferredTimeframeId, $preferredLocationId ) { + return $code->timeframe == $preferredTimeframeId && $code->location == $preferredLocationId; + }); + + // If there are preferred codes, use them. Otherwise, use all codes. + $finalCodes = !empty($preferredCodes) ? $preferredCodes : $codes; + + // Pick the first code if there are still multiple entries + $filteredCodes[] = reset($finalCodes); + } else { + $filteredCodes[] = reset($codes); + } + } + + $bookingCodes = $filteredCodes; + } + + /** + * Gets a specific booking code by item ID and date. + * + * @param int $itemId - ID of item attached to timeframe + * @param string $date - Date in format Y-m-d + * @param int $preferredTimeframeId only for compatibility with database entries created by old cb version. delete in future + * @param int $preferredLocationId see $preferredTimeframeId + * + * @return BookingCode|null + */ + private static function lookupCode(int $itemId, string $date, int $preferredTimeframeId = 0, int $preferredLocationId = 0): ?BookingCode { + global $wpdb; + $table_name = $wpdb->prefix . self::$tablename; + + $sql = $wpdb->prepare( + "SELECT * FROM $table_name + WHERE + item = %s AND + date = %s + ORDER BY item ASC, date ASC, timeframe ASC, location ASC", + $itemId, + $date + ); + + $bookingCodes = $wpdb->get_results($sql); + + self::backwardCompatibilityFilter( $bookingCodes, $preferredTimeframeId, $preferredLocationId ); // for backward compatibility: delete line in future cb + + if (count($bookingCodes)) { + return new BookingCode( + $bookingCodes[0]->date, + $bookingCodes[0]->item, + $bookingCodes[0]->code + ); + } + + return null; + } + + /** + * Returns booking code by timeframe, location, item and date. If no code exist yet and timeframe does not have end-date, generate it. * * @param Timeframe $timeframe - Timeframe object to get code for * @param int $itemId - ID of item attached to timeframe - * @param int $locationId - ID of location attached to timeframe + * @param int $locationId - ID of location attached to timeframe (DEPRECATED, has no effect) * @param string $date - Date in format Y-m-d * @param int $advanceGenerationDays - Open-ended timeframes: If 0, generates code(s) until $date. If >0, generate additional codes after $date. * @@ -143,25 +228,10 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location if ( $cacheItem ) { return $cacheItem; } else { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - $sql = $wpdb->prepare( - "SELECT * FROM $table_name - WHERE - timeframe = %s AND - item = %s AND - location = %s AND - date = %s - ORDER BY item ASC ,date ASC", - $timeframe->ID, - $itemId, - $locationId, - $date - ); - $bookingCodes = $wpdb->get_results($sql); + // timeframeid and locationid are only for backward compatibility with database entries from old cb + $bookingCodeObject = static::lookupCode( $itemId, $date, $timeframe->ID, $locationId ); - if ( empty( $bookingCodes ) ) { + if ( ! $bookingCodeObject ) { //when we have a timeframe without end-date we generate as many codes as we need if (! $timeframe->getRawEndDate() && $timeframe->bookingCodesApplicable() ) { $begin = $timeframe->getUTCStartDateDateTime(); @@ -173,63 +243,16 @@ public static function getCode( Timeframe $timeframe, int $itemId, int $location $interval = DateInterval::createFromDateString( '1 day' ); $period = new DatePeriod( $begin, $interval, $endDate ); static::generatePeriod($timeframe,$period); - $bookingCodes = $wpdb->get_results($sql); + $bookingCodeObject = static::lookupCode( $itemId, $date, $timeframe->ID, $locationId ); } } - $bookingCodeObject = null; - if ( count( $bookingCodes ) ) { - $bookingCodeObject = new BookingCode( - $bookingCodes[0]->date, - $bookingCodes[0]->item, - $bookingCodes[0]->location, - $bookingCodes[0]->timeframe, - $bookingCodes[0]->code - ); - } Plugin::setCacheItem( $bookingCodeObject, [$timeframe->ID] ); return $bookingCodeObject; } } - /** - * Will get the last booking code that was generated for a given timeframe, item and location. - * This can be used to determine if we need to generate new codes. - * - * @param Timeframe $timeframe - * @param int $itemId - * @param int $locationId - * - * @return BookingCode|null - */ - public static function getLastCode(Timeframe $timeframe) : ?BookingCode { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - $sql = $wpdb->prepare( - "SELECT * FROM $table_name - WHERE - timeframe = %s - ORDER BY date DESC", - $timeframe->ID - ); - $bookingCodes = $wpdb->get_results($sql); - - $bookingCodeObject = null; - if ( count( $bookingCodes ) ) { - $bookingCodeObject = new BookingCode( - $bookingCodes[0]->date, - $bookingCodes[0]->item, - $bookingCodes[0]->location, - $bookingCodes[0]->timeframe, - $bookingCodes[0]->code - ); - } - - return $bookingCodeObject; - } - /** * Creates booking-codes table; */ @@ -240,6 +263,7 @@ public static function initBookingCodesTable() :void { $table_name = $wpdb->prefix . self::$tablename; $charset_collate = $wpdb->get_charset_collate(); + // TODO To be removed later: timeframe, location (not used anymore) $sql = "CREATE TABLE $table_name ( date date DEFAULT '0000-00-00' NOT NULL, timeframe bigint(20) unsigned NOT NULL, @@ -303,7 +327,7 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period if (! $bookingCodesArray ){ throw new BookingCodeException( __( "No booking codes could be created because there were no booking codes to choose from. Please set some booking codes in the CommonsBooking settings.", 'commonsbooking' ) ); } - // Before we add new codes, we remove old ones, that are not relevant anymore + try { //TODO #507 $location = $timeframe->getLocation(); @@ -317,8 +341,6 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period throw new BookingCodeException( __( "No booking codes could be created because the item of the timeframe could not be found.", 'commonsbooking' ) ); } - self::deleteOldCodes( $timeframe->ID, $location->ID, $item->ID ); - $bookingCodesRandomizer = intval( $timeframe->ID ); $bookingCodesRandomizer += $item->ID; $bookingCodesRandomizer += $location->ID; @@ -326,14 +348,23 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period foreach ( $period as $dt ) { $day = new Day( $dt->format( 'Y-m-d' ) ); if ( $day->isInTimeframe( $timeframe ) ) { + + // Check if a code already exists, if so DO NOT generate new + // timeframeid and locationid are only for backward compatibility with database entries from old cb + if ( static::lookupCode($item->ID, $dt->format( 'Y-m-d' ), $timeframe->ID, $location->ID) ) { + continue; + } + $bookingCode = new BookingCode( $dt->format( 'Y-m-d' ), $item->ID, - $location->ID, - $timeframe->ID, $bookingCodesArray[ ( (int) $dt->format( 'z' ) + $bookingCodesRandomizer ) % count( $bookingCodesArray ) ] ); - self::persist( $bookingCode ); + self::persist( + $bookingCode, + $timeframe->ID, // deprecated, only for backward compatibility + $location->ID, // deprecated, only for backward compatibility + ); } } @@ -341,45 +372,27 @@ private static function generatePeriod( Timeframe $timeframe, DatePeriod $period } /** - * Removes all codes for the post, that don't have the current location-id or item-id. - * - * @param int $postId - * @param int $locationId - * @param int $itemId - */ - public static function deleteOldCodes( int $postId, int $locationId, int $itemId ) : void { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - $query = $wpdb->prepare( 'DELETE FROM ' . $table_name . ' WHERE timeframe = %d AND (location != %d OR item != %d)', - $postId, - $locationId, - $itemId - ); - $wpdb->query( $query ); - } - - /** + * Stores a booking code in database without checking if code already exists (please check before) * @param BookingCode $bookingCode + * @param timeframeId deprecated (only necessary to make database compatible to former versions of cb) + * @param locationId deprecated (only necessary to make database compatible to former versions of cb) * * @return mixed */ - public static function persist( BookingCode $bookingCode ) { + public static function persist( BookingCode $bookingCode, $timeframeId = 0, $locationId = 0 ) { global $wpdb; - $wpdb->show_errors( 0 ); $table_name = $wpdb->prefix . self::$tablename; - $result = $wpdb->replace( + $result = $wpdb->insert( $table_name, array( - 'timeframe' => $bookingCode->getTimeframe(), + 'timeframe' => $timeframeId, // deprecated field, only for backward compatibility 'date' => $bookingCode->getDate(), - 'location' => $bookingCode->getLocation(), + 'location' => $locationId, // deprecated field, only for backward compatibility 'item' => $bookingCode->getItem(), 'code' => $bookingCode->getCode() ) ); - $wpdb->show_errors( 1 ); return $result; } @@ -400,32 +413,4 @@ private static function getCodesArray(): array { }, $bookingCodesArray ); } - /** - * Deletes booking codes for current post or if posted for post with $postId. - * - * @param null $postId - */ - public static function deleteBookingCodes( $postId = null ) { - if ( $postId ) { - $post = get_post( $postId ); - } else { - global $post; - } - if ( - $post && - $post->post_type == \CommonsBooking\Wordpress\CustomPostType\Timeframe::$postType - ) { - global $wpdb; - $table_name = $wpdb->prefix . self::$tablename; - - - $query = $wpdb->prepare( 'SELECT timeframe FROM ' . $table_name . ' WHERE timeframe = %d', $post->ID ); - $var = $wpdb->get_var( $query ); - if ( $var ) { - $query2 = $wpdb->prepare( 'DELETE FROM ' . $table_name . ' WHERE timeframe = %d', $post->ID ); - $wpdb->query( $query2 ); - } - } - } - } diff --git a/src/Repository/Timeframe.php b/src/Repository/Timeframe.php index 44a337855..4905cb1d6 100644 --- a/src/Repository/Timeframe.php +++ b/src/Repository/Timeframe.php @@ -721,6 +721,8 @@ private static function filterTimeframesForCurrentUser( $posts ): array { } /** + * Will filter out all timeframes that are not in the given timerange. + * * @param \CommonsBooking\Model\Timeframe[] $timeframes * @param int $startTimestamp * @param int $endTimestamp @@ -728,8 +730,8 @@ private static function filterTimeframesForCurrentUser( $posts ): array { * @return \CommonsBooking\Model\Timeframe[] * @throws Exception */ - public static function filterTimeframesForTimerange ( array $timeframes, int $startTimestamp, int $endTimestamp ): array { - return array_filter( $timeframes, function ( $timeframe ) use ($startTimestamp, $endTimestamp) { + public static function filterTimeframesForTimerange( array $timeframes, int $startTimestamp, int $endTimestamp ): array { + return array_filter( $timeframes, function ( $timeframe ) use ( $startTimestamp, $endTimestamp ) { //filter out anything in the future if ( $timeframe->getStartDate() > $endTimestamp ) { return false; @@ -742,8 +744,9 @@ public static function filterTimeframesForTimerange ( array $timeframes, int $st if ( $timeframe->getEndDate() < $startTimestamp ) { return false; } + return true; - }); + } ); } /** diff --git a/src/View/Calendar.php b/src/View/Calendar.php index 804ef91ee..59bc9f58e 100644 --- a/src/View/Calendar.php +++ b/src/View/Calendar.php @@ -406,22 +406,45 @@ public static function getCalendarDataArray( $item, $location, string $startDate * * @return \CommonsBooking\Model\Timeframe|null */ - private static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { - // Sort timeframes by startdate - usort( - $bookableTimeframes, - function ( \CommonsBooking\Model\Timeframe $item1, \CommonsBooking\Model\Timeframe $item2 ) { - $item1StartDateDistance = abs( time() - $item1->getStartDate() ); - $item1EndDateDistance = abs( time() - $item1->getEndDate() ); - $item1SmallestDistance = min( $item1StartDateDistance, $item1EndDateDistance ); - - $item2StartDateDistance = abs( time() - $item2->getStartDate() ); - $item2EndDateDistance = abs( time() - $item2->getEndDate() ); - $item2SmallestDistance = min( $item2StartDateDistance, $item2EndDateDistance ); - - return $item2SmallestDistance <=> $item1SmallestDistance; - } - ); + public static function getClosestBookableTimeFrameForToday( $bookableTimeframes ): ?\CommonsBooking\Model\Timeframe { + $today = new Day( date( 'Y-m-d' ) ); + $todayTimeframes = \CommonsBooking\Repository\Timeframe::filterTimeframesForTimerange( $bookableTimeframes, $today->getStartTimestamp(), $today->getEndTimestamp() ); + $todayTimeframes = array_filter( $todayTimeframes, function ( $timeframe ) use ( $today ) { //also consider repetition + return $today->isInTimeframe( $timeframe ); + } ); + switch ( count( $todayTimeframes ) ) { + case 1: + $bookableTimeframes = $todayTimeframes; + break; + case 0: + usort( $bookableTimeframes, function ( $a, $b ) { + $aStartDate = $a->getStartDate(); + $bStartDate = $b->getStartDate(); + + if ( $aStartDate == $bStartDate ) { + $aStartTimeDT = $a->getStartTimeDateTime(); + $bStartTimeDT = $b->getStartTimeDateTime(); + + return $bStartTimeDT <=> $aStartTimeDT; + } + + return $bStartDate <=> $aStartDate; + } ); + break; + default: //More than one timeframe for current day + // consider starttime and endtime + $now = new DateTime(); + /** @var \CommonsBooking\Model\Timeframe $todayTimeframes */ + $bookableTimeframes = array_filter( $todayTimeframes, function ( $timeframe ) use ( $now ) { + $startTime = $timeframe->getStartTime(); + $startTimeDT = new DateTime( $startTime ); + $endTime = $timeframe->getEndTime(); + $endTimeDT = new DateTime( $endTime ); + + return $startTimeDT <= $now && $now <= $endTimeDT; + } ); + break; + } return array_pop( $bookableTimeframes ); } diff --git a/tests/cypress/e2e/cpt-creation.cy.js b/tests/cypress/e2e/cpt-creation.cy.js index 2cfd0d76b..c09b024ac 100644 --- a/tests/cypress/e2e/cpt-creation.cy.js +++ b/tests/cypress/e2e/cpt-creation.cy.js @@ -62,6 +62,7 @@ describe('correctly render metaboxes for backend CPT creation', () => { cy.get('#timeframe-repetition').select(MANUAL_SELECTION); cy.get('#holiday_load_btn').click(); cy.get('#timeframe_manual_date').should('have.prop', 'value').should('not.be.empty') + cy.screenshot('cb-timeframe-holiday-loaded'); }) }) diff --git a/tests/cypress/e2e/litepicker-overbooking.cy.js b/tests/cypress/e2e/litepicker-overbooking.cy.js index fca40a5c1..9588e1995 100644 --- a/tests/cypress/e2e/litepicker-overbooking.cy.js +++ b/tests/cypress/e2e/litepicker-overbooking.cy.js @@ -18,7 +18,7 @@ describe('test overbooking process', () => { } function updatePostMetaAndReload(postID, metaKey, metaValue){ - cy.exec('bin/wp-env-cli tests-wordpress "wp --allow-root post meta update "' + postID + '" ' + metaKey + ' ' + metaValue + '"') + cy.exec('wp-env run tests-cli wp post meta update ' + postID + ' ' + metaKey + ' ' + metaValue) cy.reload() } diff --git a/tests/php/Model/DayTest.php b/tests/php/Model/DayTest.php index 785ab116c..2700e0cc0 100644 --- a/tests/php/Model/DayTest.php +++ b/tests/php/Model/DayTest.php @@ -216,4 +216,17 @@ public function testGetRestrictions() { $this->assertTrue(count($this->instance->getRestrictions()) == 1); } + + public function testGetStartTimestamp() { + $start = strtotime( self::CURRENT_DATE . ' midnight' ); + $this->assertEquals( $start, $this->instance->getStartTimestamp() ); + } + + public function testGetEndTimestamp() { + $end = strtotime( self::CURRENT_DATE . ' 23:59:59' ); + $this->assertEquals( $end, $this->instance->getEndTimestamp() ); + } + + + } diff --git a/tests/php/Repository/BookingCodesTest.php b/tests/php/Repository/BookingCodesTest.php index 607e6c07e..e3f2286da 100644 --- a/tests/php/Repository/BookingCodesTest.php +++ b/tests/php/Repository/BookingCodesTest.php @@ -39,8 +39,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($todayDate,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithEndDate->ID,$code->getTimeframe()); //and now without end date (the fabled "infinite" timeframe) BookingCodes::generate($this->timeframeWithoutEndDate,self::ADVANCE_GENERATION_DAYS); @@ -48,8 +46,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($todayDate,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithoutEndDate->ID,$code->getTimeframe()); //make sure, that the last infinite code is also generated $advanceDays = self::ADVANCE_GENERATION_DAYS - 1; @@ -58,8 +54,6 @@ public function testGenerate() $this->assertNotNull($code); $this->assertEquals($lastCodeDay,$code->getDate()); $this->assertEquals($this->itemId,$code->getItem()); - $this->assertEquals($this->locationId,$code->getLocation()); - $this->assertEquals($this->timeframeWithoutEndDate->ID,$code->getTimeframe()); } public function testGetCode() { @@ -90,9 +84,7 @@ public function testGetCode() { self::ADVANCE_GENERATION_DAYS ); $this->assertNotNull( $code ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $code->getTimeframe() ); $this->assertEquals( $this->itemId, $code->getItem() ); - $this->assertEquals( $this->locationId, $code->getLocation() ); $this->assertEquals( $dayInFuture, $code->getDate() ); //test that the code is persisted (i.e. it's not generated again) @@ -116,9 +108,7 @@ public function testGetCode() { $dayInFutureTwo, self::ADVANCE_GENERATION_DAYS); $this->assertNotNull( $futureTwoCode ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $futureTwoCode->getTimeframe() ); $this->assertEquals( $this->itemId, $futureTwoCode->getItem() ); - $this->assertEquals( $this->locationId, $futureTwoCode->getLocation() ); $this->assertEquals( $dayInFutureTwo, $futureTwoCode->getDate() ); //now check, that the old code is still persisted $stillSameCode = BookingCodes::getCode( @@ -131,6 +121,89 @@ public function testGetCode() { $this->assertEquals( $code->getCode(), $stillSameCode->getCode() ); } + public function testIfCodesAreEternal() { + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); + + // create timeframe with parameters like $timeframeWithoutEndDate + $timeframe_1 = new Timeframe($this->createTimeframe( + $this->locationId, + $this->itemId, + strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ), + null, + )); + + // generate some codes + BookingCodes::generate( $timeframe_1, self::ADVANCE_GENERATION_DAYS ); + + // get code for $this->itemId today + $todaysCode = BookingCodes::getCode( $timeframe_1, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + $this->assertNotEmpty($todaysCode->getCode()); + + // Check if codes are persistant/eternal: + // check that codes are persistant, ie when a code is once generated for a certain item and date, it should never change again + $countBefore = $this->countBookingCodes(); + $this->assertGreaterThan(0, $countBefore); + + // add some booking codes (like a WP Admin would do on Commonbookings admin pages) + $oldBookingCodes = Settings::getOption( 'commonsbooking_options_bookingcodes', 'bookingcodes' ); + $updateSuccessful = Settings::updateOption('commonsbooking_options_bookingcodes','bookingcodes',"$oldBookingCodes,NewCode"); + $this->assertTrue($updateSuccessful); + + // repeat generate() with same parameters as above ... + BookingCodes::generate( $timeframe_1, self::ADVANCE_GENERATION_DAYS ); + + // ... it should NOT lead to any new codes, because they are already existing. Check by counting: + $countAfter = $this->countBookingCodes(); + $this->assertEquals($countBefore, $countAfter); + + // ... and check by comparing today's code + $todaysCodeAfter = BookingCodes::getCode( $timeframe_1, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + $this->assertEquals($todaysCode->getCode(), $todaysCodeAfter->getCode()); + + // now delete timeframe and create another timeframe with same parameters (especially same item and overlapping in time) + wp_delete_post( $timeframe_1->ID, true ); + + $timeframe_2 = new Timeframe($this->createTimeframe( + $this->locationId, + $this->itemId, + strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ), + null, + )); + + // repeat generate() with same parameters as above ... + BookingCodes::generate( $timeframe_2, self::ADVANCE_GENERATION_DAYS ); + + // ... and check by comparing today's code + $todaysCodeTimeframe2 = BookingCodes::getCode( $timeframe_2, + $this->itemId, + $this->locationId, + date('Y-m-d', strtotime( self::CURRENT_DATE )), + self::ADVANCE_GENERATION_DAYS); + + // even if it is another timeframe, the today's code for the same item must still be the same + $this->assertEquals($todaysCode->getCode(), $todaysCodeTimeframe2->getCode()); + + } + + private function countBookingCodes() { + global $wpdb; + $table_name = $wpdb->prefix . BookingCodes::$tablename; + + $sql = "SELECT COUNT(*) FROM $table_name"; + + return $wpdb->get_var($sql); + } + public function testGetCodes() { ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); //make sure that we get no codes before generation @@ -237,25 +310,60 @@ public function testGetCodesFuture() { } } - public function testGetLastCode() { + public function testBackwardCompatibility() { ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); - BookingCodes::generate( $this->timeframeWithEndDate, self::ADVANCE_GENERATION_DAYS ); - $lastCode = BookingCodes::getLastCode( $this->timeframeWithEndDate ); - $this->assertNotNull( $lastCode ); - $this->assertEquals( $this->timeframeWithEndDate->ID, $lastCode->getTimeframe() ); - $this->assertEquals( $this->itemId, $lastCode->getItem() ); - $this->assertEquals( $this->locationId, $lastCode->getLocation() ); - $this->assertEquals( strtotime( '+29 day', strtotime( self::CURRENT_DATE ) ), strtotime($lastCode->getDate() ) ); - $advanceGenerationDays = self::ADVANCE_GENERATION_DAYS; - BookingCodes::generate( $this->timeframeWithoutEndDate, self::ADVANCE_GENERATION_DAYS ); - $lastCode = BookingCodes::getLastCode( $this->timeframeWithoutEndDate ); - $this->assertNotNull( $lastCode ); - $this->assertEquals( $this->timeframeWithoutEndDate->ID, $lastCode->getTimeframe() ); - $this->assertEquals( $this->itemId, $lastCode->getItem() ); - $this->assertEquals( $this->locationId, $lastCode->getLocation() ); - //The DatePeriod does not include the endDay, so we have to subtract one day - $advanceGenerationDays -= 1; - $this->assertEquals( strtotime( '+' . $advanceGenerationDays . ' day', strtotime( self::CURRENT_DATE ) ), strtotime($lastCode->getDate() ) ); + $todayDate = date( 'Y-m-d', strtotime(self::CURRENT_DATE) ); + $thisTimeframeId = $this->timeframeWithEndDate->ID; + + // (at least) up to cb 2.9.3, booking codes were saved with timeframeId and locationId + // and under some circumstances, there could be several booking codes for a certain (date, itemId) pair. + // Now cb never generates new codes for a (data, itemId) pair if a code for that pair already exists. + // But old codes generated by old cb could be left in the database and should still work as expected, + // what is tested here + + // Insert codes as if generated by an old cb version: the "right" code and two "wrong" codes + // which do have matching date and itemId, but with unsuitable timeframe and location fields + // note: the "wrong" timeframe and location ids are chosen smaller for deterministic testing: + // the tested methods getCode() and getCodes() sort by ASC id so the "wrong" codes should be + // returned first such that test does not PASS accidentally + $codesToInsert = [ + ['code' => 'right_code', 'timeframe' => $thisTimeframeId, 'location' => $this->locationId], + ['code' => 'wrong_timeframe', 'timeframe' => $thisTimeframeId - 1, 'location' => $this->locationId], + ['code' => 'wrong_location', 'timeframe' => $thisTimeframeId, 'location' => $this->locationId - 1] + ]; + + // insert the codes (all with same date & itemId) + foreach ($codesToInsert as $codeData) { + self::insertBookingCode( $todayDate, $codeData['timeframe'], $codeData['location'], $this->itemId, $codeData['code'] ); + } + + BookingCodes::generate( $this->timeframeWithEndDate,self::ADVANCE_GENERATION_DAYS ); + + $code = BookingCodes::getCode( $this->timeframeWithEndDate, $this->itemId, $this->locationId, $todayDate, self::ADVANCE_GENERATION_DAYS ); + $this->assertEquals( 'right_code', $code->getCode() ); + + // now test getCodes() + $todayDateTime = new \DateTime( self::CURRENT_DATE ); + + $codes = BookingCodes::getCodes( $this->timeframeWithEndDate->ID, $todayDateTime->getTimestamp(), $todayDateTime->getTimestamp() ); + + $this->assertNotEmpty( $codes ); + $this->assertCount( 1, $codes ); + $this->assertEquals( 'right_code', $codes[0]->getCode() ); + + } + + private static function insertBookingCode($date, $timeframe, $location, $item, $code) { + global $wpdb; + $table_name = $wpdb->prefix . BookingCodes::$tablename; + + $wpdb->insert($table_name, array( + 'date' => $date, + 'timeframe' => $timeframe, + 'location' => $location, + 'item' => $item, + 'code' => $code + )); } protected function setUp(): void { diff --git a/tests/php/View/CalendarTest.php b/tests/php/View/CalendarTest.php index 051cd3b4a..291cd6e98 100644 --- a/tests/php/View/CalendarTest.php +++ b/tests/php/View/CalendarTest.php @@ -11,7 +11,6 @@ /** * @TODO: Write test for restriction cache invalidation. */ - class CalendarTest extends CustomPostTypeTest { protected const bookingDaysInAdvance = 35; @@ -26,6 +25,8 @@ class CalendarTest extends CustomPostTypeTest { protected $secondClosestTimeframe; + private $now; + public function testKeepDateRangeParam() { $startDate = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); $jsonresponse = Calendar::getCalendarDataArray( @@ -70,12 +71,12 @@ public function testAdvancedBookingDays() { // days between start date and latest possible booking date $maxBookableDays = date_diff( $latestPossibleBookingDate, $timeframeStart )->days; - $this->assertTrue( $maxBookableDays == (self::bookingDaysInAdvance - self::timeframeStart - 1) ); + $this->assertTrue( $maxBookableDays == ( self::bookingDaysInAdvance - self::timeframeStart - 1 ) ); } public function testClosestBookableTimeFrameFuntion() { - $startDate = date( 'Y-m-d', strtotime( 'midnight', strtotime(self::CURRENT_DATE) ) ); - $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ); + $startDate = date( 'Y-m-d', strtotime( 'midnight', strtotime( self::CURRENT_DATE ) ) ); + $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ); $jsonresponse = Calendar::getCalendarDataArray( $this->itemId, @@ -84,7 +85,7 @@ public function testClosestBookableTimeFrameFuntion() { $endDate ); - $this->assertTrue($jsonresponse['minDate'] == date('Y-m-d')); + $this->assertTrue( $jsonresponse['minDate'] == date( 'Y-m-d' ) ); } /* @@ -96,42 +97,42 @@ public function testOverbookingDefaultValues() { $jsonresponse = Calendar::getCalendarDataArray( $this->itemId, $this->locationId, - date( 'Y-m-d', strtotime( 'midnight', strtotime(self::CURRENT_DATE) ) ), - date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ) + date( 'Y-m-d', strtotime( 'midnight', strtotime( self::CURRENT_DATE ) ) ), + date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ) ); - $this->assertTrue($jsonresponse['disallowLockDaysInRange']); - $this->assertFalse($jsonresponse['countLockDaysInRange']); - $this->assertEquals(0, $jsonresponse['countLockDaysMaxDays']); + $this->assertTrue( $jsonresponse['disallowLockDaysInRange'] ); + $this->assertFalse( $jsonresponse['countLockDaysInRange'] ); + $this->assertEquals( 0, $jsonresponse['countLockDaysMaxDays'] ); //old locations which only have overbooking enabled should not have the countLockDaysInRange set and countLockDaysMaxDays should be 0 - $differentItemId = $this->createItem("Different Item",'publish'); - $oldLocationId = $this->createLocation("Old Location",'publish'); - $otherTimeframe = $this->createBookableTimeFrameIncludingCurrentDay($oldLocationId,$differentItemId); + $differentItemId = $this->createItem( "Different Item", 'publish' ); + $oldLocationId = $this->createLocation( "Old Location", 'publish' ); + $otherTimeframe = $this->createBookableTimeFrameIncludingCurrentDay( $oldLocationId, $differentItemId ); update_post_meta( $oldLocationId, COMMONSBOOKING_METABOX_PREFIX . 'allow_lockdays_in_range', 'on' ); - ClockMock::freeze( new \DateTime( self::CURRENT_DATE )); + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); $jsonresponse = Calendar::getCalendarDataArray( $differentItemId, $oldLocationId, - date( 'Y-m-d', strtotime( '-1 days', strtotime(self::CURRENT_DATE) ) ), - date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ) + date( 'Y-m-d', strtotime( '-1 days', strtotime( self::CURRENT_DATE ) ) ), + date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ) ); - $this->assertFalse($jsonresponse['disallowLockDaysInRange']); - $this->assertFalse($jsonresponse['countLockDaysInRange']); - $this->assertEquals(0, $jsonresponse['countLockDaysMaxDays']); + $this->assertFalse( $jsonresponse['disallowLockDaysInRange'] ); + $this->assertFalse( $jsonresponse['countLockDaysInRange'] ); + $this->assertEquals( 0, $jsonresponse['countLockDaysMaxDays'] ); } public function testBookingOffset() { - ClockMock::freeze( new \DateTime( self::CURRENT_DATE )); - $startDate = date( 'Y-m-d', strtotime( '-1 day', strtotime(self::CURRENT_DATE) ) ); - $today = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); - $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime(self::CURRENT_DATE) ) ); - $otherItemId = $this->createItem("Other Item",'publish'); - $otherLocationId = $this->createLocation("Other Location",'publish'); - $offsetTF = $this->createTimeframe( + ClockMock::freeze( new \DateTime( self::CURRENT_DATE ) ); + $startDate = date( 'Y-m-d', strtotime( '-1 day', strtotime( self::CURRENT_DATE ) ) ); + $today = date( 'Y-m-d', strtotime( self::CURRENT_DATE ) ); + $endDate = date( 'Y-m-d', strtotime( '+60 days midnight', strtotime( self::CURRENT_DATE ) ) ); + $otherItemId = $this->createItem( "Other Item", 'publish' ); + $otherLocationId = $this->createLocation( "Other Location", 'publish' ); + $offsetTF = $this->createTimeframe( $otherLocationId, $otherItemId, - strtotime($startDate), - strtotime($endDate), + strtotime( $startDate ), + strtotime( $endDate ), \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, "on", 'd', @@ -146,7 +147,7 @@ public function testBookingOffset() { 30, 2 ); - $jsonresponse = Calendar::getCalendarDataArray( + $jsonresponse = Calendar::getCalendarDataArray( $otherItemId, $otherLocationId, $startDate, @@ -154,10 +155,10 @@ public function testBookingOffset() { ); //considering the advance booking days $days = $jsonresponse['days']; - $this->assertEquals(32, count($days)); + $this->assertEquals( 32, count( $days ) ); //considering the offset, today and tomorrow should be locked - $this->assertTrue($days[$today]['locked']); - $this->assertTrue($days[date('Y-m-d', strtotime('+1 day', strtotime($today)))]['locked']); + $this->assertTrue( $days[ $today ]['locked'] ); + $this->assertTrue( $days[ date( 'Y-m-d', strtotime( '+1 day', strtotime( $today ) ) ) ]['locked'] ); } /** @@ -211,30 +212,192 @@ public function testRepetition() { } public function testRenderTable() { - $calendar = Calendar::renderTable([]); - $item = new \CommonsBooking\Model\Item($this->itemId); - $location = new \CommonsBooking\Model\Location($this->locationId); - $this->assertStringContainsString('assertStringContainsString($item->post_title, $calendar); - $this->assertStringContainsString($location->post_title, $calendar); + $calendar = Calendar::renderTable( [] ); + $item = new \CommonsBooking\Model\Item( $this->itemId ); + $location = new \CommonsBooking\Model\Location( $this->locationId ); + $this->assertStringContainsString( 'assertStringContainsString( $item->post_title, $calendar ); + $this->assertStringContainsString( $location->post_title, $calendar ); //in a year, all timeframes will have expired -> calendar should be empty $inAYear = new \DateTime(); - $inAYear->modify('+1 year'); - ClockMock::freeze($inAYear); - $calendar = Calendar::renderTable([]); - $this->assertStringContainsString('No items found', $calendar); + $inAYear->modify( '+1 year' ); + ClockMock::freeze( $inAYear ); + $calendar = Calendar::renderTable( [] ); + $this->assertStringContainsString( 'No items found', $calendar ); + } + + /** + * + * @return array + */ + public function provideGetClosestBookableTimeFrameForToday() { + $currentTimestamp = strtotime( self::CURRENT_DATE . ' 12:00' ); + //will define an array with settings for the timeframes + //that the getClosestBookableTimeFrameForToday function will be tested against + //you can provide the name of the test, the closest timeframe and another timeframe. + //supported arguments for timeframe, if not specified default values will be used + //repetition, repetition_start, repetition_end = null,weekdays = ["1","2","3","4","5","6","7"], start_time = '8:00 AM', end_time = '12:00 PM' + //if no start and endtime are provided, the timeframe will span the full day. + //If they are provided, fullday is turned off. + //Please note: The date that we test against is a thursday. + return [ + "daily not overlapping" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+8 days", $currentTimestamp ), + "repetition_end" => strtotime( "+14 days", $currentTimestamp ), + ] + ], + "weekly (different weekdays)" => [ + "closest" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "weekdays" => [ "4" ] //just thursday + ], + "other" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "weekdays" => [ "1", "2", "3", "5", "6", "7" ] //all but thursday + ] + ], + "both timeframes in future (daily rep)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+7 days", $currentTimestamp ), + "repetition_end" => strtotime( "+14 days", $currentTimestamp ), + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+15 days", $currentTimestamp ), + "repetition_end" => strtotime( "+21 days", $currentTimestamp ), + ] + ], + "weekly and daily" => [ + "closest" => [ + "repetition" => "w", + "repetition_start" => strtotime( "-64 days", $currentTimestamp ), + "repetition_end" => strtotime( "+199 days", $currentTimestamp ), + "weekdays" => [ "1", "2", "3", "4", "5" ] + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+21 days", $currentTimestamp ), + "repetition_end" => strtotime( "+21 days", $currentTimestamp ) + ] + ], + "daily overlap with different times (present)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-1 days", $currentTimestamp ), + "repetition_end" => strtotime( "+1 days", $currentTimestamp ), + "start_time" => "8:00 AM", + "end_time" => "01:00 PM" + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "-1 days", $currentTimestamp ), + "repetition_end" => strtotime( "+1 days", $currentTimestamp ), + "start_time" => "02:00 PM", + "end_time" => "06:00 PM" + ] + ], + "daily overlap with different times (future)" => [ + "closest" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+5 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "start_time" => "8:00 AM", + "end_time" => "01:00 PM" + ], + "other" => [ + "repetition" => "d", + "repetition_start" => strtotime( "+5 days", $currentTimestamp ), + "repetition_end" => strtotime( "+7 days", $currentTimestamp ), + "start_time" => "02:00 PM", + "end_time" => "06:00 PM" + ], + ] + ]; + } + + /** + * These are the tests for timeframes with daily repetition + * @return void + * @throws \Exception + * @dataProvider provideGetClosestBookableTimeFrameForToday + */ + public function testGetClosestBookableTimeFrameForToday( array $closest, array $other ) { + $testItem = $this->createItem( "Item" ); + $testLocation = $this->createLocation( "Location" ); + $currentTime = new \DateTime( self::CURRENT_DATE ); + $currentTime->setTime( 12, 0 ); + //Time set to '01.07.2021 12:00' + ClockMock::freeze( $currentTime ); + $expectedClosestTimeframe = $this->createTimeframeFromConfig( "closest timeframe", $testItem, $testLocation, $closest ); + $otherTimeframe = $this->createTimeframeFromConfig( "other timeframe", $testItem, $testLocation, $other ); + $closestTimeframe = Calendar::getClosestBookableTimeFrameForToday( [ + $expectedClosestTimeframe, + $otherTimeframe + ] ); + $this->assertEquals( $expectedClosestTimeframe->ID, $closestTimeframe->ID ); + } + + /** + * Will create the timeframes from the configuration defined in the dataProvider of testGetClosestBookableTimeFrameForToday + * + * @param int $itemId + * @param int $locationID + * @param array $config + * + * @return void + */ + private function createTimeframeFromConfig( string $name, int $itemId, int $locationID, array $config ): Timeframe { + $fullDay = ! ( isset ( $config["start_time"] ) && isset( $config["end_time"] ) ); + $grid = $fullDay ? 1 : 0; //Currently, grid is becoming hourly when not full day (TODO: Also test slots) + + return new Timeframe( + $this->createTimeframe( + $locationID, + $itemId, + $config["repetition_start"], + $config["repetition_end"] ?? null, + \CommonsBooking\Wordpress\CustomPostType\Timeframe::BOOKABLE_ID, + $fullDay ? "on" : "off", + $config["repetition"], + $grid, + $config["start_time"] ?? '8:00 AM', + $config["end_time"] ?? '12:00 PM', + "publish", + $config["weekdays"] ?? [ "1", "2", "3", "4", "5", "6", "7" ], + "", + self::USER_ID, + 3, + 30, + 0, + "on", + "on", + $name + ) + ); } - protected function setUp() : void { + protected function setUp(): void { parent::setUp(); - $now = time(); + $this->now = time(); $this->timeframeId = $this->createTimeframe( $this->locationId, $this->itemId, - strtotime( '+' . self::timeframeStart . ' days midnight', $now ), - strtotime( '+' . self::timeframeEnd . ' days midnight', $now ) + strtotime( '+' . self::timeframeStart . ' days midnight', $this->now ), + strtotime( '+' . self::timeframeEnd . ' days midnight', $this->now ) ); // set booking days in advance update_post_meta( $this->timeframeId, Timeframe::META_TIMEFRAME_ADVANCE_BOOKING_DAYS, self::bookingDaysInAdvance );