mirror of
https://framagit.org/framasoft/framadate/framadate
synced 2026-07-20 01:53:52 +00:00
Merge branch 'ical' into 'develop'
Allow downloading ics/ical files for best choices See merge request framasoft/framadate/framadate!473
This commit is contained in:
commit
4d6eba6bf3
10 changed files with 449 additions and 5 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -27,3 +27,6 @@ Thumbs.db
|
|||
.idea/
|
||||
*.iml
|
||||
test_database.sqlite
|
||||
|
||||
#ics temp file
|
||||
out.ics
|
||||
|
|
|
|||
183
app/classes/Framadate/Services/ICalService.php
Normal file
183
app/classes/Framadate/Services/ICalService.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
/**
|
||||
* This software is governed by the CeCILL-B license. If a copy of this license
|
||||
* is not distributed with this file, you can obtain one at
|
||||
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
|
||||
*
|
||||
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
|
||||
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
|
||||
*
|
||||
* =============================
|
||||
*
|
||||
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
|
||||
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
|
||||
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
|
||||
*
|
||||
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
|
||||
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
|
||||
*/
|
||||
namespace Framadate\Services;
|
||||
|
||||
use DateTime;
|
||||
use Framadate\Repositories\RepositoryFactory;
|
||||
use Sabre\VObject;
|
||||
|
||||
class ICalService {
|
||||
/**
|
||||
* @var NotificationService
|
||||
*/
|
||||
private $notificationService;
|
||||
/**
|
||||
* @var SessionService
|
||||
*/
|
||||
private $sessionService;
|
||||
/**
|
||||
* @var PurgeService
|
||||
*/
|
||||
private $purgeService;
|
||||
/**
|
||||
* @var LogService
|
||||
*/
|
||||
private $logService;
|
||||
|
||||
public function __construct(LogService $logService, NotificationService $notificationService, SessionService $sessionService, PurgeService $purgeService) {
|
||||
$this->logService = $logService;
|
||||
$this->notificationService = $notificationService;
|
||||
$this->sessionService = $sessionService;
|
||||
$this->purgeService = $purgeService;
|
||||
$this->pollRepository = RepositoryFactory::pollRepository();
|
||||
$this->slotRepository = RepositoryFactory::slotRepository();
|
||||
$this->voteRepository = RepositoryFactory::voteRepository();
|
||||
$this->commentRepository = RepositoryFactory::commentRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ical-File and initiates the download. If possible, the provided time is used, else an all day event is created.
|
||||
*/
|
||||
public function getEvent(object $poll, string $start_day, string $start_time) {
|
||||
if(!$this->dayIsReadable($start_day)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ical_text = "";
|
||||
$start_time = $this->reviseTimeString($start_time);
|
||||
if($start_time !== null) {
|
||||
$ical_text = $this->getTimedEvent1Hour($poll, $start_day . " " . $start_time);
|
||||
}
|
||||
else {
|
||||
$date = DateTime::createFromFormat('d-m-Y', $start_day);
|
||||
$day = $date->format('Ymd');
|
||||
$ical_text = $this->getAllDayEvent($poll, $day);
|
||||
}
|
||||
$this->provideFile($poll->title, $ical_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls getTimedEvent with one hour as a time slot, starting at $start_daytime
|
||||
*/
|
||||
function getTimedEvent1Hour(object $poll, string $start_daytime) {
|
||||
$end_daytime = date(DATE_ATOM, strtotime('+1 hours', strtotime($start_daytime)));
|
||||
return $this->getTimedEvent($poll, $start_daytime, $end_daytime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the text for an ical event including the time
|
||||
*/
|
||||
function getTimedEvent(object $poll, string $start_daytime, string $end_daytime) {
|
||||
$vcalendar = new VObject\Component\VCalendar([
|
||||
'VEVENT' => [
|
||||
'SUMMARY' => $poll->title,
|
||||
'DESCRIPTION' => $this->stripMD($poll->description),
|
||||
'DTSTART' => new \DateTime($start_daytime),
|
||||
'DTEND' => new \DateTime($end_daytime)
|
||||
],
|
||||
'PRODID' => ICAL_PRODID
|
||||
]);
|
||||
return $vcalendar->serialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the text for an ical event if the time is not known
|
||||
*/
|
||||
function getAllDayEvent(object $poll, string $day) {
|
||||
$vcalendar = new VObject\Component\VCalendar();
|
||||
$vevent = $vcalendar->add('VEVENT');
|
||||
$vevent->add('SUMMARY', $poll->title);
|
||||
$vevent->add('DESCRIPTION', $this->stripMD($poll->description));
|
||||
$dtstart = $vevent->add('DTSTART', $day);
|
||||
$dtstart['VALUE'] = 'DATE';
|
||||
unset($vcalendar->PRODID);
|
||||
$vcalendar->add('PRODID', ICAL_PRODID);
|
||||
return $vcalendar->serialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file and initiates the download
|
||||
* @param string $ical_text
|
||||
*/
|
||||
function provideFile(string $title, string $ical_text) {
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Disposition: attachment; filename=' . $this->stripTitle($title) . ICAL_ENDING);
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate');
|
||||
header('Pragma: public');
|
||||
header("Content-Type: text/plain");
|
||||
echo $ical_text;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reformats a string value into a time readable by DateTime
|
||||
* @param string $time
|
||||
* @return string the corrected value, null if the format is unknown
|
||||
*/
|
||||
function reviseTimeString(string $time) {
|
||||
// 24-hour clock / international format
|
||||
if (preg_match('/^\d\d(:)\d\d$/', $time)) {
|
||||
return $time;
|
||||
}
|
||||
// 12-hour clock / using am and pm
|
||||
else if (preg_match('/^\d[0-2]?:?\d{0,2}\s?[aApP][mM]$/', $time)) {
|
||||
return $this->formatTime($time);
|
||||
}
|
||||
// french format HHhMM or HHh
|
||||
else if (preg_match('/^\d\d?[hH]\d?\d?$/', $time)) {
|
||||
return $this->formatTime(str_pad(str_ireplace("H", ":", $time), 5, "0"));
|
||||
}
|
||||
// Number only
|
||||
else if (preg_match('/^\d{1,4}$/', $time)) {
|
||||
return $this->formatTime(str_pad(str_pad($time, 2, "0", STR_PAD_LEFT), 4, "0"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $time
|
||||
* @return 1 if the day string can be parsed, 0 if not and false if an error occured
|
||||
*/
|
||||
function dayIsReadable(string $day) {
|
||||
return preg_match('/^\d{2}-\d{2}-\d{4}$/', $day);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $time
|
||||
* @return string date string in format H:i (e.g. 19:00)
|
||||
*/
|
||||
function formatTime(string $time) {
|
||||
return date("H:i", strtotime($time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts MD Code to HTML, then strips HTML away
|
||||
*/
|
||||
function stripMD(string $string) {
|
||||
return strip_tags(smarty_modifier_markdown($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips a string so it's usable as a file name (only digits, letters and underline allowed)
|
||||
*/
|
||||
function stripTitle(string $string) {
|
||||
return preg_replace('/[^a-z0-9_]+/', '-', strtolower($string));
|
||||
}
|
||||
}
|
||||
|
|
@ -43,3 +43,6 @@ const SESSION_EDIT_LINK_TIME = "EditLinkMail";
|
|||
|
||||
// CSRF (300s = 5min)
|
||||
const TOKEN_TIME = 300;
|
||||
|
||||
const ICAL_ENDING = ".ics";
|
||||
const ICAL_PRODID = "-//Framasoft//Framadate//EN";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Framadate\Services\AdminPollService;
|
||||
use Framadate\Services\ICalService;
|
||||
use Framadate\Services\InputService;
|
||||
use Framadate\Services\LogService;
|
||||
use Framadate\Services\MailService;
|
||||
|
|
@ -21,6 +22,7 @@ class Services {
|
|||
private static $mailService;
|
||||
private static $notificationService;
|
||||
private static $pollService;
|
||||
private static $icalService;
|
||||
private static $purgeService;
|
||||
private static $securityService;
|
||||
private static $sessionService;
|
||||
|
|
@ -73,6 +75,13 @@ class Services {
|
|||
return self::$pollService;
|
||||
}
|
||||
|
||||
public static function ical() {
|
||||
if (self::$icalService === null) {
|
||||
self::$icalService = new ICalService(self::log(), self::notification(), self::session(), self::purge());
|
||||
}
|
||||
return self::$icalService;
|
||||
}
|
||||
|
||||
public static function purge() {
|
||||
if (self::$purgeService === null) {
|
||||
self::$purgeService = new PurgeService(self::$connect, self::log());
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@
|
|||
"doctrine/dbal": "^2.9",
|
||||
"doctrine/migrations": "^2.1",
|
||||
"sensiolabs/ansi-to-html": "^1.1",
|
||||
"symfony/translation": "^3.4"
|
||||
"symfony/translation": "^3.4",
|
||||
"sabre/vobject": "~4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5",
|
||||
|
|
|
|||
222
composer.lock
generated
222
composer.lock
generated
|
|
@ -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": "88cff367db3f191004cc66d97ec56292",
|
||||
"content-hash": "d0ff551ecaebaaf09a6d4eedc17d6832",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
|
|
@ -757,6 +757,221 @@
|
|||
],
|
||||
"time": "2019-11-01T11:05:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/uri",
|
||||
"version": "2.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/uri.git",
|
||||
"reference": "f502edffafea8d746825bd5f0b923a60fd2715ff"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/uri/zipball/f502edffafea8d746825bd5f0b923a60fd2715ff",
|
||||
"reference": "f502edffafea8d746825bd5f0b923a60fd2715ff",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "~2.16.1",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Sabre\\Uri\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Functions for making sense out of URIs.",
|
||||
"homepage": "http://sabre.io/uri/",
|
||||
"keywords": [
|
||||
"rfc3986",
|
||||
"uri",
|
||||
"url"
|
||||
],
|
||||
"time": "2020-10-03T10:33:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/vobject",
|
||||
"version": "4.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/vobject.git",
|
||||
"reference": "3168acd7c22fa3c96cb115539f93b59818ba1f06"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/vobject/zipball/3168acd7c22fa3c96cb115539f93b59818ba1f06",
|
||||
"reference": "3168acd7c22fa3c96cb115539f93b59818ba1f06",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabre/xml": "^2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "~2.17.1",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/php-invoker": "^2.0 || ^3.1",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.0"
|
||||
},
|
||||
"suggest": {
|
||||
"hoa/bench": "If you would like to run the benchmark scripts"
|
||||
},
|
||||
"bin": [
|
||||
"bin/vobject",
|
||||
"bin/generate_vcards"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabre\\VObject\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Dominik Tobschall",
|
||||
"email": "dominik@fruux.com",
|
||||
"homepage": "http://tobschall.de/",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Ivan Enderlin",
|
||||
"email": "ivan.enderlin@hoa-project.net",
|
||||
"homepage": "http://mnt.io/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
|
||||
"homepage": "http://sabre.io/vobject/",
|
||||
"keywords": [
|
||||
"availability",
|
||||
"freebusy",
|
||||
"iCalendar",
|
||||
"ical",
|
||||
"ics",
|
||||
"jCal",
|
||||
"jCard",
|
||||
"recurrence",
|
||||
"rfc2425",
|
||||
"rfc2426",
|
||||
"rfc2739",
|
||||
"rfc4770",
|
||||
"rfc5545",
|
||||
"rfc5546",
|
||||
"rfc6321",
|
||||
"rfc6350",
|
||||
"rfc6351",
|
||||
"rfc6474",
|
||||
"rfc6638",
|
||||
"rfc6715",
|
||||
"rfc6868",
|
||||
"vCalendar",
|
||||
"vCard",
|
||||
"vcf",
|
||||
"xCal",
|
||||
"xCard"
|
||||
],
|
||||
"time": "2021-02-04T09:59:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabre/xml",
|
||||
"version": "2.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sabre-io/xml.git",
|
||||
"reference": "c3b959f821c19b36952ec4a595edd695c216bfc6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sabre-io/xml/zipball/c3b959f821c19b36952ec4a595edd695c216bfc6",
|
||||
"reference": "c3b959f821c19b36952ec4a595edd695c216bfc6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-xmlreader": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"lib-libxml": ">=2.6.20",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabre/uri": ">=1.0,<3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "~2.16.1",
|
||||
"phpstan/phpstan": "^0.12",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabre\\Xml\\": "lib/"
|
||||
},
|
||||
"files": [
|
||||
"lib/Deserializer/functions.php",
|
||||
"lib/Serializer/functions.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Evert Pot",
|
||||
"email": "me@evertpot.com",
|
||||
"homepage": "http://evertpot.com/",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Markus Staab",
|
||||
"email": "markus.staab@redaxo.de",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "sabre/xml is an XML library that you may not hate.",
|
||||
"homepage": "https://sabre.io/xml/",
|
||||
"keywords": [
|
||||
"XMLReader",
|
||||
"XMLWriter",
|
||||
"dom",
|
||||
"xml"
|
||||
],
|
||||
"time": "2020-10-03T10:08:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sensiolabs/ansi-to-html",
|
||||
"version": "v1.1.3",
|
||||
|
|
@ -1214,6 +1429,7 @@
|
|||
"code",
|
||||
"zf"
|
||||
],
|
||||
"abandoned": "laminas/laminas-code",
|
||||
"time": "2019-10-05T23:18:22+00:00"
|
||||
},
|
||||
{
|
||||
|
|
@ -1268,6 +1484,7 @@
|
|||
"events",
|
||||
"zf2"
|
||||
],
|
||||
"abandoned": "laminas/laminas-eventmanager",
|
||||
"time": "2018-04-25T15:33:34+00:00"
|
||||
}
|
||||
],
|
||||
|
|
@ -3887,5 +4104,6 @@
|
|||
"platform-dev": [],
|
||||
"platform-overrides": {
|
||||
"php": "7.1.0"
|
||||
}
|
||||
},
|
||||
"plugin-api-version": "1.1.0"
|
||||
}
|
||||
|
|
|
|||
4
po/de.po
4
po/de.po
|
|
@ -1928,6 +1928,10 @@ msgstr ""
|
|||
"Benutzer, die mit \"Ja\" für diese Option abgestimmt haben, haben folgende E-"
|
||||
"Mail-Adressen:"
|
||||
|
||||
#: .studs.Download+as+ical/ics+file
|
||||
msgid "Download as ical/ics file"
|
||||
msgstr "Als ical/ics-Datei herunterladen"
|
||||
|
||||
#: .studs.Deletion+date:
|
||||
msgid "Deletion date:"
|
||||
msgstr "Löschdatum:"
|
||||
|
|
|
|||
4
po/en.po
4
po/en.po
|
|
@ -1893,6 +1893,10 @@ msgid "Users who voted \"Yes\" for this option have left those email addresses:"
|
|||
msgstr ""
|
||||
"Users who voted \"Yes\" for this option have left those email addresses:"
|
||||
|
||||
#: .studs.Download+as+ical/ics+file
|
||||
msgid "Download as ical/ics file"
|
||||
msgstr "Download as ical/ics file"
|
||||
|
||||
#: .studs.Deletion+date:
|
||||
msgid "Deletion date:"
|
||||
msgstr "Deletion date:"
|
||||
|
|
|
|||
15
studs.php
15
studs.php
|
|
@ -50,6 +50,7 @@ $selectedNewVotes = [];
|
|||
/* Services */
|
||||
/*----------*/
|
||||
|
||||
$icalService = Services::ical();
|
||||
$inputService = Services::input();
|
||||
$notificationService = Services::notification();
|
||||
$pollService = Services::poll();
|
||||
|
|
@ -218,6 +219,20 @@ function getMessageForOwnVoteEditableVote(SessionService &$sessionService, Smart
|
|||
return $message;
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Get iCal file
|
||||
// -------------------------------
|
||||
if (isset($_GET['get_ical_file'])) {
|
||||
$dayAndTime = strval(filter_input(INPUT_GET, 'get_ical_file', FILTER_DEFAULT));
|
||||
$dayAndTime = strval(Utils::base64url_decode($dayAndTime));
|
||||
$elements = explode("|", $dayAndTime);
|
||||
if(count($elements) > 1) {
|
||||
$icalService->getEvent($poll, strval($elements[0]), strval($elements[1]));
|
||||
}
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
echo 'Internal error';
|
||||
}
|
||||
|
||||
// Retrieve data
|
||||
if ($resultPubliclyVisible || $accessGranted) {
|
||||
$slots = $pollService->allSlotsByPoll($poll);
|
||||
|
|
|
|||
|
|
@ -480,8 +480,12 @@
|
|||
{foreach $slots as $slot}
|
||||
{foreach $slot->moments as $moment}
|
||||
{if $best_choices['y'][$i] == $max}
|
||||
<li><strong>{$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}</strong></li>
|
||||
{/if}
|
||||
{assign var="space" value="`$slot->day|date_format_intl:DATE_FORMAT_DATE|html`|`$moment`"}
|
||||
<li><strong>{$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}
|
||||
<a href="{poll_url id=$poll_id action='get_ical_file' action_value=($space)}" class="btn btn-link btn-sm" title="{t('studs', 'Download as ical/ics file')}">
|
||||
<i class="fa fa-calendar text-muted" aria-hidden="true"></i></a>
|
||||
</strong></li>
|
||||
{/if}
|
||||
{$i = $i+1}
|
||||
{/foreach}
|
||||
{/foreach}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue