Completed
Push — developer ( d751a3...e1a3df )
by Błażej
164:44 queued 111:37
created
libraries/SabreDAV/CalDAV/CalendarQueryValidator.php 2 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -51,7 +51,7 @@
 block discarded – undo
51 51
      * component we're checking should be specified, not the component to check
52 52
      * itself.
53 53
      *
54
-     * @param VObject\Component $parent
54
+     * @param VObject\Component\VCalendar $parent
55 55
      * @param array $filters
56 56
      * @return bool
57 57
      */
Please login to merge, or discard this patch.
Indentation   +351 added lines, -351 removed lines patch added patch discarded remove patch
@@ -20,356 +20,356 @@
 block discarded – undo
20 20
  */
21 21
 class CalendarQueryValidator {
22 22
 
23
-    /**
24
-     * Verify if a list of filters applies to the calendar data object
25
-     *
26
-     * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27
-     *
28
-     * @param VObject\Component $vObject
29
-     * @param array $filters
30
-     * @return bool
31
-     */
32
-    public function validate(VObject\Component\VCalendar $vObject, array $filters) {
33
-
34
-        // The top level object is always a component filter.
35
-        // We'll parse it manually, as it's pretty simple.
36
-        if ($vObject->name !== $filters['name']) {
37
-            return false;
38
-        }
39
-
40
-        return
41
-            $this->validateCompFilters($vObject, $filters['comp-filters']) &&
42
-            $this->validatePropFilters($vObject, $filters['prop-filters']);
43
-
44
-
45
-    }
46
-
47
-    /**
48
-     * This method checks the validity of comp-filters.
49
-     *
50
-     * A list of comp-filters needs to be specified. Also the parent of the
51
-     * component we're checking should be specified, not the component to check
52
-     * itself.
53
-     *
54
-     * @param VObject\Component $parent
55
-     * @param array $filters
56
-     * @return bool
57
-     */
58
-    protected function validateCompFilters(VObject\Component $parent, array $filters) {
59
-
60
-        foreach ($filters as $filter) {
61
-
62
-            $isDefined = isset($parent->{$filter['name']});
63
-
64
-            if ($filter['is-not-defined']) {
65
-
66
-                if ($isDefined) {
67
-                    return false;
68
-                } else {
69
-                    continue;
70
-                }
71
-
72
-            }
73
-            if (!$isDefined) {
74
-                return false;
75
-            }
76
-
77
-            if ($filter['time-range']) {
78
-                foreach ($parent->{$filter['name']} as $subComponent) {
79
-                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
80
-                        continue 2;
81
-                    }
82
-                }
83
-                return false;
84
-            }
85
-
86
-            if (!$filter['comp-filters'] && !$filter['prop-filters']) {
87
-                continue;
88
-            }
89
-
90
-            // If there are sub-filters, we need to find at least one component
91
-            // for which the subfilters hold true.
92
-            foreach ($parent->{$filter['name']} as $subComponent) {
93
-
94
-                if (
95
-                    $this->validateCompFilters($subComponent, $filter['comp-filters']) &&
96
-                    $this->validatePropFilters($subComponent, $filter['prop-filters'])) {
97
-                        // We had a match, so this comp-filter succeeds
98
-                        continue 2;
99
-                }
100
-
101
-            }
102
-
103
-            // If we got here it means there were sub-comp-filters or
104
-            // sub-prop-filters and there was no match. This means this filter
105
-            // needs to return false.
106
-            return false;
107
-
108
-        }
109
-
110
-        // If we got here it means we got through all comp-filters alive so the
111
-        // filters were all true.
112
-        return true;
113
-
114
-    }
115
-
116
-    /**
117
-     * This method checks the validity of prop-filters.
118
-     *
119
-     * A list of prop-filters needs to be specified. Also the parent of the
120
-     * property we're checking should be specified, not the property to check
121
-     * itself.
122
-     *
123
-     * @param VObject\Component $parent
124
-     * @param array $filters
125
-     * @return bool
126
-     */
127
-    protected function validatePropFilters(VObject\Component $parent, array $filters) {
128
-
129
-        foreach ($filters as $filter) {
130
-
131
-            $isDefined = isset($parent->{$filter['name']});
132
-
133
-            if ($filter['is-not-defined']) {
134
-
135
-                if ($isDefined) {
136
-                    return false;
137
-                } else {
138
-                    continue;
139
-                }
140
-
141
-            }
142
-            if (!$isDefined) {
143
-                return false;
144
-            }
145
-
146
-            if ($filter['time-range']) {
147
-                foreach ($parent->{$filter['name']} as $subComponent) {
148
-                    if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
149
-                        continue 2;
150
-                    }
151
-                }
152
-                return false;
153
-            }
154
-
155
-            if (!$filter['param-filters'] && !$filter['text-match']) {
156
-                continue;
157
-            }
158
-
159
-            // If there are sub-filters, we need to find at least one property
160
-            // for which the subfilters hold true.
161
-            foreach ($parent->{$filter['name']} as $subComponent) {
162
-
163
-                if (
164
-                    $this->validateParamFilters($subComponent, $filter['param-filters']) &&
165
-                    (!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
166
-                ) {
167
-                    // We had a match, so this prop-filter succeeds
168
-                    continue 2;
169
-                }
170
-
171
-            }
172
-
173
-            // If we got here it means there were sub-param-filters or
174
-            // text-match filters and there was no match. This means the
175
-            // filter needs to return false.
176
-            return false;
177
-
178
-        }
179
-
180
-        // If we got here it means we got through all prop-filters alive so the
181
-        // filters were all true.
182
-        return true;
183
-
184
-    }
185
-
186
-    /**
187
-     * This method checks the validity of param-filters.
188
-     *
189
-     * A list of param-filters needs to be specified. Also the parent of the
190
-     * parameter we're checking should be specified, not the parameter to check
191
-     * itself.
192
-     *
193
-     * @param VObject\Property $parent
194
-     * @param array $filters
195
-     * @return bool
196
-     */
197
-    protected function validateParamFilters(VObject\Property $parent, array $filters) {
198
-
199
-        foreach ($filters as $filter) {
200
-
201
-            $isDefined = isset($parent[$filter['name']]);
202
-
203
-            if ($filter['is-not-defined']) {
204
-
205
-                if ($isDefined) {
206
-                    return false;
207
-                } else {
208
-                    continue;
209
-                }
210
-
211
-            }
212
-            if (!$isDefined) {
213
-                return false;
214
-            }
215
-
216
-            if (!$filter['text-match']) {
217
-                continue;
218
-            }
219
-
220
-            // If there are sub-filters, we need to find at least one parameter
221
-            // for which the subfilters hold true.
222
-            foreach ($parent[$filter['name']]->getParts() as $paramPart) {
223
-
224
-                if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
225
-                    // We had a match, so this param-filter succeeds
226
-                    continue 2;
227
-                }
228
-
229
-            }
230
-
231
-            // If we got here it means there was a text-match filter and there
232
-            // were no matches. This means the filter needs to return false.
233
-            return false;
234
-
235
-        }
236
-
237
-        // If we got here it means we got through all param-filters alive so the
238
-        // filters were all true.
239
-        return true;
240
-
241
-    }
242
-
243
-    /**
244
-     * This method checks the validity of a text-match.
245
-     *
246
-     * A single text-match should be specified as well as the specific property
247
-     * or parameter we need to validate.
248
-     *
249
-     * @param VObject\Node|string $check Value to check against.
250
-     * @param array $textMatch
251
-     * @return bool
252
-     */
253
-    protected function validateTextMatch($check, array $textMatch) {
254
-
255
-        if ($check instanceof VObject\Node) {
256
-            $check = $check->getValue();
257
-        }
258
-
259
-        $isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
260
-
261
-        return ($textMatch['negate-condition'] xor $isMatching);
262
-
263
-    }
264
-
265
-    /**
266
-     * Validates if a component matches the given time range.
267
-     *
268
-     * This is all based on the rules specified in rfc4791, which are quite
269
-     * complex.
270
-     *
271
-     * @param VObject\Node $component
272
-     * @param DateTime $start
273
-     * @param DateTime $end
274
-     * @return bool
275
-     */
276
-    protected function validateTimeRange(VObject\Node $component, $start, $end) {
277
-
278
-        if (is_null($start)) {
279
-            $start = new DateTime('1900-01-01');
280
-        }
281
-        if (is_null($end)) {
282
-            $end = new DateTime('3000-01-01');
283
-        }
284
-
285
-        switch ($component->name) {
286
-
287
-            case 'VEVENT' :
288
-            case 'VTODO' :
289
-            case 'VJOURNAL' :
290
-
291
-                return $component->isInTimeRange($start, $end);
292
-
293
-            case 'VALARM' :
294
-
295
-                // If the valarm is wrapped in a recurring event, we need to
296
-                // expand the recursions, and validate each.
297
-                //
298
-                // Our datamodel doesn't easily allow us to do this straight
299
-                // in the VALARM component code, so this is a hack, and an
300
-                // expensive one too.
301
-                if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302
-
303
-                    // Fire up the iterator!
304
-                    $it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
305
-                    while ($it->valid()) {
306
-                        $expandedEvent = $it->getEventObject();
307
-
308
-                        // We need to check from these expanded alarms, which
309
-                        // one is the first to trigger. Based on this, we can
310
-                        // determine if we can 'give up' expanding events.
311
-                        $firstAlarm = null;
312
-                        if ($expandedEvent->VALARM !== null) {
313
-                            foreach ($expandedEvent->VALARM as $expandedAlarm) {
314
-
315
-                                $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
316
-                                if ($expandedAlarm->isInTimeRange($start, $end)) {
317
-                                    return true;
318
-                                }
319
-
320
-                                if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321
-                                    // This is an alarm with a non-relative trigger
322
-                                    // time, likely created by a buggy client. The
323
-                                    // implication is that every alarm in this
324
-                                    // recurring event trigger at the exact same
325
-                                    // time. It doesn't make sense to traverse
326
-                                    // further.
327
-                                } else {
328
-                                    // We store the first alarm as a means to
329
-                                    // figure out when we can stop traversing.
330
-                                    if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
331
-                                        $firstAlarm = $effectiveTrigger;
332
-                                    }
333
-                                }
334
-                            }
335
-                        }
336
-                        if (is_null($firstAlarm)) {
337
-                            // No alarm was found.
338
-                            //
339
-                            // Or technically: No alarm that will change for
340
-                            // every instance of the recurrence was found,
341
-                            // which means we can assume there was no match.
342
-                            return false;
343
-                        }
344
-                        if ($firstAlarm > $end) {
345
-                            return false;
346
-                        }
347
-                        $it->next();
348
-                    }
349
-                    return false;
350
-                } else {
351
-                    return $component->isInTimeRange($start, $end);
352
-                }
353
-
354
-            case 'VFREEBUSY' :
355
-                throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
356
-
357
-            case 'COMPLETED' :
358
-            case 'CREATED' :
359
-            case 'DTEND' :
360
-            case 'DTSTAMP' :
361
-            case 'DTSTART' :
362
-            case 'DUE' :
363
-            case 'LAST-MODIFIED' :
364
-                return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
365
-
366
-
367
-
368
-            default :
369
-                throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
370
-
371
-        }
372
-
373
-    }
23
+	/**
24
+	 * Verify if a list of filters applies to the calendar data object
25
+	 *
26
+	 * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser
27
+	 *
28
+	 * @param VObject\Component $vObject
29
+	 * @param array $filters
30
+	 * @return bool
31
+	 */
32
+	public function validate(VObject\Component\VCalendar $vObject, array $filters) {
33
+
34
+		// The top level object is always a component filter.
35
+		// We'll parse it manually, as it's pretty simple.
36
+		if ($vObject->name !== $filters['name']) {
37
+			return false;
38
+		}
39
+
40
+		return
41
+			$this->validateCompFilters($vObject, $filters['comp-filters']) &&
42
+			$this->validatePropFilters($vObject, $filters['prop-filters']);
43
+
44
+
45
+	}
46
+
47
+	/**
48
+	 * This method checks the validity of comp-filters.
49
+	 *
50
+	 * A list of comp-filters needs to be specified. Also the parent of the
51
+	 * component we're checking should be specified, not the component to check
52
+	 * itself.
53
+	 *
54
+	 * @param VObject\Component $parent
55
+	 * @param array $filters
56
+	 * @return bool
57
+	 */
58
+	protected function validateCompFilters(VObject\Component $parent, array $filters) {
59
+
60
+		foreach ($filters as $filter) {
61
+
62
+			$isDefined = isset($parent->{$filter['name']});
63
+
64
+			if ($filter['is-not-defined']) {
65
+
66
+				if ($isDefined) {
67
+					return false;
68
+				} else {
69
+					continue;
70
+				}
71
+
72
+			}
73
+			if (!$isDefined) {
74
+				return false;
75
+			}
76
+
77
+			if ($filter['time-range']) {
78
+				foreach ($parent->{$filter['name']} as $subComponent) {
79
+					if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
80
+						continue 2;
81
+					}
82
+				}
83
+				return false;
84
+			}
85
+
86
+			if (!$filter['comp-filters'] && !$filter['prop-filters']) {
87
+				continue;
88
+			}
89
+
90
+			// If there are sub-filters, we need to find at least one component
91
+			// for which the subfilters hold true.
92
+			foreach ($parent->{$filter['name']} as $subComponent) {
93
+
94
+				if (
95
+					$this->validateCompFilters($subComponent, $filter['comp-filters']) &&
96
+					$this->validatePropFilters($subComponent, $filter['prop-filters'])) {
97
+						// We had a match, so this comp-filter succeeds
98
+						continue 2;
99
+				}
100
+
101
+			}
102
+
103
+			// If we got here it means there were sub-comp-filters or
104
+			// sub-prop-filters and there was no match. This means this filter
105
+			// needs to return false.
106
+			return false;
107
+
108
+		}
109
+
110
+		// If we got here it means we got through all comp-filters alive so the
111
+		// filters were all true.
112
+		return true;
113
+
114
+	}
115
+
116
+	/**
117
+	 * This method checks the validity of prop-filters.
118
+	 *
119
+	 * A list of prop-filters needs to be specified. Also the parent of the
120
+	 * property we're checking should be specified, not the property to check
121
+	 * itself.
122
+	 *
123
+	 * @param VObject\Component $parent
124
+	 * @param array $filters
125
+	 * @return bool
126
+	 */
127
+	protected function validatePropFilters(VObject\Component $parent, array $filters) {
128
+
129
+		foreach ($filters as $filter) {
130
+
131
+			$isDefined = isset($parent->{$filter['name']});
132
+
133
+			if ($filter['is-not-defined']) {
134
+
135
+				if ($isDefined) {
136
+					return false;
137
+				} else {
138
+					continue;
139
+				}
140
+
141
+			}
142
+			if (!$isDefined) {
143
+				return false;
144
+			}
145
+
146
+			if ($filter['time-range']) {
147
+				foreach ($parent->{$filter['name']} as $subComponent) {
148
+					if ($this->validateTimeRange($subComponent, $filter['time-range']['start'], $filter['time-range']['end'])) {
149
+						continue 2;
150
+					}
151
+				}
152
+				return false;
153
+			}
154
+
155
+			if (!$filter['param-filters'] && !$filter['text-match']) {
156
+				continue;
157
+			}
158
+
159
+			// If there are sub-filters, we need to find at least one property
160
+			// for which the subfilters hold true.
161
+			foreach ($parent->{$filter['name']} as $subComponent) {
162
+
163
+				if (
164
+					$this->validateParamFilters($subComponent, $filter['param-filters']) &&
165
+					(!$filter['text-match'] || $this->validateTextMatch($subComponent, $filter['text-match']))
166
+				) {
167
+					// We had a match, so this prop-filter succeeds
168
+					continue 2;
169
+				}
170
+
171
+			}
172
+
173
+			// If we got here it means there were sub-param-filters or
174
+			// text-match filters and there was no match. This means the
175
+			// filter needs to return false.
176
+			return false;
177
+
178
+		}
179
+
180
+		// If we got here it means we got through all prop-filters alive so the
181
+		// filters were all true.
182
+		return true;
183
+
184
+	}
185
+
186
+	/**
187
+	 * This method checks the validity of param-filters.
188
+	 *
189
+	 * A list of param-filters needs to be specified. Also the parent of the
190
+	 * parameter we're checking should be specified, not the parameter to check
191
+	 * itself.
192
+	 *
193
+	 * @param VObject\Property $parent
194
+	 * @param array $filters
195
+	 * @return bool
196
+	 */
197
+	protected function validateParamFilters(VObject\Property $parent, array $filters) {
198
+
199
+		foreach ($filters as $filter) {
200
+
201
+			$isDefined = isset($parent[$filter['name']]);
202
+
203
+			if ($filter['is-not-defined']) {
204
+
205
+				if ($isDefined) {
206
+					return false;
207
+				} else {
208
+					continue;
209
+				}
210
+
211
+			}
212
+			if (!$isDefined) {
213
+				return false;
214
+			}
215
+
216
+			if (!$filter['text-match']) {
217
+				continue;
218
+			}
219
+
220
+			// If there are sub-filters, we need to find at least one parameter
221
+			// for which the subfilters hold true.
222
+			foreach ($parent[$filter['name']]->getParts() as $paramPart) {
223
+
224
+				if ($this->validateTextMatch($paramPart, $filter['text-match'])) {
225
+					// We had a match, so this param-filter succeeds
226
+					continue 2;
227
+				}
228
+
229
+			}
230
+
231
+			// If we got here it means there was a text-match filter and there
232
+			// were no matches. This means the filter needs to return false.
233
+			return false;
234
+
235
+		}
236
+
237
+		// If we got here it means we got through all param-filters alive so the
238
+		// filters were all true.
239
+		return true;
240
+
241
+	}
242
+
243
+	/**
244
+	 * This method checks the validity of a text-match.
245
+	 *
246
+	 * A single text-match should be specified as well as the specific property
247
+	 * or parameter we need to validate.
248
+	 *
249
+	 * @param VObject\Node|string $check Value to check against.
250
+	 * @param array $textMatch
251
+	 * @return bool
252
+	 */
253
+	protected function validateTextMatch($check, array $textMatch) {
254
+
255
+		if ($check instanceof VObject\Node) {
256
+			$check = $check->getValue();
257
+		}
258
+
259
+		$isMatching = \Sabre\DAV\StringUtil::textMatch($check, $textMatch['value'], $textMatch['collation']);
260
+
261
+		return ($textMatch['negate-condition'] xor $isMatching);
262
+
263
+	}
264
+
265
+	/**
266
+	 * Validates if a component matches the given time range.
267
+	 *
268
+	 * This is all based on the rules specified in rfc4791, which are quite
269
+	 * complex.
270
+	 *
271
+	 * @param VObject\Node $component
272
+	 * @param DateTime $start
273
+	 * @param DateTime $end
274
+	 * @return bool
275
+	 */
276
+	protected function validateTimeRange(VObject\Node $component, $start, $end) {
277
+
278
+		if (is_null($start)) {
279
+			$start = new DateTime('1900-01-01');
280
+		}
281
+		if (is_null($end)) {
282
+			$end = new DateTime('3000-01-01');
283
+		}
284
+
285
+		switch ($component->name) {
286
+
287
+			case 'VEVENT' :
288
+			case 'VTODO' :
289
+			case 'VJOURNAL' :
290
+
291
+				return $component->isInTimeRange($start, $end);
292
+
293
+			case 'VALARM' :
294
+
295
+				// If the valarm is wrapped in a recurring event, we need to
296
+				// expand the recursions, and validate each.
297
+				//
298
+				// Our datamodel doesn't easily allow us to do this straight
299
+				// in the VALARM component code, so this is a hack, and an
300
+				// expensive one too.
301
+				if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
302
+
303
+					// Fire up the iterator!
304
+					$it = new VObject\Recur\EventIterator($component->parent->parent, (string)$component->parent->UID);
305
+					while ($it->valid()) {
306
+						$expandedEvent = $it->getEventObject();
307
+
308
+						// We need to check from these expanded alarms, which
309
+						// one is the first to trigger. Based on this, we can
310
+						// determine if we can 'give up' expanding events.
311
+						$firstAlarm = null;
312
+						if ($expandedEvent->VALARM !== null) {
313
+							foreach ($expandedEvent->VALARM as $expandedAlarm) {
314
+
315
+								$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
316
+								if ($expandedAlarm->isInTimeRange($start, $end)) {
317
+									return true;
318
+								}
319
+
320
+								if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
321
+									// This is an alarm with a non-relative trigger
322
+									// time, likely created by a buggy client. The
323
+									// implication is that every alarm in this
324
+									// recurring event trigger at the exact same
325
+									// time. It doesn't make sense to traverse
326
+									// further.
327
+								} else {
328
+									// We store the first alarm as a means to
329
+									// figure out when we can stop traversing.
330
+									if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
331
+										$firstAlarm = $effectiveTrigger;
332
+									}
333
+								}
334
+							}
335
+						}
336
+						if (is_null($firstAlarm)) {
337
+							// No alarm was found.
338
+							//
339
+							// Or technically: No alarm that will change for
340
+							// every instance of the recurrence was found,
341
+							// which means we can assume there was no match.
342
+							return false;
343
+						}
344
+						if ($firstAlarm > $end) {
345
+							return false;
346
+						}
347
+						$it->next();
348
+					}
349
+					return false;
350
+				} else {
351
+					return $component->isInTimeRange($start, $end);
352
+				}
353
+
354
+			case 'VFREEBUSY' :
355
+				throw new \Sabre\DAV\Exception\NotImplemented('time-range filters are currently not supported on ' . $component->name . ' components');
356
+
357
+			case 'COMPLETED' :
358
+			case 'CREATED' :
359
+			case 'DTEND' :
360
+			case 'DTSTAMP' :
361
+			case 'DTSTART' :
362
+			case 'DUE' :
363
+			case 'LAST-MODIFIED' :
364
+				return ($start <= $component->getDateTime() && $end >= $component->getDateTime());
365
+
366
+
367
+
368
+			default :
369
+				throw new \Sabre\DAV\Exception\BadRequest('You cannot create a time-range filter on a ' . $component->name . ' component');
370
+
371
+		}
372
+
373
+	}
374 374
 
375 375
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/CalDAV/Schedule/Plugin.php 2 patches
Doc Comments   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -69,7 +69,7 @@  discard block
 block discarded – undo
69 69
     /**
70 70
      * Returns a list of features for the DAV: HTTP header.
71 71
      *
72
-     * @return array
72
+     * @return string[]
73 73
      */
74 74
     public function getFeatures() {
75 75
 
@@ -160,7 +160,7 @@  discard block
 block discarded – undo
160 160
      *
161 161
      * @param RequestInterface $request
162 162
      * @param ResponseInterface $response
163
-     * @return bool
163
+     * @return null|false
164 164
      */
165 165
     public function httpPost(RequestInterface $request, ResponseInterface $response) {
166 166
 
@@ -364,7 +364,7 @@  discard block
 block discarded – undo
364 364
     /**
365 365
      * This method is responsible for delivering the ITip message.
366 366
      *
367
-     * @param ITip\Message $itipMessage
367
+     * @param ITip\Message $iTipMessage
368 368
      * @return void
369 369
      */
370 370
     public function deliver(ITip\Message $iTipMessage) {
@@ -717,7 +717,7 @@  discard block
 block discarded – undo
717 717
      * returning it's result.
718 718
      *
719 719
      * @param IOutbox $outbox
720
-     * @param VObject\Component $vObject
720
+     * @param VObject\Document $vObject
721 721
      * @param RequestInterface $request
722 722
      * @param ResponseInterface $response
723 723
      * @return string
Please login to merge, or discard this patch.
Indentation   +927 added lines, -927 removed lines patch added patch discarded remove patch
@@ -54,939 +54,939 @@
 block discarded – undo
54 54
  */
55 55
 class Plugin extends ServerPlugin {
56 56
 
57
-    /**
58
-     * This is the official CalDAV namespace
59
-     */
60
-    const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
61
-
62
-    /**
63
-     * Reference to main Server object.
64
-     *
65
-     * @var Server
66
-     */
67
-    protected $server;
68
-
69
-    /**
70
-     * Returns a list of features for the DAV: HTTP header.
71
-     *
72
-     * @return array
73
-     */
74
-    public function getFeatures() {
75
-
76
-        return ['calendar-auto-schedule', 'calendar-availability'];
77
-
78
-    }
79
-
80
-    /**
81
-     * Returns the name of the plugin.
82
-     *
83
-     * Using this name other plugins will be able to access other plugins
84
-     * using Server::getPlugin
85
-     *
86
-     * @return string
87
-     */
88
-    public function getPluginName() {
89
-
90
-        return 'caldav-schedule';
91
-
92
-    }
93
-
94
-    /**
95
-     * Initializes the plugin
96
-     *
97
-     * @param Server $server
98
-     * @return void
99
-     */
100
-    public function initialize(Server $server) {
101
-
102
-        $this->server = $server;
103
-        $server->on('method:POST',          [$this, 'httpPost']);
104
-        $server->on('propFind',             [$this, 'propFind']);
105
-        $server->on('propPatch',            [$this, 'propPatch']);
106
-        $server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
107
-        $server->on('beforeUnbind',         [$this, 'beforeUnbind']);
108
-        $server->on('schedule',             [$this, 'scheduleLocalDelivery']);
109
-
110
-        $ns = '{' . self::NS_CALDAV . '}';
111
-
112
-        /**
113
-         * This information ensures that the {DAV:}resourcetype property has
114
-         * the correct values.
115
-         */
116
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
117
-        $server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
118
-
119
-        /**
120
-         * Properties we protect are made read-only by the server.
121
-         */
122
-        array_push($server->protectedProperties,
123
-            $ns . 'schedule-inbox-URL',
124
-            $ns . 'schedule-outbox-URL',
125
-            $ns . 'calendar-user-address-set',
126
-            $ns . 'calendar-user-type',
127
-            $ns . 'schedule-default-calendar-URL'
128
-        );
129
-
130
-    }
131
-
132
-    /**
133
-     * Use this method to tell the server this plugin defines additional
134
-     * HTTP methods.
135
-     *
136
-     * This method is passed a uri. It should only return HTTP methods that are
137
-     * available for the specified uri.
138
-     *
139
-     * @param string $uri
140
-     * @return array
141
-     */
142
-    public function getHTTPMethods($uri) {
143
-
144
-        try {
145
-            $node = $this->server->tree->getNodeForPath($uri);
146
-        } catch (NotFound $e) {
147
-            return [];
148
-        }
149
-
150
-        if ($node instanceof IOutbox) {
151
-            return ['POST'];
152
-        }
153
-
154
-        return [];
155
-
156
-    }
157
-
158
-    /**
159
-     * This method handles POST request for the outbox.
160
-     *
161
-     * @param RequestInterface $request
162
-     * @param ResponseInterface $response
163
-     * @return bool
164
-     */
165
-    public function httpPost(RequestInterface $request, ResponseInterface $response) {
166
-
167
-        // Checking if this is a text/calendar content type
168
-        $contentType = $request->getHeader('Content-Type');
169
-        if (strpos($contentType, 'text/calendar') !== 0) {
170
-            return;
171
-        }
172
-
173
-        $path = $request->getPath();
174
-
175
-        // Checking if we're talking to an outbox
176
-        try {
177
-            $node = $this->server->tree->getNodeForPath($path);
178
-        } catch (NotFound $e) {
179
-            return;
180
-        }
181
-        if (!$node instanceof IOutbox)
182
-            return;
183
-
184
-        $this->server->transactionType = 'post-caldav-outbox';
185
-        $this->outboxRequest($node, $request, $response);
186
-
187
-        // Returning false breaks the event chain and tells the server we've
188
-        // handled the request.
189
-        return false;
190
-
191
-    }
192
-
193
-    /**
194
-     * This method handler is invoked during fetching of properties.
195
-     *
196
-     * We use this event to add calendar-auto-schedule-specific properties.
197
-     *
198
-     * @param PropFind $propFind
199
-     * @param INode $node
200
-     * @return void
201
-     */
202
-    public function propFind(PropFind $propFind, INode $node) {
203
-
204
-        if ($node instanceof DAVACL\IPrincipal) {
205
-
206
-            $caldavPlugin = $this->server->getPlugin('caldav');
207
-            $principalUrl = $node->getPrincipalUrl();
208
-
209
-            // schedule-outbox-URL property
210
-            $propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
211
-
212
-                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
213
-                if (!$calendarHomePath) {
214
-                    return null;
215
-                }
216
-                $outboxPath = $calendarHomePath . '/outbox/';
217
-
218
-                return new Href($outboxPath);
219
-
220
-            });
221
-            // schedule-inbox-URL property
222
-            $propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
223
-
224
-                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
225
-                if (!$calendarHomePath) {
226
-                    return null;
227
-                }
228
-                $inboxPath = $calendarHomePath . '/inbox/';
229
-
230
-                return new Href($inboxPath);
231
-
232
-            });
233
-
234
-            $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
235
-
236
-                // We don't support customizing this property yet, so in the
237
-                // meantime we just grab the first calendar in the home-set.
238
-                $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
239
-
240
-                if (!$calendarHomePath) {
241
-                    return null;
242
-                }
243
-
244
-                $sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
245
-
246
-                $result = $this->server->getPropertiesForPath($calendarHomePath, [
247
-                    '{DAV:}resourcetype',
248
-                    $sccs,
249
-                ], 1);
250
-
251
-                foreach ($result as $child) {
252
-                    if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) {
253
-                        // Node is either not a calendar or a shared instance.
254
-                        continue;
255
-                    }
256
-                    if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
257
-                        // Either there is no supported-calendar-component-set
258
-                        // (which is fine) or we found one that supports VEVENT.
259
-                        return new Href($child['href']);
260
-                    }
261
-                }
262
-
263
-            });
264
-
265
-            // The server currently reports every principal to be of type
266
-            // 'INDIVIDUAL'
267
-            $propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
268
-
269
-                return 'INDIVIDUAL';
270
-
271
-            });
272
-
273
-        }
274
-
275
-        // Mapping the old property to the new property.
276
-        $propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
277
-
278
-             // In case it wasn't clear, the only difference is that we map the
279
-            // old property to a different namespace.
280
-             $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
281
-             $subPropFind = new PropFind(
282
-                 $propFind->getPath(),
283
-                 [$availProp]
284
-             );
285
-
286
-             $this->server->getPropertiesByNode(
287
-                 $subPropFind,
288
-                 $node
289
-             );
290
-
291
-             $propFind->set(
292
-                 '{http://calendarserver.org/ns/}calendar-availability',
293
-                 $subPropFind->get($availProp),
294
-                 $subPropFind->getStatus($availProp)
295
-             );
296
-
297
-        });
298
-
299
-    }
300
-
301
-    /**
302
-     * This method is called during property updates.
303
-     *
304
-     * @param string $path
305
-     * @param PropPatch $propPatch
306
-     * @return void
307
-     */
308
-    public function propPatch($path, PropPatch $propPatch) {
309
-
310
-        // Mapping the old property to the new property.
311
-        $propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
312
-
313
-            $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
314
-            $subPropPatch = new PropPatch([$availProp => $value]);
315
-            $this->server->emit('propPatch', [$path, $subPropPatch]);
316
-            $subPropPatch->commit();
317
-
318
-            return $subPropPatch->getResult()[$availProp];
319
-
320
-        });
321
-
322
-    }
323
-
324
-    /**
325
-     * This method is triggered whenever there was a calendar object gets
326
-     * created or updated.
327
-     *
328
-     * @param RequestInterface $request HTTP request
329
-     * @param ResponseInterface $response HTTP Response
330
-     * @param VCalendar $vCal Parsed iCalendar object
331
-     * @param mixed $calendarPath Path to calendar collection
332
-     * @param mixed $modified The iCalendar object has been touched.
333
-     * @param mixed $isNew Whether this was a new item or we're updating one
334
-     * @return void
335
-     */
336
-    public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
337
-
338
-        if (!$this->scheduleReply($this->server->httpRequest)) {
339
-            return;
340
-        }
341
-
342
-        $calendarNode = $this->server->tree->getNodeForPath($calendarPath);
343
-
344
-        $addresses = $this->getAddressesForPrincipal(
345
-            $calendarNode->getOwner()
346
-        );
347
-
348
-        if (!$isNew) {
349
-            $node = $this->server->tree->getNodeForPath($request->getPath());
350
-            $oldObj = Reader::read($node->get());
351
-        } else {
352
-            $oldObj = null;
353
-        }
354
-
355
-        $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
356
-
357
-        if ($oldObj) {
358
-            // Destroy circular references so PHP will GC the object.
359
-            $oldObj->destroy();
360
-        }
361
-
362
-    }
363
-
364
-    /**
365
-     * This method is responsible for delivering the ITip message.
366
-     *
367
-     * @param ITip\Message $itipMessage
368
-     * @return void
369
-     */
370
-    public function deliver(ITip\Message $iTipMessage) {
371
-
372
-        $this->server->emit('schedule', [$iTipMessage]);
373
-        if (!$iTipMessage->scheduleStatus) {
374
-            $iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
375
-        }
376
-        // In case the change was considered 'insignificant', we are going to
377
-        // remove any error statuses, if any. See ticket #525.
378
-        list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
379
-        if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
380
-            $iTipMessage->scheduleStatus = null;
381
-        }
382
-
383
-    }
384
-
385
-    /**
386
-     * This method is triggered before a file gets deleted.
387
-     *
388
-     * We use this event to make sure that when this happens, attendees get
389
-     * cancellations, and organizers get 'DECLINED' statuses.
390
-     *
391
-     * @param string $path
392
-     * @return void
393
-     */
394
-    public function beforeUnbind($path) {
395
-
396
-        if ($this->server->httpRequest->getMethod() === 'MOVE') return;
397
-
398
-        $node = $this->server->tree->getNodeForPath($path);
399
-
400
-        if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
401
-            return;
402
-        }
403
-
404
-        if (!$this->scheduleReply($this->server->httpRequest)) {
405
-            return;
406
-        }
407
-
408
-        $addresses = $this->getAddressesForPrincipal(
409
-            $node->getOwner()
410
-        );
411
-
412
-        $broker = new ITip\Broker();
413
-        $messages = $broker->parseEvent(null, $addresses, $node->get());
414
-
415
-        foreach ($messages as $message) {
416
-            $this->deliver($message);
417
-        }
418
-
419
-    }
420
-
421
-    /**
422
-     * Event handler for the 'schedule' event.
423
-     *
424
-     * This handler attempts to look at local accounts to deliver the
425
-     * scheduling object.
426
-     *
427
-     * @param ITip\Message $iTipMessage
428
-     * @return void
429
-     */
430
-    public function scheduleLocalDelivery(ITip\Message $iTipMessage) {
431
-
432
-        $aclPlugin = $this->server->getPlugin('acl');
433
-
434
-        // Local delivery is not available if the ACL plugin is not loaded.
435
-        if (!$aclPlugin) {
436
-            return;
437
-        }
438
-
439
-        $caldavNS = '{' . self::NS_CALDAV . '}';
440
-
441
-        $principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
442
-        if (!$principalUri) {
443
-            $iTipMessage->scheduleStatus = '3.7;Could not find principal.';
444
-            return;
445
-        }
446
-
447
-        // We found a principal URL, now we need to find its inbox.
448
-        // Unfortunately we may not have sufficient privileges to find this, so
449
-        // we are temporarily turning off ACL to let this come through.
450
-        //
451
-        // Once we support PHP 5.5, this should be wrapped in a try..finally
452
-        // block so we can ensure that this privilege gets added again after.
453
-        $this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
454
-
455
-        $result = $this->server->getProperties(
456
-            $principalUri,
457
-            [
458
-                '{DAV:}principal-URL',
459
-                 $caldavNS . 'calendar-home-set',
460
-                 $caldavNS . 'schedule-inbox-URL',
461
-                 $caldavNS . 'schedule-default-calendar-URL',
462
-                '{http://sabredav.org/ns}email-address',
463
-            ]
464
-        );
465
-
466
-        // Re-registering the ACL event
467
-        $this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
468
-
469
-        if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
470
-            $iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
471
-            return;
472
-        }
473
-        if (!isset($result[$caldavNS . 'calendar-home-set'])) {
474
-            $iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
475
-            return;
476
-        }
477
-        if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
478
-            $iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
479
-            return;
480
-        }
481
-
482
-        $calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
483
-        $homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
484
-        $inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
485
-
486
-        if ($iTipMessage->method === 'REPLY') {
487
-            $privilege = 'schedule-deliver-reply';
488
-        } else {
489
-            $privilege = 'schedule-deliver-invite';
490
-        }
491
-
492
-        if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
493
-            $iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox';
494
-            return;
495
-        }
496
-
497
-        // Next, we're going to find out if the item already exits in one of
498
-        // the users' calendars.
499
-        $uid = $iTipMessage->uid;
500
-
501
-        $newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
502
-
503
-        $home = $this->server->tree->getNodeForPath($homePath);
504
-        $inbox = $this->server->tree->getNodeForPath($inboxPath);
505
-
506
-        $currentObject = null;
507
-        $objectNode = null;
508
-        $isNewNode = false;
509
-
510
-        $result = $home->getCalendarObjectByUID($uid);
511
-        if ($result) {
512
-            // There was an existing object, we need to update probably.
513
-            $objectPath = $homePath . '/' . $result;
514
-            $objectNode = $this->server->tree->getNodeForPath($objectPath);
515
-            $oldICalendarData = $objectNode->get();
516
-            $currentObject = Reader::read($oldICalendarData);
517
-        } else {
518
-            $isNewNode = true;
519
-        }
520
-
521
-        $broker = new ITip\Broker();
522
-        $newObject = $broker->processMessage($iTipMessage, $currentObject);
523
-
524
-        $inbox->createFile($newFileName, $iTipMessage->message->serialize());
525
-
526
-        if (!$newObject) {
527
-            // We received an iTip message referring to a UID that we don't
528
-            // have in any calendars yet, and processMessage did not give us a
529
-            // calendarobject back.
530
-            //
531
-            // The implication is that processMessage did not understand the
532
-            // iTip message.
533
-            $iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
534
-            return;
535
-        }
536
-
537
-        // Note that we are bypassing ACL on purpose by calling this directly.
538
-        // We may need to look a bit deeper into this later. Supporting ACL
539
-        // here would be nice.
540
-        if ($isNewNode) {
541
-            $calendar = $this->server->tree->getNodeForPath($calendarPath);
542
-            $calendar->createFile($newFileName, $newObject->serialize());
543
-        } else {
544
-            // If the message was a reply, we may have to inform other
545
-            // attendees of this attendees status. Therefore we're shooting off
546
-            // another itipMessage.
547
-            if ($iTipMessage->method === 'REPLY') {
548
-                $this->processICalendarChange(
549
-                    $oldICalendarData,
550
-                    $newObject,
551
-                    [$iTipMessage->recipient],
552
-                    [$iTipMessage->sender]
553
-                );
554
-            }
555
-            $objectNode->put($newObject->serialize());
556
-        }
557
-        $iTipMessage->scheduleStatus = '1.2;Message delivered locally';
558
-
559
-    }
560
-
561
-    /**
562
-     * This method looks at an old iCalendar object, a new iCalendar object and
563
-     * starts sending scheduling messages based on the changes.
564
-     *
565
-     * A list of addresses needs to be specified, so the system knows who made
566
-     * the update, because the behavior may be different based on if it's an
567
-     * attendee or an organizer.
568
-     *
569
-     * This method may update $newObject to add any status changes.
570
-     *
571
-     * @param VCalendar|string $oldObject
572
-     * @param VCalendar $newObject
573
-     * @param array $addresses
574
-     * @param array $ignore Any addresses to not send messages to.
575
-     * @param bool $modified A marker to indicate that the original object
576
-     *   modified by this process.
577
-     * @return void
578
-     */
579
-    protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
580
-
581
-        $broker = new ITip\Broker();
582
-        $messages = $broker->parseEvent($newObject, $addresses, $oldObject);
583
-
584
-        if ($messages) $modified = true;
585
-
586
-        foreach ($messages as $message) {
587
-
588
-            if (in_array($message->recipient, $ignore)) {
589
-                continue;
590
-            }
591
-
592
-            $this->deliver($message);
593
-
594
-            if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
595
-                if ($message->scheduleStatus) {
596
-                    $newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
597
-                }
598
-                unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
599
-
600
-            } else {
601
-
602
-                if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
603
-
604
-                    if ($attendee->getNormalizedValue() === $message->recipient) {
605
-                        if ($message->scheduleStatus) {
606
-                            $attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
607
-                        }
608
-                        unset($attendee['SCHEDULE-FORCE-SEND']);
609
-                        break;
610
-                    }
611
-
612
-                }
613
-
614
-            }
615
-
616
-        }
617
-
618
-    }
619
-
620
-    /**
621
-     * Returns a list of addresses that are associated with a principal.
622
-     *
623
-     * @param string $principal
624
-     * @return array
625
-     */
626
-    protected function getAddressesForPrincipal($principal) {
627
-
628
-        $CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
629
-
630
-        $properties = $this->server->getProperties(
631
-            $principal,
632
-            [$CUAS]
633
-        );
634
-
635
-        // If we can't find this information, we'll stop processing
636
-        if (!isset($properties[$CUAS])) {
637
-            return;
638
-        }
639
-
640
-        $addresses = $properties[$CUAS]->getHrefs();
641
-        return $addresses;
642
-
643
-    }
644
-
645
-    /**
646
-     * This method handles POST requests to the schedule-outbox.
647
-     *
648
-     * Currently, two types of requests are support:
649
-     *   * FREEBUSY requests from RFC 6638
650
-     *   * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
651
-     *
652
-     * The latter is from an expired early draft of the CalDAV scheduling
653
-     * extensions, but iCal depends on a feature from that spec, so we
654
-     * implement it.
655
-     *
656
-     * @param IOutbox $outboxNode
657
-     * @param RequestInterface $request
658
-     * @param ResponseInterface $response
659
-     * @return void
660
-     */
661
-    public function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {
662
-
663
-        $outboxPath = $request->getPath();
664
-
665
-        // Parsing the request body
666
-        try {
667
-            $vObject = VObject\Reader::read($request->getBody());
668
-        } catch (VObject\ParseException $e) {
669
-            throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
670
-        }
671
-
672
-        // The incoming iCalendar object must have a METHOD property, and a
673
-        // component. The combination of both determines what type of request
674
-        // this is.
675
-        $componentType = null;
676
-        foreach ($vObject->getComponents() as $component) {
677
-            if ($component->name !== 'VTIMEZONE') {
678
-                $componentType = $component->name;
679
-                break;
680
-            }
681
-        }
682
-        if (is_null($componentType)) {
683
-            throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
684
-        }
57
+	/**
58
+	 * This is the official CalDAV namespace
59
+	 */
60
+	const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav';
61
+
62
+	/**
63
+	 * Reference to main Server object.
64
+	 *
65
+	 * @var Server
66
+	 */
67
+	protected $server;
68
+
69
+	/**
70
+	 * Returns a list of features for the DAV: HTTP header.
71
+	 *
72
+	 * @return array
73
+	 */
74
+	public function getFeatures() {
75
+
76
+		return ['calendar-auto-schedule', 'calendar-availability'];
77
+
78
+	}
79
+
80
+	/**
81
+	 * Returns the name of the plugin.
82
+	 *
83
+	 * Using this name other plugins will be able to access other plugins
84
+	 * using Server::getPlugin
85
+	 *
86
+	 * @return string
87
+	 */
88
+	public function getPluginName() {
89
+
90
+		return 'caldav-schedule';
91
+
92
+	}
93
+
94
+	/**
95
+	 * Initializes the plugin
96
+	 *
97
+	 * @param Server $server
98
+	 * @return void
99
+	 */
100
+	public function initialize(Server $server) {
101
+
102
+		$this->server = $server;
103
+		$server->on('method:POST',          [$this, 'httpPost']);
104
+		$server->on('propFind',             [$this, 'propFind']);
105
+		$server->on('propPatch',            [$this, 'propPatch']);
106
+		$server->on('calendarObjectChange', [$this, 'calendarObjectChange']);
107
+		$server->on('beforeUnbind',         [$this, 'beforeUnbind']);
108
+		$server->on('schedule',             [$this, 'scheduleLocalDelivery']);
109
+
110
+		$ns = '{' . self::NS_CALDAV . '}';
111
+
112
+		/**
113
+		 * This information ensures that the {DAV:}resourcetype property has
114
+		 * the correct values.
115
+		 */
116
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IOutbox'] = $ns . 'schedule-outbox';
117
+		$server->resourceTypeMapping['\\Sabre\\CalDAV\\Schedule\\IInbox'] = $ns . 'schedule-inbox';
118
+
119
+		/**
120
+		 * Properties we protect are made read-only by the server.
121
+		 */
122
+		array_push($server->protectedProperties,
123
+			$ns . 'schedule-inbox-URL',
124
+			$ns . 'schedule-outbox-URL',
125
+			$ns . 'calendar-user-address-set',
126
+			$ns . 'calendar-user-type',
127
+			$ns . 'schedule-default-calendar-URL'
128
+		);
129
+
130
+	}
131
+
132
+	/**
133
+	 * Use this method to tell the server this plugin defines additional
134
+	 * HTTP methods.
135
+	 *
136
+	 * This method is passed a uri. It should only return HTTP methods that are
137
+	 * available for the specified uri.
138
+	 *
139
+	 * @param string $uri
140
+	 * @return array
141
+	 */
142
+	public function getHTTPMethods($uri) {
143
+
144
+		try {
145
+			$node = $this->server->tree->getNodeForPath($uri);
146
+		} catch (NotFound $e) {
147
+			return [];
148
+		}
149
+
150
+		if ($node instanceof IOutbox) {
151
+			return ['POST'];
152
+		}
153
+
154
+		return [];
155
+
156
+	}
157
+
158
+	/**
159
+	 * This method handles POST request for the outbox.
160
+	 *
161
+	 * @param RequestInterface $request
162
+	 * @param ResponseInterface $response
163
+	 * @return bool
164
+	 */
165
+	public function httpPost(RequestInterface $request, ResponseInterface $response) {
166
+
167
+		// Checking if this is a text/calendar content type
168
+		$contentType = $request->getHeader('Content-Type');
169
+		if (strpos($contentType, 'text/calendar') !== 0) {
170
+			return;
171
+		}
172
+
173
+		$path = $request->getPath();
174
+
175
+		// Checking if we're talking to an outbox
176
+		try {
177
+			$node = $this->server->tree->getNodeForPath($path);
178
+		} catch (NotFound $e) {
179
+			return;
180
+		}
181
+		if (!$node instanceof IOutbox)
182
+			return;
183
+
184
+		$this->server->transactionType = 'post-caldav-outbox';
185
+		$this->outboxRequest($node, $request, $response);
186
+
187
+		// Returning false breaks the event chain and tells the server we've
188
+		// handled the request.
189
+		return false;
190
+
191
+	}
192
+
193
+	/**
194
+	 * This method handler is invoked during fetching of properties.
195
+	 *
196
+	 * We use this event to add calendar-auto-schedule-specific properties.
197
+	 *
198
+	 * @param PropFind $propFind
199
+	 * @param INode $node
200
+	 * @return void
201
+	 */
202
+	public function propFind(PropFind $propFind, INode $node) {
203
+
204
+		if ($node instanceof DAVACL\IPrincipal) {
205
+
206
+			$caldavPlugin = $this->server->getPlugin('caldav');
207
+			$principalUrl = $node->getPrincipalUrl();
208
+
209
+			// schedule-outbox-URL property
210
+			$propFind->handle('{' . self::NS_CALDAV . '}schedule-outbox-URL', function() use ($principalUrl, $caldavPlugin) {
211
+
212
+				$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
213
+				if (!$calendarHomePath) {
214
+					return null;
215
+				}
216
+				$outboxPath = $calendarHomePath . '/outbox/';
217
+
218
+				return new Href($outboxPath);
219
+
220
+			});
221
+			// schedule-inbox-URL property
222
+			$propFind->handle('{' . self::NS_CALDAV . '}schedule-inbox-URL', function() use ($principalUrl, $caldavPlugin) {
223
+
224
+				$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
225
+				if (!$calendarHomePath) {
226
+					return null;
227
+				}
228
+				$inboxPath = $calendarHomePath . '/inbox/';
229
+
230
+				return new Href($inboxPath);
231
+
232
+			});
233
+
234
+			$propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($principalUrl, $caldavPlugin) {
235
+
236
+				// We don't support customizing this property yet, so in the
237
+				// meantime we just grab the first calendar in the home-set.
238
+				$calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl);
239
+
240
+				if (!$calendarHomePath) {
241
+					return null;
242
+				}
243
+
244
+				$sccs = '{' . self::NS_CALDAV . '}supported-calendar-component-set';
245
+
246
+				$result = $this->server->getPropertiesForPath($calendarHomePath, [
247
+					'{DAV:}resourcetype',
248
+					$sccs,
249
+				], 1);
250
+
251
+				foreach ($result as $child) {
252
+					if (!isset($child[200]['{DAV:}resourcetype']) || !$child[200]['{DAV:}resourcetype']->is('{' . self::NS_CALDAV . '}calendar') || $child[200]['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared')) {
253
+						// Node is either not a calendar or a shared instance.
254
+						continue;
255
+					}
256
+					if (!isset($child[200][$sccs]) || in_array('VEVENT', $child[200][$sccs]->getValue())) {
257
+						// Either there is no supported-calendar-component-set
258
+						// (which is fine) or we found one that supports VEVENT.
259
+						return new Href($child['href']);
260
+					}
261
+				}
262
+
263
+			});
264
+
265
+			// The server currently reports every principal to be of type
266
+			// 'INDIVIDUAL'
267
+			$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function() {
268
+
269
+				return 'INDIVIDUAL';
270
+
271
+			});
272
+
273
+		}
274
+
275
+		// Mapping the old property to the new property.
276
+		$propFind->handle('{http://calendarserver.org/ns/}calendar-availability', function() use ($propFind, $node) {
277
+
278
+			 // In case it wasn't clear, the only difference is that we map the
279
+			// old property to a different namespace.
280
+			 $availProp = '{' . self::NS_CALDAV . '}calendar-availability';
281
+			 $subPropFind = new PropFind(
282
+				 $propFind->getPath(),
283
+				 [$availProp]
284
+			 );
285
+
286
+			 $this->server->getPropertiesByNode(
287
+				 $subPropFind,
288
+				 $node
289
+			 );
290
+
291
+			 $propFind->set(
292
+				 '{http://calendarserver.org/ns/}calendar-availability',
293
+				 $subPropFind->get($availProp),
294
+				 $subPropFind->getStatus($availProp)
295
+			 );
296
+
297
+		});
298
+
299
+	}
300
+
301
+	/**
302
+	 * This method is called during property updates.
303
+	 *
304
+	 * @param string $path
305
+	 * @param PropPatch $propPatch
306
+	 * @return void
307
+	 */
308
+	public function propPatch($path, PropPatch $propPatch) {
309
+
310
+		// Mapping the old property to the new property.
311
+		$propPatch->handle('{http://calendarserver.org/ns/}calendar-availability', function($value) use ($path) {
312
+
313
+			$availProp = '{' . self::NS_CALDAV . '}calendar-availability';
314
+			$subPropPatch = new PropPatch([$availProp => $value]);
315
+			$this->server->emit('propPatch', [$path, $subPropPatch]);
316
+			$subPropPatch->commit();
317
+
318
+			return $subPropPatch->getResult()[$availProp];
319
+
320
+		});
321
+
322
+	}
323
+
324
+	/**
325
+	 * This method is triggered whenever there was a calendar object gets
326
+	 * created or updated.
327
+	 *
328
+	 * @param RequestInterface $request HTTP request
329
+	 * @param ResponseInterface $response HTTP Response
330
+	 * @param VCalendar $vCal Parsed iCalendar object
331
+	 * @param mixed $calendarPath Path to calendar collection
332
+	 * @param mixed $modified The iCalendar object has been touched.
333
+	 * @param mixed $isNew Whether this was a new item or we're updating one
334
+	 * @return void
335
+	 */
336
+	public function calendarObjectChange(RequestInterface $request, ResponseInterface $response, VCalendar $vCal, $calendarPath, &$modified, $isNew) {
337
+
338
+		if (!$this->scheduleReply($this->server->httpRequest)) {
339
+			return;
340
+		}
341
+
342
+		$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
343
+
344
+		$addresses = $this->getAddressesForPrincipal(
345
+			$calendarNode->getOwner()
346
+		);
347
+
348
+		if (!$isNew) {
349
+			$node = $this->server->tree->getNodeForPath($request->getPath());
350
+			$oldObj = Reader::read($node->get());
351
+		} else {
352
+			$oldObj = null;
353
+		}
354
+
355
+		$this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified);
356
+
357
+		if ($oldObj) {
358
+			// Destroy circular references so PHP will GC the object.
359
+			$oldObj->destroy();
360
+		}
361
+
362
+	}
363
+
364
+	/**
365
+	 * This method is responsible for delivering the ITip message.
366
+	 *
367
+	 * @param ITip\Message $itipMessage
368
+	 * @return void
369
+	 */
370
+	public function deliver(ITip\Message $iTipMessage) {
371
+
372
+		$this->server->emit('schedule', [$iTipMessage]);
373
+		if (!$iTipMessage->scheduleStatus) {
374
+			$iTipMessage->scheduleStatus = '5.2;There was no system capable of delivering the scheduling message';
375
+		}
376
+		// In case the change was considered 'insignificant', we are going to
377
+		// remove any error statuses, if any. See ticket #525.
378
+		list($baseCode) = explode('.', $iTipMessage->scheduleStatus);
379
+		if (!$iTipMessage->significantChange && in_array($baseCode, ['3', '5'])) {
380
+			$iTipMessage->scheduleStatus = null;
381
+		}
382
+
383
+	}
384
+
385
+	/**
386
+	 * This method is triggered before a file gets deleted.
387
+	 *
388
+	 * We use this event to make sure that when this happens, attendees get
389
+	 * cancellations, and organizers get 'DECLINED' statuses.
390
+	 *
391
+	 * @param string $path
392
+	 * @return void
393
+	 */
394
+	public function beforeUnbind($path) {
395
+
396
+		if ($this->server->httpRequest->getMethod() === 'MOVE') return;
397
+
398
+		$node = $this->server->tree->getNodeForPath($path);
399
+
400
+		if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) {
401
+			return;
402
+		}
403
+
404
+		if (!$this->scheduleReply($this->server->httpRequest)) {
405
+			return;
406
+		}
407
+
408
+		$addresses = $this->getAddressesForPrincipal(
409
+			$node->getOwner()
410
+		);
411
+
412
+		$broker = new ITip\Broker();
413
+		$messages = $broker->parseEvent(null, $addresses, $node->get());
414
+
415
+		foreach ($messages as $message) {
416
+			$this->deliver($message);
417
+		}
418
+
419
+	}
420
+
421
+	/**
422
+	 * Event handler for the 'schedule' event.
423
+	 *
424
+	 * This handler attempts to look at local accounts to deliver the
425
+	 * scheduling object.
426
+	 *
427
+	 * @param ITip\Message $iTipMessage
428
+	 * @return void
429
+	 */
430
+	public function scheduleLocalDelivery(ITip\Message $iTipMessage) {
431
+
432
+		$aclPlugin = $this->server->getPlugin('acl');
433
+
434
+		// Local delivery is not available if the ACL plugin is not loaded.
435
+		if (!$aclPlugin) {
436
+			return;
437
+		}
438
+
439
+		$caldavNS = '{' . self::NS_CALDAV . '}';
440
+
441
+		$principalUri = $aclPlugin->getPrincipalByUri($iTipMessage->recipient);
442
+		if (!$principalUri) {
443
+			$iTipMessage->scheduleStatus = '3.7;Could not find principal.';
444
+			return;
445
+		}
446
+
447
+		// We found a principal URL, now we need to find its inbox.
448
+		// Unfortunately we may not have sufficient privileges to find this, so
449
+		// we are temporarily turning off ACL to let this come through.
450
+		//
451
+		// Once we support PHP 5.5, this should be wrapped in a try..finally
452
+		// block so we can ensure that this privilege gets added again after.
453
+		$this->server->removeListener('propFind', [$aclPlugin, 'propFind']);
454
+
455
+		$result = $this->server->getProperties(
456
+			$principalUri,
457
+			[
458
+				'{DAV:}principal-URL',
459
+				 $caldavNS . 'calendar-home-set',
460
+				 $caldavNS . 'schedule-inbox-URL',
461
+				 $caldavNS . 'schedule-default-calendar-URL',
462
+				'{http://sabredav.org/ns}email-address',
463
+			]
464
+		);
465
+
466
+		// Re-registering the ACL event
467
+		$this->server->on('propFind', [$aclPlugin, 'propFind'], 20);
468
+
469
+		if (!isset($result[$caldavNS . 'schedule-inbox-URL'])) {
470
+			$iTipMessage->scheduleStatus = '5.2;Could not find local inbox';
471
+			return;
472
+		}
473
+		if (!isset($result[$caldavNS . 'calendar-home-set'])) {
474
+			$iTipMessage->scheduleStatus = '5.2;Could not locate a calendar-home-set';
475
+			return;
476
+		}
477
+		if (!isset($result[$caldavNS . 'schedule-default-calendar-URL'])) {
478
+			$iTipMessage->scheduleStatus = '5.2;Could not find a schedule-default-calendar-URL property';
479
+			return;
480
+		}
481
+
482
+		$calendarPath = $result[$caldavNS . 'schedule-default-calendar-URL']->getHref();
483
+		$homePath = $result[$caldavNS . 'calendar-home-set']->getHref();
484
+		$inboxPath = $result[$caldavNS . 'schedule-inbox-URL']->getHref();
485
+
486
+		if ($iTipMessage->method === 'REPLY') {
487
+			$privilege = 'schedule-deliver-reply';
488
+		} else {
489
+			$privilege = 'schedule-deliver-invite';
490
+		}
491
+
492
+		if (!$aclPlugin->checkPrivileges($inboxPath, $caldavNS . $privilege, DAVACL\Plugin::R_PARENT, false)) {
493
+			$iTipMessage->scheduleStatus = '3.8;organizer did not have the ' . $privilege . ' privilege on the attendees inbox';
494
+			return;
495
+		}
496
+
497
+		// Next, we're going to find out if the item already exits in one of
498
+		// the users' calendars.
499
+		$uid = $iTipMessage->uid;
500
+
501
+		$newFileName = 'sabredav-' . \Sabre\DAV\UUIDUtil::getUUID() . '.ics';
502
+
503
+		$home = $this->server->tree->getNodeForPath($homePath);
504
+		$inbox = $this->server->tree->getNodeForPath($inboxPath);
505
+
506
+		$currentObject = null;
507
+		$objectNode = null;
508
+		$isNewNode = false;
509
+
510
+		$result = $home->getCalendarObjectByUID($uid);
511
+		if ($result) {
512
+			// There was an existing object, we need to update probably.
513
+			$objectPath = $homePath . '/' . $result;
514
+			$objectNode = $this->server->tree->getNodeForPath($objectPath);
515
+			$oldICalendarData = $objectNode->get();
516
+			$currentObject = Reader::read($oldICalendarData);
517
+		} else {
518
+			$isNewNode = true;
519
+		}
520
+
521
+		$broker = new ITip\Broker();
522
+		$newObject = $broker->processMessage($iTipMessage, $currentObject);
523
+
524
+		$inbox->createFile($newFileName, $iTipMessage->message->serialize());
525
+
526
+		if (!$newObject) {
527
+			// We received an iTip message referring to a UID that we don't
528
+			// have in any calendars yet, and processMessage did not give us a
529
+			// calendarobject back.
530
+			//
531
+			// The implication is that processMessage did not understand the
532
+			// iTip message.
533
+			$iTipMessage->scheduleStatus = '5.0;iTip message was not processed by the server, likely because we didn\'t understand it.';
534
+			return;
535
+		}
536
+
537
+		// Note that we are bypassing ACL on purpose by calling this directly.
538
+		// We may need to look a bit deeper into this later. Supporting ACL
539
+		// here would be nice.
540
+		if ($isNewNode) {
541
+			$calendar = $this->server->tree->getNodeForPath($calendarPath);
542
+			$calendar->createFile($newFileName, $newObject->serialize());
543
+		} else {
544
+			// If the message was a reply, we may have to inform other
545
+			// attendees of this attendees status. Therefore we're shooting off
546
+			// another itipMessage.
547
+			if ($iTipMessage->method === 'REPLY') {
548
+				$this->processICalendarChange(
549
+					$oldICalendarData,
550
+					$newObject,
551
+					[$iTipMessage->recipient],
552
+					[$iTipMessage->sender]
553
+				);
554
+			}
555
+			$objectNode->put($newObject->serialize());
556
+		}
557
+		$iTipMessage->scheduleStatus = '1.2;Message delivered locally';
558
+
559
+	}
560
+
561
+	/**
562
+	 * This method looks at an old iCalendar object, a new iCalendar object and
563
+	 * starts sending scheduling messages based on the changes.
564
+	 *
565
+	 * A list of addresses needs to be specified, so the system knows who made
566
+	 * the update, because the behavior may be different based on if it's an
567
+	 * attendee or an organizer.
568
+	 *
569
+	 * This method may update $newObject to add any status changes.
570
+	 *
571
+	 * @param VCalendar|string $oldObject
572
+	 * @param VCalendar $newObject
573
+	 * @param array $addresses
574
+	 * @param array $ignore Any addresses to not send messages to.
575
+	 * @param bool $modified A marker to indicate that the original object
576
+	 *   modified by this process.
577
+	 * @return void
578
+	 */
579
+	protected function processICalendarChange($oldObject = null, VCalendar $newObject, array $addresses, array $ignore = [], &$modified = false) {
580
+
581
+		$broker = new ITip\Broker();
582
+		$messages = $broker->parseEvent($newObject, $addresses, $oldObject);
583
+
584
+		if ($messages) $modified = true;
585
+
586
+		foreach ($messages as $message) {
587
+
588
+			if (in_array($message->recipient, $ignore)) {
589
+				continue;
590
+			}
591
+
592
+			$this->deliver($message);
593
+
594
+			if (isset($newObject->VEVENT->ORGANIZER) && ($newObject->VEVENT->ORGANIZER->getNormalizedValue() === $message->recipient)) {
595
+				if ($message->scheduleStatus) {
596
+					$newObject->VEVENT->ORGANIZER['SCHEDULE-STATUS'] = $message->getScheduleStatus();
597
+				}
598
+				unset($newObject->VEVENT->ORGANIZER['SCHEDULE-FORCE-SEND']);
599
+
600
+			} else {
601
+
602
+				if (isset($newObject->VEVENT->ATTENDEE)) foreach ($newObject->VEVENT->ATTENDEE as $attendee) {
603
+
604
+					if ($attendee->getNormalizedValue() === $message->recipient) {
605
+						if ($message->scheduleStatus) {
606
+							$attendee['SCHEDULE-STATUS'] = $message->getScheduleStatus();
607
+						}
608
+						unset($attendee['SCHEDULE-FORCE-SEND']);
609
+						break;
610
+					}
611
+
612
+				}
613
+
614
+			}
615
+
616
+		}
617
+
618
+	}
619
+
620
+	/**
621
+	 * Returns a list of addresses that are associated with a principal.
622
+	 *
623
+	 * @param string $principal
624
+	 * @return array
625
+	 */
626
+	protected function getAddressesForPrincipal($principal) {
627
+
628
+		$CUAS = '{' . self::NS_CALDAV . '}calendar-user-address-set';
629
+
630
+		$properties = $this->server->getProperties(
631
+			$principal,
632
+			[$CUAS]
633
+		);
634
+
635
+		// If we can't find this information, we'll stop processing
636
+		if (!isset($properties[$CUAS])) {
637
+			return;
638
+		}
639
+
640
+		$addresses = $properties[$CUAS]->getHrefs();
641
+		return $addresses;
642
+
643
+	}
644
+
645
+	/**
646
+	 * This method handles POST requests to the schedule-outbox.
647
+	 *
648
+	 * Currently, two types of requests are support:
649
+	 *   * FREEBUSY requests from RFC 6638
650
+	 *   * Simple iTIP messages from draft-desruisseaux-caldav-sched-04
651
+	 *
652
+	 * The latter is from an expired early draft of the CalDAV scheduling
653
+	 * extensions, but iCal depends on a feature from that spec, so we
654
+	 * implement it.
655
+	 *
656
+	 * @param IOutbox $outboxNode
657
+	 * @param RequestInterface $request
658
+	 * @param ResponseInterface $response
659
+	 * @return void
660
+	 */
661
+	public function outboxRequest(IOutbox $outboxNode, RequestInterface $request, ResponseInterface $response) {
662
+
663
+		$outboxPath = $request->getPath();
664
+
665
+		// Parsing the request body
666
+		try {
667
+			$vObject = VObject\Reader::read($request->getBody());
668
+		} catch (VObject\ParseException $e) {
669
+			throw new BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage());
670
+		}
671
+
672
+		// The incoming iCalendar object must have a METHOD property, and a
673
+		// component. The combination of both determines what type of request
674
+		// this is.
675
+		$componentType = null;
676
+		foreach ($vObject->getComponents() as $component) {
677
+			if ($component->name !== 'VTIMEZONE') {
678
+				$componentType = $component->name;
679
+				break;
680
+			}
681
+		}
682
+		if (is_null($componentType)) {
683
+			throw new BadRequest('We expected at least one VTODO, VJOURNAL, VFREEBUSY or VEVENT component');
684
+		}
685 685
 
686
-        // Validating the METHOD
687
-        $method = strtoupper((string)$vObject->METHOD);
688
-        if (!$method) {
689
-            throw new BadRequest('A METHOD property must be specified in iTIP messages');
690
-        }
686
+		// Validating the METHOD
687
+		$method = strtoupper((string)$vObject->METHOD);
688
+		if (!$method) {
689
+			throw new BadRequest('A METHOD property must be specified in iTIP messages');
690
+		}
691 691
 
692
-        // So we support one type of request:
693
-        //
694
-        // REQUEST with a VFREEBUSY component
692
+		// So we support one type of request:
693
+		//
694
+		// REQUEST with a VFREEBUSY component
695 695
 
696
-        $acl = $this->server->getPlugin('acl');
696
+		$acl = $this->server->getPlugin('acl');
697 697
 
698
-        if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
698
+		if ($componentType === 'VFREEBUSY' && $method === 'REQUEST') {
699 699
 
700
-            $acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
701
-            $this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
702
-
703
-            // Destroy circular references so PHP can GC the object.
704
-            $vObject->destroy();
705
-            unset($vObject);
700
+			$acl && $acl->checkPrivileges($outboxPath, '{' . self::NS_CALDAV . '}schedule-query-freebusy');
701
+			$this->handleFreeBusyRequest($outboxNode, $vObject, $request, $response);
702
+
703
+			// Destroy circular references so PHP can GC the object.
704
+			$vObject->destroy();
705
+			unset($vObject);
706 706
 
707
-        } else {
707
+		} else {
708 708
 
709
-            throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
709
+			throw new NotImplemented('We only support VFREEBUSY (REQUEST) on this endpoint');
710 710
 
711
-        }
712
-
713
-    }
714
-
715
-    /**
716
-     * This method is responsible for parsing a free-busy query request and
717
-     * returning it's result.
718
-     *
719
-     * @param IOutbox $outbox
720
-     * @param VObject\Component $vObject
721
-     * @param RequestInterface $request
722
-     * @param ResponseInterface $response
723
-     * @return string
724
-     */
725
-    protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
726
-
727
-        $vFreeBusy = $vObject->VFREEBUSY;
728
-        $organizer = $vFreeBusy->organizer;
729
-
730
-        $organizer = (string)$organizer;
731
-
732
-        // Validating if the organizer matches the owner of the inbox.
733
-        $owner = $outbox->getOwner();
734
-
735
-        $caldavNS = '{' . self::NS_CALDAV . '}';
736
-
737
-        $uas = $caldavNS . 'calendar-user-address-set';
738
-        $props = $this->server->getProperties($owner, [$uas]);
739
-
740
-        if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
741
-            throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
742
-        }
743
-
744
-        if (!isset($vFreeBusy->ATTENDEE)) {
745
-            throw new BadRequest('You must at least specify 1 attendee');
746
-        }
747
-
748
-        $attendees = [];
749
-        foreach ($vFreeBusy->ATTENDEE as $attendee) {
750
-            $attendees[] = (string)$attendee;
751
-        }
752
-
753
-
754
-        if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
755
-            throw new BadRequest('DTSTART and DTEND must both be specified');
756
-        }
757
-
758
-        $startRange = $vFreeBusy->DTSTART->getDateTime();
759
-        $endRange = $vFreeBusy->DTEND->getDateTime();
760
-
761
-        $results = [];
762
-        foreach ($attendees as $attendee) {
763
-            $results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
764
-        }
765
-
766
-        $dom = new \DOMDocument('1.0', 'utf-8');
767
-        $dom->formatOutput = true;
768
-        $scheduleResponse = $dom->createElement('cal:schedule-response');
769
-        foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
770
-
771
-            $scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
772
-
773
-        }
774
-        $dom->appendChild($scheduleResponse);
775
-
776
-        foreach ($results as $result) {
777
-            $xresponse = $dom->createElement('cal:response');
778
-
779
-            $recipient = $dom->createElement('cal:recipient');
780
-            $recipientHref = $dom->createElement('d:href');
781
-
782
-            $recipientHref->appendChild($dom->createTextNode($result['href']));
783
-            $recipient->appendChild($recipientHref);
784
-            $xresponse->appendChild($recipient);
785
-
786
-            $reqStatus = $dom->createElement('cal:request-status');
787
-            $reqStatus->appendChild($dom->createTextNode($result['request-status']));
788
-            $xresponse->appendChild($reqStatus);
789
-
790
-            if (isset($result['calendar-data'])) {
791
-
792
-                $calendardata = $dom->createElement('cal:calendar-data');
793
-                $calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
794
-                $xresponse->appendChild($calendardata);
795
-
796
-            }
797
-            $scheduleResponse->appendChild($xresponse);
798
-        }
799
-
800
-        $response->setStatus(200);
801
-        $response->setHeader('Content-Type', 'application/xml');
802
-        $response->setBody($dom->saveXML());
803
-
804
-    }
805
-
806
-    /**
807
-     * Returns free-busy information for a specific address. The returned
808
-     * data is an array containing the following properties:
809
-     *
810
-     * calendar-data : A VFREEBUSY VObject
811
-     * request-status : an iTip status code.
812
-     * href: The principal's email address, as requested
813
-     *
814
-     * The following request status codes may be returned:
815
-     *   * 2.0;description
816
-     *   * 3.7;description
817
-     *
818
-     * @param string $email address
819
-     * @param DateTimeInterface $start
820
-     * @param DateTimeInterface $end
821
-     * @param VObject\Component $request
822
-     * @return array
823
-     */
824
-    protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
825
-
826
-        $caldavNS = '{' . self::NS_CALDAV . '}';
827
-
828
-        $aclPlugin = $this->server->getPlugin('acl');
829
-        if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
830
-
831
-        $result = $aclPlugin->principalSearch(
832
-            ['{http://sabredav.org/ns}email-address' => $email],
833
-            [
834
-                '{DAV:}principal-URL',
835
-                $caldavNS . 'calendar-home-set',
836
-                $caldavNS . 'schedule-inbox-URL',
837
-                '{http://sabredav.org/ns}email-address',
838
-
839
-            ]
840
-        );
841
-
842
-        if (!count($result)) {
843
-            return [
844
-                'request-status' => '3.7;Could not find principal',
845
-                'href'           => 'mailto:' . $email,
846
-            ];
847
-        }
848
-
849
-        if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
850
-            return [
851
-                'request-status' => '3.7;No calendar-home-set property found',
852
-                'href'           => 'mailto:' . $email,
853
-            ];
854
-        }
855
-        if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
856
-            return [
857
-                'request-status' => '3.7;No schedule-inbox-URL property found',
858
-                'href'           => 'mailto:' . $email,
859
-            ];
860
-        }
861
-        $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
862
-        $inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
863
-
864
-        // Grabbing the calendar list
865
-        $objects = [];
866
-        $calendarTimeZone = new DateTimeZone('UTC');
867
-
868
-        foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
869
-            if (!$node instanceof ICalendar) {
870
-                continue;
871
-            }
872
-
873
-            $sct = $caldavNS . 'schedule-calendar-transp';
874
-            $ctz = $caldavNS . 'calendar-timezone';
875
-            $props = $node->getProperties([$sct, $ctz]);
876
-
877
-            if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
878
-                // If a calendar is marked as 'transparent', it means we must
879
-                // ignore it for free-busy purposes.
880
-                continue;
881
-            }
882
-
883
-            $aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
884
-
885
-            if (isset($props[$ctz])) {
886
-                $vtimezoneObj = VObject\Reader::read($props[$ctz]);
887
-                $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
888
-
889
-                // Destroy circular references so PHP can garbage collect the object.
890
-                $vtimezoneObj->destroy();
891
-
892
-            }
893
-
894
-            // Getting the list of object uris within the time-range
895
-            $urls = $node->calendarQuery([
896
-                'name'         => 'VCALENDAR',
897
-                'comp-filters' => [
898
-                    [
899
-                        'name'           => 'VEVENT',
900
-                        'comp-filters'   => [],
901
-                        'prop-filters'   => [],
902
-                        'is-not-defined' => false,
903
-                        'time-range'     => [
904
-                            'start' => $start,
905
-                            'end'   => $end,
906
-                        ],
907
-                    ],
908
-                ],
909
-                'prop-filters'   => [],
910
-                'is-not-defined' => false,
911
-                'time-range'     => null,
912
-            ]);
913
-
914
-            $calObjects = array_map(function($url) use ($node) {
915
-                $obj = $node->getChild($url)->get();
916
-                return $obj;
917
-            }, $urls);
918
-
919
-            $objects = array_merge($objects, $calObjects);
920
-
921
-        }
922
-
923
-        $inboxProps = $this->server->getProperties(
924
-            $inboxUrl,
925
-            $caldavNS . 'calendar-availability'
926
-        );
927
-
928
-        $vcalendar = new VObject\Component\VCalendar();
929
-        $vcalendar->METHOD = 'REPLY';
930
-
931
-        $generator = new VObject\FreeBusyGenerator();
932
-        $generator->setObjects($objects);
933
-        $generator->setTimeRange($start, $end);
934
-        $generator->setBaseObject($vcalendar);
935
-        $generator->setTimeZone($calendarTimeZone);
936
-
937
-        if ($inboxProps) {
938
-            $generator->setVAvailability(
939
-                VObject\Reader::read(
940
-                    $inboxProps[$caldavNS . 'calendar-availability']
941
-                )
942
-            );
943
-        }
944
-
945
-        $result = $generator->getResult();
946
-
947
-        $vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
948
-        $vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
949
-        $vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
950
-
951
-        return [
952
-            'calendar-data'  => $result,
953
-            'request-status' => '2.0;Success',
954
-            'href'           => 'mailto:' . $email,
955
-        ];
956
-    }
957
-
958
-    /**
959
-     * This method checks the 'Schedule-Reply' header
960
-     * and returns false if it's 'F', otherwise true.
961
-     *
962
-     * @param RequestInterface $request
963
-     * @return bool
964
-     */
965
-    private function scheduleReply(RequestInterface $request) {
966
-
967
-        $scheduleReply = $request->getHeader('Schedule-Reply');
968
-        return $scheduleReply !== 'F';
969
-
970
-    }
971
-
972
-    /**
973
-     * Returns a bunch of meta-data about the plugin.
974
-     *
975
-     * Providing this information is optional, and is mainly displayed by the
976
-     * Browser plugin.
977
-     *
978
-     * The description key in the returned array may contain html and will not
979
-     * be sanitized.
980
-     *
981
-     * @return array
982
-     */
983
-    public function getPluginInfo() {
984
-
985
-        return [
986
-            'name'        => $this->getPluginName(),
987
-            'description' => 'Adds calendar-auto-schedule, as defined in rf6868',
988
-            'link'        => 'http://sabre.io/dav/scheduling/',
989
-        ];
990
-
991
-    }
711
+		}
712
+
713
+	}
714
+
715
+	/**
716
+	 * This method is responsible for parsing a free-busy query request and
717
+	 * returning it's result.
718
+	 *
719
+	 * @param IOutbox $outbox
720
+	 * @param VObject\Component $vObject
721
+	 * @param RequestInterface $request
722
+	 * @param ResponseInterface $response
723
+	 * @return string
724
+	 */
725
+	protected function handleFreeBusyRequest(IOutbox $outbox, VObject\Component $vObject, RequestInterface $request, ResponseInterface $response) {
726
+
727
+		$vFreeBusy = $vObject->VFREEBUSY;
728
+		$organizer = $vFreeBusy->organizer;
729
+
730
+		$organizer = (string)$organizer;
731
+
732
+		// Validating if the organizer matches the owner of the inbox.
733
+		$owner = $outbox->getOwner();
734
+
735
+		$caldavNS = '{' . self::NS_CALDAV . '}';
736
+
737
+		$uas = $caldavNS . 'calendar-user-address-set';
738
+		$props = $this->server->getProperties($owner, [$uas]);
739
+
740
+		if (empty($props[$uas]) || !in_array($organizer, $props[$uas]->getHrefs())) {
741
+			throw new Forbidden('The organizer in the request did not match any of the addresses for the owner of this inbox');
742
+		}
743
+
744
+		if (!isset($vFreeBusy->ATTENDEE)) {
745
+			throw new BadRequest('You must at least specify 1 attendee');
746
+		}
747
+
748
+		$attendees = [];
749
+		foreach ($vFreeBusy->ATTENDEE as $attendee) {
750
+			$attendees[] = (string)$attendee;
751
+		}
752
+
753
+
754
+		if (!isset($vFreeBusy->DTSTART) || !isset($vFreeBusy->DTEND)) {
755
+			throw new BadRequest('DTSTART and DTEND must both be specified');
756
+		}
757
+
758
+		$startRange = $vFreeBusy->DTSTART->getDateTime();
759
+		$endRange = $vFreeBusy->DTEND->getDateTime();
760
+
761
+		$results = [];
762
+		foreach ($attendees as $attendee) {
763
+			$results[] = $this->getFreeBusyForEmail($attendee, $startRange, $endRange, $vObject);
764
+		}
765
+
766
+		$dom = new \DOMDocument('1.0', 'utf-8');
767
+		$dom->formatOutput = true;
768
+		$scheduleResponse = $dom->createElement('cal:schedule-response');
769
+		foreach ($this->server->xml->namespaceMap as $namespace => $prefix) {
770
+
771
+			$scheduleResponse->setAttribute('xmlns:' . $prefix, $namespace);
772
+
773
+		}
774
+		$dom->appendChild($scheduleResponse);
775
+
776
+		foreach ($results as $result) {
777
+			$xresponse = $dom->createElement('cal:response');
778
+
779
+			$recipient = $dom->createElement('cal:recipient');
780
+			$recipientHref = $dom->createElement('d:href');
781
+
782
+			$recipientHref->appendChild($dom->createTextNode($result['href']));
783
+			$recipient->appendChild($recipientHref);
784
+			$xresponse->appendChild($recipient);
785
+
786
+			$reqStatus = $dom->createElement('cal:request-status');
787
+			$reqStatus->appendChild($dom->createTextNode($result['request-status']));
788
+			$xresponse->appendChild($reqStatus);
789
+
790
+			if (isset($result['calendar-data'])) {
791
+
792
+				$calendardata = $dom->createElement('cal:calendar-data');
793
+				$calendardata->appendChild($dom->createTextNode(str_replace("\r\n", "\n", $result['calendar-data']->serialize())));
794
+				$xresponse->appendChild($calendardata);
795
+
796
+			}
797
+			$scheduleResponse->appendChild($xresponse);
798
+		}
799
+
800
+		$response->setStatus(200);
801
+		$response->setHeader('Content-Type', 'application/xml');
802
+		$response->setBody($dom->saveXML());
803
+
804
+	}
805
+
806
+	/**
807
+	 * Returns free-busy information for a specific address. The returned
808
+	 * data is an array containing the following properties:
809
+	 *
810
+	 * calendar-data : A VFREEBUSY VObject
811
+	 * request-status : an iTip status code.
812
+	 * href: The principal's email address, as requested
813
+	 *
814
+	 * The following request status codes may be returned:
815
+	 *   * 2.0;description
816
+	 *   * 3.7;description
817
+	 *
818
+	 * @param string $email address
819
+	 * @param DateTimeInterface $start
820
+	 * @param DateTimeInterface $end
821
+	 * @param VObject\Component $request
822
+	 * @return array
823
+	 */
824
+	protected function getFreeBusyForEmail($email, \DateTimeInterface $start, \DateTimeInterface $end, VObject\Component $request) {
825
+
826
+		$caldavNS = '{' . self::NS_CALDAV . '}';
827
+
828
+		$aclPlugin = $this->server->getPlugin('acl');
829
+		if (substr($email, 0, 7) === 'mailto:') $email = substr($email, 7);
830
+
831
+		$result = $aclPlugin->principalSearch(
832
+			['{http://sabredav.org/ns}email-address' => $email],
833
+			[
834
+				'{DAV:}principal-URL',
835
+				$caldavNS . 'calendar-home-set',
836
+				$caldavNS . 'schedule-inbox-URL',
837
+				'{http://sabredav.org/ns}email-address',
838
+
839
+			]
840
+		);
841
+
842
+		if (!count($result)) {
843
+			return [
844
+				'request-status' => '3.7;Could not find principal',
845
+				'href'           => 'mailto:' . $email,
846
+			];
847
+		}
848
+
849
+		if (!isset($result[0][200][$caldavNS . 'calendar-home-set'])) {
850
+			return [
851
+				'request-status' => '3.7;No calendar-home-set property found',
852
+				'href'           => 'mailto:' . $email,
853
+			];
854
+		}
855
+		if (!isset($result[0][200][$caldavNS . 'schedule-inbox-URL'])) {
856
+			return [
857
+				'request-status' => '3.7;No schedule-inbox-URL property found',
858
+				'href'           => 'mailto:' . $email,
859
+			];
860
+		}
861
+		$homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->getHref();
862
+		$inboxUrl = $result[0][200][$caldavNS . 'schedule-inbox-URL']->getHref();
863
+
864
+		// Grabbing the calendar list
865
+		$objects = [];
866
+		$calendarTimeZone = new DateTimeZone('UTC');
867
+
868
+		foreach ($this->server->tree->getNodeForPath($homeSet)->getChildren() as $node) {
869
+			if (!$node instanceof ICalendar) {
870
+				continue;
871
+			}
872
+
873
+			$sct = $caldavNS . 'schedule-calendar-transp';
874
+			$ctz = $caldavNS . 'calendar-timezone';
875
+			$props = $node->getProperties([$sct, $ctz]);
876
+
877
+			if (isset($props[$sct]) && $props[$sct]->getValue() == ScheduleCalendarTransp::TRANSPARENT) {
878
+				// If a calendar is marked as 'transparent', it means we must
879
+				// ignore it for free-busy purposes.
880
+				continue;
881
+			}
882
+
883
+			$aclPlugin->checkPrivileges($homeSet . $node->getName(), $caldavNS . 'read-free-busy');
884
+
885
+			if (isset($props[$ctz])) {
886
+				$vtimezoneObj = VObject\Reader::read($props[$ctz]);
887
+				$calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone();
888
+
889
+				// Destroy circular references so PHP can garbage collect the object.
890
+				$vtimezoneObj->destroy();
891
+
892
+			}
893
+
894
+			// Getting the list of object uris within the time-range
895
+			$urls = $node->calendarQuery([
896
+				'name'         => 'VCALENDAR',
897
+				'comp-filters' => [
898
+					[
899
+						'name'           => 'VEVENT',
900
+						'comp-filters'   => [],
901
+						'prop-filters'   => [],
902
+						'is-not-defined' => false,
903
+						'time-range'     => [
904
+							'start' => $start,
905
+							'end'   => $end,
906
+						],
907
+					],
908
+				],
909
+				'prop-filters'   => [],
910
+				'is-not-defined' => false,
911
+				'time-range'     => null,
912
+			]);
913
+
914
+			$calObjects = array_map(function($url) use ($node) {
915
+				$obj = $node->getChild($url)->get();
916
+				return $obj;
917
+			}, $urls);
918
+
919
+			$objects = array_merge($objects, $calObjects);
920
+
921
+		}
922
+
923
+		$inboxProps = $this->server->getProperties(
924
+			$inboxUrl,
925
+			$caldavNS . 'calendar-availability'
926
+		);
927
+
928
+		$vcalendar = new VObject\Component\VCalendar();
929
+		$vcalendar->METHOD = 'REPLY';
930
+
931
+		$generator = new VObject\FreeBusyGenerator();
932
+		$generator->setObjects($objects);
933
+		$generator->setTimeRange($start, $end);
934
+		$generator->setBaseObject($vcalendar);
935
+		$generator->setTimeZone($calendarTimeZone);
936
+
937
+		if ($inboxProps) {
938
+			$generator->setVAvailability(
939
+				VObject\Reader::read(
940
+					$inboxProps[$caldavNS . 'calendar-availability']
941
+				)
942
+			);
943
+		}
944
+
945
+		$result = $generator->getResult();
946
+
947
+		$vcalendar->VFREEBUSY->ATTENDEE = 'mailto:' . $email;
948
+		$vcalendar->VFREEBUSY->UID = (string)$request->VFREEBUSY->UID;
949
+		$vcalendar->VFREEBUSY->ORGANIZER = clone $request->VFREEBUSY->ORGANIZER;
950
+
951
+		return [
952
+			'calendar-data'  => $result,
953
+			'request-status' => '2.0;Success',
954
+			'href'           => 'mailto:' . $email,
955
+		];
956
+	}
957
+
958
+	/**
959
+	 * This method checks the 'Schedule-Reply' header
960
+	 * and returns false if it's 'F', otherwise true.
961
+	 *
962
+	 * @param RequestInterface $request
963
+	 * @return bool
964
+	 */
965
+	private function scheduleReply(RequestInterface $request) {
966
+
967
+		$scheduleReply = $request->getHeader('Schedule-Reply');
968
+		return $scheduleReply !== 'F';
969
+
970
+	}
971
+
972
+	/**
973
+	 * Returns a bunch of meta-data about the plugin.
974
+	 *
975
+	 * Providing this information is optional, and is mainly displayed by the
976
+	 * Browser plugin.
977
+	 *
978
+	 * The description key in the returned array may contain html and will not
979
+	 * be sanitized.
980
+	 *
981
+	 * @return array
982
+	 */
983
+	public function getPluginInfo() {
984
+
985
+		return [
986
+			'name'        => $this->getPluginName(),
987
+			'description' => 'Adds calendar-auto-schedule, as defined in rf6868',
988
+			'link'        => 'http://sabre.io/dav/scheduling/',
989
+		];
990
+
991
+	}
992 992
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/VObject/Parser/XML.php 2 patches
Doc Comments   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -147,7 +147,7 @@
 block discarded – undo
147 147
     /**
148 148
      * Parse a xCard component.
149 149
      *
150
-     * @param Component $parentComponent
150
+     * @param VCard $parentComponent
151 151
      *
152 152
      * @return void
153 153
      */
Please login to merge, or discard this patch.
Indentation   +361 added lines, -361 removed lines patch added patch discarded remove patch
@@ -20,409 +20,409 @@
 block discarded – undo
20 20
  */
21 21
 class XML extends Parser {
22 22
 
23
-    const XCAL_NAMESPACE  = 'urn:ietf:params:xml:ns:icalendar-2.0';
24
-    const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';
25
-
26
-    /**
27
-     * The input data.
28
-     *
29
-     * @var array
30
-     */
31
-    protected $input;
32
-
33
-    /**
34
-     * A pointer/reference to the input.
35
-     *
36
-     * @var array
37
-     */
38
-    private $pointer;
39
-
40
-    /**
41
-     * Document, root component.
42
-     *
43
-     * @var Sabre\VObject\Document
44
-     */
45
-    protected $root;
46
-
47
-    /**
48
-     * Creates the parser.
49
-     *
50
-     * Optionally, it's possible to parse the input stream here.
51
-     *
52
-     * @param mixed $input
53
-     * @param int $options Any parser options (OPTION constants).
54
-     *
55
-     * @return void
56
-     */
57
-    public function __construct($input = null, $options = 0) {
58
-
59
-        if (0 === $options) {
60
-            $options = parent::OPTION_FORGIVING;
61
-        }
62
-
63
-        parent::__construct($input, $options);
64
-
65
-    }
66
-
67
-    /**
68
-     * Parse xCal or xCard.
69
-     *
70
-     * @param resource|string $input
71
-     * @param int $options
72
-     *
73
-     * @throws \Exception
74
-     *
75
-     * @return Sabre\VObject\Document
76
-     */
77
-    public function parse($input = null, $options = 0) {
78
-
79
-        if (!is_null($input)) {
80
-            $this->setInput($input);
81
-        }
82
-
83
-        if (0 !== $options) {
84
-            $this->options = $options;
85
-        }
86
-
87
-        if (is_null($this->input)) {
88
-            throw new EofException('End of input stream, or no input supplied');
89
-        }
90
-
91
-        switch ($this->input['name']) {
92
-
93
-            case '{' . self::XCAL_NAMESPACE . '}icalendar':
94
-                $this->root = new VCalendar([], false);
95
-                $this->pointer = &$this->input['value'][0];
96
-                $this->parseVCalendarComponents($this->root);
97
-                break;
98
-
99
-            case '{' . self::XCARD_NAMESPACE . '}vcards':
100
-                foreach ($this->input['value'] as &$vCard) {
101
-
102
-                    $this->root = new VCard(['version' => '4.0'], false);
103
-                    $this->pointer  = &$vCard;
104
-                    $this->parseVCardComponents($this->root);
105
-
106
-                    // We just parse the first <vcard /> element.
107
-                    break;
108
-
109
-                }
110
-                break;
111
-
112
-            default:
113
-                throw new ParseException('Unsupported XML standard');
114
-
115
-        }
116
-
117
-        return $this->root;
118
-    }
119
-
120
-    /**
121
-     * Parse a xCalendar component.
122
-     *
123
-     * @param Component $parentComponent
124
-     *
125
-     * @return void
126
-     */
127
-    protected function parseVCalendarComponents(Component $parentComponent) {
128
-
129
-        foreach ($this->pointer['value'] ?: [] as $children) {
130
-
131
-            switch (static::getTagName($children['name'])) {
132
-
133
-                case 'properties':
134
-                    $this->pointer = &$children['value'];
135
-                    $this->parseProperties($parentComponent);
136
-                    break;
137
-
138
-                case 'components':
139
-                    $this->pointer = &$children;
140
-                    $this->parseComponent($parentComponent);
141
-                    break;
142
-            }
143
-        }
23
+	const XCAL_NAMESPACE  = 'urn:ietf:params:xml:ns:icalendar-2.0';
24
+	const XCARD_NAMESPACE = 'urn:ietf:params:xml:ns:vcard-4.0';
25
+
26
+	/**
27
+	 * The input data.
28
+	 *
29
+	 * @var array
30
+	 */
31
+	protected $input;
32
+
33
+	/**
34
+	 * A pointer/reference to the input.
35
+	 *
36
+	 * @var array
37
+	 */
38
+	private $pointer;
39
+
40
+	/**
41
+	 * Document, root component.
42
+	 *
43
+	 * @var Sabre\VObject\Document
44
+	 */
45
+	protected $root;
46
+
47
+	/**
48
+	 * Creates the parser.
49
+	 *
50
+	 * Optionally, it's possible to parse the input stream here.
51
+	 *
52
+	 * @param mixed $input
53
+	 * @param int $options Any parser options (OPTION constants).
54
+	 *
55
+	 * @return void
56
+	 */
57
+	public function __construct($input = null, $options = 0) {
58
+
59
+		if (0 === $options) {
60
+			$options = parent::OPTION_FORGIVING;
61
+		}
62
+
63
+		parent::__construct($input, $options);
64
+
65
+	}
66
+
67
+	/**
68
+	 * Parse xCal or xCard.
69
+	 *
70
+	 * @param resource|string $input
71
+	 * @param int $options
72
+	 *
73
+	 * @throws \Exception
74
+	 *
75
+	 * @return Sabre\VObject\Document
76
+	 */
77
+	public function parse($input = null, $options = 0) {
78
+
79
+		if (!is_null($input)) {
80
+			$this->setInput($input);
81
+		}
82
+
83
+		if (0 !== $options) {
84
+			$this->options = $options;
85
+		}
86
+
87
+		if (is_null($this->input)) {
88
+			throw new EofException('End of input stream, or no input supplied');
89
+		}
90
+
91
+		switch ($this->input['name']) {
92
+
93
+			case '{' . self::XCAL_NAMESPACE . '}icalendar':
94
+				$this->root = new VCalendar([], false);
95
+				$this->pointer = &$this->input['value'][0];
96
+				$this->parseVCalendarComponents($this->root);
97
+				break;
98
+
99
+			case '{' . self::XCARD_NAMESPACE . '}vcards':
100
+				foreach ($this->input['value'] as &$vCard) {
101
+
102
+					$this->root = new VCard(['version' => '4.0'], false);
103
+					$this->pointer  = &$vCard;
104
+					$this->parseVCardComponents($this->root);
105
+
106
+					// We just parse the first <vcard /> element.
107
+					break;
108
+
109
+				}
110
+				break;
111
+
112
+			default:
113
+				throw new ParseException('Unsupported XML standard');
114
+
115
+		}
116
+
117
+		return $this->root;
118
+	}
119
+
120
+	/**
121
+	 * Parse a xCalendar component.
122
+	 *
123
+	 * @param Component $parentComponent
124
+	 *
125
+	 * @return void
126
+	 */
127
+	protected function parseVCalendarComponents(Component $parentComponent) {
128
+
129
+		foreach ($this->pointer['value'] ?: [] as $children) {
130
+
131
+			switch (static::getTagName($children['name'])) {
132
+
133
+				case 'properties':
134
+					$this->pointer = &$children['value'];
135
+					$this->parseProperties($parentComponent);
136
+					break;
137
+
138
+				case 'components':
139
+					$this->pointer = &$children;
140
+					$this->parseComponent($parentComponent);
141
+					break;
142
+			}
143
+		}
144 144
 
145
-    }
145
+	}
146 146
 
147
-    /**
148
-     * Parse a xCard component.
149
-     *
150
-     * @param Component $parentComponent
151
-     *
152
-     * @return void
153
-     */
154
-    protected function parseVCardComponents(Component $parentComponent) {
147
+	/**
148
+	 * Parse a xCard component.
149
+	 *
150
+	 * @param Component $parentComponent
151
+	 *
152
+	 * @return void
153
+	 */
154
+	protected function parseVCardComponents(Component $parentComponent) {
155 155
 
156
-        $this->pointer = &$this->pointer['value'];
157
-        $this->parseProperties($parentComponent);
156
+		$this->pointer = &$this->pointer['value'];
157
+		$this->parseProperties($parentComponent);
158 158
 
159
-    }
159
+	}
160 160
 
161
-    /**
162
-     * Parse xCalendar and xCard properties.
163
-     *
164
-     * @param Component $parentComponent
165
-     * @param string  $propertyNamePrefix
166
-     *
167
-     * @return void
168
-     */
169
-    protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') {
161
+	/**
162
+	 * Parse xCalendar and xCard properties.
163
+	 *
164
+	 * @param Component $parentComponent
165
+	 * @param string  $propertyNamePrefix
166
+	 *
167
+	 * @return void
168
+	 */
169
+	protected function parseProperties(Component $parentComponent, $propertyNamePrefix = '') {
170 170
 
171
-        foreach ($this->pointer ?: [] as $xmlProperty) {
171
+		foreach ($this->pointer ?: [] as $xmlProperty) {
172 172
 
173
-            list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
173
+			list($namespace, $tagName) = SabreXml\Service::parseClarkNotation($xmlProperty['name']);
174 174
 
175
-            $propertyName       = $tagName;
176
-            $propertyValue      = [];
177
-            $propertyParameters = [];
178
-            $propertyType       = 'text';
175
+			$propertyName       = $tagName;
176
+			$propertyValue      = [];
177
+			$propertyParameters = [];
178
+			$propertyType       = 'text';
179 179
 
180
-            // A property which is not part of the standard.
181
-            if ($namespace !== self::XCAL_NAMESPACE
182
-                && $namespace !== self::XCARD_NAMESPACE) {
180
+			// A property which is not part of the standard.
181
+			if ($namespace !== self::XCAL_NAMESPACE
182
+				&& $namespace !== self::XCARD_NAMESPACE) {
183 183
 
184
-                $propertyName = 'xml';
185
-                $value = '<' . $tagName . ' xmlns="' . $namespace . '"';
184
+				$propertyName = 'xml';
185
+				$value = '<' . $tagName . ' xmlns="' . $namespace . '"';
186 186
 
187
-                foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
188
-                    $value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"';
189
-                }
187
+				foreach ($xmlProperty['attributes'] as $attributeName => $attributeValue) {
188
+					$value .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"';
189
+				}
190 190
 
191
-                $value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>';
191
+				$value .= '>' . $xmlProperty['value'] . '</' . $tagName . '>';
192 192
 
193
-                $propertyValue = [$value];
193
+				$propertyValue = [$value];
194 194
 
195
-                $this->createProperty(
196
-                    $parentComponent,
197
-                    $propertyName,
198
-                    $propertyParameters,
199
-                    $propertyType,
200
-                    $propertyValue
201
-                );
195
+				$this->createProperty(
196
+					$parentComponent,
197
+					$propertyName,
198
+					$propertyParameters,
199
+					$propertyType,
200
+					$propertyValue
201
+				);
202 202
 
203
-                continue;
204
-            }
203
+				continue;
204
+			}
205 205
 
206
-            // xCard group.
207
-            if ($propertyName === 'group') {
206
+			// xCard group.
207
+			if ($propertyName === 'group') {
208 208
 
209
-                if (!isset($xmlProperty['attributes']['name'])) {
210
-                    continue;
211
-                }
209
+				if (!isset($xmlProperty['attributes']['name'])) {
210
+					continue;
211
+				}
212 212
 
213
-                $this->pointer = &$xmlProperty['value'];
214
-                $this->parseProperties(
215
-                    $parentComponent,
216
-                    strtoupper($xmlProperty['attributes']['name']) . '.'
217
-                );
213
+				$this->pointer = &$xmlProperty['value'];
214
+				$this->parseProperties(
215
+					$parentComponent,
216
+					strtoupper($xmlProperty['attributes']['name']) . '.'
217
+				);
218 218
 
219
-                continue;
219
+				continue;
220 220
 
221
-            }
221
+			}
222 222
 
223
-            // Collect parameters.
224
-            foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {
223
+			// Collect parameters.
224
+			foreach ($xmlProperty['value'] as $i => $xmlPropertyChild) {
225 225
 
226
-                if (!is_array($xmlPropertyChild)
227
-                    || 'parameters' !== static::getTagName($xmlPropertyChild['name']))
228
-                    continue;
226
+				if (!is_array($xmlPropertyChild)
227
+					|| 'parameters' !== static::getTagName($xmlPropertyChild['name']))
228
+					continue;
229 229
 
230
-                $xmlParameters = $xmlPropertyChild['value'];
230
+				$xmlParameters = $xmlPropertyChild['value'];
231 231
 
232
-                foreach ($xmlParameters as $xmlParameter) {
232
+				foreach ($xmlParameters as $xmlParameter) {
233 233
 
234
-                    $propertyParameterValues = [];
234
+					$propertyParameterValues = [];
235 235
 
236
-                    foreach ($xmlParameter['value'] as $xmlParameterValues) {
237
-                        $propertyParameterValues[] = $xmlParameterValues['value'];
238
-                    }
236
+					foreach ($xmlParameter['value'] as $xmlParameterValues) {
237
+						$propertyParameterValues[] = $xmlParameterValues['value'];
238
+					}
239 239
 
240
-                    $propertyParameters[static::getTagName($xmlParameter['name'])]
241
-                        = implode(',', $propertyParameterValues);
240
+					$propertyParameters[static::getTagName($xmlParameter['name'])]
241
+						= implode(',', $propertyParameterValues);
242 242
 
243
-                }
243
+				}
244 244
 
245
-                array_splice($xmlProperty['value'], $i, 1);
245
+				array_splice($xmlProperty['value'], $i, 1);
246 246
 
247
-            }
247
+			}
248 248
 
249
-            $propertyNameExtended = ($this->root instanceof VCalendar
250
-                                      ? 'xcal'
251
-                                      : 'xcard') . ':' . $propertyName;
249
+			$propertyNameExtended = ($this->root instanceof VCalendar
250
+									  ? 'xcal'
251
+									  : 'xcard') . ':' . $propertyName;
252 252
 
253
-            switch ($propertyNameExtended) {
253
+			switch ($propertyNameExtended) {
254 254
 
255
-                case 'xcal:geo':
256
-                    $propertyType               = 'float';
257
-                    $propertyValue['latitude']  = 0;
258
-                    $propertyValue['longitude'] = 0;
255
+				case 'xcal:geo':
256
+					$propertyType               = 'float';
257
+					$propertyValue['latitude']  = 0;
258
+					$propertyValue['longitude'] = 0;
259 259
 
260
-                    foreach ($xmlProperty['value'] as $xmlRequestChild) {
261
-                        $propertyValue[static::getTagName($xmlRequestChild['name'])]
262
-                            = $xmlRequestChild['value'];
263
-                    }
264
-                    break;
260
+					foreach ($xmlProperty['value'] as $xmlRequestChild) {
261
+						$propertyValue[static::getTagName($xmlRequestChild['name'])]
262
+							= $xmlRequestChild['value'];
263
+					}
264
+					break;
265 265
 
266
-                case 'xcal:request-status':
267
-                    $propertyType = 'text';
266
+				case 'xcal:request-status':
267
+					$propertyType = 'text';
268 268
 
269
-                    foreach ($xmlProperty['value'] as $xmlRequestChild) {
270
-                        $propertyValue[static::getTagName($xmlRequestChild['name'])]
271
-                            = $xmlRequestChild['value'];
272
-                    }
273
-                    break;
269
+					foreach ($xmlProperty['value'] as $xmlRequestChild) {
270
+						$propertyValue[static::getTagName($xmlRequestChild['name'])]
271
+							= $xmlRequestChild['value'];
272
+					}
273
+					break;
274 274
 
275
-                case 'xcal:freebusy':
276
-                    $propertyType = 'freebusy';
277
-                    // We don't break because we only want to set
278
-                    // another property type.
275
+				case 'xcal:freebusy':
276
+					$propertyType = 'freebusy';
277
+					// We don't break because we only want to set
278
+					// another property type.
279 279
 
280
-                case 'xcal:categories':
281
-                case 'xcal:resources':
282
-                case 'xcal:exdate':
283
-                    foreach ($xmlProperty['value'] as $specialChild) {
284
-                        $propertyValue[static::getTagName($specialChild['name'])]
285
-                            = $specialChild['value'];
286
-                    }
287
-                    break;
280
+				case 'xcal:categories':
281
+				case 'xcal:resources':
282
+				case 'xcal:exdate':
283
+					foreach ($xmlProperty['value'] as $specialChild) {
284
+						$propertyValue[static::getTagName($specialChild['name'])]
285
+							= $specialChild['value'];
286
+					}
287
+					break;
288 288
 
289
-                case 'xcal:rdate':
290
-                    $propertyType = 'date-time';
289
+				case 'xcal:rdate':
290
+					$propertyType = 'date-time';
291 291
 
292
-                    foreach ($xmlProperty['value'] as $specialChild) {
293
-
294
-                        $tagName = static::getTagName($specialChild['name']);
295
-
296
-                        if ('period' === $tagName) {
297
-
298
-                            $propertyParameters['value'] = 'PERIOD';
299
-                            $propertyValue[]             = implode('/', $specialChild['value']);
292
+					foreach ($xmlProperty['value'] as $specialChild) {
293
+
294
+						$tagName = static::getTagName($specialChild['name']);
295
+
296
+						if ('period' === $tagName) {
297
+
298
+							$propertyParameters['value'] = 'PERIOD';
299
+							$propertyValue[]             = implode('/', $specialChild['value']);
300 300
 
301
-                        }
302
-                        else {
303
-                            $propertyValue[] = $specialChild['value'];
304
-                        }
305
-                    }
306
-                    break;
307
-
308
-                default:
309
-                    $propertyType  = static::getTagName($xmlProperty['value'][0]['name']);
301
+						}
302
+						else {
303
+							$propertyValue[] = $specialChild['value'];
304
+						}
305
+					}
306
+					break;
307
+
308
+				default:
309
+					$propertyType  = static::getTagName($xmlProperty['value'][0]['name']);
310 310
 
311
-                    foreach ($xmlProperty['value'] as $value) {
312
-                        $propertyValue[] = $value['value'];
313
-                    }
311
+					foreach ($xmlProperty['value'] as $value) {
312
+						$propertyValue[] = $value['value'];
313
+					}
314 314
 
315
-                    if ('date' === $propertyType) {
316
-                        $propertyParameters['value'] = 'DATE';
317
-                    }
318
-                    break;
319
-            }
315
+					if ('date' === $propertyType) {
316
+						$propertyParameters['value'] = 'DATE';
317
+					}
318
+					break;
319
+			}
320 320
 
321
-            $this->createProperty(
322
-                $parentComponent,
323
-                $propertyNamePrefix . $propertyName,
324
-                $propertyParameters,
325
-                $propertyType,
326
-                $propertyValue
327
-            );
328
-
329
-        }
330
-
331
-    }
332
-
333
-    /**
334
-     * Parse a component.
335
-     *
336
-     * @param Component $parentComponent
337
-     *
338
-     * @return void
339
-     */
340
-    protected function parseComponent(Component $parentComponent) {
341
-
342
-        $components = $this->pointer['value'] ?: [];
343
-
344
-        foreach ($components as $component) {
345
-
346
-            $componentName    = static::getTagName($component['name']);
347
-            $currentComponent = $this->root->createComponent(
348
-                $componentName,
349
-                null,
350
-                false
351
-            );
352
-
353
-            $this->pointer = &$component;
354
-            $this->parseVCalendarComponents($currentComponent);
355
-
356
-            $parentComponent->add($currentComponent);
357
-
358
-        }
359
-
360
-    }
361
-
362
-    /**
363
-     * Create a property.
364
-     *
365
-     * @param Component $parentComponent
366
-     * @param string $name
367
-     * @param array $parameters
368
-     * @param string $type
369
-     * @param mixed $value
370
-     *
371
-     * @return void
372
-     */
373
-    protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) {
374
-
375
-        $property = $this->root->createProperty(
376
-            $name,
377
-            null,
378
-            $parameters,
379
-            $type
380
-        );
381
-        $parentComponent->add($property);
382
-        $property->setXmlValue($value);
383
-
384
-    }
385
-
386
-    /**
387
-     * Sets the input data.
388
-     *
389
-     * @param resource|string $input
390
-     *
391
-     * @return void
392
-     */
393
-    public function setInput($input) {
394
-
395
-        if (is_resource($input)) {
396
-            $input = stream_get_contents($input);
397
-        }
398
-
399
-        if (is_string($input)) {
400
-
401
-            $reader = new SabreXml\Reader();
402
-            $reader->elementMap['{' . self::XCAL_NAMESPACE . '}period']
403
-                = 'Sabre\VObject\Parser\XML\Element\KeyValue';
404
-            $reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur']
405
-                = 'Sabre\VObject\Parser\XML\Element\KeyValue';
406
-            $reader->xml($input);
407
-            $input  = $reader->parse();
408
-
409
-        }
410
-
411
-        $this->input = $input;
412
-
413
-    }
414
-
415
-    /**
416
-     * Get tag name from a Clark notation.
417
-     *
418
-     * @param string $clarkedTagName
419
-     *
420
-     * @return string
421
-     */
422
-    protected static function getTagName($clarkedTagName) {
423
-
424
-        list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
425
-        return $tagName;
426
-
427
-    }
321
+			$this->createProperty(
322
+				$parentComponent,
323
+				$propertyNamePrefix . $propertyName,
324
+				$propertyParameters,
325
+				$propertyType,
326
+				$propertyValue
327
+			);
328
+
329
+		}
330
+
331
+	}
332
+
333
+	/**
334
+	 * Parse a component.
335
+	 *
336
+	 * @param Component $parentComponent
337
+	 *
338
+	 * @return void
339
+	 */
340
+	protected function parseComponent(Component $parentComponent) {
341
+
342
+		$components = $this->pointer['value'] ?: [];
343
+
344
+		foreach ($components as $component) {
345
+
346
+			$componentName    = static::getTagName($component['name']);
347
+			$currentComponent = $this->root->createComponent(
348
+				$componentName,
349
+				null,
350
+				false
351
+			);
352
+
353
+			$this->pointer = &$component;
354
+			$this->parseVCalendarComponents($currentComponent);
355
+
356
+			$parentComponent->add($currentComponent);
357
+
358
+		}
359
+
360
+	}
361
+
362
+	/**
363
+	 * Create a property.
364
+	 *
365
+	 * @param Component $parentComponent
366
+	 * @param string $name
367
+	 * @param array $parameters
368
+	 * @param string $type
369
+	 * @param mixed $value
370
+	 *
371
+	 * @return void
372
+	 */
373
+	protected function createProperty(Component $parentComponent, $name, $parameters, $type, $value) {
374
+
375
+		$property = $this->root->createProperty(
376
+			$name,
377
+			null,
378
+			$parameters,
379
+			$type
380
+		);
381
+		$parentComponent->add($property);
382
+		$property->setXmlValue($value);
383
+
384
+	}
385
+
386
+	/**
387
+	 * Sets the input data.
388
+	 *
389
+	 * @param resource|string $input
390
+	 *
391
+	 * @return void
392
+	 */
393
+	public function setInput($input) {
394
+
395
+		if (is_resource($input)) {
396
+			$input = stream_get_contents($input);
397
+		}
398
+
399
+		if (is_string($input)) {
400
+
401
+			$reader = new SabreXml\Reader();
402
+			$reader->elementMap['{' . self::XCAL_NAMESPACE . '}period']
403
+				= 'Sabre\VObject\Parser\XML\Element\KeyValue';
404
+			$reader->elementMap['{' . self::XCAL_NAMESPACE . '}recur']
405
+				= 'Sabre\VObject\Parser\XML\Element\KeyValue';
406
+			$reader->xml($input);
407
+			$input  = $reader->parse();
408
+
409
+		}
410
+
411
+		$this->input = $input;
412
+
413
+	}
414
+
415
+	/**
416
+	 * Get tag name from a Clark notation.
417
+	 *
418
+	 * @param string $clarkedTagName
419
+	 *
420
+	 * @return string
421
+	 */
422
+	protected static function getTagName($clarkedTagName) {
423
+
424
+		list(, $tagName) = SabreXml\Service::parseClarkNotation($clarkedTagName);
425
+		return $tagName;
426
+
427
+	}
428 428
 }
Please login to merge, or discard this patch.
libraries/restler/restler.php 1 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
-    public function isRestlerCompatibilityModeEnabled()
1557
-    {
1558
-        return false;
1559
-    }
1556
+	public function isRestlerCompatibilityModeEnabled()
1557
+	{
1558
+		return false;
1559
+	}
1560 1560
 
1561 1561
 }
1562 1562
 define('RESTLER_PATH', dirname(__FILE__));
1563 1563
\ No newline at end of file
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/RequestDecorator.php 1 patch
Indentation   +210 added lines, -210 removed lines patch added patch discarded remove patch
@@ -14,218 +14,218 @@
 block discarded – undo
14 14
  */
15 15
 class RequestDecorator implements RequestInterface {
16 16
 
17
-    use MessageDecoratorTrait;
17
+	use MessageDecoratorTrait;
18 18
 
19
-    /**
20
-     * Constructor.
21
-     *
22
-     * @param RequestInterface $inner
23
-     */
24
-    public function __construct(RequestInterface $inner) {
19
+	/**
20
+	 * Constructor.
21
+	 *
22
+	 * @param RequestInterface $inner
23
+	 */
24
+	public function __construct(RequestInterface $inner) {
25 25
 
26
-        $this->inner = $inner;
26
+		$this->inner = $inner;
27 27
 
28
-    }
28
+	}
29 29
 
30
-    /**
31
-     * Returns the current HTTP method
32
-     *
33
-     * @return string
34
-     */
35
-    public function getMethod() {
36
-
37
-        return $this->inner->getMethod();
38
-
39
-    }
40
-
41
-    /**
42
-     * Sets the HTTP method
43
-     *
44
-     * @param string $method
45
-     * @return void
46
-     */
47
-    public function setMethod($method) {
48
-
49
-        $this->inner->setMethod($method);
50
-
51
-    }
52
-
53
-    /**
54
-     * Returns the request url.
55
-     *
56
-     * @return string
57
-     */
58
-    public function getUrl() {
59
-
60
-        return $this->inner->getUrl();
61
-
62
-    }
63
-
64
-    /**
65
-     * Sets the request url.
66
-     *
67
-     * @param string $url
68
-     * @return void
69
-     */
70
-    public function setUrl($url) {
71
-
72
-        $this->inner->setUrl($url);
73
-
74
-    }
75
-
76
-    /**
77
-     * Returns the absolute url.
78
-     *
79
-     * @return string
80
-     */
81
-    public function getAbsoluteUrl() {
82
-
83
-        return $this->inner->getAbsoluteUrl();
84
-
85
-    }
86
-
87
-    /**
88
-     * Sets the absolute url.
89
-     *
90
-     * @param string $url
91
-     * @return void
92
-     */
93
-    public function setAbsoluteUrl($url) {
94
-
95
-        $this->inner->setAbsoluteUrl($url);
96
-
97
-    }
98
-
99
-    /**
100
-     * Returns the current base url.
101
-     *
102
-     * @return string
103
-     */
104
-    public function getBaseUrl() {
105
-
106
-        return $this->inner->getBaseUrl();
107
-
108
-    }
109
-
110
-    /**
111
-     * Sets a base url.
112
-     *
113
-     * This url is used for relative path calculations.
114
-     *
115
-     * The base url should default to /
116
-     *
117
-     * @param string $url
118
-     * @return void
119
-     */
120
-    public function setBaseUrl($url) {
121
-
122
-        $this->inner->setBaseUrl($url);
123
-
124
-    }
125
-
126
-    /**
127
-     * Returns the relative path.
128
-     *
129
-     * This is being calculated using the base url. This path will not start
130
-     * with a slash, so it will always return something like
131
-     * 'example/path.html'.
132
-     *
133
-     * If the full path is equal to the base url, this method will return an
134
-     * empty string.
135
-     *
136
-     * This method will also urldecode the path, and if the url was incoded as
137
-     * ISO-8859-1, it will convert it to UTF-8.
138
-     *
139
-     * If the path is outside of the base url, a LogicException will be thrown.
140
-     *
141
-     * @return string
142
-     */
143
-    public function getPath() {
144
-
145
-        return $this->inner->getPath();
146
-
147
-    }
148
-
149
-    /**
150
-     * Returns the list of query parameters.
151
-     *
152
-     * This is equivalent to PHP's $_GET superglobal.
153
-     *
154
-     * @return array
155
-     */
156
-    public function getQueryParameters() {
157
-
158
-        return $this->inner->getQueryParameters();
159
-
160
-    }
161
-
162
-    /**
163
-     * Returns the POST data.
164
-     *
165
-     * This is equivalent to PHP's $_POST superglobal.
166
-     *
167
-     * @return array
168
-     */
169
-    public function getPostData() {
170
-
171
-        return $this->inner->getPostData();
172
-
173
-    }
174
-
175
-    /**
176
-     * Sets the post data.
177
-     *
178
-     * This is equivalent to PHP's $_POST superglobal.
179
-     *
180
-     * This would not have been needed, if POST data was accessible as
181
-     * php://input, but unfortunately we need to special case it.
182
-     *
183
-     * @param array $postData
184
-     * @return void
185
-     */
186
-    public function setPostData(array $postData) {
187
-
188
-        $this->inner->setPostData($postData);
189
-
190
-    }
191
-
192
-
193
-    /**
194
-     * Returns an item from the _SERVER array.
195
-     *
196
-     * If the value does not exist in the array, null is returned.
197
-     *
198
-     * @param string $valueName
199
-     * @return string|null
200
-     */
201
-    public function getRawServerValue($valueName) {
202
-
203
-        return $this->inner->getRawServerValue($valueName);
204
-
205
-    }
206
-
207
-    /**
208
-     * Sets the _SERVER array.
209
-     *
210
-     * @param array $data
211
-     * @return void
212
-     */
213
-    public function setRawServerData(array $data) {
214
-
215
-        $this->inner->setRawServerData($data);
216
-
217
-    }
218
-
219
-    /**
220
-     * Serializes the request object as a string.
221
-     *
222
-     * This is useful for debugging purposes.
223
-     *
224
-     * @return string
225
-     */
226
-    public function __toString() {
227
-
228
-        return $this->inner->__toString();
229
-
230
-    }
30
+	/**
31
+	 * Returns the current HTTP method
32
+	 *
33
+	 * @return string
34
+	 */
35
+	public function getMethod() {
36
+
37
+		return $this->inner->getMethod();
38
+
39
+	}
40
+
41
+	/**
42
+	 * Sets the HTTP method
43
+	 *
44
+	 * @param string $method
45
+	 * @return void
46
+	 */
47
+	public function setMethod($method) {
48
+
49
+		$this->inner->setMethod($method);
50
+
51
+	}
52
+
53
+	/**
54
+	 * Returns the request url.
55
+	 *
56
+	 * @return string
57
+	 */
58
+	public function getUrl() {
59
+
60
+		return $this->inner->getUrl();
61
+
62
+	}
63
+
64
+	/**
65
+	 * Sets the request url.
66
+	 *
67
+	 * @param string $url
68
+	 * @return void
69
+	 */
70
+	public function setUrl($url) {
71
+
72
+		$this->inner->setUrl($url);
73
+
74
+	}
75
+
76
+	/**
77
+	 * Returns the absolute url.
78
+	 *
79
+	 * @return string
80
+	 */
81
+	public function getAbsoluteUrl() {
82
+
83
+		return $this->inner->getAbsoluteUrl();
84
+
85
+	}
86
+
87
+	/**
88
+	 * Sets the absolute url.
89
+	 *
90
+	 * @param string $url
91
+	 * @return void
92
+	 */
93
+	public function setAbsoluteUrl($url) {
94
+
95
+		$this->inner->setAbsoluteUrl($url);
96
+
97
+	}
98
+
99
+	/**
100
+	 * Returns the current base url.
101
+	 *
102
+	 * @return string
103
+	 */
104
+	public function getBaseUrl() {
105
+
106
+		return $this->inner->getBaseUrl();
107
+
108
+	}
109
+
110
+	/**
111
+	 * Sets a base url.
112
+	 *
113
+	 * This url is used for relative path calculations.
114
+	 *
115
+	 * The base url should default to /
116
+	 *
117
+	 * @param string $url
118
+	 * @return void
119
+	 */
120
+	public function setBaseUrl($url) {
121
+
122
+		$this->inner->setBaseUrl($url);
123
+
124
+	}
125
+
126
+	/**
127
+	 * Returns the relative path.
128
+	 *
129
+	 * This is being calculated using the base url. This path will not start
130
+	 * with a slash, so it will always return something like
131
+	 * 'example/path.html'.
132
+	 *
133
+	 * If the full path is equal to the base url, this method will return an
134
+	 * empty string.
135
+	 *
136
+	 * This method will also urldecode the path, and if the url was incoded as
137
+	 * ISO-8859-1, it will convert it to UTF-8.
138
+	 *
139
+	 * If the path is outside of the base url, a LogicException will be thrown.
140
+	 *
141
+	 * @return string
142
+	 */
143
+	public function getPath() {
144
+
145
+		return $this->inner->getPath();
146
+
147
+	}
148
+
149
+	/**
150
+	 * Returns the list of query parameters.
151
+	 *
152
+	 * This is equivalent to PHP's $_GET superglobal.
153
+	 *
154
+	 * @return array
155
+	 */
156
+	public function getQueryParameters() {
157
+
158
+		return $this->inner->getQueryParameters();
159
+
160
+	}
161
+
162
+	/**
163
+	 * Returns the POST data.
164
+	 *
165
+	 * This is equivalent to PHP's $_POST superglobal.
166
+	 *
167
+	 * @return array
168
+	 */
169
+	public function getPostData() {
170
+
171
+		return $this->inner->getPostData();
172
+
173
+	}
174
+
175
+	/**
176
+	 * Sets the post data.
177
+	 *
178
+	 * This is equivalent to PHP's $_POST superglobal.
179
+	 *
180
+	 * This would not have been needed, if POST data was accessible as
181
+	 * php://input, but unfortunately we need to special case it.
182
+	 *
183
+	 * @param array $postData
184
+	 * @return void
185
+	 */
186
+	public function setPostData(array $postData) {
187
+
188
+		$this->inner->setPostData($postData);
189
+
190
+	}
191
+
192
+
193
+	/**
194
+	 * Returns an item from the _SERVER array.
195
+	 *
196
+	 * If the value does not exist in the array, null is returned.
197
+	 *
198
+	 * @param string $valueName
199
+	 * @return string|null
200
+	 */
201
+	public function getRawServerValue($valueName) {
202
+
203
+		return $this->inner->getRawServerValue($valueName);
204
+
205
+	}
206
+
207
+	/**
208
+	 * Sets the _SERVER array.
209
+	 *
210
+	 * @param array $data
211
+	 * @return void
212
+	 */
213
+	public function setRawServerData(array $data) {
214
+
215
+		$this->inner->setRawServerData($data);
216
+
217
+	}
218
+
219
+	/**
220
+	 * Serializes the request object as a string.
221
+	 *
222
+	 * This is useful for debugging purposes.
223
+	 *
224
+	 * @return string
225
+	 */
226
+	public function __toString() {
227
+
228
+		return $this->inner->__toString();
229
+
230
+	}
231 231
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/Response.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -11,184 +11,184 @@
 block discarded – undo
11 11
  */
12 12
 class Response extends Message implements ResponseInterface {
13 13
 
14
-    /**
15
-     * This is the list of currently registered HTTP status codes.
16
-     *
17
-     * @var array
18
-     */
19
-    static $statusCodes = [
20
-        100 => 'Continue',
21
-        101 => 'Switching Protocols',
22
-        102 => 'Processing',
23
-        200 => 'OK',
24
-        201 => 'Created',
25
-        202 => 'Accepted',
26
-        203 => 'Non-Authorative Information',
27
-        204 => 'No Content',
28
-        205 => 'Reset Content',
29
-        206 => 'Partial Content',
30
-        207 => 'Multi-Status', // RFC 4918
31
-        208 => 'Already Reported', // RFC 5842
32
-        226 => 'IM Used', // RFC 3229
33
-        300 => 'Multiple Choices',
34
-        301 => 'Moved Permanently',
35
-        302 => 'Found',
36
-        303 => 'See Other',
37
-        304 => 'Not Modified',
38
-        305 => 'Use Proxy',
39
-        307 => 'Temporary Redirect',
40
-        308 => 'Permanent Redirect',
41
-        400 => 'Bad Request',
42
-        401 => 'Unauthorized',
43
-        402 => 'Payment Required',
44
-        403 => 'Forbidden',
45
-        404 => 'Not Found',
46
-        405 => 'Method Not Allowed',
47
-        406 => 'Not Acceptable',
48
-        407 => 'Proxy Authentication Required',
49
-        408 => 'Request Timeout',
50
-        409 => 'Conflict',
51
-        410 => 'Gone',
52
-        411 => 'Length Required',
53
-        412 => 'Precondition failed',
54
-        413 => 'Request Entity Too Large',
55
-        414 => 'Request-URI Too Long',
56
-        415 => 'Unsupported Media Type',
57
-        416 => 'Requested Range Not Satisfiable',
58
-        417 => 'Expectation Failed',
59
-        418 => 'I\'m a teapot', // RFC 2324
60
-        421 => 'Misdirected Request', // RFC7540 (HTTP/2)
61
-        422 => 'Unprocessable Entity', // RFC 4918
62
-        423 => 'Locked', // RFC 4918
63
-        424 => 'Failed Dependency', // RFC 4918
64
-        426 => 'Upgrade Required',
65
-        428 => 'Precondition Required', // RFC 6585
66
-        429 => 'Too Many Requests', // RFC 6585
67
-        431 => 'Request Header Fields Too Large', // RFC 6585
68
-        451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-status
69
-        500 => 'Internal Server Error',
70
-        501 => 'Not Implemented',
71
-        502 => 'Bad Gateway',
72
-        503 => 'Service Unavailable',
73
-        504 => 'Gateway Timeout',
74
-        505 => 'HTTP Version not supported',
75
-        506 => 'Variant Also Negotiates',
76
-        507 => 'Insufficient Storage', // RFC 4918
77
-        508 => 'Loop Detected', // RFC 5842
78
-        509 => 'Bandwidth Limit Exceeded', // non-standard
79
-        510 => 'Not extended',
80
-        511 => 'Network Authentication Required', // RFC 6585
81
-    ];
82
-
83
-    /**
84
-     * HTTP status code
85
-     *
86
-     * @var int
87
-     */
88
-    protected $status;
89
-
90
-    /**
91
-     * HTTP status text
92
-     *
93
-     * @var string
94
-     */
95
-    protected $statusText;
96
-
97
-    /**
98
-     * Creates the response object
99
-     *
100
-     * @param string|int $status
101
-     * @param array $headers
102
-     * @param resource $body
103
-     * @return void
104
-     */
105
-    public function __construct($status = null, array $headers = null, $body = null) {
106
-
107
-        if (!is_null($status)) $this->setStatus($status);
108
-        if (!is_null($headers)) $this->setHeaders($headers);
109
-        if (!is_null($body)) $this->setBody($body);
110
-
111
-    }
112
-
113
-
114
-    /**
115
-     * Returns the current HTTP status code.
116
-     *
117
-     * @return int
118
-     */
119
-    public function getStatus() {
120
-
121
-        return $this->status;
122
-
123
-    }
124
-
125
-    /**
126
-     * Returns the human-readable status string.
127
-     *
128
-     * In the case of a 200, this may for example be 'OK'.
129
-     *
130
-     * @return string
131
-     */
132
-    public function getStatusText() {
133
-
134
-        return $this->statusText;
135
-
136
-    }
137
-
138
-    /**
139
-     * Sets the HTTP status code.
140
-     *
141
-     * This can be either the full HTTP status code with human readable string,
142
-     * for example: "403 I can't let you do that, Dave".
143
-     *
144
-     * Or just the code, in which case the appropriate default message will be
145
-     * added.
146
-     *
147
-     * @param string|int $status
148
-     * @throws \InvalidArgumentExeption
149
-     * @return void
150
-     */
151
-    public function setStatus($status) {
152
-
153
-        if (ctype_digit($status) || is_int($status)) {
154
-
155
-            $statusCode = $status;
156
-            $statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown';
157
-
158
-        } else {
159
-            list(
160
-                $statusCode,
161
-                $statusText
162
-            ) = explode(' ', $status, 2);
163
-        }
164
-        if ($statusCode < 100 || $statusCode > 999) {
165
-            throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits');
166
-        }
167
-
168
-        $this->status = $statusCode;
169
-        $this->statusText = $statusText;
170
-
171
-    }
172
-
173
-    /**
174
-     * Serializes the response object as a string.
175
-     *
176
-     * This is useful for debugging purposes.
177
-     *
178
-     * @return string
179
-     */
180
-    public function __toString() {
181
-
182
-        $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n";
183
-        foreach ($this->getHeaders() as $key => $value) {
184
-            foreach ($value as $v) {
185
-                $str .= $key . ": " . $v . "\r\n";
186
-            }
187
-        }
188
-        $str .= "\r\n";
189
-        $str .= $this->getBodyAsString();
190
-        return $str;
191
-
192
-    }
14
+	/**
15
+	 * This is the list of currently registered HTTP status codes.
16
+	 *
17
+	 * @var array
18
+	 */
19
+	static $statusCodes = [
20
+		100 => 'Continue',
21
+		101 => 'Switching Protocols',
22
+		102 => 'Processing',
23
+		200 => 'OK',
24
+		201 => 'Created',
25
+		202 => 'Accepted',
26
+		203 => 'Non-Authorative Information',
27
+		204 => 'No Content',
28
+		205 => 'Reset Content',
29
+		206 => 'Partial Content',
30
+		207 => 'Multi-Status', // RFC 4918
31
+		208 => 'Already Reported', // RFC 5842
32
+		226 => 'IM Used', // RFC 3229
33
+		300 => 'Multiple Choices',
34
+		301 => 'Moved Permanently',
35
+		302 => 'Found',
36
+		303 => 'See Other',
37
+		304 => 'Not Modified',
38
+		305 => 'Use Proxy',
39
+		307 => 'Temporary Redirect',
40
+		308 => 'Permanent Redirect',
41
+		400 => 'Bad Request',
42
+		401 => 'Unauthorized',
43
+		402 => 'Payment Required',
44
+		403 => 'Forbidden',
45
+		404 => 'Not Found',
46
+		405 => 'Method Not Allowed',
47
+		406 => 'Not Acceptable',
48
+		407 => 'Proxy Authentication Required',
49
+		408 => 'Request Timeout',
50
+		409 => 'Conflict',
51
+		410 => 'Gone',
52
+		411 => 'Length Required',
53
+		412 => 'Precondition failed',
54
+		413 => 'Request Entity Too Large',
55
+		414 => 'Request-URI Too Long',
56
+		415 => 'Unsupported Media Type',
57
+		416 => 'Requested Range Not Satisfiable',
58
+		417 => 'Expectation Failed',
59
+		418 => 'I\'m a teapot', // RFC 2324
60
+		421 => 'Misdirected Request', // RFC7540 (HTTP/2)
61
+		422 => 'Unprocessable Entity', // RFC 4918
62
+		423 => 'Locked', // RFC 4918
63
+		424 => 'Failed Dependency', // RFC 4918
64
+		426 => 'Upgrade Required',
65
+		428 => 'Precondition Required', // RFC 6585
66
+		429 => 'Too Many Requests', // RFC 6585
67
+		431 => 'Request Header Fields Too Large', // RFC 6585
68
+		451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-status
69
+		500 => 'Internal Server Error',
70
+		501 => 'Not Implemented',
71
+		502 => 'Bad Gateway',
72
+		503 => 'Service Unavailable',
73
+		504 => 'Gateway Timeout',
74
+		505 => 'HTTP Version not supported',
75
+		506 => 'Variant Also Negotiates',
76
+		507 => 'Insufficient Storage', // RFC 4918
77
+		508 => 'Loop Detected', // RFC 5842
78
+		509 => 'Bandwidth Limit Exceeded', // non-standard
79
+		510 => 'Not extended',
80
+		511 => 'Network Authentication Required', // RFC 6585
81
+	];
82
+
83
+	/**
84
+	 * HTTP status code
85
+	 *
86
+	 * @var int
87
+	 */
88
+	protected $status;
89
+
90
+	/**
91
+	 * HTTP status text
92
+	 *
93
+	 * @var string
94
+	 */
95
+	protected $statusText;
96
+
97
+	/**
98
+	 * Creates the response object
99
+	 *
100
+	 * @param string|int $status
101
+	 * @param array $headers
102
+	 * @param resource $body
103
+	 * @return void
104
+	 */
105
+	public function __construct($status = null, array $headers = null, $body = null) {
106
+
107
+		if (!is_null($status)) $this->setStatus($status);
108
+		if (!is_null($headers)) $this->setHeaders($headers);
109
+		if (!is_null($body)) $this->setBody($body);
110
+
111
+	}
112
+
113
+
114
+	/**
115
+	 * Returns the current HTTP status code.
116
+	 *
117
+	 * @return int
118
+	 */
119
+	public function getStatus() {
120
+
121
+		return $this->status;
122
+
123
+	}
124
+
125
+	/**
126
+	 * Returns the human-readable status string.
127
+	 *
128
+	 * In the case of a 200, this may for example be 'OK'.
129
+	 *
130
+	 * @return string
131
+	 */
132
+	public function getStatusText() {
133
+
134
+		return $this->statusText;
135
+
136
+	}
137
+
138
+	/**
139
+	 * Sets the HTTP status code.
140
+	 *
141
+	 * This can be either the full HTTP status code with human readable string,
142
+	 * for example: "403 I can't let you do that, Dave".
143
+	 *
144
+	 * Or just the code, in which case the appropriate default message will be
145
+	 * added.
146
+	 *
147
+	 * @param string|int $status
148
+	 * @throws \InvalidArgumentExeption
149
+	 * @return void
150
+	 */
151
+	public function setStatus($status) {
152
+
153
+		if (ctype_digit($status) || is_int($status)) {
154
+
155
+			$statusCode = $status;
156
+			$statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown';
157
+
158
+		} else {
159
+			list(
160
+				$statusCode,
161
+				$statusText
162
+			) = explode(' ', $status, 2);
163
+		}
164
+		if ($statusCode < 100 || $statusCode > 999) {
165
+			throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits');
166
+		}
167
+
168
+		$this->status = $statusCode;
169
+		$this->statusText = $statusText;
170
+
171
+	}
172
+
173
+	/**
174
+	 * Serializes the response object as a string.
175
+	 *
176
+	 * This is useful for debugging purposes.
177
+	 *
178
+	 * @return string
179
+	 */
180
+	public function __toString() {
181
+
182
+		$str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n";
183
+		foreach ($this->getHeaders() as $key => $value) {
184
+			foreach ($value as $v) {
185
+				$str .= $key . ": " . $v . "\r\n";
186
+			}
187
+		}
188
+		$str .= "\r\n";
189
+		$str .= $this->getBodyAsString();
190
+		return $str;
191
+
192
+	}
193 193
 
194 194
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/ClientHttpException.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -14,45 +14,45 @@
 block discarded – undo
14 14
  */
15 15
 class ClientHttpException extends \Exception implements HttpException {
16 16
 
17
-    /**
18
-     * Response object
19
-     *
20
-     * @var ResponseInterface
21
-     */
22
-    protected $response;
23
-
24
-    /**
25
-     * Constructor
26
-     *
27
-     * @param ResponseInterface $response
28
-     */
29
-    public function __construct(ResponseInterface $response) {
30
-
31
-        $this->response = $response;
32
-        parent::__construct($response->getStatusText(), $response->getStatus());
33
-
34
-    }
35
-
36
-    /**
37
-     * The http status code for the error.
38
-     *
39
-     * @return int
40
-     */
41
-    public function getHttpStatus() {
42
-
43
-        return $this->response->getStatus();
44
-
45
-    }
46
-
47
-    /**
48
-     * Returns the full response object.
49
-     *
50
-     * @return ResponseInterface
51
-     */
52
-    public function getResponse() {
53
-
54
-        return $this->response;
55
-
56
-    }
17
+	/**
18
+	 * Response object
19
+	 *
20
+	 * @var ResponseInterface
21
+	 */
22
+	protected $response;
23
+
24
+	/**
25
+	 * Constructor
26
+	 *
27
+	 * @param ResponseInterface $response
28
+	 */
29
+	public function __construct(ResponseInterface $response) {
30
+
31
+		$this->response = $response;
32
+		parent::__construct($response->getStatusText(), $response->getStatus());
33
+
34
+	}
35
+
36
+	/**
37
+	 * The http status code for the error.
38
+	 *
39
+	 * @return int
40
+	 */
41
+	public function getHttpStatus() {
42
+
43
+		return $this->response->getStatus();
44
+
45
+	}
46
+
47
+	/**
48
+	 * Returns the full response object.
49
+	 *
50
+	 * @return ResponseInterface
51
+	 */
52
+	public function getResponse() {
53
+
54
+		return $this->response;
55
+
56
+	}
57 57
 
58 58
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/ResponseDecorator.php 1 patch
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -14,71 +14,71 @@
 block discarded – undo
14 14
  */
15 15
 class ResponseDecorator implements ResponseInterface {
16 16
 
17
-    use MessageDecoratorTrait;
18
-
19
-    /**
20
-     * Constructor.
21
-     *
22
-     * @param ResponseInterface $inner
23
-     */
24
-    public function __construct(ResponseInterface $inner) {
25
-
26
-        $this->inner = $inner;
27
-
28
-    }
29
-
30
-    /**
31
-     * Returns the current HTTP status code.
32
-     *
33
-     * @return int
34
-     */
35
-    public function getStatus() {
36
-
37
-        return $this->inner->getStatus();
38
-
39
-    }
40
-
41
-
42
-    /**
43
-     * Returns the human-readable status string.
44
-     *
45
-     * In the case of a 200, this may for example be 'OK'.
46
-     *
47
-     * @return string
48
-     */
49
-    public function getStatusText() {
50
-
51
-        return $this->inner->getStatusText();
52
-
53
-    }
54
-    /**
55
-     * Sets the HTTP status code.
56
-     *
57
-     * This can be either the full HTTP status code with human readable string,
58
-     * for example: "403 I can't let you do that, Dave".
59
-     *
60
-     * Or just the code, in which case the appropriate default message will be
61
-     * added.
62
-     *
63
-     * @param string|int $status
64
-     * @return void
65
-     */
66
-    public function setStatus($status) {
67
-
68
-        $this->inner->setStatus($status);
69
-
70
-    }
71
-
72
-    /**
73
-     * Serializes the request object as a string.
74
-     *
75
-     * This is useful for debugging purposes.
76
-     *
77
-     * @return string
78
-     */
79
-    public function __toString() {
80
-
81
-        return $this->inner->__toString();
82
-
83
-    }
17
+	use MessageDecoratorTrait;
18
+
19
+	/**
20
+	 * Constructor.
21
+	 *
22
+	 * @param ResponseInterface $inner
23
+	 */
24
+	public function __construct(ResponseInterface $inner) {
25
+
26
+		$this->inner = $inner;
27
+
28
+	}
29
+
30
+	/**
31
+	 * Returns the current HTTP status code.
32
+	 *
33
+	 * @return int
34
+	 */
35
+	public function getStatus() {
36
+
37
+		return $this->inner->getStatus();
38
+
39
+	}
40
+
41
+
42
+	/**
43
+	 * Returns the human-readable status string.
44
+	 *
45
+	 * In the case of a 200, this may for example be 'OK'.
46
+	 *
47
+	 * @return string
48
+	 */
49
+	public function getStatusText() {
50
+
51
+		return $this->inner->getStatusText();
52
+
53
+	}
54
+	/**
55
+	 * Sets the HTTP status code.
56
+	 *
57
+	 * This can be either the full HTTP status code with human readable string,
58
+	 * for example: "403 I can't let you do that, Dave".
59
+	 *
60
+	 * Or just the code, in which case the appropriate default message will be
61
+	 * added.
62
+	 *
63
+	 * @param string|int $status
64
+	 * @return void
65
+	 */
66
+	public function setStatus($status) {
67
+
68
+		$this->inner->setStatus($status);
69
+
70
+	}
71
+
72
+	/**
73
+	 * Serializes the request object as a string.
74
+	 *
75
+	 * This is useful for debugging purposes.
76
+	 *
77
+	 * @return string
78
+	 */
79
+	public function __toString() {
80
+
81
+		return $this->inner->__toString();
82
+
83
+	}
84 84
 }
Please login to merge, or discard this patch.
libraries/SabreDAV/HTTP/HttpException.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -17,14 +17,14 @@
 block discarded – undo
17 17
  */
18 18
 interface HttpException {
19 19
 
20
-    /**
21
-     * The http status code for the error.
22
-     *
23
-     * This may either be just the number, or a number and a human-readable
24
-     * message, separated by a space.
25
-     *
26
-     * @return string|null
27
-     */
28
-    public function getHttpStatus();
20
+	/**
21
+	 * The http status code for the error.
22
+	 *
23
+	 * This may either be just the number, or a number and a human-readable
24
+	 * message, separated by a space.
25
+	 *
26
+	 * @return string|null
27
+	 */
28
+	public function getHttpStatus();
29 29
 
30 30
 }
Please login to merge, or discard this patch.