Completed
Push — developer ( e1a3df...61e59d )
by Błażej
172:03 queued 124:42
created
libraries/restler/restler.php 2 patches
Doc Comments   +21 added lines, -4 removed lines patch added patch discarded remove patch
@@ -268,9 +268,9 @@  discard block
 block discarded – undo
268 268
      *
269 269
      * All the protected methods that do not start with _ (underscore)
270 270
      * will exposed as protected api which will require authentication
271
-     * @param string $class name of the service class
272
-     * @param string $basePath optional url prefix for mapping, uses
271
+     * @param string $base_path optional url prefix for mapping, uses
273 272
      * lowercase version of the class name when not specified
273
+     * @param string $class_name
274 274
      * @throws Exception when supplied with invalid class name
275 275
      */
276 276
     public function addAPIClass($class_name, $base_path = null)
@@ -322,8 +322,8 @@  discard block
 block discarded – undo
322 322
 
323 323
     /**
324 324
      * Convenience method to respond with an error message
325
-     * @param int $statusCode http error code
326
-     * @param string $errorMessage optional custom error message
325
+     * @param int $status_code http error code
326
+     * @param string $error_message optional custom error message
327 327
      */
328 328
     public function handleError($status_code, $error_message = null)
329 329
     {
@@ -916,6 +916,10 @@  discard block
 block discarded – undo
916 916
 {
917 917
 
918 918
 
919
+    /**
920
+     * @param integer $http_status_code
921
+     * @param string $error_message
922
+     */
919 923
     public function __construct($http_status_code, $error_message = null)
920 924
     {
921 925
         parent::__construct($error_message, $http_status_code);
@@ -1033,12 +1037,14 @@  discard block
 block discarded – undo
1033 1037
     /**
1034 1038
      * Set the selected MIME type
1035 1039
      * @param string $mime MIME type
1040
+     * @return void
1036 1041
      */
1037 1042
     public function setMIME($mime);
1038 1043
 
1039 1044
 
1040 1045
     /**
1041 1046
      * Get selected MIME type
1047
+     * @return string
1042 1048
      */
1043 1049
     public function getMIME();
1044 1050
 
@@ -1046,6 +1052,7 @@  discard block
 block discarded – undo
1046 1052
     /**
1047 1053
      * Set the selected file extension
1048 1054
      * @param string $extension file extension
1055
+     * @return void
1049 1056
      */
1050 1057
     public function setExtension($extension);
1051 1058
 
@@ -1349,6 +1356,9 @@  discard block
 block discarded – undo
1349 1356
     }
1350 1357
 
1351 1358
 
1359
+    /**
1360
+     * @param string[] $lines
1361
+     */
1352 1362
     private function parseLines($lines)
1353 1363
     {
1354 1364
         foreach ($lines as $line) {
@@ -1398,6 +1408,10 @@  discard block
 block discarded – undo
1398 1408
     }
1399 1409
 
1400 1410
 
1411
+    /**
1412
+     * @param string $param
1413
+     * @param string $value
1414
+     */
1401 1415
     private function setParam($param, $value)
1402 1416
     {
1403 1417
         if ($param == 'param' || $param == 'return') {
@@ -1454,6 +1468,9 @@  discard block
 block discarded – undo
1454 1468
 //
1455 1469
 // ------------------------------------------------------------------
1456 1470
 
1471
+/**
1472
+ * @param string $php_doc_comment
1473
+ */
1457 1474
 function parse_doc($php_doc_comment)
1458 1475
 {
1459 1476
     $p = new DocParser();
Please login to merge, or discard this patch.
Indentation   +1299 added lines, -1299 removed lines patch added patch discarded remove patch
@@ -16,883 +16,883 @@  discard block
 block discarded – undo
16 16
  */
17 17
 class Restler
18 18
 {
19
-    // ==================================================================
20
-    //
21
-    // Public variables
22
-    //
23
-    // ------------------------------------------------------------------
24
-
25
-    const VERSION = '2.2.1';
26
-
27
-    /**
28
-     * URL of the currently mapped service
29
-     * @var string
30
-     */
31
-    public $url;
32
-
33
-    /**
34
-     * Http request method of the current request.
35
-     * Any value between [GET, PUT, POST, DELETE]
36
-     * @var string
37
-     */
38
-    public $request_method;
39
-
40
-    /**
41
-     * Requested data format. Instance of the current format class
42
-     * which implements the iFormat interface
43
-     * @var iFormat
44
-     * @example jsonFormat, xmlFormat, yamlFormat etc
45
-     */
46
-    public $request_format;
47
-
48
-    /**
49
-     * Data sent to the service
50
-     * @var array
51
-     */
52
-    public $request_data = array();
53
-
54
-    /**
55
-     * Used in production mode to store the URL Map to disk
56
-     * @var string
57
-     */
58
-    public $cache_dir;
59
-
60
-    /**
61
-     * base directory to locate format and auth files
62
-     * @var string
63
-     */
64
-    public $base_dir;
65
-
66
-    /**
67
-     * Name of an iRespond implementation class
68
-     * @var string
69
-     */
70
-    public $response = 'DefaultResponse';
71
-
72
-    /**
73
-     * Response data format. Instance of the current format class
74
-     * which implements the iFormat interface
75
-     * @var iFormat
76
-     * @example jsonFormat, xmlFormat, yamlFormat etc
77
-     */
78
-    public $response_format;
79
-
80
-    // ==================================================================
81
-    //
82
-    // Private & Protected variables
83
-    //
84
-    // ------------------------------------------------------------------
85
-
86
-    /**
87
-     * When set to false, it will run in debug mode and parse the
88
-     * class files every time to map it to the URL
89
-     * @var boolean
90
-     */
91
-    protected $production_mode;
92
-
93
-    /**
94
-     * Associated array that maps urls to their respective class and method
95
-     * @var array
96
-     */
97
-    protected $routes = array();
98
-
99
-    /**
100
-     * Associated array that maps formats to their respective format class name
101
-     * @var array
102
-     */
103
-    protected $format_map = array();
104
-
105
-    /**
106
-     * Instance of the current api service class
107
-     * @var object
108
-     */
109
-    protected $service_class_instance;
110
-
111
-    /**
112
-     * Name of the api method being called
113
-     * @var string
114
-     */
115
-    protected $service_method;
116
-
117
-    /**
118
-     * list of authentication classes
119
-     * @var array
120
-     */
121
-    protected $auth_classes = array();
122
-
123
-    /**
124
-     * list of error handling classes
125
-     * @var array
126
-     */
127
-    protected $error_classes = array();
128
-
129
-    /**
130
-     * HTTP status codes
131
-     * @var array
132
-     */
133
-    private $codes = array(
134
-        100 => 'Continue',
135
-        101 => 'Switching Protocols',
136
-        200 => 'OK',
137
-        201 => 'Created',
138
-        202 => 'Accepted',
139
-        203 => 'Non-Authoritative Information',
140
-        204 => 'No Content',
141
-        205 => 'Reset Content',
142
-        206 => 'Partial Content',
143
-        300 => 'Multiple Choices',
144
-        301 => 'Moved Permanently',
145
-        302 => 'Found',
146
-        303 => 'See Other',
147
-        304 => 'Not Modified',
148
-        305 => 'Use Proxy',
149
-        306 => '(Unused)',
150
-        307 => 'Temporary Redirect',
151
-        400 => 'Bad Request',
152
-        401 => 'Unauthorized',
153
-        402 => 'Payment Required',
154
-        403 => 'Forbidden',
155
-        404 => 'Not Found',
156
-        405 => 'Method Not Allowed',
157
-        406 => 'Not Acceptable',
158
-        407 => 'Proxy Authentication Required',
159
-        408 => 'Request Timeout',
160
-        409 => 'Conflict',
161
-        410 => 'Gone',
162
-        411 => 'Length Required',
163
-        412 => 'Precondition Failed',
164
-        413 => 'Request Entity Too Large',
165
-        414 => 'Request-URI Too Long',
166
-        415 => 'Unsupported Media Type',
167
-        416 => 'Requested Range Not Satisfiable',
168
-        417 => 'Expectation Failed',
169
-        500 => 'Internal Server Error',
170
-        501 => 'Not Implemented',
171
-        502 => 'Bad Gateway',
172
-        503 => 'Service Unavailable',
173
-        504 => 'Gateway Timeout',
174
-        505 => 'HTTP Version Not Supported'
175
-    );
176
-
177
-    /**
178
-     * Caching of url map is enabled or not
179
-     * @var boolean
180
-     */
181
-    protected $cached;
182
-
183
-    // ==================================================================
184
-    //
185
-    // Public functions
186
-    //
187
-    // ------------------------------------------------------------------
188
-
189
-
190
-    /**
191
-     * Constructor
192
-     * @param boolean $production_mode When set to false, it will run in
193
-     * debug mode and parse the class files every time to map it to the URL
194
-     */
195
-    public function __construct($production_mode = false)
196
-    {
197
-        $this->production_mode = $production_mode;
198
-        $this->cache_dir = getcwd();
199
-        $this->base_dir = RESTLER_PATH;
200
-    }
201
-
202
-
203
-    /**
204
-     * Store the url map cache if needed
205
-     */
206
-    public function __destruct()
207
-    {
208
-        if ($this->production_mode && !($this->cached)) {
209
-            $this->saveCache();
210
-        }
211
-    }
212
-
213
-
214
-    /**
215
-     * Use it in production mode to refresh the url map cache
216
-     */
217
-    public function refreshCache()
218
-    {
219
-        $this->routes = array();
220
-        $this->cached = false;
221
-    }
222
-
223
-
224
-    /**
225
-     * Call this method and pass all the formats that should be
226
-     * supported by the API. Accepts multiple parameters
227
-     * @param string class name of the format class that implements iFormat
228
-     * @example $restler->setSupportedFormats('JsonFormat', 'XmlFormat'...);
229
-     */
230
-    public function setSupportedFormats()
231
-    {
232
-        $args = func_get_args();
233
-        $extensions = array();
234
-        foreach ($args as $class_name) {
235
-            if (!is_string($class_name) || !class_exists($class_name)) {
236
-                throw new Exception("$class_name is not a vaild Format Class.");
237
-            }
238
-            $obj = new $class_name;
239
-            if (!($obj instanceof iFormat)) {
240
-                throw new Exception('Invalid format class; must implement '
241
-                    . 'iFormat interface');
242
-            }
243
-            foreach ($obj->getMIMEMap() as $extension => $mime) {
244
-                if (!isset($this->format_map[$extension])) {
245
-                    $this->format_map[$extension] = $class_name;
246
-                }
247
-                $mime = explode(',', $mime);
248
-                if (!is_array($mime)) {
249
-                    $mime = array($mime);
250
-                }
251
-                foreach ($mime as $value) {
252
-                    if (!isset($this->format_map[$value])) {
253
-                        $this->format_map[$value] = $class_name;
254
-                    }
255
-                }
256
-                $extensions[".$extension"] = true;
257
-            }
258
-        }
259
-        $this->format_map['default'] = $args[0];
260
-        $this->format_map['extensions'] = array_keys($extensions);
261
-    }
262
-
263
-
264
-    /**
265
-     * Add api classes throgh this method. All the public methods
266
-     * that do not start with _ (underscore) will be  will be exposed
267
-     * as the public api by default.
268
-     *
269
-     * All the protected methods that do not start with _ (underscore)
270
-     * will exposed as protected api which will require authentication
271
-     * @param string $class name of the service class
272
-     * @param string $basePath optional url prefix for mapping, uses
273
-     * lowercase version of the class name when not specified
274
-     * @throws Exception when supplied with invalid class name
275
-     */
276
-    public function addAPIClass($class_name, $base_path = null)
277
-    {
278
-        if (!class_exists($class_name)) {
279
-            throw new Exception("API class $class_name is missing.");
280
-        }
281
-        $this->loadCache();
282
-        if (!$this->cached) {
283
-            if (is_null($base_path)) {
284
-                $base_path = strtolower($class_name);
285
-                $index = strrpos($class_name, '\\');
286
-                if ($index !== false) {
287
-                    $base_path = substr($base_path, $index + 1);
288
-                }
289
-            } else {
290
-                $base_path = trim($base_path, '/');
291
-            }
292
-            if (strlen($base_path) > 0) {
293
-                $base_path .= '/';
294
-            }
295
-            $this->generateMap($class_name, $base_path);
296
-        }
297
-    }
298
-
299
-
300
-    /**
301
-     * protected methods will need atleast one authentication class to be set
302
-     * in order to allow that method to be executed
303
-     * @param string $class_name of the authentication class
304
-     * @param string $base_path optional url prefix for mapping
305
-     */
306
-    public function addAuthenticationClass($class_name, $base_path = null)
307
-    {
308
-        $this->auth_classes[] = $class_name;
309
-        $this->addAPIClass($class_name, $base_path);
310
-    }
311
-
312
-
313
-    /**
314
-     * Add class for custom error handling
315
-     * @param string $class_name of the error handling class
316
-     */
317
-    public function addErrorClass($class_name)
318
-    {
319
-        $this->error_classes[] = $class_name;
320
-    }
321
-
322
-
323
-    /**
324
-     * Convenience method to respond with an error message
325
-     * @param int $statusCode http error code
326
-     * @param string $errorMessage optional custom error message
327
-     */
328
-    public function handleError($status_code, $error_message = null)
329
-    {
330
-        $method = "handle$status_code";
331
-        $handled = false;
332
-        foreach ($this->error_classes as $class_name) {
333
-            if (method_exists($class_name, $method)) {
334
-                $obj = new $class_name();
335
-                $obj->restler = $this;
336
-                $obj->$method();
337
-                $handled = true;
338
-            }
339
-        }
340
-        if ($handled) {
341
-            return;
342
-        }
343
-        $message = $this->codes[$status_code]
344
-            . (!$error_message ? '' : ': ' . $error_message);
345
-        $this->setStatus($status_code);
346
-        $responder = new $this->response();
347
-        $responder->restler = $this;
348
-        $this->sendData($responder->__formatError($status_code, $message));
349
-    }
350
-
351
-
352
-    /**
353
-     * An initialize function to allow use of the restler error generation 
354
-     * functions for pre-processing and pre-routing of requests.
355
-     */
356
-    public function init()
357
-    {
358
-        if (empty($this->format_map)) {
359
-            $this->setSupportedFormats('JsonFormat');
360
-        }
361
-        $this->url = $this->getPath();
362
-        $this->request_method = $this->getRequestMethod();
363
-        $this->response_format = $this->getResponseFormat();
364
-        $this->request_format = $this->getRequestFormat();
365
-        if (is_null($this->request_format)) {
366
-            $this->request_format = $this->response_format;
367
-        }
368
-        if ($this->request_method == 'PUT' || $this->request_method == 'POST') {
369
-            $this->request_data = $this->getRequestData();
370
-        }
371
-    }
372
-
373
-
374
-    /**
375
-     * Main function for processing the api request
376
-     * and return the response
377
-     * @throws Exception when the api service class is missing
378
-     * @throws RestException to send error response
379
-     */
380
-    public function handle()
381
-    {
382
-        $this->init();
383
-        $o = $this->mapUrlToMethod();
384
-
385
-        if (!isset($o->class_name)) {
386
-            $this->handleError(404);
387
-        } else {
388
-            try {
389
-                if ($o->method_flag) {
390
-                    $auth_method = '__isAuthenticated';
391
-                    if (!count($this->auth_classes)) {
392
-                        throw new RestException(401);
393
-                    }
394
-                    foreach ($this->auth_classes as $auth_class) {
395
-                        $auth_obj = new $auth_class();
396
-                        $auth_obj->restler = $this;
397
-                        $this->applyClassMetadata($auth_class, $auth_obj, $o);
398
-                        if (!method_exists($auth_obj, $auth_method)) {
399
-                            throw new RestException(401, 'Authentication Class '
400
-                                . 'should implement iAuthenticate');
401
-                        } else if (!$auth_obj->$auth_method()) {
402
-                            throw new RestException(401);
403
-                        }
404
-                    }
405
-                }
406
-                $this->applyClassMetadata(get_class($this->request_format),
407
-                    $this->request_format, $o);
408
-                $pre_process = '_' . $this->request_format->getExtension() . '_'
409
-                    . $o->method_name;
410
-                $this->service_method = $o->method_name;
411
-                if ($o->method_flag == 2) {
412
-                    $o = unprotect($o);
413
-                }
414
-                $object = $this->service_class_instance = new $o->class_name();
415
-                $object->restler = $this;
416
-                if (method_exists($o->class_name, $pre_process)) {
417
-                    call_user_func_array(
418
-                        array($object, $pre_process), $o->arguments
419
-                    );
420
-                }
421
-                switch ($o->method_flag) {
422
-                    case 3:
423
-                        $reflection_method = new ReflectionMethod($object,
424
-                                $o->method_name);
425
-                        $reflection_method->setAccessible(true);
426
-                        $result = $reflection_method->invokeArgs($object,
427
-                            $o->arguments);
428
-                        break;
429
-                    case 2:
430
-                    case 1:
431
-                    default:
432
-                        $result = call_user_func_array(array(
433
-                            $object,
434
-                            $o->method_name), $o->arguments
435
-                        );
436
-                        break;
437
-                }
438
-            } catch (RestException $e) {
439
-                $this->handleError($e->getCode(), $e->getMessage());
440
-            }
441
-        }
442
-        $responder = new $this->response();
443
-        $responder->restler = $this;
444
-        $this->applyClassMetadata($this->response, $responder, $o);
445
-        if (isset($result) && $result !== null) {
446
-            $result = $responder->__formatResponse($result);
447
-            $this->sendData($result);
448
-        }
449
-    }
450
-
451
-
452
-    /**
453
-     * Encodes the response in the prefered format
454
-     * and sends back
455
-     * @param $data array php data
456
-     */
457
-    public function sendData($data)
458
-    {
459
-        $data = $this->response_format->encode($data, 
460
-            !($this->production_mode)
461
-        );
462
-        $post_process = '_' . $this->service_method . '_'
463
-            . $this->response_format->getExtension();
464
-        if (isset($this->service_class_instance)
465
-            && method_exists($this->service_class_instance, $post_process)
466
-        ) {
467
-            $data = call_user_func(array($this->service_class_instance,
468
-                $post_process), $data);
469
-        }
470
-        header("Cache-Control: no-cache, must-revalidate");
471
-        header("Expires: 0");
472
-        header('Content-Type: ' . $this->response_format->getMIME());
473
-        //.'; charset=utf-8');
474
-        header("X-Powered-By: Luracast Restler v" . Restler::VERSION);
19
+	// ==================================================================
20
+	//
21
+	// Public variables
22
+	//
23
+	// ------------------------------------------------------------------
24
+
25
+	const VERSION = '2.2.1';
26
+
27
+	/**
28
+	 * URL of the currently mapped service
29
+	 * @var string
30
+	 */
31
+	public $url;
32
+
33
+	/**
34
+	 * Http request method of the current request.
35
+	 * Any value between [GET, PUT, POST, DELETE]
36
+	 * @var string
37
+	 */
38
+	public $request_method;
39
+
40
+	/**
41
+	 * Requested data format. Instance of the current format class
42
+	 * which implements the iFormat interface
43
+	 * @var iFormat
44
+	 * @example jsonFormat, xmlFormat, yamlFormat etc
45
+	 */
46
+	public $request_format;
47
+
48
+	/**
49
+	 * Data sent to the service
50
+	 * @var array
51
+	 */
52
+	public $request_data = array();
53
+
54
+	/**
55
+	 * Used in production mode to store the URL Map to disk
56
+	 * @var string
57
+	 */
58
+	public $cache_dir;
59
+
60
+	/**
61
+	 * base directory to locate format and auth files
62
+	 * @var string
63
+	 */
64
+	public $base_dir;
65
+
66
+	/**
67
+	 * Name of an iRespond implementation class
68
+	 * @var string
69
+	 */
70
+	public $response = 'DefaultResponse';
71
+
72
+	/**
73
+	 * Response data format. Instance of the current format class
74
+	 * which implements the iFormat interface
75
+	 * @var iFormat
76
+	 * @example jsonFormat, xmlFormat, yamlFormat etc
77
+	 */
78
+	public $response_format;
79
+
80
+	// ==================================================================
81
+	//
82
+	// Private & Protected variables
83
+	//
84
+	// ------------------------------------------------------------------
85
+
86
+	/**
87
+	 * When set to false, it will run in debug mode and parse the
88
+	 * class files every time to map it to the URL
89
+	 * @var boolean
90
+	 */
91
+	protected $production_mode;
92
+
93
+	/**
94
+	 * Associated array that maps urls to their respective class and method
95
+	 * @var array
96
+	 */
97
+	protected $routes = array();
98
+
99
+	/**
100
+	 * Associated array that maps formats to their respective format class name
101
+	 * @var array
102
+	 */
103
+	protected $format_map = array();
104
+
105
+	/**
106
+	 * Instance of the current api service class
107
+	 * @var object
108
+	 */
109
+	protected $service_class_instance;
110
+
111
+	/**
112
+	 * Name of the api method being called
113
+	 * @var string
114
+	 */
115
+	protected $service_method;
116
+
117
+	/**
118
+	 * list of authentication classes
119
+	 * @var array
120
+	 */
121
+	protected $auth_classes = array();
122
+
123
+	/**
124
+	 * list of error handling classes
125
+	 * @var array
126
+	 */
127
+	protected $error_classes = array();
128
+
129
+	/**
130
+	 * HTTP status codes
131
+	 * @var array
132
+	 */
133
+	private $codes = array(
134
+		100 => 'Continue',
135
+		101 => 'Switching Protocols',
136
+		200 => 'OK',
137
+		201 => 'Created',
138
+		202 => 'Accepted',
139
+		203 => 'Non-Authoritative Information',
140
+		204 => 'No Content',
141
+		205 => 'Reset Content',
142
+		206 => 'Partial Content',
143
+		300 => 'Multiple Choices',
144
+		301 => 'Moved Permanently',
145
+		302 => 'Found',
146
+		303 => 'See Other',
147
+		304 => 'Not Modified',
148
+		305 => 'Use Proxy',
149
+		306 => '(Unused)',
150
+		307 => 'Temporary Redirect',
151
+		400 => 'Bad Request',
152
+		401 => 'Unauthorized',
153
+		402 => 'Payment Required',
154
+		403 => 'Forbidden',
155
+		404 => 'Not Found',
156
+		405 => 'Method Not Allowed',
157
+		406 => 'Not Acceptable',
158
+		407 => 'Proxy Authentication Required',
159
+		408 => 'Request Timeout',
160
+		409 => 'Conflict',
161
+		410 => 'Gone',
162
+		411 => 'Length Required',
163
+		412 => 'Precondition Failed',
164
+		413 => 'Request Entity Too Large',
165
+		414 => 'Request-URI Too Long',
166
+		415 => 'Unsupported Media Type',
167
+		416 => 'Requested Range Not Satisfiable',
168
+		417 => 'Expectation Failed',
169
+		500 => 'Internal Server Error',
170
+		501 => 'Not Implemented',
171
+		502 => 'Bad Gateway',
172
+		503 => 'Service Unavailable',
173
+		504 => 'Gateway Timeout',
174
+		505 => 'HTTP Version Not Supported'
175
+	);
176
+
177
+	/**
178
+	 * Caching of url map is enabled or not
179
+	 * @var boolean
180
+	 */
181
+	protected $cached;
182
+
183
+	// ==================================================================
184
+	//
185
+	// Public functions
186
+	//
187
+	// ------------------------------------------------------------------
188
+
189
+
190
+	/**
191
+	 * Constructor
192
+	 * @param boolean $production_mode When set to false, it will run in
193
+	 * debug mode and parse the class files every time to map it to the URL
194
+	 */
195
+	public function __construct($production_mode = false)
196
+	{
197
+		$this->production_mode = $production_mode;
198
+		$this->cache_dir = getcwd();
199
+		$this->base_dir = RESTLER_PATH;
200
+	}
201
+
202
+
203
+	/**
204
+	 * Store the url map cache if needed
205
+	 */
206
+	public function __destruct()
207
+	{
208
+		if ($this->production_mode && !($this->cached)) {
209
+			$this->saveCache();
210
+		}
211
+	}
212
+
213
+
214
+	/**
215
+	 * Use it in production mode to refresh the url map cache
216
+	 */
217
+	public function refreshCache()
218
+	{
219
+		$this->routes = array();
220
+		$this->cached = false;
221
+	}
222
+
223
+
224
+	/**
225
+	 * Call this method and pass all the formats that should be
226
+	 * supported by the API. Accepts multiple parameters
227
+	 * @param string class name of the format class that implements iFormat
228
+	 * @example $restler->setSupportedFormats('JsonFormat', 'XmlFormat'...);
229
+	 */
230
+	public function setSupportedFormats()
231
+	{
232
+		$args = func_get_args();
233
+		$extensions = array();
234
+		foreach ($args as $class_name) {
235
+			if (!is_string($class_name) || !class_exists($class_name)) {
236
+				throw new Exception("$class_name is not a vaild Format Class.");
237
+			}
238
+			$obj = new $class_name;
239
+			if (!($obj instanceof iFormat)) {
240
+				throw new Exception('Invalid format class; must implement '
241
+					. 'iFormat interface');
242
+			}
243
+			foreach ($obj->getMIMEMap() as $extension => $mime) {
244
+				if (!isset($this->format_map[$extension])) {
245
+					$this->format_map[$extension] = $class_name;
246
+				}
247
+				$mime = explode(',', $mime);
248
+				if (!is_array($mime)) {
249
+					$mime = array($mime);
250
+				}
251
+				foreach ($mime as $value) {
252
+					if (!isset($this->format_map[$value])) {
253
+						$this->format_map[$value] = $class_name;
254
+					}
255
+				}
256
+				$extensions[".$extension"] = true;
257
+			}
258
+		}
259
+		$this->format_map['default'] = $args[0];
260
+		$this->format_map['extensions'] = array_keys($extensions);
261
+	}
262
+
263
+
264
+	/**
265
+	 * Add api classes throgh this method. All the public methods
266
+	 * that do not start with _ (underscore) will be  will be exposed
267
+	 * as the public api by default.
268
+	 *
269
+	 * All the protected methods that do not start with _ (underscore)
270
+	 * will exposed as protected api which will require authentication
271
+	 * @param string $class name of the service class
272
+	 * @param string $basePath optional url prefix for mapping, uses
273
+	 * lowercase version of the class name when not specified
274
+	 * @throws Exception when supplied with invalid class name
275
+	 */
276
+	public function addAPIClass($class_name, $base_path = null)
277
+	{
278
+		if (!class_exists($class_name)) {
279
+			throw new Exception("API class $class_name is missing.");
280
+		}
281
+		$this->loadCache();
282
+		if (!$this->cached) {
283
+			if (is_null($base_path)) {
284
+				$base_path = strtolower($class_name);
285
+				$index = strrpos($class_name, '\\');
286
+				if ($index !== false) {
287
+					$base_path = substr($base_path, $index + 1);
288
+				}
289
+			} else {
290
+				$base_path = trim($base_path, '/');
291
+			}
292
+			if (strlen($base_path) > 0) {
293
+				$base_path .= '/';
294
+			}
295
+			$this->generateMap($class_name, $base_path);
296
+		}
297
+	}
298
+
299
+
300
+	/**
301
+	 * protected methods will need atleast one authentication class to be set
302
+	 * in order to allow that method to be executed
303
+	 * @param string $class_name of the authentication class
304
+	 * @param string $base_path optional url prefix for mapping
305
+	 */
306
+	public function addAuthenticationClass($class_name, $base_path = null)
307
+	{
308
+		$this->auth_classes[] = $class_name;
309
+		$this->addAPIClass($class_name, $base_path);
310
+	}
311
+
312
+
313
+	/**
314
+	 * Add class for custom error handling
315
+	 * @param string $class_name of the error handling class
316
+	 */
317
+	public function addErrorClass($class_name)
318
+	{
319
+		$this->error_classes[] = $class_name;
320
+	}
321
+
322
+
323
+	/**
324
+	 * Convenience method to respond with an error message
325
+	 * @param int $statusCode http error code
326
+	 * @param string $errorMessage optional custom error message
327
+	 */
328
+	public function handleError($status_code, $error_message = null)
329
+	{
330
+		$method = "handle$status_code";
331
+		$handled = false;
332
+		foreach ($this->error_classes as $class_name) {
333
+			if (method_exists($class_name, $method)) {
334
+				$obj = new $class_name();
335
+				$obj->restler = $this;
336
+				$obj->$method();
337
+				$handled = true;
338
+			}
339
+		}
340
+		if ($handled) {
341
+			return;
342
+		}
343
+		$message = $this->codes[$status_code]
344
+			. (!$error_message ? '' : ': ' . $error_message);
345
+		$this->setStatus($status_code);
346
+		$responder = new $this->response();
347
+		$responder->restler = $this;
348
+		$this->sendData($responder->__formatError($status_code, $message));
349
+	}
350
+
351
+
352
+	/**
353
+	 * An initialize function to allow use of the restler error generation 
354
+	 * functions for pre-processing and pre-routing of requests.
355
+	 */
356
+	public function init()
357
+	{
358
+		if (empty($this->format_map)) {
359
+			$this->setSupportedFormats('JsonFormat');
360
+		}
361
+		$this->url = $this->getPath();
362
+		$this->request_method = $this->getRequestMethod();
363
+		$this->response_format = $this->getResponseFormat();
364
+		$this->request_format = $this->getRequestFormat();
365
+		if (is_null($this->request_format)) {
366
+			$this->request_format = $this->response_format;
367
+		}
368
+		if ($this->request_method == 'PUT' || $this->request_method == 'POST') {
369
+			$this->request_data = $this->getRequestData();
370
+		}
371
+	}
372
+
373
+
374
+	/**
375
+	 * Main function for processing the api request
376
+	 * and return the response
377
+	 * @throws Exception when the api service class is missing
378
+	 * @throws RestException to send error response
379
+	 */
380
+	public function handle()
381
+	{
382
+		$this->init();
383
+		$o = $this->mapUrlToMethod();
384
+
385
+		if (!isset($o->class_name)) {
386
+			$this->handleError(404);
387
+		} else {
388
+			try {
389
+				if ($o->method_flag) {
390
+					$auth_method = '__isAuthenticated';
391
+					if (!count($this->auth_classes)) {
392
+						throw new RestException(401);
393
+					}
394
+					foreach ($this->auth_classes as $auth_class) {
395
+						$auth_obj = new $auth_class();
396
+						$auth_obj->restler = $this;
397
+						$this->applyClassMetadata($auth_class, $auth_obj, $o);
398
+						if (!method_exists($auth_obj, $auth_method)) {
399
+							throw new RestException(401, 'Authentication Class '
400
+								. 'should implement iAuthenticate');
401
+						} else if (!$auth_obj->$auth_method()) {
402
+							throw new RestException(401);
403
+						}
404
+					}
405
+				}
406
+				$this->applyClassMetadata(get_class($this->request_format),
407
+					$this->request_format, $o);
408
+				$pre_process = '_' . $this->request_format->getExtension() . '_'
409
+					. $o->method_name;
410
+				$this->service_method = $o->method_name;
411
+				if ($o->method_flag == 2) {
412
+					$o = unprotect($o);
413
+				}
414
+				$object = $this->service_class_instance = new $o->class_name();
415
+				$object->restler = $this;
416
+				if (method_exists($o->class_name, $pre_process)) {
417
+					call_user_func_array(
418
+						array($object, $pre_process), $o->arguments
419
+					);
420
+				}
421
+				switch ($o->method_flag) {
422
+					case 3:
423
+						$reflection_method = new ReflectionMethod($object,
424
+								$o->method_name);
425
+						$reflection_method->setAccessible(true);
426
+						$result = $reflection_method->invokeArgs($object,
427
+							$o->arguments);
428
+						break;
429
+					case 2:
430
+					case 1:
431
+					default:
432
+						$result = call_user_func_array(array(
433
+							$object,
434
+							$o->method_name), $o->arguments
435
+						);
436
+						break;
437
+				}
438
+			} catch (RestException $e) {
439
+				$this->handleError($e->getCode(), $e->getMessage());
440
+			}
441
+		}
442
+		$responder = new $this->response();
443
+		$responder->restler = $this;
444
+		$this->applyClassMetadata($this->response, $responder, $o);
445
+		if (isset($result) && $result !== null) {
446
+			$result = $responder->__formatResponse($result);
447
+			$this->sendData($result);
448
+		}
449
+	}
450
+
451
+
452
+	/**
453
+	 * Encodes the response in the prefered format
454
+	 * and sends back
455
+	 * @param $data array php data
456
+	 */
457
+	public function sendData($data)
458
+	{
459
+		$data = $this->response_format->encode($data, 
460
+			!($this->production_mode)
461
+		);
462
+		$post_process = '_' . $this->service_method . '_'
463
+			. $this->response_format->getExtension();
464
+		if (isset($this->service_class_instance)
465
+			&& method_exists($this->service_class_instance, $post_process)
466
+		) {
467
+			$data = call_user_func(array($this->service_class_instance,
468
+				$post_process), $data);
469
+		}
470
+		header("Cache-Control: no-cache, must-revalidate");
471
+		header("Expires: 0");
472
+		header('Content-Type: ' . $this->response_format->getMIME());
473
+		//.'; charset=utf-8');
474
+		header("X-Powered-By: Luracast Restler v" . Restler::VERSION);
475 475
 		if($this->production_mode){
476 476
 			die($data);
477 477
 		}else{
478 478
 			echo $data;
479 479
 		}
480
-    }
481
-
482
-
483
-    /**
484
-     * Sets the HTTP response status
485
-     * @param int $code response code
486
-     */
487
-    public function setStatus($code)
488
-    {
489
-        header("{$_SERVER['SERVER_PROTOCOL']} $code " . 
490
-            $this->codes[strval($code)]);
491
-    }
492
-
493
-
494
-    /**
495
-     * Compare two strings and remove the common
496
-     * sub string from the first string and return it
497
-     * @param string $first
498
-     * @param string $second
499
-     * @param string $char optional, set it as
500
-     * blank string for char by char comparison
501
-     * @return string
502
-     */
503
-    public function removeCommonPath($first, $second, $char = '/')
504
-    {
505
-        $first = explode($char, $first);
506
-        $second = explode($char, $second);
507
-        while (count($second)) {
508
-            if ($first[0] == $second[0]) {
509
-                array_shift($first);
510
-            } else {
511
-                break;
512
-            }
513
-            array_shift($second);
514
-        }
515
-        return implode($char, $first);
516
-    }
517
-
518
-
519
-    /**
520
-     * Save cache to file
521
-     */
522
-    public function saveCache()
523
-    {
524
-        $file = $this->cache_dir . '/routes.php';
525
-        $s = '$o=array();' . PHP_EOL;
526
-        foreach ($this->routes as $key => $value) {
527
-            $s .= PHP_EOL . PHP_EOL . PHP_EOL . 
528
-                "############### $key ###############" . PHP_EOL . PHP_EOL;
529
-            $s .= '$o[\'' . $key . '\']=array();';
530
-            foreach ($value as $ke => $va) {
531
-                $s .= PHP_EOL . PHP_EOL . "#==== $key $ke" . PHP_EOL . PHP_EOL;
532
-                $s .= '$o[\'' . $key . '\'][\'' . $ke . '\']=' . str_replace(
533
-                        PHP_EOL, PHP_EOL . "\t", var_export($va, true)
534
-                    ) . ';';
535
-            }
536
-        }
537
-        $s .= PHP_EOL . 'return $o;';
538
-        $r = @file_put_contents($file, "<?php $s");
539
-        @chmod($file, 0777);
540
-        if ($r === false) {
541
-            throw new Exception(
542
-                "The cache directory located at '$this->cache_dir' needs to "
543
-                . "have the permissions set to read/write/execute for everyone"
544
-                . " in order to save cache and improve performance.");
545
-        }
546
-    }
547
-
548
-    // ==================================================================
549
-    //
550
-    // Protected functions
551
-    //
552
-    // ------------------------------------------------------------------
553
-
554
-
555
-    /**
556
-     * Parses the requst url and get the api path
557
-     * @return string api path
558
-     */
559
-    protected function getPath()
560
-    {
561
-        $path = urldecode($this->removeCommonPath($_SERVER['REQUEST_URI'],
562
-                $_SERVER['SCRIPT_NAME']));
563
-        $path = preg_replace('/(\/*\?.*$)|(\/$)/', '', $path);
564
-        $path = str_replace($this->format_map['extensions'], '', $path);
565
-        return $path;
566
-    }
567
-
568
-
569
-    /**
570
-     * Parses the request to figure out the http request type
571
-     * @return string which will be one of the following
572
-     * [GET, POST, PUT, DELETE]
573
-     * @example GET
574
-     */
575
-    protected function getRequestMethod()
576
-    {
577
-        $method = $_SERVER['REQUEST_METHOD'];
578
-        if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
579
-            $method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
580
-        }
581
-        //support for HEAD request
582
-        if ($method == 'HEAD') {
583
-            $method = 'GET';
584
-        }
585
-        return $method;
586
-    }
587
-
588
-
589
-    /**
590
-     * Parses the request to figure out format of the request data
591
-     * @return iFormat any class that implements iFormat
592
-     * @example JsonFormat
593
-     */
594
-    protected function getRequestFormat()
595
-    {
596
-        $format = null;
597
-        //check if client has sent any information on request format
598
-        if (isset($_SERVER['CONTENT_TYPE'])) {
599
-            $mime = explode(';', $_SERVER['CONTENT_TYPE']);
600
-            $mime = $mime[0];
601
-            if ($mime == UrlEncodedFormat::MIME) {
602
-                $format = new UrlEncodedFormat();
603
-            } else {
604
-                if (isset($this->format_map[$mime])) {
605
-                    $format = $this->format_map[$mime];
606
-                    $format = is_string($format) ? new $format : $format;
607
-                    $format->setMIME($mime);
608
-                }
609
-            }
610
-        }
611
-        return $format;
612
-    }
613
-
614
-
615
-    /**
616
-     * Parses the request to figure out the best format for response
617
-     * @return iFormat any class that implements iFormat
618
-     * @example JsonFormat
619
-     */
620
-    protected function getResponseFormat()
621
-    {
622
-        //check if client has specified an extension
623
-        /**
624
-         * @var iFormat
625
-         */
626
-        $format = null;
627
-        $extensions = explode('.',
628
-            parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
629
-        while ($extensions) {
630
-            $extension = array_pop($extensions);
631
-            $extension = explode('/', $extension);
632
-            $extension = array_shift($extension);
633
-            if ($extension && isset($this->format_map[$extension])) {
634
-                $format = $this->format_map[$extension];
635
-                $format = is_string($format) ? new $format : $format;
636
-                $format->setExtension($extension);
637
-                //echo "Extension $extension";
638
-                return $format;
639
-            }
640
-        }
641
-        //check if client has sent list of accepted data formats
642
-        if (isset($_SERVER['HTTP_ACCEPT'])) {
643
-            $acceptList = array();
644
-            $accepts = explode(',', strtolower($_SERVER['HTTP_ACCEPT']));
645
-            if (!is_array($accepts)) {
646
-                $accepts = array($accepts);
647
-            }
648
-            foreach ($accepts as $pos => $accept) {
649
-                $parts = explode(';q=', trim($accept));
650
-                $type = array_shift($parts);
651
-                $quality = count($parts) ? 
652
-                    floatval(array_shift($parts)) : 
653
-                    (1000 - $pos) / 1000;
654
-                $acceptList[$type] = $quality;
655
-            }
656
-            arsort($acceptList);
657
-            foreach ($acceptList as $accept => $quality) {
658
-                if (isset($this->format_map[$accept])) {
659
-                    $format = $this->format_map[$accept];
660
-                    $format = is_string($format) ? new $format : $format;
661
-                    $format->setMIME($accept);
662
-                    //echo "MIME $accept";
663
-                    // Tell cache content is based on Accept header
664
-                    header("Vary: Accept"); 
665
-                    return $format;
666
-                }
667
-            }
668
-        } else {
669
-            // RFC 2616: If no Accept header field is
670
-            // present, then it is assumed that the
671
-            // client accepts all media types.
672
-            $_SERVER['HTTP_ACCEPT'] = '*/*';
673
-        }
674
-        if (strpos($_SERVER['HTTP_ACCEPT'], '*') !== false) {
675
-            if (strpos($_SERVER['HTTP_ACCEPT'], 'application/*') !== false) {
676
-                $format = new JsonFormat;
677
-            } else if (strpos($_SERVER['HTTP_ACCEPT'], 'text/*') !== false) {
678
-                $format = new XmlFormat;
679
-            } else if (strpos($_SERVER['HTTP_ACCEPT'], '*/*') !== false) {
680
-                $format = new $this->format_map['default'];
681
-            }
682
-        }
683
-        if (empty($format)) {
684
-            // RFC 2616: If an Accept header field is present, and if the 
685
-            // server cannot send a response which is acceptable according to 
686
-            // the combined Accept field value, then the server SHOULD send 
687
-            // a 406 (not acceptable) response.
688
-            header('HTTP/1.1 406 Not Acceptable');
689
-            die('406 Not Acceptable: The server was unable to ' . 
690
-                    'negotiate content for this request.');
691
-        } else {
692
-            // Tell cache content is based ot Accept header
693
-            header("Vary: Accept"); 
694
-            return $format;
695
-        }
696
-    }
697
-
698
-
699
-    /**
700
-     * Parses the request data and returns it
701
-     * @return array php data
702
-     */
703
-    protected function getRequestData()
704
-    {
705
-        try {
706
-            $r = file_get_contents('php://input');
707
-            if (is_null($r)) {
708
-                return $_GET;
709
-            }
710
-            $r = $this->request_format->decode($r);
711
-            return is_null($r) ? array() : $r;
712
-        } catch (RestException $e) {
713
-            $this->handleError($e->getCode(), $e->getMessage());
714
-        }
715
-    }
716
-
717
-
718
-    protected function mapUrlToMethod()
719
-    {
720
-        if (!isset($this->routes[$this->request_method])) {
721
-            return array();
722
-        }
723
-        $urls = $this->routes[$this->request_method];
724
-        if (!$urls) {
725
-            return array();
726
-        }
727
-
728
-        $found = false;
729
-        $this->request_data += $_GET;
730
-        $params = array('request_data' => $this->request_data);
731
-        $params += $this->request_data;
732
-        $lc = strtolower($this->url);
733
-        foreach ($urls as $url => $call) {
734
-            //echo PHP_EOL.$url.' = '.$this->url.PHP_EOL;
735
-            $call = (object) $call;
736
-            if (strstr($url, ':')) {
737
-                $regex = preg_replace('/\\\:([^\/]+)/', '(?P<$1>[^/]+)',
738
-                    preg_quote($url));
739
-                if (preg_match(":^$regex$:i", $this->url, $matches)) {
740
-                    foreach ($matches as $arg => $match) {
741
-                        if (isset($call->arguments[$arg])) {
742
-                            //flog("$arg => $match $args[$arg]");
743
-                            $params[$arg] = $match;
744
-                        }
745
-                    }
746
-                    $found = true;
747
-                    break;
748
-                }
749
-            } else if ($url == $lc) {
750
-                $found = true;
751
-                break;
752
-            }
753
-        }
754
-        if ($found) {
755
-            //echo PHP_EOL."Found $url ";
756
-            //print_r($call);
757
-            $p = $call->defaults;
758
-            foreach ($call->arguments as $key => $value) {
759
-                //echo "$key => $value \n";
760
-                if (isset($params[$key])) {
761
-                    $p[$value] = $params[$key];
762
-                }
763
-            }
764
-            $call->arguments = $p;
765
-            return $call;
766
-        }
767
-    }
768
-
769
-
770
-    /**
771
-     * Apply static and non-static properties defined in
772
-     * the method information anotation
773
-     * @param String $class_name
774
-     * @param Object $instance instance of that class
775
-     * @param Object $method_info method information and metadata
776
-     */
777
-    protected function applyClassMetadata($class_name, $instance, $method_info)
778
-    {
779
-        if (isset($method_info->metadata[$class_name])
780
-            && is_array($method_info->metadata[$class_name])
781
-        ) {
782
-            foreach ($method_info->metadata[$class_name] as
783
-                    $property => $value) {
784
-                if (property_exists($class_name, $property)) {
785
-                    $reflection_property = 
786
-                        new ReflectionProperty($class_name, $property);
787
-                    $reflection_property->setValue($instance, $value);
788
-                }
789
-            }
790
-        }
791
-    }
792
-
793
-
794
-    protected function loadCache()
795
-    {
796
-        if ($this->cached !== null) {
797
-            return;
798
-        }
799
-        $file = $this->cache_dir . '/routes.php';
800
-        $this->cached = false;
801
-
802
-        if ($this->production_mode) {
803
-            if (file_exists($file)) {
804
-                $routes = include($file);
805
-            }
806
-            if (isset($routes) && is_array($routes)) {
807
-                $this->routes = $routes;
808
-                $this->cached = true;
809
-            }
810
-        } else {
811
-            //@unlink($this->cache_dir . "/$name.php");
812
-        }
813
-    }
814
-
815
-
816
-    /**
817
-     * Generates cachable url to method mapping
818
-     * @param string $class_name
819
-     * @param string $base_path
820
-     */
821
-    protected function generateMap($class_name, $base_path = '')
822
-    {
823
-        $reflection = new ReflectionClass($class_name);
824
-        $class_metadata = parse_doc($reflection->getDocComment());
825
-        $methods = $reflection->getMethods(
826
-            ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED
827
-        );
828
-        foreach ($methods as $method) {
829
-            $doc = $method->getDocComment();
830
-            $arguments = array();
831
-            $defaults = array();
832
-            $metadata = $class_metadata + parse_doc($doc);
833
-            $params = $method->getParameters();
834
-            $position = 0;
835
-            foreach ($params as $param) {
836
-                $arguments[$param->getName()] = $position;
837
-                $defaults[$position] = $param->isDefaultValueAvailable() ? 
838
-                    $param->getDefaultValue() : null;
839
-                $position++;
840
-            }
841
-            $method_flag = $method->isProtected() ? 
842
-                (isRestlerCompatibilityModeEnabled() ? 2 : 3) : 
843
-                (isset($metadata['protected']) ? 1 : 0);
844
-
845
-            //take note of the order
846
-            $call = array(
847
-                'class_name' => $class_name,
848
-                'method_name' => $method->getName(),
849
-                'arguments' => $arguments,
850
-                'defaults' => $defaults,
851
-                'metadata' => $metadata,
852
-                'method_flag' => $method_flag
853
-            );
854
-            $method_url = strtolower($method->getName());
855
-            if (preg_match_all(
856
-                '/@url\s+(GET|POST|PUT|DELETE|HEAD|OPTIONS)[ \t]*\/?(\S*)/s',
857
-                    $doc, $matches, PREG_SET_ORDER)
858
-            ) {
859
-                foreach ($matches as $match) {
860
-                    $http_method = $match[1];
861
-                    $url = rtrim($base_path . $match[2], '/');
862
-                    $this->routes[$http_method][$url] = $call;
863
-                }
864
-            } elseif ($method_url[0] != '_') { 
865
-                //not prefixed with underscore
866
-                // no configuration found so use convention
867
-                if (preg_match_all('/^(GET|POST|PUT|DELETE|HEAD|OPTIONS)/i',
868
-                        $method_url, $matches)
869
-                ) {
870
-                    $http_method = strtoupper($matches[0][0]);
871
-                    $method_url = substr($method_url, strlen($http_method));
872
-                } else {
873
-                    $http_method = 'GET';
874
-                }
875
-                $url = $base_path
876
-                    . ($method_url == 'index' || $method_url == 'default' ? '' :
877
-                        $method_url);
878
-                $url = rtrim($url, '/');
879
-                $this->routes[$http_method][$url] = $call;
880
-                foreach ($params as $param) {
881
-                    if ($param->getName() == 'request_data') {
882
-                        break;
883
-                    }
884
-                    $url .= $url == '' ? ':' : '/:';
885
-                    $url .= $param->getName();
886
-                    $this->routes[$http_method][$url] = $call;
887
-                }
888
-            }
889
-        }
890
-    }
480
+	}
481
+
482
+
483
+	/**
484
+	 * Sets the HTTP response status
485
+	 * @param int $code response code
486
+	 */
487
+	public function setStatus($code)
488
+	{
489
+		header("{$_SERVER['SERVER_PROTOCOL']} $code " . 
490
+			$this->codes[strval($code)]);
491
+	}
492
+
493
+
494
+	/**
495
+	 * Compare two strings and remove the common
496
+	 * sub string from the first string and return it
497
+	 * @param string $first
498
+	 * @param string $second
499
+	 * @param string $char optional, set it as
500
+	 * blank string for char by char comparison
501
+	 * @return string
502
+	 */
503
+	public function removeCommonPath($first, $second, $char = '/')
504
+	{
505
+		$first = explode($char, $first);
506
+		$second = explode($char, $second);
507
+		while (count($second)) {
508
+			if ($first[0] == $second[0]) {
509
+				array_shift($first);
510
+			} else {
511
+				break;
512
+			}
513
+			array_shift($second);
514
+		}
515
+		return implode($char, $first);
516
+	}
517
+
518
+
519
+	/**
520
+	 * Save cache to file
521
+	 */
522
+	public function saveCache()
523
+	{
524
+		$file = $this->cache_dir . '/routes.php';
525
+		$s = '$o=array();' . PHP_EOL;
526
+		foreach ($this->routes as $key => $value) {
527
+			$s .= PHP_EOL . PHP_EOL . PHP_EOL . 
528
+				"############### $key ###############" . PHP_EOL . PHP_EOL;
529
+			$s .= '$o[\'' . $key . '\']=array();';
530
+			foreach ($value as $ke => $va) {
531
+				$s .= PHP_EOL . PHP_EOL . "#==== $key $ke" . PHP_EOL . PHP_EOL;
532
+				$s .= '$o[\'' . $key . '\'][\'' . $ke . '\']=' . str_replace(
533
+						PHP_EOL, PHP_EOL . "\t", var_export($va, true)
534
+					) . ';';
535
+			}
536
+		}
537
+		$s .= PHP_EOL . 'return $o;';
538
+		$r = @file_put_contents($file, "<?php $s");
539
+		@chmod($file, 0777);
540
+		if ($r === false) {
541
+			throw new Exception(
542
+				"The cache directory located at '$this->cache_dir' needs to "
543
+				. "have the permissions set to read/write/execute for everyone"
544
+				. " in order to save cache and improve performance.");
545
+		}
546
+	}
547
+
548
+	// ==================================================================
549
+	//
550
+	// Protected functions
551
+	//
552
+	// ------------------------------------------------------------------
553
+
554
+
555
+	/**
556
+	 * Parses the requst url and get the api path
557
+	 * @return string api path
558
+	 */
559
+	protected function getPath()
560
+	{
561
+		$path = urldecode($this->removeCommonPath($_SERVER['REQUEST_URI'],
562
+				$_SERVER['SCRIPT_NAME']));
563
+		$path = preg_replace('/(\/*\?.*$)|(\/$)/', '', $path);
564
+		$path = str_replace($this->format_map['extensions'], '', $path);
565
+		return $path;
566
+	}
567
+
568
+
569
+	/**
570
+	 * Parses the request to figure out the http request type
571
+	 * @return string which will be one of the following
572
+	 * [GET, POST, PUT, DELETE]
573
+	 * @example GET
574
+	 */
575
+	protected function getRequestMethod()
576
+	{
577
+		$method = $_SERVER['REQUEST_METHOD'];
578
+		if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
579
+			$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
580
+		}
581
+		//support for HEAD request
582
+		if ($method == 'HEAD') {
583
+			$method = 'GET';
584
+		}
585
+		return $method;
586
+	}
587
+
588
+
589
+	/**
590
+	 * Parses the request to figure out format of the request data
591
+	 * @return iFormat any class that implements iFormat
592
+	 * @example JsonFormat
593
+	 */
594
+	protected function getRequestFormat()
595
+	{
596
+		$format = null;
597
+		//check if client has sent any information on request format
598
+		if (isset($_SERVER['CONTENT_TYPE'])) {
599
+			$mime = explode(';', $_SERVER['CONTENT_TYPE']);
600
+			$mime = $mime[0];
601
+			if ($mime == UrlEncodedFormat::MIME) {
602
+				$format = new UrlEncodedFormat();
603
+			} else {
604
+				if (isset($this->format_map[$mime])) {
605
+					$format = $this->format_map[$mime];
606
+					$format = is_string($format) ? new $format : $format;
607
+					$format->setMIME($mime);
608
+				}
609
+			}
610
+		}
611
+		return $format;
612
+	}
613
+
614
+
615
+	/**
616
+	 * Parses the request to figure out the best format for response
617
+	 * @return iFormat any class that implements iFormat
618
+	 * @example JsonFormat
619
+	 */
620
+	protected function getResponseFormat()
621
+	{
622
+		//check if client has specified an extension
623
+		/**
624
+		 * @var iFormat
625
+		 */
626
+		$format = null;
627
+		$extensions = explode('.',
628
+			parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
629
+		while ($extensions) {
630
+			$extension = array_pop($extensions);
631
+			$extension = explode('/', $extension);
632
+			$extension = array_shift($extension);
633
+			if ($extension && isset($this->format_map[$extension])) {
634
+				$format = $this->format_map[$extension];
635
+				$format = is_string($format) ? new $format : $format;
636
+				$format->setExtension($extension);
637
+				//echo "Extension $extension";
638
+				return $format;
639
+			}
640
+		}
641
+		//check if client has sent list of accepted data formats
642
+		if (isset($_SERVER['HTTP_ACCEPT'])) {
643
+			$acceptList = array();
644
+			$accepts = explode(',', strtolower($_SERVER['HTTP_ACCEPT']));
645
+			if (!is_array($accepts)) {
646
+				$accepts = array($accepts);
647
+			}
648
+			foreach ($accepts as $pos => $accept) {
649
+				$parts = explode(';q=', trim($accept));
650
+				$type = array_shift($parts);
651
+				$quality = count($parts) ? 
652
+					floatval(array_shift($parts)) : 
653
+					(1000 - $pos) / 1000;
654
+				$acceptList[$type] = $quality;
655
+			}
656
+			arsort($acceptList);
657
+			foreach ($acceptList as $accept => $quality) {
658
+				if (isset($this->format_map[$accept])) {
659
+					$format = $this->format_map[$accept];
660
+					$format = is_string($format) ? new $format : $format;
661
+					$format->setMIME($accept);
662
+					//echo "MIME $accept";
663
+					// Tell cache content is based on Accept header
664
+					header("Vary: Accept"); 
665
+					return $format;
666
+				}
667
+			}
668
+		} else {
669
+			// RFC 2616: If no Accept header field is
670
+			// present, then it is assumed that the
671
+			// client accepts all media types.
672
+			$_SERVER['HTTP_ACCEPT'] = '*/*';
673
+		}
674
+		if (strpos($_SERVER['HTTP_ACCEPT'], '*') !== false) {
675
+			if (strpos($_SERVER['HTTP_ACCEPT'], 'application/*') !== false) {
676
+				$format = new JsonFormat;
677
+			} else if (strpos($_SERVER['HTTP_ACCEPT'], 'text/*') !== false) {
678
+				$format = new XmlFormat;
679
+			} else if (strpos($_SERVER['HTTP_ACCEPT'], '*/*') !== false) {
680
+				$format = new $this->format_map['default'];
681
+			}
682
+		}
683
+		if (empty($format)) {
684
+			// RFC 2616: If an Accept header field is present, and if the 
685
+			// server cannot send a response which is acceptable according to 
686
+			// the combined Accept field value, then the server SHOULD send 
687
+			// a 406 (not acceptable) response.
688
+			header('HTTP/1.1 406 Not Acceptable');
689
+			die('406 Not Acceptable: The server was unable to ' . 
690
+					'negotiate content for this request.');
691
+		} else {
692
+			// Tell cache content is based ot Accept header
693
+			header("Vary: Accept"); 
694
+			return $format;
695
+		}
696
+	}
697
+
698
+
699
+	/**
700
+	 * Parses the request data and returns it
701
+	 * @return array php data
702
+	 */
703
+	protected function getRequestData()
704
+	{
705
+		try {
706
+			$r = file_get_contents('php://input');
707
+			if (is_null($r)) {
708
+				return $_GET;
709
+			}
710
+			$r = $this->request_format->decode($r);
711
+			return is_null($r) ? array() : $r;
712
+		} catch (RestException $e) {
713
+			$this->handleError($e->getCode(), $e->getMessage());
714
+		}
715
+	}
716
+
717
+
718
+	protected function mapUrlToMethod()
719
+	{
720
+		if (!isset($this->routes[$this->request_method])) {
721
+			return array();
722
+		}
723
+		$urls = $this->routes[$this->request_method];
724
+		if (!$urls) {
725
+			return array();
726
+		}
727
+
728
+		$found = false;
729
+		$this->request_data += $_GET;
730
+		$params = array('request_data' => $this->request_data);
731
+		$params += $this->request_data;
732
+		$lc = strtolower($this->url);
733
+		foreach ($urls as $url => $call) {
734
+			//echo PHP_EOL.$url.' = '.$this->url.PHP_EOL;
735
+			$call = (object) $call;
736
+			if (strstr($url, ':')) {
737
+				$regex = preg_replace('/\\\:([^\/]+)/', '(?P<$1>[^/]+)',
738
+					preg_quote($url));
739
+				if (preg_match(":^$regex$:i", $this->url, $matches)) {
740
+					foreach ($matches as $arg => $match) {
741
+						if (isset($call->arguments[$arg])) {
742
+							//flog("$arg => $match $args[$arg]");
743
+							$params[$arg] = $match;
744
+						}
745
+					}
746
+					$found = true;
747
+					break;
748
+				}
749
+			} else if ($url == $lc) {
750
+				$found = true;
751
+				break;
752
+			}
753
+		}
754
+		if ($found) {
755
+			//echo PHP_EOL."Found $url ";
756
+			//print_r($call);
757
+			$p = $call->defaults;
758
+			foreach ($call->arguments as $key => $value) {
759
+				//echo "$key => $value \n";
760
+				if (isset($params[$key])) {
761
+					$p[$value] = $params[$key];
762
+				}
763
+			}
764
+			$call->arguments = $p;
765
+			return $call;
766
+		}
767
+	}
768
+
769
+
770
+	/**
771
+	 * Apply static and non-static properties defined in
772
+	 * the method information anotation
773
+	 * @param String $class_name
774
+	 * @param Object $instance instance of that class
775
+	 * @param Object $method_info method information and metadata
776
+	 */
777
+	protected function applyClassMetadata($class_name, $instance, $method_info)
778
+	{
779
+		if (isset($method_info->metadata[$class_name])
780
+			&& is_array($method_info->metadata[$class_name])
781
+		) {
782
+			foreach ($method_info->metadata[$class_name] as
783
+					$property => $value) {
784
+				if (property_exists($class_name, $property)) {
785
+					$reflection_property = 
786
+						new ReflectionProperty($class_name, $property);
787
+					$reflection_property->setValue($instance, $value);
788
+				}
789
+			}
790
+		}
791
+	}
792
+
793
+
794
+	protected function loadCache()
795
+	{
796
+		if ($this->cached !== null) {
797
+			return;
798
+		}
799
+		$file = $this->cache_dir . '/routes.php';
800
+		$this->cached = false;
801
+
802
+		if ($this->production_mode) {
803
+			if (file_exists($file)) {
804
+				$routes = include($file);
805
+			}
806
+			if (isset($routes) && is_array($routes)) {
807
+				$this->routes = $routes;
808
+				$this->cached = true;
809
+			}
810
+		} else {
811
+			//@unlink($this->cache_dir . "/$name.php");
812
+		}
813
+	}
814
+
815
+
816
+	/**
817
+	 * Generates cachable url to method mapping
818
+	 * @param string $class_name
819
+	 * @param string $base_path
820
+	 */
821
+	protected function generateMap($class_name, $base_path = '')
822
+	{
823
+		$reflection = new ReflectionClass($class_name);
824
+		$class_metadata = parse_doc($reflection->getDocComment());
825
+		$methods = $reflection->getMethods(
826
+			ReflectionMethod::IS_PUBLIC + ReflectionMethod::IS_PROTECTED
827
+		);
828
+		foreach ($methods as $method) {
829
+			$doc = $method->getDocComment();
830
+			$arguments = array();
831
+			$defaults = array();
832
+			$metadata = $class_metadata + parse_doc($doc);
833
+			$params = $method->getParameters();
834
+			$position = 0;
835
+			foreach ($params as $param) {
836
+				$arguments[$param->getName()] = $position;
837
+				$defaults[$position] = $param->isDefaultValueAvailable() ? 
838
+					$param->getDefaultValue() : null;
839
+				$position++;
840
+			}
841
+			$method_flag = $method->isProtected() ? 
842
+				(isRestlerCompatibilityModeEnabled() ? 2 : 3) : 
843
+				(isset($metadata['protected']) ? 1 : 0);
844
+
845
+			//take note of the order
846
+			$call = array(
847
+				'class_name' => $class_name,
848
+				'method_name' => $method->getName(),
849
+				'arguments' => $arguments,
850
+				'defaults' => $defaults,
851
+				'metadata' => $metadata,
852
+				'method_flag' => $method_flag
853
+			);
854
+			$method_url = strtolower($method->getName());
855
+			if (preg_match_all(
856
+				'/@url\s+(GET|POST|PUT|DELETE|HEAD|OPTIONS)[ \t]*\/?(\S*)/s',
857
+					$doc, $matches, PREG_SET_ORDER)
858
+			) {
859
+				foreach ($matches as $match) {
860
+					$http_method = $match[1];
861
+					$url = rtrim($base_path . $match[2], '/');
862
+					$this->routes[$http_method][$url] = $call;
863
+				}
864
+			} elseif ($method_url[0] != '_') { 
865
+				//not prefixed with underscore
866
+				// no configuration found so use convention
867
+				if (preg_match_all('/^(GET|POST|PUT|DELETE|HEAD|OPTIONS)/i',
868
+						$method_url, $matches)
869
+				) {
870
+					$http_method = strtoupper($matches[0][0]);
871
+					$method_url = substr($method_url, strlen($http_method));
872
+				} else {
873
+					$http_method = 'GET';
874
+				}
875
+				$url = $base_path
876
+					. ($method_url == 'index' || $method_url == 'default' ? '' :
877
+						$method_url);
878
+				$url = rtrim($url, '/');
879
+				$this->routes[$http_method][$url] = $call;
880
+				foreach ($params as $param) {
881
+					if ($param->getName() == 'request_data') {
882
+						break;
883
+					}
884
+					$url .= $url == '' ? ':' : '/:';
885
+					$url .= $param->getName();
886
+					$this->routes[$http_method][$url] = $call;
887
+				}
888
+			}
889
+		}
890
+	}
891 891
 
892 892
 }
893 893
 
894 894
 if (version_compare(PHP_VERSION, '5.3.0') < 0) {
895
-    require_once 'compat.php';
895
+	require_once 'compat.php';
896 896
 }
897 897
 
898 898
 // ==================================================================
@@ -916,10 +916,10 @@  discard block
 block discarded – undo
916 916
 {
917 917
 
918 918
 
919
-    public function __construct($http_status_code, $error_message = null)
920
-    {
921
-        parent::__construct($error_message, $http_status_code);
922
-    }
919
+	public function __construct($http_status_code, $error_message = null)
920
+	{
921
+		parent::__construct($error_message, $http_status_code);
922
+	}
923 923
 
924 924
 }
925 925
 
@@ -937,21 +937,21 @@  discard block
 block discarded – undo
937 937
 {
938 938
 
939 939
 
940
-    /**
941
-     * Result of an api call is passed to this method
942
-     * to create a standard structure for the data
943
-     * @param unknown_type $result can be a primitive or array or object
944
-     */
945
-    public function __formatResponse($result);
940
+	/**
941
+	 * Result of an api call is passed to this method
942
+	 * to create a standard structure for the data
943
+	 * @param unknown_type $result can be a primitive or array or object
944
+	 */
945
+	public function __formatResponse($result);
946 946
 
947 947
 
948
-    /**
949
-     * When the api call results in RestException this method
950
-     * will be called to return the error message
951
-     * @param int $status_code
952
-     * @param String $message
953
-     */
954
-    public function __formatError($status_code, $message);
948
+	/**
949
+	 * When the api call results in RestException this method
950
+	 * will be called to return the error message
951
+	 * @param int $status_code
952
+	 * @param String $message
953
+	 */
954
+	public function __formatError($status_code, $message);
955 955
 }
956 956
 
957 957
 /**
@@ -968,21 +968,21 @@  discard block
 block discarded – undo
968 968
 {
969 969
 
970 970
 
971
-    public function __formatResponse($result)
972
-    {
973
-        return $result;
974
-    }
971
+	public function __formatResponse($result)
972
+	{
973
+		return $result;
974
+	}
975 975
 
976 976
 
977
-    public function __formatError($statusCode, $message)
978
-    {
979
-        return array(
980
-            'error' => array(
981
-                'code' => $statusCode,
982
-                'message' => $message
983
-            )
984
-        );
985
-    }
977
+	public function __formatError($statusCode, $message)
978
+	{
979
+		return array(
980
+			'error' => array(
981
+				'code' => $statusCode,
982
+				'message' => $message
983
+			)
984
+		);
985
+	}
986 986
 
987 987
 }
988 988
 
@@ -1000,11 +1000,11 @@  discard block
 block discarded – undo
1000 1000
 {
1001 1001
 
1002 1002
 
1003
-    /**
1004
-     * Auth function that is called when a protected method is requested
1005
-     * @return boolean true or false
1006
-     */
1007
-    public function __isAuthenticated();
1003
+	/**
1004
+	 * Auth function that is called when a protected method is requested
1005
+	 * @return boolean true or false
1006
+	 */
1007
+	public function __isAuthenticated();
1008 1008
 }
1009 1009
 
1010 1010
 /**
@@ -1022,60 +1022,60 @@  discard block
 block discarded – undo
1022 1022
 {
1023 1023
 
1024 1024
 
1025
-    /**
1026
-     * Get Extension => MIME type mappings as an associative array
1027
-     * @return array list of mime strings for the format
1028
-     * @example array('json'=>'application/json');
1029
-     */
1030
-    public function getMIMEMap();
1025
+	/**
1026
+	 * Get Extension => MIME type mappings as an associative array
1027
+	 * @return array list of mime strings for the format
1028
+	 * @example array('json'=>'application/json');
1029
+	 */
1030
+	public function getMIMEMap();
1031 1031
 
1032 1032
 
1033
-    /**
1034
-     * Set the selected MIME type
1035
-     * @param string $mime MIME type
1036
-     */
1037
-    public function setMIME($mime);
1033
+	/**
1034
+	 * Set the selected MIME type
1035
+	 * @param string $mime MIME type
1036
+	 */
1037
+	public function setMIME($mime);
1038 1038
 
1039 1039
 
1040
-    /**
1041
-     * Get selected MIME type
1042
-     */
1043
-    public function getMIME();
1040
+	/**
1041
+	 * Get selected MIME type
1042
+	 */
1043
+	public function getMIME();
1044 1044
 
1045 1045
 
1046
-    /**
1047
-     * Set the selected file extension
1048
-     * @param string $extension file extension
1049
-     */
1050
-    public function setExtension($extension);
1046
+	/**
1047
+	 * Set the selected file extension
1048
+	 * @param string $extension file extension
1049
+	 */
1050
+	public function setExtension($extension);
1051 1051
 
1052 1052
 
1053
-    /**
1054
-     * Get the selected file extension
1055
-     * @return string file extension
1056
-     */
1057
-    public function getExtension();
1053
+	/**
1054
+	 * Get the selected file extension
1055
+	 * @return string file extension
1056
+	 */
1057
+	public function getExtension();
1058 1058
 
1059 1059
 
1060
-    /**
1061
-     * Encode the given data in the format
1062
-     * @param array $data resulting data that needs to
1063
-     * be encoded in the given format
1064
-     * @param boolean $human_readable set to true when restler
1065
-     * is not running in production mode. Formatter has to
1066
-     * make the encoded output more human readable
1067
-     * @return string encoded string
1068
-     */
1069
-    public function encode($data, $human_readable = false);
1060
+	/**
1061
+	 * Encode the given data in the format
1062
+	 * @param array $data resulting data that needs to
1063
+	 * be encoded in the given format
1064
+	 * @param boolean $human_readable set to true when restler
1065
+	 * is not running in production mode. Formatter has to
1066
+	 * make the encoded output more human readable
1067
+	 * @return string encoded string
1068
+	 */
1069
+	public function encode($data, $human_readable = false);
1070 1070
 
1071 1071
 
1072
-    /**
1073
-     * Decode the given data from the format
1074
-     * @param string $data data sent from client to
1075
-     * the api in the given format.
1076
-     * @return array associative array of the parsed data
1077
-     */
1078
-    public function decode($data);
1072
+	/**
1073
+	 * Decode the given data from the format
1074
+	 * @param string $data data sent from client to
1075
+	 * the api in the given format.
1076
+	 * @return array associative array of the parsed data
1077
+	 */
1078
+	public function decode($data);
1079 1079
 }
1080 1080
 
1081 1081
 /**
@@ -1091,57 +1091,57 @@  discard block
 block discarded – undo
1091 1091
 class UrlEncodedFormat implements iFormat
1092 1092
 {
1093 1093
 
1094
-    const MIME = 'application/x-www-form-urlencoded';
1095
-    const EXTENSION = 'post';
1094
+	const MIME = 'application/x-www-form-urlencoded';
1095
+	const EXTENSION = 'post';
1096 1096
 
1097 1097
 
1098
-    public function getMIMEMap()
1099
-    {
1100
-        return array(self::EXTENSION => self::MIME);
1101
-    }
1098
+	public function getMIMEMap()
1099
+	{
1100
+		return array(self::EXTENSION => self::MIME);
1101
+	}
1102 1102
 
1103 1103
 
1104
-    public function getMIME()
1105
-    {
1106
-        return self::MIME;
1107
-    }
1104
+	public function getMIME()
1105
+	{
1106
+		return self::MIME;
1107
+	}
1108 1108
 
1109 1109
 
1110
-    public function getExtension()
1111
-    {
1112
-        return self::EXTENSION;
1113
-    }
1110
+	public function getExtension()
1111
+	{
1112
+		return self::EXTENSION;
1113
+	}
1114 1114
 
1115 1115
 
1116
-    public function setMIME($mime)
1117
-    {
1118
-        //do nothing
1119
-    }
1116
+	public function setMIME($mime)
1117
+	{
1118
+		//do nothing
1119
+	}
1120 1120
 
1121 1121
 
1122
-    public function setExtension($extension)
1123
-    {
1124
-        //do nothing
1125
-    }
1122
+	public function setExtension($extension)
1123
+	{
1124
+		//do nothing
1125
+	}
1126 1126
 
1127 1127
 
1128
-    public function encode($data, $human_readable = false)
1129
-    {
1130
-        return http_build_query($data);
1131
-    }
1128
+	public function encode($data, $human_readable = false)
1129
+	{
1130
+		return http_build_query($data);
1131
+	}
1132 1132
 
1133 1133
 
1134
-    public function decode($data)
1135
-    {
1136
-        parse_str($data, $r);
1137
-        return $r;
1138
-    }
1134
+	public function decode($data)
1135
+	{
1136
+		parse_str($data, $r);
1137
+		return $r;
1138
+	}
1139 1139
 
1140 1140
 
1141
-    public function __toString()
1142
-    {
1143
-        return $this->getExtension();
1144
-    }
1141
+	public function __toString()
1142
+	{
1143
+		return $this->getExtension();
1144
+	}
1145 1145
 
1146 1146
 }
1147 1147
 
@@ -1158,158 +1158,158 @@  discard block
 block discarded – undo
1158 1158
 class JsonFormat implements iFormat
1159 1159
 {
1160 1160
 
1161
-    const MIME = 'application/json,application/javascript';
1162
-
1163
-    static $mime = 'application/json';
1164
-
1165
-    const EXTENSION = 'json';
1166
-
1167
-
1168
-    public function getMIMEMap()
1169
-    {
1170
-        return array(self::EXTENSION => self::MIME);
1171
-    }
1172
-
1173
-
1174
-    public function getMIME()
1175
-    {
1176
-        return self::$mime;
1177
-    }
1178
-
1179
-
1180
-    public function getExtension()
1181
-    {
1182
-        return self::EXTENSION;
1183
-    }
1184
-
1185
-
1186
-    public function setMIME($mime)
1187
-    {
1188
-        self::$mime = $mime;
1189
-    }
1190
-
1191
-
1192
-    public function setExtension($extension)
1193
-    {
1194
-        //do nothing
1195
-    }
1196
-
1197
-
1198
-    public function encode($data, $human_readable = false)
1199
-    {
1200
-        return $human_readable ? 
1201
-            $this->json_format(json_encode(object_to_array($data))) : 
1202
-            json_encode(object_to_array($data));
1203
-    }
1204
-
1205
-
1206
-    public function decode($data)
1207
-    {
1208
-        $decoded = json_decode($data);
1209
-        if (function_exists('json_last_error')) {
1210
-            $message = '';
1211
-            switch (json_last_error()) {
1212
-                case JSON_ERROR_NONE:
1213
-                    return object_to_array($decoded);
1214
-                    break;
1215
-                case JSON_ERROR_DEPTH:
1216
-                    $message = 'maximum stack depth exceeded';
1217
-                    break;
1218
-                case JSON_ERROR_STATE_MISMATCH:
1219
-                    $message = 'underflow or the modes mismatch';
1220
-                    break;
1221
-                case JSON_ERROR_CTRL_CHAR:
1222
-                    $message = 'unexpected control character found';
1223
-                    break;
1224
-                case JSON_ERROR_SYNTAX:
1225
-                    $message = 'malformed JSON';
1226
-                    break;
1227
-                case JSON_ERROR_UTF8:
1228
-                    $message = 'malformed UTF-8 characters, '.
1229
-                        'possibly incorrectly encoded';
1230
-                    break;
1231
-                default:
1232
-                    $message = 'unknown error';
1233
-                    break;
1234
-            }
1235
-            throw new RestException(400, 'Error parsing JSON, ' . $message);
1236
-        } else if (strlen($data) && $decoded === null || $decoded === $data) {
1237
-            throw new RestException(400, 'Error parsing JSON');
1238
-        }
1239
-        return object_to_array($decoded);
1240
-    }
1241
-
1242
-
1243
-    /**
1244
-     * Pretty print JSON string
1245
-     * @param string $json
1246
-     * @return string formated json
1247
-     */
1248
-    private function json_format($json)
1249
-    {
1250
-        $tab = "  ";
1251
-        $new_json = "";
1252
-        $indent_level = 0;
1253
-        $in_string = false;
1254
-        $len = strlen($json);
1255
-
1256
-        for ($c = 0; $c < $len; $c++) {
1257
-            $char = $json[$c];
1258
-            switch ($char) {
1259
-                case '{':
1260
-                case '[':
1261
-                    if (!$in_string) {
1262
-                        $new_json .= $char . "\n" .
1263
-                            str_repeat($tab, $indent_level + 1);
1264
-                        $indent_level++;
1265
-                    } else {
1266
-                        $new_json .= $char;
1267
-                    }
1268
-                    break;
1269
-                case '}':
1270
-                case ']':
1271
-                    if (!$in_string) {
1272
-                        $indent_level--;
1273
-                        $new_json .= "\n" . str_repeat($tab, $indent_level) 
1274
-                            . $char;
1275
-                    } else {
1276
-                        $new_json .= $char;
1277
-                    }
1278
-                    break;
1279
-                case ',':
1280
-                    if (!$in_string) {
1281
-                        $new_json .= ",\n" . str_repeat($tab, $indent_level);
1282
-                    } else {
1283
-                        $new_json .= $char;
1284
-                    }
1285
-                    break;
1286
-                case ':':
1287
-                    if (!$in_string) {
1288
-                        $new_json .= ": ";
1289
-                    } else {
1290
-                        $new_json .= $char;
1291
-                    }
1292
-                    break;
1293
-                case '"':
1294
-                    if ($c == 0) {
1295
-                        $in_string = true;
1296
-                    } else if ($c > 0 && $json[$c - 1] != '\\') {
1297
-                        $in_string = !$in_string;
1298
-                    }
1299
-                default:
1300
-                    $new_json .= $char;
1301
-                    break;
1302
-            }
1303
-        }
1304
-
1305
-        return $new_json;
1306
-    }
1307
-
1308
-
1309
-    public function __toString()
1310
-    {
1311
-        return $this->getExtension();
1312
-    }
1161
+	const MIME = 'application/json,application/javascript';
1162
+
1163
+	static $mime = 'application/json';
1164
+
1165
+	const EXTENSION = 'json';
1166
+
1167
+
1168
+	public function getMIMEMap()
1169
+	{
1170
+		return array(self::EXTENSION => self::MIME);
1171
+	}
1172
+
1173
+
1174
+	public function getMIME()
1175
+	{
1176
+		return self::$mime;
1177
+	}
1178
+
1179
+
1180
+	public function getExtension()
1181
+	{
1182
+		return self::EXTENSION;
1183
+	}
1184
+
1185
+
1186
+	public function setMIME($mime)
1187
+	{
1188
+		self::$mime = $mime;
1189
+	}
1190
+
1191
+
1192
+	public function setExtension($extension)
1193
+	{
1194
+		//do nothing
1195
+	}
1196
+
1197
+
1198
+	public function encode($data, $human_readable = false)
1199
+	{
1200
+		return $human_readable ? 
1201
+			$this->json_format(json_encode(object_to_array($data))) : 
1202
+			json_encode(object_to_array($data));
1203
+	}
1204
+
1205
+
1206
+	public function decode($data)
1207
+	{
1208
+		$decoded = json_decode($data);
1209
+		if (function_exists('json_last_error')) {
1210
+			$message = '';
1211
+			switch (json_last_error()) {
1212
+				case JSON_ERROR_NONE:
1213
+					return object_to_array($decoded);
1214
+					break;
1215
+				case JSON_ERROR_DEPTH:
1216
+					$message = 'maximum stack depth exceeded';
1217
+					break;
1218
+				case JSON_ERROR_STATE_MISMATCH:
1219
+					$message = 'underflow or the modes mismatch';
1220
+					break;
1221
+				case JSON_ERROR_CTRL_CHAR:
1222
+					$message = 'unexpected control character found';
1223
+					break;
1224
+				case JSON_ERROR_SYNTAX:
1225
+					$message = 'malformed JSON';
1226
+					break;
1227
+				case JSON_ERROR_UTF8:
1228
+					$message = 'malformed UTF-8 characters, '.
1229
+						'possibly incorrectly encoded';
1230
+					break;
1231
+				default:
1232
+					$message = 'unknown error';
1233
+					break;
1234
+			}
1235
+			throw new RestException(400, 'Error parsing JSON, ' . $message);
1236
+		} else if (strlen($data) && $decoded === null || $decoded === $data) {
1237
+			throw new RestException(400, 'Error parsing JSON');
1238
+		}
1239
+		return object_to_array($decoded);
1240
+	}
1241
+
1242
+
1243
+	/**
1244
+	 * Pretty print JSON string
1245
+	 * @param string $json
1246
+	 * @return string formated json
1247
+	 */
1248
+	private function json_format($json)
1249
+	{
1250
+		$tab = "  ";
1251
+		$new_json = "";
1252
+		$indent_level = 0;
1253
+		$in_string = false;
1254
+		$len = strlen($json);
1255
+
1256
+		for ($c = 0; $c < $len; $c++) {
1257
+			$char = $json[$c];
1258
+			switch ($char) {
1259
+				case '{':
1260
+				case '[':
1261
+					if (!$in_string) {
1262
+						$new_json .= $char . "\n" .
1263
+							str_repeat($tab, $indent_level + 1);
1264
+						$indent_level++;
1265
+					} else {
1266
+						$new_json .= $char;
1267
+					}
1268
+					break;
1269
+				case '}':
1270
+				case ']':
1271
+					if (!$in_string) {
1272
+						$indent_level--;
1273
+						$new_json .= "\n" . str_repeat($tab, $indent_level) 
1274
+							. $char;
1275
+					} else {
1276
+						$new_json .= $char;
1277
+					}
1278
+					break;
1279
+				case ',':
1280
+					if (!$in_string) {
1281
+						$new_json .= ",\n" . str_repeat($tab, $indent_level);
1282
+					} else {
1283
+						$new_json .= $char;
1284
+					}
1285
+					break;
1286
+				case ':':
1287
+					if (!$in_string) {
1288
+						$new_json .= ": ";
1289
+					} else {
1290
+						$new_json .= $char;
1291
+					}
1292
+					break;
1293
+				case '"':
1294
+					if ($c == 0) {
1295
+						$in_string = true;
1296
+					} else if ($c > 0 && $json[$c - 1] != '\\') {
1297
+						$in_string = !$in_string;
1298
+					}
1299
+				default:
1300
+					$new_json .= $char;
1301
+					break;
1302
+			}
1303
+		}
1304
+
1305
+		return $new_json;
1306
+	}
1307
+
1308
+
1309
+	public function __toString()
1310
+	{
1311
+		return $this->getExtension();
1312
+	}
1313 1313
 
1314 1314
 }
1315 1315
 
@@ -1327,123 +1327,123 @@  discard block
 block discarded – undo
1327 1327
 class DocParser
1328 1328
 {
1329 1329
 
1330
-    private $params = array();
1331
-
1332
-
1333
-    public function parse($doc = '')
1334
-    {
1335
-        if ($doc == '') {
1336
-            return $this->params;
1337
-        }
1338
-        //Get the comment
1339
-        if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
1340
-            return $this->params;
1341
-        }
1342
-        $comment = trim($comment[1]);
1343
-        //Get all the lines and strip the * from the first character
1344
-        if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
1345
-            return $this->params;
1346
-        }
1347
-        $this->parseLines($lines[1]);
1348
-        return $this->params;
1349
-    }
1350
-
1351
-
1352
-    private function parseLines($lines)
1353
-    {
1354
-        foreach ($lines as $line) {
1355
-            $parsedLine = $this->parseLine($line); //Parse the line
1356
-
1357
-            if ($parsedLine === false && !isset($this->params['description'])) {
1358
-                if (isset($desc)) {
1359
-                    //Store the first line in the short description
1360
-                    $this->params['description'] = implode(PHP_EOL, $desc);
1361
-                }
1362
-                $desc = array();
1363
-            } else if ($parsedLine !== false) {
1364
-                $desc[] = $parsedLine; //Store the line in the long description
1365
-            }
1366
-        }
1367
-        $desc = implode(' ', $desc);
1368
-        if (!empty($desc)) {
1369
-            $this->params['long_description'] = $desc;
1370
-        }
1371
-    }
1372
-
1373
-
1374
-    private function parseLine($line)
1375
-    {
1376
-        //trim the whitespace from the line
1377
-        $line = trim($line);
1378
-
1379
-        if (empty($line)) {
1380
-            return false; //Empty line
1381
-        }
1382
-
1383
-        if (strpos($line, '@') === 0) {
1384
-            if (strpos($line, ' ') > 0) {
1385
-                //Get the parameter name
1386
-                $param = substr($line, 1, strpos($line, ' ') - 1);
1387
-                $value = substr($line, strlen($param) + 2); //Get the value
1388
-            } else {
1389
-                $param = substr($line, 1);
1390
-                $value = '';
1391
-            }
1392
-            //Parse the line and return false if the parameter is valid
1393
-            if ($this->setParam($param, $value)) {
1394
-                return false;
1395
-            }
1396
-        }
1397
-        return $line;
1398
-    }
1399
-
1400
-
1401
-    private function setParam($param, $value)
1402
-    {
1403
-        if ($param == 'param' || $param == 'return') {
1404
-            $value = $this->formatParamOrReturn($value);
1405
-        }
1406
-        if ($param == 'class') {
1407
-            list($param, $value) = $this->formatClass($value);
1408
-        }
1409
-
1410
-        if (empty($this->params[$param])) {
1411
-            $this->params[$param] = $value;
1412
-        } else if ($param == 'param') {
1413
-            $arr = array($this->params[$param], $value);
1414
-            $this->params[$param] = $arr;
1415
-        } else {
1416
-            $this->params[$param] = $value + $this->params[$param];
1417
-        }
1418
-        return true;
1419
-    }
1420
-
1421
-
1422
-    private function formatClass($value)
1423
-    {
1424
-        $r = preg_split("[\(|\)]", $value);
1425
-        if (count($r) > 1) {
1426
-            $param = $r[0];
1427
-            parse_str($r[1], $value);
1428
-            foreach ($value as $key => $val) {
1429
-                $val = explode(',', $val);
1430
-                if (count($val) > 1) {
1431
-                    $value[$key] = $val;
1432
-                }
1433
-            }
1434
-        } else {
1435
-            $param = 'Unknown';
1436
-        }
1437
-        return array($param, $value);
1438
-    }
1439
-
1440
-
1441
-    private function formatParamOrReturn($string)
1442
-    {
1443
-        $pos = strpos($string, ' ');
1444
-        $type = substr($string, 0, $pos);
1445
-        return '(' . $type . ')' . substr($string, $pos + 1);
1446
-    }
1330
+	private $params = array();
1331
+
1332
+
1333
+	public function parse($doc = '')
1334
+	{
1335
+		if ($doc == '') {
1336
+			return $this->params;
1337
+		}
1338
+		//Get the comment
1339
+		if (preg_match('#^/\*\*(.*)\*/#s', $doc, $comment) === false) {
1340
+			return $this->params;
1341
+		}
1342
+		$comment = trim($comment[1]);
1343
+		//Get all the lines and strip the * from the first character
1344
+		if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false) {
1345
+			return $this->params;
1346
+		}
1347
+		$this->parseLines($lines[1]);
1348
+		return $this->params;
1349
+	}
1350
+
1351
+
1352
+	private function parseLines($lines)
1353
+	{
1354
+		foreach ($lines as $line) {
1355
+			$parsedLine = $this->parseLine($line); //Parse the line
1356
+
1357
+			if ($parsedLine === false && !isset($this->params['description'])) {
1358
+				if (isset($desc)) {
1359
+					//Store the first line in the short description
1360
+					$this->params['description'] = implode(PHP_EOL, $desc);
1361
+				}
1362
+				$desc = array();
1363
+			} else if ($parsedLine !== false) {
1364
+				$desc[] = $parsedLine; //Store the line in the long description
1365
+			}
1366
+		}
1367
+		$desc = implode(' ', $desc);
1368
+		if (!empty($desc)) {
1369
+			$this->params['long_description'] = $desc;
1370
+		}
1371
+	}
1372
+
1373
+
1374
+	private function parseLine($line)
1375
+	{
1376
+		//trim the whitespace from the line
1377
+		$line = trim($line);
1378
+
1379
+		if (empty($line)) {
1380
+			return false; //Empty line
1381
+		}
1382
+
1383
+		if (strpos($line, '@') === 0) {
1384
+			if (strpos($line, ' ') > 0) {
1385
+				//Get the parameter name
1386
+				$param = substr($line, 1, strpos($line, ' ') - 1);
1387
+				$value = substr($line, strlen($param) + 2); //Get the value
1388
+			} else {
1389
+				$param = substr($line, 1);
1390
+				$value = '';
1391
+			}
1392
+			//Parse the line and return false if the parameter is valid
1393
+			if ($this->setParam($param, $value)) {
1394
+				return false;
1395
+			}
1396
+		}
1397
+		return $line;
1398
+	}
1399
+
1400
+
1401
+	private function setParam($param, $value)
1402
+	{
1403
+		if ($param == 'param' || $param == 'return') {
1404
+			$value = $this->formatParamOrReturn($value);
1405
+		}
1406
+		if ($param == 'class') {
1407
+			list($param, $value) = $this->formatClass($value);
1408
+		}
1409
+
1410
+		if (empty($this->params[$param])) {
1411
+			$this->params[$param] = $value;
1412
+		} else if ($param == 'param') {
1413
+			$arr = array($this->params[$param], $value);
1414
+			$this->params[$param] = $arr;
1415
+		} else {
1416
+			$this->params[$param] = $value + $this->params[$param];
1417
+		}
1418
+		return true;
1419
+	}
1420
+
1421
+
1422
+	private function formatClass($value)
1423
+	{
1424
+		$r = preg_split("[\(|\)]", $value);
1425
+		if (count($r) > 1) {
1426
+			$param = $r[0];
1427
+			parse_str($r[1], $value);
1428
+			foreach ($value as $key => $val) {
1429
+				$val = explode(',', $val);
1430
+				if (count($val) > 1) {
1431
+					$value[$key] = $val;
1432
+				}
1433
+			}
1434
+		} else {
1435
+			$param = 'Unknown';
1436
+		}
1437
+		return array($param, $value);
1438
+	}
1439
+
1440
+
1441
+	private function formatParamOrReturn($string)
1442
+	{
1443
+		$pos = strpos($string, ' ');
1444
+		$type = substr($string, 0, $pos);
1445
+		return '(' . $type . ')' . substr($string, $pos + 1);
1446
+	}
1447 1447
 
1448 1448
 }
1449 1449
 
@@ -1456,30 +1456,30 @@  discard block
 block discarded – undo
1456 1456
 
1457 1457
 function parse_doc($php_doc_comment)
1458 1458
 {
1459
-    $p = new DocParser();
1460
-    return $p->parse($php_doc_comment);
1459
+	$p = new DocParser();
1460
+	return $p->parse($php_doc_comment);
1461 1461
 
1462
-    $p = new Parser($php_doc_comment);
1463
-    return $p;
1462
+	$p = new Parser($php_doc_comment);
1463
+	return $p;
1464 1464
 
1465
-    $php_doc_comment = preg_replace(
1466
-        "/(^[\\s]*\\/\\*\\*)
1465
+	$php_doc_comment = preg_replace(
1466
+		"/(^[\\s]*\\/\\*\\*)
1467 1467
         |(^[\\s]\\*\\/)
1468 1468
         |(^[\\s]*\\*?\\s)
1469 1469
         |(^[\\s]*)
1470 1470
         |(^[\\t]*)/ixm",
1471
-        "", $php_doc_comment);
1472
-    $php_doc_comment = str_replace("\r", "", $php_doc_comment);
1473
-    $php_doc_comment = preg_replace("/([\\t])+/", "\t", $php_doc_comment);
1474
-    return explode("\n", $php_doc_comment);
1475
-
1476
-    $php_doc_comment = trim(preg_replace('/\r?\n *\* */', ' ', 
1477
-            $php_doc_comment));
1478
-    return $php_doc_comment;
1479
-
1480
-    preg_match_all('/@([a-z]+)\s+(.*?)\s*(?=$|@[a-z]+\s)/s', $php_doc_comment,
1481
-        $matches);
1482
-    return array_combine($matches[1], $matches[2]);
1471
+		"", $php_doc_comment);
1472
+	$php_doc_comment = str_replace("\r", "", $php_doc_comment);
1473
+	$php_doc_comment = preg_replace("/([\\t])+/", "\t", $php_doc_comment);
1474
+	return explode("\n", $php_doc_comment);
1475
+
1476
+	$php_doc_comment = trim(preg_replace('/\r?\n *\* */', ' ', 
1477
+			$php_doc_comment));
1478
+	return $php_doc_comment;
1479
+
1480
+	preg_match_all('/@([a-z]+)\s+(.*?)\s*(?=$|@[a-z]+\s)/s', $php_doc_comment,
1481
+		$matches);
1482
+	return array_combine($matches[1], $matches[2]);
1483 1483
 }
1484 1484
 
1485 1485
 
@@ -1498,21 +1498,21 @@  discard block
 block discarded – undo
1498 1498
  */
1499 1499
 function object_to_array($object, $utf_encode = false)
1500 1500
 {
1501
-    if (is_array($object)
1502
-        || (is_object($object)
1503
-        && !($object instanceof JsonSerializable))
1504
-    ) {
1505
-        $array = array();
1506
-        foreach ($object as $key => $value) {
1507
-            $value = object_to_array($value, $utf_encode);
1508
-            if ($utf_encode && is_string($value)) {
1509
-                $value = utf8_encode($value);
1510
-            }
1511
-            $array[$key] = $value;
1512
-        }
1513
-        return $array;
1514
-    }
1515
-    return $object;
1501
+	if (is_array($object)
1502
+		|| (is_object($object)
1503
+		&& !($object instanceof JsonSerializable))
1504
+	) {
1505
+		$array = array();
1506
+		foreach ($object as $key => $value) {
1507
+			$value = object_to_array($value, $utf_encode);
1508
+			if ($utf_encode && is_string($value)) {
1509
+				$value = utf8_encode($value);
1510
+			}
1511
+			$array[$key] = $value;
1512
+		}
1513
+		return $array;
1514
+	}
1515
+	return $object;
1516 1516
 }
1517 1517
 
1518 1518
 
@@ -1522,21 +1522,21 @@  discard block
 block discarded – undo
1522 1522
  */
1523 1523
 function autoload_formats($class_name)
1524 1524
 {
1525
-    $class_name = strtolower($class_name);
1525
+	$class_name = strtolower($class_name);
1526 1526
 	
1527
-    $file = RESTLER_PATH . "../../../api/mobile_services/$class_name.php";
1528
-    if (file_exists($file)) {
1529
-        require_once ($file);
1530
-    } else {
1527
+	$file = RESTLER_PATH . "../../../api/mobile_services/$class_name.php";
1528
+	if (file_exists($file)) {
1529
+		require_once ($file);
1530
+	} else {
1531 1531
 		$file = RESTLER_PATH . "/../../api/mobile_services/$class_name.php";
1532
-        if (file_exists($file)) {
1533
-            require_once ($file);
1534
-        } elseif (file_exists(RESTLER_PATH . "/../api/mobile_services/$class_name.php")) {
1535
-            require_once ("/../api/mobile_services/$class_name.php");
1536
-        } elseif (file_exists("$class_name.php")) {
1537
-            require_once ("$class_name.php");
1538
-        }
1539
-    }
1532
+		if (file_exists($file)) {
1533
+			require_once ($file);
1534
+		} elseif (file_exists(RESTLER_PATH . "/../api/mobile_services/$class_name.php")) {
1535
+			require_once ("/../api/mobile_services/$class_name.php");
1536
+		} elseif (file_exists("$class_name.php")) {
1537
+			require_once ("$class_name.php");
1538
+		}
1539
+	}
1540 1540
 }
1541 1541
 
1542 1542
 // ==================================================================
@@ -1553,10 +1553,10 @@  discard block
 block discarded – undo
1553 1553
 if (!function_exists('isRestlerCompatibilityModeEnabled')) {
1554 1554
 
1555 1555
 
1556
-    function isRestlerCompatibilityModeEnabled()
1557
-    {
1558
-        return false;
1559
-    }
1556
+	function isRestlerCompatibilityModeEnabled()
1557
+	{
1558
+		return false;
1559
+	}
1560 1560
 
1561 1561
 }
1562 1562
 define('RESTLER_PATH', dirname(__FILE__));
Please login to merge, or discard this patch.
libraries/SabreDAV/Uri/functions.php 1 patch
Indentation   +186 added lines, -186 removed lines patch added patch discarded remove patch
@@ -22,80 +22,80 @@  discard block
 block discarded – undo
22 22
  */
23 23
 function resolve($basePath, $newPath) {
24 24
 
25
-    $base = parse($basePath);
26
-    $delta = parse($newPath);
27
-
28
-    $pick = function($part) use ($base, $delta) {
29
-
30
-        if ($delta[$part]) {
31
-            return $delta[$part];
32
-        } elseif ($base[$part]) {
33
-            return $base[$part];
34
-        }
35
-        return null;
36
-
37
-    };
38
-
39
-    // If the new path defines a scheme, it's absolute and we can just return
40
-    // that.
41
-    if ($delta['scheme']) {
42
-        return build($delta);
43
-    }
44
-
45
-    $newParts = [];
46
-
47
-    $newParts['scheme'] = $pick('scheme');
48
-    $newParts['host']   = $pick('host');
49
-    $newParts['port']   = $pick('port');
50
-
51
-    $path = '';
52
-    if ($delta['path']) {
53
-        // If the path starts with a slash
54
-        if ($delta['path'][0] === '/') {
55
-            $path = $delta['path'];
56
-        } else {
57
-            // Removing last component from base path.
58
-            $path = $base['path'];
59
-            if (strpos($path, '/') !== false) {
60
-                $path = substr($path, 0, strrpos($path, '/'));
61
-            }
62
-            $path .= '/' . $delta['path'];
63
-        }
64
-    } else {
65
-        $path = $base['path'] ?: '/';
66
-    }
67
-    // Removing .. and .
68
-    $pathParts = explode('/', $path);
69
-    $newPathParts = [];
70
-    foreach ($pathParts as $pathPart) {
71
-
72
-        switch ($pathPart) {
73
-            //case '' :
74
-            case '.' :
75
-                break;
76
-            case '..' :
77
-                array_pop($newPathParts);
78
-                break;
79
-            default :
80
-                $newPathParts[] = $pathPart;
81
-                break;
82
-        }
83
-    }
84
-
85
-    $path = implode('/', $newPathParts);
86
-
87
-    // If the source url ended with a /, we want to preserve that.
88
-    $newParts['path'] = $path;
89
-    if ($delta['query']) {
90
-        $newParts['query'] = $delta['query'];
91
-    } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) {
92
-        // Keep the old query if host and path didn't change
93
-        $newParts['query'] = $base['query'];
94
-    }
95
-    if ($delta['fragment']) {
96
-        $newParts['fragment'] = $delta['fragment'];
97
-    }
98
-    return build($newParts);
25
+	$base = parse($basePath);
26
+	$delta = parse($newPath);
27
+
28
+	$pick = function($part) use ($base, $delta) {
29
+
30
+		if ($delta[$part]) {
31
+			return $delta[$part];
32
+		} elseif ($base[$part]) {
33
+			return $base[$part];
34
+		}
35
+		return null;
36
+
37
+	};
38
+
39
+	// If the new path defines a scheme, it's absolute and we can just return
40
+	// that.
41
+	if ($delta['scheme']) {
42
+		return build($delta);
43
+	}
44
+
45
+	$newParts = [];
46
+
47
+	$newParts['scheme'] = $pick('scheme');
48
+	$newParts['host']   = $pick('host');
49
+	$newParts['port']   = $pick('port');
50
+
51
+	$path = '';
52
+	if ($delta['path']) {
53
+		// If the path starts with a slash
54
+		if ($delta['path'][0] === '/') {
55
+			$path = $delta['path'];
56
+		} else {
57
+			// Removing last component from base path.
58
+			$path = $base['path'];
59
+			if (strpos($path, '/') !== false) {
60
+				$path = substr($path, 0, strrpos($path, '/'));
61
+			}
62
+			$path .= '/' . $delta['path'];
63
+		}
64
+	} else {
65
+		$path = $base['path'] ?: '/';
66
+	}
67
+	// Removing .. and .
68
+	$pathParts = explode('/', $path);
69
+	$newPathParts = [];
70
+	foreach ($pathParts as $pathPart) {
71
+
72
+		switch ($pathPart) {
73
+			//case '' :
74
+			case '.' :
75
+				break;
76
+			case '..' :
77
+				array_pop($newPathParts);
78
+				break;
79
+			default :
80
+				$newPathParts[] = $pathPart;
81
+				break;
82
+		}
83
+	}
84
+
85
+	$path = implode('/', $newPathParts);
86
+
87
+	// If the source url ended with a /, we want to preserve that.
88
+	$newParts['path'] = $path;
89
+	if ($delta['query']) {
90
+		$newParts['query'] = $delta['query'];
91
+	} elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) {
92
+		// Keep the old query if host and path didn't change
93
+		$newParts['query'] = $base['query'];
94
+	}
95
+	if ($delta['fragment']) {
96
+		$newParts['fragment'] = $delta['fragment'];
97
+	}
98
+	return build($newParts);
99 99
 
100 100
 }
101 101
 
@@ -113,55 +113,55 @@  discard block
 block discarded – undo
113 113
  */
114 114
 function normalize($uri) {
115 115
 
116
-    $parts = parse($uri);
117
-
118
-    if (!empty($parts['path'])) {
119
-        $pathParts = explode('/', ltrim($parts['path'], '/'));
120
-        $newPathParts = [];
121
-        foreach ($pathParts as $pathPart) {
122
-            switch ($pathPart) {
123
-                case '.':
124
-                    // skip
125
-                    break;
126
-                case '..' :
127
-                    // One level up in the hierarchy
128
-                    array_pop($newPathParts);
129
-                    break;
130
-                default :
131
-                    // Ensuring that everything is correctly percent-encoded.
132
-                    $newPathParts[] = rawurlencode(rawurldecode($pathPart));
133
-                    break;
134
-            }
135
-        }
136
-        $parts['path'] = '/' . implode('/', $newPathParts);
137
-    }
138
-
139
-    if ($parts['scheme']) {
140
-        $parts['scheme'] = strtolower($parts['scheme']);
141
-        $defaultPorts = [
142
-            'http'  => '80',
143
-            'https' => '443',
144
-        ];
145
-
146
-        if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) {
147
-            // Removing default ports.
148
-            unset($parts['port']);
149
-        }
150
-        // A few HTTP specific rules.
151
-        switch ($parts['scheme']) {
152
-            case 'http' :
153
-            case 'https' :
154
-                if (empty($parts['path'])) {
155
-                    // An empty path is equivalent to / in http.
156
-                    $parts['path'] = '/';
157
-                }
158
-                break;
159
-        }
160
-    }
161
-
162
-    if ($parts['host']) $parts['host'] = strtolower($parts['host']);
163
-
164
-    return build($parts);
116
+	$parts = parse($uri);
117
+
118
+	if (!empty($parts['path'])) {
119
+		$pathParts = explode('/', ltrim($parts['path'], '/'));
120
+		$newPathParts = [];
121
+		foreach ($pathParts as $pathPart) {
122
+			switch ($pathPart) {
123
+				case '.':
124
+					// skip
125
+					break;
126
+				case '..' :
127
+					// One level up in the hierarchy
128
+					array_pop($newPathParts);
129
+					break;
130
+				default :
131
+					// Ensuring that everything is correctly percent-encoded.
132
+					$newPathParts[] = rawurlencode(rawurldecode($pathPart));
133
+					break;
134
+			}
135
+		}
136
+		$parts['path'] = '/' . implode('/', $newPathParts);
137
+	}
138
+
139
+	if ($parts['scheme']) {
140
+		$parts['scheme'] = strtolower($parts['scheme']);
141
+		$defaultPorts = [
142
+			'http'  => '80',
143
+			'https' => '443',
144
+		];
145
+
146
+		if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) {
147
+			// Removing default ports.
148
+			unset($parts['port']);
149
+		}
150
+		// A few HTTP specific rules.
151
+		switch ($parts['scheme']) {
152
+			case 'http' :
153
+			case 'https' :
154
+				if (empty($parts['path'])) {
155
+					// An empty path is equivalent to / in http.
156
+					$parts['path'] = '/';
157
+				}
158
+				break;
159
+		}
160
+	}
161
+
162
+	if ($parts['host']) $parts['host'] = strtolower($parts['host']);
163
+
164
+	return build($parts);
165 165
 
166 166
 }
167 167
 
@@ -180,29 +180,29 @@  discard block
 block discarded – undo
180 180
  */
181 181
 function parse($uri) {
182 182
 
183
-    // Normally a URI must be ASCII, however. However, often it's not and
184
-    // parse_url might corrupt these strings.
185
-    //
186
-    // For that reason we take any non-ascii characters from the uri and
187
-    // uriencode them first.
188
-    $uri = preg_replace_callback(
189
-        '/[^[:ascii:]]/u',
190
-        function($matches) {
191
-            return rawurlencode($matches[0]);
192
-        },
193
-        $uri
194
-    );
195
-
196
-    return
197
-        parse_url($uri) + [
198
-            'scheme'   => null,
199
-            'host'     => null,
200
-            'path'     => null,
201
-            'port'     => null,
202
-            'user'     => null,
203
-            'query'    => null,
204
-            'fragment' => null,
205
-        ];
183
+	// Normally a URI must be ASCII, however. However, often it's not and
184
+	// parse_url might corrupt these strings.
185
+	//
186
+	// For that reason we take any non-ascii characters from the uri and
187
+	// uriencode them first.
188
+	$uri = preg_replace_callback(
189
+		'/[^[:ascii:]]/u',
190
+		function($matches) {
191
+			return rawurlencode($matches[0]);
192
+		},
193
+		$uri
194
+	);
195
+
196
+	return
197
+		parse_url($uri) + [
198
+			'scheme'   => null,
199
+			'host'     => null,
200
+			'path'     => null,
201
+			'port'     => null,
202
+			'user'     => null,
203
+			'query'    => null,
204
+			'fragment' => null,
205
+		];
206 206
 
207 207
 }
208 208
 
@@ -215,41 +215,41 @@  discard block
 block discarded – undo
215 215
  */
216 216
 function build(array $parts) {
217 217
 
218
-    $uri = '';
219
-
220
-    $authority = '';
221
-    if (!empty($parts['host'])) {
222
-        $authority = $parts['host'];
223
-        if (!empty($parts['user'])) {
224
-            $authority = $parts['user'] . '@' . $authority;
225
-        }
226
-        if (!empty($parts['port'])) {
227
-            $authority = $authority . ':' . $parts['port'];
228
-        }
229
-    }
230
-
231
-    if (!empty($parts['scheme'])) {
232
-        // If there's a scheme, there's also a host.
233
-        $uri = $parts['scheme'] . ':';
234
-
235
-    }
236
-    if ($authority) {
237
-        // No scheme, but there is a host.
238
-        $uri .= '//' . $authority;
239
-
240
-    }
241
-
242
-    if (!empty($parts['path'])) {
243
-        $uri .= $parts['path'];
244
-    }
245
-    if (!empty($parts['query'])) {
246
-        $uri .= '?' . $parts['query'];
247
-    }
248
-    if (!empty($parts['fragment'])) {
249
-        $uri .= '#' . $parts['fragment'];
250
-    }
251
-
252
-    return $uri;
218
+	$uri = '';
219
+
220
+	$authority = '';
221
+	if (!empty($parts['host'])) {
222
+		$authority = $parts['host'];
223
+		if (!empty($parts['user'])) {
224
+			$authority = $parts['user'] . '@' . $authority;
225
+		}
226
+		if (!empty($parts['port'])) {
227
+			$authority = $authority . ':' . $parts['port'];
228
+		}
229
+	}
230
+
231
+	if (!empty($parts['scheme'])) {
232
+		// If there's a scheme, there's also a host.
233
+		$uri = $parts['scheme'] . ':';
234
+
235
+	}
236
+	if ($authority) {
237
+		// No scheme, but there is a host.
238
+		$uri .= '//' . $authority;
239
+
240
+	}
241
+
242
+	if (!empty($parts['path'])) {
243
+		$uri .= $parts['path'];
244
+	}
245
+	if (!empty($parts['query'])) {
246
+		$uri .= '?' . $parts['query'];
247
+	}
248
+	if (!empty($parts['fragment'])) {
249
+		$uri .= '#' . $parts['fragment'];
250
+	}
251
+
252
+	return $uri;
253 253
 
254 254
 }
255 255
 
@@ -273,10 +273,10 @@  discard block
 block discarded – undo
273 273
  */
274 274
 function split($path) {
275 275
 
276
-    $matches = [];
277
-    if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) {
278
-        return [$matches[1], $matches[2]];
279
-    }
280
-    return [null,null];
276
+	$matches = [];
277
+	if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) {
278
+		return [$matches[1], $matches[2]];
279
+	}
280
+	return [null,null];
281 281
 
282 282
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/ICSExportPlugin.php 3 patches
Unused Use Statements   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -2,13 +2,13 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\CalDAV;
4 4
 
5
+use DateTime;
5 6
 use DateTimeZone;
6 7
 use Sabre\DAV;
7
-use Sabre\VObject;
8
+use Sabre\DAV\Exception\BadRequest;
8 9
 use Sabre\HTTP\RequestInterface;
9 10
 use Sabre\HTTP\ResponseInterface;
10
-use Sabre\DAV\Exception\BadRequest;
11
-use DateTime;
11
+use Sabre\VObject;
12 12
 
13 13
 /**
14 14
  * ICS Exporter
Please login to merge, or discard this patch.
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -43,7 +43,7 @@
 block discarded – undo
43 43
      *
44 44
      * @param RequestInterface $request
45 45
      * @param ResponseInterface $response
46
-     * @return bool
46
+     * @return null|false
47 47
      */
48 48
     function httpGet(RequestInterface $request, ResponseInterface $response) {
49 49
 
Please login to merge, or discard this patch.
Indentation   +316 added lines, -316 removed lines patch added patch discarded remove patch
@@ -46,321 +46,321 @@
 block discarded – undo
46 46
  */
47 47
 class ICSExportPlugin extends DAV\ServerPlugin {
48 48
 
49
-    /**
50
-     * Reference to Server class
51
-     *
52
-     * @var \Sabre\DAV\Server
53
-     */
54
-    protected $server;
55
-
56
-    /**
57
-     * Initializes the plugin and registers event handlers
58
-     *
59
-     * @param \Sabre\DAV\Server $server
60
-     * @return void
61
-     */
62
-    public function initialize(DAV\Server $server) {
63
-
64
-        $this->server = $server;
65
-        $server->on('method:GET', [$this, 'httpGet'], 90);
66
-        $server->on('browserButtonActions', function($path, $node, &$actions) {
67
-            if ($node instanceof ICalendar) {
68
-                $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
69
-            }
70
-        });
71
-
72
-    }
73
-
74
-    /**
75
-     * Intercepts GET requests on calendar urls ending with ?export.
76
-     *
77
-     * @param RequestInterface $request
78
-     * @param ResponseInterface $response
79
-     * @return bool
80
-     */
81
-    public function httpGet(RequestInterface $request, ResponseInterface $response) {
82
-
83
-        $queryParams = $request->getQueryParameters();
84
-        if (!array_key_exists('export', $queryParams)) return;
85
-
86
-        $path = $request->getPath();
87
-
88
-        $node = $this->server->getProperties($path, [
89
-            '{DAV:}resourcetype',
90
-            '{DAV:}displayname',
91
-            '{http://sabredav.org/ns}sync-token',
92
-            '{DAV:}sync-token',
93
-            '{http://apple.com/ns/ical/}calendar-color',
94
-        ]);
95
-
96
-        if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
97
-            return;
98
-        }
99
-        // Marking the transactionType, for logging purposes.
100
-        $this->server->transactionType = 'get-calendar-export';
101
-
102
-        $properties = $node;
103
-
104
-        $start = null;
105
-        $end = null;
106
-        $expand = false;
107
-        $componentType = false;
108
-        if (isset($queryParams['start'])) {
109
-            if (!ctype_digit($queryParams['start'])) {
110
-                throw new BadRequest('The start= parameter must contain a unix timestamp');
111
-            }
112
-            $start = DateTime::createFromFormat('U', $queryParams['start']);
113
-        }
114
-        if (isset($queryParams['end'])) {
115
-            if (!ctype_digit($queryParams['end'])) {
116
-                throw new BadRequest('The end= parameter must contain a unix timestamp');
117
-            }
118
-            $end = DateTime::createFromFormat('U', $queryParams['end']);
119
-        }
120
-        if (isset($queryParams['expand']) && !!$queryParams['expand']) {
121
-            if (!$start || !$end) {
122
-                throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
123
-            }
124
-            $expand = true;
125
-            $componentType = 'VEVENT';
126
-        }
127
-        if (isset($queryParams['componentType'])) {
128
-            if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
129
-                throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
130
-            }
131
-            $componentType = $queryParams['componentType'];
132
-        }
133
-
134
-        $format = \Sabre\HTTP\Util::Negotiate(
135
-            $request->getHeader('Accept'),
136
-            [
137
-                'text/calendar',
138
-                'application/calendar+json',
139
-            ]
140
-        );
141
-
142
-        if (isset($queryParams['accept'])) {
143
-            if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
144
-                $format = 'application/calendar+json';
145
-            }
146
-        }
147
-        if (!$format) {
148
-            $format = 'text/calendar';
149
-        }
150
-
151
-        $this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
152
-
153
-        // Returning false to break the event chain
154
-        return false;
155
-
156
-    }
157
-
158
-    /**
159
-     * This method is responsible for generating the actual, full response.
160
-     *
161
-     * @param string $path
162
-     * @param DateTime|null $start
163
-     * @param DateTime|null $end
164
-     * @param bool $expand
165
-     * @param string $componentType
166
-     * @param string $format
167
-     * @param array $properties
168
-     * @param ResponseInterface $response
169
-     */
170
-    protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
171
-
172
-        $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
173
-
174
-        $blobs = [];
175
-        if ($start || $end || $componentType) {
176
-
177
-            // If there was a start or end filter, we need to enlist
178
-            // calendarQuery for speed.
179
-            $calendarNode = $this->server->tree->getNodeForPath($path);
180
-            $queryResult = $calendarNode->calendarQuery([
181
-                'name'         => 'VCALENDAR',
182
-                'comp-filters' => [
183
-                    [
184
-                        'name'           => $componentType,
185
-                        'comp-filters'   => [],
186
-                        'prop-filters'   => [],
187
-                        'is-not-defined' => false,
188
-                        'time-range'     => [
189
-                            'start' => $start,
190
-                            'end'   => $end,
191
-                        ],
192
-                    ],
193
-                ],
194
-                'prop-filters'   => [],
195
-                'is-not-defined' => false,
196
-                'time-range'     => null,
197
-            ]);
198
-
199
-            // queryResult is just a list of base urls. We need to prefix the
200
-            // calendar path.
201
-            $queryResult = array_map(
202
-                function($item) use ($path) {
203
-                    return $path . '/' . $item;
204
-                },
205
-                $queryResult
206
-            );
207
-            $nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
208
-            unset($queryResult);
209
-
210
-        } else {
211
-            $nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
212
-        }
213
-
214
-        // Flattening the arrays
215
-        foreach ($nodes as $node) {
216
-            if (isset($node[200][$calDataProp])) {
217
-                $blobs[$node['href']] = $node[200][$calDataProp];
218
-            }
219
-        }
220
-        unset($nodes);
221
-
222
-        $mergedCalendar = $this->mergeObjects(
223
-            $properties,
224
-            $blobs
225
-        );
226
-
227
-        if ($expand) {
228
-            $calendarTimeZone = null;
229
-            // We're expanding, and for that we need to figure out the
230
-            // calendar's timezone.
231
-            $tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
232
-            $tzResult = $this->server->getProperties($path, [$tzProp]);
233
-            if (isset($tzResult[$tzProp])) {
234
-                // This property contains a VCALENDAR with a single
235
-                // VTIMEZONE.
236
-                $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
237
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
238
-                // Destroy circular references to PHP will GC the object.
239
-                $vtimezoneObj->destroy();
240
-                unset($vtimezoneObj);
241
-            } else {
242
-                // Defaulting to UTC.
243
-                $calendarTimeZone = new DateTimeZone('UTC');
244
-            }
245
-
246
-            $mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
247
-        }
248
-
249
-        $response->setHeader('Content-Type', $format);
250
-
251
-        switch ($format) {
252
-            case 'text/calendar' :
253
-                $mergedCalendar = $mergedCalendar->serialize();
254
-                break;
255
-            case 'application/calendar+json' :
256
-                $mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
257
-                break;
258
-        }
259
-
260
-        $response->setStatus(200);
261
-        $response->setBody($mergedCalendar);
262
-
263
-    }
264
-
265
-    /**
266
-     * Merges all calendar objects, and builds one big iCalendar blob.
267
-     *
268
-     * @param array $properties Some CalDAV properties
269
-     * @param array $inputObjects
270
-     * @return VObject\Component\VCalendar
271
-     */
272
-    public function mergeObjects(array $properties, array $inputObjects) {
273
-
274
-        $calendar = new VObject\Component\VCalendar();
275
-        $calendar->version = '2.0';
276
-        if (DAV\Server::$exposeVersion) {
277
-            $calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
278
-        } else {
279
-            $calendar->prodid = '-//SabreDAV//SabreDAV//EN';
280
-        }
281
-        if (isset($properties['{DAV:}displayname'])) {
282
-            $calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
283
-        }
284
-        if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
285
-            $calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
286
-        }
287
-
288
-        $collectedTimezones = [];
289
-
290
-        $timezones = [];
291
-        $objects = [];
292
-
293
-        foreach ($inputObjects as $href => $inputObject) {
294
-
295
-            $nodeComp = VObject\Reader::read($inputObject);
296
-
297
-            foreach ($nodeComp->children() as $child) {
298
-
299
-                switch ($child->name) {
300
-                    case 'VEVENT' :
301
-                    case 'VTODO' :
302
-                    case 'VJOURNAL' :
303
-                        $objects[] = clone $child;
304
-                        break;
305
-
306
-                    // VTIMEZONE is special, because we need to filter out the duplicates
307
-                    case 'VTIMEZONE' :
308
-                        // Naively just checking tzid.
309
-                        if (in_array((string)$child->TZID, $collectedTimezones)) continue;
310
-
311
-                        $timezones[] = clone $child;
312
-                        $collectedTimezones[] = $child->TZID;
313
-                        break;
314
-
315
-                }
316
-
317
-            }
318
-            // Destroy circular references to PHP will GC the object.
319
-            $nodeComp->destroy();
320
-            unset($nodeComp);
321
-
322
-        }
323
-
324
-        foreach ($timezones as $tz) $calendar->add($tz);
325
-        foreach ($objects as $obj) $calendar->add($obj);
326
-
327
-        return $calendar;
328
-
329
-    }
330
-
331
-    /**
332
-     * Returns a plugin name.
333
-     *
334
-     * Using this name other plugins will be able to access other plugins
335
-     * using \Sabre\DAV\Server::getPlugin
336
-     *
337
-     * @return string
338
-     */
339
-    public function getPluginName() {
340
-
341
-        return 'ics-export';
342
-
343
-    }
344
-
345
-    /**
346
-     * Returns a bunch of meta-data about the plugin.
347
-     *
348
-     * Providing this information is optional, and is mainly displayed by the
349
-     * Browser plugin.
350
-     *
351
-     * The description key in the returned array may contain html and will not
352
-     * be sanitized.
353
-     *
354
-     * @return array
355
-     */
356
-    public function getPluginInfo() {
357
-
358
-        return [
359
-            'name'        => $this->getPluginName(),
360
-            'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
361
-            'link'        => 'http://sabre.io/dav/ics-export-plugin/',
362
-        ];
363
-
364
-    }
49
+	/**
50
+	 * Reference to Server class
51
+	 *
52
+	 * @var \Sabre\DAV\Server
53
+	 */
54
+	protected $server;
55
+
56
+	/**
57
+	 * Initializes the plugin and registers event handlers
58
+	 *
59
+	 * @param \Sabre\DAV\Server $server
60
+	 * @return void
61
+	 */
62
+	public function initialize(DAV\Server $server) {
63
+
64
+		$this->server = $server;
65
+		$server->on('method:GET', [$this, 'httpGet'], 90);
66
+		$server->on('browserButtonActions', function($path, $node, &$actions) {
67
+			if ($node instanceof ICalendar) {
68
+				$actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="calendar"></span></a>';
69
+			}
70
+		});
71
+
72
+	}
73
+
74
+	/**
75
+	 * Intercepts GET requests on calendar urls ending with ?export.
76
+	 *
77
+	 * @param RequestInterface $request
78
+	 * @param ResponseInterface $response
79
+	 * @return bool
80
+	 */
81
+	public function httpGet(RequestInterface $request, ResponseInterface $response) {
82
+
83
+		$queryParams = $request->getQueryParameters();
84
+		if (!array_key_exists('export', $queryParams)) return;
85
+
86
+		$path = $request->getPath();
87
+
88
+		$node = $this->server->getProperties($path, [
89
+			'{DAV:}resourcetype',
90
+			'{DAV:}displayname',
91
+			'{http://sabredav.org/ns}sync-token',
92
+			'{DAV:}sync-token',
93
+			'{http://apple.com/ns/ical/}calendar-color',
94
+		]);
95
+
96
+		if (!isset($node['{DAV:}resourcetype']) || !$node['{DAV:}resourcetype']->is('{' . Plugin::NS_CALDAV . '}calendar')) {
97
+			return;
98
+		}
99
+		// Marking the transactionType, for logging purposes.
100
+		$this->server->transactionType = 'get-calendar-export';
101
+
102
+		$properties = $node;
103
+
104
+		$start = null;
105
+		$end = null;
106
+		$expand = false;
107
+		$componentType = false;
108
+		if (isset($queryParams['start'])) {
109
+			if (!ctype_digit($queryParams['start'])) {
110
+				throw new BadRequest('The start= parameter must contain a unix timestamp');
111
+			}
112
+			$start = DateTime::createFromFormat('U', $queryParams['start']);
113
+		}
114
+		if (isset($queryParams['end'])) {
115
+			if (!ctype_digit($queryParams['end'])) {
116
+				throw new BadRequest('The end= parameter must contain a unix timestamp');
117
+			}
118
+			$end = DateTime::createFromFormat('U', $queryParams['end']);
119
+		}
120
+		if (isset($queryParams['expand']) && !!$queryParams['expand']) {
121
+			if (!$start || !$end) {
122
+				throw new BadRequest('If you\'d like to expand recurrences, you must specify both a start= and end= parameter.');
123
+			}
124
+			$expand = true;
125
+			$componentType = 'VEVENT';
126
+		}
127
+		if (isset($queryParams['componentType'])) {
128
+			if (!in_array($queryParams['componentType'], ['VEVENT', 'VTODO', 'VJOURNAL'])) {
129
+				throw new BadRequest('You are not allowed to search for components of type: ' . $queryParams['componentType'] . ' here');
130
+			}
131
+			$componentType = $queryParams['componentType'];
132
+		}
133
+
134
+		$format = \Sabre\HTTP\Util::Negotiate(
135
+			$request->getHeader('Accept'),
136
+			[
137
+				'text/calendar',
138
+				'application/calendar+json',
139
+			]
140
+		);
141
+
142
+		if (isset($queryParams['accept'])) {
143
+			if ($queryParams['accept'] === 'application/calendar+json' || $queryParams['accept'] === 'jcal') {
144
+				$format = 'application/calendar+json';
145
+			}
146
+		}
147
+		if (!$format) {
148
+			$format = 'text/calendar';
149
+		}
150
+
151
+		$this->generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, $response);
152
+
153
+		// Returning false to break the event chain
154
+		return false;
155
+
156
+	}
157
+
158
+	/**
159
+	 * This method is responsible for generating the actual, full response.
160
+	 *
161
+	 * @param string $path
162
+	 * @param DateTime|null $start
163
+	 * @param DateTime|null $end
164
+	 * @param bool $expand
165
+	 * @param string $componentType
166
+	 * @param string $format
167
+	 * @param array $properties
168
+	 * @param ResponseInterface $response
169
+	 */
170
+	protected function generateResponse($path, $start, $end, $expand, $componentType, $format, $properties, ResponseInterface $response) {
171
+
172
+		$calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data';
173
+
174
+		$blobs = [];
175
+		if ($start || $end || $componentType) {
176
+
177
+			// If there was a start or end filter, we need to enlist
178
+			// calendarQuery for speed.
179
+			$calendarNode = $this->server->tree->getNodeForPath($path);
180
+			$queryResult = $calendarNode->calendarQuery([
181
+				'name'         => 'VCALENDAR',
182
+				'comp-filters' => [
183
+					[
184
+						'name'           => $componentType,
185
+						'comp-filters'   => [],
186
+						'prop-filters'   => [],
187
+						'is-not-defined' => false,
188
+						'time-range'     => [
189
+							'start' => $start,
190
+							'end'   => $end,
191
+						],
192
+					],
193
+				],
194
+				'prop-filters'   => [],
195
+				'is-not-defined' => false,
196
+				'time-range'     => null,
197
+			]);
198
+
199
+			// queryResult is just a list of base urls. We need to prefix the
200
+			// calendar path.
201
+			$queryResult = array_map(
202
+				function($item) use ($path) {
203
+					return $path . '/' . $item;
204
+				},
205
+				$queryResult
206
+			);
207
+			$nodes = $this->server->getPropertiesForMultiplePaths($queryResult, [$calDataProp]);
208
+			unset($queryResult);
209
+
210
+		} else {
211
+			$nodes = $this->server->getPropertiesForPath($path, [$calDataProp], 1);
212
+		}
213
+
214
+		// Flattening the arrays
215
+		foreach ($nodes as $node) {
216
+			if (isset($node[200][$calDataProp])) {
217
+				$blobs[$node['href']] = $node[200][$calDataProp];
218
+			}
219
+		}
220
+		unset($nodes);
221
+
222
+		$mergedCalendar = $this->mergeObjects(
223
+			$properties,
224
+			$blobs
225
+		);
226
+
227
+		if ($expand) {
228
+			$calendarTimeZone = null;
229
+			// We're expanding, and for that we need to figure out the
230
+			// calendar's timezone.
231
+			$tzProp = '{' . Plugin::NS_CALDAV . '}calendar-timezone';
232
+			$tzResult = $this->server->getProperties($path, [$tzProp]);
233
+			if (isset($tzResult[$tzProp])) {
234
+				// This property contains a VCALENDAR with a single
235
+				// VTIMEZONE.
236
+				$vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]);
237
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
238
+				// Destroy circular references to PHP will GC the object.
239
+				$vtimezoneObj->destroy();
240
+				unset($vtimezoneObj);
241
+			} else {
242
+				// Defaulting to UTC.
243
+				$calendarTimeZone = new DateTimeZone('UTC');
244
+			}
245
+
246
+			$mergedCalendar = $mergedCalendar->expand($start, $end, $calendarTimeZone);
247
+		}
248
+
249
+		$response->setHeader('Content-Type', $format);
250
+
251
+		switch ($format) {
252
+			case 'text/calendar' :
253
+				$mergedCalendar = $mergedCalendar->serialize();
254
+				break;
255
+			case 'application/calendar+json' :
256
+				$mergedCalendar = json_encode($mergedCalendar->jsonSerialize());
257
+				break;
258
+		}
259
+
260
+		$response->setStatus(200);
261
+		$response->setBody($mergedCalendar);
262
+
263
+	}
264
+
265
+	/**
266
+	 * Merges all calendar objects, and builds one big iCalendar blob.
267
+	 *
268
+	 * @param array $properties Some CalDAV properties
269
+	 * @param array $inputObjects
270
+	 * @return VObject\Component\VCalendar
271
+	 */
272
+	public function mergeObjects(array $properties, array $inputObjects) {
273
+
274
+		$calendar = new VObject\Component\VCalendar();
275
+		$calendar->version = '2.0';
276
+		if (DAV\Server::$exposeVersion) {
277
+			$calendar->prodid = '-//SabreDAV//SabreDAV ' . DAV\Version::VERSION . '//EN';
278
+		} else {
279
+			$calendar->prodid = '-//SabreDAV//SabreDAV//EN';
280
+		}
281
+		if (isset($properties['{DAV:}displayname'])) {
282
+			$calendar->{'X-WR-CALNAME'} = $properties['{DAV:}displayname'];
283
+		}
284
+		if (isset($properties['{http://apple.com/ns/ical/}calendar-color'])) {
285
+			$calendar->{'X-APPLE-CALENDAR-COLOR'} = $properties['{http://apple.com/ns/ical/}calendar-color'];
286
+		}
287
+
288
+		$collectedTimezones = [];
289
+
290
+		$timezones = [];
291
+		$objects = [];
292
+
293
+		foreach ($inputObjects as $href => $inputObject) {
294
+
295
+			$nodeComp = VObject\Reader::read($inputObject);
296
+
297
+			foreach ($nodeComp->children() as $child) {
298
+
299
+				switch ($child->name) {
300
+					case 'VEVENT' :
301
+					case 'VTODO' :
302
+					case 'VJOURNAL' :
303
+						$objects[] = clone $child;
304
+						break;
305
+
306
+					// VTIMEZONE is special, because we need to filter out the duplicates
307
+					case 'VTIMEZONE' :
308
+						// Naively just checking tzid.
309
+						if (in_array((string)$child->TZID, $collectedTimezones)) continue;
310
+
311
+						$timezones[] = clone $child;
312
+						$collectedTimezones[] = $child->TZID;
313
+						break;
314
+
315
+				}
316
+
317
+			}
318
+			// Destroy circular references to PHP will GC the object.
319
+			$nodeComp->destroy();
320
+			unset($nodeComp);
321
+
322
+		}
323
+
324
+		foreach ($timezones as $tz) $calendar->add($tz);
325
+		foreach ($objects as $obj) $calendar->add($obj);
326
+
327
+		return $calendar;
328
+
329
+	}
330
+
331
+	/**
332
+	 * Returns a plugin name.
333
+	 *
334
+	 * Using this name other plugins will be able to access other plugins
335
+	 * using \Sabre\DAV\Server::getPlugin
336
+	 *
337
+	 * @return string
338
+	 */
339
+	public function getPluginName() {
340
+
341
+		return 'ics-export';
342
+
343
+	}
344
+
345
+	/**
346
+	 * Returns a bunch of meta-data about the plugin.
347
+	 *
348
+	 * Providing this information is optional, and is mainly displayed by the
349
+	 * Browser plugin.
350
+	 *
351
+	 * The description key in the returned array may contain html and will not
352
+	 * be sanitized.
353
+	 *
354
+	 * @return array
355
+	 */
356
+	public function getPluginInfo() {
357
+
358
+		return [
359
+			'name'        => $this->getPluginName(),
360
+			'description' => 'Adds the ability to export CalDAV calendars as a single iCalendar file.',
361
+			'link'        => 'http://sabre.io/dav/ics-export-plugin/',
362
+		];
363
+
364
+	}
365 365
 
366 366
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/Event/coroutine.php 2 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\Event;
4 4
 
5
-use Generator;
6 5
 use Exception;
6
+use Generator;
7 7
 
8 8
 /**
9 9
  * Turn asynchronous promise-based code into something that looks synchronous
Please login to merge, or discard this patch.
Indentation   +60 added lines, -60 removed lines patch added patch discarded remove patch
@@ -46,75 +46,75 @@
 block discarded – undo
46 46
  */
47 47
 function coroutine(callable $gen) {
48 48
 
49
-    $generator = $gen();
50
-    if (!$generator instanceof Generator) {
51
-        throw new \InvalidArgumentException('You must pass a generator function');
52
-    }
49
+	$generator = $gen();
50
+	if (!$generator instanceof Generator) {
51
+		throw new \InvalidArgumentException('You must pass a generator function');
52
+	}
53 53
 
54
-    // This is the value we're returning.
55
-    $promise = new Promise();
54
+	// This is the value we're returning.
55
+	$promise = new Promise();
56 56
 
57
-    $lastYieldResult = null;
57
+	$lastYieldResult = null;
58 58
 
59
-    /**
60
-     * So tempted to use the mythical y-combinator here, but it's not needed in
61
-     * PHP.
62
-     */
63
-    $advanceGenerator = function() use (&$advanceGenerator, $generator, $promise, &$lastYieldResult) {
59
+	/**
60
+	 * So tempted to use the mythical y-combinator here, but it's not needed in
61
+	 * PHP.
62
+	 */
63
+	$advanceGenerator = function() use (&$advanceGenerator, $generator, $promise, &$lastYieldResult) {
64 64
 
65
-        while ($generator->valid()) {
65
+		while ($generator->valid()) {
66 66
 
67
-            $yieldedValue = $generator->current();
68
-            if ($yieldedValue instanceof Promise) {
69
-                $yieldedValue->then(
70
-                    function($value) use ($generator, &$advanceGenerator, &$lastYieldResult) {
71
-                        $lastYieldResult = $value;
72
-                        $generator->send($value);
73
-                        $advanceGenerator();
74
-                    },
75
-                    function($reason) use ($generator, $advanceGenerator) {
76
-                        if ($reason instanceof Exception) {
77
-                            $generator->throw($reason);
78
-                        } elseif (is_scalar($reason)) {
79
-                            $generator->throw(new Exception($reason));
80
-                        } else {
81
-                            $type = is_object($reason) ? get_class($reason) : gettype($reason);
82
-                            $generator->throw(new Exception('Promise was rejected with reason of type: ' . $type));
83
-                        }
84
-                        $advanceGenerator();
85
-                    }
86
-                )->error(function($reason) use ($promise) {
87
-                    // This error handler would be called, if something in the
88
-                    // generator throws an exception, and it's not caught
89
-                    // locally.
90
-                    $promise->reject($reason);
91
-                });
92
-                // We need to break out of the loop, because $advanceGenerator
93
-                // will be called asynchronously when the promise has a result.
94
-                break;
95
-            } else {
96
-                // If the value was not a promise, we'll just let it pass through.
97
-                $lastYieldResult = $yieldedValue;
98
-                $generator->send($yieldedValue);
99
-            }
67
+			$yieldedValue = $generator->current();
68
+			if ($yieldedValue instanceof Promise) {
69
+				$yieldedValue->then(
70
+					function($value) use ($generator, &$advanceGenerator, &$lastYieldResult) {
71
+						$lastYieldResult = $value;
72
+						$generator->send($value);
73
+						$advanceGenerator();
74
+					},
75
+					function($reason) use ($generator, $advanceGenerator) {
76
+						if ($reason instanceof Exception) {
77
+							$generator->throw($reason);
78
+						} elseif (is_scalar($reason)) {
79
+							$generator->throw(new Exception($reason));
80
+						} else {
81
+							$type = is_object($reason) ? get_class($reason) : gettype($reason);
82
+							$generator->throw(new Exception('Promise was rejected with reason of type: ' . $type));
83
+						}
84
+						$advanceGenerator();
85
+					}
86
+				)->error(function($reason) use ($promise) {
87
+					// This error handler would be called, if something in the
88
+					// generator throws an exception, and it's not caught
89
+					// locally.
90
+					$promise->reject($reason);
91
+				});
92
+				// We need to break out of the loop, because $advanceGenerator
93
+				// will be called asynchronously when the promise has a result.
94
+				break;
95
+			} else {
96
+				// If the value was not a promise, we'll just let it pass through.
97
+				$lastYieldResult = $yieldedValue;
98
+				$generator->send($yieldedValue);
99
+			}
100 100
 
101
-        }
101
+		}
102 102
 
103
-        // If the generator is at the end, and we didn't run into an exception,
104
-        // we can fullfill the promise with the last thing that was yielded to
105
-        // us.
106
-        if (!$generator->valid() && $promise->state === Promise::PENDING) {
107
-            $promise->fulfill($lastYieldResult);
108
-        }
103
+		// If the generator is at the end, and we didn't run into an exception,
104
+		// we can fullfill the promise with the last thing that was yielded to
105
+		// us.
106
+		if (!$generator->valid() && $promise->state === Promise::PENDING) {
107
+			$promise->fulfill($lastYieldResult);
108
+		}
109 109
 
110
-    };
110
+	};
111 111
 
112
-    try {
113
-        $advanceGenerator();
114
-    } catch (Exception $e) {
115
-        $promise->reject($e);
116
-    }
112
+	try {
113
+		$advanceGenerator();
114
+	} catch (Exception $e) {
115
+		$promise->reject($e);
116
+	}
117 117
 
118
-    return $promise;
118
+	return $promise;
119 119
 
120 120
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Cli.php 2 patches
Unused Use Statements   +1 added lines, -2 removed lines patch added patch discarded remove patch
@@ -2,8 +2,7 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\VObject;
4 4
 
5
-use
6
-    InvalidArgumentException;
5
+use InvalidArgumentException;
7 6
 
8 7
 /**
9 8
  * This is the CLI interface for sabre-vobject.
Please login to merge, or discard this patch.
Indentation   +748 added lines, -748 removed lines patch added patch discarded remove patch
@@ -3,7 +3,7 @@  discard block
 block discarded – undo
3 3
 namespace Sabre\VObject;
4 4
 
5 5
 use
6
-    InvalidArgumentException;
6
+	InvalidArgumentException;
7 7
 
8 8
 /**
9 9
  * This is the CLI interface for sabre-vobject.
@@ -14,758 +14,758 @@  discard block
 block discarded – undo
14 14
  */
15 15
 class Cli {
16 16
 
17
-    /**
18
-     * No output.
19
-     *
20
-     * @var bool
21
-     */
22
-    protected $quiet = false;
23
-
24
-    /**
25
-     * Help display.
26
-     *
27
-     * @var bool
28
-     */
29
-    protected $showHelp = false;
30
-
31
-    /**
32
-     * Wether to spit out 'mimedir' or 'json' format.
33
-     *
34
-     * @var string
35
-     */
36
-    protected $format;
37
-
38
-    /**
39
-     * JSON pretty print.
40
-     *
41
-     * @var bool
42
-     */
43
-    protected $pretty;
44
-
45
-    /**
46
-     * Source file.
47
-     *
48
-     * @var string
49
-     */
50
-    protected $inputPath;
51
-
52
-    /**
53
-     * Destination file.
54
-     *
55
-     * @var string
56
-     */
57
-    protected $outputPath;
58
-
59
-    /**
60
-     * output stream.
61
-     *
62
-     * @var resource
63
-     */
64
-    protected $stdout;
65
-
66
-    /**
67
-     * stdin.
68
-     *
69
-     * @var resource
70
-     */
71
-    protected $stdin;
72
-
73
-    /**
74
-     * stderr.
75
-     *
76
-     * @var resource
77
-     */
78
-    protected $stderr;
79
-
80
-    /**
81
-     * Input format (one of json or mimedir).
82
-     *
83
-     * @var string
84
-     */
85
-    protected $inputFormat;
86
-
87
-    /**
88
-     * Makes the parser less strict.
89
-     *
90
-     * @var bool
91
-     */
92
-    protected $forgiving = false;
93
-
94
-    /**
95
-     * Main function.
96
-     *
97
-     * @return int
98
-     */
99
-    public function main(array $argv) {
100
-
101
-        // @codeCoverageIgnoreStart
102
-        // We cannot easily test this, so we'll skip it. Pretty basic anyway.
103
-
104
-        if (!$this->stderr) {
105
-            $this->stderr = fopen('php://stderr', 'w');
106
-        }
107
-        if (!$this->stdout) {
108
-            $this->stdout = fopen('php://stdout', 'w');
109
-        }
110
-        if (!$this->stdin) {
111
-            $this->stdin = fopen('php://stdin', 'r');
112
-        }
113
-
114
-        // @codeCoverageIgnoreEnd
115
-
116
-
117
-        try {
118
-
119
-            list($options, $positional) = $this->parseArguments($argv);
120
-
121
-            if (isset($options['q'])) {
122
-                $this->quiet = true;
123
-            }
124
-            $this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
125
-
126
-            foreach ($options as $name => $value) {
127
-
128
-                switch ($name) {
129
-
130
-                    case 'q' :
131
-                        // Already handled earlier.
132
-                        break;
133
-                    case 'h' :
134
-                    case 'help' :
135
-                        $this->showHelp();
136
-                        return 0;
137
-                        break;
138
-                    case 'format' :
139
-                        switch ($value) {
140
-
141
-                            // jcard/jcal documents
142
-                            case 'jcard' :
143
-                            case 'jcal' :
144
-
145
-                            // specific document versions
146
-                            case 'vcard21' :
147
-                            case 'vcard30' :
148
-                            case 'vcard40' :
149
-                            case 'icalendar20' :
150
-
151
-                            // specific formats
152
-                            case 'json' :
153
-                            case 'mimedir' :
154
-
155
-                            // icalendar/vcad
156
-                            case 'icalendar' :
157
-                            case 'vcard' :
158
-                                $this->format = $value;
159
-                                break;
160
-
161
-                            default :
162
-                                throw new InvalidArgumentException('Unknown format: ' . $value);
163
-
164
-                        }
165
-                        break;
166
-                    case 'pretty' :
167
-                        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
168
-                            $this->pretty = true;
169
-                        }
170
-                        break;
171
-                    case 'forgiving' :
172
-                        $this->forgiving = true;
173
-                        break;
174
-                    case 'inputformat' :
175
-                        switch ($value) {
176
-                            // json formats
177
-                            case 'jcard' :
178
-                            case 'jcal' :
179
-                            case 'json' :
180
-                                $this->inputFormat = 'json';
181
-                                break;
182
-
183
-                            // mimedir formats
184
-                            case 'mimedir' :
185
-                            case 'icalendar' :
186
-                            case 'vcard' :
187
-                            case 'vcard21' :
188
-                            case 'vcard30' :
189
-                            case 'vcard40' :
190
-                            case 'icalendar20' :
191
-
192
-                                $this->inputFormat = 'mimedir';
193
-                                break;
194
-
195
-                            default :
196
-                                throw new InvalidArgumentException('Unknown format: ' . $value);
197
-
198
-                        }
199
-                        break;
200
-                    default :
201
-                        throw new InvalidArgumentException('Unknown option: ' . $name);
202
-
203
-                }
204
-
205
-            }
206
-
207
-            if (count($positional) === 0) {
208
-                $this->showHelp();
209
-                return 1;
210
-            }
211
-
212
-            if (count($positional) === 1) {
213
-                throw new InvalidArgumentException('Inputfile is a required argument');
214
-            }
215
-
216
-            if (count($positional) > 3) {
217
-                throw new InvalidArgumentException('Too many arguments');
218
-            }
219
-
220
-            if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
221
-                throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
222
-            }
223
-
224
-        } catch (InvalidArgumentException $e) {
225
-            $this->showHelp();
226
-            $this->log('Error: ' . $e->getMessage(), 'red');
227
-            return 1;
228
-        }
229
-
230
-        $command = $positional[0];
231
-
232
-        $this->inputPath = $positional[1];
233
-        $this->outputPath = isset($positional[2]) ? $positional[2] : '-';
234
-
235
-        if ($this->outputPath !== '-') {
236
-            $this->stdout = fopen($this->outputPath, 'w');
237
-        }
238
-
239
-        if (!$this->inputFormat) {
240
-            if (substr($this->inputPath, -5) === '.json') {
241
-                $this->inputFormat = 'json';
242
-            } else {
243
-                $this->inputFormat = 'mimedir';
244
-            }
245
-        }
246
-        if (!$this->format) {
247
-            if (substr($this->outputPath, -5) === '.json') {
248
-                $this->format = 'json';
249
-            } else {
250
-                $this->format = 'mimedir';
251
-            }
252
-        }
253
-
254
-
255
-        $realCode = 0;
256
-
257
-        try {
258
-
259
-            while ($input = $this->readInput()) {
260
-
261
-                $returnCode = $this->$command($input);
262
-                if ($returnCode !== 0) $realCode = $returnCode;
263
-
264
-            }
265
-
266
-        } catch (EofException $e) {
267
-            // end of file
268
-        } catch (\Exception $e) {
269
-            $this->log('Error: ' . $e->getMessage(), 'red');
270
-            return 2;
271
-        }
272
-
273
-        return $realCode;
274
-
275
-    }
276
-
277
-    /**
278
-     * Shows the help message.
279
-     *
280
-     * @return void
281
-     */
282
-    protected function showHelp() {
283
-
284
-        $this->log('Usage:', 'yellow');
285
-        $this->log("  vobject [options] command [arguments]");
286
-        $this->log('');
287
-        $this->log('Options:', 'yellow');
288
-        $this->log($this->colorize('green', '  -q            ') . "Don't output anything.");
289
-        $this->log($this->colorize('green', '  -help -h      ') . "Display this help message.");
290
-        $this->log($this->colorize('green', '  --format      ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
291
-        $this->log($this->colorize('green', '  --forgiving   ') . "Makes the parser less strict.");
292
-        $this->log("                vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
293
-        $this->log($this->colorize('green', '  --inputformat ') . "If the input format cannot be guessed from the extension, it");
294
-        $this->log("                must be specified here.");
295
-        // Only PHP 5.4 and up
296
-        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
297
-            $this->log($this->colorize('green', '  --pretty      ') . "json pretty-print.");
298
-        }
299
-        $this->log('');
300
-        $this->log('Commands:', 'yellow');
301
-        $this->log($this->colorize('green', '  validate') . ' source_file              Validates a file for correctness.');
302
-        $this->log($this->colorize('green', '  repair') . ' source_file [output_file]  Repairs a file.');
303
-        $this->log($this->colorize('green', '  convert') . ' source_file [output_file] Converts a file.');
304
-        $this->log($this->colorize('green', '  color') . ' source_file                 Colorize a file, useful for debbugging.');
305
-        $this->log(
306
-        <<<HELP
17
+	/**
18
+	 * No output.
19
+	 *
20
+	 * @var bool
21
+	 */
22
+	protected $quiet = false;
23
+
24
+	/**
25
+	 * Help display.
26
+	 *
27
+	 * @var bool
28
+	 */
29
+	protected $showHelp = false;
30
+
31
+	/**
32
+	 * Wether to spit out 'mimedir' or 'json' format.
33
+	 *
34
+	 * @var string
35
+	 */
36
+	protected $format;
37
+
38
+	/**
39
+	 * JSON pretty print.
40
+	 *
41
+	 * @var bool
42
+	 */
43
+	protected $pretty;
44
+
45
+	/**
46
+	 * Source file.
47
+	 *
48
+	 * @var string
49
+	 */
50
+	protected $inputPath;
51
+
52
+	/**
53
+	 * Destination file.
54
+	 *
55
+	 * @var string
56
+	 */
57
+	protected $outputPath;
58
+
59
+	/**
60
+	 * output stream.
61
+	 *
62
+	 * @var resource
63
+	 */
64
+	protected $stdout;
65
+
66
+	/**
67
+	 * stdin.
68
+	 *
69
+	 * @var resource
70
+	 */
71
+	protected $stdin;
72
+
73
+	/**
74
+	 * stderr.
75
+	 *
76
+	 * @var resource
77
+	 */
78
+	protected $stderr;
79
+
80
+	/**
81
+	 * Input format (one of json or mimedir).
82
+	 *
83
+	 * @var string
84
+	 */
85
+	protected $inputFormat;
86
+
87
+	/**
88
+	 * Makes the parser less strict.
89
+	 *
90
+	 * @var bool
91
+	 */
92
+	protected $forgiving = false;
93
+
94
+	/**
95
+	 * Main function.
96
+	 *
97
+	 * @return int
98
+	 */
99
+	public function main(array $argv) {
100
+
101
+		// @codeCoverageIgnoreStart
102
+		// We cannot easily test this, so we'll skip it. Pretty basic anyway.
103
+
104
+		if (!$this->stderr) {
105
+			$this->stderr = fopen('php://stderr', 'w');
106
+		}
107
+		if (!$this->stdout) {
108
+			$this->stdout = fopen('php://stdout', 'w');
109
+		}
110
+		if (!$this->stdin) {
111
+			$this->stdin = fopen('php://stdin', 'r');
112
+		}
113
+
114
+		// @codeCoverageIgnoreEnd
115
+
116
+
117
+		try {
118
+
119
+			list($options, $positional) = $this->parseArguments($argv);
120
+
121
+			if (isset($options['q'])) {
122
+				$this->quiet = true;
123
+			}
124
+			$this->log($this->colorize('green', "sabre/vobject ") . $this->colorize('yellow', Version::VERSION));
125
+
126
+			foreach ($options as $name => $value) {
127
+
128
+				switch ($name) {
129
+
130
+					case 'q' :
131
+						// Already handled earlier.
132
+						break;
133
+					case 'h' :
134
+					case 'help' :
135
+						$this->showHelp();
136
+						return 0;
137
+						break;
138
+					case 'format' :
139
+						switch ($value) {
140
+
141
+							// jcard/jcal documents
142
+							case 'jcard' :
143
+							case 'jcal' :
144
+
145
+							// specific document versions
146
+							case 'vcard21' :
147
+							case 'vcard30' :
148
+							case 'vcard40' :
149
+							case 'icalendar20' :
150
+
151
+							// specific formats
152
+							case 'json' :
153
+							case 'mimedir' :
154
+
155
+							// icalendar/vcad
156
+							case 'icalendar' :
157
+							case 'vcard' :
158
+								$this->format = $value;
159
+								break;
160
+
161
+							default :
162
+								throw new InvalidArgumentException('Unknown format: ' . $value);
163
+
164
+						}
165
+						break;
166
+					case 'pretty' :
167
+						if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
168
+							$this->pretty = true;
169
+						}
170
+						break;
171
+					case 'forgiving' :
172
+						$this->forgiving = true;
173
+						break;
174
+					case 'inputformat' :
175
+						switch ($value) {
176
+							// json formats
177
+							case 'jcard' :
178
+							case 'jcal' :
179
+							case 'json' :
180
+								$this->inputFormat = 'json';
181
+								break;
182
+
183
+							// mimedir formats
184
+							case 'mimedir' :
185
+							case 'icalendar' :
186
+							case 'vcard' :
187
+							case 'vcard21' :
188
+							case 'vcard30' :
189
+							case 'vcard40' :
190
+							case 'icalendar20' :
191
+
192
+								$this->inputFormat = 'mimedir';
193
+								break;
194
+
195
+							default :
196
+								throw new InvalidArgumentException('Unknown format: ' . $value);
197
+
198
+						}
199
+						break;
200
+					default :
201
+						throw new InvalidArgumentException('Unknown option: ' . $name);
202
+
203
+				}
204
+
205
+			}
206
+
207
+			if (count($positional) === 0) {
208
+				$this->showHelp();
209
+				return 1;
210
+			}
211
+
212
+			if (count($positional) === 1) {
213
+				throw new InvalidArgumentException('Inputfile is a required argument');
214
+			}
215
+
216
+			if (count($positional) > 3) {
217
+				throw new InvalidArgumentException('Too many arguments');
218
+			}
219
+
220
+			if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
221
+				throw new InvalidArgumentException('Uknown command: ' . $positional[0]);
222
+			}
223
+
224
+		} catch (InvalidArgumentException $e) {
225
+			$this->showHelp();
226
+			$this->log('Error: ' . $e->getMessage(), 'red');
227
+			return 1;
228
+		}
229
+
230
+		$command = $positional[0];
231
+
232
+		$this->inputPath = $positional[1];
233
+		$this->outputPath = isset($positional[2]) ? $positional[2] : '-';
234
+
235
+		if ($this->outputPath !== '-') {
236
+			$this->stdout = fopen($this->outputPath, 'w');
237
+		}
238
+
239
+		if (!$this->inputFormat) {
240
+			if (substr($this->inputPath, -5) === '.json') {
241
+				$this->inputFormat = 'json';
242
+			} else {
243
+				$this->inputFormat = 'mimedir';
244
+			}
245
+		}
246
+		if (!$this->format) {
247
+			if (substr($this->outputPath, -5) === '.json') {
248
+				$this->format = 'json';
249
+			} else {
250
+				$this->format = 'mimedir';
251
+			}
252
+		}
253
+
254
+
255
+		$realCode = 0;
256
+
257
+		try {
258
+
259
+			while ($input = $this->readInput()) {
260
+
261
+				$returnCode = $this->$command($input);
262
+				if ($returnCode !== 0) $realCode = $returnCode;
263
+
264
+			}
265
+
266
+		} catch (EofException $e) {
267
+			// end of file
268
+		} catch (\Exception $e) {
269
+			$this->log('Error: ' . $e->getMessage(), 'red');
270
+			return 2;
271
+		}
272
+
273
+		return $realCode;
274
+
275
+	}
276
+
277
+	/**
278
+	 * Shows the help message.
279
+	 *
280
+	 * @return void
281
+	 */
282
+	protected function showHelp() {
283
+
284
+		$this->log('Usage:', 'yellow');
285
+		$this->log("  vobject [options] command [arguments]");
286
+		$this->log('');
287
+		$this->log('Options:', 'yellow');
288
+		$this->log($this->colorize('green', '  -q            ') . "Don't output anything.");
289
+		$this->log($this->colorize('green', '  -help -h      ') . "Display this help message.");
290
+		$this->log($this->colorize('green', '  --format      ') . "Convert to a specific format. Must be one of: vcard, vcard21,");
291
+		$this->log($this->colorize('green', '  --forgiving   ') . "Makes the parser less strict.");
292
+		$this->log("                vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.");
293
+		$this->log($this->colorize('green', '  --inputformat ') . "If the input format cannot be guessed from the extension, it");
294
+		$this->log("                must be specified here.");
295
+		// Only PHP 5.4 and up
296
+		if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
297
+			$this->log($this->colorize('green', '  --pretty      ') . "json pretty-print.");
298
+		}
299
+		$this->log('');
300
+		$this->log('Commands:', 'yellow');
301
+		$this->log($this->colorize('green', '  validate') . ' source_file              Validates a file for correctness.');
302
+		$this->log($this->colorize('green', '  repair') . ' source_file [output_file]  Repairs a file.');
303
+		$this->log($this->colorize('green', '  convert') . ' source_file [output_file] Converts a file.');
304
+		$this->log($this->colorize('green', '  color') . ' source_file                 Colorize a file, useful for debbugging.');
305
+		$this->log(
306
+		<<<HELP
307 307
 
308 308
 If source_file is set as '-', STDIN will be used.
309 309
 If output_file is omitted, STDOUT will be used.
310 310
 All other output is sent to STDERR.
311 311
 
312 312
 HELP
313
-        );
314
-
315
-        $this->log('Examples:', 'yellow');
316
-        $this->log('   vobject convert contact.vcf contact.json');
317
-        $this->log('   vobject convert --format=vcard40 old.vcf new.vcf');
318
-        $this->log('   vobject convert --inputformat=json --format=mimedir - -');
319
-        $this->log('   vobject color calendar.ics');
320
-        $this->log('');
321
-        $this->log('https://github.com/fruux/sabre-vobject', 'purple');
322
-
323
-    }
324
-
325
-    /**
326
-     * Validates a VObject file.
327
-     *
328
-     * @param Component $vObj
329
-     *
330
-     * @return int
331
-     */
332
-    protected function validate(Component $vObj) {
333
-
334
-        $returnCode = 0;
335
-
336
-        switch ($vObj->name) {
337
-            case 'VCALENDAR' :
338
-                $this->log("iCalendar: " . (string)$vObj->VERSION);
339
-                break;
340
-            case 'VCARD' :
341
-                $this->log("vCard: " . (string)$vObj->VERSION);
342
-                break;
343
-        }
344
-
345
-        $warnings = $vObj->validate();
346
-        if (!count($warnings)) {
347
-            $this->log("  No warnings!");
348
-        } else {
349
-
350
-            $levels = [
351
-                1 => 'REPAIRED',
352
-                2 => 'WARNING',
353
-                3 => 'ERROR',
354
-            ];
355
-            $returnCode = 2;
356
-            foreach ($warnings as $warn) {
357
-
358
-                $extra = '';
359
-                if ($warn['node'] instanceof Property) {
360
-                    $extra = ' (property: "' . $warn['node']->name . '")';
361
-                }
362
-                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
363
-
364
-            }
365
-
366
-        }
367
-
368
-        return $returnCode;
369
-
370
-    }
371
-
372
-    /**
373
-     * Repairs a VObject file.
374
-     *
375
-     * @param Component $vObj
376
-     *
377
-     * @return int
378
-     */
379
-    protected function repair(Component $vObj) {
380
-
381
-        $returnCode = 0;
382
-
383
-        switch ($vObj->name) {
384
-            case 'VCALENDAR' :
385
-                $this->log("iCalendar: " . (string)$vObj->VERSION);
386
-                break;
387
-            case 'VCARD' :
388
-                $this->log("vCard: " . (string)$vObj->VERSION);
389
-                break;
390
-        }
391
-
392
-        $warnings = $vObj->validate(Node::REPAIR);
393
-        if (!count($warnings)) {
394
-            $this->log("  No warnings!");
395
-        } else {
396
-
397
-            $levels = [
398
-                1 => 'REPAIRED',
399
-                2 => 'WARNING',
400
-                3 => 'ERROR',
401
-            ];
402
-            $returnCode = 2;
403
-            foreach ($warnings as $warn) {
404
-
405
-                $extra = '';
406
-                if ($warn['node'] instanceof Property) {
407
-                    $extra = ' (property: "' . $warn['node']->name . '")';
408
-                }
409
-                $this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
410
-
411
-            }
412
-
413
-        }
414
-        fwrite($this->stdout, $vObj->serialize());
415
-
416
-        return $returnCode;
417
-
418
-    }
419
-
420
-    /**
421
-     * Converts a vObject file to a new format.
422
-     *
423
-     * @param Component $vObj
424
-     *
425
-     * @return int
426
-     */
427
-    protected function convert($vObj) {
428
-
429
-        $json = false;
430
-        $convertVersion = null;
431
-        $forceInput = null;
432
-
433
-        switch ($this->format) {
434
-            case 'json' :
435
-                $json = true;
436
-                if ($vObj->name === 'VCARD') {
437
-                    $convertVersion = Document::VCARD40;
438
-                }
439
-                break;
440
-            case 'jcard' :
441
-                $json = true;
442
-                $forceInput = 'VCARD';
443
-                $convertVersion = Document::VCARD40;
444
-                break;
445
-            case 'jcal' :
446
-                $json = true;
447
-                $forceInput = 'VCALENDAR';
448
-                break;
449
-            case 'mimedir' :
450
-            case 'icalendar' :
451
-            case 'icalendar20' :
452
-            case 'vcard' :
453
-                break;
454
-            case 'vcard21' :
455
-                $convertVersion = Document::VCARD21;
456
-                break;
457
-            case 'vcard30' :
458
-                $convertVersion = Document::VCARD30;
459
-                break;
460
-            case 'vcard40' :
461
-                $convertVersion = Document::VCARD40;
462
-                break;
463
-
464
-        }
465
-
466
-        if ($forceInput && $vObj->name !== $forceInput) {
467
-            throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
468
-        }
469
-        if ($convertVersion) {
470
-            $vObj = $vObj->convert($convertVersion);
471
-        }
472
-        if ($json) {
473
-            $jsonOptions = 0;
474
-            if ($this->pretty) {
475
-                $jsonOptions = JSON_PRETTY_PRINT;
476
-            }
477
-            fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
478
-        } else {
479
-            fwrite($this->stdout, $vObj->serialize());
480
-        }
481
-
482
-        return 0;
483
-
484
-    }
485
-
486
-    /**
487
-     * Colorizes a file.
488
-     *
489
-     * @param Component $vObj
490
-     *
491
-     * @return int
492
-     */
493
-    protected function color($vObj) {
494
-
495
-        fwrite($this->stdout, $this->serializeComponent($vObj));
496
-
497
-    }
498
-
499
-    /**
500
-     * Returns an ansi color string for a color name.
501
-     *
502
-     * @param string $color
503
-     *
504
-     * @return string
505
-     */
506
-    protected function colorize($color, $str, $resetTo = 'default') {
507
-
508
-        $colors = [
509
-            'cyan'    => '1;36',
510
-            'red'     => '1;31',
511
-            'yellow'  => '1;33',
512
-            'blue'    => '0;34',
513
-            'green'   => '0;32',
514
-            'default' => '0',
515
-            'purple'  => '0;35',
516
-        ];
517
-        return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m";
518
-
519
-    }
520
-
521
-    /**
522
-     * Writes out a string in specific color.
523
-     *
524
-     * @param string $color
525
-     * @param string $str
526
-     *
527
-     * @return void
528
-     */
529
-    protected function cWrite($color, $str) {
530
-
531
-        fwrite($this->stdout, $this->colorize($color, $str));
532
-
533
-    }
534
-
535
-    protected function serializeComponent(Component $vObj) {
536
-
537
-        $this->cWrite('cyan', 'BEGIN');
538
-        $this->cWrite('red', ':');
539
-        $this->cWrite('yellow', $vObj->name . "\n");
540
-
541
-        /**
542
-         * Gives a component a 'score' for sorting purposes.
543
-         *
544
-         * This is solely used by the childrenSort method.
545
-         *
546
-         * A higher score means the item will be lower in the list.
547
-         * To avoid score collisions, each "score category" has a reasonable
548
-         * space to accomodate elements. The $key is added to the $score to
549
-         * preserve the original relative order of elements.
550
-         *
551
-         * @param int $key
552
-         * @param array $array
553
-         *
554
-         * @return int
555
-         */
556
-        $sortScore = function($key, $array) {
557
-
558
-            if ($array[$key] instanceof Component) {
559
-
560
-                // We want to encode VTIMEZONE first, this is a personal
561
-                // preference.
562
-                if ($array[$key]->name === 'VTIMEZONE') {
563
-                    $score = 300000000;
564
-                    return $score + $key;
565
-                } else {
566
-                    $score = 400000000;
567
-                    return $score + $key;
568
-                }
569
-            } else {
570
-                // Properties get encoded first
571
-                // VCARD version 4.0 wants the VERSION property to appear first
572
-                if ($array[$key] instanceof Property) {
573
-                    if ($array[$key]->name === 'VERSION') {
574
-                        $score = 100000000;
575
-                        return $score + $key;
576
-                    } else {
577
-                        // All other properties
578
-                        $score = 200000000;
579
-                        return $score + $key;
580
-                    }
581
-                }
582
-            }
583
-
584
-        };
585
-
586
-        $children = $vObj->children();
587
-        $tmp = $children;
588
-        uksort(
589
-            $children,
590
-            function($a, $b) use ($sortScore, $tmp) {
591
-
592
-                $sA = $sortScore($a, $tmp);
593
-                $sB = $sortScore($b, $tmp);
594
-
595
-                return $sA - $sB;
596
-
597
-            }
598
-        );
599
-
600
-        foreach ($children as $child) {
601
-            if ($child instanceof Component) {
602
-                $this->serializeComponent($child);
603
-            } else {
604
-                $this->serializeProperty($child);
605
-            }
606
-        }
607
-
608
-        $this->cWrite('cyan', 'END');
609
-        $this->cWrite('red', ':');
610
-        $this->cWrite('yellow', $vObj->name . "\n");
611
-
612
-    }
613
-
614
-    /**
615
-     * Colorizes a property.
616
-     *
617
-     * @param Property $property
618
-     *
619
-     * @return void
620
-     */
621
-    protected function serializeProperty(Property $property) {
622
-
623
-        if ($property->group) {
624
-            $this->cWrite('default', $property->group);
625
-            $this->cWrite('red', '.');
626
-        }
627
-
628
-        $this->cWrite('yellow', $property->name);
629
-
630
-        foreach ($property->parameters as $param) {
631
-
632
-            $this->cWrite('red', ';');
633
-            $this->cWrite('blue', $param->serialize());
634
-
635
-        }
636
-        $this->cWrite('red', ':');
637
-
638
-        if ($property instanceof Property\Binary) {
639
-
640
-            $this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
641
-
642
-        } else {
643
-
644
-            $parts = $property->getParts();
645
-            $first1 = true;
646
-            // Looping through property values
647
-            foreach ($parts as $part) {
648
-                if ($first1) {
649
-                    $first1 = false;
650
-                } else {
651
-                    $this->cWrite('red', $property->delimiter);
652
-                }
653
-                $first2 = true;
654
-                // Looping through property sub-values
655
-                foreach ((array)$part as $subPart) {
656
-                    if ($first2) {
657
-                        $first2 = false;
658
-                    } else {
659
-                        // The sub-value delimiter is always comma
660
-                        $this->cWrite('red', ',');
661
-                    }
662
-
663
-                    $subPart = strtr(
664
-                        $subPart,
665
-                        [
666
-                            '\\' => $this->colorize('purple', '\\\\', 'green'),
667
-                            ';'  => $this->colorize('purple', '\;', 'green'),
668
-                            ','  => $this->colorize('purple', '\,', 'green'),
669
-                            "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
670
-                            "\r" => "",
671
-                        ]
672
-                    );
673
-
674
-                    $this->cWrite('green', $subPart);
675
-                }
676
-            }
677
-
678
-        }
679
-        $this->cWrite("default", "\n");
680
-
681
-    }
682
-
683
-    /**
684
-     * Parses the list of arguments.
685
-     *
686
-     * @param array $argv
687
-     *
688
-     * @return void
689
-     */
690
-    protected function parseArguments(array $argv) {
691
-
692
-        $positional = [];
693
-        $options = [];
694
-
695
-        for ($ii = 0; $ii < count($argv); $ii++) {
696
-
697
-            // Skipping the first argument.
698
-            if ($ii === 0) continue;
699
-
700
-            $v = $argv[$ii];
701
-
702
-            if (substr($v, 0, 2) === '--') {
703
-                // This is a long-form option.
704
-                $optionName = substr($v, 2);
705
-                $optionValue = true;
706
-                if (strpos($optionName, '=')) {
707
-                    list($optionName, $optionValue) = explode('=', $optionName);
708
-                }
709
-                $options[$optionName] = $optionValue;
710
-            } elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) {
711
-                // This is a short-form option.
712
-                foreach (str_split(substr($v, 1)) as $option) {
713
-                    $options[$option] = true;
714
-                }
715
-
716
-            } else {
717
-
718
-                $positional[] = $v;
719
-
720
-            }
721
-
722
-        }
723
-
724
-        return [$options, $positional];
725
-
726
-    }
727
-
728
-    protected $parser;
729
-
730
-    /**
731
-     * Reads the input file.
732
-     *
733
-     * @return Component
734
-     */
735
-    protected function readInput() {
736
-
737
-        if (!$this->parser) {
738
-            if ($this->inputPath !== '-') {
739
-                $this->stdin = fopen($this->inputPath, 'r');
740
-            }
741
-
742
-            if ($this->inputFormat === 'mimedir') {
743
-                $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
744
-            } else {
745
-                $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
746
-            }
747
-        }
748
-
749
-        return $this->parser->parse();
750
-
751
-    }
752
-
753
-    /**
754
-     * Sends a message to STDERR.
755
-     *
756
-     * @param string $msg
757
-     *
758
-     * @return void
759
-     */
760
-    protected function log($msg, $color = 'default') {
761
-
762
-        if (!$this->quiet) {
763
-            if ($color !== 'default') {
764
-                $msg = $this->colorize($color, $msg);
765
-            }
766
-            fwrite($this->stderr, $msg . "\n");
767
-        }
768
-
769
-    }
313
+		);
314
+
315
+		$this->log('Examples:', 'yellow');
316
+		$this->log('   vobject convert contact.vcf contact.json');
317
+		$this->log('   vobject convert --format=vcard40 old.vcf new.vcf');
318
+		$this->log('   vobject convert --inputformat=json --format=mimedir - -');
319
+		$this->log('   vobject color calendar.ics');
320
+		$this->log('');
321
+		$this->log('https://github.com/fruux/sabre-vobject', 'purple');
322
+
323
+	}
324
+
325
+	/**
326
+	 * Validates a VObject file.
327
+	 *
328
+	 * @param Component $vObj
329
+	 *
330
+	 * @return int
331
+	 */
332
+	protected function validate(Component $vObj) {
333
+
334
+		$returnCode = 0;
335
+
336
+		switch ($vObj->name) {
337
+			case 'VCALENDAR' :
338
+				$this->log("iCalendar: " . (string)$vObj->VERSION);
339
+				break;
340
+			case 'VCARD' :
341
+				$this->log("vCard: " . (string)$vObj->VERSION);
342
+				break;
343
+		}
344
+
345
+		$warnings = $vObj->validate();
346
+		if (!count($warnings)) {
347
+			$this->log("  No warnings!");
348
+		} else {
349
+
350
+			$levels = [
351
+				1 => 'REPAIRED',
352
+				2 => 'WARNING',
353
+				3 => 'ERROR',
354
+			];
355
+			$returnCode = 2;
356
+			foreach ($warnings as $warn) {
357
+
358
+				$extra = '';
359
+				if ($warn['node'] instanceof Property) {
360
+					$extra = ' (property: "' . $warn['node']->name . '")';
361
+				}
362
+				$this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
363
+
364
+			}
365
+
366
+		}
367
+
368
+		return $returnCode;
369
+
370
+	}
371
+
372
+	/**
373
+	 * Repairs a VObject file.
374
+	 *
375
+	 * @param Component $vObj
376
+	 *
377
+	 * @return int
378
+	 */
379
+	protected function repair(Component $vObj) {
380
+
381
+		$returnCode = 0;
382
+
383
+		switch ($vObj->name) {
384
+			case 'VCALENDAR' :
385
+				$this->log("iCalendar: " . (string)$vObj->VERSION);
386
+				break;
387
+			case 'VCARD' :
388
+				$this->log("vCard: " . (string)$vObj->VERSION);
389
+				break;
390
+		}
391
+
392
+		$warnings = $vObj->validate(Node::REPAIR);
393
+		if (!count($warnings)) {
394
+			$this->log("  No warnings!");
395
+		} else {
396
+
397
+			$levels = [
398
+				1 => 'REPAIRED',
399
+				2 => 'WARNING',
400
+				3 => 'ERROR',
401
+			];
402
+			$returnCode = 2;
403
+			foreach ($warnings as $warn) {
404
+
405
+				$extra = '';
406
+				if ($warn['node'] instanceof Property) {
407
+					$extra = ' (property: "' . $warn['node']->name . '")';
408
+				}
409
+				$this->log("  [" . $levels[$warn['level']] . '] ' . $warn['message'] . $extra);
410
+
411
+			}
412
+
413
+		}
414
+		fwrite($this->stdout, $vObj->serialize());
415
+
416
+		return $returnCode;
417
+
418
+	}
419
+
420
+	/**
421
+	 * Converts a vObject file to a new format.
422
+	 *
423
+	 * @param Component $vObj
424
+	 *
425
+	 * @return int
426
+	 */
427
+	protected function convert($vObj) {
428
+
429
+		$json = false;
430
+		$convertVersion = null;
431
+		$forceInput = null;
432
+
433
+		switch ($this->format) {
434
+			case 'json' :
435
+				$json = true;
436
+				if ($vObj->name === 'VCARD') {
437
+					$convertVersion = Document::VCARD40;
438
+				}
439
+				break;
440
+			case 'jcard' :
441
+				$json = true;
442
+				$forceInput = 'VCARD';
443
+				$convertVersion = Document::VCARD40;
444
+				break;
445
+			case 'jcal' :
446
+				$json = true;
447
+				$forceInput = 'VCALENDAR';
448
+				break;
449
+			case 'mimedir' :
450
+			case 'icalendar' :
451
+			case 'icalendar20' :
452
+			case 'vcard' :
453
+				break;
454
+			case 'vcard21' :
455
+				$convertVersion = Document::VCARD21;
456
+				break;
457
+			case 'vcard30' :
458
+				$convertVersion = Document::VCARD30;
459
+				break;
460
+			case 'vcard40' :
461
+				$convertVersion = Document::VCARD40;
462
+				break;
463
+
464
+		}
465
+
466
+		if ($forceInput && $vObj->name !== $forceInput) {
467
+			throw new \Exception('You cannot convert a ' . strtolower($vObj->name) . ' to ' . $this->format);
468
+		}
469
+		if ($convertVersion) {
470
+			$vObj = $vObj->convert($convertVersion);
471
+		}
472
+		if ($json) {
473
+			$jsonOptions = 0;
474
+			if ($this->pretty) {
475
+				$jsonOptions = JSON_PRETTY_PRINT;
476
+			}
477
+			fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
478
+		} else {
479
+			fwrite($this->stdout, $vObj->serialize());
480
+		}
481
+
482
+		return 0;
483
+
484
+	}
485
+
486
+	/**
487
+	 * Colorizes a file.
488
+	 *
489
+	 * @param Component $vObj
490
+	 *
491
+	 * @return int
492
+	 */
493
+	protected function color($vObj) {
494
+
495
+		fwrite($this->stdout, $this->serializeComponent($vObj));
496
+
497
+	}
498
+
499
+	/**
500
+	 * Returns an ansi color string for a color name.
501
+	 *
502
+	 * @param string $color
503
+	 *
504
+	 * @return string
505
+	 */
506
+	protected function colorize($color, $str, $resetTo = 'default') {
507
+
508
+		$colors = [
509
+			'cyan'    => '1;36',
510
+			'red'     => '1;31',
511
+			'yellow'  => '1;33',
512
+			'blue'    => '0;34',
513
+			'green'   => '0;32',
514
+			'default' => '0',
515
+			'purple'  => '0;35',
516
+		];
517
+		return "\033[" . $colors[$color] . 'm' . $str . "\033[" . $colors[$resetTo] . "m";
518
+
519
+	}
520
+
521
+	/**
522
+	 * Writes out a string in specific color.
523
+	 *
524
+	 * @param string $color
525
+	 * @param string $str
526
+	 *
527
+	 * @return void
528
+	 */
529
+	protected function cWrite($color, $str) {
530
+
531
+		fwrite($this->stdout, $this->colorize($color, $str));
532
+
533
+	}
534
+
535
+	protected function serializeComponent(Component $vObj) {
536
+
537
+		$this->cWrite('cyan', 'BEGIN');
538
+		$this->cWrite('red', ':');
539
+		$this->cWrite('yellow', $vObj->name . "\n");
540
+
541
+		/**
542
+		 * Gives a component a 'score' for sorting purposes.
543
+		 *
544
+		 * This is solely used by the childrenSort method.
545
+		 *
546
+		 * A higher score means the item will be lower in the list.
547
+		 * To avoid score collisions, each "score category" has a reasonable
548
+		 * space to accomodate elements. The $key is added to the $score to
549
+		 * preserve the original relative order of elements.
550
+		 *
551
+		 * @param int $key
552
+		 * @param array $array
553
+		 *
554
+		 * @return int
555
+		 */
556
+		$sortScore = function($key, $array) {
557
+
558
+			if ($array[$key] instanceof Component) {
559
+
560
+				// We want to encode VTIMEZONE first, this is a personal
561
+				// preference.
562
+				if ($array[$key]->name === 'VTIMEZONE') {
563
+					$score = 300000000;
564
+					return $score + $key;
565
+				} else {
566
+					$score = 400000000;
567
+					return $score + $key;
568
+				}
569
+			} else {
570
+				// Properties get encoded first
571
+				// VCARD version 4.0 wants the VERSION property to appear first
572
+				if ($array[$key] instanceof Property) {
573
+					if ($array[$key]->name === 'VERSION') {
574
+						$score = 100000000;
575
+						return $score + $key;
576
+					} else {
577
+						// All other properties
578
+						$score = 200000000;
579
+						return $score + $key;
580
+					}
581
+				}
582
+			}
583
+
584
+		};
585
+
586
+		$children = $vObj->children();
587
+		$tmp = $children;
588
+		uksort(
589
+			$children,
590
+			function($a, $b) use ($sortScore, $tmp) {
591
+
592
+				$sA = $sortScore($a, $tmp);
593
+				$sB = $sortScore($b, $tmp);
594
+
595
+				return $sA - $sB;
596
+
597
+			}
598
+		);
599
+
600
+		foreach ($children as $child) {
601
+			if ($child instanceof Component) {
602
+				$this->serializeComponent($child);
603
+			} else {
604
+				$this->serializeProperty($child);
605
+			}
606
+		}
607
+
608
+		$this->cWrite('cyan', 'END');
609
+		$this->cWrite('red', ':');
610
+		$this->cWrite('yellow', $vObj->name . "\n");
611
+
612
+	}
613
+
614
+	/**
615
+	 * Colorizes a property.
616
+	 *
617
+	 * @param Property $property
618
+	 *
619
+	 * @return void
620
+	 */
621
+	protected function serializeProperty(Property $property) {
622
+
623
+		if ($property->group) {
624
+			$this->cWrite('default', $property->group);
625
+			$this->cWrite('red', '.');
626
+		}
627
+
628
+		$this->cWrite('yellow', $property->name);
629
+
630
+		foreach ($property->parameters as $param) {
631
+
632
+			$this->cWrite('red', ';');
633
+			$this->cWrite('blue', $param->serialize());
634
+
635
+		}
636
+		$this->cWrite('red', ':');
637
+
638
+		if ($property instanceof Property\Binary) {
639
+
640
+			$this->cWrite('default', 'embedded binary stripped. (' . strlen($property->getValue()) . ' bytes)');
641
+
642
+		} else {
643
+
644
+			$parts = $property->getParts();
645
+			$first1 = true;
646
+			// Looping through property values
647
+			foreach ($parts as $part) {
648
+				if ($first1) {
649
+					$first1 = false;
650
+				} else {
651
+					$this->cWrite('red', $property->delimiter);
652
+				}
653
+				$first2 = true;
654
+				// Looping through property sub-values
655
+				foreach ((array)$part as $subPart) {
656
+					if ($first2) {
657
+						$first2 = false;
658
+					} else {
659
+						// The sub-value delimiter is always comma
660
+						$this->cWrite('red', ',');
661
+					}
662
+
663
+					$subPart = strtr(
664
+						$subPart,
665
+						[
666
+							'\\' => $this->colorize('purple', '\\\\', 'green'),
667
+							';'  => $this->colorize('purple', '\;', 'green'),
668
+							','  => $this->colorize('purple', '\,', 'green'),
669
+							"\n" => $this->colorize('purple', "\\n\n\t", 'green'),
670
+							"\r" => "",
671
+						]
672
+					);
673
+
674
+					$this->cWrite('green', $subPart);
675
+				}
676
+			}
677
+
678
+		}
679
+		$this->cWrite("default", "\n");
680
+
681
+	}
682
+
683
+	/**
684
+	 * Parses the list of arguments.
685
+	 *
686
+	 * @param array $argv
687
+	 *
688
+	 * @return void
689
+	 */
690
+	protected function parseArguments(array $argv) {
691
+
692
+		$positional = [];
693
+		$options = [];
694
+
695
+		for ($ii = 0; $ii < count($argv); $ii++) {
696
+
697
+			// Skipping the first argument.
698
+			if ($ii === 0) continue;
699
+
700
+			$v = $argv[$ii];
701
+
702
+			if (substr($v, 0, 2) === '--') {
703
+				// This is a long-form option.
704
+				$optionName = substr($v, 2);
705
+				$optionValue = true;
706
+				if (strpos($optionName, '=')) {
707
+					list($optionName, $optionValue) = explode('=', $optionName);
708
+				}
709
+				$options[$optionName] = $optionValue;
710
+			} elseif (substr($v, 0, 1) === '-' && strlen($v) > 1) {
711
+				// This is a short-form option.
712
+				foreach (str_split(substr($v, 1)) as $option) {
713
+					$options[$option] = true;
714
+				}
715
+
716
+			} else {
717
+
718
+				$positional[] = $v;
719
+
720
+			}
721
+
722
+		}
723
+
724
+		return [$options, $positional];
725
+
726
+	}
727
+
728
+	protected $parser;
729
+
730
+	/**
731
+	 * Reads the input file.
732
+	 *
733
+	 * @return Component
734
+	 */
735
+	protected function readInput() {
736
+
737
+		if (!$this->parser) {
738
+			if ($this->inputPath !== '-') {
739
+				$this->stdin = fopen($this->inputPath, 'r');
740
+			}
741
+
742
+			if ($this->inputFormat === 'mimedir') {
743
+				$this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
744
+			} else {
745
+				$this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
746
+			}
747
+		}
748
+
749
+		return $this->parser->parse();
750
+
751
+	}
752
+
753
+	/**
754
+	 * Sends a message to STDERR.
755
+	 *
756
+	 * @param string $msg
757
+	 *
758
+	 * @return void
759
+	 */
760
+	protected function log($msg, $color = 'default') {
761
+
762
+		if (!$this->quiet) {
763
+			if ($color !== 'default') {
764
+				$msg = $this->colorize($color, $msg);
765
+			}
766
+			fwrite($this->stderr, $msg . "\n");
767
+		}
768
+
769
+	}
770 770
 
771 771
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Parameter.php 3 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
      *
177 177
      * This may be either a single, or multiple strings in an array.
178 178
      *
179
-     * @param string|array $value
179
+     * @param string|null $value
180 180
      *
181 181
      * @return void
182 182
      */
@@ -342,7 +342,7 @@  discard block
 block discarded – undo
342 342
      * This method returns an array, with the representation as it should be
343 343
      * encoded in JSON. This is used to create jCard or jCal documents.
344 344
      *
345
-     * @return array
345
+     * @return string
346 346
      */
347 347
     function jsonSerialize() {
348 348
 
Please login to merge, or discard this patch.
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\VObject;
4 4
 
5
-use Sabre\Xml;
6 5
 use ArrayIterator;
6
+use Sabre\Xml;
7 7
 
8 8
 /**
9 9
  * VObject Parameter.
Please login to merge, or discard this patch.
Indentation   +371 added lines, -371 removed lines patch added patch discarded remove patch
@@ -19,376 +19,376 @@
 block discarded – undo
19 19
  */
20 20
 class Parameter extends Node {
21 21
 
22
-    /**
23
-     * Parameter name.
24
-     *
25
-     * @var string
26
-     */
27
-    public $name;
28
-
29
-    /**
30
-     * vCard 2.1 allows parameters to be encoded without a name.
31
-     *
32
-     * We can deduce the parameter name based on it's value.
33
-     *
34
-     * @var bool
35
-     */
36
-    public $noName = false;
37
-
38
-    /**
39
-     * Parameter value.
40
-     *
41
-     * @var string
42
-     */
43
-    protected $value;
44
-
45
-    /**
46
-     * Sets up the object.
47
-     *
48
-     * It's recommended to use the create:: factory method instead.
49
-     *
50
-     * @param string $name
51
-     * @param string $value
52
-     */
53
-    public function __construct(Document $root, $name, $value = null) {
54
-
55
-        $this->name = strtoupper($name);
56
-        $this->root = $root;
57
-        if (is_null($name)) {
58
-            $this->noName = true;
59
-            $this->name = static::guessParameterNameByValue($value);
60
-        }
61
-
62
-        // If guessParameterNameByValue() returns an empty string
63
-        // above, we're actually dealing with a parameter that has no value.
64
-        // In that case we have to move the value to the name.
65
-        if ($this->name === '') {
66
-            $this->noName = false;
67
-            $this->name = strtoupper($value);
68
-        } else {
69
-            $this->setValue($value);
70
-        }
71
-
72
-    }
73
-
74
-    /**
75
-     * Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
76
-     *
77
-     * Figuring out what the name should have been. Note that a ton of
78
-     * these are rather silly in 2014 and would probably rarely be
79
-     * used, but we like to be complete.
80
-     *
81
-     * @param string $value
82
-     *
83
-     * @return string
84
-     */
85
-    static function guessParameterNameByValue($value) {
86
-        switch (strtoupper($value)) {
87
-
88
-            // Encodings
89
-            case '7-BIT' :
90
-            case 'QUOTED-PRINTABLE' :
91
-            case 'BASE64' :
92
-                $name = 'ENCODING';
93
-                break;
94
-
95
-            // Common types
96
-            case 'WORK' :
97
-            case 'HOME' :
98
-            case 'PREF' :
99
-
100
-            // Delivery Label Type
101
-            case 'DOM' :
102
-            case 'INTL' :
103
-            case 'POSTAL' :
104
-            case 'PARCEL' :
105
-
106
-            // Telephone types
107
-            case 'VOICE' :
108
-            case 'FAX' :
109
-            case 'MSG' :
110
-            case 'CELL' :
111
-            case 'PAGER' :
112
-            case 'BBS' :
113
-            case 'MODEM' :
114
-            case 'CAR' :
115
-            case 'ISDN' :
116
-            case 'VIDEO' :
117
-
118
-            // EMAIL types (lol)
119
-            case 'AOL' :
120
-            case 'APPLELINK' :
121
-            case 'ATTMAIL' :
122
-            case 'CIS' :
123
-            case 'EWORLD' :
124
-            case 'INTERNET' :
125
-            case 'IBMMAIL' :
126
-            case 'MCIMAIL' :
127
-            case 'POWERSHARE' :
128
-            case 'PRODIGY' :
129
-            case 'TLX' :
130
-            case 'X400' :
131
-
132
-            // Photo / Logo format types
133
-            case 'GIF' :
134
-            case 'CGM' :
135
-            case 'WMF' :
136
-            case 'BMP' :
137
-            case 'DIB' :
138
-            case 'PICT' :
139
-            case 'TIFF' :
140
-            case 'PDF' :
141
-            case 'PS' :
142
-            case 'JPEG' :
143
-            case 'MPEG' :
144
-            case 'MPEG2' :
145
-            case 'AVI' :
146
-            case 'QTIME' :
147
-
148
-            // Sound Digital Audio Type
149
-            case 'WAVE' :
150
-            case 'PCM' :
151
-            case 'AIFF' :
152
-
153
-            // Key types
154
-            case 'X509' :
155
-            case 'PGP' :
156
-                $name = 'TYPE';
157
-                break;
158
-
159
-            // Value types
160
-            case 'INLINE' :
161
-            case 'URL' :
162
-            case 'CONTENT-ID' :
163
-            case 'CID' :
164
-                $name = 'VALUE';
165
-                break;
166
-
167
-            default:
168
-                $name = '';
169
-        }
170
-
171
-        return $name;
172
-    }
173
-
174
-    /**
175
-     * Updates the current value.
176
-     *
177
-     * This may be either a single, or multiple strings in an array.
178
-     *
179
-     * @param string|array $value
180
-     *
181
-     * @return void
182
-     */
183
-    public function setValue($value) {
184
-
185
-        $this->value = $value;
186
-
187
-    }
188
-
189
-    /**
190
-     * Returns the current value.
191
-     *
192
-     * This method will always return a string, or null. If there were multiple
193
-     * values, it will automatically concatenate them (separated by comma).
194
-     *
195
-     * @return string|null
196
-     */
197
-    public function getValue() {
198
-
199
-        if (is_array($this->value)) {
200
-            return implode(',', $this->value);
201
-        } else {
202
-            return $this->value;
203
-        }
204
-
205
-    }
206
-
207
-    /**
208
-     * Sets multiple values for this parameter.
209
-     *
210
-     * @param array $value
211
-     *
212
-     * @return void
213
-     */
214
-    public function setParts(array $value) {
215
-
216
-        $this->value = $value;
217
-
218
-    }
219
-
220
-    /**
221
-     * Returns all values for this parameter.
222
-     *
223
-     * If there were no values, an empty array will be returned.
224
-     *
225
-     * @return array
226
-     */
227
-    public function getParts() {
228
-
229
-        if (is_array($this->value)) {
230
-            return $this->value;
231
-        } elseif (is_null($this->value)) {
232
-            return [];
233
-        } else {
234
-            return [$this->value];
235
-        }
236
-
237
-    }
238
-
239
-    /**
240
-     * Adds a value to this parameter.
241
-     *
242
-     * If the argument is specified as an array, all items will be added to the
243
-     * parameter value list.
244
-     *
245
-     * @param string|array $part
246
-     *
247
-     * @return void
248
-     */
249
-    public function addValue($part) {
250
-
251
-        if (is_null($this->value)) {
252
-            $this->value = $part;
253
-        } else {
254
-            $this->value = array_merge((array)$this->value, (array)$part);
255
-        }
256
-
257
-    }
258
-
259
-    /**
260
-     * Checks if this parameter contains the specified value.
261
-     *
262
-     * This is a case-insensitive match. It makes sense to call this for for
263
-     * instance the TYPE parameter, to see if it contains a keyword such as
264
-     * 'WORK' or 'FAX'.
265
-     *
266
-     * @param string $value
267
-     *
268
-     * @return bool
269
-     */
270
-    public function has($value) {
271
-
272
-        return in_array(
273
-            strtolower($value),
274
-            array_map('strtolower', (array)$this->value)
275
-        );
276
-
277
-    }
278
-
279
-    /**
280
-     * Turns the object back into a serialized blob.
281
-     *
282
-     * @return string
283
-     */
284
-    public function serialize() {
285
-
286
-        $value = $this->getParts();
287
-
288
-        if (count($value) === 0) {
289
-            return $this->name . '=';
290
-        }
291
-
292
-        if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
293
-
294
-            return implode(';', $value);
295
-
296
-        }
297
-
298
-        return $this->name . '=' . array_reduce(
299
-            $value,
300
-            function($out, $item) {
301
-
302
-                if (!is_null($out)) $out .= ',';
303
-
304
-                // If there's no special characters in the string, we'll use the simple
305
-                // format.
306
-                //
307
-                // The list of special characters is defined as:
308
-                //
309
-                // Any character except CONTROL, DQUOTE, ";", ":", ","
310
-                //
311
-                // by the iCalendar spec:
312
-                // https://tools.ietf.org/html/rfc5545#section-3.1
313
-                //
314
-                // And we add ^ to that because of:
315
-                // https://tools.ietf.org/html/rfc6868
316
-                //
317
-                // But we've found that iCal (7.0, shipped with OSX 10.9)
318
-                // severaly trips on + characters not being quoted, so we
319
-                // added + as well.
320
-                if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
321
-                    return $out . $item;
322
-                } else {
323
-                    // Enclosing in double-quotes, and using RFC6868 for encoding any
324
-                    // special characters
325
-                    $out .= '"' . strtr(
326
-                        $item,
327
-                        [
328
-                            '^'  => '^^',
329
-                            "\n" => '^n',
330
-                            '"'  => '^\'',
331
-                        ]
332
-                    ) . '"';
333
-                    return $out;
334
-                }
335
-
336
-            }
337
-        );
338
-
339
-    }
340
-
341
-    /**
342
-     * This method returns an array, with the representation as it should be
343
-     * encoded in JSON. This is used to create jCard or jCal documents.
344
-     *
345
-     * @return array
346
-     */
347
-    public function jsonSerialize() {
348
-
349
-        return $this->value;
350
-
351
-    }
352
-
353
-    /**
354
-     * This method serializes the data into XML. This is used to create xCard or
355
-     * xCal documents.
356
-     *
357
-     * @param Xml\Writer $writer  XML writer.
358
-     *
359
-     * @return void
360
-     */
361
-    public function xmlSerialize(Xml\Writer $writer) {
362
-
363
-        foreach (explode(',', $this->value) as $value) {
364
-            $writer->writeElement('text', $value);
365
-        }
366
-
367
-    }
368
-
369
-    /**
370
-     * Called when this object is being cast to a string.
371
-     *
372
-     * @return string
373
-     */
374
-    public function __toString() {
375
-
376
-        return (string)$this->getValue();
377
-
378
-    }
379
-
380
-    /**
381
-     * Returns the iterator for this object.
382
-     *
383
-     * @return ElementList
384
-     */
385
-    public function getIterator() {
386
-
387
-        if (!is_null($this->iterator))
388
-            return $this->iterator;
389
-
390
-        return $this->iterator = new ArrayIterator((array)$this->value);
391
-
392
-    }
22
+	/**
23
+	 * Parameter name.
24
+	 *
25
+	 * @var string
26
+	 */
27
+	public $name;
28
+
29
+	/**
30
+	 * vCard 2.1 allows parameters to be encoded without a name.
31
+	 *
32
+	 * We can deduce the parameter name based on it's value.
33
+	 *
34
+	 * @var bool
35
+	 */
36
+	public $noName = false;
37
+
38
+	/**
39
+	 * Parameter value.
40
+	 *
41
+	 * @var string
42
+	 */
43
+	protected $value;
44
+
45
+	/**
46
+	 * Sets up the object.
47
+	 *
48
+	 * It's recommended to use the create:: factory method instead.
49
+	 *
50
+	 * @param string $name
51
+	 * @param string $value
52
+	 */
53
+	public function __construct(Document $root, $name, $value = null) {
54
+
55
+		$this->name = strtoupper($name);
56
+		$this->root = $root;
57
+		if (is_null($name)) {
58
+			$this->noName = true;
59
+			$this->name = static::guessParameterNameByValue($value);
60
+		}
61
+
62
+		// If guessParameterNameByValue() returns an empty string
63
+		// above, we're actually dealing with a parameter that has no value.
64
+		// In that case we have to move the value to the name.
65
+		if ($this->name === '') {
66
+			$this->noName = false;
67
+			$this->name = strtoupper($value);
68
+		} else {
69
+			$this->setValue($value);
70
+		}
71
+
72
+	}
73
+
74
+	/**
75
+	 * Try to guess property name by value, can be used for vCard 2.1 nameless parameters.
76
+	 *
77
+	 * Figuring out what the name should have been. Note that a ton of
78
+	 * these are rather silly in 2014 and would probably rarely be
79
+	 * used, but we like to be complete.
80
+	 *
81
+	 * @param string $value
82
+	 *
83
+	 * @return string
84
+	 */
85
+	static function guessParameterNameByValue($value) {
86
+		switch (strtoupper($value)) {
87
+
88
+			// Encodings
89
+			case '7-BIT' :
90
+			case 'QUOTED-PRINTABLE' :
91
+			case 'BASE64' :
92
+				$name = 'ENCODING';
93
+				break;
94
+
95
+			// Common types
96
+			case 'WORK' :
97
+			case 'HOME' :
98
+			case 'PREF' :
99
+
100
+			// Delivery Label Type
101
+			case 'DOM' :
102
+			case 'INTL' :
103
+			case 'POSTAL' :
104
+			case 'PARCEL' :
105
+
106
+			// Telephone types
107
+			case 'VOICE' :
108
+			case 'FAX' :
109
+			case 'MSG' :
110
+			case 'CELL' :
111
+			case 'PAGER' :
112
+			case 'BBS' :
113
+			case 'MODEM' :
114
+			case 'CAR' :
115
+			case 'ISDN' :
116
+			case 'VIDEO' :
117
+
118
+			// EMAIL types (lol)
119
+			case 'AOL' :
120
+			case 'APPLELINK' :
121
+			case 'ATTMAIL' :
122
+			case 'CIS' :
123
+			case 'EWORLD' :
124
+			case 'INTERNET' :
125
+			case 'IBMMAIL' :
126
+			case 'MCIMAIL' :
127
+			case 'POWERSHARE' :
128
+			case 'PRODIGY' :
129
+			case 'TLX' :
130
+			case 'X400' :
131
+
132
+			// Photo / Logo format types
133
+			case 'GIF' :
134
+			case 'CGM' :
135
+			case 'WMF' :
136
+			case 'BMP' :
137
+			case 'DIB' :
138
+			case 'PICT' :
139
+			case 'TIFF' :
140
+			case 'PDF' :
141
+			case 'PS' :
142
+			case 'JPEG' :
143
+			case 'MPEG' :
144
+			case 'MPEG2' :
145
+			case 'AVI' :
146
+			case 'QTIME' :
147
+
148
+			// Sound Digital Audio Type
149
+			case 'WAVE' :
150
+			case 'PCM' :
151
+			case 'AIFF' :
152
+
153
+			// Key types
154
+			case 'X509' :
155
+			case 'PGP' :
156
+				$name = 'TYPE';
157
+				break;
158
+
159
+			// Value types
160
+			case 'INLINE' :
161
+			case 'URL' :
162
+			case 'CONTENT-ID' :
163
+			case 'CID' :
164
+				$name = 'VALUE';
165
+				break;
166
+
167
+			default:
168
+				$name = '';
169
+		}
170
+
171
+		return $name;
172
+	}
173
+
174
+	/**
175
+	 * Updates the current value.
176
+	 *
177
+	 * This may be either a single, or multiple strings in an array.
178
+	 *
179
+	 * @param string|array $value
180
+	 *
181
+	 * @return void
182
+	 */
183
+	public function setValue($value) {
184
+
185
+		$this->value = $value;
186
+
187
+	}
188
+
189
+	/**
190
+	 * Returns the current value.
191
+	 *
192
+	 * This method will always return a string, or null. If there were multiple
193
+	 * values, it will automatically concatenate them (separated by comma).
194
+	 *
195
+	 * @return string|null
196
+	 */
197
+	public function getValue() {
198
+
199
+		if (is_array($this->value)) {
200
+			return implode(',', $this->value);
201
+		} else {
202
+			return $this->value;
203
+		}
204
+
205
+	}
206
+
207
+	/**
208
+	 * Sets multiple values for this parameter.
209
+	 *
210
+	 * @param array $value
211
+	 *
212
+	 * @return void
213
+	 */
214
+	public function setParts(array $value) {
215
+
216
+		$this->value = $value;
217
+
218
+	}
219
+
220
+	/**
221
+	 * Returns all values for this parameter.
222
+	 *
223
+	 * If there were no values, an empty array will be returned.
224
+	 *
225
+	 * @return array
226
+	 */
227
+	public function getParts() {
228
+
229
+		if (is_array($this->value)) {
230
+			return $this->value;
231
+		} elseif (is_null($this->value)) {
232
+			return [];
233
+		} else {
234
+			return [$this->value];
235
+		}
236
+
237
+	}
238
+
239
+	/**
240
+	 * Adds a value to this parameter.
241
+	 *
242
+	 * If the argument is specified as an array, all items will be added to the
243
+	 * parameter value list.
244
+	 *
245
+	 * @param string|array $part
246
+	 *
247
+	 * @return void
248
+	 */
249
+	public function addValue($part) {
250
+
251
+		if (is_null($this->value)) {
252
+			$this->value = $part;
253
+		} else {
254
+			$this->value = array_merge((array)$this->value, (array)$part);
255
+		}
256
+
257
+	}
258
+
259
+	/**
260
+	 * Checks if this parameter contains the specified value.
261
+	 *
262
+	 * This is a case-insensitive match. It makes sense to call this for for
263
+	 * instance the TYPE parameter, to see if it contains a keyword such as
264
+	 * 'WORK' or 'FAX'.
265
+	 *
266
+	 * @param string $value
267
+	 *
268
+	 * @return bool
269
+	 */
270
+	public function has($value) {
271
+
272
+		return in_array(
273
+			strtolower($value),
274
+			array_map('strtolower', (array)$this->value)
275
+		);
276
+
277
+	}
278
+
279
+	/**
280
+	 * Turns the object back into a serialized blob.
281
+	 *
282
+	 * @return string
283
+	 */
284
+	public function serialize() {
285
+
286
+		$value = $this->getParts();
287
+
288
+		if (count($value) === 0) {
289
+			return $this->name . '=';
290
+		}
291
+
292
+		if ($this->root->getDocumentType() === Document::VCARD21 && $this->noName) {
293
+
294
+			return implode(';', $value);
295
+
296
+		}
297
+
298
+		return $this->name . '=' . array_reduce(
299
+			$value,
300
+			function($out, $item) {
301
+
302
+				if (!is_null($out)) $out .= ',';
303
+
304
+				// If there's no special characters in the string, we'll use the simple
305
+				// format.
306
+				//
307
+				// The list of special characters is defined as:
308
+				//
309
+				// Any character except CONTROL, DQUOTE, ";", ":", ","
310
+				//
311
+				// by the iCalendar spec:
312
+				// https://tools.ietf.org/html/rfc5545#section-3.1
313
+				//
314
+				// And we add ^ to that because of:
315
+				// https://tools.ietf.org/html/rfc6868
316
+				//
317
+				// But we've found that iCal (7.0, shipped with OSX 10.9)
318
+				// severaly trips on + characters not being quoted, so we
319
+				// added + as well.
320
+				if (!preg_match('#(?: [\n":;\^,\+] )#x', $item)) {
321
+					return $out . $item;
322
+				} else {
323
+					// Enclosing in double-quotes, and using RFC6868 for encoding any
324
+					// special characters
325
+					$out .= '"' . strtr(
326
+						$item,
327
+						[
328
+							'^'  => '^^',
329
+							"\n" => '^n',
330
+							'"'  => '^\'',
331
+						]
332
+					) . '"';
333
+					return $out;
334
+				}
335
+
336
+			}
337
+		);
338
+
339
+	}
340
+
341
+	/**
342
+	 * This method returns an array, with the representation as it should be
343
+	 * encoded in JSON. This is used to create jCard or jCal documents.
344
+	 *
345
+	 * @return array
346
+	 */
347
+	public function jsonSerialize() {
348
+
349
+		return $this->value;
350
+
351
+	}
352
+
353
+	/**
354
+	 * This method serializes the data into XML. This is used to create xCard or
355
+	 * xCal documents.
356
+	 *
357
+	 * @param Xml\Writer $writer  XML writer.
358
+	 *
359
+	 * @return void
360
+	 */
361
+	public function xmlSerialize(Xml\Writer $writer) {
362
+
363
+		foreach (explode(',', $this->value) as $value) {
364
+			$writer->writeElement('text', $value);
365
+		}
366
+
367
+	}
368
+
369
+	/**
370
+	 * Called when this object is being cast to a string.
371
+	 *
372
+	 * @return string
373
+	 */
374
+	public function __toString() {
375
+
376
+		return (string)$this->getValue();
377
+
378
+	}
379
+
380
+	/**
381
+	 * Returns the iterator for this object.
382
+	 *
383
+	 * @return ElementList
384
+	 */
385
+	public function getIterator() {
386
+
387
+		if (!is_null($this->iterator))
388
+			return $this->iterator;
389
+
390
+		return $this->iterator = new ArrayIterator((array)$this->value);
391
+
392
+	}
393 393
 
394 394
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Parser/MimeDir.php 3 patches
Doc Comments   +2 added lines, -1 removed lines patch added patch discarded remove patch
@@ -331,6 +331,7 @@  discard block
 block discarded – undo
331 331
     /**
332 332
      * Reads a property or component from a line.
333 333
      *
334
+     * @param string $line
334 335
      * @return void
335 336
      */
336 337
     protected function readProperty($line) {
@@ -623,7 +624,7 @@  discard block
 block discarded – undo
623 624
      *
624 625
      * @param string $input
625 626
      *
626
-     * @return void
627
+     * @return string
627 628
      */
628 629
     private function unescapeParam($input) {
629 630
 
Please login to merge, or discard this patch.
Unused Use Statements   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -2,12 +2,12 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\VObject\Parser;
4 4
 
5
-use Sabre\VObject\ParseException;
6
-use Sabre\VObject\EofException;
7 5
 use Sabre\VObject\Component;
8 6
 use Sabre\VObject\Component\VCalendar;
9 7
 use Sabre\VObject\Component\VCard;
10 8
 use Sabre\VObject\Document;
9
+use Sabre\VObject\EofException;
10
+use Sabre\VObject\ParseException;
11 11
 
12 12
 /**
13 13
  * MimeDir parser.
Please login to merge, or discard this patch.
Indentation   +646 added lines, -646 removed lines patch added patch discarded remove patch
@@ -24,328 +24,328 @@  discard block
 block discarded – undo
24 24
  */
25 25
 class MimeDir extends Parser {
26 26
 
27
-    /**
28
-     * The input stream.
29
-     *
30
-     * @var resource
31
-     */
32
-    protected $input;
33
-
34
-    /**
35
-     * Root component.
36
-     *
37
-     * @var Component
38
-     */
39
-    protected $root;
40
-
41
-    /**
42
-     * By default all input will be assumed to be UTF-8.
43
-     *
44
-     * However, both iCalendar and vCard might be encoded using different
45
-     * character sets. The character set is usually set in the mime-type.
46
-     *
47
-     * If this is the case, use setEncoding to specify that a different
48
-     * encoding will be used. If this is set, the parser will automatically
49
-     * convert all incoming data to UTF-8.
50
-     *
51
-     * @var string
52
-     */
53
-    protected $charset = 'UTF-8';
54
-
55
-    /**
56
-     * The list of character sets we support when decoding.
57
-     *
58
-     * This would be a const expression but for now we need to support PHP 5.5
59
-     */
60
-    protected static $SUPPORTED_CHARSETS = [
61
-        'UTF-8',
62
-        'ISO-8859-1',
63
-        'Windows-1252',
64
-    ];
65
-
66
-    /**
67
-     * Parses an iCalendar or vCard file.
68
-     *
69
-     * Pass a stream or a string. If null is parsed, the existing buffer is
70
-     * used.
71
-     *
72
-     * @param string|resource|null $input
73
-     * @param int $options
74
-     *
75
-     * @return Sabre\VObject\Document
76
-     */
77
-    public function parse($input = null, $options = 0) {
78
-
79
-        $this->root = null;
80
-
81
-        if (!is_null($input)) {
82
-            $this->setInput($input);
83
-        }
84
-
85
-        if (0 !== $options) {
86
-            $this->options = $options;
87
-        }
88
-
89
-        $this->parseDocument();
90
-
91
-        return $this->root;
92
-
93
-    }
94
-
95
-    /**
96
-     * By default all input will be assumed to be UTF-8.
97
-     *
98
-     * However, both iCalendar and vCard might be encoded using different
99
-     * character sets. The character set is usually set in the mime-type.
100
-     *
101
-     * If this is the case, use setEncoding to specify that a different
102
-     * encoding will be used. If this is set, the parser will automatically
103
-     * convert all incoming data to UTF-8.
104
-     *
105
-     * @param string $charset
106
-     */
107
-    public function setCharset($charset) {
108
-
109
-        if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
110
-            throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')');
111
-        }
112
-        $this->charset = $charset;
113
-
114
-    }
115
-
116
-    /**
117
-     * Sets the input buffer. Must be a string or stream.
118
-     *
119
-     * @param resource|string $input
120
-     *
121
-     * @return void
122
-     */
123
-    public function setInput($input) {
124
-
125
-        // Resetting the parser
126
-        $this->lineIndex = 0;
127
-        $this->startLine = 0;
128
-
129
-        if (is_string($input)) {
130
-            // Convering to a stream.
131
-            $stream = fopen('php://temp', 'r+');
132
-            fwrite($stream, $input);
133
-            rewind($stream);
134
-            $this->input = $stream;
135
-        } elseif (is_resource($input)) {
136
-            $this->input = $input;
137
-        } else {
138
-            throw new \InvalidArgumentException('This parser can only read from strings or streams.');
139
-        }
140
-
141
-    }
142
-
143
-    /**
144
-     * Parses an entire document.
145
-     *
146
-     * @return void
147
-     */
148
-    protected function parseDocument() {
149
-
150
-        $line = $this->readLine();
151
-
152
-        // BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
153
-        // It's 0xEF 0xBB 0xBF in UTF-8 hex.
154
-        if (3 <= strlen($line)
155
-            && ord($line[0]) === 0xef
156
-            && ord($line[1]) === 0xbb
157
-            && ord($line[2]) === 0xbf) {
158
-            $line = substr($line, 3);
159
-        }
160
-
161
-        switch (strtoupper($line)) {
162
-            case 'BEGIN:VCALENDAR' :
163
-                $class = VCalendar::$componentMap['VCALENDAR'];
164
-                break;
165
-            case 'BEGIN:VCARD' :
166
-                $class = VCard::$componentMap['VCARD'];
167
-                break;
168
-            default :
169
-                throw new ParseException('This parser only supports VCARD and VCALENDAR files');
170
-        }
171
-
172
-        $this->root = new $class([], false);
173
-
174
-        while (true) {
175
-
176
-            // Reading until we hit END:
177
-            $line = $this->readLine();
178
-            if (strtoupper(substr($line, 0, 4)) === 'END:') {
179
-                break;
180
-            }
181
-            $result = $this->parseLine($line);
182
-            if ($result) {
183
-                $this->root->add($result);
184
-            }
185
-
186
-        }
187
-
188
-        $name = strtoupper(substr($line, 4));
189
-        if ($name !== $this->root->name) {
190
-            throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
191
-        }
192
-
193
-    }
194
-
195
-    /**
196
-     * Parses a line, and if it hits a component, it will also attempt to parse
197
-     * the entire component.
198
-     *
199
-     * @param string $line Unfolded line
200
-     *
201
-     * @return Node
202
-     */
203
-    protected function parseLine($line) {
204
-
205
-        // Start of a new component
206
-        if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
207
-
208
-            $component = $this->root->createComponent(substr($line, 6), [], false);
209
-
210
-            while (true) {
211
-
212
-                // Reading until we hit END:
213
-                $line = $this->readLine();
214
-                if (strtoupper(substr($line, 0, 4)) === 'END:') {
215
-                    break;
216
-                }
217
-                $result = $this->parseLine($line);
218
-                if ($result) {
219
-                    $component->add($result);
220
-                }
221
-
222
-            }
223
-
224
-            $name = strtoupper(substr($line, 4));
225
-            if ($name !== $component->name) {
226
-                throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
227
-            }
228
-
229
-            return $component;
230
-
231
-        } else {
232
-
233
-            // Property reader
234
-            $property = $this->readProperty($line);
235
-            if (!$property) {
236
-                // Ignored line
237
-                return false;
238
-            }
239
-            return $property;
240
-
241
-        }
242
-
243
-    }
244
-
245
-    /**
246
-     * We need to look ahead 1 line every time to see if we need to 'unfold'
247
-     * the next line.
248
-     *
249
-     * If that was not the case, we store it here.
250
-     *
251
-     * @var null|string
252
-     */
253
-    protected $lineBuffer;
254
-
255
-    /**
256
-     * The real current line number.
257
-     */
258
-    protected $lineIndex = 0;
259
-
260
-    /**
261
-     * In the case of unfolded lines, this property holds the line number for
262
-     * the start of the line.
263
-     *
264
-     * @var int
265
-     */
266
-    protected $startLine = 0;
267
-
268
-    /**
269
-     * Contains a 'raw' representation of the current line.
270
-     *
271
-     * @var string
272
-     */
273
-    protected $rawLine;
274
-
275
-    /**
276
-     * Reads a single line from the buffer.
277
-     *
278
-     * This method strips any newlines and also takes care of unfolding.
279
-     *
280
-     * @throws \Sabre\VObject\EofException
281
-     *
282
-     * @return string
283
-     */
284
-    protected function readLine() {
285
-
286
-        if (!is_null($this->lineBuffer)) {
287
-            $rawLine = $this->lineBuffer;
288
-            $this->lineBuffer = null;
289
-        } else {
290
-            do {
291
-                $eof = feof($this->input);
292
-
293
-                $rawLine = fgets($this->input);
294
-
295
-                if ($eof || (feof($this->input) && $rawLine === false)) {
296
-                    throw new EofException('End of document reached prematurely');
297
-                }
298
-                if ($rawLine === false) {
299
-                    throw new ParseException('Error reading from input stream');
300
-                }
301
-                $rawLine = rtrim($rawLine, "\r\n");
302
-            } while ($rawLine === ''); // Skipping empty lines
303
-            $this->lineIndex++;
304
-        }
305
-        $line = $rawLine;
306
-
307
-        $this->startLine = $this->lineIndex;
308
-
309
-        // Looking ahead for folded lines.
310
-        while (true) {
311
-
312
-            $nextLine = rtrim(fgets($this->input), "\r\n");
313
-            $this->lineIndex++;
314
-            if (!$nextLine) {
315
-                break;
316
-            }
317
-            if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
318
-                $line .= substr($nextLine, 1);
319
-                $rawLine .= "\n " . substr($nextLine, 1);
320
-            } else {
321
-                $this->lineBuffer = $nextLine;
322
-                break;
323
-            }
324
-
325
-        }
326
-        $this->rawLine = $rawLine;
327
-        return $line;
328
-
329
-    }
330
-
331
-    /**
332
-     * Reads a property or component from a line.
333
-     *
334
-     * @return void
335
-     */
336
-    protected function readProperty($line) {
337
-
338
-        if ($this->options & self::OPTION_FORGIVING) {
339
-            $propNameToken = 'A-Z0-9\-\._\\/';
340
-        } else {
341
-            $propNameToken = 'A-Z0-9\-\.';
342
-        }
343
-
344
-        $paramNameToken = 'A-Z0-9\-';
345
-        $safeChar = '^";:,';
346
-        $qSafeChar = '^"';
347
-
348
-        $regex = "/
27
+	/**
28
+	 * The input stream.
29
+	 *
30
+	 * @var resource
31
+	 */
32
+	protected $input;
33
+
34
+	/**
35
+	 * Root component.
36
+	 *
37
+	 * @var Component
38
+	 */
39
+	protected $root;
40
+
41
+	/**
42
+	 * By default all input will be assumed to be UTF-8.
43
+	 *
44
+	 * However, both iCalendar and vCard might be encoded using different
45
+	 * character sets. The character set is usually set in the mime-type.
46
+	 *
47
+	 * If this is the case, use setEncoding to specify that a different
48
+	 * encoding will be used. If this is set, the parser will automatically
49
+	 * convert all incoming data to UTF-8.
50
+	 *
51
+	 * @var string
52
+	 */
53
+	protected $charset = 'UTF-8';
54
+
55
+	/**
56
+	 * The list of character sets we support when decoding.
57
+	 *
58
+	 * This would be a const expression but for now we need to support PHP 5.5
59
+	 */
60
+	protected static $SUPPORTED_CHARSETS = [
61
+		'UTF-8',
62
+		'ISO-8859-1',
63
+		'Windows-1252',
64
+	];
65
+
66
+	/**
67
+	 * Parses an iCalendar or vCard file.
68
+	 *
69
+	 * Pass a stream or a string. If null is parsed, the existing buffer is
70
+	 * used.
71
+	 *
72
+	 * @param string|resource|null $input
73
+	 * @param int $options
74
+	 *
75
+	 * @return Sabre\VObject\Document
76
+	 */
77
+	public function parse($input = null, $options = 0) {
78
+
79
+		$this->root = null;
80
+
81
+		if (!is_null($input)) {
82
+			$this->setInput($input);
83
+		}
84
+
85
+		if (0 !== $options) {
86
+			$this->options = $options;
87
+		}
88
+
89
+		$this->parseDocument();
90
+
91
+		return $this->root;
92
+
93
+	}
94
+
95
+	/**
96
+	 * By default all input will be assumed to be UTF-8.
97
+	 *
98
+	 * However, both iCalendar and vCard might be encoded using different
99
+	 * character sets. The character set is usually set in the mime-type.
100
+	 *
101
+	 * If this is the case, use setEncoding to specify that a different
102
+	 * encoding will be used. If this is set, the parser will automatically
103
+	 * convert all incoming data to UTF-8.
104
+	 *
105
+	 * @param string $charset
106
+	 */
107
+	public function setCharset($charset) {
108
+
109
+		if (!in_array($charset, self::$SUPPORTED_CHARSETS)) {
110
+			throw new \InvalidArgumentException('Unsupported encoding. (Supported encodings: ' . implode(', ', self::$SUPPORTED_CHARSETS) . ')');
111
+		}
112
+		$this->charset = $charset;
113
+
114
+	}
115
+
116
+	/**
117
+	 * Sets the input buffer. Must be a string or stream.
118
+	 *
119
+	 * @param resource|string $input
120
+	 *
121
+	 * @return void
122
+	 */
123
+	public function setInput($input) {
124
+
125
+		// Resetting the parser
126
+		$this->lineIndex = 0;
127
+		$this->startLine = 0;
128
+
129
+		if (is_string($input)) {
130
+			// Convering to a stream.
131
+			$stream = fopen('php://temp', 'r+');
132
+			fwrite($stream, $input);
133
+			rewind($stream);
134
+			$this->input = $stream;
135
+		} elseif (is_resource($input)) {
136
+			$this->input = $input;
137
+		} else {
138
+			throw new \InvalidArgumentException('This parser can only read from strings or streams.');
139
+		}
140
+
141
+	}
142
+
143
+	/**
144
+	 * Parses an entire document.
145
+	 *
146
+	 * @return void
147
+	 */
148
+	protected function parseDocument() {
149
+
150
+		$line = $this->readLine();
151
+
152
+		// BOM is ZERO WIDTH NO-BREAK SPACE (U+FEFF).
153
+		// It's 0xEF 0xBB 0xBF in UTF-8 hex.
154
+		if (3 <= strlen($line)
155
+			&& ord($line[0]) === 0xef
156
+			&& ord($line[1]) === 0xbb
157
+			&& ord($line[2]) === 0xbf) {
158
+			$line = substr($line, 3);
159
+		}
160
+
161
+		switch (strtoupper($line)) {
162
+			case 'BEGIN:VCALENDAR' :
163
+				$class = VCalendar::$componentMap['VCALENDAR'];
164
+				break;
165
+			case 'BEGIN:VCARD' :
166
+				$class = VCard::$componentMap['VCARD'];
167
+				break;
168
+			default :
169
+				throw new ParseException('This parser only supports VCARD and VCALENDAR files');
170
+		}
171
+
172
+		$this->root = new $class([], false);
173
+
174
+		while (true) {
175
+
176
+			// Reading until we hit END:
177
+			$line = $this->readLine();
178
+			if (strtoupper(substr($line, 0, 4)) === 'END:') {
179
+				break;
180
+			}
181
+			$result = $this->parseLine($line);
182
+			if ($result) {
183
+				$this->root->add($result);
184
+			}
185
+
186
+		}
187
+
188
+		$name = strtoupper(substr($line, 4));
189
+		if ($name !== $this->root->name) {
190
+			throw new ParseException('Invalid MimeDir file. expected: "END:' . $this->root->name . '" got: "END:' . $name . '"');
191
+		}
192
+
193
+	}
194
+
195
+	/**
196
+	 * Parses a line, and if it hits a component, it will also attempt to parse
197
+	 * the entire component.
198
+	 *
199
+	 * @param string $line Unfolded line
200
+	 *
201
+	 * @return Node
202
+	 */
203
+	protected function parseLine($line) {
204
+
205
+		// Start of a new component
206
+		if (strtoupper(substr($line, 0, 6)) === 'BEGIN:') {
207
+
208
+			$component = $this->root->createComponent(substr($line, 6), [], false);
209
+
210
+			while (true) {
211
+
212
+				// Reading until we hit END:
213
+				$line = $this->readLine();
214
+				if (strtoupper(substr($line, 0, 4)) === 'END:') {
215
+					break;
216
+				}
217
+				$result = $this->parseLine($line);
218
+				if ($result) {
219
+					$component->add($result);
220
+				}
221
+
222
+			}
223
+
224
+			$name = strtoupper(substr($line, 4));
225
+			if ($name !== $component->name) {
226
+				throw new ParseException('Invalid MimeDir file. expected: "END:' . $component->name . '" got: "END:' . $name . '"');
227
+			}
228
+
229
+			return $component;
230
+
231
+		} else {
232
+
233
+			// Property reader
234
+			$property = $this->readProperty($line);
235
+			if (!$property) {
236
+				// Ignored line
237
+				return false;
238
+			}
239
+			return $property;
240
+
241
+		}
242
+
243
+	}
244
+
245
+	/**
246
+	 * We need to look ahead 1 line every time to see if we need to 'unfold'
247
+	 * the next line.
248
+	 *
249
+	 * If that was not the case, we store it here.
250
+	 *
251
+	 * @var null|string
252
+	 */
253
+	protected $lineBuffer;
254
+
255
+	/**
256
+	 * The real current line number.
257
+	 */
258
+	protected $lineIndex = 0;
259
+
260
+	/**
261
+	 * In the case of unfolded lines, this property holds the line number for
262
+	 * the start of the line.
263
+	 *
264
+	 * @var int
265
+	 */
266
+	protected $startLine = 0;
267
+
268
+	/**
269
+	 * Contains a 'raw' representation of the current line.
270
+	 *
271
+	 * @var string
272
+	 */
273
+	protected $rawLine;
274
+
275
+	/**
276
+	 * Reads a single line from the buffer.
277
+	 *
278
+	 * This method strips any newlines and also takes care of unfolding.
279
+	 *
280
+	 * @throws \Sabre\VObject\EofException
281
+	 *
282
+	 * @return string
283
+	 */
284
+	protected function readLine() {
285
+
286
+		if (!is_null($this->lineBuffer)) {
287
+			$rawLine = $this->lineBuffer;
288
+			$this->lineBuffer = null;
289
+		} else {
290
+			do {
291
+				$eof = feof($this->input);
292
+
293
+				$rawLine = fgets($this->input);
294
+
295
+				if ($eof || (feof($this->input) && $rawLine === false)) {
296
+					throw new EofException('End of document reached prematurely');
297
+				}
298
+				if ($rawLine === false) {
299
+					throw new ParseException('Error reading from input stream');
300
+				}
301
+				$rawLine = rtrim($rawLine, "\r\n");
302
+			} while ($rawLine === ''); // Skipping empty lines
303
+			$this->lineIndex++;
304
+		}
305
+		$line = $rawLine;
306
+
307
+		$this->startLine = $this->lineIndex;
308
+
309
+		// Looking ahead for folded lines.
310
+		while (true) {
311
+
312
+			$nextLine = rtrim(fgets($this->input), "\r\n");
313
+			$this->lineIndex++;
314
+			if (!$nextLine) {
315
+				break;
316
+			}
317
+			if ($nextLine[0] === "\t" || $nextLine[0] === " ") {
318
+				$line .= substr($nextLine, 1);
319
+				$rawLine .= "\n " . substr($nextLine, 1);
320
+			} else {
321
+				$this->lineBuffer = $nextLine;
322
+				break;
323
+			}
324
+
325
+		}
326
+		$this->rawLine = $rawLine;
327
+		return $line;
328
+
329
+	}
330
+
331
+	/**
332
+	 * Reads a property or component from a line.
333
+	 *
334
+	 * @return void
335
+	 */
336
+	protected function readProperty($line) {
337
+
338
+		if ($this->options & self::OPTION_FORGIVING) {
339
+			$propNameToken = 'A-Z0-9\-\._\\/';
340
+		} else {
341
+			$propNameToken = 'A-Z0-9\-\.';
342
+		}
343
+
344
+		$paramNameToken = 'A-Z0-9\-';
345
+		$safeChar = '^";:,';
346
+		$qSafeChar = '^"';
347
+
348
+		$regex = "/
349 349
             ^(?P<name> [$propNameToken]+ ) (?=[;:])        # property name
350 350
             |
351 351
             (?<=:)(?P<propValue> .+)$                      # property value
@@ -358,339 +358,339 @@  discard block
 block discarded – undo
358 358
             ) (?=[;:,])
359 359
             /xi";
360 360
 
361
-        //echo $regex, "\n"; die();
362
-        preg_match_all($regex, $line, $matches,  PREG_SET_ORDER);
363
-
364
-        $property = [
365
-            'name'       => null,
366
-            'parameters' => [],
367
-            'value'      => null
368
-        ];
369
-
370
-        $lastParam = null;
371
-
372
-        /**
373
-         * Looping through all the tokens.
374
-         *
375
-         * Note that we are looping through them in reverse order, because if a
376
-         * sub-pattern matched, the subsequent named patterns will not show up
377
-         * in the result.
378
-         */
379
-        foreach ($matches as $match) {
380
-
381
-            if (isset($match['paramValue'])) {
382
-                if ($match['paramValue'] && $match['paramValue'][0] === '"') {
383
-                    $value = substr($match['paramValue'], 1, -1);
384
-                } else {
385
-                    $value = $match['paramValue'];
386
-                }
387
-
388
-                $value = $this->unescapeParam($value);
389
-
390
-                if (is_null($lastParam)) {
391
-                    throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
392
-                }
393
-                if (is_null($property['parameters'][$lastParam])) {
394
-                    $property['parameters'][$lastParam] = $value;
395
-                } elseif (is_array($property['parameters'][$lastParam])) {
396
-                    $property['parameters'][$lastParam][] = $value;
397
-                } else {
398
-                    $property['parameters'][$lastParam] = [
399
-                        $property['parameters'][$lastParam],
400
-                        $value
401
-                    ];
402
-                }
403
-                continue;
404
-            }
405
-            if (isset($match['paramName'])) {
406
-                $lastParam = strtoupper($match['paramName']);
407
-                if (!isset($property['parameters'][$lastParam])) {
408
-                    $property['parameters'][$lastParam] = null;
409
-                }
410
-                continue;
411
-            }
412
-            if (isset($match['propValue'])) {
413
-                $property['value'] = $match['propValue'];
414
-                continue;
415
-            }
416
-            if (isset($match['name']) && $match['name']) {
417
-                $property['name'] = strtoupper($match['name']);
418
-                continue;
419
-            }
420
-
421
-            // @codeCoverageIgnoreStart
422
-            throw new \LogicException('This code should not be reachable');
423
-            // @codeCoverageIgnoreEnd
424
-
425
-        }
426
-
427
-        if (is_null($property['value'])) {
428
-            $property['value'] = '';
429
-        }
430
-        if (!$property['name']) {
431
-            if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
432
-                return false;
433
-            }
434
-            throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
435
-        }
436
-
437
-        // vCard 2.1 states that parameters may appear without a name, and only
438
-        // a value. We can deduce the value based on it's name.
439
-        //
440
-        // Our parser will get those as parameters without a value instead, so
441
-        // we're filtering these parameters out first.
442
-        $namedParameters = [];
443
-        $namelessParameters = [];
444
-
445
-        foreach ($property['parameters'] as $name => $value) {
446
-            if (!is_null($value)) {
447
-                $namedParameters[$name] = $value;
448
-            } else {
449
-                $namelessParameters[] = $name;
450
-            }
451
-        }
452
-
453
-        $propObj = $this->root->createProperty($property['name'], null, $namedParameters);
454
-
455
-        foreach ($namelessParameters as $namelessParameter) {
456
-            $propObj->add(null, $namelessParameter);
457
-        }
458
-
459
-        if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
460
-            $propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
461
-        } else {
462
-            $charset = $this->charset;
463
-            if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) {
464
-                // vCard 2.1 allows the character set to be specified per property.
465
-                $charset = (string)$propObj['CHARSET'];
466
-            }
467
-            switch ($charset) {
468
-                case 'UTF-8' :
469
-                    break;
470
-                case 'ISO-8859-1' :
471
-                    $property['value'] = utf8_encode($property['value']);
472
-                    break;
473
-                case 'Windows-1252' :
474
-                    $property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
475
-                    break;
476
-                default :
477
-                    throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']);
478
-            }
479
-            $propObj->setRawMimeDirValue($property['value']);
480
-        }
481
-
482
-        return $propObj;
483
-
484
-    }
485
-
486
-    /**
487
-     * Unescapes a property value.
488
-     *
489
-     * vCard 2.1 says:
490
-     *   * Semi-colons must be escaped in some property values, specifically
491
-     *     ADR, ORG and N.
492
-     *   * Semi-colons must be escaped in parameter values, because semi-colons
493
-     *     are also use to separate values.
494
-     *   * No mention of escaping backslashes with another backslash.
495
-     *   * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
496
-     *     span values over more than 1 line.
497
-     *
498
-     * vCard 3.0 says:
499
-     *   * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
500
-     *     escaped, all time time.
501
-     *   * Comma's are used for delimeters in multiple values
502
-     *   * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
503
-     *     as in some properties semi-colon is used for separators.
504
-     *   * Properties using semi-colons: N, ADR, GEO, ORG
505
-     *   * Both ADR and N's individual parts may be broken up further with a
506
-     *     comma.
507
-     *   * Properties using commas: NICKNAME, CATEGORIES
508
-     *
509
-     * vCard 4.0 (rfc6350) says:
510
-     *   * Commas must be escaped.
511
-     *   * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
512
-     *     delimiter, depending on the property.
513
-     *   * Backslashes must be escaped
514
-     *   * Newlines must be escaped as either \N or \n.
515
-     *   * Some compound properties may contain multiple parts themselves, so a
516
-     *     comma within a semi-colon delimited property may also be unescaped
517
-     *     to denote multiple parts _within_ the compound property.
518
-     *   * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
519
-     *   * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
520
-     *
521
-     * Even though the spec says that commas must always be escaped, the
522
-     * example for GEO in Section 6.5.2 seems to violate this.
523
-     *
524
-     * iCalendar 2.0 (rfc5545) says:
525
-     *   * Commas or semi-colons may be used as delimiters, depending on the
526
-     *     property.
527
-     *   * Commas, semi-colons, backslashes, newline (\N or \n) are always
528
-     *     escaped, unless they are delimiters.
529
-     *   * Colons shall not be escaped.
530
-     *   * Commas can be considered the 'default delimiter' and is described as
531
-     *     the delimiter in cases where the order of the multiple values is
532
-     *     insignificant.
533
-     *   * Semi-colons are described as the delimiter for 'structured values'.
534
-     *     They are specifically used in Semi-colons are used as a delimiter in
535
-     *     REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
536
-     *
537
-     * Now for the parameters
538
-     *
539
-     * If delimiter is not set (null) this method will just return a string.
540
-     * If it's a comma or a semi-colon the string will be split on those
541
-     * characters, and always return an array.
542
-     *
543
-     * @param string $input
544
-     * @param string $delimiter
545
-     *
546
-     * @return string|string[]
547
-     */
548
-    static function unescapeValue($input, $delimiter = ';') {
549
-
550
-        $regex = '#  (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
551
-        if ($delimiter) {
552
-            $regex .= ' | (' . $delimiter . ')';
553
-        }
554
-        $regex .= ') #x';
555
-
556
-        $matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
557
-
558
-        $resultArray = [];
559
-        $result = '';
560
-
561
-        foreach ($matches as $match) {
562
-
563
-            switch ($match) {
564
-                case '\\\\' :
565
-                    $result .= '\\';
566
-                    break;
567
-                case '\N' :
568
-                case '\n' :
569
-                    $result .= "\n";
570
-                    break;
571
-                case '\;' :
572
-                    $result .= ';';
573
-                    break;
574
-                case '\,' :
575
-                    $result .= ',';
576
-                    break;
577
-                case $delimiter :
578
-                    $resultArray[] = $result;
579
-                    $result = '';
580
-                    break;
581
-                default :
582
-                    $result .= $match;
583
-                    break;
584
-
585
-            }
586
-
587
-        }
588
-
589
-        $resultArray[] = $result;
590
-        return $delimiter ? $resultArray : $result;
591
-
592
-    }
593
-
594
-    /**
595
-     * Unescapes a parameter value.
596
-     *
597
-     * vCard 2.1:
598
-     *   * Does not mention a mechanism for this. In addition, double quotes
599
-     *     are never used to wrap values.
600
-     *   * This means that parameters can simply not contain colons or
601
-     *     semi-colons.
602
-     *
603
-     * vCard 3.0 (rfc2425, rfc2426):
604
-     *   * Parameters _may_ be surrounded by double quotes.
605
-     *   * If this is not the case, semi-colon, colon and comma may simply not
606
-     *     occur (the comma used for multiple parameter values though).
607
-     *   * If it is surrounded by double-quotes, it may simply not contain
608
-     *     double-quotes.
609
-     *   * This means that a parameter can in no case encode double-quotes, or
610
-     *     newlines.
611
-     *
612
-     * vCard 4.0 (rfc6350)
613
-     *   * Behavior seems to be identical to vCard 3.0
614
-     *
615
-     * iCalendar 2.0 (rfc5545)
616
-     *   * Behavior seems to be identical to vCard 3.0
617
-     *
618
-     * Parameter escaping mechanism (rfc6868) :
619
-     *   * This rfc describes a new way to escape parameter values.
620
-     *   * New-line is encoded as ^n
621
-     *   * ^ is encoded as ^^.
622
-     *   * " is encoded as ^'
623
-     *
624
-     * @param string $input
625
-     *
626
-     * @return void
627
-     */
628
-    private function unescapeParam($input) {
629
-
630
-        return
631
-            preg_replace_callback(
632
-                '#(\^(\^|n|\'))#',
633
-                function($matches) {
634
-                    switch ($matches[2]) {
635
-                        case 'n' :
636
-                            return "\n";
637
-                        case '^' :
638
-                            return '^';
639
-                        case '\'' :
640
-                            return '"';
641
-
642
-                    // @codeCoverageIgnoreStart
643
-                    }
644
-                    // @codeCoverageIgnoreEnd
645
-                },
646
-                $input
647
-            );
648
-    }
649
-
650
-    /**
651
-     * Gets the full quoted printable value.
652
-     *
653
-     * We need a special method for this, because newlines have both a meaning
654
-     * in vCards, and in QuotedPrintable.
655
-     *
656
-     * This method does not do any decoding.
657
-     *
658
-     * @return string
659
-     */
660
-    private function extractQuotedPrintableValue() {
661
-
662
-        // We need to parse the raw line again to get the start of the value.
663
-        //
664
-        // We are basically looking for the first colon (:), but we need to
665
-        // skip over the parameters first, as they may contain one.
666
-        $regex = '/^
361
+		//echo $regex, "\n"; die();
362
+		preg_match_all($regex, $line, $matches,  PREG_SET_ORDER);
363
+
364
+		$property = [
365
+			'name'       => null,
366
+			'parameters' => [],
367
+			'value'      => null
368
+		];
369
+
370
+		$lastParam = null;
371
+
372
+		/**
373
+		 * Looping through all the tokens.
374
+		 *
375
+		 * Note that we are looping through them in reverse order, because if a
376
+		 * sub-pattern matched, the subsequent named patterns will not show up
377
+		 * in the result.
378
+		 */
379
+		foreach ($matches as $match) {
380
+
381
+			if (isset($match['paramValue'])) {
382
+				if ($match['paramValue'] && $match['paramValue'][0] === '"') {
383
+					$value = substr($match['paramValue'], 1, -1);
384
+				} else {
385
+					$value = $match['paramValue'];
386
+				}
387
+
388
+				$value = $this->unescapeParam($value);
389
+
390
+				if (is_null($lastParam)) {
391
+					throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
392
+				}
393
+				if (is_null($property['parameters'][$lastParam])) {
394
+					$property['parameters'][$lastParam] = $value;
395
+				} elseif (is_array($property['parameters'][$lastParam])) {
396
+					$property['parameters'][$lastParam][] = $value;
397
+				} else {
398
+					$property['parameters'][$lastParam] = [
399
+						$property['parameters'][$lastParam],
400
+						$value
401
+					];
402
+				}
403
+				continue;
404
+			}
405
+			if (isset($match['paramName'])) {
406
+				$lastParam = strtoupper($match['paramName']);
407
+				if (!isset($property['parameters'][$lastParam])) {
408
+					$property['parameters'][$lastParam] = null;
409
+				}
410
+				continue;
411
+			}
412
+			if (isset($match['propValue'])) {
413
+				$property['value'] = $match['propValue'];
414
+				continue;
415
+			}
416
+			if (isset($match['name']) && $match['name']) {
417
+				$property['name'] = strtoupper($match['name']);
418
+				continue;
419
+			}
420
+
421
+			// @codeCoverageIgnoreStart
422
+			throw new \LogicException('This code should not be reachable');
423
+			// @codeCoverageIgnoreEnd
424
+
425
+		}
426
+
427
+		if (is_null($property['value'])) {
428
+			$property['value'] = '';
429
+		}
430
+		if (!$property['name']) {
431
+			if ($this->options & self::OPTION_IGNORE_INVALID_LINES) {
432
+				return false;
433
+			}
434
+			throw new ParseException('Invalid Mimedir file. Line starting at ' . $this->startLine . ' did not follow iCalendar/vCard conventions');
435
+		}
436
+
437
+		// vCard 2.1 states that parameters may appear without a name, and only
438
+		// a value. We can deduce the value based on it's name.
439
+		//
440
+		// Our parser will get those as parameters without a value instead, so
441
+		// we're filtering these parameters out first.
442
+		$namedParameters = [];
443
+		$namelessParameters = [];
444
+
445
+		foreach ($property['parameters'] as $name => $value) {
446
+			if (!is_null($value)) {
447
+				$namedParameters[$name] = $value;
448
+			} else {
449
+				$namelessParameters[] = $name;
450
+			}
451
+		}
452
+
453
+		$propObj = $this->root->createProperty($property['name'], null, $namedParameters);
454
+
455
+		foreach ($namelessParameters as $namelessParameter) {
456
+			$propObj->add(null, $namelessParameter);
457
+		}
458
+
459
+		if (strtoupper($propObj['ENCODING']) === 'QUOTED-PRINTABLE') {
460
+			$propObj->setQuotedPrintableValue($this->extractQuotedPrintableValue());
461
+		} else {
462
+			$charset = $this->charset;
463
+			if ($this->root->getDocumentType() === Document::VCARD21 && isset($propObj['CHARSET'])) {
464
+				// vCard 2.1 allows the character set to be specified per property.
465
+				$charset = (string)$propObj['CHARSET'];
466
+			}
467
+			switch ($charset) {
468
+				case 'UTF-8' :
469
+					break;
470
+				case 'ISO-8859-1' :
471
+					$property['value'] = utf8_encode($property['value']);
472
+					break;
473
+				case 'Windows-1252' :
474
+					$property['value'] = mb_convert_encoding($property['value'], 'UTF-8', $charset);
475
+					break;
476
+				default :
477
+					throw new ParseException('Unsupported CHARSET: ' . $propObj['CHARSET']);
478
+			}
479
+			$propObj->setRawMimeDirValue($property['value']);
480
+		}
481
+
482
+		return $propObj;
483
+
484
+	}
485
+
486
+	/**
487
+	 * Unescapes a property value.
488
+	 *
489
+	 * vCard 2.1 says:
490
+	 *   * Semi-colons must be escaped in some property values, specifically
491
+	 *     ADR, ORG and N.
492
+	 *   * Semi-colons must be escaped in parameter values, because semi-colons
493
+	 *     are also use to separate values.
494
+	 *   * No mention of escaping backslashes with another backslash.
495
+	 *   * newlines are not escaped either, instead QUOTED-PRINTABLE is used to
496
+	 *     span values over more than 1 line.
497
+	 *
498
+	 * vCard 3.0 says:
499
+	 *   * (rfc2425) Backslashes, newlines (\n or \N) and comma's must be
500
+	 *     escaped, all time time.
501
+	 *   * Comma's are used for delimeters in multiple values
502
+	 *   * (rfc2426) Adds to to this that the semi-colon MUST also be escaped,
503
+	 *     as in some properties semi-colon is used for separators.
504
+	 *   * Properties using semi-colons: N, ADR, GEO, ORG
505
+	 *   * Both ADR and N's individual parts may be broken up further with a
506
+	 *     comma.
507
+	 *   * Properties using commas: NICKNAME, CATEGORIES
508
+	 *
509
+	 * vCard 4.0 (rfc6350) says:
510
+	 *   * Commas must be escaped.
511
+	 *   * Semi-colons may be escaped, an unescaped semi-colon _may_ be a
512
+	 *     delimiter, depending on the property.
513
+	 *   * Backslashes must be escaped
514
+	 *   * Newlines must be escaped as either \N or \n.
515
+	 *   * Some compound properties may contain multiple parts themselves, so a
516
+	 *     comma within a semi-colon delimited property may also be unescaped
517
+	 *     to denote multiple parts _within_ the compound property.
518
+	 *   * Text-properties using semi-colons: N, ADR, ORG, CLIENTPIDMAP.
519
+	 *   * Text-properties using commas: NICKNAME, RELATED, CATEGORIES, PID.
520
+	 *
521
+	 * Even though the spec says that commas must always be escaped, the
522
+	 * example for GEO in Section 6.5.2 seems to violate this.
523
+	 *
524
+	 * iCalendar 2.0 (rfc5545) says:
525
+	 *   * Commas or semi-colons may be used as delimiters, depending on the
526
+	 *     property.
527
+	 *   * Commas, semi-colons, backslashes, newline (\N or \n) are always
528
+	 *     escaped, unless they are delimiters.
529
+	 *   * Colons shall not be escaped.
530
+	 *   * Commas can be considered the 'default delimiter' and is described as
531
+	 *     the delimiter in cases where the order of the multiple values is
532
+	 *     insignificant.
533
+	 *   * Semi-colons are described as the delimiter for 'structured values'.
534
+	 *     They are specifically used in Semi-colons are used as a delimiter in
535
+	 *     REQUEST-STATUS, RRULE, GEO and EXRULE. EXRULE is deprecated however.
536
+	 *
537
+	 * Now for the parameters
538
+	 *
539
+	 * If delimiter is not set (null) this method will just return a string.
540
+	 * If it's a comma or a semi-colon the string will be split on those
541
+	 * characters, and always return an array.
542
+	 *
543
+	 * @param string $input
544
+	 * @param string $delimiter
545
+	 *
546
+	 * @return string|string[]
547
+	 */
548
+	static function unescapeValue($input, $delimiter = ';') {
549
+
550
+		$regex = '#  (?: (\\\\ (?: \\\\ | N | n | ; | , ) )';
551
+		if ($delimiter) {
552
+			$regex .= ' | (' . $delimiter . ')';
553
+		}
554
+		$regex .= ') #x';
555
+
556
+		$matches = preg_split($regex, $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
557
+
558
+		$resultArray = [];
559
+		$result = '';
560
+
561
+		foreach ($matches as $match) {
562
+
563
+			switch ($match) {
564
+				case '\\\\' :
565
+					$result .= '\\';
566
+					break;
567
+				case '\N' :
568
+				case '\n' :
569
+					$result .= "\n";
570
+					break;
571
+				case '\;' :
572
+					$result .= ';';
573
+					break;
574
+				case '\,' :
575
+					$result .= ',';
576
+					break;
577
+				case $delimiter :
578
+					$resultArray[] = $result;
579
+					$result = '';
580
+					break;
581
+				default :
582
+					$result .= $match;
583
+					break;
584
+
585
+			}
586
+
587
+		}
588
+
589
+		$resultArray[] = $result;
590
+		return $delimiter ? $resultArray : $result;
591
+
592
+	}
593
+
594
+	/**
595
+	 * Unescapes a parameter value.
596
+	 *
597
+	 * vCard 2.1:
598
+	 *   * Does not mention a mechanism for this. In addition, double quotes
599
+	 *     are never used to wrap values.
600
+	 *   * This means that parameters can simply not contain colons or
601
+	 *     semi-colons.
602
+	 *
603
+	 * vCard 3.0 (rfc2425, rfc2426):
604
+	 *   * Parameters _may_ be surrounded by double quotes.
605
+	 *   * If this is not the case, semi-colon, colon and comma may simply not
606
+	 *     occur (the comma used for multiple parameter values though).
607
+	 *   * If it is surrounded by double-quotes, it may simply not contain
608
+	 *     double-quotes.
609
+	 *   * This means that a parameter can in no case encode double-quotes, or
610
+	 *     newlines.
611
+	 *
612
+	 * vCard 4.0 (rfc6350)
613
+	 *   * Behavior seems to be identical to vCard 3.0
614
+	 *
615
+	 * iCalendar 2.0 (rfc5545)
616
+	 *   * Behavior seems to be identical to vCard 3.0
617
+	 *
618
+	 * Parameter escaping mechanism (rfc6868) :
619
+	 *   * This rfc describes a new way to escape parameter values.
620
+	 *   * New-line is encoded as ^n
621
+	 *   * ^ is encoded as ^^.
622
+	 *   * " is encoded as ^'
623
+	 *
624
+	 * @param string $input
625
+	 *
626
+	 * @return void
627
+	 */
628
+	private function unescapeParam($input) {
629
+
630
+		return
631
+			preg_replace_callback(
632
+				'#(\^(\^|n|\'))#',
633
+				function($matches) {
634
+					switch ($matches[2]) {
635
+						case 'n' :
636
+							return "\n";
637
+						case '^' :
638
+							return '^';
639
+						case '\'' :
640
+							return '"';
641
+
642
+					// @codeCoverageIgnoreStart
643
+					}
644
+					// @codeCoverageIgnoreEnd
645
+				},
646
+				$input
647
+			);
648
+	}
649
+
650
+	/**
651
+	 * Gets the full quoted printable value.
652
+	 *
653
+	 * We need a special method for this, because newlines have both a meaning
654
+	 * in vCards, and in QuotedPrintable.
655
+	 *
656
+	 * This method does not do any decoding.
657
+	 *
658
+	 * @return string
659
+	 */
660
+	private function extractQuotedPrintableValue() {
661
+
662
+		// We need to parse the raw line again to get the start of the value.
663
+		//
664
+		// We are basically looking for the first colon (:), but we need to
665
+		// skip over the parameters first, as they may contain one.
666
+		$regex = '/^
667 667
             (?: [^:])+ # Anything but a colon
668 668
             (?: "[^"]")* # A parameter in double quotes
669 669
             : # start of the value we really care about
670 670
             (.*)$
671 671
         /xs';
672 672
 
673
-        preg_match($regex, $this->rawLine, $matches);
673
+		preg_match($regex, $this->rawLine, $matches);
674 674
 
675
-        $value = $matches[1];
676
-        // Removing the first whitespace character from every line. Kind of
677
-        // like unfolding, but we keep the newline.
678
-        $value = str_replace("\n ", "\n", $value);
675
+		$value = $matches[1];
676
+		// Removing the first whitespace character from every line. Kind of
677
+		// like unfolding, but we keep the newline.
678
+		$value = str_replace("\n ", "\n", $value);
679 679
 
680
-        // Microsoft products don't always correctly fold lines, they may be
681
-        // missing a whitespace. So if 'forgiving' is turned on, we will take
682
-        // those as well.
683
-        if ($this->options & self::OPTION_FORGIVING) {
684
-            while (substr($value, -1) === '=') {
685
-                // Reading the line
686
-                $this->readLine();
687
-                // Grabbing the raw form
688
-                $value .= "\n" . $this->rawLine;
689
-            }
690
-        }
680
+		// Microsoft products don't always correctly fold lines, they may be
681
+		// missing a whitespace. So if 'forgiving' is turned on, we will take
682
+		// those as well.
683
+		if ($this->options & self::OPTION_FORGIVING) {
684
+			while (substr($value, -1) === '=') {
685
+				// Reading the line
686
+				$this->readLine();
687
+				// Grabbing the raw form
688
+				$value .= "\n" . $this->rawLine;
689
+			}
690
+		}
691 691
 
692
-        return $value;
692
+		return $value;
693 693
 
694
-    }
694
+	}
695 695
 
696 696
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/Event/Promise/functions.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -31,30 +31,30 @@  discard block
 block discarded – undo
31 31
  */
32 32
 function all(array $promises) {
33 33
 
34
-    return new Promise(function($success, $fail) use ($promises) {
35
-
36
-        $successCount = 0;
37
-        $completeResult = [];
38
-
39
-        foreach ($promises as $promiseIndex => $subPromise) {
40
-
41
-            $subPromise->then(
42
-                function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) {
43
-                    $completeResult[$promiseIndex] = $result;
44
-                    $successCount++;
45
-                    if ($successCount === count($promises)) {
46
-                        $success($completeResult);
47
-                    }
48
-                    return $result;
49
-                }
50
-            )->error(
51
-                function($reason) use ($fail) {
52
-                    $fail($reason);
53
-                }
54
-            );
55
-
56
-        }
57
-    });
34
+	return new Promise(function($success, $fail) use ($promises) {
35
+
36
+		$successCount = 0;
37
+		$completeResult = [];
38
+
39
+		foreach ($promises as $promiseIndex => $subPromise) {
40
+
41
+			$subPromise->then(
42
+				function($result) use ($promiseIndex, &$completeResult, &$successCount, $success, $promises) {
43
+					$completeResult[$promiseIndex] = $result;
44
+					$successCount++;
45
+					if ($successCount === count($promises)) {
46
+						$success($completeResult);
47
+					}
48
+					return $result;
49
+				}
50
+			)->error(
51
+				function($reason) use ($fail) {
52
+					$fail($reason);
53
+				}
54
+			);
55
+
56
+		}
57
+	});
58 58
 
59 59
 }
60 60
 
@@ -70,31 +70,31 @@  discard block
 block discarded – undo
70 70
  */
71 71
 function race(array $promises) {
72 72
 
73
-    return new Promise(function($success, $fail) use ($promises) {
73
+	return new Promise(function($success, $fail) use ($promises) {
74 74
 
75
-        $alreadyDone = false;
76
-        foreach ($promises as $promise) {
75
+		$alreadyDone = false;
76
+		foreach ($promises as $promise) {
77 77
 
78
-            $promise->then(
79
-                function($result) use ($success, &$alreadyDone) {
80
-                    if ($alreadyDone) {
81
-                        return;
82
-                    }
83
-                    $alreadyDone = true;
84
-                    $success($result);
85
-                },
86
-                function($reason) use ($fail, &$alreadyDone) {
87
-                    if ($alreadyDone) {
88
-                        return;
89
-                    }
90
-                    $alreadyDone = true;
91
-                    $fail($reason);
92
-                }
93
-            );
78
+			$promise->then(
79
+				function($result) use ($success, &$alreadyDone) {
80
+					if ($alreadyDone) {
81
+						return;
82
+					}
83
+					$alreadyDone = true;
84
+					$success($result);
85
+				},
86
+				function($reason) use ($fail, &$alreadyDone) {
87
+					if ($alreadyDone) {
88
+						return;
89
+					}
90
+					$alreadyDone = true;
91
+					$fail($reason);
92
+				}
93
+			);
94 94
 
95
-        }
95
+		}
96 96
 
97
-    });
97
+	});
98 98
 
99 99
 }
100 100
 
@@ -110,13 +110,13 @@  discard block
 block discarded – undo
110 110
  */
111 111
 function resolve($value) {
112 112
 
113
-    if ($value instanceof Promise) {
114
-        return $value->then();
115
-    } else {
116
-        $promise = new Promise();
117
-        $promise->fulfill($value);
118
-        return $promise;
119
-    }
113
+	if ($value instanceof Promise) {
114
+		return $value->then();
115
+	} else {
116
+		$promise = new Promise();
117
+		$promise->fulfill($value);
118
+		return $promise;
119
+	}
120 120
 
121 121
 }
122 122
 
@@ -128,8 +128,8 @@  discard block
 block discarded – undo
128 128
  */
129 129
 function reject($reason) {
130 130
 
131
-    $promise = new Promise();
132
-    $promise->reject($reason);
133
-    return $promise;
131
+	$promise = new Promise();
132
+	$promise->reject($reason);
133
+	return $promise;
134 134
 
135 135
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/FreeBusyGenerator.php 2 patches
Unused Use Statements   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -2,8 +2,8 @@
 block discarded – undo
2 2
 
3 3
 namespace Sabre\VObject\Recur;
4 4
 
5
-use DateTimeInterface;
6 5
 use DateTimeImmutable;
6
+use DateTimeInterface;
7 7
 use Iterator;
8 8
 use Sabre\VObject\DateTimeParser;
9 9
 use Sabre\VObject\InvalidDataException;
Please login to merge, or discard this patch.
Indentation   +553 added lines, -553 removed lines patch added patch discarded remove patch
@@ -25,580 +25,580 @@
 block discarded – undo
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, it's 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
-    /**
107
-     * Sets the VCALENDAR object.
108
-     *
109
-     * If this is set, it will not be generated for you. You are responsible
110
-     * for setting things like the METHOD, CALSCALE, VERSION, etc..
111
-     *
112
-     * The VFREEBUSY object will be automatically added though.
113
-     *
114
-     * @param Document $vcalendar
115
-     * @return void
116
-     */
117
-    public function setBaseObject(Document $vcalendar) {
118
-
119
-        $this->baseObject = $vcalendar;
120
-
121
-    }
122
-
123
-    /**
124
-     * Sets a VAVAILABILITY document.
125
-     *
126
-     * @param Document $vcalendar
127
-     * @return void
128
-     */
129
-    public function setVAvailability(Document $vcalendar) {
130
-
131
-        $this->vavailability = $vcalendar;
132
-
133
-    }
134
-
135
-    /**
136
-     * Sets the input objects.
137
-     *
138
-     * You must either specify a valendar object as a string, or as the parse
139
-     * Component.
140
-     * It's also possible to specify multiple objects as an array.
141
-     *
142
-     * @param mixed $objects
143
-     *
144
-     * @return void
145
-     */
146
-    public function setObjects($objects) {
147
-
148
-        if (!is_array($objects)) {
149
-            $objects = [$objects];
150
-        }
151
-
152
-        $this->objects = [];
153
-        foreach ($objects as $object) {
154
-
155
-            if (is_string($object)) {
156
-                $this->objects[] = Reader::read($object);
157
-            } elseif ($object instanceof Component) {
158
-                $this->objects[] = $object;
159
-            } else {
160
-                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
161
-            }
162
-
163
-        }
164
-
165
-    }
166
-
167
-    /**
168
-     * Sets the time range.
169
-     *
170
-     * Any freebusy object falling outside of this time range will be ignored.
171
-     *
172
-     * @param DateTimeInterface $start
173
-     * @param DateTimeInterface $end
174
-     *
175
-     * @return void
176
-     */
177
-    public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) {
178
-
179
-        if (!$start) {
180
-            $start = new DateTimeImmutable(Settings::$minDate);
181
-        }
182
-        if (!$end) {
183
-            $end = new DateTimeImmutable(Settings::$maxDate);
184
-        }
185
-        $this->start = $start;
186
-        $this->end = $end;
187
-
188
-    }
189
-
190
-    /**
191
-     * Sets the reference timezone for floating times.
192
-     *
193
-     * @param DateTimeZone $timeZone
194
-     *
195
-     * @return void
196
-     */
197
-    public function setTimeZone(DateTimeZone $timeZone) {
198
-
199
-        $this->timeZone = $timeZone;
200
-
201
-    }
202
-
203
-    /**
204
-     * Parses the input data and returns a correct VFREEBUSY object, wrapped in
205
-     * a VCALENDAR.
206
-     *
207
-     * @return Component
208
-     */
209
-    public function getResult() {
210
-
211
-        $fbData = new FreeBusyData(
212
-            $this->start->getTimeStamp(),
213
-            $this->end->getTimeStamp()
214
-        );
215
-        if ($this->vavailability) {
216
-
217
-            $this->calculateAvailability($fbData, $this->vavailability);
218
-
219
-        }
220
-
221
-        $this->calculateBusy($fbData, $this->objects);
222
-
223
-        return $this->generateFreeBusyCalendar($fbData);
224
-
225
-
226
-    }
227
-
228
-    /**
229
-     * This method takes a VAVAILABILITY component and figures out all the
230
-     * available times.
231
-     *
232
-     * @param FreeBusyData $fbData
233
-     * @param VCalendar $vavailability
234
-     * @return void
235
-     */
236
-    protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) {
237
-
238
-        $vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
239
-        usort(
240
-            $vavailComps,
241
-            function($a, $b) {
242
-
243
-                // We need to order the components by priority. Priority 1
244
-                // comes first, up until priority 9. Priority 0 comes after
245
-                // priority 9. No priority implies priority 0.
246
-                //
247
-                // Yes, I'm serious.
248
-                $priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0;
249
-                $priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0;
250
-
251
-                if ($priorityA === 0) $priorityA = 10;
252
-                if ($priorityB === 0) $priorityB = 10;
253
-
254
-                return $priorityA - $priorityB;
255
-
256
-            }
257
-        );
258
-
259
-        // Now we go over all the VAVAILABILITY components and figure if
260
-        // there's any we don't need to consider.
261
-        //
262
-        // This is can be because of one of two reasons: either the
263
-        // VAVAILABILITY component falls outside the time we are interested in,
264
-        // or a different VAVAILABILITY component with a higher priority has
265
-        // already completely covered the time-range.
266
-        $old = $vavailComps;
267
-        $new = [];
268
-
269
-        foreach ($old as $vavail) {
270
-
271
-            list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
272
-
273
-            // We don't care about datetimes that are earlier or later than the
274
-            // start and end of the freebusy report, so this gets normalized
275
-            // first.
276
-            if (is_null($compStart) || $compStart < $this->start) {
277
-                $compStart = $this->start;
278
-            }
279
-            if (is_null($compEnd) || $compEnd > $this->end) {
280
-                $compEnd = $this->end;
281
-            }
282
-
283
-            // If the item fell out of the timerange, we can just skip it.
284
-            if ($compStart > $this->end || $compEnd < $this->start) {
285
-                continue;
286
-            }
287
-
288
-            // Going through our existing list of components to see if there's
289
-            // a higher priority component that already fully covers this one.
290
-            foreach ($new as $higherVavail) {
291
-
292
-                list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
293
-                if (
294
-                    (is_null($higherStart) || $higherStart < $compStart) &&
295
-                    (is_null($higherEnd)   || $higherEnd > $compEnd)
296
-                ) {
297
-
298
-                    // Component is fully covered by a higher priority
299
-                    // component. We can skip this component.
300
-                    continue 2;
301
-
302
-                }
303
-
304
-            }
305
-
306
-            // We're keeping it!
307
-            $new[] = $vavail;
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, it's 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
+	/**
107
+	 * Sets the VCALENDAR object.
108
+	 *
109
+	 * If this is set, it will not be generated for you. You are responsible
110
+	 * for setting things like the METHOD, CALSCALE, VERSION, etc..
111
+	 *
112
+	 * The VFREEBUSY object will be automatically added though.
113
+	 *
114
+	 * @param Document $vcalendar
115
+	 * @return void
116
+	 */
117
+	public function setBaseObject(Document $vcalendar) {
118
+
119
+		$this->baseObject = $vcalendar;
120
+
121
+	}
122
+
123
+	/**
124
+	 * Sets a VAVAILABILITY document.
125
+	 *
126
+	 * @param Document $vcalendar
127
+	 * @return void
128
+	 */
129
+	public function setVAvailability(Document $vcalendar) {
130
+
131
+		$this->vavailability = $vcalendar;
132
+
133
+	}
134
+
135
+	/**
136
+	 * Sets the input objects.
137
+	 *
138
+	 * You must either specify a valendar object as a string, or as the parse
139
+	 * Component.
140
+	 * It's also possible to specify multiple objects as an array.
141
+	 *
142
+	 * @param mixed $objects
143
+	 *
144
+	 * @return void
145
+	 */
146
+	public function setObjects($objects) {
147
+
148
+		if (!is_array($objects)) {
149
+			$objects = [$objects];
150
+		}
151
+
152
+		$this->objects = [];
153
+		foreach ($objects as $object) {
154
+
155
+			if (is_string($object)) {
156
+				$this->objects[] = Reader::read($object);
157
+			} elseif ($object instanceof Component) {
158
+				$this->objects[] = $object;
159
+			} else {
160
+				throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component arguments to setObjects');
161
+			}
162
+
163
+		}
164
+
165
+	}
166
+
167
+	/**
168
+	 * Sets the time range.
169
+	 *
170
+	 * Any freebusy object falling outside of this time range will be ignored.
171
+	 *
172
+	 * @param DateTimeInterface $start
173
+	 * @param DateTimeInterface $end
174
+	 *
175
+	 * @return void
176
+	 */
177
+	public function setTimeRange(DateTimeInterface $start = null, DateTimeInterface $end = null) {
178
+
179
+		if (!$start) {
180
+			$start = new DateTimeImmutable(Settings::$minDate);
181
+		}
182
+		if (!$end) {
183
+			$end = new DateTimeImmutable(Settings::$maxDate);
184
+		}
185
+		$this->start = $start;
186
+		$this->end = $end;
187
+
188
+	}
189
+
190
+	/**
191
+	 * Sets the reference timezone for floating times.
192
+	 *
193
+	 * @param DateTimeZone $timeZone
194
+	 *
195
+	 * @return void
196
+	 */
197
+	public function setTimeZone(DateTimeZone $timeZone) {
198
+
199
+		$this->timeZone = $timeZone;
200
+
201
+	}
202
+
203
+	/**
204
+	 * Parses the input data and returns a correct VFREEBUSY object, wrapped in
205
+	 * a VCALENDAR.
206
+	 *
207
+	 * @return Component
208
+	 */
209
+	public function getResult() {
210
+
211
+		$fbData = new FreeBusyData(
212
+			$this->start->getTimeStamp(),
213
+			$this->end->getTimeStamp()
214
+		);
215
+		if ($this->vavailability) {
216
+
217
+			$this->calculateAvailability($fbData, $this->vavailability);
218
+
219
+		}
220
+
221
+		$this->calculateBusy($fbData, $this->objects);
222
+
223
+		return $this->generateFreeBusyCalendar($fbData);
224
+
225
+
226
+	}
227
+
228
+	/**
229
+	 * This method takes a VAVAILABILITY component and figures out all the
230
+	 * available times.
231
+	 *
232
+	 * @param FreeBusyData $fbData
233
+	 * @param VCalendar $vavailability
234
+	 * @return void
235
+	 */
236
+	protected function calculateAvailability(FreeBusyData $fbData, VCalendar $vavailability) {
237
+
238
+		$vavailComps = iterator_to_array($vavailability->VAVAILABILITY);
239
+		usort(
240
+			$vavailComps,
241
+			function($a, $b) {
242
+
243
+				// We need to order the components by priority. Priority 1
244
+				// comes first, up until priority 9. Priority 0 comes after
245
+				// priority 9. No priority implies priority 0.
246
+				//
247
+				// Yes, I'm serious.
248
+				$priorityA = isset($a->PRIORITY) ? (int)$a->PRIORITY->getValue() : 0;
249
+				$priorityB = isset($b->PRIORITY) ? (int)$b->PRIORITY->getValue() : 0;
250
+
251
+				if ($priorityA === 0) $priorityA = 10;
252
+				if ($priorityB === 0) $priorityB = 10;
253
+
254
+				return $priorityA - $priorityB;
255
+
256
+			}
257
+		);
258
+
259
+		// Now we go over all the VAVAILABILITY components and figure if
260
+		// there's any we don't need to consider.
261
+		//
262
+		// This is can be because of one of two reasons: either the
263
+		// VAVAILABILITY component falls outside the time we are interested in,
264
+		// or a different VAVAILABILITY component with a higher priority has
265
+		// already completely covered the time-range.
266
+		$old = $vavailComps;
267
+		$new = [];
268
+
269
+		foreach ($old as $vavail) {
270
+
271
+			list($compStart, $compEnd) = $vavail->getEffectiveStartEnd();
272
+
273
+			// We don't care about datetimes that are earlier or later than the
274
+			// start and end of the freebusy report, so this gets normalized
275
+			// first.
276
+			if (is_null($compStart) || $compStart < $this->start) {
277
+				$compStart = $this->start;
278
+			}
279
+			if (is_null($compEnd) || $compEnd > $this->end) {
280
+				$compEnd = $this->end;
281
+			}
282
+
283
+			// If the item fell out of the timerange, we can just skip it.
284
+			if ($compStart > $this->end || $compEnd < $this->start) {
285
+				continue;
286
+			}
287
+
288
+			// Going through our existing list of components to see if there's
289
+			// a higher priority component that already fully covers this one.
290
+			foreach ($new as $higherVavail) {
291
+
292
+				list($higherStart, $higherEnd) = $higherVavail->getEffectiveStartEnd();
293
+				if (
294
+					(is_null($higherStart) || $higherStart < $compStart) &&
295
+					(is_null($higherEnd)   || $higherEnd > $compEnd)
296
+				) {
297
+
298
+					// Component is fully covered by a higher priority
299
+					// component. We can skip this component.
300
+					continue 2;
301
+
302
+				}
303
+
304
+			}
305
+
306
+			// We're keeping it!
307
+			$new[] = $vavail;
308 308
 
309
-        }
309
+		}
310 310
 
311
-        // Lastly, we need to traverse the remaining components and fill in the
312
-        // freebusydata slots.
313
-        //
314
-        // We traverse the components in reverse, because we want the higher
315
-        // priority components to override the lower ones.
316
-        foreach (array_reverse($new) as $vavail) {
317
-
318
-            $busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
319
-            list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
320
-
321
-            // Making the component size no larger than the requested free-busy
322
-            // report range.
323
-            if (!$vavailStart || $vavailStart < $this->start) {
324
-                $vavailStart = $this->start;
325
-            }
326
-            if (!$vavailEnd || $vavailEnd > $this->end) {
327
-                $vavailEnd = $this->end;
328
-            }
329
-
330
-            // Marking the entire time range of the VAVAILABILITY component as
331
-            // busy.
332
-            $fbData->add(
333
-                $vavailStart->getTimeStamp(),
334
-                $vavailEnd->getTimeStamp(),
335
-                $busyType
336
-            );
337
-
338
-            // Looping over the AVAILABLE components.
339
-            if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) {
340
-
341
-                list($availStart, $availEnd) = $available->getEffectiveStartEnd();
342
-                $fbData->add(
343
-                    $availStart->getTimeStamp(),
344
-                    $availEnd->getTimeStamp(),
345
-                    'FREE'
346
-                );
347
-
348
-                if ($available->RRULE) {
349
-                    // Our favourite thing: recurrence!!
350
-
351
-                    $rruleIterator = new Recur\RRuleIterator(
352
-                        $available->RRULE->getValue(),
353
-                        $availStart
354
-                    );
355
-                    $rruleIterator->fastForward($vavailStart);
356
-
357
-                    $startEndDiff = $availStart->diff($availEnd);
358
-
359
-                    while ($rruleIterator->valid()) {
360
-
361
-                        $recurStart = $rruleIterator->current();
362
-                        $recurEnd = $recurStart->add($startEndDiff);
363
-
364
-                        if ($recurStart > $vavailEnd) {
365
-                            // We're beyond the legal timerange.
366
-                            break;
367
-                        }
368
-
369
-                        if ($recurEnd > $vavailEnd) {
370
-                            // Truncating the end if it exceeds the
371
-                            // VAVAILABILITY end.
372
-                            $recurEnd = $vavailEnd;
373
-                        }
374
-
375
-                        $fbData->add(
376
-                            $recurStart->getTimeStamp(),
377
-                            $recurEnd->getTimeStamp(),
378
-                            'FREE'
379
-                        );
380
-
381
-                        $rruleIterator->next();
382
-
383
-                    }
384
-                }
385
-
386
-            }
387
-
388
-        }
389
-
390
-    }
391
-
392
-    /**
393
-     * This method takes an array of iCalendar objects and applies its busy
394
-     * times on fbData.
395
-     *
396
-     * @param FreeBusyData $fbData
397
-     * @param VCalendar[] $objects
398
-     */
399
-    protected function calculateBusy(FreeBusyData $fbData, array $objects) {
400
-
401
-        foreach ($objects as $key => $object) {
402
-
403
-            foreach ($object->getBaseComponents() as $component) {
404
-
405
-                switch ($component->name) {
406
-
407
-                    case 'VEVENT' :
408
-
409
-                        $FBTYPE = 'BUSY';
410
-                        if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
411
-                            break;
412
-                        }
413
-                        if (isset($component->STATUS)) {
414
-                            $status = strtoupper($component->STATUS);
415
-                            if ($status === 'CANCELLED') {
416
-                                break;
417
-                            }
418
-                            if ($status === 'TENTATIVE') {
419
-                                $FBTYPE = 'BUSY-TENTATIVE';
420
-                            }
421
-                        }
422
-
423
-                        $times = [];
424
-
425
-                        if ($component->RRULE) {
426
-                            try {
427
-                                $iterator = new EventIterator($object, (string)$component->UID, $this->timeZone);
428
-                            } catch (NoInstancesException $e) {
429
-                                // This event is recurring, but it doesn't have a single
430
-                                // instance. We are skipping this event from the output
431
-                                // entirely.
432
-                                unset($this->objects[$key]);
433
-                                continue;
434
-                            }
435
-
436
-                            if ($this->start) {
437
-                                $iterator->fastForward($this->start);
438
-                            }
311
+		// Lastly, we need to traverse the remaining components and fill in the
312
+		// freebusydata slots.
313
+		//
314
+		// We traverse the components in reverse, because we want the higher
315
+		// priority components to override the lower ones.
316
+		foreach (array_reverse($new) as $vavail) {
317
+
318
+			$busyType = isset($vavail->BUSYTYPE) ? strtoupper($vavail->BUSYTYPE) : 'BUSY-UNAVAILABLE';
319
+			list($vavailStart, $vavailEnd) = $vavail->getEffectiveStartEnd();
320
+
321
+			// Making the component size no larger than the requested free-busy
322
+			// report range.
323
+			if (!$vavailStart || $vavailStart < $this->start) {
324
+				$vavailStart = $this->start;
325
+			}
326
+			if (!$vavailEnd || $vavailEnd > $this->end) {
327
+				$vavailEnd = $this->end;
328
+			}
329
+
330
+			// Marking the entire time range of the VAVAILABILITY component as
331
+			// busy.
332
+			$fbData->add(
333
+				$vavailStart->getTimeStamp(),
334
+				$vavailEnd->getTimeStamp(),
335
+				$busyType
336
+			);
337
+
338
+			// Looping over the AVAILABLE components.
339
+			if (isset($vavail->AVAILABLE)) foreach ($vavail->AVAILABLE as $available) {
340
+
341
+				list($availStart, $availEnd) = $available->getEffectiveStartEnd();
342
+				$fbData->add(
343
+					$availStart->getTimeStamp(),
344
+					$availEnd->getTimeStamp(),
345
+					'FREE'
346
+				);
347
+
348
+				if ($available->RRULE) {
349
+					// Our favourite thing: recurrence!!
350
+
351
+					$rruleIterator = new Recur\RRuleIterator(
352
+						$available->RRULE->getValue(),
353
+						$availStart
354
+					);
355
+					$rruleIterator->fastForward($vavailStart);
356
+
357
+					$startEndDiff = $availStart->diff($availEnd);
358
+
359
+					while ($rruleIterator->valid()) {
360
+
361
+						$recurStart = $rruleIterator->current();
362
+						$recurEnd = $recurStart->add($startEndDiff);
363
+
364
+						if ($recurStart > $vavailEnd) {
365
+							// We're beyond the legal timerange.
366
+							break;
367
+						}
368
+
369
+						if ($recurEnd > $vavailEnd) {
370
+							// Truncating the end if it exceeds the
371
+							// VAVAILABILITY end.
372
+							$recurEnd = $vavailEnd;
373
+						}
374
+
375
+						$fbData->add(
376
+							$recurStart->getTimeStamp(),
377
+							$recurEnd->getTimeStamp(),
378
+							'FREE'
379
+						);
380
+
381
+						$rruleIterator->next();
382
+
383
+					}
384
+				}
385
+
386
+			}
387
+
388
+		}
389
+
390
+	}
391
+
392
+	/**
393
+	 * This method takes an array of iCalendar objects and applies its busy
394
+	 * times on fbData.
395
+	 *
396
+	 * @param FreeBusyData $fbData
397
+	 * @param VCalendar[] $objects
398
+	 */
399
+	protected function calculateBusy(FreeBusyData $fbData, array $objects) {
400
+
401
+		foreach ($objects as $key => $object) {
402
+
403
+			foreach ($object->getBaseComponents() as $component) {
404
+
405
+				switch ($component->name) {
406
+
407
+					case 'VEVENT' :
408
+
409
+						$FBTYPE = 'BUSY';
410
+						if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
411
+							break;
412
+						}
413
+						if (isset($component->STATUS)) {
414
+							$status = strtoupper($component->STATUS);
415
+							if ($status === 'CANCELLED') {
416
+								break;
417
+							}
418
+							if ($status === 'TENTATIVE') {
419
+								$FBTYPE = 'BUSY-TENTATIVE';
420
+							}
421
+						}
422
+
423
+						$times = [];
424
+
425
+						if ($component->RRULE) {
426
+							try {
427
+								$iterator = new EventIterator($object, (string)$component->UID, $this->timeZone);
428
+							} catch (NoInstancesException $e) {
429
+								// This event is recurring, but it doesn't have a single
430
+								// instance. We are skipping this event from the output
431
+								// entirely.
432
+								unset($this->objects[$key]);
433
+								continue;
434
+							}
435
+
436
+							if ($this->start) {
437
+								$iterator->fastForward($this->start);
438
+							}
439 439
 
440
-                            $maxRecurrences = Settings::$maxRecurrences;
441
-
442
-                            while ($iterator->valid() && --$maxRecurrences) {
443
-
444
-                                $startTime = $iterator->getDTStart();
445
-                                if ($this->end && $startTime > $this->end) {
446
-                                    break;
447
-                                }
448
-                                $times[] = [
449
-                                    $iterator->getDTStart(),
450
-                                    $iterator->getDTEnd(),
451
-                                ];
452
-
453
-                                $iterator->next();
454
-
455
-                            }
440
+							$maxRecurrences = Settings::$maxRecurrences;
441
+
442
+							while ($iterator->valid() && --$maxRecurrences) {
443
+
444
+								$startTime = $iterator->getDTStart();
445
+								if ($this->end && $startTime > $this->end) {
446
+									break;
447
+								}
448
+								$times[] = [
449
+									$iterator->getDTStart(),
450
+									$iterator->getDTEnd(),
451
+								];
452
+
453
+								$iterator->next();
454
+
455
+							}
456 456
 
457
-                        } else {
458
-
459
-                            $startTime = $component->DTSTART->getDateTime($this->timeZone);
460
-                            if ($this->end && $startTime > $this->end) {
461
-                                break;
462
-                            }
463
-                            $endTime = null;
464
-                            if (isset($component->DTEND)) {
465
-                                $endTime = $component->DTEND->getDateTime($this->timeZone);
466
-                            } elseif (isset($component->DURATION)) {
467
-                                $duration = DateTimeParser::parseDuration((string)$component->DURATION);
468
-                                $endTime = clone $startTime;
469
-                                $endTime = $endTime->add($duration);
470
-                            } elseif (!$component->DTSTART->hasTime()) {
471
-                                $endTime = clone $startTime;
472
-                                $endTime = $endTime->modify('+1 day');
473
-                            } else {
474
-                                // The event had no duration (0 seconds)
475
-                                break;
476
-                            }
477
-
478
-                            $times[] = [$startTime, $endTime];
479
-
480
-                        }
481
-
482
-                        foreach ($times as $time) {
483
-
484
-                            if ($this->end && $time[0] > $this->end) break;
485
-                            if ($this->start && $time[1] < $this->start) break;
486
-
487
-                            $fbData->add(
488
-                                $time[0]->getTimeStamp(),
489
-                                $time[1]->getTimeStamp(),
490
-                                $FBTYPE
491
-                            );
492
-                        }
493
-                        break;
494
-
495
-                    case 'VFREEBUSY' :
496
-                        foreach ($component->FREEBUSY as $freebusy) {
497
-
498
-                            $fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
499
-
500
-                            // Skipping intervals marked as 'free'
501
-                            if ($fbType === 'FREE')
502
-                                continue;
503
-
504
-                            $values = explode(',', $freebusy);
505
-                            foreach ($values as $value) {
506
-                                list($startTime, $endTime) = explode('/', $value);
507
-                                $startTime = DateTimeParser::parseDateTime($startTime);
508
-
509
-                                if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
510
-                                    $duration = DateTimeParser::parseDuration($endTime);
511
-                                    $endTime = clone $startTime;
512
-                                    $endTime = $endTime->add($duration);
513
-                                } else {
514
-                                    $endTime = DateTimeParser::parseDateTime($endTime);
515
-                                }
516
-
517
-                                if ($this->start && $this->start > $endTime) continue;
518
-                                if ($this->end && $this->end < $startTime) continue;
519
-                                $fbData->add(
520
-                                    $startTime->getTimeStamp(),
521
-                                    $endTime->getTimeStamp(),
522
-                                    $fbType
523
-                                );
524
-
525
-                            }
526
-
527
-
528
-                        }
529
-                        break;
530
-
531
-                }
457
+						} else {
458
+
459
+							$startTime = $component->DTSTART->getDateTime($this->timeZone);
460
+							if ($this->end && $startTime > $this->end) {
461
+								break;
462
+							}
463
+							$endTime = null;
464
+							if (isset($component->DTEND)) {
465
+								$endTime = $component->DTEND->getDateTime($this->timeZone);
466
+							} elseif (isset($component->DURATION)) {
467
+								$duration = DateTimeParser::parseDuration((string)$component->DURATION);
468
+								$endTime = clone $startTime;
469
+								$endTime = $endTime->add($duration);
470
+							} elseif (!$component->DTSTART->hasTime()) {
471
+								$endTime = clone $startTime;
472
+								$endTime = $endTime->modify('+1 day');
473
+							} else {
474
+								// The event had no duration (0 seconds)
475
+								break;
476
+							}
477
+
478
+							$times[] = [$startTime, $endTime];
479
+
480
+						}
481
+
482
+						foreach ($times as $time) {
483
+
484
+							if ($this->end && $time[0] > $this->end) break;
485
+							if ($this->start && $time[1] < $this->start) break;
486
+
487
+							$fbData->add(
488
+								$time[0]->getTimeStamp(),
489
+								$time[1]->getTimeStamp(),
490
+								$FBTYPE
491
+							);
492
+						}
493
+						break;
494
+
495
+					case 'VFREEBUSY' :
496
+						foreach ($component->FREEBUSY as $freebusy) {
497
+
498
+							$fbType = isset($freebusy['FBTYPE']) ? strtoupper($freebusy['FBTYPE']) : 'BUSY';
499
+
500
+							// Skipping intervals marked as 'free'
501
+							if ($fbType === 'FREE')
502
+								continue;
503
+
504
+							$values = explode(',', $freebusy);
505
+							foreach ($values as $value) {
506
+								list($startTime, $endTime) = explode('/', $value);
507
+								$startTime = DateTimeParser::parseDateTime($startTime);
508
+
509
+								if (substr($endTime, 0, 1) === 'P' || substr($endTime, 0, 2) === '-P') {
510
+									$duration = DateTimeParser::parseDuration($endTime);
511
+									$endTime = clone $startTime;
512
+									$endTime = $endTime->add($duration);
513
+								} else {
514
+									$endTime = DateTimeParser::parseDateTime($endTime);
515
+								}
516
+
517
+								if ($this->start && $this->start > $endTime) continue;
518
+								if ($this->end && $this->end < $startTime) continue;
519
+								$fbData->add(
520
+									$startTime->getTimeStamp(),
521
+									$endTime->getTimeStamp(),
522
+									$fbType
523
+								);
524
+
525
+							}
526
+
527
+
528
+						}
529
+						break;
530
+
531
+				}
532 532
 
533 533
 
534
-            }
534
+			}
535 535
 
536
-        }
536
+		}
537 537
 
538
-    }
538
+	}
539 539
 
540
-    /**
541
-     * This method takes a FreeBusyData object and generates the VCALENDAR
542
-     * object associated with it.
543
-     *
544
-     * @return VCalendar
545
-     */
546
-    protected function generateFreeBusyCalendar(FreeBusyData $fbData) {
547
-
548
-        if ($this->baseObject) {
549
-            $calendar = $this->baseObject;
550
-        } else {
551
-            $calendar = new VCalendar();
552
-        }
540
+	/**
541
+	 * This method takes a FreeBusyData object and generates the VCALENDAR
542
+	 * object associated with it.
543
+	 *
544
+	 * @return VCalendar
545
+	 */
546
+	protected function generateFreeBusyCalendar(FreeBusyData $fbData) {
547
+
548
+		if ($this->baseObject) {
549
+			$calendar = $this->baseObject;
550
+		} else {
551
+			$calendar = new VCalendar();
552
+		}
553 553
 
554
-        $vfreebusy = $calendar->createComponent('VFREEBUSY');
555
-        $calendar->add($vfreebusy);
554
+		$vfreebusy = $calendar->createComponent('VFREEBUSY');
555
+		$calendar->add($vfreebusy);
556 556
 
557
-        if ($this->start) {
558
-            $dtstart = $calendar->createProperty('DTSTART');
559
-            $dtstart->setDateTime($this->start);
560
-            $vfreebusy->add($dtstart);
561
-        }
562
-        if ($this->end) {
563
-            $dtend = $calendar->createProperty('DTEND');
564
-            $dtend->setDateTime($this->end);
565
-            $vfreebusy->add($dtend);
566
-        }
557
+		if ($this->start) {
558
+			$dtstart = $calendar->createProperty('DTSTART');
559
+			$dtstart->setDateTime($this->start);
560
+			$vfreebusy->add($dtstart);
561
+		}
562
+		if ($this->end) {
563
+			$dtend = $calendar->createProperty('DTEND');
564
+			$dtend->setDateTime($this->end);
565
+			$vfreebusy->add($dtend);
566
+		}
567 567
 
568
-        $tz = new \DateTimeZone('UTC');
569
-        $dtstamp = $calendar->createProperty('DTSTAMP');
570
-        $dtstamp->setDateTime(new DateTimeImmutable('now', $tz));
571
-        $vfreebusy->add($dtstamp);
568
+		$tz = new \DateTimeZone('UTC');
569
+		$dtstamp = $calendar->createProperty('DTSTAMP');
570
+		$dtstamp->setDateTime(new DateTimeImmutable('now', $tz));
571
+		$vfreebusy->add($dtstamp);
572 572
 
573
-        foreach ($fbData->getData() as $busyTime) {
573
+		foreach ($fbData->getData() as $busyTime) {
574 574
 
575
-            $busyType = strtoupper($busyTime['type']);
575
+			$busyType = strtoupper($busyTime['type']);
576 576
 
577
-            // Ignoring all the FREE parts, because those are already assumed.
578
-            if ($busyType === 'FREE') {
579
-                continue;
580
-            }
577
+			// Ignoring all the FREE parts, because those are already assumed.
578
+			if ($busyType === 'FREE') {
579
+				continue;
580
+			}
581 581
 
582
-            $busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz);
583
-            $busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz);
582
+			$busyTime[0] = new \DateTimeImmutable('@' . $busyTime['start'], $tz);
583
+			$busyTime[1] = new \DateTimeImmutable('@' . $busyTime['end'], $tz);
584 584
 
585
-            $prop = $calendar->createProperty(
586
-                'FREEBUSY',
587
-                $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
588
-            );
585
+			$prop = $calendar->createProperty(
586
+				'FREEBUSY',
587
+				$busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
588
+			);
589 589
 
590
-            // Only setting FBTYPE if it's not BUSY, because BUSY is the
591
-            // default anyway.
592
-            if ($busyType !== 'BUSY') {
593
-                $prop['FBTYPE'] = $busyType;
594
-            }
595
-            $vfreebusy->add($prop);
590
+			// Only setting FBTYPE if it's not BUSY, because BUSY is the
591
+			// default anyway.
592
+			if ($busyType !== 'BUSY') {
593
+				$prop['FBTYPE'] = $busyType;
594
+			}
595
+			$vfreebusy->add($prop);
596 596
 
597
-        }
597
+		}
598 598
 
599
-        return $calendar;
599
+		return $calendar;
600 600
 
601 601
 
602
-    }
602
+	}
603 603
 
604 604
 }
Please login to merge, or discard this patch.