[ Index ] |
PHP Cross Reference of vtigercrm-6.1.0 |
[Summary view] [Print] [Text view]
1 <?php // $Id: iCalendar_rfc2445.php,v 1.7 2005/07/21 23:23:48 defacer Exp $ 2 3 /* 4 5 All names of properties, property parameters, enumerated property 6 values and property parameter values are case-insensitive. However, 7 all other property values are case-sensitive, unless otherwise 8 stated. 9 10 */ 11 12 define('RFC2445_CRLF', "\r\n"); 13 define('RFC2445_WSP', "\t "); 14 define('RFC2445_WEEKDAYS', 'MO,TU,WE,TH,FR,SA,SU'); 15 define('RFC2445_FOLDED_LINE_LENGTH', 75); 16 17 define('RFC2445_REQUIRED', 0x01); 18 define('RFC2445_OPTIONAL', 0x02); 19 define('RFC2445_ONCE', 0x04); 20 21 define('RFC2445_PROP_FLAGS', 0); 22 define('RFC2445_PROP_TYPE', 1); 23 define('RFC2445_PROP_DEFAULT', 2); 24 25 define('RFC2445_XNAME', 'X-'); 26 27 define('RFC2445_TYPE_BINARY', 0); 28 define('RFC2445_TYPE_BOOLEAN', 1); 29 define('RFC2445_TYPE_CAL_ADDRESS', 2); 30 define('RFC2445_TYPE_DATE', 3); 31 define('RFC2445_TYPE_DATE_TIME', 4); 32 define('RFC2445_TYPE_DURATION', 5); 33 define('RFC2445_TYPE_FLOAT', 6); 34 define('RFC2445_TYPE_INTEGER', 7); 35 define('RFC2445_TYPE_PERIOD', 8); 36 define('RFC2445_TYPE_RECUR', 9); 37 define('RFC2445_TYPE_TEXT', 10); 38 define('RFC2445_TYPE_TIME', 11); 39 define('RFC2445_TYPE_URI', 12); // CAL_ADDRESS === URI 40 define('RFC2445_TYPE_UTC_OFFSET', 13); 41 42 43 function rfc2445_fold($string) { 44 if(strlen($string) <= RFC2445_FOLDED_LINE_LENGTH) { 45 return $string; 46 } 47 48 $retval = ''; 49 50 while(strlen($string) > RFC2445_FOLDED_LINE_LENGTH) { 51 $retval .= substr($string, 0, RFC2445_FOLDED_LINE_LENGTH - 1) . RFC2445_CRLF . ' '; 52 $string = substr($string, RFC2445_FOLDED_LINE_LENGTH - 1); 53 } 54 55 $retval .= $string; 56 57 return $retval; 58 59 } 60 61 function rfc2445_unfold($string) { 62 for($i = 0; $i < strlen(RFC2445_WSP); ++$i) { 63 $string = str_replace(RFC2445_CRLF.substr(RFC2445_WSP, $i, 1), '', $string); 64 } 65 66 return $string; 67 } 68 69 function rfc2445_is_xname($name) { 70 71 // If it's less than 3 chars, it cannot be legal 72 if(strlen($name) < 3) { 73 return false; 74 } 75 76 // If it contains an illegal char anywhere, reject it 77 if(strspn($name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-') != strlen($name)) { 78 return false; 79 } 80 81 // To be legal, it must still start with "X-" 82 return substr($name, 0, 2) === 'X-'; 83 } 84 85 function rfc2445_is_valid_value($value, $type) { 86 87 // This branch should only be taken with xname values 88 if($type === NULL) { 89 return true; 90 } 91 92 switch($type) { 93 case RFC2445_TYPE_CAL_ADDRESS: 94 case RFC2445_TYPE_URI: 95 if(!is_string($value)) { 96 return false; 97 } 98 $valid_schemes = array('ftp', 'http', 'ldap', 'gopher', 'mailto', 'news', 'nntp', 'telnet', 'wais', 'file', 'prospero'); 99 100 $pos = strpos($value, ':'); 101 if(!$pos) { 102 return false; 103 } 104 105 $scheme = strtolower(substr($value, 0, $pos)); 106 $remain = substr($value, $pos + 1); 107 108 if(!in_array($scheme, $valid_schemes)) { 109 return false; 110 } 111 112 if($scheme === 'mailto') { 113 $regexp = '/^[a-zA-Z0-9]+[_a-zA-Z0-9\-]*(\.[_a-z0-9\-]+)*@(([0-9a-zA-Z\-]+\.)+[a-zA-Z][0-9a-zA-Z\-]+|([0-9]{1,3}\.){3}[0-9]{1,3})$/'; 114 } 115 else { 116 $regexp = '/^//(.+(:.*)?@)?(([0-9a-zA-Z\-]+\.)+[a-zA-Z][0-9a-zA-Z\-]+|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]{1,5})?(/.*)?$/'; 117 } 118 119 return preg_match($regexp, $remain); 120 break; 121 122 case RFC2445_TYPE_BINARY: 123 if(!is_string($value)) { 124 return false; 125 } 126 127 $len = strlen($value); 128 129 if($len % 4 != 0) { 130 return false; 131 } 132 133 for($i = 0; $i < $len; ++$i) { 134 $ch = $value{$i}; 135 if(!($ch >= 'a' && $ch <= 'z' || $ch >= 'A' && $ch <= 'Z' || $ch >= '0' && $ch <= '9' || $ch == '-' || $ch == '+')) { 136 if($ch == '=' && $len - $i <= 2) { 137 continue; 138 } 139 return false; 140 } 141 } 142 return true; 143 break; 144 145 case RFC2445_TYPE_BOOLEAN: 146 if(is_bool($value)) { 147 return true; 148 } 149 if(is_string($value)) { 150 $value = strtoupper($value); 151 return ($value == 'TRUE' || $value == 'FALSE'); 152 } 153 return false; 154 break; 155 156 case RFC2445_TYPE_DATE: 157 if(is_int($value)) { 158 if($value < 0) { 159 return false; 160 } 161 $value = "$value"; 162 } 163 else if(!is_string($value)) { 164 return false; 165 } 166 167 if(strlen($value) != 8) { 168 return false; 169 } 170 171 $y = intval(substr($value, 0, 4)); 172 $m = intval(substr($value, 4, 2)); 173 $d = intval(substr($value, 6, 2)); 174 175 return checkdate($m, $d, $y); 176 break; 177 178 case RFC2445_TYPE_DATE_TIME: 179 if(!is_string($value) || strlen($value) < 15) { 180 return false; 181 } 182 183 return($value{8} == 'T' && 184 rfc2445_is_valid_value(substr($value, 0, 8), RFC2445_TYPE_DATE) && 185 rfc2445_is_valid_value(substr($value, 9), RFC2445_TYPE_TIME)); 186 break; 187 188 case RFC2445_TYPE_DURATION: 189 if(!is_string($value)) { 190 return false; 191 } 192 193 $len = strlen($value); 194 195 if($len < 3) { 196 // Minimum conformant length: "P1W" 197 return false; 198 } 199 200 if($value{0} == '+' || $value{0} == '-') { 201 $value = substr($value, 1); 202 --$len; // Don't forget to update this! 203 } 204 205 if($value{0} != 'P') { 206 return false; 207 } 208 209 // OK, now break it up 210 $num = ''; 211 $allowed = 'WDT'; 212 213 for($i = 1; $i < $len; ++$i) { 214 $ch = $value{$i}; 215 if($ch >= '0' && $ch <= '9') { 216 $num .= $ch; 217 continue; 218 } 219 if(strpos($allowed, $ch) === false) { 220 // Non-numeric character which shouldn't be here 221 return false; 222 } 223 if($num === '' && $ch != 'T') { 224 // Allowed non-numeric character, but no digits came before it 225 return false; 226 } 227 228 // OK, $ch now holds a character which tells us what $num is 229 switch($ch) { 230 case 'W': 231 // If duration in weeks is specified, this must end the string 232 return ($i == $len - 1); 233 break; 234 235 case 'D': 236 // Days specified, now if anything comes after it must be a 'T' 237 $allowed = 'T'; 238 break; 239 240 case 'T': 241 // Starting to specify time, H M S are now valid delimiters 242 $allowed = 'HMS'; 243 break; 244 245 case 'H': 246 $allowed = 'M'; 247 break; 248 249 case 'M': 250 $allowed = 'S'; 251 break; 252 253 case 'S': 254 return ($i == $len - 1); 255 break; 256 } 257 258 // If we 're going to continue, reset $num 259 $num = ''; 260 261 } 262 263 // $num is kept for this reason: if we 're here, we ran out of chars 264 // therefore $num must be empty for the period to be legal 265 return ($num === '' && $ch != 'T'); 266 267 break; 268 269 case RFC2445_TYPE_FLOAT: 270 if(is_float($value)) { 271 return true; 272 } 273 if(!is_string($value) || $value === '') { 274 return false; 275 } 276 277 $dot = false; 278 $int = false; 279 $len = strlen($value); 280 for($i = 0; $i < $len; ++$i) { 281 switch($value{$i}) { 282 case '-': case '+': 283 // A sign can only be seen at position 0 and cannot be the only char 284 if($i != 0 || $len == 1) { 285 return false; 286 } 287 break; 288 case '.': 289 // A second dot is an error 290 // Make sure we had at least one int before the dot 291 if($dot || !$int) { 292 return false; 293 } 294 $dot = true; 295 // Make also sure that the float doesn't end with a dot 296 if($i == $len - 1) { 297 return false; 298 } 299 break; 300 case '0': case '1': case '2': case '3': case '4': 301 case '5': case '6': case '7': case '8': case '9': 302 $int = true; 303 break; 304 default: 305 // Any other char is a no-no 306 return false; 307 break; 308 } 309 } 310 return true; 311 break; 312 313 case RFC2445_TYPE_INTEGER: 314 if(is_int($value)) { 315 return true; 316 } 317 if(!is_string($value) || $value === '') { 318 return false; 319 } 320 321 if($value{0} == '+' || $value{0} == '-') { 322 if(strlen($value) == 1) { 323 return false; 324 } 325 $value = substr($value, 1); 326 } 327 328 if(strspn($value, '0123456789') != strlen($value)) { 329 return false; 330 } 331 332 return ($value >= -2147483648 && $value <= 2147483647); 333 break; 334 335 case RFC2445_TYPE_PERIOD: 336 if(!is_string($value) || empty($value)) { 337 return false; 338 } 339 340 $parts = explode('/', $value); 341 if(count($parts) != 2) { 342 return false; 343 } 344 345 if(!rfc2445_is_valid_value($parts[0], RFC2445_TYPE_DATE_TIME)) { 346 return false; 347 } 348 349 // Two legal cases for the second part: 350 if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DATE_TIME)) { 351 // It has to be after the start time, so 352 return ($parts[1] > $parts[0]); 353 } 354 else if(rfc2445_is_valid_value($parts[1], RFC2445_TYPE_DURATION)) { 355 // The period MUST NOT be negative 356 return ($parts[1]{0} != '-'); 357 } 358 359 // It seems to be illegal 360 return false; 361 break; 362 363 case RFC2445_TYPE_RECUR: 364 if(!is_string($value)) { 365 return false; 366 } 367 368 $parts = explode(';', strtoupper($value)); 369 370 // First of all, we need at least a FREQ and a UNTIL or COUNT part, so... 371 if(count($parts) < 2) { 372 return false; 373 } 374 375 // Let's get that into a more easily comprehensible format 376 $vars = array(); 377 foreach($parts as $part) { 378 379 $pieces = explode('=', $part); 380 // There must be exactly 2 pieces, e.g. FREQ=WEEKLY 381 if(count($pieces) != 2) { 382 return false; 383 } 384 385 // It's illegal for a variable to appear twice 386 if(isset($vars[$pieces[0]])) { 387 return false; 388 } 389 390 // Sounds good 391 $vars[$pieces[0]] = $pieces[1]; 392 } 393 394 // OK... now to test everything else 395 396 // FREQ must be the first thing appearing 397 reset($vars); 398 if(key($vars) != 'FREQ') { 399 return false; 400 } 401 402 // It's illegal to have both UNTIL and COUNT appear 403 if(isset($vars['UNTIL']) && isset($vars['COUNT'])) { 404 return false; 405 } 406 407 // Special case: BYWEEKNO is only valid for FREQ=YEARLY 408 if(isset($vars['BYWEEKNO']) && $vars['FREQ'] != 'YEARLY') { 409 return false; 410 } 411 412 // Special case: BYSETPOS is only valid if another BY option is specified 413 if(isset($vars['BYSETPOS'])) { 414 $options = array('BYSECOND', 'BYMINUTE', 'BYHOUR', 'BYDAY', 'BYMONTHDAY', 'BYYEARDAY', 'BYWEEKNO', 'BYMONTH'); 415 $defined = array_keys($vars); 416 $common = array_intersect($options, $defined); 417 if(empty($common)) { 418 return false; 419 } 420 } 421 422 // OK, now simply check if each element has a valid value, 423 // unsetting them on the way. If at the end the array still 424 // has some elements, they are illegal. 425 426 if($vars['FREQ'] != 'SECONDLY' && $vars['FREQ'] != 'MINUTELY' && $vars['FREQ'] != 'HOURLY' && 427 $vars['FREQ'] != 'DAILY' && $vars['FREQ'] != 'WEEKLY' && 428 $vars['FREQ'] != 'MONTHLY' && $vars['FREQ'] != 'YEARLY') { 429 return false; 430 } 431 unset($vars['FREQ']); 432 433 // Set this, we may need it later 434 $weekdays = explode(',', RFC2445_WEEKDAYS); 435 436 if(isset($vars['UNTIL'])) { 437 if(rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) { 438 // The time MUST be in UTC format 439 if(!(substr($vars['UNTIL'], -1) == 'Z')) { 440 return false; 441 } 442 } 443 else if(!rfc2445_is_valid_value($vars['UNTIL'], RFC2445_TYPE_DATE_TIME)) { 444 return false; 445 } 446 } 447 unset($vars['UNTIL']); 448 449 450 if(isset($vars['COUNT'])) { 451 if(empty($vars['COUNT'])) { 452 // This also catches the string '0', which makes no sense 453 return false; 454 } 455 if(strspn($vars['COUNT'], '0123456789') != strlen($vars['COUNT'])) { 456 return false; 457 } 458 } 459 unset($vars['COUNT']); 460 461 462 if(isset($vars['INTERVAL'])) { 463 if(empty($vars['INTERVAL'])) { 464 // This also catches the string '0', which makes no sense 465 return false; 466 } 467 if(strspn($vars['INTERVAL'], '0123456789') != strlen($vars['INTERVAL'])) { 468 return false; 469 } 470 } 471 unset($vars['INTERVAL']); 472 473 474 if(isset($vars['BYSECOND'])) { 475 if($vars['BYSECOND'] == '') { 476 return false; 477 } 478 // Comma also allowed 479 if(strspn($vars['BYSECOND'], '0123456789,') != strlen($vars['BYSECOND'])) { 480 return false; 481 } 482 $secs = explode(',', $vars['BYSECOND']); 483 foreach($secs as $sec) { 484 if($sec == '' || $sec < 0 || $sec > 59) { 485 return false; 486 } 487 } 488 } 489 unset($vars['BYSECOND']); 490 491 492 if(isset($vars['BYMINUTE'])) { 493 if($vars['BYMINUTE'] == '') { 494 return false; 495 } 496 // Comma also allowed 497 if(strspn($vars['BYMINUTE'], '0123456789,') != strlen($vars['BYMINUTE'])) { 498 return false; 499 } 500 $mins = explode(',', $vars['BYMINUTE']); 501 foreach($mins as $min) { 502 if($min == '' || $min < 0 || $min > 59) { 503 return false; 504 } 505 } 506 } 507 unset($vars['BYMINUTE']); 508 509 510 if(isset($vars['BYHOUR'])) { 511 if($vars['BYHOUR'] == '') { 512 return false; 513 } 514 // Comma also allowed 515 if(strspn($vars['BYHOUR'], '0123456789,') != strlen($vars['BYHOUR'])) { 516 return false; 517 } 518 $hours = explode(',', $vars['BYHOUR']); 519 foreach($hours as $hour) { 520 if($hour == '' || $hour < 0 || $hour > 23) { 521 return false; 522 } 523 } 524 } 525 unset($vars['BYHOUR']); 526 527 528 if(isset($vars['BYDAY'])) { 529 if(empty($vars['BYDAY'])) { 530 return false; 531 } 532 533 // First off, split up all values we may have 534 $days = explode(',', $vars['BYDAY']); 535 536 foreach($days as $day) { 537 $daypart = substr($day, -2); 538 if(!in_array($daypart, $weekdays)) { 539 return false; 540 } 541 542 if(strlen($day) > 2) { 543 $intpart = substr($day, 0, strlen($day) - 2); 544 if(!rfc2445_is_valid_value($intpart, RFC2445_TYPE_INTEGER)) { 545 return false; 546 } 547 if(intval($intpart) == 0) { 548 return false; 549 } 550 } 551 } 552 } 553 unset($vars['BYDAY']); 554 555 556 if(isset($vars['BYMONTHDAY'])) { 557 if(empty($vars['BYMONTHDAY'])) { 558 return false; 559 } 560 $mdays = explode(',', $vars['BYMONTHDAY']); 561 foreach($mdays as $mday) { 562 if(!rfc2445_is_valid_value($mday, RFC2445_TYPE_INTEGER)) { 563 return false; 564 } 565 $mday = abs(intval($mday)); 566 if($mday == 0 || $mday > 31) { 567 return false; 568 } 569 } 570 } 571 unset($vars['BYMONTHDAY']); 572 573 574 if(isset($vars['BYYEARDAY'])) { 575 if(empty($vars['BYYEARDAY'])) { 576 return false; 577 } 578 $ydays = explode(',', $vars['BYYEARDAY']); 579 foreach($ydays as $yday) { 580 if(!rfc2445_is_valid_value($yday, RFC2445_TYPE_INTEGER)) { 581 return false; 582 } 583 $yday = abs(intval($yday)); 584 if($yday == 0 || $yday > 366) { 585 return false; 586 } 587 } 588 } 589 unset($vars['BYYEARDAY']); 590 591 592 if(isset($vars['BYWEEKNO'])) { 593 if(empty($vars['BYWEEKNO'])) { 594 return false; 595 } 596 $weeknos = explode(',', $vars['BYWEEKNO']); 597 foreach($weeknos as $weekno) { 598 if(!rfc2445_is_valid_value($weekno, RFC2445_TYPE_INTEGER)) { 599 return false; 600 } 601 $weekno = abs(intval($weekno)); 602 if($weekno == 0 || $weekno > 53) { 603 return false; 604 } 605 } 606 } 607 unset($vars['BYWEEKNO']); 608 609 610 if(isset($vars['BYMONTH'])) { 611 if(empty($vars['BYMONTH'])) { 612 return false; 613 } 614 // Comma also allowed 615 if(strspn($vars['BYMONTH'], '0123456789,') != strlen($vars['BYMONTH'])) { 616 return false; 617 } 618 $months = explode(',', $vars['BYMONTH']); 619 foreach($months as $month) { 620 if($month == '' || $month < 1 || $month > 12) { 621 return false; 622 } 623 } 624 } 625 unset($vars['BYMONTH']); 626 627 628 if(isset($vars['BYSETPOS'])) { 629 if(empty($vars['BYSETPOS'])) { 630 return false; 631 } 632 $sets = explode(',', $vars['BYSETPOS']); 633 foreach($sets as $set) { 634 if(!rfc2445_is_valid_value($set, RFC2445_TYPE_INTEGER)) { 635 return false; 636 } 637 $set = abs(intval($set)); 638 if($set == 0 || $set > 366) { 639 return false; 640 } 641 } 642 } 643 unset($vars['BYSETPOS']); 644 645 646 if(isset($vars['WKST'])) { 647 if(!in_array($vars['WKST'], $weekdays)) { 648 return false; 649 } 650 } 651 unset($vars['WKST']); 652 653 654 // Any remaining vars must be x-names 655 if(empty($vars)) { 656 return true; 657 } 658 659 foreach($vars as $name => $var) { 660 if(!rfc2445_is_xname($name)) { 661 return false; 662 } 663 } 664 665 // At last, all is OK! 666 return true; 667 668 break; 669 670 case RFC2445_TYPE_TEXT: 671 return true; 672 break; 673 674 case RFC2445_TYPE_TIME: 675 if(is_int($value)) { 676 if($value < 0) { 677 return false; 678 } 679 $value = "$value"; 680 } 681 else if(!is_string($value)) { 682 return false; 683 } 684 685 if(strlen($value) == 7) { 686 if(strtoupper(substr($value, -1)) != 'Z') { 687 return false; 688 } 689 $value = substr($value, 0, 6); 690 } 691 if(strlen($value) != 6) { 692 return false; 693 } 694 695 $h = intval(substr($value, 0, 2)); 696 $m = intval(substr($value, 2, 2)); 697 $s = intval(substr($value, 4, 2)); 698 699 return ($h <= 23 && $m <= 59 && $s <= 60); 700 break; 701 702 case RFC2445_TYPE_UTC_OFFSET: 703 if(is_int($value)) { 704 if($value >= 0) { 705 $value = "+$value"; 706 } 707 else { 708 $value = "$value"; 709 } 710 } 711 else if(!is_string($value)) { 712 return false; 713 } 714 715 if(strlen($value) == 7) { 716 $s = intval(substr($value, 5, 2)); 717 $value = substr($value, 0, 5); 718 } 719 if(strlen($value) != 5 || $value == "-0000") { 720 return false; 721 } 722 723 if($value{0} != '+' && $value{0} != '-') { 724 return false; 725 } 726 727 $h = intval(substr($value, 1, 2)); 728 $m = intval(substr($value, 3, 2)); 729 730 return ($h <= 23 && $m <= 59 && $s <= 59); 731 break; 732 } 733 734 // TODO: remove this assertion 735 trigger_error('bad code path', E_USER_WARNING); 736 var_dump($type); 737 return false; 738 } 739 740 function rfc2445_do_value_formatting($value, $type) { 741 // Note: this does not only do formatting; it also does conversion to string! 742 switch($type) { 743 case RFC2445_TYPE_CAL_ADDRESS: 744 case RFC2445_TYPE_URI: 745 // Enclose in double quotes 746 $value = '"'.$value.'"'; 747 break; 748 case RFC2445_TYPE_TEXT: 749 // Escape entities 750 $value = strtr($value, array("\n" => '\\n', '\\' => '\\\\', ',' => '\\,', ';' => '\\;')); 751 break; 752 } 753 return $value; 754 } 755 756 function rfc2445_undo_value_formatting($value, $type) { 757 switch($type) { 758 case RFC2445_TYPE_CAL_ADDRESS: 759 case RFC2445_TYPE_URI: 760 // Trim beginning and end double quote 761 $value = substr($value, 1, strlen($value) - 2); 762 break; 763 case RFC2445_TYPE_TEXT: 764 // Unescape entities 765 $value = strtr($value, array('\\n' => "\n", '\\N' => "\n", '\\\\' => '\\', '\\,' => ',', '\\;' => ';')); 766 break; 767 } 768 return $value; 769 } 770 771 // This is cheating: GUIDs have nothing to do with RFC 2445 772 773 function rfc2445_guid() { 774 // Implemented as per the Network Working Group draft on UUIDs and GUIDs 775 776 // These two octets get special treatment 777 $time_hi_and_version = sprintf('%02x', (1 << 6) + mt_rand(0, 15)); // 0100 plus 4 random bits 778 $clock_seq_hi_and_reserved = sprintf('%02x', (1 << 7) + mt_rand(0, 63)); // 10 plus 6 random bits 779 780 // Need another 14 random octects 781 $pool = ''; 782 for($i = 0; $i < 7; ++$i) { 783 $pool .= sprintf('%04x', mt_rand(0, 65535)); 784 } 785 786 // time_low = 4 octets 787 $random = substr($pool, 0, 8).'-'; 788 789 // time_mid = 2 octets 790 $random .= substr($pool, 8, 4).'-'; 791 792 // time_high_and_version = 2 octets 793 $random .= $time_hi_and_version.substr($pool, 12, 2).'-'; 794 795 // clock_seq_high_and_reserved = 1 octet 796 $random .= $clock_seq_hi_and_reserved; 797 798 // clock_seq_low = 1 octet 799 $random .= substr($pool, 13, 2).'-'; 800 801 // node = 6 octets 802 $random .= substr($pool, 14, 12); 803 804 return $random; 805 } 806 807 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:08:37 2014 | Cross-referenced by PHPXref 0.7.1 |