* * Can also output hCard microformat: * @link http://microformats.org/wiki/hcard * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Esther Brunner * @author Jürgen A.Lamers * @author Elan Ruusamäe */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_vcard extends DokuWiki_Syntax_Plugin { /** * plugin folded is present and enabled * @var bool $have_folded */ private $have_folded = false; public function __construct() { $this->have_folded = !plugin_isdisabled('folded'); } function getType(){ return 'substition'; } function getSort(){ return 314; } function connectTo($mode) { $this->Lexer->addSpecialPattern("{{vcard>.*?}}", $mode, 'plugin_vcard'); } /** * Handle the match */ function handle($match, $state, $pos, Doku_Handler $handler) { // strip markup $match = substr($match, 8, -2); // split address from rest and divide it into parts list($match, $address) = explode('|', $match, 2); if ($address) { list($street, $place, $country) = explode(',', $address, 3); list($zip, $city) = explode(' ', trim($place), 2); } // split phone(s) from rest and create an array list($match, $phones) = explode('#', $match, 2); $phones = explode('&', $phones); foreach ($phones as $phone) { $phone = trim($phone); } // get birthday if (preg_match('/\d{4}\-\d{2}\-\d{2}/', $match, $m)) { $birthday = $m[0]; } // get website $punc = '.:?\-;,'; $any = '\w/~:.?+=&%@!\-'; if (preg_match('#http://['.$any.']+?(?=['.$punc.']*[^'.$any.'])#i',$match, $m)) { $website = $m[0]; } // get e-mail address if (preg_match('/<[\w0-9\-_.]+?@[\w\-]+\.[\w\-\.]+\.*[\w]+>/i', $match, $email, PREG_OFFSET_CAPTURE)) { $match = substr($match,0,$email[0][1]); $email = substr($email[0][0],1,-1); } // get company name if (preg_match('/\[(.*)\]/', $match, $m)) { $match = str_replace($m[0], '', $match); $company = $m[1]; } // the rest is the name $match = trim($match); $pos = strrpos($match, ' '); if ($pos !== false) { list($first,$middle) = explode(' ', substr($match, 0, $pos), 2); $last = substr($match, $pos + 1); } else { $first = $match; $middle = null; $last = null; } return array( 'given-name' => $first, 'additional-name' => $middle, 'family-name' => $last, 'email' => $email, 'website' => $website, 'bday' => $birthday, 'work' => trim($phones[0]), 'cell' => trim($phones[1]), 'home' => trim($phones[2]), 'fax' => trim($phones[3]), 'street-address' => trim($street), 'postal-code' => $zip, 'locality' => $city, 'country-name' => trim($country), 'org' => $company, ); } /** * Create output */ function render($mode, Doku_Renderer $renderer, $data) { if ($mode != 'xhtml') { return false; } $html = ''; $hcard = $this->getConf('do_hcard'); if ($this->getConf('email_shortcut')) { $html .= ' '.$this->_emaillink($renderer, $data['email']).' '; } if ($hcard) { $name = $this->_tagclass('given-name', $data['given-name']); if ($data['additional-name']) { $name .= ' '.$this->_tagclass('additional-name', $data['additional-name']); } if ($data['family-name']) { $name .= ' '.$this->_tagclass('family-name', $data['family-name']); } } else { $name = $renderer->_xmlEntities($data['given-name']. ($data['family-name'] ? ' '.$data['family-name'] : '')); } $url = DOKU_URL.'lib/plugins/vcard/vcard.php?'.buildURLparams($data); $html .= $this->_weblink($renderer, $url, $name, 'iw_vcard url fn n', $data['email']); if ($this->have_folded) { global $plugin_folded_count; $plugin_folded_count++; // folded plugin is installed: enables additional feature $html .= ''; $html .= ''; } if ($hcard) { $renderer->doc .= $this->_tagclass('vcard', $html); } else { $renderer->doc .= $html; } return true; } /** * Build hCard formatted folded body */ private function _folded_hcard(&$renderer, $data) { $folded = ''; if ($data['org']) { $folded .= $this->_tag('org', ''.$data['org'].''); } if ($data['email']) { $folded .= ' '.$this->getLang('email').' '; $folded .= $this->_emaillink($renderer, $data['email'], $data['email']); } if ($data['work']) { $folded .= $this->_tel($renderer, 'work', $data['work']); } if ($data['cell']) { $folded .= $this->_tel($renderer, 'cell', $data['cell']); } if ($data['home']) { $folded .= $this->_tel($renderer, 'home', $data['home']); } if ($data['fax']) { $folded .= $this->_tel($renderer, 'fax', $data['fax']); } if ($data['website']) { $html = ''.$this->getLang('website').' '; $html .= $this->_weblink($renderer, $data['website'], $renderer->_xmlEntities($data['website']), 'url'); $folded .= $this->_tag('url', $html); } if ($data['bday']) { $html = ''.$this->getLang('bday').' '. $renderer->_xmlEntities($data['bday']); $folded .= $this->_tagclass('bday', $html); } $addr = array(); if ($data['street-address']) { $html = $renderer->_xmlEntities($data['street-address']); $addr[] = $this->_tagclass('street-address', $html); } // postal code and locality (city) separated by space if ($data['postal-code'] || $data['locality']) { $loc = array(); if ($data['postal-code']) { $html = $renderer->_xmlEntities($data['postal-code']); $loc[] = $this->_tagclass('postal-code', $html); } if ($data['locality']) { $html = $renderer->_xmlEntities($data['locality']); $loc[] = $this->_tagclass('locality', $html); } $addr[] = join(' ', $loc); } if ($data['country-name']) { $html = $renderer->_xmlEntities($data['country-name']); $addr[] = $this->_tagclass('country-name', $html); } if (!empty($addr)) { $folded .= ' '.$this->getLang('address').' '. join(', ', $addr); } return $folded; } /** * Build plain formatting for vCard * @deprecated, to be dropped if hCard output is complete, because this * portion is incomplete and hCard provides similar (but complete) output */ private function _folded_vcard(&$renderer, $data) { $folded = ''; if ($data['org']) { $folded .= ''.$data['org'].''; } if ($data['email']) { $folded .= ' '.$this->_emaillink($renderer, $data['email'], $data['email']); } if ($data['work']) { $html = $renderer->_xmlEntities($data['work']); $folded .= ' '.$this->getLang('work').': '. $html; } if ($data['cell']) { $html = $renderer->_xmlEntities($data['cell']); $folded .= ' '.$this->getLang('cell').': '. $html; } if ($data['home']) { $html = $renderer->_xmlEntities($data['home']); $folded .= ' '.$this->getLang('home').' '. $html; } if ($data['website']) { $html = $renderer->_xmlEntities($data['website']); $folded .= $this->_weblink($renderer, $data['website'], $html, 'url'); } if ($data['street-address']) { $folded .= ' '.$renderer->_xmlEntities($data['street-address']).','; } if ($data['postal-code']) { $folded .= ' '.$renderer->_xmlEntities($data['postal-code']); } if ($data['locality']) { $folded .= ' '.$renderer->_xmlEntities($data['locality']); } return $folded; } /** * create $tag with $class */ private function _tag($tag_, $text, $class = '') { $tag = $this->getConf("tag_$tag_"); if (!$tag) { $tag = 'span'; } $html = ''; $html .= '<'.$tag; if ($class) { $html .= ' class="'. $class. '"'; } // if tag is 'abbr', use translation and value in title if ($tag == 'abbr') { $html .= ' title="'. $text. '"'; $html .= '>'.$this->getLang($text); } else { $html .= '>'.$text; } $html .= ''; return $html; } /** * create tag with class as $tag */ private function _tagclass($tag, $text) { return $this->_tag($tag, $text, $tag); } /** * normalize telephone number to include all non-numeric outside class=value * see http://microformats.org/wiki/value-class-pattern */ private function _tel_normalize($value) { $res = array(); // match all but +, digits and space if (!preg_match_all('/[^+\d\s]+/', $value, $matches, PREG_OFFSET_CAPTURE)) { $res[] = array('+', $value); return $res; } $offset = 0; foreach ($matches[0] as $match) { $v = substr($value, $offset, $match[1] - $offset); if ($v) { $res[] = array('+', $v); } $res[] = array('-', $match[0]); $offset = $match[1] + strlen($match[0]); } $v = substr($value, $offset); if ($v) { $res[] = array('+', $v); } return $res; } /** * format hcard telephone numbers */ private function _tel(&$renderer, $type, $value) { $type = ''.$this->_tag('tel_type_'.$type, $type, 'type').' '; $values = ''; foreach ($this->_tel_normalize($value) as $res) { list($t, $value) = $res; if ($t === '+') { $values .= $this->_tag('tel_value', $renderer->_xmlEntities($value), 'value'); } else { $values .= $value; } } return $this->_tagclass('tel', $type.$values); } private function _emaillink(&$renderer, $mail, $name = '') { return $renderer->_formatLink(array( 'url' => 'mailto:'.$mail, 'name' => $name, 'class'=> 'email', )); } private function _weblink(&$renderer, $url, $name = '', $class = '', $title ='') { global $conf; return $renderer->_formatLink(array( 'url' => $url, 'name' => $name, 'title' => $title, 'rel' => 'nofollow', 'target' => $conf['target']['extern'], 'class'=> trim('urlextern '. $class), )); } } //Setup VIM: ex: noet ts=4 sw=4 enc=utf-8 :