1<?php 2 3namespace Sabre\VObject\Property\ICalendar; 4 5use Sabre\VObject\Property; 6use Sabre\Xml; 7 8/** 9 * Recur property. 10 * 11 * This object represents RECUR properties. 12 * These values are just used for RRULE and the now deprecated EXRULE. 13 * 14 * The RRULE property may look something like this: 15 * 16 * RRULE:FREQ=MONTHLY;BYDAY=1,2,3;BYHOUR=5. 17 * 18 * This property exposes this as a key=>value array that is accessible using 19 * getParts, and may be set using setParts. 20 * 21 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 22 * @author Evert Pot (http://evertpot.com/) 23 * @license http://sabre.io/license/ Modified BSD License 24 */ 25class Recur extends Property { 26 27 /** 28 * Updates the current value. 29 * 30 * This may be either a single, or multiple strings in an array. 31 * 32 * @param string|array $value 33 * 34 * @return void 35 */ 36 function setValue($value) { 37 38 // If we're getting the data from json, we'll be receiving an object 39 if ($value instanceof \StdClass) { 40 $value = (array)$value; 41 } 42 43 if (is_array($value)) { 44 $newVal = []; 45 foreach ($value as $k => $v) { 46 47 if (is_string($v)) { 48 $v = strtoupper($v); 49 50 // The value had multiple sub-values 51 if (strpos($v, ',') !== false) { 52 $v = explode(',', $v); 53 } 54 if (strcmp($k, 'until') === 0) { 55 $v = strtr($v, [':' => '', '-' => '']); 56 } 57 } elseif (is_array($v)) { 58 $v = array_map('strtoupper', $v); 59 } 60 61 $newVal[strtoupper($k)] = $v; 62 } 63 $this->value = $newVal; 64 } elseif (is_string($value)) { 65 $this->value = self::stringToArray($value); 66 } else { 67 throw new \InvalidArgumentException('You must either pass a string, or a key=>value array'); 68 } 69 70 } 71 72 /** 73 * Returns the current value. 74 * 75 * This method will always return a singular value. If this was a 76 * multi-value object, some decision will be made first on how to represent 77 * it as a string. 78 * 79 * To get the correct multi-value version, use getParts. 80 * 81 * @return string 82 */ 83 function getValue() { 84 85 $out = []; 86 foreach ($this->value as $key => $value) { 87 $out[] = $key . '=' . (is_array($value) ? implode(',', $value) : $value); 88 } 89 return strtoupper(implode(';', $out)); 90 91 } 92 93 /** 94 * Sets a multi-valued property. 95 * 96 * @param array $parts 97 * @return void 98 */ 99 function setParts(array $parts) { 100 101 $this->setValue($parts); 102 103 } 104 105 /** 106 * Returns a multi-valued property. 107 * 108 * This method always returns an array, if there was only a single value, 109 * it will still be wrapped in an array. 110 * 111 * @return array 112 */ 113 function getParts() { 114 115 return $this->value; 116 117 } 118 119 /** 120 * Sets a raw value coming from a mimedir (iCalendar/vCard) file. 121 * 122 * This has been 'unfolded', so only 1 line will be passed. Unescaping is 123 * not yet done, but parameters are not included. 124 * 125 * @param string $val 126 * 127 * @return void 128 */ 129 function setRawMimeDirValue($val) { 130 131 $this->setValue($val); 132 133 } 134 135 /** 136 * Returns a raw mime-dir representation of the value. 137 * 138 * @return string 139 */ 140 function getRawMimeDirValue() { 141 142 return $this->getValue(); 143 144 } 145 146 /** 147 * Returns the type of value. 148 * 149 * This corresponds to the VALUE= parameter. Every property also has a 150 * 'default' valueType. 151 * 152 * @return string 153 */ 154 function getValueType() { 155 156 return 'RECUR'; 157 158 } 159 160 /** 161 * Returns the value, in the format it should be encoded for json. 162 * 163 * This method must always return an array. 164 * 165 * @return array 166 */ 167 function getJsonValue() { 168 169 $values = []; 170 foreach ($this->getParts() as $k => $v) { 171 if (strcmp($k, 'UNTIL') === 0) { 172 $date = new DateTime($this->root, null, $v); 173 $values[strtolower($k)] = $date->getJsonValue()[0]; 174 } elseif (strcmp($k, 'COUNT') === 0) { 175 $values[strtolower($k)] = intval($v); 176 } else { 177 $values[strtolower($k)] = $v; 178 } 179 } 180 return [$values]; 181 182 } 183 184 /** 185 * This method serializes only the value of a property. This is used to 186 * create xCard or xCal documents. 187 * 188 * @param Xml\Writer $writer XML writer. 189 * 190 * @return void 191 */ 192 protected function xmlSerializeValue(Xml\Writer $writer) { 193 194 $valueType = strtolower($this->getValueType()); 195 196 foreach ($this->getJsonValue() as $value) { 197 $writer->writeElement($valueType, $value); 198 } 199 200 } 201 202 /** 203 * Parses an RRULE value string, and turns it into a struct-ish array. 204 * 205 * @param string $value 206 * 207 * @return array 208 */ 209 static function stringToArray($value) { 210 211 $value = strtoupper($value); 212 $newValue = []; 213 foreach (explode(';', $value) as $part) { 214 215 // Skipping empty parts. 216 if (empty($part)) { 217 continue; 218 } 219 list($partName, $partValue) = explode('=', $part); 220 221 // The value itself had multiple values.. 222 if (strpos($partValue, ',') !== false) { 223 $partValue = explode(',', $partValue); 224 } 225 $newValue[$partName] = $partValue; 226 227 } 228 229 return $newValue; 230 } 231 232 /** 233 * Validates the node for correctness. 234 * 235 * The following options are supported: 236 * Node::REPAIR - May attempt to automatically repair the problem. 237 * 238 * This method returns an array with detected problems. 239 * Every element has the following properties: 240 * 241 * * level - problem level. 242 * * message - A human-readable string describing the issue. 243 * * node - A reference to the problematic node. 244 * 245 * The level means: 246 * 1 - The issue was repaired (only happens if REPAIR was turned on) 247 * 2 - An inconsequential issue 248 * 3 - A severe issue. 249 * 250 * @param int $options 251 * 252 * @return array 253 */ 254 function validate($options = 0) { 255 256 $repair = ($options & self::REPAIR); 257 258 $warnings = parent::validate($options); 259 $values = $this->getParts(); 260 261 foreach ($values as $key => $value) { 262 263 if ($value === '') { 264 $warnings[] = [ 265 'level' => $repair ? 1 : 3, 266 'message' => 'Invalid value for ' . $key . ' in ' . $this->name, 267 'node' => $this 268 ]; 269 if ($repair) { 270 unset($values[$key]); 271 } 272 } elseif ($key == 'BYMONTH') { 273 $byMonth = (array)$value; 274 foreach ($byMonth as $i => $v) { 275 if (!is_numeric($v) || (int)$v < 1 || (int)$v > 12) { 276 $warnings[] = [ 277 'level' => $repair ? 1 : 3, 278 'message' => 'BYMONTH in RRULE must have value(s) between 1 and 12!', 279 'node' => $this 280 ]; 281 if ($repair) { 282 if (is_array($value)) { 283 unset($values[$key][$i]); 284 } else { 285 unset($values[$key]); 286 } 287 } 288 } 289 } 290 // if there is no valid entry left, remove the whole value 291 if (is_array($value) && empty($values[$key])) { 292 unset($values[$key]); 293 } 294 } elseif ($key == 'BYWEEKNO') { 295 $byWeekNo = (array)$value; 296 foreach ($byWeekNo as $i => $v) { 297 if (!is_numeric($v) || (int)$v < -53 || (int)$v == 0 || (int)$v > 53) { 298 $warnings[] = [ 299 'level' => $repair ? 1 : 3, 300 'message' => 'BYWEEKNO in RRULE must have value(s) from -53 to -1, or 1 to 53!', 301 'node' => $this 302 ]; 303 if ($repair) { 304 if (is_array($value)) { 305 unset($values[$key][$i]); 306 } else { 307 unset($values[$key]); 308 } 309 } 310 } 311 } 312 // if there is no valid entry left, remove the whole value 313 if (is_array($value) && empty($values[$key])) { 314 unset($values[$key]); 315 } 316 } elseif ($key == 'BYYEARDAY') { 317 $byYearDay = (array)$value; 318 foreach ($byYearDay as $i => $v) { 319 if (!is_numeric($v) || (int)$v < -366 || (int)$v == 0 || (int)$v > 366) { 320 $warnings[] = [ 321 'level' => $repair ? 1 : 3, 322 'message' => 'BYYEARDAY in RRULE must have value(s) from -366 to -1, or 1 to 366!', 323 'node' => $this 324 ]; 325 if ($repair) { 326 if (is_array($value)) { 327 unset($values[$key][$i]); 328 } else { 329 unset($values[$key]); 330 } 331 } 332 } 333 } 334 // if there is no valid entry left, remove the whole value 335 if (is_array($value) && empty($values[$key])) { 336 unset($values[$key]); 337 } 338 } 339 340 } 341 if (!isset($values['FREQ'])) { 342 $warnings[] = [ 343 'level' => $repair ? 1 : 3, 344 'message' => 'FREQ is required in ' . $this->name, 345 'node' => $this 346 ]; 347 if ($repair) { 348 $this->parent->remove($this); 349 } 350 } 351 if ($repair) { 352 $this->setValue($values); 353 } 354 355 return $warnings; 356 357 } 358 359} 360