PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/common/libraries/plugin/pear/File/Contact_Vcard_Parse.php

https://bitbucket.org/chamilo/chamilo/
PHP | 841 lines | 267 code | 124 blank | 450 comment | 38 complexity | 2c1b4706a6cae196c2cdfc7912528abe MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT
  1. <?php
  2. /* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2002 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available at through the world-wide-web at |
  11. // | http://www.php.net/license/2_02.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Paul M. Jones <pmjones@php.net> |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Contact_Vcard_Parse.php 137 2009-11-09 13:24:37Z vanpouckesven $
  20. /**
  21. *
  22. * Parser for vCards.
  23. *
  24. * This class parses vCard 2.1 and 3.0 sources from file or text into a
  25. * structured array.
  26. *
  27. * Usage:
  28. *
  29. * <code>
  30. * // include this class file
  31. * require_once 'Contact_Vcard_Parse.php';
  32. *
  33. * // instantiate a parser object
  34. * $parse = new Contact_Vcard_Parse();
  35. *
  36. * // parse a vCard file and store the data
  37. * // in $cardinfo
  38. * $cardinfo = $parse->fromFile('sample.vcf');
  39. *
  40. * // view the card info array
  41. * echo '<pre>';
  42. * print_r($cardinfo);
  43. * echo '</pre>';
  44. * </code>
  45. *
  46. *
  47. * @author Paul M. Jones <pmjones@php.net>
  48. *
  49. * @package Contact_Vcard_Parse
  50. *
  51. * @version 1.31
  52. *
  53. */
  54. class Contact_Vcard_Parse {
  55. /**
  56. *
  57. * Reads a file for parsing, then sends it to $this->fromText()
  58. * and returns the results.
  59. *
  60. * @access public
  61. *
  62. * @param array $filename The filename to read for vCard information.
  63. *
  64. * @return array An array of of vCard information extracted from the
  65. * file.
  66. *
  67. * @see Contact_Vcard_Parse::fromText()
  68. *
  69. * @see Contact_Vcard_Parse::_fromArray()
  70. *
  71. */
  72. function fromFile($filename, $decode_qp = true)
  73. {
  74. $text = $this->fileGetContents($filename);
  75. if ($text === false) {
  76. return false;
  77. } else {
  78. // dump to, and get return from, the fromText() method.
  79. return $this->fromText($text, $decode_qp);
  80. }
  81. }
  82. /**
  83. *
  84. * Reads the contents of a file. Included for users whose PHP < 4.3.0.
  85. *
  86. * @access public
  87. *
  88. * @param array $filename The filename to read for vCard information.
  89. *
  90. * @return string|bool The contents of the file if it exists and is
  91. * readable, or boolean false if not.
  92. *
  93. * @see Contact_Vcard_Parse::fromFile()
  94. *
  95. */
  96. function fileGetContents($filename)
  97. {
  98. if (file_exists($filename) &&
  99. is_readable($filename)) {
  100. $text = '';
  101. $len = filesize($filename);
  102. $fp = fopen($filename, 'r');
  103. while ($line = fread($fp, filesize($filename))) {
  104. $text .= $line;
  105. }
  106. fclose($fp);
  107. return $text;
  108. } else {
  109. return false;
  110. }
  111. }
  112. /**
  113. *
  114. * Prepares a block of text for parsing, then sends it through and
  115. * returns the results from $this->fromArray().
  116. *
  117. * @access public
  118. *
  119. * @param array $text A block of text to read for vCard information.
  120. *
  121. * @return array An array of vCard information extracted from the
  122. * source text.
  123. *
  124. * @see Contact_Vcard_Parse::_fromArray()
  125. *
  126. */
  127. function fromText($text, $decode_qp = true)
  128. {
  129. // convert all kinds of line endings to Unix-standard and get
  130. // rid of double blank lines.
  131. $this->convertLineEndings($text);
  132. // unfold lines. concat two lines where line 1 ends in \n and
  133. // line 2 starts with a whitespace character. only removes
  134. // the first whitespace character, leaves others in place.
  135. $fold_regex = '(\n)([ |\t])';
  136. $text = preg_replace("/$fold_regex/i", "", $text);
  137. // massage for Macintosh OS X Address Book (remove nulls that
  138. // Address Book puts in for unicode chars)
  139. $text = str_replace("\x00", '', $text);
  140. // convert the resulting text to an array of lines
  141. $lines = explode("\n", $text);
  142. // parse the array of lines and return vCard info
  143. return $this->_fromArray($lines, $decode_qp);
  144. }
  145. /**
  146. *
  147. * Converts line endings in text.
  148. *
  149. * Takes any text block and converts all line endings to UNIX
  150. * standard. DOS line endings are \r\n, Mac are \r, and UNIX is \n.
  151. *
  152. * NOTE: Acts on the text block in-place; does not return a value.
  153. *
  154. * @access public
  155. *
  156. * @param string $text The string on which to convert line endings.
  157. *
  158. * @return void
  159. *
  160. */
  161. function convertLineEndings(&$text)
  162. {
  163. // DOS
  164. $text = str_replace("\r\n", "\n", $text);
  165. // Mac
  166. $text = str_replace("\r", "\n", $text);
  167. }
  168. /**
  169. *
  170. * Splits a string into an array at semicolons. Honors backslash-
  171. * escaped semicolons (i.e., splits at ';' not '\;').
  172. *
  173. * @access public
  174. *
  175. * @param string $text The string to split into an array.
  176. *
  177. * @param bool $convertSingle If splitting the string results in a
  178. * single array element, return a string instead of a one-element
  179. * array.
  180. *
  181. * @return mixed An array of values, or a single string.
  182. *
  183. */
  184. function splitBySemi($text, $convertSingle = false)
  185. {
  186. // we use these double-backs (\\) because they get get converted
  187. // to single-backs (\) by preg_split. the quad-backs (\\\\) end
  188. // up as as double-backs (\\), which is what preg_split requires
  189. // to indicate a single backslash (\). what a mess.
  190. $regex = '(?<!\\\\)(\;)';
  191. $tmp = preg_split("/$regex/i", $text);
  192. // if there is only one array-element and $convertSingle is
  193. // true, then return only the value of that one array element
  194. // (instead of returning the array).
  195. if ($convertSingle && count($tmp) == 1) {
  196. return $tmp[0];
  197. } else {
  198. return $tmp;
  199. }
  200. }
  201. /**
  202. *
  203. * Splits a string into an array at commas. Honors backslash-
  204. * escaped commas (i.e., splits at ',' not '\,').
  205. *
  206. * @access public
  207. *
  208. * @param string $text The string to split into an array.
  209. *
  210. * @param bool $convertSingle If splitting the string results in a
  211. * single array element, return a string instead of a one-element
  212. * array.
  213. *
  214. * @return mixed An array of values, or a single string.
  215. *
  216. */
  217. function splitByComma($text, $convertSingle = false)
  218. {
  219. // we use these double-backs (\\) because they get get converted
  220. // to single-backs (\) by preg_split. the quad-backs (\\\\) end
  221. // up as as double-backs (\\), which is what preg_split requires
  222. // to indicate a single backslash (\). ye gods, how ugly.
  223. $regex = '(?<!\\\\)(\,)';
  224. $tmp = preg_split("/$regex/i", $text);
  225. // if there is only one array-element and $convertSingle is
  226. // true, then return only the value of that one array element
  227. // (instead of returning the array).
  228. if ($convertSingle && count($tmp) == 1) {
  229. return $tmp[0];
  230. } else {
  231. return $tmp;
  232. }
  233. }
  234. /**
  235. *
  236. * Used to make string human-readable after being a vCard value.
  237. *
  238. * Converts...
  239. * \: => :
  240. * \; => ;
  241. * \, => ,
  242. * literal \n => newline
  243. *
  244. * @access public
  245. *
  246. * @param mixed $text The text to unescape.
  247. *
  248. * @return void
  249. *
  250. */
  251. function unescape(&$text)
  252. {
  253. if (is_array($text)) {
  254. foreach ($text as $key => $val) {
  255. $this->unescape($val);
  256. $text[$key] = $val;
  257. }
  258. } else {
  259. $text = str_replace('\:', ':', $text);
  260. $text = str_replace('\;', ';', $text);
  261. $text = str_replace('\,', ',', $text);
  262. $text = str_replace('\n', "\n", $text);
  263. }
  264. }
  265. /**
  266. *
  267. * Emulated destructor.
  268. *
  269. * @access private
  270. * @return boolean true
  271. *
  272. */
  273. function _Contact_Vcard_Parse()
  274. {
  275. return true;
  276. }
  277. /**
  278. *
  279. * Parses an array of source lines and returns an array of vCards.
  280. * Each element of the array is itself an array expressing the types,
  281. * parameters, and values of each part of the vCard. Processes both
  282. * 2.1 and 3.0 vCard sources.
  283. *
  284. * @access private
  285. *
  286. * @param array $source An array of lines to be read for vCard
  287. * information.
  288. *
  289. * @return array An array of of vCard information extracted from the
  290. * source array.
  291. *
  292. */
  293. function _fromArray($source, $decode_qp = true)
  294. {
  295. // the info array will hold all resulting vCard information.
  296. $info = array();
  297. // tells us whether the source text indicates the beginning of a
  298. // new vCard with a BEGIN:VCARD tag.
  299. $begin = false;
  300. // holds information about the current vCard being read from the
  301. // source text.
  302. $card = array();
  303. // loop through each line in the source array
  304. foreach ($source as $line) {
  305. // if the line is blank, skip it.
  306. if (trim($line) == '') {
  307. continue;
  308. }
  309. // find the first instance of ':' on the line. The part
  310. // to the left of the colon is the type and parameters;
  311. // the part to the right of the colon is the value data.
  312. $pos = strpos($line, ':');
  313. // if there is no colon, skip the line.
  314. if ($pos === false) {
  315. continue;
  316. }
  317. // get the left and right portions
  318. $left = trim(substr($line, 0, $pos));
  319. $right = trim(substr($line, $pos+1, strlen($line)));
  320. // have we started yet?
  321. if (! $begin) {
  322. // nope. does this line indicate the beginning of
  323. // a new vCard?
  324. if (strtoupper($left) == 'BEGIN' &&
  325. strtoupper($right) == 'VCARD') {
  326. // tell the loop that we've begun a new card
  327. $begin = true;
  328. }
  329. // regardless, loop to the next line of source. if begin
  330. // is still false, the next loop will check the line. if
  331. // begin has now been set to true, the loop will start
  332. // collecting card info.
  333. continue;
  334. } else {
  335. // yep, we've started, but we don't know how far along
  336. // we are in the card. is this the ending line of the
  337. // current vCard?
  338. if (strtoupper($left) == 'END' &&
  339. strtoupper($right) == 'VCARD') {
  340. // yep, we're done. keep the info from the current
  341. // card...
  342. $info[] = $card;
  343. // ...and reset to grab a new card if one exists in
  344. // the source array.
  345. $begin = false;
  346. $card = array();
  347. } else {
  348. // we're not on an ending line, so collect info from
  349. // this line into the current card. split the
  350. // left-portion of the line into a type-definition
  351. // (the kind of information) and parameters for the
  352. // type.
  353. $typedef = $this->_getTypeDef($left);
  354. $params = $this->_getParams($left);
  355. // if we are decoding quoted-printable, do so now.
  356. // QUOTED-PRINTABLE is not allowed in version 3.0,
  357. // but we don't check for versioning, so we do it
  358. // regardless. ;-)
  359. $this->_decode_qp($params, $right);
  360. // now get the value-data from the line, based on
  361. // the typedef
  362. switch ($typedef) {
  363. case 'N':
  364. // structured name of the person
  365. $value = $this->_parseN($right);
  366. break;
  367. case 'ADR':
  368. // structured address of the person
  369. $value = $this->_parseADR($right);
  370. break;
  371. case 'NICKNAME':
  372. // nicknames
  373. $value = $this->_parseNICKNAME($right);
  374. break;
  375. case 'ORG':
  376. // organizations the person belongs to
  377. $value = $this->_parseORG($right);
  378. break;
  379. case 'CATEGORIES':
  380. // categories to which this card is assigned
  381. $value = $this->_parseCATEGORIES($right);
  382. break;
  383. case 'GEO':
  384. // geographic coordinates
  385. $value = $this->_parseGEO($right);
  386. break;
  387. default:
  388. // by default, just grab the plain value. keep
  389. // as an array to make sure *all* values are
  390. // arrays. for consistency. ;-)
  391. $value = array(array($right));
  392. break;
  393. }
  394. // add the type, parameters, and value to the
  395. // current card array. note that we allow multiple
  396. // instances of the same type, which might be dumb
  397. // in some cases (e.g., N).
  398. $card[$typedef][] = array(
  399. 'param' => $params,
  400. 'value' => $value
  401. );
  402. }
  403. }
  404. }
  405. $this->unescape($info);
  406. return $info;
  407. }
  408. /**
  409. *
  410. * Takes a vCard line and extracts the Type-Definition for the line.
  411. *
  412. * @access private
  413. *
  414. * @param string $text A left-part (before-the-colon part) from a
  415. * vCard line.
  416. *
  417. * @return string The type definition for the line.
  418. *
  419. */
  420. function _getTypeDef($text)
  421. {
  422. // split the text by semicolons
  423. $split = $this->splitBySemi($text);
  424. // only return first element (the typedef)
  425. return strtoupper($split[0]);
  426. }
  427. /**
  428. *
  429. * Finds the Type-Definition parameters for a vCard line.
  430. *
  431. * @access private
  432. *
  433. * @param string $text A left-part (before-the-colon part) from a
  434. * vCard line.
  435. *
  436. * @return mixed An array of parameters.
  437. *
  438. */
  439. function _getParams($text)
  440. {
  441. // split the text by semicolons into an array
  442. $split = $this->splitBySemi($text);
  443. // drop the first element of the array (the type-definition)
  444. array_shift($split);
  445. // set up an array to retain the parameters, if any
  446. $params = array();
  447. // loop through each parameter. the params may be in the format...
  448. // "TYPE=type1,type2,type3"
  449. // ...or...
  450. // "TYPE=type1;TYPE=type2;TYPE=type3"
  451. foreach ($split as $full) {
  452. // split the full parameter at the equal sign so we can tell
  453. // the parameter name from the parameter value
  454. $tmp = explode("=", $full);
  455. // the key is the left portion of the parameter (before
  456. // '='). if in 2.1 format, the key may in fact be the
  457. // parameter value, not the parameter name.
  458. $key = strtoupper(trim($tmp[0]));
  459. // get the parameter name by checking to see if it's in
  460. // vCard 2.1 or 3.0 format.
  461. $name = $this->_getParamName($key);
  462. // list of all parameter values
  463. $listall = trim($tmp[1]);
  464. // if there is a value-list for this parameter, they are
  465. // separated by commas, so split them out too.
  466. $list = $this->splitByComma($listall);
  467. // now loop through each value in the parameter and retain
  468. // it. if the value is blank, that means it's a 2.1-style
  469. // param, and the key itself is the value.
  470. foreach ($list as $val) {
  471. if (trim($val) != '') {
  472. // 3.0 formatted parameter
  473. $params[$name][] = trim($val);
  474. } else {
  475. // 2.1 formatted parameter
  476. $params[$name][] = $key;
  477. }
  478. }
  479. // if, after all this, there are no parameter values for the
  480. // parameter name, retain no info about the parameter (saves
  481. // ram and checking-time later).
  482. if (count($params[$name]) == 0) {
  483. unset($params[$name]);
  484. }
  485. }
  486. // return the parameters array.
  487. return $params;
  488. }
  489. /**
  490. *
  491. * Looks at the parameters of a vCard line; if one of them is
  492. * ENCODING[] => QUOTED-PRINTABLE then decode the text in-place.
  493. *
  494. * @access private
  495. *
  496. * @param array $params A parameter array from a vCard line.
  497. *
  498. * @param string $text A right-part (after-the-colon part) from a
  499. * vCard line.
  500. *
  501. * @return void
  502. *
  503. */
  504. function _decode_qp(&$params, &$text)
  505. {
  506. // loop through each parameter
  507. foreach ($params as $param_key => $param_val) {
  508. // check to see if it's an encoding param
  509. if (trim(strtoupper($param_key)) == 'ENCODING') {
  510. // loop through each encoding param value
  511. foreach ($param_val as $enc_key => $enc_val) {
  512. // if any of the values are QP, decode the text
  513. // in-place and return
  514. if (trim(strtoupper($enc_val)) == 'QUOTED-PRINTABLE') {
  515. $text = quoted_printable_decode($text);
  516. return;
  517. }
  518. }
  519. }
  520. }
  521. }
  522. /**
  523. *
  524. * Returns parameter names from 2.1-formatted vCards.
  525. *
  526. * The vCard 2.1 specification allows parameter values without a
  527. * name. The parameter name is then determined from the unique
  528. * parameter value.
  529. *
  530. * Shamelessly lifted from Frank Hellwig <frank@hellwig.org> and his
  531. * vCard PHP project <http://vcardphp.sourceforge.net>.
  532. *
  533. * @access private
  534. *
  535. * @param string $value The first element in a parameter name-value
  536. * pair.
  537. *
  538. * @return string The proper parameter name (TYPE, ENCODING, or
  539. * VALUE).
  540. *
  541. */
  542. function _getParamName($value)
  543. {
  544. static $types = array (
  545. 'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK',
  546. 'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER',
  547. 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO',
  548. 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD',
  549. 'INTERNET', 'IBMMAIL', 'MCIMAIL',
  550. 'POWERSHARE', 'PRODIGY', 'TLX', 'X400',
  551. 'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB',
  552. 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME',
  553. 'MPEG', 'MPEG2', 'AVI',
  554. 'WAVE', 'AIFF', 'PCM',
  555. 'X509', 'PGP'
  556. );
  557. // CONTENT-ID added by pmj
  558. static $values = array (
  559. 'INLINE', 'URL', 'CID', 'CONTENT-ID'
  560. );
  561. // 8BIT added by pmj
  562. static $encodings = array (
  563. '7BIT', '8BIT', 'QUOTED-PRINTABLE', 'BASE64'
  564. );
  565. // changed by pmj to the following so that the name defaults to
  566. // whatever the original value was. Frank Hellwig's original
  567. // code was "$name = 'UNKNOWN'".
  568. $name = $value;
  569. if (in_array($value, $types)) {
  570. $name = 'TYPE';
  571. } elseif (in_array($value, $values)) {
  572. $name = 'VALUE';
  573. } elseif (in_array($value, $encodings)) {
  574. $name = 'ENCODING';
  575. }
  576. return $name;
  577. }
  578. /**
  579. *
  580. * Parses a vCard line value identified as being of the "N"
  581. * (structured name) type-defintion.
  582. *
  583. * @access private
  584. *
  585. * @param string $text The right-part (after-the-colon part) of a
  586. * vCard line.
  587. *
  588. * @return array An array of key-value pairs where the key is the
  589. * portion-name and the value is the portion-value. The value itself
  590. * may be an array as well if multiple comma-separated values were
  591. * indicated in the vCard source.
  592. *
  593. */
  594. function _parseN($text)
  595. {
  596. // make sure there are always at least 5 elements
  597. $tmp = array_pad($this->splitBySemi($text), 5, '');
  598. return array(
  599. $this->splitByComma($tmp[0]), // family (last)
  600. $this->splitByComma($tmp[1]), // given (first)
  601. $this->splitByComma($tmp[2]), // addl (middle)
  602. $this->splitByComma($tmp[3]), // prefix
  603. $this->splitByComma($tmp[4]) // suffix
  604. );
  605. }
  606. /**
  607. *
  608. * Parses a vCard line value identified as being of the "ADR"
  609. * (structured address) type-defintion.
  610. *
  611. * @access private
  612. *
  613. * @param string $text The right-part (after-the-colon part) of a
  614. * vCard line.
  615. *
  616. * @return array An array of key-value pairs where the key is the
  617. * portion-name and the value is the portion-value. The value itself
  618. * may be an array as well if multiple comma-separated values were
  619. * indicated in the vCard source.
  620. *
  621. */
  622. function _parseADR($text)
  623. {
  624. // make sure there are always at least 7 elements
  625. $tmp = array_pad($this->splitBySemi($text), 7, '');
  626. return array(
  627. $this->splitByComma($tmp[0]), // pob
  628. $this->splitByComma($tmp[1]), // extend
  629. $this->splitByComma($tmp[2]), // street
  630. $this->splitByComma($tmp[3]), // locality (city)
  631. $this->splitByComma($tmp[4]), // region (state)
  632. $this->splitByComma($tmp[5]), // postcode (ZIP)
  633. $this->splitByComma($tmp[6]) // country
  634. );
  635. }
  636. /**
  637. *
  638. * Parses a vCard line value identified as being of the "NICKNAME"
  639. * (informal or descriptive name) type-defintion.
  640. *
  641. * @access private
  642. *
  643. * @param string $text The right-part (after-the-colon part) of a
  644. * vCard line.
  645. *
  646. * @return array An array of nicknames.
  647. *
  648. */
  649. function _parseNICKNAME($text)
  650. {
  651. return array($this->splitByComma($text));
  652. }
  653. /**
  654. *
  655. * Parses a vCard line value identified as being of the "ORG"
  656. * (organizational info) type-defintion.
  657. *
  658. * @access private
  659. *
  660. * @param string $text The right-part (after-the-colon part) of a
  661. * vCard line.
  662. *
  663. * @return array An array of organizations; each element of the array
  664. * is itself an array, which indicates primary organization and
  665. * sub-organizations.
  666. *
  667. */
  668. function _parseORG($text)
  669. {
  670. $tmp = $this->splitbySemi($text);
  671. $list = array();
  672. foreach ($tmp as $val) {
  673. $list[] = array($val);
  674. }
  675. return $list;
  676. }
  677. /**
  678. *
  679. * Parses a vCard line value identified as being of the "CATEGORIES"
  680. * (card-category) type-defintion.
  681. *
  682. * @access private
  683. *
  684. * @param string $text The right-part (after-the-colon part) of a
  685. * vCard line.
  686. *
  687. * @return mixed An array of categories.
  688. *
  689. */
  690. function _parseCATEGORIES($text)
  691. {
  692. return array($this->splitByComma($text));
  693. }
  694. /**
  695. *
  696. * Parses a vCard line value identified as being of the "GEO"
  697. * (geographic coordinate) type-defintion.
  698. *
  699. * @access private
  700. *
  701. * @param string $text The right-part (after-the-colon part) of a
  702. * vCard line.
  703. *
  704. * @return mixed An array of lat-lon geocoords.
  705. *
  706. */
  707. function _parseGEO($text)
  708. {
  709. // make sure there are always at least 2 elements
  710. $tmp = array_pad($this->splitBySemi($text), 2, '');
  711. return array(
  712. array($tmp[0]), // lat
  713. array($tmp[1]) // lon
  714. );
  715. }
  716. }
  717. ?>