aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/CalDAV/Export/ExportService.php
blob: 552b9e2b675dcc6b819f7545bde62e21d32dab16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
namespace OCA\DAV\CalDAV\Export;

use Generator;
use OCP\Calendar\CalendarExportOptions;
use OCP\Calendar\ICalendarExport;
use OCP\ServerVersion;
use Sabre\VObject\Component;
use Sabre\VObject\Writer;

/**
 * Calendar Export Service
 */
class ExportService {

	public const FORMATS = ['ical', 'jcal', 'xcal'];
	private string $systemVersion;

	public function __construct(ServerVersion $serverVersion) {
		$this->systemVersion = $serverVersion->getVersionString();
	}

	/**
	 * Generates serialized content stream for a calendar and objects based in selected format
	 *
	 * @return Generator<string>
	 */
	public function export(ICalendarExport $calendar, CalendarExportOptions $options): Generator {
		// output start of serialized content based on selected format
		yield $this->exportStart($options->getFormat());
		// iterate through each returned vCalendar entry
		// extract each component except timezones, convert to appropriate format and output
		// extract any timezones and save them but do not output
		$timezones = [];
		foreach ($calendar->export($options) as $entry) {
			$consecutive = false;
			foreach ($entry->getComponents() as $vComponent) {
				if ($vComponent->name === 'VTIMEZONE') {
					if (isset($vComponent->TZID) && !isset($timezones[$vComponent->TZID->getValue()])) {
						$timezones[$vComponent->TZID->getValue()] = clone $vComponent;
					}
				} else {
					yield $this->exportObject($vComponent, $options->getFormat(), $consecutive);
					$consecutive = true;
				}
			}
		}
		// iterate through each saved vTimezone entry, convert to appropriate format and output
		foreach ($timezones as $vComponent) {
			yield $this->exportObject($vComponent, $options->getFormat(), $consecutive);
			$consecutive = true;
		}
		// output end of serialized content based on selected format
		yield $this->exportFinish($options->getFormat());
	}

	/**
	 * Generates serialized content start based on selected format
	 */
	private function exportStart(string $format): string {
		return match ($format) {
			'jcal' => '["vcalendar",[["version",{},"text","2.0"],["prodid",{},"text","-\/\/IDN nextcloud.com\/\/Calendar Export v' . $this->systemVersion . '\/\/EN"]],[',
			'xcal' => '<?xml version="1.0" encoding="UTF-8"?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><properties><version><text>2.0</text></version><prodid><text>-//IDN nextcloud.com//Calendar Export v' . $this->systemVersion . '//EN</text></prodid></properties><components>',
			default => "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//IDN nextcloud.com//Calendar Export v" . $this->systemVersion . "//EN\n"
		};
	}

	/**
	 * Generates serialized content end based on selected format
	 */
	private function exportFinish(string $format): string {
		return match ($format) {
			'jcal' => ']]',
			'xcal' => '</components></vcalendar></icalendar>',
			default => "END:VCALENDAR\n"
		};
	}

	/**
	 * Generates serialized content for a component based on selected format
	 */
	private function exportObject(Component $vobject, string $format, bool $consecutive): string {
		return match ($format) {
			'jcal' => $consecutive ? ',' . Writer::writeJson($vobject) : Writer::writeJson($vobject),
			'xcal' => $this->exportObjectXml($vobject),
			default => Writer::write($vobject)
		};
	}

	/**
	 * Generates serialized content for a component in xml format
	 */
	private function exportObjectXml(Component $vobject): string {
		$writer = new \Sabre\Xml\Writer();
		$writer->openMemory();
		$writer->setIndent(false);
		$vobject->xmlSerialize($writer);
		return $writer->outputMemory();
	}

}