diff --git a/action/ajax.php b/action/ajax.php --- a/action/ajax.php +++ b/action/ajax.php @@ -1,88 +1,123 @@ hlp =& plugin_load('helper','davcard'); } function register(Doku_Event_Handler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); } function handle_ajax_call_unknown(&$event, $param) { if($event->data != 'plugin_davcard') return; $event->preventDefault(); $event->stopPropagation(); global $INPUT; $action = trim($INPUT->post->str('action')); $id = trim($INPUT->post->str('id')); $page = trim($INPUT->post->str('page')); $params = $INPUT->post->arr('params'); if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) $user = $_SERVER['REMOTE_USER']; else $user = null; if(!checkSecurityToken()) { echo "CSRF Attack."; return; } $data = array(); $data['result'] = false; $data['html'] = $this->getLang('unknown_error'); // Parse the requested action switch($action) { // Add a new Contact case 'newContact': if($this->hlp->addContactEntryToAddressbookForPage($id, $user, $params) === true) { $data['result'] = true; } else { $data['result'] = false; $data['html'] = $this->getLang('error_adding'); } break; // Edit a contact case 'editContact': break; // Delete a Contact case 'deleteContact': break; + // Get AJAX popup + case 'getContactAjax': + $contactdata = $this->hlp->getContactByUri($id, $params['uri']); + foreach($contactdata['photo'] as $type => $photodata) + { + $type = strtolower($type); + $imgdata = base64_encode($photodata); + $pattern = '/^(?:[;\/?:@&=+$,]|(?:[^\W_]|[-_.!~*\()\[\] ])|(?:%[\da-fA-F]{2}))*$/'; + + // PNG images + if($type == 'png') + { + $imgdata = 'data:image/png;base64,'.$imgdata; + echo ''; + } + // JPEG images + elseif(($type == 'jpeg') || ($type == 'jpg')) + { + $imgdata = 'data:image/jpeg;base64,'.$imgdata; + echo ''; + } + // GIF images + elseif($type == 'gif') + { + $imgdata = 'data:image/gif;base64,'.$imgdata; + echo ''; + } + // URLs (no type given) + elseif(preg_match( $pattern, $string ) == 1) + { + echo ''; + } + } + return; + break; } // If we are still here, JSON output is requested //json library of DokuWiki require_once DOKU_INC . 'inc/JSON.php'; $json = new JSON(); //set content type header('Content-Type: application/json'); echo $json->encode($data); } } diff --git a/helper.php b/helper.php --- a/helper.php +++ b/helper.php @@ -1,411 +1,423 @@ sqlite =& plugin_load('helper', 'sqlite'); global $conf; if(!$this->sqlite) { if($conf['allowdebug']) dbglog('This plugin requires the sqlite plugin. Please install it.'); msg('This plugin requires the sqlite plugin. Please install it.'); return; } if(!$this->sqlite->init('davcard', DOKU_PLUGIN.'davcard/db/')) { if($conf['allowdebug']) dbglog('Error initialising the SQLite DB for DAVCard'); return; } } private function getContactByDetails($id, $type, $params = array()) { if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return $this->getLang('no_wdc'); $connectionId = str_replace('webdav://', '', $id); $settings = $wdc->getConnection($connectionId); if($settings === false) return array('formattedname' => $this->getLang('settings_not_found')); if($settings['type'] !== 'contacts') return array('formattedname' => $this->getLang('wrong_type')); $entries = $wdc->getAddressbookEntries($connectionId); foreach($entries as $entry) { switch($type) { case 'structuredname': $contactdata = explode(';', strtolower($entry['structuredname'])); if(count($contactdata) < 2) // We need at least first and lat name + return array('formattedname' => $this->getLang('contact_not_found')); if(($params['lastname'] != '') && ($contactdata[0] === $params['lastname']) || $params['lastname'] === '') { // last name matched or no last name given if(($params['firstname'] != '') && ($contactdata[1] === $params['firstname']) || $params['firstname'] === '') { // first name matched too or no first name given $info = $this->parseVcard($entry['contactdata'], $entry['uri']); return $info; } } break; case 'formattedname': if(trim(strtolower($entry['formattedname'])) == $params['formattedname']) { $info = $this->parseVcard($entry['contactdata'], $entry['uri']); return $info; } break; case 'email': $info = $this->parseVcard($entry['contactdata'], $entry['uri']); foreach($info['mail'] as $key => $mail) { if($mail === strtolower($params['email'])) return $info; } break; } } } return array('formattedname' => $this->getLang('contact_not_found')); } public function getContactByStructuredName($id, $firstname = '', $lastname = '') { return $this->getContactByDetails($id, 'structuredname', array('firstname' => strtolower($firstname), 'lastname' => strtolower($lastname))); } public function getContactByEmail($id, $email) { // FIXME: Maybe it's a good idea to save the e-mail in the database as well! return $this->getContactByDetails($id, 'email', array('email' => strtolower($email))); } public function getContactByFormattedName($id, $name) { return $this->getContactByDetails($id, 'formattedname', array('formattedname' => strtolower($name))); } public function getContactByUri($id, $uri) { if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return $this->getLang('no_wdc'); $connectionId = str_replace('webdav://', '', $id); $settings = $wdc->getConnection($connectionId); if($settings === false) return array('formattedname' => $this->getLang('settings_not_found')); if($settings['type'] !== 'contacts') return array('formattedname' => $this->getLang('wrong_type')); - $row = $wdc->getgetAddressbookEntryByUri($connectionId, $uri); + $row = $wdc->getAddressbookEntryByUri($connectionId, $uri); if($row === false) return array('formattedname' => $this->getLang('contact_not_found')); $info = $this->parseVcard($row['contactdata'], $row['uri']); return $info; } return array('formattedname' => $this->getLang('contact_not_found')); } public function setAddressbookNameForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { $userid = uniqid('davcard-'); } } $bookid = $this->getAddressbookIdForPage($id); if($bookid === false) return $this->createAddressbookForPage($name, $description, $id, $userid); $query = "UPDATE addressbooks SET displayname = ?, description = ? WHERE id = ?"; $res = $this->sqlite->query($query, $name, $description, $bookid); if($res !== false) return true; return false; } public function getAddressbookIdForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $query = "SELECT addressbookid FROM pagetoaddressbookmapping WHERE page = ?"; $res = $this->sqlite->query($query, $id); $row = $this->sqlite->res2row($res); if(isset($row['addressbookid'])) { $calid = $row['addressbookid']; return $calid; } return false; } public function createAddressbookForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { $userid = uniqid('davcard-'); } } $values = array('principals/'.$userid, $name, str_replace(array('/', ' ', ':'), '_', $id), $description, 1); $query = "INSERT INTO addressbooks (principaluri, displayname, uri, description, synctoken) ". "VALUES (?, ?, ?, ?, ?)"; $res = $this->sqlite->query($query, $values); if($res === false) return false; // Get the new addressbook ID $query = "SELECT id FROM addressbooks WHERE principaluri = ? AND displayname = ? AND ". "uri = ? AND description = ? AND synctoken = ?"; $res = $this->sqlite->query($query, $values); $row = $this->sqlite->res2row($res); // Update the pagetocalendarmapping table with the new calendar ID if(isset($row['id'])) { $query = "INSERT INTO pagetoaddressbookmapping (page, addressbookid) VALUES (?, ?)"; $res = $this->sqlite->query($query, $id, $row['id']); return ($res !== false); } return false; } public function addContactEntryToAddressbookForPage($id, $user, $params) { require_once(DOKU_PLUGIN.'davcard/vendor/autoload.php'); $vcard = new \Sabre\VObject\Component\VCard(); $formattedname = $params['firstname'].' '.$params['lastname']; // FIXME: Make this configurable? $structuredname = array($params['lastname'], $params['firstname'], '', '', ''); $vcard->FN = $formattedname; if($params['cellphone'] != '') { $vcard->add('TEL', $params['cellphone'], array('type' => 'cell')); } if($params['phone'] != '') { $vcard->add('TEL', $params['phone'], array('type' => 'home')); } $vcard->N = $structuredname; $vcard->add('ADR', array('', '', $params['street'], $params['city'], '', $params['zipcode'], $params['country']), array('type' => 'home')); $contactdata = $vcard->serialize(); if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return false; $connectionId = str_replace('webdav://', '', $id); return $wdc->addAddressbookEntry($connectionId, $contactdata); } else { $addressbookid = $this->getAddressbookIdForPage($id); $uri = uniqid('dokuwiki-').'.vcf'; $now = new \DateTime(); $query = "INSERT INTO addressbookobjects (contactdata, uri, addressbookid, lastmodified, etag, size, formattedname, structuredname) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; $res = $this->sqlite->query($query, $contactdata, $uri, $addressbookid, $now->getTimestamp(), md5($contactdata), strlen($contactdata), $formattedname, implode(';', $structuredname) ); // If successfully, update the sync token database if($res !== false) { $this->updateSyncTokenLog($addressbookid, $uri, 'added'); return true; } } } private function parseVcard($card, $uri) { require_once(DOKU_PLUGIN.'davcard/vendor/autoload.php'); $vObject = \Sabre\VObject\Reader::read($card); $formattedname = ''; $structuredname = ''; $tel = array(); $addr = array(); $mail = array(); + $photo = array(); if(isset($vObject->FN)) $formattedname = (string)$vObject->FN; if(isset($vObject->N)) $structuredname = join(';', $vObject->N->getParts()); if(isset($vObject->TEL)) { foreach($vObject->TEL as $number) { if(isset($number['TYPE'])) $tel[(string)$number['TYPE']] = (string)$number; else $tel[] = (string)$number; } } if(isset($vObject->ADR)) { foreach($vObject->ADR as $adr) { if(isset($adr['TYPE'])) $addr[(string)$adr['TYPE']] = $adr->getParts(); else $addr[] = $adr->getParts(); } } if(isset($vObject->EMAIL)) { foreach($vObject->EMAIL as $email) { if(isset($email['TYPE'])) $mail[(string)$email['TYPE']] = (string)$email; else $mail[] = (string)$email; } } + if(isset($vObject->PHOTO)) + { + if(isset($vObject->PHOTO['TYPE'])) + { + $photo[(string)$vObject->PHOTO['TYPE']] = (string)$vObject->PHOTO; + } + else + $photo[] = (string)$vObject->PHOTO; + } return array( 'formattedname' => $formattedname, 'structuredname' => $structuredname, 'tel' => $tel, 'mail' => $mail, 'addr' => $addr, - 'uri' => $uri + 'uri' => $uri, + 'photo' => $photo ); } public function getAddressbookSettings($addressbookid) { $query = "SELECT id, principaluri, displayname, uri, description, synctoken FROM addressbooks WHERE id= ? "; $res = $this->sqlite->query($query, $addressbookid); $row = $this->sqlite->res2row($res); return $row; } public function getSyncTokenForAddressbook($addressbookid) { $row = $this->getAddressbookSettings($addressbookid); if(isset($row['synctoken'])) return $row['synctoken']; return false; } /** * Helper function to convert the operation name to * an operation code as stored in the database * * @param string $operationName The operation name * * @return mixed The operation code or false */ public function operationNameToOperation($operationName) { switch($operationName) { case 'added': return 1; break; case 'modified': return 2; break; case 'deleted': return 3; break; } return false; } private function updateSyncTokenLog($addressbookid, $uri, $operation) { $currentToken = $this->getSyncTokenForAddressbook($addressbookid); $operationCode = $this->operationNameToOperation($operation); if(($operationCode === false) || ($currentToken === false)) return false; $values = array($uri, $currentToken, $addressbookid, $operationCode ); $query = "INSERT INTO addressbookchanges (uri, synctoken, addressbookid, operation) VALUES(?, ?, ?, ?)"; $res = $this->sqlite->query($query, $uri, $currentToken, $addressbookid, $operationCode); if($res === false) return false; $currentToken++; $query = "UPDATE addressbooks SET synctoken = ? WHERE id = ?"; $res = $this->sqlite->query($query, $currentToken, $addressbookid); return ($res !== false); } } diff --git a/script.js b/script.js --- a/script.js +++ b/script.js @@ -1,285 +1,334 @@ jQuery(function() { // Attach to addressbook links var addressbookpage = jQuery('#davcardAddressbookList').data('addressbookpage'); if(!addressbookpage) return; dw_davcard__modals.page = addressbookpage; jQuery('div.davcardAddressbookAddNew a').each(function() { var $link = jQuery(this); var href = $link.attr('href'); if (!href) return; $link.click( function(e) { dw_davcard__modals.showEditContactDialog(null, false); e.preventDefault(); return ''; } ); } - ); + ); + var links = 0; + // Remove the CSS-only popup and replace it with a jQuery/AJAX popup + jQuery('div.dokuwiki a.plugin_davcard_url span.plugin_davcard_popup').each(function() { + jQuery(this).addClass('plugin_davcard_nopopup'); + jQuery(this).removeClass('plugin_davcard_popup'); + var $link = jQuery(this).parents('a.plugin_davcard_url'); + if(!$link) + return; + $link.davcard_popup_id = 'plugin_davcard_popup_'+(links++); + $link.mouseover(function () { + $link.davcard_timer = window.setTimeout( + function () { + dw_davcard__modals.showOverlay($link); + $link.davcard_timer = null; + }, + 300 + ); + }); + + $link.mouseout(function () { + $link = jQuery(this); + if ($link.davcard_timer) + window.clearTimeout($link.davcard_timer); + $link.davcard_timer = null; + }); + + }); }); /** - * This holds all modal windows that DAVCal uses. + * This holds all modal windows that DAVCard uses. */ var dw_davcard__modals = { $editContactDialog: null, $confirmDialog: null, page: null, uri: null, action: null, completeCb: null, msg: null, showEditContactDialog : function(entry, edit) { if(dw_davcard__modals.$editContactDialog) return; var title = ''; var dialogButtons = {}; if(edit) { title = LANG.plugins.davcard['edit_entry']; dialogButtons[LANG.plugins.davcard['edit']] = function() { var postArray = { }; var pageid = dw_davcard__modals.page; jQuery("input.dw_davcard__editcontact").each(function() { if(jQuery(this).attr('type') == 'checkbox') { postArray[jQuery(this).prop('name')] = jQuery(this).prop('checked') ? 1 : 0; } else { postArray[jQuery(this).prop('name')] = jQuery(this).val(); } }); jQuery('#dw_davcard__ajaxedit').html(''); jQuery.post( DOKU_BASE + 'lib/exe/ajax.php', { call: 'plugin_davcard', id: pageid, page: dw_davcard__modals.page, action: 'editContact', params: postArray, sectok: JSINFO.plugin.davcal['sectok'] }, function(data) { var result = data['result']; var html = data['html']; jQuery('#dw_davcard__ajaxedit').html(html); if(result === true) { dw_davcard__modals.hideEditContactDialog(); location.reload(); } } ); }; dialogButtons[LANG.plugins.davcal['delete']] = function() { dw_davcard__modals.action = 'deleteContact'; dw_davcard__modals.msg = LANG.plugins.davcard['really_delete_this_entry']; dw_davcard__modals.completeCb = function(data) { var result = data['result']; if(result === true) { dw_davcard__modals.hideEditContactDialog(); location.reload(); } }; dw_davcard__modals.showDialog(true); }; } else { title = LANG.plugins.davcard['create_entry']; dialogButtons[LANG.plugins.davcard['create']] = function() { var postArray = { }; var pageid = dw_davcard__modals.page; jQuery("input.dw_davcard__editcontact").each(function() { if(jQuery(this).attr('type') == 'checkbox') { postArray[jQuery(this).prop('name')] = jQuery(this).prop('checked') ? 1 : 0; } else { postArray[jQuery(this).prop('name')] = jQuery(this).val(); } }); jQuery('#dw_davcard__ajaxedit').html(''); jQuery.post( DOKU_BASE + 'lib/exe/ajax.php', { call: 'plugin_davcard', id: pageid, page: dw_davcard__modals.page, action: 'newContact', params: postArray, sectok: JSINFO.plugin.davcard['sectok'] }, function(data) { var result = data['result']; var html = data['html']; jQuery('#dw_davcard__ajaxedit').html(html); if(result === true) { dw_davcard__modals.hideEditContactDialog(); location.reload(); } } ); }; } dialogButtons[LANG.plugins.davcard['cancel']] = function() { dw_davcard__modals.hideEditContactDialog(); }; dw_davcard__modals.$editContactDialog = jQuery(document.createElement('div')) .dialog({ autoOpen: false, draggable: true, // fix for dragging: http://stackoverflow.com/questions/17247486/jquery-ui-dialog-dragging-issues drag: function(event, ui) { var fixPix = jQuery(document).scrollTop(); iObj = ui.position; iObj.top = iObj.top - fixPix; jQuery(this).closest(".ui-dialog").css("top", iObj.top + "px"); }, title: title, resizable: true, buttons: dialogButtons, }) .html( '
' + LANG.plugins.davcal['calendar'] + ' | |
' + LANG.plugins.davcard['firstname'] + ' | |
' + LANG.plugins.davcard['lastname'] + ' | |
' + LANG.plugins.davcard['cellphone'] + ' | |
' + LANG.plugins.davcard['phone'] + ' | |
' + LANG.plugins.davcard['email'] + ' | |
' + LANG.plugins.davcard['street'] + ' | |
' + LANG.plugins.davcard['zipcode'] + ' | |
' + LANG.plugins.davcard['city'] + ' | |
' + LANG.plugins.davcard['country'] + ' |