Add Caldav for member birthdays
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
e10742d298
commit
8fcbec95b0
|
@ -0,0 +1,339 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Dav;
|
||||||
|
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\User;
|
||||||
|
use Sabre\CalDAV\Backend\AbstractBackend;
|
||||||
|
use Sabre\DAV\PropPatch;
|
||||||
|
use Sabre\VObject\Component\VCard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template M as array{lastmodified: int, etag: string, uri: string, id: int, size: int}
|
||||||
|
*/
|
||||||
|
class CalendarBackend extends AbstractBackend
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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 is 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.
|
||||||
|
*
|
||||||
|
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
|
||||||
|
* ACL will automatically be put in read-only mode.
|
||||||
|
*
|
||||||
|
* @param string $principalUri
|
||||||
|
*
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
public function getCalendarsForUser($principalUri)
|
||||||
|
{
|
||||||
|
if (1 !== preg_match('/^principals\/(.*)$/', $principalUri, $matches)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
User::where('email', $matches[1])->firstOrFail();
|
||||||
|
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
'id' => 'birthdays',
|
||||||
|
'principaluri' => $principalUri,
|
||||||
|
'uri' => 'birthdays',
|
||||||
|
'{DAV:}displayname' => 'Geburtstage',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* The id can be any type, including ints, strings, objects or array.
|
||||||
|
*
|
||||||
|
* @param string $principalUri
|
||||||
|
* @param string $calendarUri
|
||||||
|
* @param array<string, mixed> $properties
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function createCalendar($principalUri, $calendarUri, array $properties)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates properties for a calendar.
|
||||||
|
*
|
||||||
|
* 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 documentation for more info and examples.
|
||||||
|
*
|
||||||
|
* @param mixed $calendarId
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a calendar and all its objects.
|
||||||
|
*
|
||||||
|
* @param mixed $calendarId
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deleteCalendar($calendarId)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all calendar objects within a calendar.
|
||||||
|
*
|
||||||
|
* Every item contains an array with the following keys:
|
||||||
|
* * 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"')
|
||||||
|
* * 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
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getCalendarObjects($calendarId)
|
||||||
|
{
|
||||||
|
return Member::whereNotNull('birthday')->get()->map(fn ($member) => $this->calendarObjectMeta($member))->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calendarObjectMeta(Member $member): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'calendardata' => $member->toCalendarObject()->serialize(),
|
||||||
|
'uri' => $member->slug . '.ics',
|
||||||
|
'lastmodified' => $member->updated_at->timestamp,
|
||||||
|
'etag' => '"' . $member->etag . '"',
|
||||||
|
'size' => strlen($member->toCalendarObject()->serialize()),
|
||||||
|
'component' => 'vevent',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
$member = Member::where('slug', str($objectUri)->replace('.ics', ''))->first();
|
||||||
|
|
||||||
|
if (!$member || !$member->toCalendarObject()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...$this->calendarObjectMeta($member),
|
||||||
|
'calendardata' => $member->toCalendarObject()->serialize(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of calendar objects.
|
||||||
|
*
|
||||||
|
* This method should work identical to getCalendarObject, but instead
|
||||||
|
* return all the calendar objects in the list as an array.
|
||||||
|
*
|
||||||
|
* If the backend supports this, it may allow for some speed-ups.
|
||||||
|
*
|
||||||
|
* @param mixed $calendarId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getMultipleCalendarObjects($calendarId, array $uris)
|
||||||
|
{
|
||||||
|
return Member::whereNotNull('birthday')->get()->map(fn ($member) => $this->getCalendarObject($calendarId, $member->slug . '.ics'))->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new calendar object.
|
||||||
|
*
|
||||||
|
* The object uri is only the basename, or filename and not a full path.
|
||||||
|
*
|
||||||
|
* It is possible to 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public function deleteCalendarObject($calendarId, $objectUri)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a calendar-query on the contents of this calendar.
|
||||||
|
*
|
||||||
|
* The calendar-query is defined in RFC4791 : CalDAV. Using the
|
||||||
|
* calendar-query it is possible for a client to request a specific set of
|
||||||
|
* object, based on contents of iCalendar properties, date-ranges and
|
||||||
|
* iCalendar component types (VTODO, VEVENT).
|
||||||
|
*
|
||||||
|
* This method should just return a list of (relative) urls that match this
|
||||||
|
* query.
|
||||||
|
*
|
||||||
|
* The list of filters are specified as an array. The exact array is
|
||||||
|
* documented by Sabre\CalDAV\CalendarQueryParser.
|
||||||
|
*
|
||||||
|
* Note that it is extremely likely that getCalendarObject for every path
|
||||||
|
* returned from this method will be called almost immediately after. You
|
||||||
|
* may want to anticipate this to speed up these requests.
|
||||||
|
*
|
||||||
|
* This method provides a default implementation, which parses *all* the
|
||||||
|
* iCalendar objects in the specified calendar.
|
||||||
|
*
|
||||||
|
* This default may well be good enough for personal use, and calendars
|
||||||
|
* that aren't very large. But if you anticipate high usage, big calendars
|
||||||
|
* or high loads, you are strongly adviced to optimize certain paths.
|
||||||
|
*
|
||||||
|
* The best way to do so is override this method and to optimize
|
||||||
|
* specifically for 'common filters'.
|
||||||
|
*
|
||||||
|
* Requests that are extremely common are:
|
||||||
|
* * requests for just VEVENTS
|
||||||
|
* * requests for just VTODO
|
||||||
|
* * requests with a time-range-filter on either VEVENT or VTODO.
|
||||||
|
*
|
||||||
|
* ..and combinations of these requests. It may not be worth it to try to
|
||||||
|
* handle every possible situation and just rely on the (relatively
|
||||||
|
* easy to use) CalendarQueryValidator to handle the rest.
|
||||||
|
*
|
||||||
|
* Note that especially time-range-filters may be difficult to parse. A
|
||||||
|
* time-range filter specified on a VEVENT must for instance also handle
|
||||||
|
* recurrence rules correctly.
|
||||||
|
* A good example of how to interprete all these filters can also simply
|
||||||
|
* be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
|
||||||
|
* as possible, so it gives you a good idea on what type of stuff you need
|
||||||
|
* to think of.
|
||||||
|
*
|
||||||
|
* @param mixed $calendarId
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function calendarQuery($calendarId, array $filters)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches through all of a users calendars and calendar objects to find
|
||||||
|
* an object with a specific UID.
|
||||||
|
*
|
||||||
|
* This method should return the path to this object, relative to the
|
||||||
|
* calendar home, so this path usually only contains two parts:
|
||||||
|
*
|
||||||
|
* calendarpath/objectpath.ics
|
||||||
|
*
|
||||||
|
* If the uid is not found, return null.
|
||||||
|
*
|
||||||
|
* This method should only consider * objects that the principal owns, so
|
||||||
|
* any calendars owned by other principals that also appear in this
|
||||||
|
* collection should be ignored.
|
||||||
|
*
|
||||||
|
* @param string $principalUri
|
||||||
|
* @param string $uid
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getCalendarObjectByUID($principalUri, $uid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,9 @@ use Illuminate\Support\ServiceProvider as BaseServiceProvider;
|
||||||
use LaravelSabre\Http\Auth\AuthBackend;
|
use LaravelSabre\Http\Auth\AuthBackend;
|
||||||
use LaravelSabre\LaravelSabre;
|
use LaravelSabre\LaravelSabre;
|
||||||
use Sabre\CardDAV\AddressBookRoot;
|
use Sabre\CardDAV\AddressBookRoot;
|
||||||
|
use Sabre\CalDAV\CalendarRoot;
|
||||||
use Sabre\CardDAV\Plugin as CardDAVPlugin;
|
use Sabre\CardDAV\Plugin as CardDAVPlugin;
|
||||||
|
use Sabre\CalDAV\Plugin as CalDAVPlugin;
|
||||||
use Sabre\DAV\Auth\Plugin as AuthPlugin;
|
use Sabre\DAV\Auth\Plugin as AuthPlugin;
|
||||||
use Sabre\DAV\Browser\Plugin as BrowserPlugin;
|
use Sabre\DAV\Browser\Plugin as BrowserPlugin;
|
||||||
use Sabre\DAVACL\AbstractPrincipalCollection;
|
use Sabre\DAVACL\AbstractPrincipalCollection;
|
||||||
|
@ -42,11 +44,12 @@ class ServiceProvider extends BaseServiceProvider
|
||||||
{
|
{
|
||||||
$principalBackend = new Principal();
|
$principalBackend = new Principal();
|
||||||
$addressBookBackend = new AddressBookBackend();
|
$addressBookBackend = new AddressBookBackend();
|
||||||
|
$calendarBackend = new CalendarBackend();
|
||||||
|
|
||||||
// Directory tree
|
|
||||||
return [
|
return [
|
||||||
new PrincipalCollection($principalBackend),
|
new PrincipalCollection($principalBackend),
|
||||||
new AddressBookRoot($principalBackend, $addressBookBackend),
|
new AddressBookRoot($principalBackend, $addressBookBackend),
|
||||||
|
new CalendarRoot($principalBackend, $calendarBackend),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +61,7 @@ class ServiceProvider extends BaseServiceProvider
|
||||||
new BrowserPlugin(),
|
new BrowserPlugin(),
|
||||||
new AuthPlugin($authBackend),
|
new AuthPlugin($authBackend),
|
||||||
new CardDAVPlugin(),
|
new CardDAVPlugin(),
|
||||||
|
new CalDAVPlugin(),
|
||||||
new AclPlugin(),
|
new AclPlugin(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Scout\Attributes\SearchUsingFullText;
|
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
use Sabre\VObject\Component\VCalendar;
|
||||||
use Sabre\VObject\Component\VCard;
|
use Sabre\VObject\Component\VCard;
|
||||||
use Sabre\VObject\Reader;
|
use Sabre\VObject\Reader;
|
||||||
use Spatie\LaravelData\Lazy;
|
use Spatie\LaravelData\Lazy;
|
||||||
|
@ -437,6 +437,24 @@ class Member extends Model implements Geolocatable
|
||||||
return $card;
|
return $card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toCalendarObject(): ?VCalendar
|
||||||
|
{
|
||||||
|
if (!$this->birthday) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vcalendar = new VCalendar([
|
||||||
|
'VEVENT' => [
|
||||||
|
'SUMMARY' => 'Geburtstag von ' . $this->fullname,
|
||||||
|
'RRULE' => 'FREQ=YEARLY',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$vcalendar->VEVENT->add('DTSTART', new \DateTime($this->birthday->format('Y-m-d')), ['VALUE' => 'DATE']);
|
||||||
|
|
||||||
|
return $vcalendar;
|
||||||
|
}
|
||||||
|
|
||||||
public function toSender(): Sender
|
public function toSender(): Sender
|
||||||
{
|
{
|
||||||
return Sender::from([
|
return Sender::from([
|
||||||
|
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
namespace Tests\Feature\Member;
|
namespace Tests\Feature\Member;
|
||||||
|
|
||||||
use App\Group;
|
|
||||||
use App\Member\Member;
|
use App\Member\Member;
|
||||||
use App\Nationality;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use App\Payment\Subscription;
|
|
||||||
use App\Setting\NamiSettings;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
@ -14,101 +11,28 @@ class DavTest extends TestCase
|
||||||
{
|
{
|
||||||
use DatabaseTransactions;
|
use DatabaseTransactions;
|
||||||
|
|
||||||
public function testItCanStoreAMemberFromAVcard(): void
|
public function testItDisplaysCalendar(): void
|
||||||
{
|
{
|
||||||
Nationality::factory()->create(['name' => 'englisch']);
|
$this->createDavUser()->withoutExceptionHandling();
|
||||||
$subscription = Subscription::factory()->forFee()->create(['name' => 'Voll']);
|
|
||||||
$nationality = Nationality::factory()->create(['name' => 'deutsch']);
|
|
||||||
$group = Group::factory()->create();
|
|
||||||
NamiSettings::fake(['default_group_id' => $group->id]);
|
|
||||||
$cardUri = '97266d2e-36e7-4fb6-8b6c-bbf57a061685.vcf';
|
|
||||||
$cardData = <<<VCARD
|
|
||||||
BEGIN:VCARD
|
|
||||||
VERSION:3.0
|
|
||||||
PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V77.0//EN-US
|
|
||||||
UID:97266d2e-36e7-4fb6-8b6c-bbf57a061685
|
|
||||||
CATEGORIES:Scoutrobot
|
|
||||||
FN:given familya Silva
|
|
||||||
N:familya;given;;;
|
|
||||||
BDAY:20221003
|
|
||||||
ORG:Silva
|
|
||||||
EMAIL:mail@maild.ee
|
|
||||||
ITEM1.TEL:+49 176 70342420
|
|
||||||
ITEM1.X-ABLABEL:eltern
|
|
||||||
ADR:;;Itterstr 3;Solingen;NRW;42719;Germany
|
|
||||||
REV:2022-10-07T14:17:06Z
|
|
||||||
END:VCARD
|
|
||||||
|
|
||||||
VCARD;
|
$this->getDavCalendars()->assertSee('Geburtstage');
|
||||||
$member = Member::fromVcard($cardUri, $cardData);
|
|
||||||
|
|
||||||
$member->save();
|
|
||||||
|
|
||||||
$this->assertDatabaseHas('members', [
|
|
||||||
'slug' => '97266d2e-36e7-4fb6-8b6c-bbf57a061685',
|
|
||||||
'firstname' => 'given',
|
|
||||||
'lastname' => 'familya',
|
|
||||||
'address' => 'Itterstr 3',
|
|
||||||
'zip' => '42719',
|
|
||||||
'location' => 'Solingen',
|
|
||||||
'group_id' => $group->id,
|
|
||||||
'nationality_id' => $nationality->id,
|
|
||||||
'subscription_id' => $subscription->id,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTheVcardHasTheMembersSlug(): void
|
public function testItDisplaysBirthdaysOfMembers(): void
|
||||||
{
|
{
|
||||||
$member = Member::factory()->defaults()->create(['firstname' => 'max', 'lastname' => 'muster']);
|
$this->createDavUser()->withoutExceptionHandling();
|
||||||
|
|
||||||
$card = $member->toVcard();
|
|
||||||
|
|
||||||
$this->assertEquals('max-muster', $card->UID->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItSetsTheNames(): void
|
|
||||||
{
|
|
||||||
$member = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
|
||||||
|
|
||||||
$card = $member->toVcard();
|
|
||||||
|
|
||||||
$this->assertEquals(['Muster', 'Max', '', '', ''], $card->N->getParts());
|
|
||||||
$this->assertEquals('Max Muster', $card->FN->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItDoesntNeedBirthday(): void
|
|
||||||
{
|
|
||||||
$member = Member::factory()->defaults()->create(['birthday' => null]);
|
|
||||||
|
|
||||||
$card = $member->toVcard();
|
|
||||||
|
|
||||||
$this->assertNull($card->BDAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItSetsTheBirthday(): void
|
|
||||||
{
|
|
||||||
$member = Member::factory()->defaults()->create(['birthday' => '1993-05-06']);
|
|
||||||
|
|
||||||
$card = $member->toVcard();
|
|
||||||
|
|
||||||
$this->assertEquals('19930506', $card->BDAY->getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testItUnsetsMobilePhoneNumber(): void
|
|
||||||
{
|
|
||||||
$member = Member::factory()->defaults()->create();
|
$member = Member::factory()->defaults()->create();
|
||||||
|
|
||||||
$member->update(['mobile_phone' => '']);
|
$this->getDavCalendar('birthdays')->assertSee($member->slug . '.ics');
|
||||||
|
}
|
||||||
|
|
||||||
if (!is_null($member->toVcard()->TEL)) {
|
public function testItGetsObjectsWhenBirthdayIsNull(): void
|
||||||
foreach ($member->toVcard()->TEL as $t) {
|
{
|
||||||
if ($t['TYPE'] && 'cell' === $t['TYPE']->getValue()) {
|
$this->createDavUser()->withoutExceptionHandling();
|
||||||
$this->assertFalse(true, 'Phone number found');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertTrue(true);
|
$member = Member::factory()->defaults()->create(['birthday' => null]);
|
||||||
|
|
||||||
|
$this->getDavCalendar('birthdays')->assertStatus(207);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Member;
|
||||||
|
|
||||||
|
use App\Group;
|
||||||
|
use App\Member\Member;
|
||||||
|
use App\Nationality;
|
||||||
|
use App\Payment\Subscription;
|
||||||
|
use App\Setting\NamiSettings;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class VcardTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
public function testItCanStoreAMemberFromAVcard(): void
|
||||||
|
{
|
||||||
|
Nationality::factory()->create(['name' => 'englisch']);
|
||||||
|
$subscription = Subscription::factory()->forFee()->create(['name' => 'Voll']);
|
||||||
|
$nationality = Nationality::factory()->create(['name' => 'deutsch']);
|
||||||
|
$group = Group::factory()->create();
|
||||||
|
NamiSettings::fake(['default_group_id' => $group->id]);
|
||||||
|
$cardUri = '97266d2e-36e7-4fb6-8b6c-bbf57a061685.vcf';
|
||||||
|
$cardData = <<<VCARD
|
||||||
|
BEGIN:VCARD
|
||||||
|
VERSION:3.0
|
||||||
|
PRODID:-//Thunderbird.net/NONSGML Thunderbird CardBook V77.0//EN-US
|
||||||
|
UID:97266d2e-36e7-4fb6-8b6c-bbf57a061685
|
||||||
|
CATEGORIES:Scoutrobot
|
||||||
|
FN:given familya Silva
|
||||||
|
N:familya;given;;;
|
||||||
|
BDAY:20221003
|
||||||
|
ORG:Silva
|
||||||
|
EMAIL:mail@maild.ee
|
||||||
|
ITEM1.TEL:+49 176 70342420
|
||||||
|
ITEM1.X-ABLABEL:eltern
|
||||||
|
ADR:;;Itterstr 3;Solingen;NRW;42719;Germany
|
||||||
|
REV:2022-10-07T14:17:06Z
|
||||||
|
END:VCARD
|
||||||
|
|
||||||
|
VCARD;
|
||||||
|
$member = Member::fromVcard($cardUri, $cardData);
|
||||||
|
|
||||||
|
$member->save();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas('members', [
|
||||||
|
'slug' => '97266d2e-36e7-4fb6-8b6c-bbf57a061685',
|
||||||
|
'firstname' => 'given',
|
||||||
|
'lastname' => 'familya',
|
||||||
|
'address' => 'Itterstr 3',
|
||||||
|
'zip' => '42719',
|
||||||
|
'location' => 'Solingen',
|
||||||
|
'group_id' => $group->id,
|
||||||
|
'nationality_id' => $nationality->id,
|
||||||
|
'subscription_id' => $subscription->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheVcardHasTheMembersSlug(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create(['firstname' => 'max', 'lastname' => 'muster']);
|
||||||
|
|
||||||
|
$card = $member->toVcard();
|
||||||
|
|
||||||
|
$this->assertEquals('max-muster', $card->UID->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItSetsTheNames(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
|
||||||
|
|
||||||
|
$card = $member->toVcard();
|
||||||
|
|
||||||
|
$this->assertEquals(['Muster', 'Max', '', '', ''], $card->N->getParts());
|
||||||
|
$this->assertEquals('Max Muster', $card->FN->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItDoesntNeedBirthday(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create(['birthday' => null]);
|
||||||
|
|
||||||
|
$card = $member->toVcard();
|
||||||
|
|
||||||
|
$this->assertNull($card->BDAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItSetsTheBirthday(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create(['birthday' => '1993-05-06']);
|
||||||
|
|
||||||
|
$card = $member->toVcard();
|
||||||
|
|
||||||
|
$this->assertEquals('19930506', $card->BDAY->getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testItUnsetsMobilePhoneNumber(): void
|
||||||
|
{
|
||||||
|
$member = Member::factory()->defaults()->create();
|
||||||
|
|
||||||
|
$member->update(['mobile_phone' => '']);
|
||||||
|
|
||||||
|
if (!is_null($member->toVcard()->TEL)) {
|
||||||
|
foreach ($member->toVcard()->TEL as $t) {
|
||||||
|
if ($t['TYPE'] && 'cell' === $t['TYPE']->getValue()) {
|
||||||
|
$this->assertFalse(true, 'Phone number found');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Lib;
|
||||||
|
|
||||||
|
use App\User;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Testing\TestResponse;
|
||||||
|
|
||||||
|
trait TestsDav
|
||||||
|
{
|
||||||
|
|
||||||
|
public $davUserEmail = 'user@example.com';
|
||||||
|
public $davUserPassword = 'secret';
|
||||||
|
|
||||||
|
public function getDavCalendars(): TestResponse
|
||||||
|
{
|
||||||
|
$this->withBasicAuth($this->davUserEmail, $this->davUserPassword);
|
||||||
|
return $this->call('PROPFIND', '/dav/calendars/' . $this->davUserEmail, [], [], [], $this->transformHeadersToServerVars([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getDavCalendar(string $calendar): TestResponse
|
||||||
|
{
|
||||||
|
$this->withBasicAuth($this->davUserEmail, $this->davUserPassword);
|
||||||
|
return $this->call('PROPFIND', '/dav/calendars/' . $this->davUserEmail . '/' . $calendar, [], [], [], $this->transformHeadersToServerVars([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDavUser(): self
|
||||||
|
{
|
||||||
|
$this->me = User::factory()->create(['email' => $this->davUserEmail, 'password' => Hash::make($this->davUserPassword)]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ use Illuminate\Testing\TestResponse;
|
||||||
use Phake;
|
use Phake;
|
||||||
use PHPUnit\Framework\Assert;
|
use PHPUnit\Framework\Assert;
|
||||||
use Tests\Lib\MakesHttpCalls;
|
use Tests\Lib\MakesHttpCalls;
|
||||||
|
use Tests\Lib\TestsDav;
|
||||||
use Tests\Lib\TestsInertia;
|
use Tests\Lib\TestsInertia;
|
||||||
use Zoomyboy\LaravelNami\Authentication\Auth;
|
use Zoomyboy\LaravelNami\Authentication\Auth;
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ abstract class TestCase extends BaseTestCase
|
||||||
use CreatesApplication;
|
use CreatesApplication;
|
||||||
use TestsInertia;
|
use TestsInertia;
|
||||||
use MakesHttpCalls;
|
use MakesHttpCalls;
|
||||||
|
use TestsDav;
|
||||||
|
|
||||||
protected User $me;
|
protected User $me;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue