@@ -26,68 +26,68 @@ |
||
26 | 26 | */ |
27 | 27 | class GuessContentType extends DAV\ServerPlugin |
28 | 28 | { |
29 | - /** |
|
30 | - * List of recognized file extensions. |
|
31 | - * |
|
32 | - * Feel free to add more |
|
33 | - * |
|
34 | - * @var array |
|
35 | - */ |
|
36 | - public $extensionMap = [ |
|
37 | - // images |
|
38 | - 'jpg' => 'image/jpeg', |
|
39 | - 'gif' => 'image/gif', |
|
40 | - 'png' => 'image/png', |
|
29 | + /** |
|
30 | + * List of recognized file extensions. |
|
31 | + * |
|
32 | + * Feel free to add more |
|
33 | + * |
|
34 | + * @var array |
|
35 | + */ |
|
36 | + public $extensionMap = [ |
|
37 | + // images |
|
38 | + 'jpg' => 'image/jpeg', |
|
39 | + 'gif' => 'image/gif', |
|
40 | + 'png' => 'image/png', |
|
41 | 41 | |
42 | - // groupware |
|
43 | - 'ics' => 'text/calendar', |
|
44 | - 'vcf' => 'text/vcard', |
|
42 | + // groupware |
|
43 | + 'ics' => 'text/calendar', |
|
44 | + 'vcf' => 'text/vcard', |
|
45 | 45 | |
46 | - // text |
|
47 | - 'txt' => 'text/plain', |
|
48 | - ]; |
|
46 | + // text |
|
47 | + 'txt' => 'text/plain', |
|
48 | + ]; |
|
49 | 49 | |
50 | - /** |
|
51 | - * Initializes the plugin. |
|
52 | - */ |
|
53 | - public function initialize(DAV\Server $server) |
|
54 | - { |
|
55 | - // Using a relatively low priority (200) to allow other extensions |
|
56 | - // to set the content-type first. |
|
57 | - $server->on('propFind', [$this, 'propFind'], 200); |
|
58 | - } |
|
50 | + /** |
|
51 | + * Initializes the plugin. |
|
52 | + */ |
|
53 | + public function initialize(DAV\Server $server) |
|
54 | + { |
|
55 | + // Using a relatively low priority (200) to allow other extensions |
|
56 | + // to set the content-type first. |
|
57 | + $server->on('propFind', [$this, 'propFind'], 200); |
|
58 | + } |
|
59 | 59 | |
60 | - /** |
|
61 | - * Our PROPFIND handler. |
|
62 | - * |
|
63 | - * Here we set a contenttype, if the node didn't already have one. |
|
64 | - */ |
|
65 | - public function propFind(PropFind $propFind, INode $node) |
|
66 | - { |
|
67 | - $propFind->handle('{DAV:}getcontenttype', function () use ($propFind) { |
|
68 | - list(, $fileName) = Uri\split($propFind->getPath()); |
|
60 | + /** |
|
61 | + * Our PROPFIND handler. |
|
62 | + * |
|
63 | + * Here we set a contenttype, if the node didn't already have one. |
|
64 | + */ |
|
65 | + public function propFind(PropFind $propFind, INode $node) |
|
66 | + { |
|
67 | + $propFind->handle('{DAV:}getcontenttype', function () use ($propFind) { |
|
68 | + list(, $fileName) = Uri\split($propFind->getPath()); |
|
69 | 69 | |
70 | - return $this->getContentType($fileName); |
|
71 | - }); |
|
72 | - } |
|
70 | + return $this->getContentType($fileName); |
|
71 | + }); |
|
72 | + } |
|
73 | 73 | |
74 | - /** |
|
75 | - * Simple method to return the contenttype. |
|
76 | - * |
|
77 | - * @param string $fileName |
|
78 | - * |
|
79 | - * @return string |
|
80 | - */ |
|
81 | - protected function getContentType($fileName) |
|
82 | - { |
|
83 | - if (null !== $fileName) { |
|
84 | - // Just grabbing the extension |
|
85 | - $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1)); |
|
86 | - if (isset($this->extensionMap[$extension])) { |
|
87 | - return $this->extensionMap[$extension]; |
|
88 | - } |
|
89 | - } |
|
74 | + /** |
|
75 | + * Simple method to return the contenttype. |
|
76 | + * |
|
77 | + * @param string $fileName |
|
78 | + * |
|
79 | + * @return string |
|
80 | + */ |
|
81 | + protected function getContentType($fileName) |
|
82 | + { |
|
83 | + if (null !== $fileName) { |
|
84 | + // Just grabbing the extension |
|
85 | + $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1)); |
|
86 | + if (isset($this->extensionMap[$extension])) { |
|
87 | + return $this->extensionMap[$extension]; |
|
88 | + } |
|
89 | + } |
|
90 | 90 | |
91 | - return 'application/octet-stream'; |
|
92 | - } |
|
91 | + return 'application/octet-stream'; |
|
92 | + } |
|
93 | 93 | } |
@@ -15,65 +15,65 @@ |
||
15 | 15 | */ |
16 | 16 | interface ICollection extends INode |
17 | 17 | { |
18 | - /** |
|
19 | - * Creates a new file in the directory. |
|
20 | - * |
|
21 | - * Data will either be supplied as a stream resource, or in certain cases |
|
22 | - * as a string. Keep in mind that you may have to support either. |
|
23 | - * |
|
24 | - * After successful creation of the file, you may choose to return the ETag |
|
25 | - * of the new file here. |
|
26 | - * |
|
27 | - * The returned ETag must be surrounded by double-quotes (The quotes should |
|
28 | - * be part of the actual string). |
|
29 | - * |
|
30 | - * If you cannot accurately determine the ETag, you should not return it. |
|
31 | - * If you don't store the file exactly as-is (you're transforming it |
|
32 | - * somehow) you should also not return an ETag. |
|
33 | - * |
|
34 | - * This means that if a subsequent GET to this new file does not exactly |
|
35 | - * return the same contents of what was submitted here, you are strongly |
|
36 | - * recommended to omit the ETag. |
|
37 | - * |
|
38 | - * @param string $name Name of the file |
|
39 | - * @param resource|string $data Initial payload |
|
40 | - * |
|
41 | - * @return string|null |
|
42 | - */ |
|
43 | - public function createFile($name, $data = null); |
|
18 | + /** |
|
19 | + * Creates a new file in the directory. |
|
20 | + * |
|
21 | + * Data will either be supplied as a stream resource, or in certain cases |
|
22 | + * as a string. Keep in mind that you may have to support either. |
|
23 | + * |
|
24 | + * After successful creation of the file, you may choose to return the ETag |
|
25 | + * of the new file here. |
|
26 | + * |
|
27 | + * The returned ETag must be surrounded by double-quotes (The quotes should |
|
28 | + * be part of the actual string). |
|
29 | + * |
|
30 | + * If you cannot accurately determine the ETag, you should not return it. |
|
31 | + * If you don't store the file exactly as-is (you're transforming it |
|
32 | + * somehow) you should also not return an ETag. |
|
33 | + * |
|
34 | + * This means that if a subsequent GET to this new file does not exactly |
|
35 | + * return the same contents of what was submitted here, you are strongly |
|
36 | + * recommended to omit the ETag. |
|
37 | + * |
|
38 | + * @param string $name Name of the file |
|
39 | + * @param resource|string $data Initial payload |
|
40 | + * |
|
41 | + * @return string|null |
|
42 | + */ |
|
43 | + public function createFile($name, $data = null); |
|
44 | 44 | |
45 | - /** |
|
46 | - * Creates a new subdirectory. |
|
47 | - * |
|
48 | - * @param string $name |
|
49 | - */ |
|
50 | - public function createDirectory($name); |
|
45 | + /** |
|
46 | + * Creates a new subdirectory. |
|
47 | + * |
|
48 | + * @param string $name |
|
49 | + */ |
|
50 | + public function createDirectory($name); |
|
51 | 51 | |
52 | - /** |
|
53 | - * Returns a specific child node, referenced by its name. |
|
54 | - * |
|
55 | - * This method must throw Sabre\DAV\Exception\NotFound if the node does not |
|
56 | - * exist. |
|
57 | - * |
|
58 | - * @param string $name |
|
59 | - * |
|
60 | - * @return INode |
|
61 | - */ |
|
62 | - public function getChild($name); |
|
52 | + /** |
|
53 | + * Returns a specific child node, referenced by its name. |
|
54 | + * |
|
55 | + * This method must throw Sabre\DAV\Exception\NotFound if the node does not |
|
56 | + * exist. |
|
57 | + * |
|
58 | + * @param string $name |
|
59 | + * |
|
60 | + * @return INode |
|
61 | + */ |
|
62 | + public function getChild($name); |
|
63 | 63 | |
64 | - /** |
|
65 | - * Returns an array with all the child nodes. |
|
66 | - * |
|
67 | - * @return INode[] |
|
68 | - */ |
|
69 | - public function getChildren(); |
|
64 | + /** |
|
65 | + * Returns an array with all the child nodes. |
|
66 | + * |
|
67 | + * @return INode[] |
|
68 | + */ |
|
69 | + public function getChildren(); |
|
70 | 70 | |
71 | - /** |
|
72 | - * Checks if a child-node with the specified name exists. |
|
73 | - * |
|
74 | - * @param string $name |
|
75 | - * |
|
76 | - * @return bool |
|
77 | - */ |
|
78 | - public function childExists($name); |
|
71 | + /** |
|
72 | + * Checks if a child-node with the specified name exists. |
|
73 | + * |
|
74 | + * @param string $name |
|
75 | + * |
|
76 | + * @return bool |
|
77 | + */ |
|
78 | + public function childExists($name); |
|
79 | 79 | } |
@@ -17,50 +17,50 @@ |
||
17 | 17 | */ |
18 | 18 | class UUIDUtil |
19 | 19 | { |
20 | - /** |
|
21 | - * Returns a pseudo-random v4 UUID. |
|
22 | - * |
|
23 | - * This function is based on a comment by Andrew Moore on php.net |
|
24 | - * |
|
25 | - * @see http://www.php.net/manual/en/function.uniqid.php#94959 |
|
26 | - * |
|
27 | - * @return string |
|
28 | - */ |
|
29 | - public static function getUUID() |
|
30 | - { |
|
31 | - return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
32 | - // 32 bits for "time_low" |
|
33 | - mt_rand(0, 0xffff), mt_rand(0, 0xffff), |
|
20 | + /** |
|
21 | + * Returns a pseudo-random v4 UUID. |
|
22 | + * |
|
23 | + * This function is based on a comment by Andrew Moore on php.net |
|
24 | + * |
|
25 | + * @see http://www.php.net/manual/en/function.uniqid.php#94959 |
|
26 | + * |
|
27 | + * @return string |
|
28 | + */ |
|
29 | + public static function getUUID() |
|
30 | + { |
|
31 | + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
32 | + // 32 bits for "time_low" |
|
33 | + mt_rand(0, 0xffff), mt_rand(0, 0xffff), |
|
34 | 34 | |
35 | - // 16 bits for "time_mid" |
|
36 | - mt_rand(0, 0xffff), |
|
35 | + // 16 bits for "time_mid" |
|
36 | + mt_rand(0, 0xffff), |
|
37 | 37 | |
38 | - // 16 bits for "time_hi_and_version", |
|
39 | - // four most significant bits holds version number 4 |
|
40 | - mt_rand(0, 0x0fff) | 0x4000, |
|
38 | + // 16 bits for "time_hi_and_version", |
|
39 | + // four most significant bits holds version number 4 |
|
40 | + mt_rand(0, 0x0fff) | 0x4000, |
|
41 | 41 | |
42 | - // 16 bits, 8 bits for "clk_seq_hi_res", |
|
43 | - // 8 bits for "clk_seq_low", |
|
44 | - // two most significant bits holds zero and one for variant DCE1.1 |
|
45 | - mt_rand(0, 0x3fff) | 0x8000, |
|
42 | + // 16 bits, 8 bits for "clk_seq_hi_res", |
|
43 | + // 8 bits for "clk_seq_low", |
|
44 | + // two most significant bits holds zero and one for variant DCE1.1 |
|
45 | + mt_rand(0, 0x3fff) | 0x8000, |
|
46 | 46 | |
47 | - // 48 bits for "node" |
|
48 | - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) |
|
49 | - ); |
|
50 | - } |
|
47 | + // 48 bits for "node" |
|
48 | + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) |
|
49 | + ); |
|
50 | + } |
|
51 | 51 | |
52 | - /** |
|
53 | - * Checks if a string is a valid UUID. |
|
54 | - * |
|
55 | - * @param string $uuid |
|
56 | - * |
|
57 | - * @return bool |
|
58 | - */ |
|
59 | - public static function validateUUID($uuid) |
|
60 | - { |
|
61 | - return 0 !== preg_match( |
|
62 | - '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', |
|
63 | - $uuid |
|
64 | - ); |
|
65 | - } |
|
52 | + /** |
|
53 | + * Checks if a string is a valid UUID. |
|
54 | + * |
|
55 | + * @param string $uuid |
|
56 | + * |
|
57 | + * @return bool |
|
58 | + */ |
|
59 | + public static function validateUUID($uuid) |
|
60 | + { |
|
61 | + return 0 !== preg_match( |
|
62 | + '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', |
|
63 | + $uuid |
|
64 | + ); |
|
65 | + } |
|
66 | 66 | } |
@@ -28,231 +28,231 @@ discard block |
||
28 | 28 | */ |
29 | 29 | class Plugin extends ServerPlugin |
30 | 30 | { |
31 | - const ACCESS_NOTSHARED = 0; |
|
32 | - const ACCESS_SHAREDOWNER = 1; |
|
33 | - const ACCESS_READ = 2; |
|
34 | - const ACCESS_READWRITE = 3; |
|
35 | - const ACCESS_NOACCESS = 4; |
|
36 | - |
|
37 | - const INVITE_NORESPONSE = 1; |
|
38 | - const INVITE_ACCEPTED = 2; |
|
39 | - const INVITE_DECLINED = 3; |
|
40 | - const INVITE_INVALID = 4; |
|
41 | - |
|
42 | - /** |
|
43 | - * Reference to SabreDAV server object. |
|
44 | - * |
|
45 | - * @var Server |
|
46 | - */ |
|
47 | - protected $server; |
|
48 | - |
|
49 | - /** |
|
50 | - * This method should return a list of server-features. |
|
51 | - * |
|
52 | - * This is for example 'versioning' and is added to the DAV: header |
|
53 | - * in an OPTIONS response. |
|
54 | - * |
|
55 | - * @return array |
|
56 | - */ |
|
57 | - public function getFeatures() |
|
58 | - { |
|
59 | - return ['resource-sharing']; |
|
60 | - } |
|
61 | - |
|
62 | - /** |
|
63 | - * Returns a plugin name. |
|
64 | - * |
|
65 | - * Using this name other plugins will be able to access other plugins |
|
66 | - * using \Sabre\DAV\Server::getPlugin |
|
67 | - * |
|
68 | - * @return string |
|
69 | - */ |
|
70 | - public function getPluginName() |
|
71 | - { |
|
72 | - return 'sharing'; |
|
73 | - } |
|
74 | - |
|
75 | - /** |
|
76 | - * This initializes the plugin. |
|
77 | - * |
|
78 | - * This function is called by Sabre\DAV\Server, after |
|
79 | - * addPlugin is called. |
|
80 | - * |
|
81 | - * This method should set up the required event subscriptions. |
|
82 | - */ |
|
83 | - public function initialize(Server $server) |
|
84 | - { |
|
85 | - $this->server = $server; |
|
86 | - |
|
87 | - $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource'; |
|
88 | - |
|
89 | - array_push( |
|
90 | - $server->protectedProperties, |
|
91 | - '{DAV:}share-mode' |
|
92 | - ); |
|
93 | - |
|
94 | - $server->on('method:POST', [$this, 'httpPost']); |
|
95 | - $server->on('propFind', [$this, 'propFind']); |
|
96 | - $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); |
|
97 | - $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); |
|
98 | - $server->on('onBrowserPostAction', [$this, 'browserPostAction']); |
|
99 | - } |
|
100 | - |
|
101 | - /** |
|
102 | - * Updates the list of sharees on a shared resource. |
|
103 | - * |
|
104 | - * The sharees array is a list of people that are to be added modified |
|
105 | - * or removed in the list of shares. |
|
106 | - * |
|
107 | - * @param string $path |
|
108 | - * @param Sharee[] $sharees |
|
109 | - */ |
|
110 | - public function shareResource($path, array $sharees) |
|
111 | - { |
|
112 | - $node = $this->server->tree->getNodeForPath($path); |
|
113 | - |
|
114 | - if (!$node instanceof ISharedNode) { |
|
115 | - throw new Forbidden('Sharing is not allowed on this node'); |
|
116 | - } |
|
117 | - |
|
118 | - // Getting ACL info |
|
119 | - $acl = $this->server->getPlugin('acl'); |
|
120 | - |
|
121 | - // If there's no ACL support, we allow everything |
|
122 | - if ($acl) { |
|
123 | - $acl->checkPrivileges($path, '{DAV:}share'); |
|
124 | - } |
|
125 | - |
|
126 | - foreach ($sharees as $sharee) { |
|
127 | - // We're going to attempt to get a local principal uri for a share |
|
128 | - // href by emitting the getPrincipalByUri event. |
|
129 | - $principal = null; |
|
130 | - $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]); |
|
131 | - $sharee->principal = $principal; |
|
132 | - } |
|
133 | - $node->updateInvites($sharees); |
|
134 | - } |
|
135 | - |
|
136 | - /** |
|
137 | - * This event is triggered when properties are requested for nodes. |
|
138 | - * |
|
139 | - * This allows us to inject any sharings-specific properties. |
|
140 | - */ |
|
141 | - public function propFind(PropFind $propFind, INode $node) |
|
142 | - { |
|
143 | - if ($node instanceof ISharedNode) { |
|
144 | - $propFind->handle('{DAV:}share-access', function () use ($node) { |
|
145 | - return new Property\ShareAccess($node->getShareAccess()); |
|
146 | - }); |
|
147 | - $propFind->handle('{DAV:}invite', function () use ($node) { |
|
148 | - return new Property\Invite($node->getInvites()); |
|
149 | - }); |
|
150 | - $propFind->handle('{DAV:}share-resource-uri', function () use ($node) { |
|
151 | - return new Property\Href($node->getShareResourceUri()); |
|
152 | - }); |
|
153 | - } |
|
154 | - } |
|
155 | - |
|
156 | - /** |
|
157 | - * We intercept this to handle POST requests on shared resources. |
|
158 | - * |
|
159 | - * @return bool|null |
|
160 | - */ |
|
161 | - public function httpPost(RequestInterface $request, ResponseInterface $response) |
|
162 | - { |
|
163 | - $path = $request->getPath(); |
|
164 | - $contentType = $request->getHeader('Content-Type'); |
|
165 | - if (null === $contentType) { |
|
166 | - return; |
|
167 | - } |
|
168 | - |
|
169 | - // We're only interested in the davsharing content type. |
|
170 | - if (false === strpos($contentType, 'application/davsharing+xml')) { |
|
171 | - return; |
|
172 | - } |
|
173 | - |
|
174 | - $message = $this->server->xml->parse( |
|
175 | - $request->getBody(), |
|
176 | - $request->getUrl(), |
|
177 | - $documentType |
|
178 | - ); |
|
179 | - |
|
180 | - switch ($documentType) { |
|
181 | - case '{DAV:}share-resource': |
|
182 | - $this->shareResource($path, $message->sharees); |
|
183 | - $response->setStatus(200); |
|
184 | - // Adding this because sending a response body may cause issues, |
|
185 | - // and I wanted some type of indicator the response was handled. |
|
186 | - $response->setHeader('X-Sabre-Status', 'everything-went-well'); |
|
187 | - |
|
188 | - // Breaking the event chain |
|
189 | - return false; |
|
190 | - |
|
191 | - default: |
|
192 | - throw new BadRequest('Unexpected document type: '.$documentType.' for this Content-Type'); |
|
193 | - } |
|
194 | - } |
|
195 | - |
|
196 | - /** |
|
197 | - * This method is triggered whenever a subsystem reqeuests the privileges |
|
198 | - * hat are supported on a particular node. |
|
199 | - * |
|
200 | - * We need to add a number of privileges for scheduling purposes. |
|
201 | - */ |
|
202 | - public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) |
|
203 | - { |
|
204 | - if ($node instanceof ISharedNode) { |
|
205 | - $supportedPrivilegeSet['{DAV:}share'] = [ |
|
206 | - 'abstract' => false, |
|
207 | - 'aggregates' => [], |
|
208 | - ]; |
|
209 | - } |
|
210 | - } |
|
211 | - |
|
212 | - /** |
|
213 | - * Returns a bunch of meta-data about the plugin. |
|
214 | - * |
|
215 | - * Providing this information is optional, and is mainly displayed by the |
|
216 | - * Browser plugin. |
|
217 | - * |
|
218 | - * The description key in the returned array may contain html and will not |
|
219 | - * be sanitized. |
|
220 | - * |
|
221 | - * @return array |
|
222 | - */ |
|
223 | - public function getPluginInfo() |
|
224 | - { |
|
225 | - return [ |
|
226 | - 'name' => $this->getPluginName(), |
|
227 | - 'description' => 'This plugin implements WebDAV resource sharing', |
|
228 | - 'link' => 'https://github.com/evert/webdav-sharing', |
|
229 | - ]; |
|
230 | - } |
|
231 | - |
|
232 | - /** |
|
233 | - * This method is used to generate HTML output for the |
|
234 | - * DAV\Browser\Plugin. |
|
235 | - * |
|
236 | - * @param string $output |
|
237 | - * @param string $path |
|
238 | - * |
|
239 | - * @return bool|null |
|
240 | - */ |
|
241 | - public function htmlActionsPanel(INode $node, &$output, $path) |
|
242 | - { |
|
243 | - if (!$node instanceof ISharedNode) { |
|
244 | - return; |
|
245 | - } |
|
246 | - |
|
247 | - $aclPlugin = $this->server->getPlugin('acl'); |
|
248 | - if ($aclPlugin) { |
|
249 | - if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) { |
|
250 | - // Sharing is not permitted, we will not draw this interface. |
|
251 | - return; |
|
252 | - } |
|
253 | - } |
|
254 | - |
|
255 | - $output .= '<tr><td colspan="2"><form method="post" action=""> |
|
31 | + const ACCESS_NOTSHARED = 0; |
|
32 | + const ACCESS_SHAREDOWNER = 1; |
|
33 | + const ACCESS_READ = 2; |
|
34 | + const ACCESS_READWRITE = 3; |
|
35 | + const ACCESS_NOACCESS = 4; |
|
36 | + |
|
37 | + const INVITE_NORESPONSE = 1; |
|
38 | + const INVITE_ACCEPTED = 2; |
|
39 | + const INVITE_DECLINED = 3; |
|
40 | + const INVITE_INVALID = 4; |
|
41 | + |
|
42 | + /** |
|
43 | + * Reference to SabreDAV server object. |
|
44 | + * |
|
45 | + * @var Server |
|
46 | + */ |
|
47 | + protected $server; |
|
48 | + |
|
49 | + /** |
|
50 | + * This method should return a list of server-features. |
|
51 | + * |
|
52 | + * This is for example 'versioning' and is added to the DAV: header |
|
53 | + * in an OPTIONS response. |
|
54 | + * |
|
55 | + * @return array |
|
56 | + */ |
|
57 | + public function getFeatures() |
|
58 | + { |
|
59 | + return ['resource-sharing']; |
|
60 | + } |
|
61 | + |
|
62 | + /** |
|
63 | + * Returns a plugin name. |
|
64 | + * |
|
65 | + * Using this name other plugins will be able to access other plugins |
|
66 | + * using \Sabre\DAV\Server::getPlugin |
|
67 | + * |
|
68 | + * @return string |
|
69 | + */ |
|
70 | + public function getPluginName() |
|
71 | + { |
|
72 | + return 'sharing'; |
|
73 | + } |
|
74 | + |
|
75 | + /** |
|
76 | + * This initializes the plugin. |
|
77 | + * |
|
78 | + * This function is called by Sabre\DAV\Server, after |
|
79 | + * addPlugin is called. |
|
80 | + * |
|
81 | + * This method should set up the required event subscriptions. |
|
82 | + */ |
|
83 | + public function initialize(Server $server) |
|
84 | + { |
|
85 | + $this->server = $server; |
|
86 | + |
|
87 | + $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource'; |
|
88 | + |
|
89 | + array_push( |
|
90 | + $server->protectedProperties, |
|
91 | + '{DAV:}share-mode' |
|
92 | + ); |
|
93 | + |
|
94 | + $server->on('method:POST', [$this, 'httpPost']); |
|
95 | + $server->on('propFind', [$this, 'propFind']); |
|
96 | + $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); |
|
97 | + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); |
|
98 | + $server->on('onBrowserPostAction', [$this, 'browserPostAction']); |
|
99 | + } |
|
100 | + |
|
101 | + /** |
|
102 | + * Updates the list of sharees on a shared resource. |
|
103 | + * |
|
104 | + * The sharees array is a list of people that are to be added modified |
|
105 | + * or removed in the list of shares. |
|
106 | + * |
|
107 | + * @param string $path |
|
108 | + * @param Sharee[] $sharees |
|
109 | + */ |
|
110 | + public function shareResource($path, array $sharees) |
|
111 | + { |
|
112 | + $node = $this->server->tree->getNodeForPath($path); |
|
113 | + |
|
114 | + if (!$node instanceof ISharedNode) { |
|
115 | + throw new Forbidden('Sharing is not allowed on this node'); |
|
116 | + } |
|
117 | + |
|
118 | + // Getting ACL info |
|
119 | + $acl = $this->server->getPlugin('acl'); |
|
120 | + |
|
121 | + // If there's no ACL support, we allow everything |
|
122 | + if ($acl) { |
|
123 | + $acl->checkPrivileges($path, '{DAV:}share'); |
|
124 | + } |
|
125 | + |
|
126 | + foreach ($sharees as $sharee) { |
|
127 | + // We're going to attempt to get a local principal uri for a share |
|
128 | + // href by emitting the getPrincipalByUri event. |
|
129 | + $principal = null; |
|
130 | + $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]); |
|
131 | + $sharee->principal = $principal; |
|
132 | + } |
|
133 | + $node->updateInvites($sharees); |
|
134 | + } |
|
135 | + |
|
136 | + /** |
|
137 | + * This event is triggered when properties are requested for nodes. |
|
138 | + * |
|
139 | + * This allows us to inject any sharings-specific properties. |
|
140 | + */ |
|
141 | + public function propFind(PropFind $propFind, INode $node) |
|
142 | + { |
|
143 | + if ($node instanceof ISharedNode) { |
|
144 | + $propFind->handle('{DAV:}share-access', function () use ($node) { |
|
145 | + return new Property\ShareAccess($node->getShareAccess()); |
|
146 | + }); |
|
147 | + $propFind->handle('{DAV:}invite', function () use ($node) { |
|
148 | + return new Property\Invite($node->getInvites()); |
|
149 | + }); |
|
150 | + $propFind->handle('{DAV:}share-resource-uri', function () use ($node) { |
|
151 | + return new Property\Href($node->getShareResourceUri()); |
|
152 | + }); |
|
153 | + } |
|
154 | + } |
|
155 | + |
|
156 | + /** |
|
157 | + * We intercept this to handle POST requests on shared resources. |
|
158 | + * |
|
159 | + * @return bool|null |
|
160 | + */ |
|
161 | + public function httpPost(RequestInterface $request, ResponseInterface $response) |
|
162 | + { |
|
163 | + $path = $request->getPath(); |
|
164 | + $contentType = $request->getHeader('Content-Type'); |
|
165 | + if (null === $contentType) { |
|
166 | + return; |
|
167 | + } |
|
168 | + |
|
169 | + // We're only interested in the davsharing content type. |
|
170 | + if (false === strpos($contentType, 'application/davsharing+xml')) { |
|
171 | + return; |
|
172 | + } |
|
173 | + |
|
174 | + $message = $this->server->xml->parse( |
|
175 | + $request->getBody(), |
|
176 | + $request->getUrl(), |
|
177 | + $documentType |
|
178 | + ); |
|
179 | + |
|
180 | + switch ($documentType) { |
|
181 | + case '{DAV:}share-resource': |
|
182 | + $this->shareResource($path, $message->sharees); |
|
183 | + $response->setStatus(200); |
|
184 | + // Adding this because sending a response body may cause issues, |
|
185 | + // and I wanted some type of indicator the response was handled. |
|
186 | + $response->setHeader('X-Sabre-Status', 'everything-went-well'); |
|
187 | + |
|
188 | + // Breaking the event chain |
|
189 | + return false; |
|
190 | + |
|
191 | + default: |
|
192 | + throw new BadRequest('Unexpected document type: '.$documentType.' for this Content-Type'); |
|
193 | + } |
|
194 | + } |
|
195 | + |
|
196 | + /** |
|
197 | + * This method is triggered whenever a subsystem reqeuests the privileges |
|
198 | + * hat are supported on a particular node. |
|
199 | + * |
|
200 | + * We need to add a number of privileges for scheduling purposes. |
|
201 | + */ |
|
202 | + public function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) |
|
203 | + { |
|
204 | + if ($node instanceof ISharedNode) { |
|
205 | + $supportedPrivilegeSet['{DAV:}share'] = [ |
|
206 | + 'abstract' => false, |
|
207 | + 'aggregates' => [], |
|
208 | + ]; |
|
209 | + } |
|
210 | + } |
|
211 | + |
|
212 | + /** |
|
213 | + * Returns a bunch of meta-data about the plugin. |
|
214 | + * |
|
215 | + * Providing this information is optional, and is mainly displayed by the |
|
216 | + * Browser plugin. |
|
217 | + * |
|
218 | + * The description key in the returned array may contain html and will not |
|
219 | + * be sanitized. |
|
220 | + * |
|
221 | + * @return array |
|
222 | + */ |
|
223 | + public function getPluginInfo() |
|
224 | + { |
|
225 | + return [ |
|
226 | + 'name' => $this->getPluginName(), |
|
227 | + 'description' => 'This plugin implements WebDAV resource sharing', |
|
228 | + 'link' => 'https://github.com/evert/webdav-sharing', |
|
229 | + ]; |
|
230 | + } |
|
231 | + |
|
232 | + /** |
|
233 | + * This method is used to generate HTML output for the |
|
234 | + * DAV\Browser\Plugin. |
|
235 | + * |
|
236 | + * @param string $output |
|
237 | + * @param string $path |
|
238 | + * |
|
239 | + * @return bool|null |
|
240 | + */ |
|
241 | + public function htmlActionsPanel(INode $node, &$output, $path) |
|
242 | + { |
|
243 | + if (!$node instanceof ISharedNode) { |
|
244 | + return; |
|
245 | + } |
|
246 | + |
|
247 | + $aclPlugin = $this->server->getPlugin('acl'); |
|
248 | + if ($aclPlugin) { |
|
249 | + if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) { |
|
250 | + // Sharing is not permitted, we will not draw this interface. |
|
251 | + return; |
|
252 | + } |
|
253 | + } |
|
254 | + |
|
255 | + $output .= '<tr><td colspan="2"><form method="post" action=""> |
|
256 | 256 | <h3>Share this resource</h3> |
257 | 257 | <input type="hidden" name="sabreAction" value="share" /> |
258 | 258 | <label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:[email protected]"/><br /> |
@@ -265,48 +265,48 @@ discard block |
||
265 | 265 | <input type="submit" value="share" /> |
266 | 266 | </form> |
267 | 267 | </td></tr>'; |
268 | - } |
|
269 | - |
|
270 | - /** |
|
271 | - * This method is triggered for POST actions generated by the browser |
|
272 | - * plugin. |
|
273 | - * |
|
274 | - * @param string $path |
|
275 | - * @param string $action |
|
276 | - * @param array $postVars |
|
277 | - */ |
|
278 | - public function browserPostAction($path, $action, $postVars) |
|
279 | - { |
|
280 | - if ('share' !== $action) { |
|
281 | - return; |
|
282 | - } |
|
283 | - |
|
284 | - if (empty($postVars['href'])) { |
|
285 | - throw new BadRequest('The "href" POST parameter is required'); |
|
286 | - } |
|
287 | - if (empty($postVars['access'])) { |
|
288 | - throw new BadRequest('The "access" POST parameter is required'); |
|
289 | - } |
|
290 | - |
|
291 | - $accessMap = [ |
|
292 | - 'readwrite' => self::ACCESS_READWRITE, |
|
293 | - 'read' => self::ACCESS_READ, |
|
294 | - 'no-access' => self::ACCESS_NOACCESS, |
|
295 | - ]; |
|
296 | - |
|
297 | - if (!isset($accessMap[$postVars['access']])) { |
|
298 | - throw new BadRequest('The "access" POST must be readwrite, read or no-access'); |
|
299 | - } |
|
300 | - $sharee = new Sharee([ |
|
301 | - 'href' => $postVars['href'], |
|
302 | - 'access' => $accessMap[$postVars['access']], |
|
303 | - ]); |
|
304 | - |
|
305 | - $this->shareResource( |
|
306 | - $path, |
|
307 | - [$sharee] |
|
308 | - ); |
|
309 | - |
|
310 | - return false; |
|
311 | - } |
|
268 | + } |
|
269 | + |
|
270 | + /** |
|
271 | + * This method is triggered for POST actions generated by the browser |
|
272 | + * plugin. |
|
273 | + * |
|
274 | + * @param string $path |
|
275 | + * @param string $action |
|
276 | + * @param array $postVars |
|
277 | + */ |
|
278 | + public function browserPostAction($path, $action, $postVars) |
|
279 | + { |
|
280 | + if ('share' !== $action) { |
|
281 | + return; |
|
282 | + } |
|
283 | + |
|
284 | + if (empty($postVars['href'])) { |
|
285 | + throw new BadRequest('The "href" POST parameter is required'); |
|
286 | + } |
|
287 | + if (empty($postVars['access'])) { |
|
288 | + throw new BadRequest('The "access" POST parameter is required'); |
|
289 | + } |
|
290 | + |
|
291 | + $accessMap = [ |
|
292 | + 'readwrite' => self::ACCESS_READWRITE, |
|
293 | + 'read' => self::ACCESS_READ, |
|
294 | + 'no-access' => self::ACCESS_NOACCESS, |
|
295 | + ]; |
|
296 | + |
|
297 | + if (!isset($accessMap[$postVars['access']])) { |
|
298 | + throw new BadRequest('The "access" POST must be readwrite, read or no-access'); |
|
299 | + } |
|
300 | + $sharee = new Sharee([ |
|
301 | + 'href' => $postVars['href'], |
|
302 | + 'access' => $accessMap[$postVars['access']], |
|
303 | + ]); |
|
304 | + |
|
305 | + $this->shareResource( |
|
306 | + $path, |
|
307 | + [$sharee] |
|
308 | + ); |
|
309 | + |
|
310 | + return false; |
|
311 | + } |
|
312 | 312 | } |
@@ -17,53 +17,53 @@ |
||
17 | 17 | */ |
18 | 18 | interface ISharedNode extends INode |
19 | 19 | { |
20 | - /** |
|
21 | - * Returns the 'access level' for the instance of this shared resource. |
|
22 | - * |
|
23 | - * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_ |
|
24 | - * constants. |
|
25 | - * |
|
26 | - * @return int |
|
27 | - */ |
|
28 | - public function getShareAccess(); |
|
20 | + /** |
|
21 | + * Returns the 'access level' for the instance of this shared resource. |
|
22 | + * |
|
23 | + * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_ |
|
24 | + * constants. |
|
25 | + * |
|
26 | + * @return int |
|
27 | + */ |
|
28 | + public function getShareAccess(); |
|
29 | 29 | |
30 | - /** |
|
31 | - * This function must return a URI that uniquely identifies the shared |
|
32 | - * resource. This URI should be identical across instances, and is |
|
33 | - * also used in several other XML bodies to connect invites to |
|
34 | - * resources. |
|
35 | - * |
|
36 | - * This may simply be a relative reference to the original shared instance, |
|
37 | - * but it could also be a urn. As long as it's a valid URI and unique. |
|
38 | - * |
|
39 | - * @return string |
|
40 | - */ |
|
41 | - public function getShareResourceUri(); |
|
30 | + /** |
|
31 | + * This function must return a URI that uniquely identifies the shared |
|
32 | + * resource. This URI should be identical across instances, and is |
|
33 | + * also used in several other XML bodies to connect invites to |
|
34 | + * resources. |
|
35 | + * |
|
36 | + * This may simply be a relative reference to the original shared instance, |
|
37 | + * but it could also be a urn. As long as it's a valid URI and unique. |
|
38 | + * |
|
39 | + * @return string |
|
40 | + */ |
|
41 | + public function getShareResourceUri(); |
|
42 | 42 | |
43 | - /** |
|
44 | - * Updates the list of sharees. |
|
45 | - * |
|
46 | - * Every item must be a Sharee object. |
|
47 | - * |
|
48 | - * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees |
|
49 | - */ |
|
50 | - public function updateInvites(array $sharees); |
|
43 | + /** |
|
44 | + * Updates the list of sharees. |
|
45 | + * |
|
46 | + * Every item must be a Sharee object. |
|
47 | + * |
|
48 | + * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees |
|
49 | + */ |
|
50 | + public function updateInvites(array $sharees); |
|
51 | 51 | |
52 | - /** |
|
53 | - * Returns the list of people whom this resource is shared with. |
|
54 | - * |
|
55 | - * Every item in the returned array must be a Sharee object with |
|
56 | - * at least the following properties set: |
|
57 | - * |
|
58 | - * * $href |
|
59 | - * * $shareAccess |
|
60 | - * * $inviteStatus |
|
61 | - * |
|
62 | - * and optionally: |
|
63 | - * |
|
64 | - * * $properties |
|
65 | - * |
|
66 | - * @return \Sabre\DAV\Xml\Element\Sharee[] |
|
67 | - */ |
|
68 | - public function getInvites(); |
|
52 | + /** |
|
53 | + * Returns the list of people whom this resource is shared with. |
|
54 | + * |
|
55 | + * Every item in the returned array must be a Sharee object with |
|
56 | + * at least the following properties set: |
|
57 | + * |
|
58 | + * * $href |
|
59 | + * * $shareAccess |
|
60 | + * * $inviteStatus |
|
61 | + * |
|
62 | + * and optionally: |
|
63 | + * |
|
64 | + * * $properties |
|
65 | + * |
|
66 | + * @return \Sabre\DAV\Xml\Element\Sharee[] |
|
67 | + */ |
|
68 | + public function getInvites(); |
|
69 | 69 | } |
@@ -13,32 +13,32 @@ |
||
13 | 13 | */ |
14 | 14 | interface INode |
15 | 15 | { |
16 | - /** |
|
17 | - * Deleted the current node. |
|
18 | - */ |
|
19 | - public function delete(); |
|
16 | + /** |
|
17 | + * Deleted the current node. |
|
18 | + */ |
|
19 | + public function delete(); |
|
20 | 20 | |
21 | - /** |
|
22 | - * Returns the name of the node. |
|
23 | - * |
|
24 | - * This is used to generate the url. |
|
25 | - * |
|
26 | - * @return string |
|
27 | - */ |
|
28 | - public function getName(); |
|
21 | + /** |
|
22 | + * Returns the name of the node. |
|
23 | + * |
|
24 | + * This is used to generate the url. |
|
25 | + * |
|
26 | + * @return string |
|
27 | + */ |
|
28 | + public function getName(); |
|
29 | 29 | |
30 | - /** |
|
31 | - * Renames the node. |
|
32 | - * |
|
33 | - * @param string $name The new name |
|
34 | - */ |
|
35 | - public function setName($name); |
|
30 | + /** |
|
31 | + * Renames the node. |
|
32 | + * |
|
33 | + * @param string $name The new name |
|
34 | + */ |
|
35 | + public function setName($name); |
|
36 | 36 | |
37 | - /** |
|
38 | - * Returns the last modification time, as a unix timestamp. Return null |
|
39 | - * if the information is not available. |
|
40 | - * |
|
41 | - * @return int|null |
|
42 | - */ |
|
43 | - public function getLastModified(); |
|
37 | + /** |
|
38 | + * Returns the last modification time, as a unix timestamp. Return null |
|
39 | + * if the information is not available. |
|
40 | + * |
|
41 | + * @return int|null |
|
42 | + */ |
|
43 | + public function getLastModified(); |
|
44 | 44 | } |
@@ -24,249 +24,249 @@ |
||
24 | 24 | */ |
25 | 25 | class TimeZoneUtil |
26 | 26 | { |
27 | - /** @var self */ |
|
28 | - private static $instance = null; |
|
27 | + /** @var self */ |
|
28 | + private static $instance = null; |
|
29 | 29 | |
30 | - /** @var TimezoneGuesser[] */ |
|
31 | - private $timezoneGuessers = []; |
|
30 | + /** @var TimezoneGuesser[] */ |
|
31 | + private $timezoneGuessers = []; |
|
32 | 32 | |
33 | - /** @var TimezoneFinder[] */ |
|
34 | - private $timezoneFinders = []; |
|
33 | + /** @var TimezoneFinder[] */ |
|
34 | + private $timezoneFinders = []; |
|
35 | 35 | |
36 | - private function __construct() |
|
37 | - { |
|
38 | - $this->addGuesser('lic', new GuessFromLicEntry()); |
|
39 | - $this->addGuesser('msTzId', new GuessFromMsTzId()); |
|
40 | - $this->addFinder('tzid', new FindFromTimezoneIdentifier()); |
|
41 | - $this->addFinder('tzmap', new FindFromTimezoneMap()); |
|
42 | - $this->addFinder('offset', new FindFromOffset()); |
|
43 | - } |
|
36 | + private function __construct() |
|
37 | + { |
|
38 | + $this->addGuesser('lic', new GuessFromLicEntry()); |
|
39 | + $this->addGuesser('msTzId', new GuessFromMsTzId()); |
|
40 | + $this->addFinder('tzid', new FindFromTimezoneIdentifier()); |
|
41 | + $this->addFinder('tzmap', new FindFromTimezoneMap()); |
|
42 | + $this->addFinder('offset', new FindFromOffset()); |
|
43 | + } |
|
44 | 44 | |
45 | - private static function getInstance(): self |
|
46 | - { |
|
47 | - if (null === self::$instance) { |
|
48 | - self::$instance = new self(); |
|
49 | - } |
|
45 | + private static function getInstance(): self |
|
46 | + { |
|
47 | + if (null === self::$instance) { |
|
48 | + self::$instance = new self(); |
|
49 | + } |
|
50 | 50 | |
51 | - return self::$instance; |
|
52 | - } |
|
51 | + return self::$instance; |
|
52 | + } |
|
53 | 53 | |
54 | - private function addGuesser(string $key, TimezoneGuesser $guesser): void |
|
55 | - { |
|
56 | - $this->timezoneGuessers[$key] = $guesser; |
|
57 | - } |
|
54 | + private function addGuesser(string $key, TimezoneGuesser $guesser): void |
|
55 | + { |
|
56 | + $this->timezoneGuessers[$key] = $guesser; |
|
57 | + } |
|
58 | 58 | |
59 | - private function addFinder(string $key, TimezoneFinder $finder): void |
|
60 | - { |
|
61 | - $this->timezoneFinders[$key] = $finder; |
|
62 | - } |
|
59 | + private function addFinder(string $key, TimezoneFinder $finder): void |
|
60 | + { |
|
61 | + $this->timezoneFinders[$key] = $finder; |
|
62 | + } |
|
63 | 63 | |
64 | - /** |
|
65 | - * This method will try to find out the correct timezone for an iCalendar |
|
66 | - * date-time value. |
|
67 | - * |
|
68 | - * You must pass the contents of the TZID parameter, as well as the full |
|
69 | - * calendar. |
|
70 | - * |
|
71 | - * If the lookup fails, this method will return the default PHP timezone |
|
72 | - * (as configured using date_default_timezone_set, or the date.timezone ini |
|
73 | - * setting). |
|
74 | - * |
|
75 | - * Alternatively, if $failIfUncertain is set to true, it will throw an |
|
76 | - * exception if we cannot accurately determine the timezone. |
|
77 | - */ |
|
78 | - private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone |
|
79 | - { |
|
80 | - foreach ($this->timezoneFinders as $timezoneFinder) { |
|
81 | - $timezone = $timezoneFinder->find($tzid, $failIfUncertain); |
|
82 | - if (!$timezone instanceof DateTimeZone) { |
|
83 | - continue; |
|
84 | - } |
|
64 | + /** |
|
65 | + * This method will try to find out the correct timezone for an iCalendar |
|
66 | + * date-time value. |
|
67 | + * |
|
68 | + * You must pass the contents of the TZID parameter, as well as the full |
|
69 | + * calendar. |
|
70 | + * |
|
71 | + * If the lookup fails, this method will return the default PHP timezone |
|
72 | + * (as configured using date_default_timezone_set, or the date.timezone ini |
|
73 | + * setting). |
|
74 | + * |
|
75 | + * Alternatively, if $failIfUncertain is set to true, it will throw an |
|
76 | + * exception if we cannot accurately determine the timezone. |
|
77 | + */ |
|
78 | + private function findTimeZone(string $tzid, Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone |
|
79 | + { |
|
80 | + foreach ($this->timezoneFinders as $timezoneFinder) { |
|
81 | + $timezone = $timezoneFinder->find($tzid, $failIfUncertain); |
|
82 | + if (!$timezone instanceof DateTimeZone) { |
|
83 | + continue; |
|
84 | + } |
|
85 | 85 | |
86 | - return $timezone; |
|
87 | - } |
|
86 | + return $timezone; |
|
87 | + } |
|
88 | 88 | |
89 | - if ($vcalendar) { |
|
90 | - // If that didn't work, we will scan VTIMEZONE objects |
|
91 | - foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { |
|
92 | - if ((string) $vtimezone->TZID === $tzid) { |
|
93 | - foreach ($this->timezoneGuessers as $timezoneGuesser) { |
|
94 | - $timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain); |
|
95 | - if (!$timezone instanceof DateTimeZone) { |
|
96 | - continue; |
|
97 | - } |
|
89 | + if ($vcalendar) { |
|
90 | + // If that didn't work, we will scan VTIMEZONE objects |
|
91 | + foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) { |
|
92 | + if ((string) $vtimezone->TZID === $tzid) { |
|
93 | + foreach ($this->timezoneGuessers as $timezoneGuesser) { |
|
94 | + $timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain); |
|
95 | + if (!$timezone instanceof DateTimeZone) { |
|
96 | + continue; |
|
97 | + } |
|
98 | 98 | |
99 | - return $timezone; |
|
100 | - } |
|
101 | - } |
|
102 | - } |
|
103 | - } |
|
99 | + return $timezone; |
|
100 | + } |
|
101 | + } |
|
102 | + } |
|
103 | + } |
|
104 | 104 | |
105 | - if ($failIfUncertain) { |
|
106 | - throw new InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid); |
|
107 | - } |
|
105 | + if ($failIfUncertain) { |
|
106 | + throw new InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid); |
|
107 | + } |
|
108 | 108 | |
109 | - // If we got all the way here, we default to whatever has been set as the PHP default timezone. |
|
110 | - return new DateTimeZone(date_default_timezone_get()); |
|
111 | - } |
|
109 | + // If we got all the way here, we default to whatever has been set as the PHP default timezone. |
|
110 | + return new DateTimeZone(date_default_timezone_get()); |
|
111 | + } |
|
112 | 112 | |
113 | - public static function addTimezoneGuesser(string $key, TimezoneGuesser $guesser): void |
|
114 | - { |
|
115 | - self::getInstance()->addGuesser($key, $guesser); |
|
116 | - } |
|
113 | + public static function addTimezoneGuesser(string $key, TimezoneGuesser $guesser): void |
|
114 | + { |
|
115 | + self::getInstance()->addGuesser($key, $guesser); |
|
116 | + } |
|
117 | 117 | |
118 | - public static function addTimezoneFinder(string $key, TimezoneFinder $finder): void |
|
119 | - { |
|
120 | - self::getInstance()->addFinder($key, $finder); |
|
121 | - } |
|
118 | + public static function addTimezoneFinder(string $key, TimezoneFinder $finder): void |
|
119 | + { |
|
120 | + self::getInstance()->addFinder($key, $finder); |
|
121 | + } |
|
122 | 122 | |
123 | - /** |
|
124 | - * @param string $tzid |
|
125 | - * @param false $failIfUncertain |
|
126 | - * |
|
127 | - * @return DateTimeZone |
|
128 | - */ |
|
129 | - public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) |
|
130 | - { |
|
131 | - return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain); |
|
132 | - } |
|
123 | + /** |
|
124 | + * @param string $tzid |
|
125 | + * @param false $failIfUncertain |
|
126 | + * |
|
127 | + * @return DateTimeZone |
|
128 | + */ |
|
129 | + public static function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) |
|
130 | + { |
|
131 | + return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain); |
|
132 | + } |
|
133 | 133 | |
134 | - public static function clean(): void |
|
135 | - { |
|
136 | - self::$instance = null; |
|
137 | - } |
|
134 | + public static function clean(): void |
|
135 | + { |
|
136 | + self::$instance = null; |
|
137 | + } |
|
138 | 138 | |
139 | - // Keeping things for backwards compatibility |
|
140 | - /** |
|
141 | - * @var array|null |
|
142 | - * |
|
143 | - * @deprecated |
|
144 | - */ |
|
145 | - public static $map = null; |
|
139 | + // Keeping things for backwards compatibility |
|
140 | + /** |
|
141 | + * @var array|null |
|
142 | + * |
|
143 | + * @deprecated |
|
144 | + */ |
|
145 | + public static $map = null; |
|
146 | 146 | |
147 | - /** |
|
148 | - * List of microsoft exchange timezone ids. |
|
149 | - * |
|
150 | - * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx |
|
151 | - * |
|
152 | - * @deprecated |
|
153 | - */ |
|
154 | - public static $microsoftExchangeMap = [ |
|
155 | - 0 => 'UTC', |
|
156 | - 31 => 'Africa/Casablanca', |
|
157 | - // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. |
|
158 | - // I'm not even kidding.. We handle this special case in the |
|
159 | - // getTimeZone method. |
|
160 | - 2 => 'Europe/Lisbon', |
|
161 | - 1 => 'Europe/London', |
|
162 | - 4 => 'Europe/Berlin', |
|
163 | - 6 => 'Europe/Prague', |
|
164 | - 3 => 'Europe/Paris', |
|
165 | - 69 => 'Africa/Luanda', // This was a best guess |
|
166 | - 7 => 'Europe/Athens', |
|
167 | - 5 => 'Europe/Bucharest', |
|
168 | - 49 => 'Africa/Cairo', |
|
169 | - 50 => 'Africa/Harare', |
|
170 | - 59 => 'Europe/Helsinki', |
|
171 | - 27 => 'Asia/Jerusalem', |
|
172 | - 26 => 'Asia/Baghdad', |
|
173 | - 74 => 'Asia/Kuwait', |
|
174 | - 51 => 'Europe/Moscow', |
|
175 | - 56 => 'Africa/Nairobi', |
|
176 | - 25 => 'Asia/Tehran', |
|
177 | - 24 => 'Asia/Muscat', // Best guess |
|
178 | - 54 => 'Asia/Baku', |
|
179 | - 48 => 'Asia/Kabul', |
|
180 | - 58 => 'Asia/Yekaterinburg', |
|
181 | - 47 => 'Asia/Karachi', |
|
182 | - 23 => 'Asia/Calcutta', |
|
183 | - 62 => 'Asia/Kathmandu', |
|
184 | - 46 => 'Asia/Almaty', |
|
185 | - 71 => 'Asia/Dhaka', |
|
186 | - 66 => 'Asia/Colombo', |
|
187 | - 61 => 'Asia/Rangoon', |
|
188 | - 22 => 'Asia/Bangkok', |
|
189 | - 64 => 'Asia/Krasnoyarsk', |
|
190 | - 45 => 'Asia/Shanghai', |
|
191 | - 63 => 'Asia/Irkutsk', |
|
192 | - 21 => 'Asia/Singapore', |
|
193 | - 73 => 'Australia/Perth', |
|
194 | - 75 => 'Asia/Taipei', |
|
195 | - 20 => 'Asia/Tokyo', |
|
196 | - 72 => 'Asia/Seoul', |
|
197 | - 70 => 'Asia/Yakutsk', |
|
198 | - 19 => 'Australia/Adelaide', |
|
199 | - 44 => 'Australia/Darwin', |
|
200 | - 18 => 'Australia/Brisbane', |
|
201 | - 76 => 'Australia/Sydney', |
|
202 | - 43 => 'Pacific/Guam', |
|
203 | - 42 => 'Australia/Hobart', |
|
204 | - 68 => 'Asia/Vladivostok', |
|
205 | - 41 => 'Asia/Magadan', |
|
206 | - 17 => 'Pacific/Auckland', |
|
207 | - 40 => 'Pacific/Fiji', |
|
208 | - 67 => 'Pacific/Tongatapu', |
|
209 | - 29 => 'Atlantic/Azores', |
|
210 | - 53 => 'Atlantic/Cape_Verde', |
|
211 | - 30 => 'America/Noronha', |
|
212 | - 8 => 'America/Sao_Paulo', // Best guess |
|
213 | - 32 => 'America/Argentina/Buenos_Aires', |
|
214 | - 60 => 'America/Godthab', |
|
215 | - 28 => 'America/St_Johns', |
|
216 | - 9 => 'America/Halifax', |
|
217 | - 33 => 'America/Caracas', |
|
218 | - 65 => 'America/Santiago', |
|
219 | - 35 => 'America/Bogota', |
|
220 | - 10 => 'America/New_York', |
|
221 | - 34 => 'America/Indiana/Indianapolis', |
|
222 | - 55 => 'America/Guatemala', |
|
223 | - 11 => 'America/Chicago', |
|
224 | - 37 => 'America/Mexico_City', |
|
225 | - 36 => 'America/Edmonton', |
|
226 | - 38 => 'America/Phoenix', |
|
227 | - 12 => 'America/Denver', // Best guess |
|
228 | - 13 => 'America/Los_Angeles', // Best guess |
|
229 | - 14 => 'America/Anchorage', |
|
230 | - 15 => 'Pacific/Honolulu', |
|
231 | - 16 => 'Pacific/Midway', |
|
232 | - 39 => 'Pacific/Kwajalein', |
|
233 | - ]; |
|
147 | + /** |
|
148 | + * List of microsoft exchange timezone ids. |
|
149 | + * |
|
150 | + * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx |
|
151 | + * |
|
152 | + * @deprecated |
|
153 | + */ |
|
154 | + public static $microsoftExchangeMap = [ |
|
155 | + 0 => 'UTC', |
|
156 | + 31 => 'Africa/Casablanca', |
|
157 | + // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo. |
|
158 | + // I'm not even kidding.. We handle this special case in the |
|
159 | + // getTimeZone method. |
|
160 | + 2 => 'Europe/Lisbon', |
|
161 | + 1 => 'Europe/London', |
|
162 | + 4 => 'Europe/Berlin', |
|
163 | + 6 => 'Europe/Prague', |
|
164 | + 3 => 'Europe/Paris', |
|
165 | + 69 => 'Africa/Luanda', // This was a best guess |
|
166 | + 7 => 'Europe/Athens', |
|
167 | + 5 => 'Europe/Bucharest', |
|
168 | + 49 => 'Africa/Cairo', |
|
169 | + 50 => 'Africa/Harare', |
|
170 | + 59 => 'Europe/Helsinki', |
|
171 | + 27 => 'Asia/Jerusalem', |
|
172 | + 26 => 'Asia/Baghdad', |
|
173 | + 74 => 'Asia/Kuwait', |
|
174 | + 51 => 'Europe/Moscow', |
|
175 | + 56 => 'Africa/Nairobi', |
|
176 | + 25 => 'Asia/Tehran', |
|
177 | + 24 => 'Asia/Muscat', // Best guess |
|
178 | + 54 => 'Asia/Baku', |
|
179 | + 48 => 'Asia/Kabul', |
|
180 | + 58 => 'Asia/Yekaterinburg', |
|
181 | + 47 => 'Asia/Karachi', |
|
182 | + 23 => 'Asia/Calcutta', |
|
183 | + 62 => 'Asia/Kathmandu', |
|
184 | + 46 => 'Asia/Almaty', |
|
185 | + 71 => 'Asia/Dhaka', |
|
186 | + 66 => 'Asia/Colombo', |
|
187 | + 61 => 'Asia/Rangoon', |
|
188 | + 22 => 'Asia/Bangkok', |
|
189 | + 64 => 'Asia/Krasnoyarsk', |
|
190 | + 45 => 'Asia/Shanghai', |
|
191 | + 63 => 'Asia/Irkutsk', |
|
192 | + 21 => 'Asia/Singapore', |
|
193 | + 73 => 'Australia/Perth', |
|
194 | + 75 => 'Asia/Taipei', |
|
195 | + 20 => 'Asia/Tokyo', |
|
196 | + 72 => 'Asia/Seoul', |
|
197 | + 70 => 'Asia/Yakutsk', |
|
198 | + 19 => 'Australia/Adelaide', |
|
199 | + 44 => 'Australia/Darwin', |
|
200 | + 18 => 'Australia/Brisbane', |
|
201 | + 76 => 'Australia/Sydney', |
|
202 | + 43 => 'Pacific/Guam', |
|
203 | + 42 => 'Australia/Hobart', |
|
204 | + 68 => 'Asia/Vladivostok', |
|
205 | + 41 => 'Asia/Magadan', |
|
206 | + 17 => 'Pacific/Auckland', |
|
207 | + 40 => 'Pacific/Fiji', |
|
208 | + 67 => 'Pacific/Tongatapu', |
|
209 | + 29 => 'Atlantic/Azores', |
|
210 | + 53 => 'Atlantic/Cape_Verde', |
|
211 | + 30 => 'America/Noronha', |
|
212 | + 8 => 'America/Sao_Paulo', // Best guess |
|
213 | + 32 => 'America/Argentina/Buenos_Aires', |
|
214 | + 60 => 'America/Godthab', |
|
215 | + 28 => 'America/St_Johns', |
|
216 | + 9 => 'America/Halifax', |
|
217 | + 33 => 'America/Caracas', |
|
218 | + 65 => 'America/Santiago', |
|
219 | + 35 => 'America/Bogota', |
|
220 | + 10 => 'America/New_York', |
|
221 | + 34 => 'America/Indiana/Indianapolis', |
|
222 | + 55 => 'America/Guatemala', |
|
223 | + 11 => 'America/Chicago', |
|
224 | + 37 => 'America/Mexico_City', |
|
225 | + 36 => 'America/Edmonton', |
|
226 | + 38 => 'America/Phoenix', |
|
227 | + 12 => 'America/Denver', // Best guess |
|
228 | + 13 => 'America/Los_Angeles', // Best guess |
|
229 | + 14 => 'America/Anchorage', |
|
230 | + 15 => 'Pacific/Honolulu', |
|
231 | + 16 => 'Pacific/Midway', |
|
232 | + 39 => 'Pacific/Kwajalein', |
|
233 | + ]; |
|
234 | 234 | |
235 | - /** |
|
236 | - * This method will load in all the tz mapping information, if it's not yet |
|
237 | - * done. |
|
238 | - * |
|
239 | - * @deprecated |
|
240 | - */ |
|
241 | - public static function loadTzMaps() |
|
242 | - { |
|
243 | - if (!is_null(self::$map)) { |
|
244 | - return; |
|
245 | - } |
|
235 | + /** |
|
236 | + * This method will load in all the tz mapping information, if it's not yet |
|
237 | + * done. |
|
238 | + * |
|
239 | + * @deprecated |
|
240 | + */ |
|
241 | + public static function loadTzMaps() |
|
242 | + { |
|
243 | + if (!is_null(self::$map)) { |
|
244 | + return; |
|
245 | + } |
|
246 | 246 | |
247 | - self::$map = array_merge( |
|
248 | - include __DIR__.'/timezonedata/windowszones.php', |
|
249 | - include __DIR__.'/timezonedata/lotuszones.php', |
|
250 | - include __DIR__.'/timezonedata/exchangezones.php', |
|
251 | - include __DIR__.'/timezonedata/php-workaround.php' |
|
252 | - ); |
|
253 | - } |
|
247 | + self::$map = array_merge( |
|
248 | + include __DIR__.'/timezonedata/windowszones.php', |
|
249 | + include __DIR__.'/timezonedata/lotuszones.php', |
|
250 | + include __DIR__.'/timezonedata/exchangezones.php', |
|
251 | + include __DIR__.'/timezonedata/php-workaround.php' |
|
252 | + ); |
|
253 | + } |
|
254 | 254 | |
255 | - /** |
|
256 | - * This method returns an array of timezone identifiers, that are supported |
|
257 | - * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers(). |
|
258 | - * |
|
259 | - * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: |
|
260 | - * - It's not supported by some PHP versions as well as HHVM. |
|
261 | - * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. |
|
262 | - * (See timezonedata/php-bc.php and timezonedata php-workaround.php) |
|
263 | - * |
|
264 | - * @return array |
|
265 | - * |
|
266 | - * @deprecated |
|
267 | - */ |
|
268 | - public static function getIdentifiersBC() |
|
269 | - { |
|
270 | - return include __DIR__.'/timezonedata/php-bc.php'; |
|
271 | - } |
|
255 | + /** |
|
256 | + * This method returns an array of timezone identifiers, that are supported |
|
257 | + * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers(). |
|
258 | + * |
|
259 | + * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because: |
|
260 | + * - It's not supported by some PHP versions as well as HHVM. |
|
261 | + * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions. |
|
262 | + * (See timezonedata/php-bc.php and timezonedata php-workaround.php) |
|
263 | + * |
|
264 | + * @return array |
|
265 | + * |
|
266 | + * @deprecated |
|
267 | + */ |
|
268 | + public static function getIdentifiersBC() |
|
269 | + { |
|
270 | + return include __DIR__.'/timezonedata/php-bc.php'; |
|
271 | + } |
|
272 | 272 | } |
@@ -18,247 +18,247 @@ |
||
18 | 18 | */ |
19 | 19 | abstract class Document extends Component |
20 | 20 | { |
21 | - /** |
|
22 | - * Unknown document type. |
|
23 | - */ |
|
24 | - const UNKNOWN = 1; |
|
21 | + /** |
|
22 | + * Unknown document type. |
|
23 | + */ |
|
24 | + const UNKNOWN = 1; |
|
25 | 25 | |
26 | - /** |
|
27 | - * vCalendar 1.0. |
|
28 | - */ |
|
29 | - const VCALENDAR10 = 2; |
|
26 | + /** |
|
27 | + * vCalendar 1.0. |
|
28 | + */ |
|
29 | + const VCALENDAR10 = 2; |
|
30 | 30 | |
31 | - /** |
|
32 | - * iCalendar 2.0. |
|
33 | - */ |
|
34 | - const ICALENDAR20 = 3; |
|
31 | + /** |
|
32 | + * iCalendar 2.0. |
|
33 | + */ |
|
34 | + const ICALENDAR20 = 3; |
|
35 | 35 | |
36 | - /** |
|
37 | - * vCard 2.1. |
|
38 | - */ |
|
39 | - const VCARD21 = 4; |
|
36 | + /** |
|
37 | + * vCard 2.1. |
|
38 | + */ |
|
39 | + const VCARD21 = 4; |
|
40 | 40 | |
41 | - /** |
|
42 | - * vCard 3.0. |
|
43 | - */ |
|
44 | - const VCARD30 = 5; |
|
41 | + /** |
|
42 | + * vCard 3.0. |
|
43 | + */ |
|
44 | + const VCARD30 = 5; |
|
45 | 45 | |
46 | - /** |
|
47 | - * vCard 4.0. |
|
48 | - */ |
|
49 | - const VCARD40 = 6; |
|
46 | + /** |
|
47 | + * vCard 4.0. |
|
48 | + */ |
|
49 | + const VCARD40 = 6; |
|
50 | 50 | |
51 | - /** |
|
52 | - * The default name for this component. |
|
53 | - * |
|
54 | - * This should be 'VCALENDAR' or 'VCARD'. |
|
55 | - * |
|
56 | - * @var string |
|
57 | - */ |
|
58 | - public static $defaultName; |
|
51 | + /** |
|
52 | + * The default name for this component. |
|
53 | + * |
|
54 | + * This should be 'VCALENDAR' or 'VCARD'. |
|
55 | + * |
|
56 | + * @var string |
|
57 | + */ |
|
58 | + public static $defaultName; |
|
59 | 59 | |
60 | - /** |
|
61 | - * List of properties, and which classes they map to. |
|
62 | - * |
|
63 | - * @var array |
|
64 | - */ |
|
65 | - public static $propertyMap = []; |
|
60 | + /** |
|
61 | + * List of properties, and which classes they map to. |
|
62 | + * |
|
63 | + * @var array |
|
64 | + */ |
|
65 | + public static $propertyMap = []; |
|
66 | 66 | |
67 | - /** |
|
68 | - * List of components, along with which classes they map to. |
|
69 | - * |
|
70 | - * @var array |
|
71 | - */ |
|
72 | - public static $componentMap = []; |
|
67 | + /** |
|
68 | + * List of components, along with which classes they map to. |
|
69 | + * |
|
70 | + * @var array |
|
71 | + */ |
|
72 | + public static $componentMap = []; |
|
73 | 73 | |
74 | - /** |
|
75 | - * List of value-types, and which classes they map to. |
|
76 | - * |
|
77 | - * @var array |
|
78 | - */ |
|
79 | - public static $valueMap = []; |
|
74 | + /** |
|
75 | + * List of value-types, and which classes they map to. |
|
76 | + * |
|
77 | + * @var array |
|
78 | + */ |
|
79 | + public static $valueMap = []; |
|
80 | 80 | |
81 | - /** |
|
82 | - * Creates a new document. |
|
83 | - * |
|
84 | - * We're changing the default behavior slightly here. First, we don't want |
|
85 | - * to have to specify a name (we already know it), and we want to allow |
|
86 | - * children to be specified in the first argument. |
|
87 | - * |
|
88 | - * But, the default behavior also works. |
|
89 | - * |
|
90 | - * So the two sigs: |
|
91 | - * |
|
92 | - * new Document(array $children = [], $defaults = true); |
|
93 | - * new Document(string $name, array $children = [], $defaults = true) |
|
94 | - */ |
|
95 | - public function __construct() |
|
96 | - { |
|
97 | - $args = func_get_args(); |
|
98 | - $name = static::$defaultName; |
|
99 | - if (0 === count($args) || is_array($args[0])) { |
|
100 | - $children = isset($args[0]) ? $args[0] : []; |
|
101 | - $defaults = isset($args[1]) ? $args[1] : true; |
|
102 | - } else { |
|
103 | - $name = $args[0]; |
|
104 | - $children = isset($args[1]) ? $args[1] : []; |
|
105 | - $defaults = isset($args[2]) ? $args[2] : true; |
|
106 | - } |
|
107 | - parent::__construct($this, $name, $children, $defaults); |
|
108 | - } |
|
81 | + /** |
|
82 | + * Creates a new document. |
|
83 | + * |
|
84 | + * We're changing the default behavior slightly here. First, we don't want |
|
85 | + * to have to specify a name (we already know it), and we want to allow |
|
86 | + * children to be specified in the first argument. |
|
87 | + * |
|
88 | + * But, the default behavior also works. |
|
89 | + * |
|
90 | + * So the two sigs: |
|
91 | + * |
|
92 | + * new Document(array $children = [], $defaults = true); |
|
93 | + * new Document(string $name, array $children = [], $defaults = true) |
|
94 | + */ |
|
95 | + public function __construct() |
|
96 | + { |
|
97 | + $args = func_get_args(); |
|
98 | + $name = static::$defaultName; |
|
99 | + if (0 === count($args) || is_array($args[0])) { |
|
100 | + $children = isset($args[0]) ? $args[0] : []; |
|
101 | + $defaults = isset($args[1]) ? $args[1] : true; |
|
102 | + } else { |
|
103 | + $name = $args[0]; |
|
104 | + $children = isset($args[1]) ? $args[1] : []; |
|
105 | + $defaults = isset($args[2]) ? $args[2] : true; |
|
106 | + } |
|
107 | + parent::__construct($this, $name, $children, $defaults); |
|
108 | + } |
|
109 | 109 | |
110 | - /** |
|
111 | - * Returns the current document type. |
|
112 | - * |
|
113 | - * @return int |
|
114 | - */ |
|
115 | - public function getDocumentType() |
|
116 | - { |
|
117 | - return self::UNKNOWN; |
|
118 | - } |
|
110 | + /** |
|
111 | + * Returns the current document type. |
|
112 | + * |
|
113 | + * @return int |
|
114 | + */ |
|
115 | + public function getDocumentType() |
|
116 | + { |
|
117 | + return self::UNKNOWN; |
|
118 | + } |
|
119 | 119 | |
120 | - /** |
|
121 | - * Creates a new component or property. |
|
122 | - * |
|
123 | - * If it's a known component, we will automatically call createComponent. |
|
124 | - * otherwise, we'll assume it's a property and call createProperty instead. |
|
125 | - * |
|
126 | - * @param string $name |
|
127 | - * @param string $arg1,... Unlimited number of args |
|
128 | - * |
|
129 | - * @return mixed |
|
130 | - */ |
|
131 | - public function create($name) |
|
132 | - { |
|
133 | - if (isset(static::$componentMap[strtoupper($name)])) { |
|
134 | - return call_user_func_array([$this, 'createComponent'], func_get_args()); |
|
135 | - } else { |
|
136 | - return call_user_func_array([$this, 'createProperty'], func_get_args()); |
|
137 | - } |
|
138 | - } |
|
120 | + /** |
|
121 | + * Creates a new component or property. |
|
122 | + * |
|
123 | + * If it's a known component, we will automatically call createComponent. |
|
124 | + * otherwise, we'll assume it's a property and call createProperty instead. |
|
125 | + * |
|
126 | + * @param string $name |
|
127 | + * @param string $arg1,... Unlimited number of args |
|
128 | + * |
|
129 | + * @return mixed |
|
130 | + */ |
|
131 | + public function create($name) |
|
132 | + { |
|
133 | + if (isset(static::$componentMap[strtoupper($name)])) { |
|
134 | + return call_user_func_array([$this, 'createComponent'], func_get_args()); |
|
135 | + } else { |
|
136 | + return call_user_func_array([$this, 'createProperty'], func_get_args()); |
|
137 | + } |
|
138 | + } |
|
139 | 139 | |
140 | - /** |
|
141 | - * Creates a new component. |
|
142 | - * |
|
143 | - * This method automatically searches for the correct component class, based |
|
144 | - * on its name. |
|
145 | - * |
|
146 | - * You can specify the children either in key=>value syntax, in which case |
|
147 | - * properties will automatically be created, or you can just pass a list of |
|
148 | - * Component and Property object. |
|
149 | - * |
|
150 | - * By default, a set of sensible values will be added to the component. For |
|
151 | - * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To |
|
152 | - * ensure that this does not happen, set $defaults to false. |
|
153 | - * |
|
154 | - * @param string $name |
|
155 | - * @param array $children |
|
156 | - * @param bool $defaults |
|
157 | - * |
|
158 | - * @return Component |
|
159 | - */ |
|
160 | - public function createComponent($name, array $children = null, $defaults = true) |
|
161 | - { |
|
162 | - $name = strtoupper($name); |
|
163 | - $class = Component::class; |
|
140 | + /** |
|
141 | + * Creates a new component. |
|
142 | + * |
|
143 | + * This method automatically searches for the correct component class, based |
|
144 | + * on its name. |
|
145 | + * |
|
146 | + * You can specify the children either in key=>value syntax, in which case |
|
147 | + * properties will automatically be created, or you can just pass a list of |
|
148 | + * Component and Property object. |
|
149 | + * |
|
150 | + * By default, a set of sensible values will be added to the component. For |
|
151 | + * an iCalendar object, this may be something like CALSCALE:GREGORIAN. To |
|
152 | + * ensure that this does not happen, set $defaults to false. |
|
153 | + * |
|
154 | + * @param string $name |
|
155 | + * @param array $children |
|
156 | + * @param bool $defaults |
|
157 | + * |
|
158 | + * @return Component |
|
159 | + */ |
|
160 | + public function createComponent($name, array $children = null, $defaults = true) |
|
161 | + { |
|
162 | + $name = strtoupper($name); |
|
163 | + $class = Component::class; |
|
164 | 164 | |
165 | - if (isset(static::$componentMap[$name])) { |
|
166 | - $class = static::$componentMap[$name]; |
|
167 | - } |
|
168 | - if (is_null($children)) { |
|
169 | - $children = []; |
|
170 | - } |
|
165 | + if (isset(static::$componentMap[$name])) { |
|
166 | + $class = static::$componentMap[$name]; |
|
167 | + } |
|
168 | + if (is_null($children)) { |
|
169 | + $children = []; |
|
170 | + } |
|
171 | 171 | |
172 | - return new $class($this, $name, $children, $defaults); |
|
173 | - } |
|
172 | + return new $class($this, $name, $children, $defaults); |
|
173 | + } |
|
174 | 174 | |
175 | - /** |
|
176 | - * Factory method for creating new properties. |
|
177 | - * |
|
178 | - * This method automatically searches for the correct property class, based |
|
179 | - * on its name. |
|
180 | - * |
|
181 | - * You can specify the parameters either in key=>value syntax, in which case |
|
182 | - * parameters will automatically be created, or you can just pass a list of |
|
183 | - * Parameter objects. |
|
184 | - * |
|
185 | - * @param string $name |
|
186 | - * @param mixed $value |
|
187 | - * @param array $parameters |
|
188 | - * @param string $valueType Force a specific valuetype, such as URI or TEXT |
|
189 | - * |
|
190 | - * @return Property |
|
191 | - */ |
|
192 | - public function createProperty($name, $value = null, array $parameters = null, $valueType = null) |
|
193 | - { |
|
194 | - // If there's a . in the name, it means it's prefixed by a groupname. |
|
195 | - if (false !== ($i = strpos($name, '.'))) { |
|
196 | - $group = substr($name, 0, $i); |
|
197 | - $name = strtoupper(substr($name, $i + 1)); |
|
198 | - } else { |
|
199 | - $name = strtoupper($name); |
|
200 | - $group = null; |
|
201 | - } |
|
175 | + /** |
|
176 | + * Factory method for creating new properties. |
|
177 | + * |
|
178 | + * This method automatically searches for the correct property class, based |
|
179 | + * on its name. |
|
180 | + * |
|
181 | + * You can specify the parameters either in key=>value syntax, in which case |
|
182 | + * parameters will automatically be created, or you can just pass a list of |
|
183 | + * Parameter objects. |
|
184 | + * |
|
185 | + * @param string $name |
|
186 | + * @param mixed $value |
|
187 | + * @param array $parameters |
|
188 | + * @param string $valueType Force a specific valuetype, such as URI or TEXT |
|
189 | + * |
|
190 | + * @return Property |
|
191 | + */ |
|
192 | + public function createProperty($name, $value = null, array $parameters = null, $valueType = null) |
|
193 | + { |
|
194 | + // If there's a . in the name, it means it's prefixed by a groupname. |
|
195 | + if (false !== ($i = strpos($name, '.'))) { |
|
196 | + $group = substr($name, 0, $i); |
|
197 | + $name = strtoupper(substr($name, $i + 1)); |
|
198 | + } else { |
|
199 | + $name = strtoupper($name); |
|
200 | + $group = null; |
|
201 | + } |
|
202 | 202 | |
203 | - $class = null; |
|
203 | + $class = null; |
|
204 | 204 | |
205 | - if ($valueType) { |
|
206 | - // The valueType argument comes first to figure out the correct |
|
207 | - // class. |
|
208 | - $class = $this->getClassNameForPropertyValue($valueType); |
|
209 | - } |
|
205 | + if ($valueType) { |
|
206 | + // The valueType argument comes first to figure out the correct |
|
207 | + // class. |
|
208 | + $class = $this->getClassNameForPropertyValue($valueType); |
|
209 | + } |
|
210 | 210 | |
211 | - if (is_null($class)) { |
|
212 | - // If a VALUE parameter is supplied, we should use that. |
|
213 | - if (isset($parameters['VALUE'])) { |
|
214 | - $class = $this->getClassNameForPropertyValue($parameters['VALUE']); |
|
215 | - if (is_null($class)) { |
|
216 | - throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); |
|
217 | - } |
|
218 | - } else { |
|
219 | - $class = $this->getClassNameForPropertyName($name); |
|
220 | - } |
|
221 | - } |
|
222 | - if (is_null($parameters)) { |
|
223 | - $parameters = []; |
|
224 | - } |
|
211 | + if (is_null($class)) { |
|
212 | + // If a VALUE parameter is supplied, we should use that. |
|
213 | + if (isset($parameters['VALUE'])) { |
|
214 | + $class = $this->getClassNameForPropertyValue($parameters['VALUE']); |
|
215 | + if (is_null($class)) { |
|
216 | + throw new InvalidDataException('Unsupported VALUE parameter for '.$name.' property. You supplied "'.$parameters['VALUE'].'"'); |
|
217 | + } |
|
218 | + } else { |
|
219 | + $class = $this->getClassNameForPropertyName($name); |
|
220 | + } |
|
221 | + } |
|
222 | + if (is_null($parameters)) { |
|
223 | + $parameters = []; |
|
224 | + } |
|
225 | 225 | |
226 | - return new $class($this, $name, $value, $parameters, $group); |
|
227 | - } |
|
226 | + return new $class($this, $name, $value, $parameters, $group); |
|
227 | + } |
|
228 | 228 | |
229 | - /** |
|
230 | - * This method returns a full class-name for a value parameter. |
|
231 | - * |
|
232 | - * For instance, DTSTART may have VALUE=DATE. In that case we will look in |
|
233 | - * our valueMap table and return the appropriate class name. |
|
234 | - * |
|
235 | - * This method returns null if we don't have a specialized class. |
|
236 | - * |
|
237 | - * @param string $valueParam |
|
238 | - * |
|
239 | - * @return string|null |
|
240 | - */ |
|
241 | - public function getClassNameForPropertyValue($valueParam) |
|
242 | - { |
|
243 | - $valueParam = strtoupper($valueParam); |
|
244 | - if (isset(static::$valueMap[$valueParam])) { |
|
245 | - return static::$valueMap[$valueParam]; |
|
246 | - } |
|
247 | - } |
|
229 | + /** |
|
230 | + * This method returns a full class-name for a value parameter. |
|
231 | + * |
|
232 | + * For instance, DTSTART may have VALUE=DATE. In that case we will look in |
|
233 | + * our valueMap table and return the appropriate class name. |
|
234 | + * |
|
235 | + * This method returns null if we don't have a specialized class. |
|
236 | + * |
|
237 | + * @param string $valueParam |
|
238 | + * |
|
239 | + * @return string|null |
|
240 | + */ |
|
241 | + public function getClassNameForPropertyValue($valueParam) |
|
242 | + { |
|
243 | + $valueParam = strtoupper($valueParam); |
|
244 | + if (isset(static::$valueMap[$valueParam])) { |
|
245 | + return static::$valueMap[$valueParam]; |
|
246 | + } |
|
247 | + } |
|
248 | 248 | |
249 | - /** |
|
250 | - * Returns the default class for a property name. |
|
251 | - * |
|
252 | - * @param string $propertyName |
|
253 | - * |
|
254 | - * @return string |
|
255 | - */ |
|
256 | - public function getClassNameForPropertyName($propertyName) |
|
257 | - { |
|
258 | - if (isset(static::$propertyMap[$propertyName])) { |
|
259 | - return static::$propertyMap[$propertyName]; |
|
260 | - } else { |
|
261 | - return Property\Unknown::class; |
|
262 | - } |
|
263 | - } |
|
249 | + /** |
|
250 | + * Returns the default class for a property name. |
|
251 | + * |
|
252 | + * @param string $propertyName |
|
253 | + * |
|
254 | + * @return string |
|
255 | + */ |
|
256 | + public function getClassNameForPropertyName($propertyName) |
|
257 | + { |
|
258 | + if (isset(static::$propertyMap[$propertyName])) { |
|
259 | + return static::$propertyMap[$propertyName]; |
|
260 | + } else { |
|
261 | + return Property\Unknown::class; |
|
262 | + } |
|
263 | + } |
|
264 | 264 | } |
@@ -25,525 +25,525 @@ |
||
25 | 25 | */ |
26 | 26 | class FreeBusyGenerator |
27 | 27 | { |
28 | - /** |
|
29 | - * Input objects. |
|
30 | - * |
|
31 | - * @var array |
|
32 | - */ |
|
33 | - protected $objects = []; |
|
34 | - |
|
35 | - /** |
|
36 | - * Start of range. |
|
37 | - * |
|
38 | - * @var DateTimeInterface|null |
|
39 | - */ |
|
40 | - protected $start; |
|
41 | - |
|
42 | - /** |
|
43 | - * End of range. |
|
44 | - * |
|
45 | - * @var DateTimeInterface|null |
|
46 | - */ |
|
47 | - protected $end; |
|
48 | - |
|
49 | - /** |
|
50 | - * VCALENDAR object. |
|
51 | - * |
|
52 | - * @var Document |
|
53 | - */ |
|
54 | - protected $baseObject; |
|
55 | - |
|
56 | - /** |
|
57 | - * Reference timezone. |
|
58 | - * |
|
59 | - * When we are calculating busy times, and we come across so-called |
|
60 | - * floating times (times without a timezone), we use the reference timezone |
|
61 | - * instead. |
|
62 | - * |
|
63 | - * This is also used for all-day events. |
|
64 | - * |
|
65 | - * This defaults to UTC. |
|
66 | - * |
|
67 | - * @var DateTimeZone |
|
68 | - */ |
|
69 | - protected $timeZone; |
|
70 | - |
|
71 | - /** |
|
72 | - * A VAVAILABILITY document. |
|
73 | - * |
|
74 | - * If this is set, its information will be included when calculating |
|
75 | - * freebusy time. |
|
76 | - * |
|
77 | - * @var Document |
|
78 | - */ |
|
79 | - protected $vavailability; |
|
80 | - |
|
81 | - /** |
|
82 | - * Creates the generator. |
|
83 | - * |
|
84 | - * Check the setTimeRange and setObjects methods for details about the |
|
85 | - * arguments. |
|
86 | - * |
|
87 | - * @param DateTimeInterface $start |
|
88 | - * @param DateTimeInterface $end |
|
89 | - * @param mixed $objects |
|
90 | - * @param DateTimeZone $timeZone |
|
91 | - */ |
|
92 | - public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) |
|
93 | - { |
|
94 | - $this->setTimeRange($start, $end); |
|
95 | - |
|
96 | - if ($objects) { |
|
97 | - $this->setObjects($objects); |
|
98 | - } |
|
99 | - if (is_null($timeZone)) { |
|
100 | - $timeZone = new DateTimeZone('UTC'); |
|
101 | - } |
|
102 | - $this->setTimeZone($timeZone); |
|
103 | - } |
|
104 | - |
|
105 | - /** |
|
106 | - * Sets the VCALENDAR object. |
|
107 | - * |
|
108 | - * If this is set, it will not be generated for you. You are responsible |
|
109 | - * for setting things like the METHOD, CALSCALE, VERSION, etc.. |
|
110 | - * |
|
111 | - * The VFREEBUSY object will be automatically added though. |
|
112 | - */ |
|
113 | - public function setBaseObject(Document $vcalendar) |
|
114 | - { |
|
115 | - $this->baseObject = $vcalendar; |
|
116 | - } |
|
117 | - |
|
118 | - /** |
|
119 | - * Sets a VAVAILABILITY document. |
|
120 | - */ |
|
121 | - public function setVAvailability(Document $vcalendar) |
|
122 | - { |
|
123 | - $this->vavailability = $vcalendar; |
|
124 | - } |
|
125 | - |
|
126 | - /** |
|
127 | - * Sets the input objects. |
|
128 | - * |
|
129 | - * You must either specify a vcalendar object as a string, or as the parse |
|
130 | - * Component. |
|
131 | - * It's also possible to specify multiple objects as an array. |
|
132 | - * |
|
133 | - * @param mixed $objects |
|
134 | - */ |
|
135 | - public function setObjects($objects) |
|
136 | - { |
|
137 | - if (!is_array($objects)) { |
|
138 | - $objects = [$objects]; |
|
139 | - } |
|
140 | - |
|
141 | - $this->objects = []; |
|
142 | - foreach ($objects as $object) { |
|
143 | - if (is_string($object) || is_resource($object)) { |
|
144 | - $this->objects[] = Reader::read($object); |
|
145 | - } elseif ($object instanceof Component) { |
|
146 | - $this->objects[] = $object; |
|
147 | - } else { |
|
148 | - throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); |
|
149 | - } |
|
150 | - } |
|
151 | - } |
|
152 | - |
|
153 | - /** |
|
154 | - * Sets the time range. |
|
155 | - * |
|
156 | - * Any freebusy object falling outside of this time range will be ignored. |
|
157 | - * |
|
158 | - * @param DateTimeInterface $start |
|
159 | - * @param DateTimeInterface $end |
|
160 | - */ |
|
161 | - public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) |
|
162 | - { |
|
163 | - if (!$start) { |
|
164 | - $start = new DateTimeImmutable(Settings::$minDate); |
|
165 | - } |
|
166 | - if (!$end) { |
|
167 | - $end = new DateTimeImmutable(Settings::$maxDate); |
|
168 | - } |
|
169 | - $this->start = $start; |
|
170 | - $this->end = $end; |
|
171 | - } |
|
172 | - |
|
173 | - /** |
|
174 | - * Sets the reference timezone for floating times. |
|
175 | - */ |
|
176 | - public function setTimeZone(DateTimeZone $timeZone) |
|
177 | - { |
|
178 | - $this->timeZone = $timeZone; |
|
179 | - } |
|
180 | - |
|
181 | - /** |
|
182 | - * Parses the input data and returns a correct VFREEBUSY object, wrapped in |
|
183 | - * a VCALENDAR. |
|
184 | - * |
|
185 | - * @return Component |
|
186 | - */ |
|
187 | - public function getResult() |
|
188 | - { |
|
189 | - $fbData = new FreeBusyData( |
|
190 | - $this->start->getTimeStamp(), |
|
191 | - $this->end->getTimeStamp() |
|
192 | - ); |
|
193 | - if ($this->vavailability) { |
|
194 | - $this->calculateAvailability($fbData, $this->vavailability); |
|
195 | - } |
|
196 | - |
|
197 | - $this->calculateBusy($fbData, $this->objects); |
|
198 | - |
|
199 | - return $this->generateFreeBusyCalendar($fbData); |
|
200 | - } |
|
201 | - |
|
202 | - /** |
|
203 | - * This method takes a VAVAILABILITY component and figures out all the |
|
204 | - * available times. |
|
205 | - */ |
|
206 | - protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) |
|
207 | - { |
|
208 | - $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); |
|
209 | - usort( |
|
210 | - $vavailComps, |
|
211 | - function ($a, $b) { |
|
212 | - // We need to order the components by priority. Priority 1 |
|
213 | - // comes first, up until priority 9. Priority 0 comes after |
|
214 | - // priority 9. No priority implies priority 0. |
|
215 | - // |
|
216 | - // Yes, I'm serious. |
|
217 | - $priorityA = isset($a->PRIORITY) ? (int) $a->PRIORITY->getValue() : 0; |
|
218 | - $priorityB = isset($b->PRIORITY) ? (int) $b->PRIORITY->getValue() : 0; |
|
219 | - |
|
220 | - if (0 === $priorityA) { |
|
221 | - $priorityA = 10; |
|
222 | - } |
|
223 | - if (0 === $priorityB) { |
|
224 | - $priorityB = 10; |
|
225 | - } |
|
226 | - |
|
227 | - return $priorityA - $priorityB; |
|
228 | - } |
|
229 | - ); |
|
230 | - |
|
231 | - // Now we go over all the VAVAILABILITY components and figure if |
|
232 | - // there's any we don't need to consider. |
|
233 | - // |
|
234 | - // This is can be because of one of two reasons: either the |
|
235 | - // VAVAILABILITY component falls outside the time we are interested in, |
|
236 | - // or a different VAVAILABILITY component with a higher priority has |
|
237 | - // already completely covered the time-range. |
|
238 | - $old = $vavailComps; |
|
239 | - $new = []; |
|
240 | - |
|
241 | - foreach ($old as $vavail) { |
|
242 | - list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); |
|
243 | - |
|
244 | - // We don't care about datetimes that are earlier or later than the |
|
245 | - // start and end of the freebusy report, so this gets normalized |
|
246 | - // first. |
|
247 | - if (is_null($compStart) || $compStart < $this->start) { |
|
248 | - $compStart = $this->start; |
|
249 | - } |
|
250 | - if (is_null($compEnd) || $compEnd > $this->end) { |
|
251 | - $compEnd = $this->end; |
|
252 | - } |
|
253 | - |
|
254 | - // If the item fell out of the timerange, we can just skip it. |
|
255 | - if ($compStart > $this->end || $compEnd < $this->start) { |
|
256 | - continue; |
|
257 | - } |
|
258 | - |
|
259 | - // Going through our existing list of components to see if there's |
|
260 | - // a higher priority component that already fully covers this one. |
|
261 | - foreach ($new as $higherVavail) { |
|
262 | - list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); |
|
263 | - if ( |
|
264 | - (is_null($higherStart) || $higherStart < $compStart) && |
|
265 | - (is_null($higherEnd) || $higherEnd > $compEnd) |
|
266 | - ) { |
|
267 | - // Component is fully covered by a higher priority |
|
268 | - // component. We can skip this component. |
|
269 | - continue 2; |
|
270 | - } |
|
271 | - } |
|
272 | - |
|
273 | - // We're keeping it! |
|
274 | - $new[] = $vavail; |
|
275 | - } |
|
276 | - |
|
277 | - // Lastly, we need to traverse the remaining components and fill in the |
|
278 | - // freebusydata slots. |
|
279 | - // |
|
280 | - // We traverse the components in reverse, because we want the higher |
|
281 | - // priority components to override the lower ones. |
|
282 | - foreach (array_reverse($new) as $vavail) { |
|
283 | - $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; |
|
284 | - list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); |
|
285 | - |
|
286 | - // Making the component size no larger than the requested free-busy |
|
287 | - // report range. |
|
288 | - if (!$vavailStart || $vavailStart < $this->start) { |
|
289 | - $vavailStart = $this->start; |
|
290 | - } |
|
291 | - if (!$vavailEnd || $vavailEnd > $this->end) { |
|
292 | - $vavailEnd = $this->end; |
|
293 | - } |
|
294 | - |
|
295 | - // Marking the entire time range of the VAVAILABILITY component as |
|
296 | - // busy. |
|
297 | - $fbData->add( |
|
298 | - $vavailStart->getTimeStamp(), |
|
299 | - $vavailEnd->getTimeStamp(), |
|
300 | - $busyType |
|
301 | - ); |
|
302 | - |
|
303 | - // Looping over the AVAILABLE components. |
|
304 | - if (isset($vavail->AVAILABLE)) { |
|
305 | - foreach ($vavail->AVAILABLE as $available) { |
|
306 | - list($availStart, $availEnd) = $available->getEffectiveStartEnd(); |
|
307 | - $fbData->add( |
|
308 | - $availStart->getTimeStamp(), |
|
309 | - $availEnd->getTimeStamp(), |
|
310 | - 'FREE' |
|
311 | - ); |
|
312 | - |
|
313 | - if ($available->RRULE) { |
|
314 | - // Our favourite thing: recurrence!! |
|
315 | - |
|
316 | - $rruleIterator = new Recur\RRuleIterator( |
|
317 | - $available->RRULE->getValue(), |
|
318 | - $availStart |
|
319 | - ); |
|
320 | - $rruleIterator->fastForward($vavailStart); |
|
321 | - |
|
322 | - $startEndDiff = $availStart->diff($availEnd); |
|
323 | - |
|
324 | - while ($rruleIterator->valid()) { |
|
325 | - $recurStart = $rruleIterator->current(); |
|
326 | - $recurEnd = $recurStart->add($startEndDiff); |
|
327 | - |
|
328 | - if ($recurStart > $vavailEnd) { |
|
329 | - // We're beyond the legal timerange. |
|
330 | - break; |
|
331 | - } |
|
332 | - |
|
333 | - if ($recurEnd > $vavailEnd) { |
|
334 | - // Truncating the end if it exceeds the |
|
335 | - // VAVAILABILITY end. |
|
336 | - $recurEnd = $vavailEnd; |
|
337 | - } |
|
338 | - |
|
339 | - $fbData->add( |
|
340 | - $recurStart->getTimeStamp(), |
|
341 | - $recurEnd->getTimeStamp(), |
|
342 | - 'FREE' |
|
343 | - ); |
|
344 | - |
|
345 | - $rruleIterator->next(); |
|
346 | - } |
|
347 | - } |
|
348 | - } |
|
349 | - } |
|
350 | - } |
|
351 | - } |
|
352 | - |
|
353 | - /** |
|
354 | - * This method takes an array of iCalendar objects and applies its busy |
|
355 | - * times on fbData. |
|
356 | - * |
|
357 | - * @param VCalendar[] $objects |
|
358 | - */ |
|
359 | - protected function calculateBusy(FreeBusyData $fbData, array $objects) |
|
360 | - { |
|
361 | - foreach ($objects as $key => $object) { |
|
362 | - foreach ($object->getBaseComponents() as $component) { |
|
363 | - switch ($component->name) { |
|
364 | - case 'VEVENT': |
|
365 | - $FBTYPE = 'BUSY'; |
|
366 | - if (isset($component->TRANSP) && ('TRANSPARENT' === strtoupper($component->TRANSP))) { |
|
367 | - break; |
|
368 | - } |
|
369 | - if (isset($component->STATUS)) { |
|
370 | - $status = strtoupper($component->STATUS); |
|
371 | - if ('CANCELLED' === $status) { |
|
372 | - break; |
|
373 | - } |
|
374 | - if ('TENTATIVE' === $status) { |
|
375 | - $FBTYPE = 'BUSY-TENTATIVE'; |
|
376 | - } |
|
377 | - } |
|
378 | - |
|
379 | - $times = []; |
|
380 | - |
|
381 | - if ($component->RRULE) { |
|
382 | - try { |
|
383 | - $iterator = new EventIterator($object, (string) $component->UID, $this->timeZone); |
|
384 | - } catch (NoInstancesException $e) { |
|
385 | - // This event is recurring, but it doesn't have a single |
|
386 | - // instance. We are skipping this event from the output |
|
387 | - // entirely. |
|
388 | - unset($this->objects[$key]); |
|
389 | - break; |
|
390 | - } |
|
391 | - |
|
392 | - if ($this->start) { |
|
393 | - $iterator->fastForward($this->start); |
|
394 | - } |
|
395 | - |
|
396 | - $maxRecurrences = Settings::$maxRecurrences; |
|
397 | - |
|
398 | - while ($iterator->valid() && --$maxRecurrences) { |
|
399 | - $startTime = $iterator->getDTStart(); |
|
400 | - if ($this->end && $startTime > $this->end) { |
|
401 | - break; |
|
402 | - } |
|
403 | - $times[] = [ |
|
404 | - $iterator->getDTStart(), |
|
405 | - $iterator->getDTEnd(), |
|
406 | - ]; |
|
407 | - |
|
408 | - $iterator->next(); |
|
409 | - } |
|
410 | - } else { |
|
411 | - $startTime = $component->DTSTART->getDateTime($this->timeZone); |
|
412 | - if ($this->end && $startTime > $this->end) { |
|
413 | - break; |
|
414 | - } |
|
415 | - $endTime = null; |
|
416 | - if (isset($component->DTEND)) { |
|
417 | - $endTime = $component->DTEND->getDateTime($this->timeZone); |
|
418 | - } elseif (isset($component->DURATION)) { |
|
419 | - $duration = DateTimeParser::parseDuration((string) $component->DURATION); |
|
420 | - $endTime = clone $startTime; |
|
421 | - $endTime = $endTime->add($duration); |
|
422 | - } elseif (!$component->DTSTART->hasTime()) { |
|
423 | - $endTime = clone $startTime; |
|
424 | - $endTime = $endTime->modify('+1 day'); |
|
425 | - } else { |
|
426 | - // The event had no duration (0 seconds) |
|
427 | - break; |
|
428 | - } |
|
429 | - |
|
430 | - $times[] = [$startTime, $endTime]; |
|
431 | - } |
|
432 | - |
|
433 | - foreach ($times as $time) { |
|
434 | - if ($this->end && $time[0] > $this->end) { |
|
435 | - break; |
|
436 | - } |
|
437 | - if ($this->start && $time[1] < $this->start) { |
|
438 | - break; |
|
439 | - } |
|
440 | - |
|
441 | - $fbData->add( |
|
442 | - $time[0]->getTimeStamp(), |
|
443 | - $time[1]->getTimeStamp(), |
|
444 | - $FBTYPE |
|
445 | - ); |
|
446 | - } |
|
447 | - break; |
|
448 | - |
|
449 | - case 'VFREEBUSY': |
|
450 | - foreach ($component->FREEBUSY as $freebusy) { |
|
451 | - $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; |
|
452 | - |
|
453 | - // Skipping intervals marked as 'free' |
|
454 | - if ('FREE' === $fbType) { |
|
455 | - continue; |
|
456 | - } |
|
457 | - |
|
458 | - $values = explode(',', $freebusy); |
|
459 | - foreach ($values as $value) { |
|
460 | - list($startTime, $endTime) = explode('/', $value); |
|
461 | - $startTime = DateTimeParser::parseDateTime($startTime); |
|
462 | - |
|
463 | - if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) { |
|
464 | - $duration = DateTimeParser::parseDuration($endTime); |
|
465 | - $endTime = clone $startTime; |
|
466 | - $endTime = $endTime->add($duration); |
|
467 | - } else { |
|
468 | - $endTime = DateTimeParser::parseDateTime($endTime); |
|
469 | - } |
|
470 | - |
|
471 | - if ($this->start && $this->start > $endTime) { |
|
472 | - continue; |
|
473 | - } |
|
474 | - if ($this->end && $this->end < $startTime) { |
|
475 | - continue; |
|
476 | - } |
|
477 | - $fbData->add( |
|
478 | - $startTime->getTimeStamp(), |
|
479 | - $endTime->getTimeStamp(), |
|
480 | - $fbType |
|
481 | - ); |
|
482 | - } |
|
483 | - } |
|
484 | - break; |
|
485 | - } |
|
486 | - } |
|
487 | - } |
|
488 | - } |
|
489 | - |
|
490 | - /** |
|
491 | - * This method takes a FreeBusyData object and generates the VCALENDAR |
|
492 | - * object associated with it. |
|
493 | - * |
|
494 | - * @return VCalendar |
|
495 | - */ |
|
496 | - protected function generateFreeBusyCalendar(FreeBusyData $fbData) |
|
497 | - { |
|
498 | - if ($this->baseObject) { |
|
499 | - $calendar = $this->baseObject; |
|
500 | - } else { |
|
501 | - $calendar = new VCalendar(); |
|
502 | - } |
|
503 | - |
|
504 | - $vfreebusy = $calendar->createComponent('VFREEBUSY'); |
|
505 | - $calendar->add($vfreebusy); |
|
506 | - |
|
507 | - if ($this->start) { |
|
508 | - $dtstart = $calendar->createProperty('DTSTART'); |
|
509 | - $dtstart->setDateTime($this->start); |
|
510 | - $vfreebusy->add($dtstart); |
|
511 | - } |
|
512 | - if ($this->end) { |
|
513 | - $dtend = $calendar->createProperty('DTEND'); |
|
514 | - $dtend->setDateTime($this->end); |
|
515 | - $vfreebusy->add($dtend); |
|
516 | - } |
|
517 | - |
|
518 | - $tz = new \DateTimeZone('UTC'); |
|
519 | - $dtstamp = $calendar->createProperty('DTSTAMP'); |
|
520 | - $dtstamp->setDateTime(new DateTimeImmutable('now', $tz)); |
|
521 | - $vfreebusy->add($dtstamp); |
|
522 | - |
|
523 | - foreach ($fbData->getData() as $busyTime) { |
|
524 | - $busyType = strtoupper($busyTime['type']); |
|
525 | - |
|
526 | - // Ignoring all the FREE parts, because those are already assumed. |
|
527 | - if ('FREE' === $busyType) { |
|
528 | - continue; |
|
529 | - } |
|
530 | - |
|
531 | - $busyTime[0] = new \DateTimeImmutable('@'.$busyTime['start'], $tz); |
|
532 | - $busyTime[1] = new \DateTimeImmutable('@'.$busyTime['end'], $tz); |
|
533 | - |
|
534 | - $prop = $calendar->createProperty( |
|
535 | - 'FREEBUSY', |
|
536 | - $busyTime[0]->format('Ymd\\THis\\Z').'/'.$busyTime[1]->format('Ymd\\THis\\Z') |
|
537 | - ); |
|
538 | - |
|
539 | - // Only setting FBTYPE if it's not BUSY, because BUSY is the |
|
540 | - // default anyway. |
|
541 | - if ('BUSY' !== $busyType) { |
|
542 | - $prop['FBTYPE'] = $busyType; |
|
543 | - } |
|
544 | - $vfreebusy->add($prop); |
|
545 | - } |
|
546 | - |
|
547 | - return $calendar; |
|
548 | - } |
|
28 | + /** |
|
29 | + * Input objects. |
|
30 | + * |
|
31 | + * @var array |
|
32 | + */ |
|
33 | + protected $objects = []; |
|
34 | + |
|
35 | + /** |
|
36 | + * Start of range. |
|
37 | + * |
|
38 | + * @var DateTimeInterface|null |
|
39 | + */ |
|
40 | + protected $start; |
|
41 | + |
|
42 | + /** |
|
43 | + * End of range. |
|
44 | + * |
|
45 | + * @var DateTimeInterface|null |
|
46 | + */ |
|
47 | + protected $end; |
|
48 | + |
|
49 | + /** |
|
50 | + * VCALENDAR object. |
|
51 | + * |
|
52 | + * @var Document |
|
53 | + */ |
|
54 | + protected $baseObject; |
|
55 | + |
|
56 | + /** |
|
57 | + * Reference timezone. |
|
58 | + * |
|
59 | + * When we are calculating busy times, and we come across so-called |
|
60 | + * floating times (times without a timezone), we use the reference timezone |
|
61 | + * instead. |
|
62 | + * |
|
63 | + * This is also used for all-day events. |
|
64 | + * |
|
65 | + * This defaults to UTC. |
|
66 | + * |
|
67 | + * @var DateTimeZone |
|
68 | + */ |
|
69 | + protected $timeZone; |
|
70 | + |
|
71 | + /** |
|
72 | + * A VAVAILABILITY document. |
|
73 | + * |
|
74 | + * If this is set, its information will be included when calculating |
|
75 | + * freebusy time. |
|
76 | + * |
|
77 | + * @var Document |
|
78 | + */ |
|
79 | + protected $vavailability; |
|
80 | + |
|
81 | + /** |
|
82 | + * Creates the generator. |
|
83 | + * |
|
84 | + * Check the setTimeRange and setObjects methods for details about the |
|
85 | + * arguments. |
|
86 | + * |
|
87 | + * @param DateTimeInterface $start |
|
88 | + * @param DateTimeInterface $end |
|
89 | + * @param mixed $objects |
|
90 | + * @param DateTimeZone $timeZone |
|
91 | + */ |
|
92 | + public function __construct(DateTimeInterface $start = null, DateTimeInterface $end = null, $objects = null, DateTimeZone $timeZone = null) |
|
93 | + { |
|
94 | + $this->setTimeRange($start, $end); |
|
95 | + |
|
96 | + if ($objects) { |
|
97 | + $this->setObjects($objects); |
|
98 | + } |
|
99 | + if (is_null($timeZone)) { |
|
100 | + $timeZone = new DateTimeZone('UTC'); |
|
101 | + } |
|
102 | + $this->setTimeZone($timeZone); |
|
103 | + } |
|
104 | + |
|
105 | + /** |
|
106 | + * Sets the VCALENDAR object. |
|
107 | + * |
|
108 | + * If this is set, it will not be generated for you. You are responsible |
|
109 | + * for setting things like the METHOD, CALSCALE, VERSION, etc.. |
|
110 | + * |
|
111 | + * The VFREEBUSY object will be automatically added though. |
|
112 | + */ |
|
113 | + public function setBaseObject(Document $vcalendar) |
|
114 | + { |
|
115 | + $this->baseObject = $vcalendar; |
|
116 | + } |
|
117 | + |
|
118 | + /** |
|
119 | + * Sets a VAVAILABILITY document. |
|
120 | + */ |
|
121 | + public function setVAvailability(Document $vcalendar) |
|
122 | + { |
|
123 | + $this->vavailability = $vcalendar; |
|
124 | + } |
|
125 | + |
|
126 | + /** |
|
127 | + * Sets the input objects. |
|
128 | + * |
|
129 | + * You must either specify a vcalendar object as a string, or as the parse |
|
130 | + * Component. |
|
131 | + * It's also possible to specify multiple objects as an array. |
|
132 | + * |
|
133 | + * @param mixed $objects |
|
134 | + */ |
|
135 | + public function setObjects($objects) |
|
136 | + { |
|
137 | + if (!is_array($objects)) { |
|
138 | + $objects = [$objects]; |
|
139 | + } |
|
140 | + |
|
141 | + $this->objects = []; |
|
142 | + foreach ($objects as $object) { |
|
143 | + if (is_string($object) || is_resource($object)) { |
|
144 | + $this->objects[] = Reader::read($object); |
|
145 | + } elseif ($object instanceof Component) { |
|
146 | + $this->objects[] = $object; |
|
147 | + } else { |
|
148 | + throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects'); |
|
149 | + } |
|
150 | + } |
|
151 | + } |
|
152 | + |
|
153 | + /** |
|
154 | + * Sets the time range. |
|
155 | + * |
|
156 | + * Any freebusy object falling outside of this time range will be ignored. |
|
157 | + * |
|
158 | + * @param DateTimeInterface $start |
|
159 | + * @param DateTimeInterface $end |
|
160 | + */ |
|
161 | + public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) |
|
162 | + { |
|
163 | + if (!$start) { |
|
164 | + $start = new DateTimeImmutable(Settings::$minDate); |
|
165 | + } |
|
166 | + if (!$end) { |
|
167 | + $end = new DateTimeImmutable(Settings::$maxDate); |
|
168 | + } |
|
169 | + $this->start = $start; |
|
170 | + $this->end = $end; |
|
171 | + } |
|
172 | + |
|
173 | + /** |
|
174 | + * Sets the reference timezone for floating times. |
|
175 | + */ |
|
176 | + public function setTimeZone(DateTimeZone $timeZone) |
|
177 | + { |
|
178 | + $this->timeZone = $timeZone; |
|
179 | + } |
|
180 | + |
|
181 | + /** |
|
182 | + * Parses the input data and returns a correct VFREEBUSY object, wrapped in |
|
183 | + * a VCALENDAR. |
|
184 | + * |
|
185 | + * @return Component |
|
186 | + */ |
|
187 | + public function getResult() |
|
188 | + { |
|
189 | + $fbData = new FreeBusyData( |
|
190 | + $this->start->getTimeStamp(), |
|
191 | + $this->end->getTimeStamp() |
|
192 | + ); |
|
193 | + if ($this->vavailability) { |
|
194 | + $this->calculateAvailability($fbData, $this->vavailability); |
|
195 | + } |
|
196 | + |
|
197 | + $this->calculateBusy($fbData, $this->objects); |
|
198 | + |
|
199 | + return $this->generateFreeBusyCalendar($fbData); |
|
200 | + } |
|
201 | + |
|
202 | + /** |
|
203 | + * This method takes a VAVAILABILITY component and figures out all the |
|
204 | + * available times. |
|
205 | + */ |
|
206 | + protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) |
|
207 | + { |
|
208 | + $vavailComps = iterator_to_array($vavailability->VAVAILABILITY); |
|
209 | + usort( |
|
210 | + $vavailComps, |
|
211 | + function ($a, $b) { |
|
212 | + // We need to order the components by priority. Priority 1 |
|
213 | + // comes first, up until priority 9. Priority 0 comes after |
|
214 | + // priority 9. No priority implies priority 0. |
|
215 | + // |
|
216 | + // Yes, I'm serious. |
|
217 | + $priorityA = isset($a->PRIORITY) ? (int) $a->PRIORITY->getValue() : 0; |
|
218 | + $priorityB = isset($b->PRIORITY) ? (int) $b->PRIORITY->getValue() : 0; |
|
219 | + |
|
220 | + if (0 === $priorityA) { |
|
221 | + $priorityA = 10; |
|
222 | + } |
|
223 | + if (0 === $priorityB) { |
|
224 | + $priorityB = 10; |
|
225 | + } |
|
226 | + |
|
227 | + return $priorityA - $priorityB; |
|
228 | + } |
|
229 | + ); |
|
230 | + |
|
231 | + // Now we go over all the VAVAILABILITY components and figure if |
|
232 | + // there's any we don't need to consider. |
|
233 | + // |
|
234 | + // This is can be because of one of two reasons: either the |
|
235 | + // VAVAILABILITY component falls outside the time we are interested in, |
|
236 | + // or a different VAVAILABILITY component with a higher priority has |
|
237 | + // already completely covered the time-range. |
|
238 | + $old = $vavailComps; |
|
239 | + $new = []; |
|
240 | + |
|
241 | + foreach ($old as $vavail) { |
|
242 | + list($compStart, $compEnd) = $vavail->getEffectiveStartEnd(); |
|
243 | + |
|
244 | + // We don't care about datetimes that are earlier or later than the |
|
245 | + // start and end of the freebusy report, so this gets normalized |
|
246 | + // first. |
|
247 | + if (is_null($compStart) || $compStart < $this->start) { |
|
248 | + $compStart = $this->start; |
|
249 | + } |
|
250 | + if (is_null($compEnd) || $compEnd > $this->end) { |
|
251 | + $compEnd = $this->end; |
|
252 | + } |
|
253 | + |
|
254 | + // If the item fell out of the timerange, we can just skip it. |
|
255 | + if ($compStart > $this->end || $compEnd < $this->start) { |
|
256 | + continue; |
|
257 | + } |
|
258 | + |
|
259 | + // Going through our existing list of components to see if there's |
|
260 | + // a higher priority component that already fully covers this one. |
|
261 | + foreach ($new as $higherVavail) { |
|
262 | + list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd(); |
|
263 | + if ( |
|
264 | + (is_null($higherStart) || $higherStart < $compStart) && |
|
265 | + (is_null($higherEnd) || $higherEnd > $compEnd) |
|
266 | + ) { |
|
267 | + // Component is fully covered by a higher priority |
|
268 | + // component. We can skip this component. |
|
269 | + continue 2; |
|
270 | + } |
|
271 | + } |
|
272 | + |
|
273 | + // We're keeping it! |
|
274 | + $new[] = $vavail; |
|
275 | + } |
|
276 | + |
|
277 | + // Lastly, we need to traverse the remaining components and fill in the |
|
278 | + // freebusydata slots. |
|
279 | + // |
|
280 | + // We traverse the components in reverse, because we want the higher |
|
281 | + // priority components to override the lower ones. |
|
282 | + foreach (array_reverse($new) as $vavail) { |
|
283 | + $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE'; |
|
284 | + list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd(); |
|
285 | + |
|
286 | + // Making the component size no larger than the requested free-busy |
|
287 | + // report range. |
|
288 | + if (!$vavailStart || $vavailStart < $this->start) { |
|
289 | + $vavailStart = $this->start; |
|
290 | + } |
|
291 | + if (!$vavailEnd || $vavailEnd > $this->end) { |
|
292 | + $vavailEnd = $this->end; |
|
293 | + } |
|
294 | + |
|
295 | + // Marking the entire time range of the VAVAILABILITY component as |
|
296 | + // busy. |
|
297 | + $fbData->add( |
|
298 | + $vavailStart->getTimeStamp(), |
|
299 | + $vavailEnd->getTimeStamp(), |
|
300 | + $busyType |
|
301 | + ); |
|
302 | + |
|
303 | + // Looping over the AVAILABLE components. |
|
304 | + if (isset($vavail->AVAILABLE)) { |
|
305 | + foreach ($vavail->AVAILABLE as $available) { |
|
306 | + list($availStart, $availEnd) = $available->getEffectiveStartEnd(); |
|
307 | + $fbData->add( |
|
308 | + $availStart->getTimeStamp(), |
|
309 | + $availEnd->getTimeStamp(), |
|
310 | + 'FREE' |
|
311 | + ); |
|
312 | + |
|
313 | + if ($available->RRULE) { |
|
314 | + // Our favourite thing: recurrence!! |
|
315 | + |
|
316 | + $rruleIterator = new Recur\RRuleIterator( |
|
317 | + $available->RRULE->getValue(), |
|
318 | + $availStart |
|
319 | + ); |
|
320 | + $rruleIterator->fastForward($vavailStart); |
|
321 | + |
|
322 | + $startEndDiff = $availStart->diff($availEnd); |
|
323 | + |
|
324 | + while ($rruleIterator->valid()) { |
|
325 | + $recurStart = $rruleIterator->current(); |
|
326 | + $recurEnd = $recurStart->add($startEndDiff); |
|
327 | + |
|
328 | + if ($recurStart > $vavailEnd) { |
|
329 | + // We're beyond the legal timerange. |
|
330 | + break; |
|
331 | + } |
|
332 | + |
|
333 | + if ($recurEnd > $vavailEnd) { |
|
334 | + // Truncating the end if it exceeds the |
|
335 | + // VAVAILABILITY end. |
|
336 | + $recurEnd = $vavailEnd; |
|
337 | + } |
|
338 | + |
|
339 | + $fbData->add( |
|
340 | + $recurStart->getTimeStamp(), |
|
341 | + $recurEnd->getTimeStamp(), |
|
342 | + 'FREE' |
|
343 | + ); |
|
344 | + |
|
345 | + $rruleIterator->next(); |
|
346 | + } |
|
347 | + } |
|
348 | + } |
|
349 | + } |
|
350 | + } |
|
351 | + } |
|
352 | + |
|
353 | + /** |
|
354 | + * This method takes an array of iCalendar objects and applies its busy |
|
355 | + * times on fbData. |
|
356 | + * |
|
357 | + * @param VCalendar[] $objects |
|
358 | + */ |
|
359 | + protected function calculateBusy(FreeBusyData $fbData, array $objects) |
|
360 | + { |
|
361 | + foreach ($objects as $key => $object) { |
|
362 | + foreach ($object->getBaseComponents() as $component) { |
|
363 | + switch ($component->name) { |
|
364 | + case 'VEVENT': |
|
365 | + $FBTYPE = 'BUSY'; |
|
366 | + if (isset($component->TRANSP) && ('TRANSPARENT' === strtoupper($component->TRANSP))) { |
|
367 | + break; |
|
368 | + } |
|
369 | + if (isset($component->STATUS)) { |
|
370 | + $status = strtoupper($component->STATUS); |
|
371 | + if ('CANCELLED' === $status) { |
|
372 | + break; |
|
373 | + } |
|
374 | + if ('TENTATIVE' === $status) { |
|
375 | + $FBTYPE = 'BUSY-TENTATIVE'; |
|
376 | + } |
|
377 | + } |
|
378 | + |
|
379 | + $times = []; |
|
380 | + |
|
381 | + if ($component->RRULE) { |
|
382 | + try { |
|
383 | + $iterator = new EventIterator($object, (string) $component->UID, $this->timeZone); |
|
384 | + } catch (NoInstancesException $e) { |
|
385 | + // This event is recurring, but it doesn't have a single |
|
386 | + // instance. We are skipping this event from the output |
|
387 | + // entirely. |
|
388 | + unset($this->objects[$key]); |
|
389 | + break; |
|
390 | + } |
|
391 | + |
|
392 | + if ($this->start) { |
|
393 | + $iterator->fastForward($this->start); |
|
394 | + } |
|
395 | + |
|
396 | + $maxRecurrences = Settings::$maxRecurrences; |
|
397 | + |
|
398 | + while ($iterator->valid() && --$maxRecurrences) { |
|
399 | + $startTime = $iterator->getDTStart(); |
|
400 | + if ($this->end && $startTime > $this->end) { |
|
401 | + break; |
|
402 | + } |
|
403 | + $times[] = [ |
|
404 | + $iterator->getDTStart(), |
|
405 | + $iterator->getDTEnd(), |
|
406 | + ]; |
|
407 | + |
|
408 | + $iterator->next(); |
|
409 | + } |
|
410 | + } else { |
|
411 | + $startTime = $component->DTSTART->getDateTime($this->timeZone); |
|
412 | + if ($this->end && $startTime > $this->end) { |
|
413 | + break; |
|
414 | + } |
|
415 | + $endTime = null; |
|
416 | + if (isset($component->DTEND)) { |
|
417 | + $endTime = $component->DTEND->getDateTime($this->timeZone); |
|
418 | + } elseif (isset($component->DURATION)) { |
|
419 | + $duration = DateTimeParser::parseDuration((string) $component->DURATION); |
|
420 | + $endTime = clone $startTime; |
|
421 | + $endTime = $endTime->add($duration); |
|
422 | + } elseif (!$component->DTSTART->hasTime()) { |
|
423 | + $endTime = clone $startTime; |
|
424 | + $endTime = $endTime->modify('+1 day'); |
|
425 | + } else { |
|
426 | + // The event had no duration (0 seconds) |
|
427 | + break; |
|
428 | + } |
|
429 | + |
|
430 | + $times[] = [$startTime, $endTime]; |
|
431 | + } |
|
432 | + |
|
433 | + foreach ($times as $time) { |
|
434 | + if ($this->end && $time[0] > $this->end) { |
|
435 | + break; |
|
436 | + } |
|
437 | + if ($this->start && $time[1] < $this->start) { |
|
438 | + break; |
|
439 | + } |
|
440 | + |
|
441 | + $fbData->add( |
|
442 | + $time[0]->getTimeStamp(), |
|
443 | + $time[1]->getTimeStamp(), |
|
444 | + $FBTYPE |
|
445 | + ); |
|
446 | + } |
|
447 | + break; |
|
448 | + |
|
449 | + case 'VFREEBUSY': |
|
450 | + foreach ($component->FREEBUSY as $freebusy) { |
|
451 | + $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY'; |
|
452 | + |
|
453 | + // Skipping intervals marked as 'free' |
|
454 | + if ('FREE' === $fbType) { |
|
455 | + continue; |
|
456 | + } |
|
457 | + |
|
458 | + $values = explode(',', $freebusy); |
|
459 | + foreach ($values as $value) { |
|
460 | + list($startTime, $endTime) = explode('/', $value); |
|
461 | + $startTime = DateTimeParser::parseDateTime($startTime); |
|
462 | + |
|
463 | + if ('P' === substr($endTime, 0, 1) || '-P' === substr($endTime, 0, 2)) { |
|
464 | + $duration = DateTimeParser::parseDuration($endTime); |
|
465 | + $endTime = clone $startTime; |
|
466 | + $endTime = $endTime->add($duration); |
|
467 | + } else { |
|
468 | + $endTime = DateTimeParser::parseDateTime($endTime); |
|
469 | + } |
|
470 | + |
|
471 | + if ($this->start && $this->start > $endTime) { |
|
472 | + continue; |
|
473 | + } |
|
474 | + if ($this->end && $this->end < $startTime) { |
|
475 | + continue; |
|
476 | + } |
|
477 | + $fbData->add( |
|
478 | + $startTime->getTimeStamp(), |
|
479 | + $endTime->getTimeStamp(), |
|
480 | + $fbType |
|
481 | + ); |
|
482 | + } |
|
483 | + } |
|
484 | + break; |
|
485 | + } |
|
486 | + } |
|
487 | + } |
|
488 | + } |
|
489 | + |
|
490 | + /** |
|
491 | + * This method takes a FreeBusyData object and generates the VCALENDAR |
|
492 | + * object associated with it. |
|
493 | + * |
|
494 | + * @return VCalendar |
|
495 | + */ |
|
496 | + protected function generateFreeBusyCalendar(FreeBusyData $fbData) |
|
497 | + { |
|
498 | + if ($this->baseObject) { |
|
499 | + $calendar = $this->baseObject; |
|
500 | + } else { |
|
501 | + $calendar = new VCalendar(); |
|
502 | + } |
|
503 | + |
|
504 | + $vfreebusy = $calendar->createComponent('VFREEBUSY'); |
|
505 | + $calendar->add($vfreebusy); |
|
506 | + |
|
507 | + if ($this->start) { |
|
508 | + $dtstart = $calendar->createProperty('DTSTART'); |
|
509 | + $dtstart->setDateTime($this->start); |
|
510 | + $vfreebusy->add($dtstart); |
|
511 | + } |
|
512 | + if ($this->end) { |
|
513 | + $dtend = $calendar->createProperty('DTEND'); |
|
514 | + $dtend->setDateTime($this->end); |
|
515 | + $vfreebusy->add($dtend); |
|
516 | + } |
|
517 | + |
|
518 | + $tz = new \DateTimeZone('UTC'); |
|
519 | + $dtstamp = $calendar->createProperty('DTSTAMP'); |
|
520 | + $dtstamp->setDateTime(new DateTimeImmutable('now', $tz)); |
|
521 | + $vfreebusy->add($dtstamp); |
|
522 | + |
|
523 | + foreach ($fbData->getData() as $busyTime) { |
|
524 | + $busyType = strtoupper($busyTime['type']); |
|
525 | + |
|
526 | + // Ignoring all the FREE parts, because those are already assumed. |
|
527 | + if ('FREE' === $busyType) { |
|
528 | + continue; |
|
529 | + } |
|
530 | + |
|
531 | + $busyTime[0] = new \DateTimeImmutable('@'.$busyTime['start'], $tz); |
|
532 | + $busyTime[1] = new \DateTimeImmutable('@'.$busyTime['end'], $tz); |
|
533 | + |
|
534 | + $prop = $calendar->createProperty( |
|
535 | + 'FREEBUSY', |
|
536 | + $busyTime[0]->format('Ymd\\THis\\Z').'/'.$busyTime[1]->format('Ymd\\THis\\Z') |
|
537 | + ); |
|
538 | + |
|
539 | + // Only setting FBTYPE if it's not BUSY, because BUSY is the |
|
540 | + // default anyway. |
|
541 | + if ('BUSY' !== $busyType) { |
|
542 | + $prop['FBTYPE'] = $busyType; |
|
543 | + } |
|
544 | + $vfreebusy->add($prop); |
|
545 | + } |
|
546 | + |
|
547 | + return $calendar; |
|
548 | + } |
|
549 | 549 | } |