merge changes

This commit is contained in:
Andreas Bräu 2019-03-18 12:33:06 +01:00
commit 5a99cdcc3b
457 changed files with 63199 additions and 38634 deletions

View file

@ -12,6 +12,7 @@ namespace Hackspace\Bundle\CalciferBundle\Command;
use Doctrine\ORM\EntityManager;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Entity\RepeatingEvent;
use Hackspace\Bundle\CalciferBundle\Entity\RepeatingEventLogEntry;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@ -52,7 +53,6 @@ class GenerateEventsCommand extends ContainerAwareCommand
$event = null;
while (($next_date = $parser->getNext()) < $end) {
/** @var \DateTime $next_date */
$output->writeln(sprintf("Creating Event %s for %s",$entity->summary,$next_date->format('Y-m-d H:i')));
$event = new Event();
$event->location = $entity->location;
$event->startdate = $next_date;
@ -75,6 +75,12 @@ class GenerateEventsCommand extends ContainerAwareCommand
$event->addTag($tag);
}
$entityManager->persist($event);
$logEntry = new RepeatingEventLogEntry();
$logEntry->event = $event;
$logEntry->repeating_event = $entity;
$logEntry->event_startdate = $event->startdate;
$logEntry->event_enddate = $event->enddate;
$entityManager->persist($logEntry);
$entityManager->flush();
$parser->setNow($next_date);
}

View file

@ -15,19 +15,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Utility\Formatter;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\CalendarExport;
use
Sabre\VObject,
Sabre\CalDAV,
Sabre\DAV,
Sabre\DAVACL,
Sabre\DAV\Exception\Forbidden,
Hackspace\Bundle\CalciferBundle\libs\CalciferCaldavBackend,
Hackspace\Bundle\CalciferBundle\libs\CalciferPrincipalBackend;
use Sabre\VObject;
/**
* Event controller.
*
@ -35,45 +24,6 @@ use
*/
class EventController extends Controller
{
/**
* Finds and displays a Event entity.
*
* @Route("/{url}", name="events_caldav", requirements={"url" : "caldav(.+)"})
*/
public function caldavEntry()
{
// Backends
$calendarBackend = new CalciferCaldavBackend($this);
$principalBackend = new CalciferPrincipalBackend();
// Directory structure
$tree = [
new CalDAV\CalendarRootNode($principalBackend, $calendarBackend),
];
$server = new DAV\Server($tree);
$server->setBaseUri('/caldav');
/*$aclPlugin = new DAVACL\Plugin();
$aclPlugin->allowAccessToNodesWithoutACL = false;
$server->addPlugin($aclPlugin);*/
/* CalDAV support */
$caldavPlugin = new CalDAV\Plugin();
$server->addPlugin($caldavPlugin);
/* WebDAV-Sync plugin */
$server->addPlugin(new DAV\Sync\Plugin());
// Support for html frontend
$browser = new DAV\Browser\Plugin();
$server->addPlugin($browser);
// And off we go!
$server->exec();
return new Response();
}
/**
* Lists all Event entities as ICS.
*
@ -96,22 +46,14 @@ class EventController extends Controller
->setParameter('startdate', $now);
$entities = $qb->getQuery()->execute();
$calendar = new Calendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
$vcalendar = new VObject\Component\VCalendar();
foreach ($entities as $entity) {
/** @var Event $entity */
$event = $entity->ConvertToCalendarEvent();
$calendar->addEvent($event);
/** @var Event $entity */
$vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
}
$calendarExport = new CalendarExport(new CalendarStream, new Formatter());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response = new Response($vcalendar->serialize());
$response->headers->set('Content-Type', 'text/calendar');
return $response;

View file

@ -16,12 +16,9 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Utility\Formatter;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\CalendarExport;
use Symfony\Component\HttpFoundation\AcceptHeader;
use Sabre\VObject;
/**
* Location controller.
*
@ -68,24 +65,15 @@ class LocationController extends Controller
$entities = $qb->getQuery()->execute();
if ($format == 'ics') {
$calendar = new Calendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
$vcalendar = new VObject\Component\VCalendar();
foreach ($entities as $entity) {
/** @var Event $entity */
$event = $entity->ConvertToCalendarEvent();
$calendar->addEvent($event);
/** @var Event $entity */
$vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
}
$calendarExport = new CalendarExport(new CalendarStream, new Formatter());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response = new Response($vcalendar->serialize());
$response->headers->set('Content-Type', 'text/calendar');
return $response;
} else {
return array(
@ -213,8 +201,13 @@ class LocationController extends Controller
);
}
$retval = [
"success" => true,
"results" => $locations,
];
$response = new Response(json_encode($locations));
$response = new Response(json_encode($retval));
$response->headers->set('Content-Type', 'application/json');
return $response;

View file

@ -48,6 +48,28 @@ class RepeatingEventController extends Controller
];
}
/**
* Displays all repeating events
*
* @Route("/logs", name="repeating_event_logs")
* @Method("GET")
* @Template()
*/
public function logIndexAction()
{
/** @var EntityManager $em */
$em = $this->getDoctrine()->getManager();
/** @var EntityRepository $repo */
$repo = $em->getRepository('CalciferBundle:RepeatingEventLogEntry');
$entities = $repo->findBy([], ['event_startdate' => 'DESC']);
return [
'entities' => $entities,
];
}
/**
* Displays a form to create a repeating event
*

View file

@ -7,7 +7,6 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Hackspace\Bundle\CalciferBundle\Entity\Location;
use Hackspace\Bundle\CalciferBundle\Entity\Tag;
use Jsvrcek\ICS\Model\Description\Geo;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
@ -16,18 +15,12 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Hackspace\Bundle\CalciferBundle\Form\EventType;
use Symfony\Component\HttpFoundation\Response;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Model\CalendarEvent;
use Jsvrcek\ICS\Model\Relationship\Attendee;
use Jsvrcek\ICS\Model\Relationship\Organizer;
use Jsvrcek\ICS\Utility\Formatter;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\CalendarExport;
use Symfony\Component\Validator\Constraints\DateTime;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Component\HttpFoundation\AcceptHeader;
use Sabre\VObject;
/**
* Tag controller.
*
@ -143,23 +136,14 @@ EOF;
}
if ($format == 'ics') {
$calendar = new Calendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
$calendar->setTimeZone(new \DateTimeZone('Europe/Berlin'));
$vcalendar = new VObject\Component\VCalendar();
foreach ($entities as $entity) {
/** @var Event $entity */
$event = $entity->ConvertToCalendarEvent();
$calendar->addEvent($event);
/** @var Event $entity */
$vcalendar->add('VEVENT',$entity->ConvertToCalendarEvent());
}
$calendarExport = new CalendarExport(new CalendarStream, new Formatter());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$result = $calendarExport->getStream();
$response = new Response($result);
$response = new Response($vcalendar->serialize());
$response->headers->set('Content-Type', 'text/calendar');
return $response;
@ -202,8 +186,13 @@ EOF;
];
}
$retval = [
'success' => true,
'results' => $tags,
];
$response = new Response(json_encode($tags));
$response = new Response(json_encode($retval));
$response->headers->set('Content-Type', 'application/json');
return $response;

View file

@ -5,17 +5,7 @@ namespace Hackspace\Bundle\CalciferBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Jsvrcek\ICS\Model\Description\Location As EventLocation;
use Symfony\Component\Validator\Constraints\DateTime;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Model\CalendarEvent;
use Jsvrcek\ICS\Model\Relationship\Attendee;
use Jsvrcek\ICS\Model\Relationship\Organizer;
use Jsvrcek\ICS\Utility\Formatter;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\CalendarExport;
use Jsvrcek\ICS\Model\Description\Geo;
/**
* Event
@ -144,32 +134,41 @@ class Event extends BaseEntity
public function ConvertToCalendarEvent() {
$event = new CalendarEvent();
$event->setStart($this->startdate);
if ($this->enddate instanceof \DateTime)
$event->setEnd($this->enddate);
$event->setSummary($this->summary);
$event->setUrl($this->url);
$uid = sprintf("https://%s/termine/%s",$_SERVER['HTTP_HOST'],$this->slug);
$event->setUid($uid);
if (count($this->tags) > 0) {
$categories = [];
foreach($this->tags as $tag) {
$event->addCategory($tag->name);
}
$categories = [];
foreach($this->tags as $tag) {
$categories[] = $tag->name;
}
if (array_key_exists('HTTP_HOST',$_SERVER)) {
$uid = sprintf("https://%s/termine/%s",$_SERVER['HTTP_HOST'],$this->slug);
} else {
$uid = sprintf("https://localhost/termine/%s",$this->slug);
}
$event = [
'SUMMARY' => $this->summary,
'DTSTART' => $this->startdate,
'DESCRIPTION' => $this->description,
'URL' => $this->url,
'CATEGORIES' => $categories,
'UID' => $uid,
];
if (!is_null($this->enddate))
$event["DTEND"] = $this->enddate;
if ($this->location instanceof Location) {
$location = new EventLocation();
$location->setName($this->location->name);
$event->setLocations([$location]);
$event["LOCATION"] = $this->location->name;
if (\is_float($this->location->lon) && \is_float($this->location->lat)) {
$geo = new Geo();
$geo->setLatitude($this->location->lat);
$geo->setLongitude($this->location->lon);
$event->setGeo($geo);
$event["GEO"] = [$this->location->lat, $this->location->lon];
}
}
$event->setDescription($this->description);
if (!array_key_exists('HTTP_HOST',$_SERVER)) {
$dtstamp = new \DateTime();
$dtstamp->setDate(2016,06,27);
$dtstamp->setTime(0,0,0);
$event['DTSTAMP'] = $dtstamp;
}
return $event;
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* Created by PhpStorm.
* User: tim
* Date: 21.11.2016
* Time: 21:15
*/
namespace Hackspace\Bundle\CalciferBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Class RepeatingEventLogEntry
* @package Hackspace\Bundle\CalciferBundle\Entity
*
* @property RepeatingEvent $repeating_event
* @property Event $event
* @property \DateTime $event_startdate
* @property \DateTime $event_enddate
*
* @ORM\Table(name="repeating_events_log_entries")
* @ORM\Entity
*/
class RepeatingEventLogEntry extends BaseEntity
{
/**
* @var integer
*
* @ORM\Column(name="repeating_events_id", type="integer", nullable=false)
*/
protected $repeating_events_id;
/**
* @var RepeatingEvent
*
* @ORM\ManyToOne(targetEntity="RepeatingEvent")
* @ORM\JoinColumn(name="repeating_events_id", referencedColumnName="id")
*/
protected $repeating_event;
/**
* @var string
*
* @ORM\Column(name="events_id", type="integer", nullable=false)
*/
protected $events_id;
/**
* @var Event
*
* @ORM\ManyToOne(targetEntity="Event")
* @ORM\JoinColumn(name="events_id", referencedColumnName="id")
*/
protected $event;
/**
* @var \DateTime
*
* @ORM\Column(name="event_startdate", type="datetimetz")
*/
protected $event_startdate;
/**
* @var \DateTime
*
* @ORM\Column(name="event_enddate", type="datetimetz", nullable=true)
*/
protected $event_enddate;
}

View file

@ -55,7 +55,7 @@ trait TagTrait
}
return implode(',', $tags);
} else {
return '';
return null;
}
}
}

View file

@ -18,6 +18,34 @@
height: auto;
max-height: none;
}
#event_location.ui.dropdown.active {
.text .ui.fluid.card {
opacity: 0.5;
}
}
#event_location.ui.dropdown {
input.search {
height: 100%;
line-height: 100%;
width: 100%;
}
.default.text {
color: black;
}
.text {
width: 100%;
.ui.fluid.card {
.description {
display: none;
}
}
}
}
}
#view-map, #map {

View file

@ -13,6 +13,12 @@ jQuery(document).ready(function () {
jQuery('.icon.link').popup();
}
$('.ui.sticky')
.sticky({
context: '#main'
})
;
if (jQuery('input[type=datetime]').length > 0) {
jQuery('input[type=datetime]').datetimepicker({lang: 'de', format: 'Y-m-d H:i'});
}
@ -109,85 +115,71 @@ $(document).ready(function() {
if (card_selector.length > 0) {
calcBoxSize(4);
}
$location = $('#event_location');
$('#event_tags').selectize({
create: true,
diacritics: true,
valueField: 'name',
labelField: 'name',
searchField: 'name',
render: {
item: function(data,escape){
console.log([data,escape]);
return '<div class="ui green compact small label"><i class="tag icon"></i>' + escape(data.name) + '</div>';
}
},
load: function(query, callback) {
if (!query.length) return callback();
$.ajax({
url: "/tags/",
type: "GET",
dataType: 'json',
data: {
q: query
if ($location.length == 1) {
$('#event_location')
.dropdown({
minCharacters: 4,
allowAdditions: true,
apiSettings: {
url: '/orte/?q={query}'
},
error: function() {
callback();
fields: {
remoteValues : 'results', // grouping for api results
values : 'values', // grouping for all dropdown values
name : 'name', // displayed dropdown text
value : 'name' // actual dropdown value
},
success: function(res) {
console.log(res);
callback(res);
templates: {
menu: function(response, fields) {
var
values = response[fields.values] || {},
html = ''
;
$.each(values, function(index, option) {
var item = option;
html += '<div class="item" data-value="' + option[fields.value] + '"><div class="ui fluid green card">' +
'<div class="content">'+
'<div class="header">' +
'<i class="ui icon map marker"></i>' + item.name +
'</div>' +
'<div class="meta">'+
(item.lon && item.lat ? 'lon: '+ escape(item.lon)+' lat: ' + escape(item.lat) : '')+
(item.streetaddress ? ' Anschrift: ' + item.streetaddress + ' ' + item.streetnumber + ' ' + item.zipcode + ' ' + item.city : '')+
'</div>'+
(item.description ? '<div class="description">' + item.description + '</div>' : '') +
'</div>'+
'</div></div>';
});
return html;
}
}
});
}
});
})
;
}
$('#event_location').selectize({
create: true,
diacritics: true,
valueField: 'name',
labelField: 'name',
searchField: 'name',
maxItems: 1,
render: {
item: function(data,escape){
console.log([data,escape]);
return '<div class="ui green compact small label"><i class="map marker icon"></i>' + escape(data.name) + '</div>';
},
option: function(item, escape) {
return '<div class="ui fluid green card">' +
'<div class="content">'+
'<div class="header">' +
'<i class="ui icon map marker"></i>' + escape(item.name) +
'</div>' +
'<div class="meta">'+
(item.lon && item.lat ? 'lon: '+ escape(item.lon)+' lat: ' + escape(item.lat) : '')+
(item.streetaddress ? ' Anschrift: ' + item.streetaddress + ' ' + item.streetnumber + ' ' + item.zipcode + ' ' + item.city : '')+
'</div>'+
(item.description ? '<div class="description">' + item.description + '</div>' : '') +
'</div>'+
'</div>';
$('#event_tags')
.dropdown({
minCharacters: 2,
allowAdditions: true,
apiSettings: {
url: '/tags/?q={query}'
},
fields: {
remoteValues: 'results', // grouping for api results
values: 'values', // grouping for all dropdown values
name: 'name', // displayed dropdown text
value: 'name' // actual dropdown value
}/*,
templates: {
label: function (value, text) {
return '<i class="tag icon"></i>' + text + '<i class="delete icon"></i>';
}
}*/
}
},
load: function(query, callback) {
if (!query.length) return callback();
$.ajax({
url: "/orte/",
type: "GET",
dataType: 'json',
data: {
q: query
},
error: function() {
callback();
},
success: function(res) {
console.log(res);
callback(res);
}
});
}
});
);
if (view_map_selector.length == 1) {
jQuery('.show_map').click(addGeoCoordinates);

View file

@ -8,7 +8,6 @@
%}
<link rel="stylesheet" href="{{ asset_url }}"/>
{% endstylesheets %}
<link rel="stylesheet" href="/vendor/selectize.js/dist/css/selectize.css"/>
{% endblock %}
{% block javascripts %}
@ -19,7 +18,6 @@
%}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
<script src="/vendor/selectize.js/dist/js/standalone/selectize.js"></script>
{% endblock %}
{% block body -%}

View file

@ -80,16 +80,37 @@
</div>
</div>
<div class="required field">
<div class="field">
<label for="event_location">Ort</label>
<div class="ui icon input attached-{% if entity.location.lat|default(0) > 0 %}geo-{% endif %}label">
<input type="text"
id="event_location"
name="location"
maxlength="255"
value="{{ entity.location.name|default('') }}"
class="form-control">
<div id="event_location" class="ui fluid search selection dropdown">
<input type="hidden" name="location">
<div class="default text">
{% if entity.location is null %}
Wähle einen Ort…
{% else %}
<div class="ui fluid green card">
<div class="content">
<div class="header">
<i class="ui icon map marker"></i> {{ entity.location.name }}
</div>
<div class="meta">
{% if (not entity.location.lat is null) and (not entity.location.lon is null) %}
lon: {{ entity.location.lon }}, lat: {{ entity.location.lat }}
{% endif %}
{% if (not entity.location.streetaddress) %}
Anschrift: {{ entity.location.streetaddress }} {{ entity.location.streetnumber }} {{ entity.location.zipcode }} {{ entity.location.city }}
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
<div class="menu">
</div>
</input>
</div>
<input type="hidden" name="location_lat" value="{{ entity.location.lat|default('') }}">
<input type="hidden" name="location_lon" value="{{ entity.location.lon|default('') }}">
<i class="icon map marker"></i>
@ -123,11 +144,17 @@
<label for="event_tags">Tags</label>
<div class="ui icon input">
<input type="text"
id="event_tags"
name="tags"
value="{{ entity.getTagsAsText() }}"
class="form-control">
<div class="ui fluid search multiple selection dropdown" id="event_tags">
<input name="tags" type="hidden" value="{{ entity.getTagsAsText()|default('') }}">
<div class="default text">Du kannst hier kommasepariert Tags angeben…</div>
<div class="menu">
{% for tag in entity.tags %}
<div class="item" data-value="{{ tag.name }}">{{ tag.name }}</div>
{% endfor %}
</div>
</input>
</div>
<i class="icon tag"></i>
</div>
<div class="ui label">Du kannst hier kommasepariert <a
@ -164,5 +191,5 @@
</div>
</div>
<input type="submit" class="ui button green" value="Speichern"/>
<input type="submit" class="ui button green" name="save" value="Speichern"/>
</form>

View file

@ -0,0 +1,58 @@
{% extends 'CalciferBundle::layout.html.twig' %}
{% block css %}
{% stylesheets filter="compass"
"@CalciferBundle/Resources/assets/css/events.scss" %}
<link rel="stylesheet" href="{{ asset_url }}"/>
{% endstylesheets %}
{% endblock %}
{% block javascripts %}
{% javascripts
"@CalciferBundle/Resources/assets/js/events.js" %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
{% block body -%}
<div class="ui basic segment">
<h1 class="ui green block dividing header">
Logeinträge für Wiederholende Termine
</h1>
</div>
<div class="ui basic segment">
<table class="ui table">
<thead>
<tr>
<th>Wiederholender Termin</th>
<th>Termin</th>
<th>Termin Startdatum</th>
<th>Termin Enddatum</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr>
<td>
{{ entity.repeating_event.summary }}
</td>
<td>
<a href="{{ path('_show', {'slug':entity.event.slug}) }}">{{ entity.event.summary }}</a>
</td>
<td>
{{ entity.event_startdate.format('Y-m-d H:i') }}
</td>
<td>
{% if entity.event_enddate %}
{{ entity.event_enddate.format('Y-m-d H:i') }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -26,8 +26,8 @@
<p>Es gibt 2 verschiedene Wiederholungsmustertypen. Feste Termine oder Interval Termine.</p>
<p>Der erste definiert sich dadurch das der Termin immer an einem bestimmten Tag im Monat passieren soll.
Das Hackspace-Plenum findet z.B. am „<code>Zweiten Freitag des Monats</code>“ statt. Die
Sicherheitssprechstunde findet immer am „<code>Ersten Dienstag des Monats</code>“ statt. Anhand dieser
Das Hackspace-Plenum findet z.B. am „<code>Zweiter Freitag des Monats</code>“ statt. Die
Sicherheitssprechstunde findet immer am „<code>Erster Dienstag des Monats</code>“ statt. Anhand dieser
Beispiele sollte eigentlich klar sein wie dieses Wiederholungsmuster funktioniert:</p>
<p>An erster stelle definiert man die Woche: Erster, Zweiter, Dritter, Letzter (In manchen Fällen kann
@ -42,4 +42,4 @@
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -8,7 +8,6 @@
<link href="/semantic/dist/semantic.css" rel="stylesheet">
{% stylesheets filter="compass"
"@CalciferBundle/Resources/assets/css/main.scss"
"css/custom.scss"
%}
<link rel="stylesheet" href="{{ asset_url }}"/>
{% endstylesheets %}
@ -19,15 +18,15 @@
</head>
<body>
{% include "CalciferBundle::navigation.html.twig" %}
<div class="ui sticky">
{% include "CalciferBundle::navigation.html.twig" %}
</div>
<div id="main">
{% block body %}{% endblock %}
</div>
<!-- jQuery (necessary for Bootstraps JavaScript plugins) -->
<script src="{{ asset('js/jquery.js') }}"></script>
<!-- Include all JavaScripts, compiled by Assetic -->
<script src="{{ asset('semantic/dist/semantic.min.js') }}"></script>
{% block javascripts %}{% endblock %}
</body>
</html>
</html>

View file

@ -1,8 +1,7 @@
<header class="ui fixed green main menu">
<div class="ui basic segment">
<div class="title {% if app.request.pathinfo == path('') %}active {% endif %}item"><a href="{{ path('') }}">Start</a></div>
<div class="{% if app.request.pathinfo == path('_new') %}active {% endif %}item"><a href="{{ path('_new') }}">Neuer Termin</a></div>
<div class="{% if app.request.pathinfo == path('repeating_event_show') %}active {% endif %}item"><a href="{{ path('repeating_event_show') }}">Wiederholende Termine</a></div>
<div class="{% if app.request.pathinfo == path('about_calcifer') %}active {% endif %}item"><a href="{{ path('about_calcifer') }}">Über Calcifer</a></div>
</div>
</header>
<header class="ui green main menu">
<a class="title {% if app.request.pathinfo == path('') %}active {% endif %}item" href="{{ path('') }}">Start</a>
<a class="{% if app.request.pathinfo == path('_new') %}active {% endif %}item" href="{{ path('_new') }}">Neuer Termin</a>
<a class="{% if app.request.pathinfo == path('repeating_event_show') %}active {% endif %}item" href="{{ path('repeating_event_show') }}">Wiederholende Termine</a>
<a class="{% if app.request.pathinfo == path('about_calcifer') %}active {% endif %}item" href="{{ path('about_calcifer') }}">Über Calcifer</a>
</header>

View file

@ -1,17 +0,0 @@
<?php
namespace Hackspace\Bundle\CalciferBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();
$crawler = $client->request('GET', '/hello/Fabien');
$this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);
}
}

View file

@ -2,54 +2,191 @@
namespace Hackspace\Bundle\CalciferBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
use Doctrine\ORM\EntityRepository;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
use Liip\FunctionalTestBundle\Test\WebTestCase;
class EventControllerTest extends WebTestCase
{
/*
public function testCompleteScenario()
/** @var \DateTime */
private $now = null;
/** @var \DateTime */
private $startdate = null;
/** @var \DateTime */
private $enddate = null;
const dateformat = "Y-m-d H:i";
/**
* EventControllerTest constructor.
*/
public function __construct($name = null, array $data = [], $dataName = '')
{
// Create a new client to browse the application
$client = static::createClient();
parent::__construct($name,$data,$dataName);
$this->now = new \DateTime();
$this->now->setTime(0,0,0);
// Create a new entry in the database
$crawler = $client->request('GET', '//');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET //");
$crawler = $client->click($crawler->selectLink('Create a new entry')->link());
$tz = new \DateTimeZone("Europe/Berlin");
$this->now->setTimezone($tz);
// Fill in the form and submit it
$form = $crawler->selectButton('Create')->form(array(
'hackspace_bundle_calciferbundle_event[field_name]' => 'Test',
// ... other fields to fill
));
$this->startdate = clone $this->now;
$this->startdate->add(new \DateInterval("P1D"));
$this->enddate = clone $this->now;
$this->enddate->add(new \DateInterval("P1DT2H"));
$client->submit($form);
$crawler = $client->followRedirect();
// Check data in the show view
$this->assertGreaterThan(0, $crawler->filter('td:contains("Test")')->count(), 'Missing element td:contains("Test")');
// Edit the entity
$crawler = $client->click($crawler->selectLink('Edit')->link());
$form = $crawler->selectButton('Update')->form(array(
'hackspace_bundle_calciferbundle_event[field_name]' => 'Foo',
// ... other fields to fill
));
$client->submit($form);
$crawler = $client->followRedirect();
// Check the element contains an attribute with value equals "Foo"
$this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(), 'Missing element [value="Foo"]');
// Delete the entity
$client->submit($crawler->selectButton('Delete')->form());
$crawler = $client->followRedirect();
// Check the entity has been delete on the list
$this->assertNotRegExp('/Foo/', $client->getResponse()->getContent());
}
*/
public static function runCommandStatic($name) {
$command = sprintf("php app/console %s", $name);
$output = "";
exec($command,$output);
return $output;
}
public static function setUpBeforeClass()
{
EventControllerTest::runCommandStatic("doctrine:database:drop --force --env=test");
EventControllerTest::runCommandStatic("doctrine:database:create --env=test");
EventControllerTest::runCommandStatic("doctrine:schema:create --env=test");
}
public function testEmptyListing() {
$client = static::makeClient();
$crawler = $client->request('GET', '/');
$this->assertStatusCode(200, $client);
}
public function testPostEventForm()
{
$client = static::makeClient();
$url = $client->getContainer()->get('router')->generate('_new');
$crawler = $client->request('GET', $url);
$this->assertStatusCode(200, $client);
$form = $crawler->selectButton('save')->form();
$form['startdate'] = $this->startdate->format(EventControllerTest::dateformat);
$form['enddate'] = $this->enddate->format(EventControllerTest::dateformat);
$form['summary'] = "Testevent";
$form['url'] = "https://calcifer.datenknoten.me";
$form["location"] = "Krautspace";
$form["location_lat"] = 1;
$form["location_lon"] = 2;
$form["tags"] = "foo,bar,krautspace";
$form["description"] = "Testdescription";
$crawler = $client->submit($form);
$this->assertStatusCode(302, $client);
$target = $client->getResponse()->headers->get('location');
$slug = explode("/",$target)[2];
$this->assertGreaterThan(0,strlen($slug));
/** @var EntityManagerDecorator $em */
$em = $this->getContainer()->get('doctrine')->getManager();
/** @var EntityRepository $repo */
$repo = $em->getRepository('CalciferBundle:Event');
$qb = $em->createQueryBuilder();
$qb->select(array('e'))
->from('CalciferBundle:Event', 'e')
->innerJoin('e.tags','t')
->innerJoin('e.location','l')
->where('e.slug>= :slug')
->setParameter('slug', $slug);
$entities = $qb->getQuery()->execute();
$this->assertCount(1,$entities);
/** @var Event $entity */
$entity = $entities[0];
$this->assertInstanceOf('Hackspace\Bundle\CalciferBundle\Entity\Event', $entity);
$this->assertTrue($this->startdate == $entity->startdate, "Startdate equal");
$this->assertTrue($this->enddate == $entity->enddate, "Enddate equal");
$this->assertTrue($form["summary"]->getValue() == $entity->summary, "Summary equal");
$this->assertTrue($form["url"]->getValue() == $entity->url, "URL equal");
$this->assertTrue($form["description"]->getValue() == $entity->description, "Description equal");
$tags = explode(",",$form["tags"]->getValue());
foreach($entity->tags as $tag) {
$this->assertTrue(in_array($tag->name,$tags));
}
$this->assertTrue($form["location"]->getValue() == $entity->location->name);
$this->assertTrue($form["location_lat"]->getValue() == $entity->location->lat);
$this->assertTrue($form["location_lon"]->getValue() == $entity->location->lon);
$em->close();
/** @var Registry $doc */
$doc = $this->getContainer()->get('doctrine');
foreach($doc->getConnections() as $connection) {
$connection->close();
}
}
public function testICS() {
$client = static::makeClient();
// events_ics
$url = $client->getContainer()->get('router')->generate('events_ics');
$crawler = $client->request('GET', $url);
$this->assertStatusCode(200, $client);
$test_doc = <<<EOF
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Sabre//Sabre VObject 4.1.1//EN
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:https://localhost/termine/testevent
DTSTAMP;TZID=Europe/Berlin:20160627T000000
SUMMARY:Testevent
DTSTART:%s
DESCRIPTION:Testdescription
URL;VALUE=URI:https://calcifer.datenknoten.me
CATEGORIES:foo,bar,krautspace
DTEND:%s
LOCATION:Krautspace
GEO:1;2
END:VEVENT
END:VCALENDAR
EOF;
$new_tz = new \DateTimeZone("UTC");
$this->startdate->setTimezone($new_tz);
$this->enddate->setTimezone($new_tz);
$start = $this->startdate->format("Ymd") . "T" . $this->startdate->format("His") . "Z";
$end = $this->enddate->format("Ymd") . "T" . $this->enddate->format("His") . "Z";
$test_doc = sprintf($test_doc,$start,$end);
$content = $client->getResponse()->getContent();
$content = preg_replace('~\R~u', "\r\n", $content);
$test_doc = preg_replace('~\R~u', "\r\n", $test_doc);
$this->assertGreaterThan(0,strlen($content));
$this->assertEquals($test_doc, $content);
}
}

View file

@ -1,312 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: tim
* Date: 15.11.14
* Time: 17:13
*/
namespace Hackspace\Bundle\CalciferBundle\libs;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\CalendarExport;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\Model\CalendarEvent;
use Jsvrcek\ICS\Utility\Formatter;
use Sabre\CalDAV\Backend\AbstractBackend;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Hackspace\Bundle\CalciferBundle\Entity\Event;
class CalciferCaldavBackend extends AbstractBackend
{
/** @var Controller */
private $controller = null;
function __construct(Controller $controller)
{
$this->controller = $controller;
}
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri)
{
return [[
'id' => 1,
'uri' => 'calendar',
'principaluri' => '/caldav/calcifer',
]];
}
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this calendar in other methods, such as updateCalendar.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
public function createCalendar($principalUri, $calendarUri, array $properties)
{
throw new \Exception('Not implemented');
}
/**
* Delete a calendar and all it's objects
*
* @param mixed $calendarId
* @return void
*/
public function deleteCalendar($calendarId)
{
throw new \Exception('Not implemented');
}
private function FormatCalendarEvent(CalendarEvent $event)
{
$stream = new CalendarStream();
$formatter = new Formatter();
$stream->addItem('BEGIN:VEVENT')
->addItem('UID:' . $event->getUid())
->addItem('DTSTART:' . $formatter->getFormattedUTCDateTime($event->getStart()))
->addItem('DTEND:' . $formatter->getFormattedUTCDateTime($event->getEnd()))
->addItem('SUMMARY:' . $event->getSummary())
->addItem('DESCRIPTION:' . $event->getDescription());
if ($event->getClass())
$stream->addItem('CLASS:' . $event->getClass());
/* @var $location Location */
foreach ($event->getLocations() as $location) {
$stream
->addItem('LOCATION' . $location->getUri() . $location->getLanguage() . ':' . $location->getName());
}
if ($event->getGeo())
$stream->addItem('GEO:' . $event->getGeo()->getLatitude() . ';' . $event->getGeo()->getLongitude());
if ($event->getUrl())
$stream->addItem('URL:' . $event->getUrl());
if ($event->getCreated())
$stream->addItem('CREATED:' . $formatter->getFormattedUTCDateTime($event->getCreated()));
if ($event->getLastModified())
$stream->addItem('LAST-MODIFIED:' . $formatter->getFormattedUTCDateTime($event->getLastModified()));
foreach ($event->getAttendees() as $attendee) {
$stream->addItem($attendee->__toString());
}
if ($event->getOrganizer())
$stream->addItem($event->getOrganizer()->__toString());
$stream->addItem('END:VEVENT');
return $stream->getStream();
}
private function formatEvent(Event $event)
{
/** @var CalendarEvent $calendar_event */
$calendar_event = $event->ConvertToCalendarEvent();
$calendar = new Calendar();
$calendar->setProdId('-//My Company//Cool Calendar App//EN');
$calendar->addEvent($calendar_event);
$calendarExport = new CalendarExport(new CalendarStream, new Formatter());
$calendarExport->addCalendar($calendar);
//output .ics formatted text
$calendar_data = $calendarExport->getStream();
$event_data = [
'id' => $event->id,
'uri' => $event->slug . '.ics',
'lastmodified' => $event->startdate,
'etag' => '"' . sha1($calendar_data) . '"',
'calendarid' => 1,
'calendardata' => $calendar_data,
'size' => strlen($calendar_data),
'component' => 'VEVENT',
];
return $event_data;
}
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * id - unique identifier which will be used for subsequent updates
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can
* be any arbitrary string, but making sure it ends with '.ics' is a
* good idea. This is only the basename, or filename, not the full
* path.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* '"abcdef"')
* * calendarid - The calendarid as it was passed to this function.
* * size - The size of the calendar objects, in bytes.
* * component - optional, a string containing the type of object, such
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
* the Content-Type header.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param mixed $calendarId
* @return array
*/
public function getCalendarObjects($calendarId)
{
/** @var EntityManager $em */
$em = $this->controller->getDoctrine()->getManager();
$now = new \DateTime();
$now->setTime(0, 0, 0);
/** @var QueryBuilder $qb */
$qb = $em->createQueryBuilder();
$qb->select(array('e'))
->from('CalciferBundle:Event', 'e')
->orderBy('e.startdate');
$entities = $qb->getQuery()->execute();
if (count($entities) > 0) {
$events = [];
foreach ($entities as $event) {
/** @var Event $event */
$events[] = $this->formatEvent($event);
}
return $events;
}
}
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* This method must return null if the object did not exist.
*
* @param mixed $calendarId
* @param string $objectUri
* @return array|null
*/
public function getCalendarObject($calendarId, $objectUri)
{
/** @var EntityManager $em */
$em = $this->controller->getDoctrine()->getManager();
/** @var EntityRepository $repo */
$repo = $em->getRepository('CalciferBundle:Event');
/** @var Event $entity */
$event = $repo->findOneBy(['slug' => substr($objectUri,0,strlen($objectUri) - 4)]);
if (!($event instanceof Event)) {
throw $this->controller->createNotFoundException('Unable to find Event entity.');
}
return $this->formatEvent($event);
}
/**
* Creates a new calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId, $objectUri, $calendarData)
{
// TODO: Implement createCalendarObject() method.
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId, $objectUri, $calendarData)
{
throw new \Exception('Not implemented');
}
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId, $objectUri)
{
throw new \Exception('Not implemented');
}
}

View file

@ -1,158 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: tim
* Date: 15.11.14
* Time: 19:45
*/
namespace Hackspace\Bundle\CalciferBundle\libs;
use
Sabre\DAV,
Sabre\DAVACL,
Sabre\HTTP\URLUtil;
class CalciferPrincipalBackend extends DAVACL\PrincipalBackend\AbstractBackend
{
/**
* Returns a list of principals based on a prefix.
*
* This prefix will often contain something like 'principals'. You are only
* expected to return principals that are in this base path.
*
* You are expected to return at least a 'uri' for every user, you can
* return any additional properties if you wish so. Common properties are:
* {DAV:}displayname
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
* field that's actually injected in a number of other properties. If
* you have an email address, use this property.
*
* @param string $prefixPath
* @return array
*/
function getPrincipalsByPrefix($prefixPath)
{
return [
[
'{DAV:}displayname' => 'calcifer',
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
'uri' => '/caldav/calcifer',
]
];
}
/**
* Returns a specific principal, specified by it's path.
* The returned structure should be the exact same as from
* getPrincipalsByPrefix.
*
* @param string $path
* @return array
*/
function getPrincipalByPath($path)
{
return [
'{DAV:}displayname' => 'calcifer',
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
'uri' => '/caldav/calcifer',
];
}
/**
* Updates one ore more webdav properties on a principal.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documenation for more info and examples.
*
* @param string $path
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch)
{
throw new \Exception('Not implemented');
}
/**
* This method is used to search for principals matching a set of
* properties.
*
* This search is specifically used by RFC3744's principal-property-search
* REPORT. You should at least allow searching on
* http://sabredav.org/ns}email-address.
*
* The actual search should be a unicode-non-case-sensitive search. The
* keys in searchProperties are the WebDAV property names, while the values
* are the property values to search on.
*
* If multiple properties are being searched on, the search should be
* AND'ed.
*
* This method should simply return an array with full principal uri's.
*
* If somebody attempted to search on a property the backend does not
* support, you should simply return 0 results.
*
* You can also just return 0 results if you choose to not support
* searching at all, but keep in mind that this may stop certain features
* from working.
*
* @param string $prefixPath
* @param array $searchProperties
* @return array
*/
function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof')
{
return [
[
'{DAV:}displayname' => 'calcifer',
'{http://sabredav.org/ns}email-address' => 'calcifer@example.com',
'uri' => '/caldav/calcifer',
]
];
}
/**
* Returns the list of members for a group-principal
*
* @param string $principal
* @return array
*/
function getGroupMemberSet($principal)
{
throw new \Exception('Not implemented');
}
/**
* Returns the list of groups a principal is a member of
*
* @param string $principal
* @return array
*/
function getGroupMembership($principal)
{
throw new \Exception('Not implemented');
}
/**
* Updates the list of group members for a group principal.
*
* The principals should be passed as a list of uri's.
*
* @param string $principal
* @param array $members
* @return void
*/
function setGroupMemberSet($principal, array $members)
{
throw new \Exception('Not implemented');
}
}