commit 174e8090544c0d1925597cf59808ff9a0fb021a1 Author: Dirk Heilig Date: Thu Apr 20 12:37:33 2023 +0200 init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d0fb3f2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +htdocs/vendor/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..acac0b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +htdocs/cache/* +htdocs/vendor/* diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6df1723 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +htdocs/cache/* +htdocs/vendor/* +idea/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b29788 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM php:8-apache +RUN apt-get update && apt-get upgrade -y && apt-get install -y curl git unzip && rm -rf /var/lib/apt/lists/* +RUN rm /var/www/html/* -rf +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer +COPY htdocs/ /var/www/html +WORKDIR /var/www/html +RUN composer install +RUN chown www-data:www-data /var/www/html diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7fbb274 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +.PHONY: install prettier + +prettier: + prettier -w . + + +install: htdocs/vendor + +htdocs/vendor: htdocs/composer.json htdocs/composer.lock + cd htdocs ; composer install --no-dev --optimize-autoloader + touch htdocs/vendor + +htdocs/composer.lock: htdocs/composer.json + cd htdocs ; composer install --no-dev --optimize-autoloader + touch htdocs/composer.lock + diff --git a/htdocs/api/index.php b/htdocs/api/index.php new file mode 100644 index 0000000..3b02027 --- /dev/null +++ b/htdocs/api/index.php @@ -0,0 +1,198 @@ + $end) { + header("HTTP/1.1 400 Bad Request"); + echo "Start value is after end value"; + exit(); +} + +if ($end - $start > 60 * 60 * 24 * 90) { + header("HTTP/1.1 400 Bad Request"); + echo "Maximum range is 90 days"; + exit(); +} +if ($maxItems > 100) { + header("HTTP/1.1 400 Bad Request"); + echo "Maximum number of items is 100"; + exit(); +} + +header("X-Debug-used-url: $url"); +header("X-Debug-start: $start (" . date("Y-m-d", $start) . ")"); +header("X-Debug-end: $end (" . date("Y-m-d", $end) . ")"); +header("X-Debug-max-items: $maxItems"); + +require_once __DIR__ . "/../vendor/autoload.php"; + +if (!is_dir(CACHE_DIR)) { + mkdir(CACHE_DIR, 0777, true); +} + +$urlCacheFile = CACHE_DIR . "/url_" . md5($url); +header( + "X-Debug-Cache-File-data: " . + json_encode([$url, $start, $end, $maxItems], JSON_UNESCAPED_SLASHES) +); +if ( + file_exists($completeCacheFile) && + filemtime($completeCacheFile) > time() - CACHE_TTL +) { + header("Content-Type: application/json"); + header("X-Debug-Cache-Hit: complete"); + readfile($completeCacheFile); + //use fast response time to allow cache cleaning for 500ms + $cacheCleanEnd = microtime(true) + 0.5; + $cacheFiles = glob(CACHE_DIR . "/*"); + do { + $cacheFile = array_shift($cacheFiles); + if (filemtime($cacheFile) < time() - CACHE_TTL) { + unlink($cacheFile); + } + } while (microtime(true) <= $cacheCleanEnd && count($cacheFiles) > 0); + + exit(); +} + +if ( + !file_exists($urlCacheFile) || + filemtime($urlCacheFile) < time() - CACHE_TTL +) { + $dlStart = microtime(true); + $size = 0; + $fp_in = fopen($url, "r"); + $fp_out = fopen($urlCacheFile, "w"); + while ($fp_in && !feof($fp_in)) { + $size += fwrite($fp_out, fread($fp_in, 1024 * 1024)); + if ($size > MAX_FILE_SIZE) { + header("HTTP/1.1 400 Bad Request"); + echo "File is too big"; + fclose($fp_in); + fclose($fp_out); + unlink($urlCacheFile); + exit(); + } + } + fclose($fp_in); + fclose($fp_out); + $dlEnd = microtime(true); + $dlTime = round($dlEnd - $dlStart, 3); + header("X-Debug-Download-Time: $dlTime s"); + $prefixes = ["B", "KB", "MB", "GB", "TB", "PB"]; + $prefix = array_shift($prefixes); + while ($size > 1500) { + $prefix = array_shift($prefixes); + $size = $size / 1024; + } + $hsize = round($size, 3) . " " . $prefix; + header("X-Debug-Download-Size: $hsize"); +} else { + header("X-Debug-Cache-Hit: url"); +} + +$errors = false; + +try { + $parseStart = microtime(true); + $ical = new ICal($urlCacheFile, [ + "defaultTimeZone" => "UTC", + ]); + $events = $ical->eventsFromRange( + date("Y-m-d", strtotime("today")), + date("Y-m-d", strtotime("today + 90 days")) + ); + $parseEnd = microtime(true); + $parseTime = round($parseEnd - $parseStart, 3); + header("X-Debug-Parse-Time: $parseTime s"); + + while (count($events) > $maxItems) { + array_pop($events); + } + + $events = array_map(function (Event $event) { + $r = []; + @$r["summary"] = $event->summary; + @$r["description"] = $event->description; + @$r["location"] = $event->location; + @$r["start"] = $event->dtstart_array[2]; + @$r["end"] = $event->dtend_array[2]; + @$r["duration"] = $event->duration; + @$r["url"] = $event->url; + @$r["status"] = $event->status; + return $r; + }, $events); + usort($events, function ($a, $b) { + return $a["start"] - $b["start"]; + }); +} catch (\Exception $e) { + header("HTTP/1.1 500 Internal Server Error"); + echo "Error parsing ical file"; + exit(); +} + +$data = json_encode( + $events, + JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT +); +file_put_contents($completeCacheFile, $data); +header("Content-Type: application/json"); +echo $data; + +foreach (glob(CACHE_DIR . "/*") as $file) { + if (filemtime($file) < time() - CACHE_TTL) { + unlink($file); + } +} diff --git a/htdocs/app.js b/htdocs/app.js new file mode 100644 index 0000000..1eda5fa --- /dev/null +++ b/htdocs/app.js @@ -0,0 +1,86 @@ +let previewData = []; +let getData = function () { + let data = {}; + data.url = document.getElementById("url").value; + data.start = document.getElementById("start").value; + data.end = document.getElementById("end").value; + data.maxitems = document.getElementById("maxitems").value; + for (let key in data) { + if (data[key] == "") { + delete data[key]; + } + } + return data; +}; + +document.getElementById("form").onsubmit = function () { + // Get data from form + let data = getData(); + // Send data to server as url form encoded + let xhr = new XMLHttpRequest(); + let outputEl = document.getElementById("preview"); + outputEl.innerHTML = "Loading..."; + outputEl.classList.add("loading"); + outputEl.classList.remove("error"); + let url = "/api/?" + new URLSearchParams(data).toString(); + xhr.open("get", url); + xhr.onload = function () { + if (xhr.status == 200) { + outputEl.classList.remove("loading"); + outputEl.classList.remove("error"); + } else { + outputEl.classList.remove("loading"); + outputEl.classList.add("error"); + } + document.getElementById("preview").innerHTML = xhr.responseText; + previewData = JSON.parse(xhr.responseText); + }; + xhr.send(); + + document.getElementById("previewUrl").value = + window.location.protocol + "//" + window.location.host + url; + return false; +}; +let start = document.getElementById("start"); +start.onchange = function () { + let val = this.value; + if (val == "") { + val = start.getAttribute("placeholder"); + } + let xhr = new XMLHttpRequest(); + xhr.open( + "get", + "/strtotime/?" + + new URLSearchParams({ date: val, format: "Y-m-d" }).toString() + ); + xhr.onload = function () { + if (xhr.status == 200) { + document.querySelector(".previewStart").textContent = xhr.responseText; + } + }; + xhr.send(); +}; +start.onkeyup = start.onchange; +start.onchange(); + +let end = document.getElementById("end"); +end.onchange = function () { + let val = this.value; + if (val == "") { + val = end.getAttribute("placeholder"); + } + let xhr = new XMLHttpRequest(); + xhr.open( + "get", + "/strtotime/?" + + new URLSearchParams({ date: val, format: "Y-m-d" }).toString() + ); + xhr.onload = function () { + if (xhr.status == 200) { + document.querySelector(".previewEnd").textContent = xhr.responseText; + } + }; + xhr.send(); +}; +end.onkeyup = end.onchange; +end.onchange(); diff --git a/htdocs/composer.json b/htdocs/composer.json new file mode 100644 index 0000000..9377f0d --- /dev/null +++ b/htdocs/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "johngrogg/ics-parser": "^2.1" + } +} diff --git a/htdocs/composer.lock b/htdocs/composer.lock new file mode 100644 index 0000000..bedacc1 --- /dev/null +++ b/htdocs/composer.lock @@ -0,0 +1,155 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "10482d28879f2a0ab5cd26452cfd53d5", + "packages": [ + { + "name": "johngrogg/ics-parser", + "version": "v2.2.2", + "source": { + "type": "git", + "url": "https://github.com/u01jmg3/ics-parser.git", + "reference": "69c80471a0a99142ebc72b21c2bc084e81a7c4f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/u01jmg3/ics-parser/zipball/69c80471a0a99142ebc72b21c2bc084e81a7c4f4", + "reference": "69c80471a0a99142ebc72b21c2bc084e81a7c4f4", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4", + "squizlabs/php_codesniffer": "~2.9.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "ICal": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Jonathan Goode", + "role": "Developer/Owner" + }, + { + "name": "John Grogg", + "email": "john.grogg@gmail.com", + "role": "Developer/Prior Owner" + } + ], + "description": "ICS Parser", + "homepage": "https://github.com/u01jmg3/ics-parser", + "keywords": [ + "iCalendar", + "ical", + "ical-parser", + "ics", + "ics-parser", + "ifb" + ], + "support": { + "issues": "https://github.com/u01jmg3/ics-parser/issues", + "source": "https://github.com/u01jmg3/ics-parser/tree/v2.2.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/u01jmg3", + "type": "github" + } + ], + "time": "2020-11-02T10:28:33+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": ["bootstrap.php"], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": ["MIT"], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": ["compatibility", "mbstring", "polyfill", "portable", "shim"], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.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": "2022-11-03T14:55:06+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/htdocs/index.html b/htdocs/index.html new file mode 100644 index 0000000..c0fd326 --- /dev/null +++ b/htdocs/index.html @@ -0,0 +1,50 @@ + + + ICAL2JSON helper + + + +

URL-Builder

+
+ + + + + +
+

Preview data

+ +
No data
+ + + + diff --git a/htdocs/strtotime/index.php b/htdocs/strtotime/index.php new file mode 100644 index 0000000..9428c49 --- /dev/null +++ b/htdocs/strtotime/index.php @@ -0,0 +1,4 @@ + input { + margin: 1em; + height: 3em; + width: 50em; +} +body { + font-family: Arial, Helvetica, sans-serif; + font-size: 1em; + color: #333; + background: #fff; +} + +#preview { + font-family: monospace; + white-space: pre; + border: 0.3em inset #4c4; + min-height: 3em; +} +#preview.error { + border-color: #c44; +} +#preview.loading { + border-color: #cc4; +} +textarea { + display: block; + margin: 1em; + width: calc(100% - 2em); +} + +.grow-wrap { + display: grid; +} +.grow-wrap::after { + content: attr(data-replicated-value) " "; + + white-space: pre-wrap; + + visibility: hidden; +} +.grow-wrap > textarea { + resize: none; + + overflow: hidden; +} +.grow-wrap > textarea, +.grow-wrap::after { + border: 1px solid black; + padding: 0.5rem; + font: inherit; + + grid-area: 1 / 1 / 2 / 2; +}