├── .travis.yml ├── README.md ├── api ├── contact.php └── local.php ├── appinfo ├── app.php ├── application.php ├── database.xml ├── info.xml ├── remote.php └── routes.php ├── carddav.php ├── controller ├── addressbookcontroller.php ├── contactsapicontroller.php ├── contactscontroller.php ├── exportcontroller.php ├── importcontroller.php ├── pagecontroller.php └── photocontroller.php ├── css ├── 3rdparty │ ├── MarkerCluster.Default.css │ ├── MarkerCluster.Default.ie.css │ ├── MarkerCluster.css │ ├── fontello │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── config.json │ │ ├── css │ │ │ ├── animation.css │ │ │ ├── bootstrap.css │ │ │ ├── fontello-codes.css │ │ │ ├── fontello-embedded.css │ │ │ ├── fontello-ie7-codes.css │ │ │ ├── fontello-ie7.css │ │ │ └── fontello.css │ │ ├── demo.html │ │ └── font │ │ │ ├── fontello.eot │ │ │ ├── fontello.svg │ │ │ ├── fontello.ttf │ │ │ └── fontello.woff │ ├── images │ │ ├── layers-2x.png │ │ ├── layers.png │ │ ├── marker-icon-2x.png │ │ ├── marker-icon.png │ │ ├── marker-shadow.png │ │ ├── markers-matte.png │ │ ├── markers-matte@2x.png │ │ ├── markers-plain.png │ │ ├── markers-shadow.png │ │ ├── markers-shadow@2x.png │ │ ├── markers-soft.png │ │ └── markers-soft@2x.png │ ├── jquery.webui-popover.css │ ├── leaflet.awesome-markers.css │ └── leaflet.css ├── import.css ├── jquery.Jcrop.css └── style.css ├── http └── imageresponse.php ├── img ├── Jcrop.gif ├── contactsplus.png ├── contactsplus.svg ├── edge-arrow-marker-black.png ├── edge-arrow-marker.png ├── favicon.png └── loading.gif ├── js ├── 3rdparty │ ├── Leaflet.EdgeMarker.js │ ├── jquery-ui.drag-multiple.js │ ├── jquery.webui-popover.js │ ├── leaflet-src.js │ ├── leaflet.awesome-markers.js │ ├── leaflet.js │ └── leaflet.markercluster-src.js ├── app.js ├── jquery.Jcrop.js ├── jquery.Jcrop.min.js ├── jquery.nicescroll.min.js ├── jquery.scrollTo.min.js ├── loader.js ├── search.js └── settings.js ├── l10n ├── de.js ├── de.json ├── de_DE.js └── de_DE.json ├── lib ├── addressbook.php ├── addressbookprovider.php ├── app.php ├── connector │ └── sabre │ │ └── carddav │ │ ├── addressbook.php │ │ ├── addressbookroot.php │ │ ├── backend.php │ │ ├── card.php │ │ └── useraddressbooks.php ├── hooks.php ├── import.php ├── search │ ├── provider.php │ └── result.php ├── share │ └── backend │ │ ├── addressbook.php │ │ └── contact.php ├── vcard.php └── vobject │ └── stringpropertycategories.php ├── templates ├── contact.edit.php ├── contact.new.php ├── contact.show.php ├── index.php ├── part.cropphoto.php └── part.import.php └── tests ├── bootstrap.php ├── clover.xml ├── phpunit.xml └── unit └── importtest.php /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7 8 | - hhvm 9 | 10 | env: 11 | global: 12 | - CORE_BRANCH=master 13 | - APPNAME=contactsplus 14 | matrix: 15 | - DB=sqlite 16 | 17 | branches: 18 | only: 19 | - master 20 | - /^stable\d+(\.\d+)?$/ 21 | 22 | before_install: 23 | - wget https://raw.githubusercontent.com/owncloud/administration/master/travis-ci/before_install.sh 24 | - bash ./before_install.sh $APPNAME $CORE_BRANCH $DB 25 | - cd ../core 26 | - php occ app:enable $APPNAME 27 | 28 | before_script: 29 | - cd apps/$APPNAME 30 | 31 | script: 32 | # Test lint 33 | - find . -name \*.php -not -path './vendor/*' -exec php -l "{}" \; 34 | 35 | # Run phpunit tests 36 | - cd tests 37 | - phpunit --configuration phpunit.xml 38 | 39 | matrix: 40 | include: 41 | - php: 5.4 42 | env: DB=mysql 43 | - php: 5.4 44 | env: DB=pgsql 45 | 46 | allow_failures: 47 | - php: hhvm 48 | - php: 7 49 | fast_finish: true 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Contacts+ App 2 | ============= 3 | 4 | Maintainer: 5 | =========== 6 | Sebastian Döll 7 | 8 | Version Info: 9 | ============ 10 | 1.0.9 11 | 12 | Setup Info: 13 | =========== 14 | This version of the contacts app is only compatible with upcoming ownCloud Version 8.1 or later! 15 | 16 | 17 | Installation: 18 | ============= 19 | Download the zip file and rename folder from contactsplus-master to contactsplus! Upload the app to your apps directory and activate it on the apps settings page! 20 | 21 | Import 22 | ====== 23 | - 1. Method 24 | VCF File per Drag & Drop on the contacts+ app, import dialog will prompt 25 | - 2. Method 26 | Upload the VCF File on the files app and then click on the uploaded VCF File and the import dialog will prompt 27 | 28 | Important: 29 | If the default contacts app is enabled, too, then you have to disable the contacts app for import from the files app! 30 | 31 | Carddav Addresses: 32 | ================== 33 | The syncing URL is shown up in the settings dialog (left sidebar bottom) 34 | 35 | Features: 36 | ========== 37 | - create, edit & delete contacts 38 | - Thumbs & List View of contacts 39 | - Sortable Groups 40 | - syncing per carddav 41 | - add contacts to your favourites 42 | - share addressbook with users 43 | - copy or move contacts between addressbooks 44 | - export single contact 45 | - export single addressbook 46 | - import vcf file per drag & drop 47 | - batch (delete contact, delete groups from contact, move, copy contacts) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /api/contact.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\API; 24 | 25 | class Local { 26 | 27 | /** 28 | * get all shares 29 | * 30 | * @param array $params option 'file' to limit the result to a specific file/folder 31 | * @return \OC_OCS_Result share information 32 | */ 33 | 34 | private static $sItems = array(); 35 | const ITEM_TYPE = 'cpladdrbook'; 36 | 37 | public static function getAllShares($params) { 38 | 39 | if (isset($_GET['shared_with_me']) && $_GET['shared_with_me'] !== 'false') { 40 | return self::getItemsSharedWithMe(); 41 | } 42 | //Show all reshared options 43 | $bReshare=false; 44 | if(isset($_GET['reshares']) && $_GET['reshares'] === 'true'){ 45 | $bReshare=true; 46 | } 47 | 48 | $shares = \OCP\Share::getItemShared(self::ITEM_TYPE, null); 49 | 50 | if ($shares === false) { 51 | return new \OC_OCS_Result(null, 404, 'could not get shares'); 52 | } else { 53 | $reshares=array(); 54 | foreach ($shares as &$share) { 55 | if($bReshare === true){ 56 | self::checkReShare($share['item_source']); 57 | } 58 | } 59 | 60 | if(count(self::$sItems)>0){ 61 | $shares=array_merge($shares, self::$sItems); 62 | } 63 | 64 | return new \OC_OCS_Result($shares); 65 | } 66 | 67 | } 68 | /** 69 | * resolves reshares down to the last real share 70 | * @param array $linkItem 71 | * @return array file owner 72 | */ 73 | public static function checkReShare($itemsource){ 74 | 75 | 76 | $getReshares = \OCP\DB::prepare("SELECT * FROM `*PREFIX*share` WHERE `item_source` = ? AND `uid_owner` != ? AND `item_type` = ? AND `parent` != 'NULL' "); 77 | $items = $getReshares->execute(array($itemsource, \OCP\User::getUser(), self::ITEM_TYPE))->fetchAll(); 78 | 79 | foreach($items as $reshare){ 80 | $reshare['share_type'] = (int) $reshare['share_type']; 81 | 82 | if (isset($reshare['share_with']) && $reshare['share_with'] !== '') { 83 | $reshare['share_with_displayname'] = \OCP\User::getDisplayName($reshare['share_with']); 84 | } 85 | self::$sItems[$reshare['id']]=$reshare; 86 | } 87 | 88 | } 89 | 90 | /** 91 | * get share information for a given share 92 | * 93 | * @param array $params which contains a 'id' 94 | * @return \OC_OCS_Result share information 95 | */ 96 | public static function getShare($params) { 97 | 98 | $s = self::getShareFromId($params['id']); 99 | //Show all reshared options 100 | $bReshare=false; 101 | if(isset($_GET['reshares']) && $_GET['reshares'] === 'true'){ 102 | $bReshare=true; 103 | } 104 | $params['itemSource'] = $s['item_source']; 105 | $params['itemType'] = $s['item_type']; 106 | $params['itemTarget'] = $s['item_target']; 107 | $params['specificShare'] = true; 108 | $params['reshare']=$bReshare; 109 | 110 | return self::collectShares($params); 111 | } 112 | 113 | /** 114 | * collect all share information, either of a specific share or all 115 | * shares for a given path 116 | * @param array $params 117 | * @return \OC_OCS_Result 118 | */ 119 | private static function collectShares($params) { 120 | 121 | $itemSource = $params['itemSource']; 122 | $itemType = $params['itemType']; 123 | $getSpecificShare = isset($params['specificShare']) ? $params['specificShare'] : false; 124 | 125 | if ($itemSource !== null) { 126 | $shares = \OCP\Share::getItemShared($itemType, $itemSource); 127 | $receivedFrom = \OCP\Share::getItemSharedWithBySource($itemType, $itemSource); 128 | // if a specific share was specified only return this one 129 | if ($getSpecificShare === true) { 130 | $shareEE=array(); 131 | foreach ($shares as $share) { 132 | if ($share['id'] === (int) $params['id']) { 133 | 134 | $shareEE[] = $share; 135 | 136 | break; 137 | } 138 | } 139 | 140 | if($params['reshare'] === true){ 141 | self::checkReShare($itemSource,$itemType); 142 | 143 | if(count(self::$sItems)>0){ 144 | $shares=array_merge($shareEE, self::$sItems); 145 | } 146 | } 147 | 148 | } 149 | 150 | if ($receivedFrom) { 151 | foreach ($shares as $key => $share) { 152 | $shares[$key]['received_from'] = $receivedFrom['uid_owner']; 153 | $shares[$key]['received_from_displayname'] = \OCP\User::getDisplayName($receivedFrom['uid_owner']); 154 | } 155 | } 156 | } else { 157 | $shares = null; 158 | } 159 | 160 | if ($shares === null || empty($shares)) { 161 | return new \OC_OCS_Result(null, 404, 'share doesn\'t exist'); 162 | } else { 163 | return new \OC_OCS_Result($shares); 164 | } 165 | } 166 | 167 | /** 168 | * get files shared with the user 169 | * @return \OC_OCS_Result 170 | */ 171 | private static function getItemsSharedWithMe() { 172 | try { 173 | $shares = \OCP\Share::getItemsSharedWith(self::ITEM_TYPE); 174 | 175 | $result = new \OC_OCS_Result($shares); 176 | } catch (\Exception $e) { 177 | $result = new \OC_OCS_Result(null, 403, $e->getMessage()); 178 | } 179 | 180 | return $result; 181 | 182 | } 183 | 184 | 185 | /** 186 | * get some information from a given share 187 | * @param int $shareID 188 | * @return array with: item_source, share_type, share_with, item_type, permissions 189 | */ 190 | private static function getShareFromId($shareID) { 191 | $sql = 'SELECT `id`,`item_source`, `share_type`, `share_with`, `item_type`, `item_target`, `permissions`, `stime` FROM `*PREFIX*share` WHERE `id` = ? AND `item_type` = ?'; 192 | $args = array($shareID,self::ITEM_TYPE); 193 | $query = \OCP\DB::prepare($sql); 194 | $result = $query->execute($args); 195 | 196 | if (\OCP\DB::isError($result)) { 197 | \OCP\Util::writeLog('contactsplus', \OCP\DB::getErrorMessage($result), \OCP\Util::ERROR); 198 | return null; 199 | } 200 | if ($share = $result->fetchRow()) { 201 | 202 | 203 | return $share; 204 | } 205 | 206 | return null; 207 | 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /appinfo/app.php: -------------------------------------------------------------------------------- 1 | getContainer(); 8 | 9 | $contactsAppName='contactsplus'; 10 | // add an navigation entr 11 | $navigationEntry = function () use ($c) { 12 | return [ 13 | 'id' => $c->getAppName(), 14 | 'order' => 1, 15 | 'name' => $c->query('L10N')->t('Contacts+'), 16 | 'href' => $c->query('URLGenerator')->linkToRoute($c->getAppName().'.page.index'), 17 | 'icon' => $c->query('URLGenerator')->imagePath($c->getAppName(), 'contactsplus.svg'), 18 | ]; 19 | }; 20 | $c->getServer()->getNavigationManager()->add($navigationEntry); 21 | 22 | if (\OCP\User::isLoggedIn()) { 23 | \OCP\Util::addScript('contactsplus','search'); 24 | } 25 | 26 | \OC::$server->getSearch()->registerProvider('OCA\ContactsPlus\Search\Provider', array('app' => $contactsAppName)); 27 | 28 | \Sabre\VObject\Component\VCard::$propertyMap['CATEGORIES'] = '\OCA\ContactsPlus\VObject\StringPropertyCategories'; 29 | 30 | \OCP\Share::registerBackend(ContactsApp::SHAREADDRESSBOOK, 'OCA\ContactsPlus\Share\Backend\Addressbook'); 31 | \OCP\Share::registerBackend(ContactsApp::SHARECONTACT, 'OCA\ContactsPlus\Share\Backend\Contact'); 32 | 33 | 34 | \OCP\Util::connectHook('OC_User', 'post_deleteUser', '\OCA\ContactsPlus\Hooks', 'deleteUser'); 35 | 36 | if (\OCP\User::isLoggedIn() && !\OCP\App::isEnabled('contacts')) { 37 | $request = $c->query('Request'); 38 | if (isset($request->server['REQUEST_URI'])) { 39 | 40 | $url = $request->server['REQUEST_URI']; 41 | if (preg_match('%index.php/apps/files(/.*)?%', $url) || preg_match('%index.php/s/(/.*)?%', $url)) { 42 | \OCP\Util::addscript($contactsAppName,'loader'); 43 | } 44 | } 45 | 46 | 47 | } 48 | 49 | 50 | if (\OCP\User::isLoggedIn()) { 51 | $cm = \OC::$server->getContactsManager(); 52 | 53 | $cm->register(function() use ($cm) { 54 | $myApp = new Application(); 55 | $addressBooks = $myApp->getContainer()->query('AddressbookController')->getAddressBooksForCM(); 56 | 57 | foreach ($addressBooks as $addressBook) { 58 | $cm->registerAddressBook(new AddressbookProvider($addressBook)); 59 | 60 | } 61 | }); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /appinfo/application.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\AppInfo; 24 | 25 | use OC\AppFramework\Utility\SimpleContainer; 26 | use \OCP\AppFramework\App; 27 | use \OCP\Share; 28 | use \OCP\IContainer; 29 | use OCP\AppFramework\IAppContainer; 30 | 31 | use \OCA\ContactsPlus\Controller\PageController; 32 | use \OCA\ContactsPlus\Controller\PhotoController; 33 | use \OCA\ContactsPlus\Controller\ContactsController; 34 | use \OCA\ContactsPlus\Controller\ExportController; 35 | use \OCA\ContactsPlus\Controller\ImportController; 36 | use \OCA\ContactsPlus\Controller\AddressbookController; 37 | use \OCA\ContactsPlus\Controller\ContactsApiController; 38 | 39 | 40 | class Application extends App { 41 | 42 | /** 43 | * An array holding the current users address books. 44 | * @var array 45 | */ 46 | protected static $addressBooks = array(); 47 | 48 | public function __construct (array $urlParams=array()) { 49 | 50 | parent::__construct('contactsplus', $urlParams); 51 | $container = $this->getContainer(); 52 | 53 | 54 | $container->registerService('PageController', function(IContainer $c) { 55 | return new PageController( 56 | $c->query('AppName'), 57 | $c->query('Request'), 58 | $c->query('UserId'), 59 | $c->query('L10N'), 60 | $c->query('Settings') 61 | ); 62 | }); 63 | 64 | $container->registerService('PhotoController', function(IContainer $c) { 65 | return new PhotoController( 66 | $c->query('AppName'), 67 | $c->query('Request'), 68 | $c->query('L10N') 69 | ); 70 | }); 71 | 72 | 73 | $container->registerService('ContactsController', function(IContainer $c) { 74 | return new ContactsController( 75 | $c->query('AppName'), 76 | $c->query('Request'), 77 | $c->query('UserId'), 78 | $c->query('L10N'), 79 | $c->query('Settings') 80 | ); 81 | }); 82 | 83 | $container->registerService('ExportController', function(IContainer $c) { 84 | return new ExportController( 85 | $c->query('AppName'), 86 | $c->query('Request'), 87 | $c->query('UserId'), 88 | $c->query('L10N'), 89 | $c->query('Settings') 90 | ); 91 | }); 92 | 93 | $container->registerService('ImportController', function(IContainer $c) { 94 | return new ImportController( 95 | $c->query('AppName'), 96 | $c->query('Request'), 97 | $c->query('UserId'), 98 | $c->query('L10N'), 99 | $c->query('Settings') 100 | ); 101 | }); 102 | 103 | 104 | $container->registerService('AddressbookController', function(IContainer $c) { 105 | return new AddressbookController( 106 | $c->query('AppName'), 107 | $c->query('Request'), 108 | $c->query('UserId'), 109 | $c->query('L10N'), 110 | $c->query('Settings') 111 | ); 112 | }); 113 | /* 114 | $container->registerService('ContactsApiController', function(IContainer $c) { 115 | return new ContactsApiController( 116 | $c->query('AppName'), 117 | $c->query('Request') 118 | ); 119 | });*/ 120 | 121 | /** 122 | * Core 123 | */ 124 | 125 | $container->registerService('URLGenerator', function(IContainer $c) { 126 | /** @var \OC\Server $server */ 127 | $server = $c->query('ServerContainer'); 128 | return $server->getURLGenerator(); 129 | }); 130 | 131 | $container -> registerService('UserId', function(IContainer $c) { 132 | $server = $c->query('ServerContainer'); 133 | 134 | $user = $server->getUserSession()->getUser(); 135 | return ($user) ? $user->getUID() : ''; 136 | 137 | }); 138 | 139 | $container -> registerService('L10N', function(IContainer $c) { 140 | return $c -> query('ServerContainer') -> getL10N($c -> query('AppName')); 141 | }); 142 | 143 | $container->registerService('Settings', function($c) { 144 | return $c->query('ServerContainer')->getConfig(); 145 | }); 146 | 147 | $container->registerService('Session', function (IAppContainer $c) { 148 | return $c->getServer() 149 | ->getSession(); 150 | } 151 | ); 152 | $container->registerService('Token', function (IContainer $c) { 153 | return $c->query('Request') ->getParam('token'); 154 | } 155 | ); 156 | } 157 | 158 | 159 | 160 | } 161 | 162 | -------------------------------------------------------------------------------- /appinfo/database.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | *dbname* 5 | true 6 | false 7 | utf8 8 | 9 | 10 | 11 | *dbprefix*conplus_addressbooks 12 | 13 | 14 | 15 | 16 | id 17 | integer 18 | 0 19 | true 20 | 1 21 | true 22 | 4 23 | 24 | 25 | 26 | userid 27 | text 28 | 29 | true 30 | 255 31 | 32 | 33 | 34 | displayname 35 | text 36 | 37 | false 38 | 255 39 | 40 | 41 | 42 | uri 43 | text 44 | 45 | false 46 | 200 47 | 48 | 49 | 50 | description 51 | text 52 | false 53 | 255 54 | 55 | 56 | 57 | ctag 58 | integer 59 | 1 60 | true 61 | true 62 | 4 63 | 64 | 65 | 66 | active 67 | integer 68 | 1 69 | true 70 | 4 71 | 72 | 73 | 74 | sync 75 | integer 76 | 1 77 | true 78 | 2 79 | 80 | 81 | 82 | cp_addressbook_userid_index 83 | 84 | userid 85 | ascending 86 | 87 | 88 | 89 | 90 |
91 | 92 | 93 | 94 | *dbprefix*conplus_cards 95 | 96 | 97 | 98 | 99 | id 100 | integer 101 | 0 102 | true 103 | 1 104 | true 105 | 4 106 | 107 | 108 | 109 | addressbookid 110 | integer 111 | 112 | true 113 | true 114 | 4 115 | 116 | 117 | 118 | fullname 119 | text 120 | 121 | false 122 | 255 123 | 124 | 125 | surename 126 | text 127 | 128 | false 129 | 255 130 | 131 | 132 | lastname 133 | text 134 | 135 | false 136 | 255 137 | 138 | 139 | organization 140 | text 141 | 142 | false 143 | 255 144 | 145 | 146 | bcompany 147 | integer 148 | 149 | false 150 | true 151 | 2 152 | 153 | 154 | carddata 155 | clob 156 | false 157 | 158 | 159 | 160 | uri 161 | text 162 | 163 | false 164 | 200 165 | 166 | 167 | 168 | lastmodified 169 | integer 170 | 171 | false 172 | true 173 | 4 174 | 175 | 176 | 177 | component 178 | text 179 | 180 | false 181 | 100 182 | 183 | 184 | 185 | bcategory 186 | integer 187 | 188 | false 189 | true 190 | 2 191 | 192 | 193 | 194 | cp_addressbookid_index 195 | 196 | addressbookid 197 | ascending 198 | 199 | 200 | 201 | 202 |
203 | 204 | 205 | 206 | *dbprefix*conplus_cards_properties 207 | 208 | 209 | 210 | 211 | id 212 | integer 213 | 0 214 | true 215 | 1 216 | true 217 | 4 218 | 219 | 220 | 221 | userid 222 | text 223 | 224 | true 225 | 255 226 | 227 | 228 | 229 | contactid 230 | integer 231 | 232 | true 233 | true 234 | 4 235 | 236 | 237 | 238 | name 239 | text 240 | 241 | false 242 | 64 243 | 244 | 245 | 246 | value 247 | text 248 | 249 | false 250 | 255 251 | 252 | 253 | 254 | preferred 255 | integer 256 | 1 257 | true 258 | 4 259 | 260 | 261 | 262 | cpp_contactid_index 263 | 264 | contactid 265 | ascending 266 | 267 | 268 | 269 | 270 | cpp_name_index 271 | 272 | name 273 | ascending 274 | 275 | 276 | 277 | 278 | cpp_value_index 279 | 280 | value 281 | ascending 282 | 283 | 284 | 285 | 286 |
287 | 288 |
289 | -------------------------------------------------------------------------------- /appinfo/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | contactsplus 4 | Contacts Plus 5 | AGPL 6 | Döll Sebastian 7 | false 8 | Contacts with carddav support 9 | 1.0.9 10 | ContactsPlus 11 | productivity 12 | 13 | 14 | appinfo/remote.php 15 | 16 | 17 | 18 | 19 | 20 | 171237 21 | 22 | -------------------------------------------------------------------------------- /appinfo/remote.php: -------------------------------------------------------------------------------- 1 | 4 | * This file is licensed under the Affero General Public License version 3 or 5 | * later. 6 | * See the COPYING-README file. 7 | */ 8 | 9 | 10 | 11 | /* 12 | if(substr(OCP\Util::getRequestUri(),0,strlen(OC_App::getAppWebPath('contactsplus').'/carddav.php')) === OC_App::getAppWebPath('contactsplus'). '/carddav.php') { 13 | $baseuri = OC_App::getAppWebPath('contactsplus').'/carddav.php'; 14 | } 15 | 16 | // only need authentication apps 17 | $RUNTIME_APPTYPES=array('authentication'); 18 | OC_App::loadApps($RUNTIME_APPTYPES); 19 | */ 20 | // Backends 21 | $authBackend = new \OC\Connector\Sabre\Auth(); 22 | $principalBackend = new \OC\Connector\Sabre\Principal( 23 | \OC::$server->getConfig(), 24 | \OC::$server->getUserManager() 25 | ); 26 | 27 | 28 | 29 | $carddavBackend = new OCA\ContactsPlus\Connector\Sabre\Carddav\Backend(); 30 | 31 | // Root nodes 32 | $principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend); 33 | $principalCollection->disableListing = true; // Disable listening 34 | 35 | $addressBookRoot = new OCA\ContactsPlus\Connector\Sabre\Carddav\AddressBookRoot($principalBackend, $carddavBackend); 36 | $addressBookRoot->disableListing = true; // Disable listening 37 | 38 | $nodes = array( 39 | $principalCollection, 40 | $addressBookRoot, 41 | ); 42 | 43 | // Fire up server 44 | $server = new \Sabre\DAV\Server($nodes); 45 | $server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); 46 | $server->setBaseUri($baseuri); 47 | 48 | 49 | // Add plugins 50 | $defaults = new OC_Defaults(); 51 | $server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); 52 | $server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); 53 | $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend,$defaults->getName())); 54 | $server->addPlugin(new \Sabre\CardDAV\Plugin()); 55 | $server->addPlugin(new \Sabre\DAVACL\Plugin()); 56 | $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); 57 | 58 | $server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('carddav', \OC::$server->getLogger())); 59 | $server->addPlugin(new \OC\Connector\Sabre\AppEnabledPlugin( 60 | 'contactsplus', 61 | OC::$server->getAppManager() 62 | )); 63 | 64 | 65 | // And off we go! 66 | $server->exec(); 67 | -------------------------------------------------------------------------------- /appinfo/routes.php: -------------------------------------------------------------------------------- 1 | registerRoutes($this, ['routes' => [ 7 | ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], 8 | ['name' => 'photo#getImageFromCloud', 'url' => '/getimagefromcloud', 'verb' => 'GET'], 9 | ['name' => 'photo#cropPhoto', 'url' => '/cropphoto', 'verb' => 'POST'], 10 | ['name' => 'photo#saveCropPhotoContact', 'url' => '/savecropphotocontact', 'verb' => 'POST'], 11 | ['name' => 'photo#uploadPhoto', 'url' => '/uploadphoto', 'verb' => 'POST'], 12 | ['name' => 'photo#deletePhoto', 'url' => '/deletephoto', 'verb' => 'GET'], 13 | ['name' => 'photo#clearPhotoCache', 'url' => '/clearphotocache', 'verb' => 'POST'], 14 | ['name' => 'contacts#getNewFormContact', 'url' => '/getnewformcontact', 'verb' => 'POST'], 15 | ['name' => 'contacts#newContactSave', 'url' => '/newcontactsave', 'verb' => 'POST'], 16 | ['name' => 'contacts#getEditFormContact', 'url' => '/geteditformcontact', 'verb' => 'POST'], 17 | ['name' => 'contacts#editContactSave', 'url' => '/editcontactsave', 'verb' => 'POST'], 18 | // ['name' => 'contacts#showContact', 'url' => '/showcontact', 'verb' => 'POST'], 19 | ['name' => 'contacts#showContact', 'url' => '/showcontact/{id}', 'verb' => 'GET'], 20 | ['name' => 'contacts#getContactPhoto', 'url' => '/getcontactphoto/{id}', 'verb' => 'GET'], 21 | ['name' => 'contacts#deleteContact', 'url' => '/deletecontact', 'verb' => 'POST'], 22 | ['name' => 'contacts#deleteContactFromGroup', 'url' => '/deletecontactfromgroup', 'verb' => 'POST'], 23 | ['name' => 'contacts#addProbertyToContact', 'url' => '/addprobertytocontact', 'verb' => 'POST'], 24 | ['name' => 'contacts#copyContact', 'url' => '/copycontact', 'verb' => 'POST'], 25 | ['name' => 'contacts#moveContact', 'url' => '/movecontact', 'verb' => 'POST'], 26 | ['name' => 'contacts#getContactCards', 'url' => '/getcontactcards', 'verb' => 'POST'], 27 | ['name' => 'contacts#updateTag', 'url' => '/updatetag', 'verb' => 'GET'], 28 | ['name' => 'export#exportContacts', 'url' => '/exportcontacts', 'verb' => 'GET'], 29 | ['name' => 'export#exportBirthdays', 'url' => '/exportbirthdays', 'verb' => 'GET'], 30 | ['name' => 'import#getImportDialogTpl', 'url' => '/getimportdialogtplcontacts', 'verb' => 'POST'], 31 | ['name' => 'import#checkAddressbookExists', 'url' => '/checkaddressbookexists', 'verb' => 'POST'], 32 | ['name' => 'import#importVcards', 'url' => '/importvcards', 'verb' => 'POST'], 33 | ['name' => 'addressbook#getAddressBooks', 'url' => '/getaddressbooks', 'verb' => 'GET'], 34 | ['name' => 'addressbook#add', 'url' => '/addaddrbook', 'verb' => 'POST'], 35 | ['name' => 'addressbook#update', 'url' => '/updateaddrbook', 'verb' => 'POST'], 36 | ['name' => 'addressbook#delete', 'url' => '/deleteaddrbook', 'verb' => 'POST'], 37 | ['name' => 'addressbook#activate', 'url' => '/activateaddrbook', 'verb' => 'POST'], 38 | ['name' => 'addressbook#getCategories', 'url' => '/getcategoriesaddrbook', 'verb' => 'POST'], 39 | ['name' => 'addressbook#addIosGroupsSupport', 'url' => '/addiosgroupssupport', 'verb' => 'POST'], 40 | ['name' => 'addressbook#prepareIosGroups', 'url' => '/prepareiosgroups', 'verb' => 'POST'], 41 | ['name' => 'addressbook#saveSortOrderGroups', 'url' => '/savesortordergroups', 'verb' => 'POST'], 42 | ['name' => 'addressbook#changeCiewContacts', 'url' => '/changeviewcontacts', 'verb' => 'POST'], 43 | //Appi 1.0 44 | ['name' => 'contacts_api#version', 'url' => '/api/1.0/version', 'verb' => 'GET'], 45 | ['name' => 'contacts_api#contact', 'url' => '/api/1.0/contact/{id}', 'verb' => 'GET'], 46 | array('name' => 'contacts_api#preflighted_cors', 47 | 'url' => '/api/1.0/{path}', 48 | 'verb' => 'OPTIONS', 49 | 'requirements' => array('path' => '.+')) 50 | ]]); 51 | 52 | 53 | 54 | \OCP\API::register('get', 55 | '/apps/contactsplus/api/v1/shares', 56 | array('\OCA\ContactsPlus\API\Local', 'getAllShares'), 57 | 'contactsplus'); 58 | \OCP\API::register('get', 59 | '/apps/contactsplus/api/v1/shares/{id}', 60 | array('\OCA\ContactsPlus\API\Local', 'getShare'), 61 | 'contactsplus'); 62 | //\OCP\API::register('get','/apps/contactsplus/api/v1/contact/{id}',array('\OCA\ContactsPlus\API\Contact', 'getContact'),'contactsplus'); 63 | -------------------------------------------------------------------------------- /carddav.php: -------------------------------------------------------------------------------- 1 | $version]; 25 | } 26 | 27 | /** 28 | * @NoAdminRequired 29 | * @NoCSRFRequired 30 | * 31 | * @CORS 32 | */ 33 | public function contact($id){ 34 | 35 | $app = new Application(); 36 | $c = $app->getContainer(); 37 | $contactsController = $c->query('ContactsController'); 38 | 39 | return $contactsController->ApiContactData($id); 40 | 41 | //test http://{owncloudomain}/ocs/v1.php/apps/contactsplus/api/v1/contact/283 42 | /* 43 | $contact = VCard::find($id); 44 | if(!is_null($contact['carddata'])){ 45 | $vcard = VObject\Reader::read($contact['carddata']); 46 | $details = VCard::structureContact($vcard); 47 | 48 | $addrInfo = AddressBook::find($contact['addressbookid']); 49 | $details['addressbook'] = $addrInfo['displayname']; 50 | $details['addressbookuri'] = $addrInfo['uri']; 51 | } 52 | 53 | return $details;*/ 54 | } 55 | } -------------------------------------------------------------------------------- /controller/exportcontroller.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\Controller; 24 | 25 | 26 | use OCA\ContactsPlus\App as ContactsApp; 27 | use OCA\ContactsPlus\VCard; 28 | use OCA\ContactsPlus\Addressbook; 29 | use Sabre\VObject; 30 | 31 | use \OCP\AppFramework\Controller; 32 | use \OCP\AppFramework\Http\JSONResponse; 33 | use \OCP\AppFramework\Http\DataDownloadResponse; 34 | use \OCP\IRequest; 35 | use \OCP\Share; 36 | use \OCP\IConfig; 37 | 38 | class ExportController extends Controller { 39 | 40 | private $userId; 41 | private $l10n; 42 | private $configInfo; 43 | 44 | public function __construct($appName, IRequest $request, $userId, $l10n, IConfig $settings) { 45 | parent::__construct($appName, $request); 46 | $this -> userId = $userId; 47 | $this->l10n = $l10n; 48 | $this->configInfo = $settings; 49 | } 50 | 51 | /** 52 | * @NoAdminRequired 53 | * @NoCSRFRequired 54 | */ 55 | public function exportContacts(){ 56 | $bookid=$this -> params('bookid'); 57 | $bookid = isset($bookid) ? $bookid : null; 58 | 59 | $contactid=$this -> params('contactid'); 60 | $contactid = isset($contactid) ? $contactid : null; 61 | 62 | $selectedids=$this -> params('selectedids'); 63 | $selectedids = isset($selectedids) ? $selectedids : null; 64 | 65 | $nl = "\n"; 66 | if(!is_null($bookid)) { 67 | try { 68 | $addressbook = Addressbook::find($bookid); 69 | } catch(Exception $e) { 70 | OCP\JSON::error( 71 | array( 72 | 'data' => array( 73 | 'message' => $e->getMessage(), 74 | ) 75 | ) 76 | ); 77 | exit(); 78 | } 79 | 80 | $start = 0; 81 | $ContactsOutput=''; 82 | $batchsize = $this->configInfo->getUserValue($this->userId,$this->appName,'export_batch_size', 20); 83 | while($cardobjects = VCard::all($bookid, $start, $batchsize, array('carddata'))) { 84 | foreach($cardobjects as $card) { 85 | $ContactsOutput .= $card['carddata'] . $nl; 86 | } 87 | $start += $batchsize; 88 | } 89 | 90 | $name= str_replace(' ', '_', $addressbook['displayname']) . '.vcf'; 91 | 92 | $response = new DataDownloadResponse($ContactsOutput, $name, 'text/directory'); 93 | 94 | return $response; 95 | 96 | }elseif(!is_null($contactid)) { 97 | try { 98 | $data = VCard::find($contactid); 99 | } catch(Exception $e) { 100 | OCP\JSON::error( 101 | array( 102 | 'data' => array( 103 | 'message' => $e->getMessage(), 104 | ) 105 | ) 106 | ); 107 | exit(); 108 | } 109 | 110 | $name= str_replace(' ', '_', $data['fullname']) . '.vcf'; 111 | 112 | $response = new DataDownloadResponse($data['carddata'], $name, 'text/vcard'); 113 | return $response; 114 | 115 | }elseif(!is_null($selectedids)) { 116 | $selectedids = explode(',', $selectedids); 117 | $name= (string) $this->l10n->t('%d_selected_contacts', array(count($selectedids))) . '.vcf'; 118 | 119 | $ContactsOutput=''; 120 | foreach($selectedids as $id) { 121 | try { 122 | $data = VCard::find($id); 123 | $ContactsOutput.= $data['carddata'] . $nl; 124 | } catch(Exception $e) { 125 | continue; 126 | } 127 | } 128 | $response = new DataDownloadResponse($ContactsOutput, $name, 'text/directory'); 129 | return $response; 130 | 131 | } 132 | 133 | } 134 | 135 | /** 136 | * @NoAdminRequired 137 | * @NoCSRFRequired 138 | */ 139 | public function exportBirthdays(){ 140 | $bookid=$this -> params('aid'); 141 | $bookid = isset($bookid) ? $bookid : null; 142 | 143 | if(!is_null($bookid)){ 144 | $addressbook = Addressbook::find($bookid); 145 | $aDefNArray=array('0'=>'fname','1'=>'lname','3'=>'title','4'=>''); 146 | 147 | foreach(VCard::all($bookid) as $contact) { 148 | try { 149 | $vcard = VObject\Reader::read($contact['carddata']); 150 | 151 | } catch (Exception $e) { 152 | continue; 153 | 154 | } 155 | 156 | $birthday = $vcard->BDAY; 157 | 158 | if ((string) $birthday) { 159 | 160 | $details = VCard::structureContact($vcard); 161 | 162 | $BirthdayTemp = new \DateTime($birthday); 163 | $checkForm = $BirthdayTemp->format('d-m-Y'); 164 | $temp = explode('-',$checkForm); 165 | $getAge= $this->getAgeCalc($temp[2],$temp[1],$temp[0]); 166 | //$getAge=$BirthdayTemp->format('d-m-Y'); 167 | $title=isset($vcard->FN)?strtr($vcard->FN->getValue(), array('\,' => ',', '\;' => ';')):''; 168 | 169 | $sNameOutput=''; 170 | if(isset($details['N'][0]['value']) && count($details['N'][0]['value'])>0){ 171 | foreach($details['N'][0]['value'] as $key => $val){ 172 | if($val!='') { 173 | $aNameOutput[$aDefNArray[$key]]=$val; 174 | 175 | } 176 | } 177 | //$sNameOutput=isset($aNameOutput['title'])?$aNameOutput['title'].' ':''; 178 | $sNameOutput.=isset($aNameOutput['lname'])?$aNameOutput['lname'].' ':''; 179 | $sNameOutput.=isset($aNameOutput['fname'])?$aNameOutput['fname'].' ':''; 180 | 181 | unset($aNameOutput); 182 | } 183 | 184 | if($sNameOutput=='') {$sNameOutput=$title;} 185 | 186 | $sTitle1 =(string)$this->l10n->t('%1$s (%2$s)',array($sNameOutput,$getAge)); 187 | 188 | 189 | $aktYear=$BirthdayTemp->format('d-m'); 190 | $aktYear=$aktYear.date('-Y'); 191 | $start = new \DateTime($aktYear); 192 | $end = new \DateTime($aktYear.' +1 day'); 193 | 194 | $vcalendar = new VObject\Component\VCalendar(); 195 | $vevent = $vcalendar->createComponent('VEVENT'); 196 | $vevent->add('DTSTART'); 197 | $vevent->DTSTART->setDateTime( 198 | $start 199 | ); 200 | $vevent->DTSTART['VALUE'] = 'date'; 201 | $vevent->add('DTEND'); 202 | $vevent->DTEND->setDateTime( 203 | $end 204 | ); 205 | $vevent->DTEND['VALUE'] = 'date'; 206 | $vevent->{'SUMMARY'} = (string)$sTitle1; 207 | $vevent->{'UID'} = substr(md5(rand().time()), 0, 10); 208 | 209 | $params['events'][] = $vevent->serialize(); 210 | } 211 | } 212 | 213 | if(is_array($params['events'])){ 214 | $return = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:ownCloud Calendar " . \OCP\App::getAppVersion('calendar') . "\nX-WR-CALNAME: export-bday-".$bookid."\n"; 215 | 216 | foreach($params['events'] as $event) { 217 | $return .= $event; 218 | } 219 | 220 | $return .= "END:VCALENDAR"; 221 | 222 | $name = str_replace(' ', '_', $addressbook['displayname']).'_birthdays' . '.ics'; 223 | $response = new DataDownloadResponse($return, $name, 'text/calendar'); 224 | 225 | return $response; 226 | 227 | } 228 | 229 | } 230 | } 231 | 232 | private function getAgeCalc($y, $m, $d) { 233 | return date('Y') - $y - (date('n') < (ltrim($m,'0') + (date('j') < ltrim($d,'0')))); 234 | } 235 | 236 | 237 | } -------------------------------------------------------------------------------- /controller/importcontroller.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\Controller; 24 | 25 | 26 | use OCA\ContactsPlus\App as ContactsApp; 27 | use OCA\ContactsPlus\VCard; 28 | use OCA\ContactsPlus\Addressbook; 29 | use \OCA\ContactsPlus\Import; 30 | use Sabre\VObject; 31 | 32 | use \OCP\AppFramework\Controller; 33 | use \OCP\AppFramework\Http\JSONResponse; 34 | use \OCP\AppFramework\Http\TemplateResponse; 35 | use \OCP\IRequest; 36 | use \OCP\Share; 37 | use \OCP\IConfig; 38 | 39 | class ImportController extends Controller { 40 | 41 | private $userId; 42 | private $l10n; 43 | private $configInfo; 44 | 45 | public function __construct($appName, IRequest $request, $userId, $l10n, IConfig $settings) { 46 | parent::__construct($appName, $request); 47 | $this -> userId = $userId; 48 | $this->l10n = $l10n; 49 | $this->configInfo = $settings; 50 | } 51 | 52 | /** 53 | * @NoAdminRequired 54 | */ 55 | public function getImportDialogTpl() { 56 | 57 | $pPath = $this -> params('path'); 58 | $pFile = $this -> params('filename'); 59 | 60 | $params=[ 61 | 'path' => $pPath, 62 | 'filename' => $pFile, 63 | ]; 64 | 65 | $response = new TemplateResponse($this->appName, 'part.import',$params, ''); 66 | 67 | return $response; 68 | 69 | } 70 | 71 | 72 | /** 73 | * @NoAdminRequired 74 | */ 75 | public function importVcards() { 76 | $pProgresskey = $this -> params('progresskey'); 77 | $pGetprogress = $this -> params('getprogress'); 78 | $pPath = $this -> params('path'); 79 | $pFile = $this -> params('file'); 80 | $pMethod = $this -> params('method'); 81 | $pAddressbook = $this -> params('addressbookname'); 82 | $pIsDragged = $this->params('isDragged'); 83 | $pId = $this -> params('id'); 84 | 85 | $pOverwrite = $this -> params('overwrite'); 86 | \OC::$server->getSession()->close(); 87 | 88 | 89 | if (isset($pProgresskey) && isset($pGetprogress)) { 90 | $aCurrent = \OC::$server->getCache()->get($pProgresskey); 91 | $aCurrent = json_decode($aCurrent); 92 | 93 | $numVC = (isset($aCurrent->{'all'})?$aCurrent->{'all'}:0); 94 | $currentVCCount = (isset($aCurrent->{'current'})?$aCurrent->{'current'}:0); 95 | $currentFn = (isset($aCurrent->{'currentFn'})?$aCurrent->{'currentFn'}:''); 96 | $percent = (isset($aCurrent->{'percent'})?$aCurrent->{'percent'}:''); 97 | 98 | if($percent ==''){ 99 | $percent = 0; 100 | } 101 | $params = [ 102 | 'status' => 'success', 103 | 'percent' =>$percent , 104 | 'currentmsg' => $currentFn.' '.$percent.'% ('.$currentVCCount.'/'.$numVC.')' 105 | ]; 106 | 107 | $response = new JSONResponse($params); 108 | return $response; 109 | } 110 | 111 | if($pIsDragged === 'true') { 112 | //OCP\JSON::error(array('error'=>'404')); 113 | $file = explode(',', $pFile); 114 | $file = end($file); 115 | $file = base64_decode($file); 116 | }else{ 117 | $file = \OC\Files\Filesystem::file_get_contents($pPath . '/' . $pFile); 118 | } 119 | 120 | if(!$file) { 121 | $params = [ 122 | 'status' => 'error', 123 | 'error' => '404', 124 | ]; 125 | $response = new JSONResponse($params); 126 | return $response; 127 | 128 | } 129 | $file = \Sabre\VObject\StringUtil::convertToUTF8($file); 130 | 131 | $import = new Import($file); 132 | $import->setUserID($this->userId); 133 | $import->setProgresskey($pProgresskey); 134 | $import->enableProgressCache(); 135 | 136 | \OCP\Util::writeLog($this->appName,' PROG: '.$pProgresskey, \OCP\Util::DEBUG); 137 | 138 | if(!$import->isValid()) { 139 | $params = [ 140 | 'status' => 'error', 141 | 'error' => 'notvalid', 142 | ]; 143 | $response = new JSONResponse($params); 144 | return $response; 145 | } 146 | 147 | $newAddressbook = false; 148 | 149 | if($pMethod == 'new') { 150 | $id = Addressbook::add($this->userId, $pAddressbook); 151 | 152 | if($id) { 153 | Addressbook::setActive($id, 1); 154 | $newAddressbook = true; 155 | } 156 | }else{ 157 | $id= $pId; 158 | Addressbook::find($id); 159 | 160 | $import->setOverwrite($pOverwrite); 161 | } 162 | //\OCP\Util::writeLog($this->appName,' METHOD: '.$pMethod.'ID:'.$id, \OCP\Util::DEBUG); 163 | $import->setAddressbookId($id); 164 | try{ 165 | $import->import(); 166 | 167 | 168 | }catch (\Exception $e) { 169 | $params = [ 170 | 'status' => 'error', 171 | 'message' => $this->l10n -> t('Import failed'), 172 | ]; 173 | $response = new JSONResponse($params); 174 | return $response; 175 | } 176 | $count = $import->getCount(); 177 | $errorCount = $import->getErrorCount(); 178 | if($count == 0) { 179 | if($newAddressbook) { 180 | Addressbook::delete($id); 181 | } 182 | $params = [ 183 | 'status' => 'error', 184 | 'message' => $this->l10n -> t('The file contained either no vcard or all vcards are already saved in your addessbook.'), 185 | ]; 186 | $response = new JSONResponse($params); 187 | return $response; 188 | }else{ 189 | if($newAddressbook) { 190 | $params = [ 191 | 'status' => 'success', 192 | 'message' => $count . ' ' . $this->l10n -> t('vcards has been saved in the new addressbook'). ' ' . strip_tags($pAddressbook), 193 | ]; 194 | 195 | $response = new JSONResponse($params); 196 | return $response; 197 | }else{ 198 | $params = [ 199 | 'status' => 'success', 200 | 'message' => $errorCount.' Error - '.$count . ' ' . $this->l10n -> t('vcards has been saved in your addressbook'), 201 | ]; 202 | 203 | $response = new JSONResponse($params); 204 | return $response; 205 | } 206 | } 207 | 208 | } 209 | 210 | /** 211 | * @NoAdminRequired 212 | */ 213 | public function checkAddressbookExists() { 214 | $pAddressbook = $this -> params('addrbookname'); 215 | $id = Addressbook::checkIfExist($pAddressbook); 216 | //\OCP\Util::writeLog($this->appName,'CHECK: '.$id, \OCP\Util::DEBUG); 217 | if($id){ 218 | $params = [ 219 | 'status' => 'success', 220 | 'message' => 'exists' 221 | ]; 222 | $response = new JSONResponse($params); 223 | return $response; 224 | } 225 | } 226 | 227 | } -------------------------------------------------------------------------------- /controller/pagecontroller.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\Controller; 24 | 25 | use \OCP\AppFramework\Controller; 26 | use \OCP\AppFramework\Http\TemplateResponse; 27 | use \OCP\IRequest; 28 | use \OCP\IL10N; 29 | use \OCP\IConfig; 30 | use OCA\ContactsPlus\Addressbook; 31 | use OCA\ContactsPlus\App as ContactsApp; 32 | /** 33 | * Controller class for main page. 34 | */ 35 | class PageController extends Controller { 36 | 37 | private $userId; 38 | private $l10n; 39 | private $configInfo; 40 | 41 | 42 | public function __construct($appName, IRequest $request, $userId, IL10N $l10n, IConfig $settings) { 43 | parent::__construct($appName, $request); 44 | $this -> userId = $userId; 45 | $this->l10n = $l10n; 46 | $this->configInfo = $settings; 47 | } 48 | 49 | /** 50 | * @NoAdminRequired 51 | * @NoCSRFRequired 52 | */ 53 | public function index() { 54 | 55 | 56 | 57 | $iosSupport= $this->configInfo->getUserValue($this->userId, $this->appName,'iossupport'); 58 | 59 | $activeView = $this->configInfo->getUserValue($this->userId, $this->appName,'view','listview'); 60 | 61 | $lastSelectedBook = $this->configInfo->getUserValue($this->userId, $this->appName, 'currentbook', 0); 62 | 63 | $maxUploadFilesize = \OCP\Util::maxUploadFilesize('/'); 64 | 65 | $addressbooks = Addressbook::all($this->userId); 66 | if(count($addressbooks) == 0) { 67 | Addressbook::addDefault($this->userId); 68 | $addressbooks = Addressbook::all($this->userId); 69 | } 70 | //ContactsApp::addingDummyContacts(1000); 71 | 72 | $params = [ 73 | 'uploadMaxFilesize' => $maxUploadFilesize, 74 | 'uploadMaxHumanFilesize' => \OCP\Util::humanFileSize($maxUploadFilesize), 75 | 'iossupport' => $iosSupport, 76 | 'addressbooks' => $addressbooks, 77 | 'activeView' => $activeView, 78 | 'lastSelectedBook' => $lastSelectedBook, 79 | ]; 80 | 81 | $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); 82 | $csp->addAllowedImageDomain('\'self\''); 83 | $csp->addAllowedImageDomain('data:'); 84 | $csp->addAllowedImageDomain('*'); 85 | $csp->addAllowedFrameDomain('*'); 86 | $response = new TemplateResponse($this->appName, 'index'); 87 | $response->setContentSecurityPolicy($csp); 88 | $response->setParams($params); 89 | 90 | return $response; 91 | } 92 | } -------------------------------------------------------------------------------- /controller/photocontroller.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\ContactsPlus\Controller; 24 | 25 | use \OCP\AppFramework\Controller; 26 | use \OCP\AppFramework\Http\JSONResponse; 27 | use \OCP\AppFramework\Http\TemplateResponse; 28 | use \OCP\IRequest; 29 | use OCA\ContactsPlus\App as ContactsApp; 30 | use OCA\ContactsPlus\VCard; 31 | 32 | class PhotoController extends Controller { 33 | 34 | private $l10n; 35 | 36 | public function __construct($appName, IRequest $request, $l10n) { 37 | parent::__construct($appName, $request); 38 | 39 | $this->l10n = $l10n; 40 | 41 | } 42 | 43 | /** 44 | * @NoAdminRequired 45 | */ 46 | 47 | public function cropPhoto(){ 48 | 49 | $id = $this -> params('id'); 50 | $tmpkey = $this -> params('tmpkey'); 51 | 52 | $params=array( 53 | 'tmpkey' => $tmpkey, 54 | 'id' => $id, 55 | ); 56 | $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); 57 | $csp->addAllowedImageDomain('data:'); 58 | 59 | $response = new TemplateResponse($this->appName, 'part.cropphoto', $params, ''); 60 | $response->setContentSecurityPolicy($csp); 61 | 62 | return $response; 63 | } 64 | 65 | public function clearPhotoCache(){ 66 | //$id = $this -> params('id'); 67 | $tmpkey = $this -> params('tmpkey'); 68 | $data = \OC::$server->getCache()->get($tmpkey); 69 | //\OCP\Util::writeLog('pinit','cleared.'.$tmpkey,\OCP\Util::DEBUG); 70 | if($data) { 71 | 72 | \OC::$server->getCache()->remove($tmpkey); 73 | } 74 | } 75 | 76 | /** 77 | * @NoAdminRequired 78 | */ 79 | public function saveCropPhotoContact(){ 80 | $id = $this -> params('id'); 81 | $tmpkey = $this -> params('tmpkey'); 82 | $x = $this -> params('x1', 0); 83 | $y = $this -> params('y1', 0); 84 | $w = $this -> params('w', -1); 85 | $h = $this -> params('h', -1); 86 | 87 | $image = null; 88 | 89 | //\OCP\Util::writeLog($this->appName,'TMP:'.$tmpkey,\OCP\Util::DEBUG); 90 | 91 | $data = \OC::$server->getCache()->get($tmpkey); 92 | if($data) { 93 | \OC::$server->getCache()->remove($tmpkey); 94 | $image = new \OCP\Image(); 95 | if($image->loadFromdata($data)) { 96 | $w = ($w !== -1 ? $w : $image->width()); 97 | $h = ($h !== -1 ? $h : $image->height()); 98 | 99 | if($image->crop($x, $y, $w, $h)) { 100 | if(($image->width() <= 400 && $image->height() <= 400) || $image->resize(400)) { 101 | 102 | $vcard = ContactsApp::getContactVCard($id); 103 | if(!$vcard) { 104 | \OC::$server->getCache()->remove($tmpkey); 105 | } 106 | if(isset($vcard->PHOTO)) { 107 | $property =$vcard->select('PHOTO'); 108 | if(!$property) { 109 | \OC::$server->getCache()->remove($tmpkey); 110 | //bailOut(OCA\Kontakte\App::$l10n->t('Error getting PHOTO property.')); 111 | } 112 | unset($vcard->PHOTO); 113 | $vcard->add('PHOTO', 114 | base64_decode($image->__toString()), array('ENCODING' => 'b', 115 | 'TYPE' => $image->mimeType())); 116 | 117 | }else{ 118 | $vcard->add('PHOTO', 119 | base64_decode($image->__toString()), array('ENCODING' => 'b', 120 | 'TYPE' => $image->mimeType())); 121 | } 122 | $now = new \DateTime; 123 | $vcard->REV=$now->format(\DateTime::W3C); 124 | 125 | if(!VCard::edit($id, $vcard)) { 126 | //bailOut(OCA\Kontakte\App::$l10n->t('Error saving contact.')); 127 | } 128 | $imgString=$image->__toString(); 129 | $result=[ 130 | 'status' => 'success', 131 | 'data' =>[ 132 | 'id' => $id, 133 | 'width' => $image->width(), 134 | 'height' => $image->height(), 135 | 'dataimg' =>$imgString, 136 | 'mimetype' =>$image->mimeType(), 137 | 'lastmodified' => ContactsApp::lastModified($vcard)->format('U') 138 | ] 139 | ]; 140 | 141 | 142 | \OC::$server->getCache()->remove($tmpkey); 143 | \OC::$server->getCache()->remove('show-contacts-foto-' . $id); 144 | \OC::$server->getCache()->set($tmpkey, $image->data(), 600); 145 | \OC::$server->getCache()->set('show-contacts-foto-' . $id, $image->data(), 600); 146 | $response = new JSONResponse(); 147 | $response -> setData($result); 148 | 149 | return $response; 150 | } 151 | } 152 | } 153 | } 154 | 155 | 156 | } 157 | 158 | /** 159 | * @NoAdminRequired 160 | */ 161 | public function getImageFromCloud(){ 162 | $id = $this -> params('id'); 163 | $path = $this -> params('path'); 164 | 165 | $localpath = \OC\Files\Filesystem::getLocalFile($path); 166 | $tmpkey = 'editphoto' ; 167 | $size = getimagesize($localpath, $info); 168 | //$exif = @exif_read_data($localpath); 169 | $image = new \OCP\Image(); 170 | $image -> loadFromFile($localpath); 171 | if ($image -> width() > 400 || $image -> height() > 400) { 172 | $image -> resize(400); 173 | } 174 | $image -> fixOrientation(); 175 | 176 | $imgString = $image -> __toString(); 177 | $imgMimeType = $image -> mimeType(); 178 | \OC::$server->getCache()->remove($tmpkey); 179 | if (\OC::$server->getCache()->set($tmpkey, $image -> data(), 600)) { 180 | 181 | $resultData = array( 182 | 'id' =>$id, 183 | 'tmp' => $tmpkey, 184 | 'imgdata' => $imgString, 185 | 'mimetype' => $imgMimeType, 186 | ); 187 | 188 | $response = new JSONResponse(); 189 | $response -> setData($resultData); 190 | 191 | return $response; 192 | 193 | } 194 | 195 | } 196 | 197 | /** 198 | * @NoAdminRequired 199 | */ 200 | public function deletePhoto(){ 201 | $id = $this -> params('id'); 202 | $vcard = ContactsApp::getContactVCard($id); 203 | unset($vcard->PHOTO); 204 | 205 | VCard::edit($id, $vcard); 206 | 207 | $result = [ 208 | 'status' => 'success', 209 | 'data' =>[ 210 | 'id' => $id 211 | ], 212 | ]; 213 | 214 | $response = new JSONResponse(); 215 | $response -> setData($result); 216 | return $response; 217 | 218 | 219 | } 220 | 221 | 222 | /** 223 | * @NoAdminRequired 224 | */ 225 | public function uploadPhoto(){ 226 | //$type = $this->request->getHeader('Content-Type'); 227 | $id = $this -> params('id'); 228 | $file = $this->request->getUploadedFile('imagefile'); 229 | 230 | $error = $file['error']; 231 | if($error !== UPLOAD_ERR_OK) { 232 | $errors = array( 233 | 0=>$this->l10n->t("There is no error, the file uploaded with success"), 234 | 1=>$this->l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'), 235 | 2=>$this->l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"), 236 | 3=>$this->l10n->t("The uploaded file was only partially uploaded"), 237 | 4=>$this->l10n->t("No file was uploaded"), 238 | 6=>$this->l10n->t("Missing a temporary folder") 239 | ); 240 | \OCP\Util::writeLog($this->appName,'Uploaderror: '.$errors[$error],\OCP\Util::DEBUG); 241 | } 242 | 243 | if(file_exists($file['tmp_name'])) { 244 | $tmpkey = 'editphoto' ; 245 | $size = getimagesize($file['tmp_name'], $info); 246 | //$exif = @exif_read_data($file['tmp_name']); 247 | $image = new \OCP\Image(); 248 | if($image->loadFromFile($file['tmp_name'])) { 249 | 250 | if($image->width() > 400 || $image->height() > 400) { 251 | $image->resize(400); // Prettier resizing than with browser and saves bandwidth. 252 | } 253 | if(!$image->fixOrientation()) { // No fatal error so we don't bail out. 254 | \OCP\Util::writeLog($this->appName,'Couldn\'t save correct image orientation: '.$tmpkey,\OCP\Util::DEBUG); 255 | } 256 | \OC::$server->getCache()->remove($tmpkey); 257 | if(\OC::$server->getCache()->set($tmpkey, $image->data(), 600)) { 258 | $imgString=$image->__toString(); 259 | 260 | $resultData=array( 261 | 'mime'=>$file['type'], 262 | 'size'=>$file['size'], 263 | 'name'=>$file['name'], 264 | 'id'=>$id, 265 | 'tmp'=>$tmpkey, 266 | 'imgdata' =>$imgString, 267 | ); 268 | $response = new JSONResponse(); 269 | $response -> setData($resultData); 270 | 271 | return $response; 272 | 273 | } 274 | 275 | 276 | } 277 | 278 | 279 | 280 | } 281 | 282 | 283 | } 284 | 285 | 286 | } -------------------------------------------------------------------------------- /css/3rdparty/MarkerCluster.Default.css: -------------------------------------------------------------------------------- 1 | .marker-cluster-small { 2 | background-color: rgba(181, 226, 140, 0.6); 3 | } 4 | .marker-cluster-small div { 5 | background-color: rgba(110, 204, 57, 0.6); 6 | } 7 | 8 | .marker-cluster-medium { 9 | background-color: rgba(241, 211, 87, 0.6); 10 | } 11 | .marker-cluster-medium div { 12 | background-color: rgba(240, 194, 12, 0.6); 13 | } 14 | 15 | .marker-cluster-large { 16 | background-color: rgba(253, 156, 115, 0.6); 17 | } 18 | .marker-cluster-large div { 19 | background-color: rgba(241, 128, 23, 0.6); 20 | } 21 | 22 | /* IE 6-8 fallback colors */ 23 | .leaflet-oldie .marker-cluster-small { 24 | background-color: rgb(181, 226, 140); 25 | } 26 | .leaflet-oldie .marker-cluster-small div { 27 | background-color: rgb(110, 204, 57); 28 | } 29 | 30 | .leaflet-oldie .marker-cluster-medium { 31 | background-color: rgb(241, 211, 87); 32 | } 33 | .leaflet-oldie .marker-cluster-medium div { 34 | background-color: rgb(240, 194, 12); 35 | } 36 | 37 | .leaflet-oldie .marker-cluster-large { 38 | background-color: rgb(253, 156, 115); 39 | } 40 | .leaflet-oldie .marker-cluster-large div { 41 | background-color: rgb(241, 128, 23); 42 | } 43 | 44 | .marker-cluster { 45 | background-clip: padding-box; 46 | border-radius: 20px; 47 | } 48 | .marker-cluster div { 49 | width: 30px; 50 | height: 30px; 51 | margin-left: 5px; 52 | margin-top: 5px; 53 | 54 | text-align: center; 55 | border-radius: 15px; 56 | font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif; 57 | } 58 | .marker-cluster span { 59 | line-height: 30px; 60 | } -------------------------------------------------------------------------------- /css/3rdparty/MarkerCluster.Default.ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6-8 fallback colors */ 2 | .marker-cluster-small { 3 | background-color: rgb(181, 226, 140); 4 | } 5 | .marker-cluster-small div { 6 | background-color: rgb(110, 204, 57); 7 | } 8 | 9 | .marker-cluster-medium { 10 | background-color: rgb(241, 211, 87); 11 | } 12 | .marker-cluster-medium div { 13 | background-color: rgb(240, 194, 12); 14 | } 15 | 16 | .marker-cluster-large { 17 | background-color: rgb(253, 156, 115); 18 | } 19 | .marker-cluster-large div { 20 | background-color: rgb(241, 128, 23); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /css/3rdparty/MarkerCluster.css: -------------------------------------------------------------------------------- 1 | .leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow { 2 | -webkit-transition: -webkit-transform 0.2s ease-out, opacity 0.2s ease-in; 3 | -moz-transition: -moz-transform 0.2s ease-out, opacity 0.2s ease-in; 4 | -o-transition: -o-transform 0.2s ease-out, opacity 0.2s ease-in; 5 | transition: transform 0.2s ease-out, opacity 0.2s ease-in; 6 | } 7 | -------------------------------------------------------------------------------- /css/3rdparty/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Entypo 5 | 6 | Copyright (C) 2012 by Daniel Bruce 7 | 8 | Author: Daniel Bruce 9 | License: SIL (http://scripts.sil.org/OFL) 10 | Homepage: http://www.entypo.com 11 | 12 | 13 | ## Font Awesome 14 | 15 | Copyright (C) 2012 by Dave Gandy 16 | 17 | Author: Dave Gandy 18 | License: SIL () 19 | Homepage: http://fortawesome.github.com/Font-Awesome/ 20 | 21 | 22 | -------------------------------------------------------------------------------- /css/3rdparty/fontello/README.txt: -------------------------------------------------------------------------------- 1 | This webfont is generated by http://fontello.com open source project. 2 | 3 | 4 | ================================================================================ 5 | Please, note, that you should obey original font licences, used to make this 6 | webfont pack. Details available in LICENSE.txt file. 7 | 8 | - Usually, it's enough to publish content of LICENSE.txt file somewhere on your 9 | site in "About" section. 10 | 11 | - If your project is open-source, usually, it will be ok to make LICENSE.txt 12 | file publically available in your repository. 13 | 14 | - Fonts, used in Fontello, don't require a clickable link on your site. 15 | But any kind of additional authors crediting is welcome. 16 | ================================================================================ 17 | 18 | 19 | Comments on archive content 20 | --------------------------- 21 | 22 | - /font/* - fonts in different formats 23 | 24 | - /css/* - different kinds of css, for all situations. Should be ok with 25 | twitter bootstrap. Also, you can skip style and assign icon classes 26 | directly to text elements, if you don't mind about IE7. 27 | 28 | - demo.html - demo file, to show your webfont content 29 | 30 | - LICENSE.txt - license info about source fonts, used to build your one. 31 | 32 | - config.json - keeps your settings. You can import it back into fontello 33 | anytime, to continue your work 34 | 35 | 36 | Why so many CSS files ? 37 | ----------------------- 38 | 39 | Because we like to fit all your needs :) 40 | 41 | - basic file, .css - is usually enough, it contains @font-face 42 | and character code definitions 43 | 44 | - *-ie7.css - if you need IE7 support, but still don't wish to put char codes 45 | directly into html 46 | 47 | - *-codes.css and *-ie7-codes.css - if you like to use your own @font-face 48 | rules, but still wish to benefit from css generation. That can be very 49 | convenient for automated asset build systems. When you need to update font - 50 | no need to manually edit files, just override old version with archive 51 | content. See fontello source code for examples. 52 | 53 | - *-embedded.css - basic css file, but with embedded WOFF font, to avoid 54 | CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain. 55 | We strongly recommend to resolve this issue by `Access-Control-Allow-Origin` 56 | server headers. But if you ok with dirty hack - this file is for you. Note, 57 | that data url moved to separate @font-face to avoid problems with 2 | 3 | 4 | Copyright (C) 2015 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /css/3rdparty/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /css/3rdparty/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/fontello/font/fontello.woff -------------------------------------------------------------------------------- /css/3rdparty/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/layers-2x.png -------------------------------------------------------------------------------- /css/3rdparty/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/layers.png -------------------------------------------------------------------------------- /css/3rdparty/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/marker-icon-2x.png -------------------------------------------------------------------------------- /css/3rdparty/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/marker-icon.png -------------------------------------------------------------------------------- /css/3rdparty/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/marker-shadow.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-matte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-matte.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-matte@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-matte@2x.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-plain.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-shadow.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-shadow@2x.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-soft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-soft.png -------------------------------------------------------------------------------- /css/3rdparty/images/markers-soft@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/css/3rdparty/images/markers-soft@2x.png -------------------------------------------------------------------------------- /css/3rdparty/jquery.webui-popover.css: -------------------------------------------------------------------------------- 1 | /* webui popover */ 2 | .webui-popover { 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | z-index: 500; 7 | display: none; 8 | width: 276px; 9 | min-height: 32px; 10 | text-align: left; 11 | white-space: normal; 12 | background-color: #ffffff; 13 | background-clip: padding-box; 14 | background: white; 15 | color: #333333; 16 | border-radius: 3px; 17 | box-shadow: 0 0 7px #888888; 18 | padding: 15px; 19 | padding-bottom:5px; 20 | } 21 | .webui-popover.top, 22 | .webui-popover.top-left, 23 | .webui-popover.top-right { 24 | margin-top: -10px; 25 | } 26 | .webui-popover.right, 27 | .webui-popover.right-top, 28 | .webui-popover.right-bottom { 29 | margin-left: 10px; 30 | } 31 | .webui-popover.bottom, 32 | .webui-popover.bottom-left, 33 | .webui-popover.bottom-right { 34 | margin-top: 10px; 35 | } 36 | .webui-popover.left, 37 | .webui-popover.left-top, 38 | .webui-popover.left-bottom { 39 | margin-left: -10px; 40 | } 41 | .webui-popover.pop { 42 | -webkit-transform: scale(0.8); 43 | -o-transform: scale(0.8); 44 | transform: scale(0.8); 45 | transition: transform 0.15s cubic-bezier(0.3, 0, 0, 1.5); 46 | } 47 | .webui-popover.fade { 48 | transition: opacity .15s linear; 49 | } 50 | .webui-popover.in { 51 | -webkit-transform: none; 52 | -o-transform: none; 53 | transform: none; 54 | opacity: 1; 55 | } 56 | .webui-popover-inner .close { 57 | font-family: arial; 58 | margin: 0; 59 | float: right; 60 | position:absolute; 61 | top:7px; 62 | right:7px; 63 | font-size: 26px; 64 | font-weight: bold; 65 | line-height: 20px; 66 | color: #000000; 67 | text-shadow: 0 1px 0 #fff; 68 | opacity: 0.4; 69 | filter: alpha(opacity=20); 70 | text-decoration: none; 71 | } 72 | .webui-popover-inner .close:hover, 73 | .webui-popover-inner .close:focus { 74 | opacity: 0.8; 75 | filter: alpha(opacity=80); 76 | } 77 | .webui-popover-title { 78 | padding: 8px 14px; 79 | margin: 0; 80 | font-size: 14px; 81 | font-weight: bold; 82 | line-height: 18px; 83 | background-color: #ffffff; 84 | border-bottom: 1px solid #f2f2f2; 85 | border-radius: 5px 5px 0 0; 86 | } 87 | .webui-popover-content { 88 | padding: 9px 14px; 89 | overflow: auto; 90 | } 91 | .webui-popover-inverse { 92 | background-color: #333333; 93 | color: #eeeeee; 94 | } 95 | .webui-popover-inverse .webui-popover-title { 96 | background: #333333; 97 | border-bottom: 1px solid #3b3b3b; 98 | color: #eeeeee; 99 | } 100 | .webui-no-padding .webui-popover-content { 101 | padding: 0; 102 | } 103 | .webui-no-padding .list-group-item { 104 | border-right: none; 105 | border-left: none; 106 | } 107 | .webui-no-padding .list-group-item:first-child { 108 | border-top: 0; 109 | } 110 | .webui-no-padding .list-group-item:last-child { 111 | border-bottom: 0; 112 | } 113 | .webui-popover > .arrow, 114 | .webui-popover > .arrow:after { 115 | position: absolute; 116 | display: block; 117 | width: 0; 118 | height: 0; 119 | border-color: transparent; 120 | border-style: solid; 121 | } 122 | .webui-popover > .arrow { 123 | border-width: 11px; 124 | } 125 | .webui-popover > .arrow:after { 126 | border-width: 10px; 127 | content: ""; 128 | } 129 | .webui-popover.top > .arrow, 130 | .webui-popover.top-right > .arrow, 131 | .webui-popover.top-left > .arrow { 132 | bottom: -11px; 133 | left: 50%; 134 | margin-left: -11px; 135 | border-top-color: #ccc; 136 | 137 | border-bottom-width: 0; 138 | } 139 | .webui-popover.top > .arrow:after, 140 | .webui-popover.top-right > .arrow:after, 141 | .webui-popover.top-left > .arrow:after { 142 | content: " "; 143 | bottom: 1px; 144 | margin-left: -10px; 145 | border-top-color: #ffffff; 146 | border-bottom-width: 0; 147 | } 148 | .webui-popover.right > .arrow, 149 | .webui-popover.right-top > .arrow, 150 | .webui-popover.right-bottom > .arrow { 151 | top: 50%; 152 | left: -11px; 153 | margin-top: -11px; 154 | border-left-width: 0; 155 | border-right-color: #ccc; 156 | 157 | } 158 | .webui-popover.right > .arrow:after, 159 | .webui-popover.right-top > .arrow:after, 160 | .webui-popover.right-bottom > .arrow:after { 161 | content: " "; 162 | left: 1px; 163 | bottom: -10px; 164 | border-left-width: 0; 165 | border-right-color: #ffffff; 166 | } 167 | .webui-popover.bottom > .arrow, 168 | .webui-popover.bottom-right > .arrow, 169 | .webui-popover.bottom-left > .arrow { 170 | top: -11px; 171 | left: 50%; 172 | margin-left: -11px; 173 | border-bottom-color: #ccc; 174 | border-bottom-color: rgba(0, 0, 0, 0.25); 175 | border-top-width: 0; 176 | } 177 | .webui-popover.bottom > .arrow:after, 178 | .webui-popover.bottom-right > .arrow:after, 179 | .webui-popover.bottom-left > .arrow:after { 180 | content: " "; 181 | top: 1px; 182 | margin-left: -10px; 183 | border-bottom-color: #ffffff; 184 | border-top-width: 0; 185 | } 186 | .webui-popover.left > .arrow, 187 | .webui-popover.left-top > .arrow, 188 | .webui-popover.left-bottom > .arrow { 189 | top: 50%; 190 | right: -11px; 191 | margin-top: -11px; 192 | border-right-width: 0; 193 | border-left-color: #ccc; 194 | border-left-color: rgba(0, 0, 0, 0.25); 195 | } 196 | .webui-popover.left > .arrow:after, 197 | .webui-popover.left-top > .arrow:after, 198 | .webui-popover.left-bottom > .arrow:after { 199 | content: " "; 200 | right: 1px; 201 | border-right-width: 0; 202 | border-left-color: #ffffff; 203 | bottom: -10px; 204 | } 205 | .webui-popover-inverse.top > .arrow, 206 | .webui-popover-inverse.top-left > .arrow, 207 | .webui-popover-inverse.top-right > .arrow, 208 | .webui-popover-inverse.top > .arrow:after, 209 | .webui-popover-inverse.top-left > .arrow:after, 210 | .webui-popover-inverse.top-right > .arrow:after { 211 | border-top-color: #ccc; 212 | } 213 | .webui-popover-inverse.right > .arrow, 214 | .webui-popover-inverse.right-top > .arrow, 215 | .webui-popover-inverse.right-bottom > .arrow, 216 | .webui-popover-inverse.right > .arrow:after, 217 | .webui-popover-inverse.right-top > .arrow:after, 218 | .webui-popover-inverse.right-bottom > .arrow:after { 219 | border-right-color: #ccc; 220 | } 221 | .webui-popover-inverse.bottom > .arrow, 222 | .webui-popover-inverse.bottom-left > .arrow, 223 | .webui-popover-inverse.bottom-right > .arrow, 224 | .webui-popover-inverse.bottom > .arrow:after, 225 | .webui-popover-inverse.bottom-left > .arrow:after, 226 | .webui-popover-inverse.bottom-right > .arrow:after { 227 | border-bottom-color: #ccc; 228 | } 229 | .webui-popover-inverse.left > .arrow, 230 | .webui-popover-inverse.left-top > .arrow, 231 | .webui-popover-inverse.left-bottom > .arrow, 232 | .webui-popover-inverse.left > .arrow:after, 233 | .webui-popover-inverse.left-top > .arrow:after, 234 | .webui-popover-inverse.left-bottom > .arrow:after { 235 | border-left-color: #ccc; 236 | } 237 | .webui-popover i.icon-refresh:before { 238 | content: ""; 239 | } 240 | .webui-popover i.icon-refresh { 241 | display: block; 242 | width: 30px; 243 | height: 30px; 244 | font-size: 20px; 245 | top: 50%; 246 | left: 50%; 247 | position: absolute; 248 | margin-left: -15px; 249 | margin-right: -15px; 250 | background: url(../../img/loading.gif) no-repeat; 251 | } 252 | @-webkit-keyframes rotate { 253 | 100% { 254 | -webkit-transform: rotate(360deg); 255 | } 256 | } 257 | @keyframes rotate { 258 | 100% { 259 | transform: rotate(360deg); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /css/3rdparty/leaflet.awesome-markers.css: -------------------------------------------------------------------------------- 1 | /* 2 | Author: L. Voogdt 3 | License: MIT 4 | Version: 1.0 5 | */ 6 | 7 | /* Marker setup */ 8 | .awesome-marker { 9 | background: url('./images/markers-soft.png') no-repeat 0 0; 10 | width: 35px; 11 | height: 46px; 12 | position:absolute; 13 | left:0; 14 | top:0; 15 | display: block; 16 | text-align: center; 17 | } 18 | 19 | .awesome-marker-shadow { 20 | background: url('./images/markers-shadow.png') no-repeat 0 0; 21 | width: 36px; 22 | height: 16px; 23 | } 24 | 25 | /* Retina displays */ 26 | @media (min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2), 27 | (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5),(min-resolution: 1.5dppx) { 28 | .awesome-marker { 29 | background-image: url('./images/markers-soft@2x.png'); 30 | background-size: 720px 46px; 31 | } 32 | .awesome-marker-shadow { 33 | background-image: url('./images/markers-shadow@2x.png'); 34 | background-size: 35px 16px; 35 | } 36 | } 37 | 38 | .awesome-marker i { 39 | color: #333; 40 | margin-top: 10px; 41 | display: inline-block; 42 | font-size: 14px; 43 | } 44 | 45 | .awesome-marker .icon-white { 46 | color: #fff; 47 | } 48 | 49 | /* Colors */ 50 | .awesome-marker-icon-red { 51 | background-position: 0 0; 52 | } 53 | 54 | .awesome-marker-icon-darkred { 55 | background-position: -180px 0; 56 | } 57 | 58 | .awesome-marker-icon-lightred { 59 | background-position: -360px 0; 60 | } 61 | 62 | .awesome-marker-icon-orange { 63 | background-position: -36px 0; 64 | } 65 | 66 | .awesome-marker-icon-beige { 67 | background-position: -396px 0; 68 | } 69 | 70 | .awesome-marker-icon-green { 71 | background-position: -72px 0; 72 | } 73 | 74 | .awesome-marker-icon-darkgreen { 75 | background-position: -252px 0; 76 | } 77 | 78 | .awesome-marker-icon-lightgreen { 79 | background-position: -432px 0; 80 | } 81 | 82 | .awesome-marker-icon-blue { 83 | background-position: -108px 0; 84 | } 85 | 86 | .awesome-marker-icon-darkblue { 87 | background-position: -216px 0; 88 | } 89 | 90 | .awesome-marker-icon-lightblue { 91 | background-position: -468px 0; 92 | } 93 | 94 | .awesome-marker-icon-purple { 95 | background-position: -144px 0; 96 | } 97 | 98 | .awesome-marker-icon-darkpurple { 99 | background-position: -288px 0; 100 | } 101 | 102 | .awesome-marker-icon-pink { 103 | background-position: -504px 0; 104 | } 105 | 106 | .awesome-marker-icon-cadetblue { 107 | background-position: -324px 0; 108 | } 109 | 110 | .awesome-marker-icon-white { 111 | background-position: -574px 0; 112 | } 113 | 114 | .awesome-marker-icon-gray { 115 | background-position: -648px 0; 116 | } 117 | 118 | .awesome-marker-icon-lightgray { 119 | background-position: -612px 0; 120 | } 121 | 122 | .awesome-marker-icon-black { 123 | background-position: -682px 0; 124 | } 125 | -------------------------------------------------------------------------------- /css/import.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Georg Ehrke 3 | * This file is licensed under the Affero General Public License version 3 or 4 | * later. 5 | * See the COPYING-README file. 6 | */ 7 | #contacts_import_newaddrform, #contacts_import_mergewarning, #contacts_import_process, #contacts_import_done{display:none;} 8 | #contacts_import_process_message, #contacts_import_status, #contacts_import_form_message, #contacts_import_mergewarning{text-align:center;} 9 | #contacts_import_form_message{font-weight: bold;} 10 | #contacts_import_newaddressbook{width:94%;float:left;} 11 | #contacts_import_mergewarning{clear: both;} 12 | .contacts_import_warning{border-color: #fc3333;} 13 | #contacts_import_dialog{ 14 | padding:0; 15 | margin:0; 16 | } 17 | -------------------------------------------------------------------------------- /css/jquery.Jcrop.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.css v0.9.12 - MIT License */ 2 | /* 3 | The outer-most container in a typical Jcrop instance 4 | If you are having difficulty with formatting related to styles 5 | on a parent element, place any fixes here or in a like selector 6 | 7 | You can also style this element if you want to add a border, etc 8 | A better method for styling can be seen below with .jcrop-light 9 | (Add a class to the holder and style elements for that extended class) 10 | */ 11 | .jcrop-holder { 12 | direction: ltr; 13 | text-align: left; 14 | } 15 | /* Selection Border */ 16 | .jcrop-vline, 17 | .jcrop-hline { 18 | background: #ffffff url("../img/Jcrop.gif"); 19 | font-size: 0; 20 | position: absolute; 21 | } 22 | .jcrop-vline { 23 | height: 100%; 24 | width: 1px !important; 25 | } 26 | .jcrop-vline.right { 27 | right: 0; 28 | } 29 | .jcrop-hline { 30 | height: 1px !important; 31 | width: 100%; 32 | } 33 | .jcrop-hline.bottom { 34 | bottom: 0; 35 | } 36 | /* Invisible click targets */ 37 | .jcrop-tracker { 38 | height: 100%; 39 | width: 100%; 40 | /* "turn off" link highlight */ 41 | -webkit-tap-highlight-color: transparent; 42 | /* disable callout, image save panel */ 43 | -webkit-touch-callout: none; 44 | /* disable cut copy paste */ 45 | -webkit-user-select: none; 46 | } 47 | /* Selection Handles */ 48 | .jcrop-handle { 49 | background-color: #333333; 50 | border: 1px #eeeeee solid; 51 | width: 7px; 52 | height: 7px; 53 | font-size: 1px; 54 | } 55 | .jcrop-handle.ord-n { 56 | left: 50%; 57 | margin-left: -4px; 58 | margin-top: -4px; 59 | top: 0; 60 | } 61 | .jcrop-handle.ord-s { 62 | bottom: 0; 63 | left: 50%; 64 | margin-bottom: -4px; 65 | margin-left: -4px; 66 | } 67 | .jcrop-handle.ord-e { 68 | margin-right: -4px; 69 | margin-top: -4px; 70 | right: 0; 71 | top: 50%; 72 | } 73 | .jcrop-handle.ord-w { 74 | left: 0; 75 | margin-left: -4px; 76 | margin-top: -4px; 77 | top: 50%; 78 | } 79 | .jcrop-handle.ord-nw { 80 | left: 0; 81 | margin-left: -4px; 82 | margin-top: -4px; 83 | top: 0; 84 | } 85 | .jcrop-handle.ord-ne { 86 | margin-right: -4px; 87 | margin-top: -4px; 88 | right: 0; 89 | top: 0; 90 | } 91 | .jcrop-handle.ord-se { 92 | bottom: 0; 93 | margin-bottom: -4px; 94 | margin-right: -4px; 95 | right: 0; 96 | } 97 | .jcrop-handle.ord-sw { 98 | bottom: 0; 99 | left: 0; 100 | margin-bottom: -4px; 101 | margin-left: -4px; 102 | } 103 | /* Dragbars */ 104 | .jcrop-dragbar.ord-n, 105 | .jcrop-dragbar.ord-s { 106 | height: 7px; 107 | width: 100%; 108 | } 109 | .jcrop-dragbar.ord-e, 110 | .jcrop-dragbar.ord-w { 111 | height: 100%; 112 | width: 7px; 113 | } 114 | .jcrop-dragbar.ord-n { 115 | margin-top: -4px; 116 | } 117 | .jcrop-dragbar.ord-s { 118 | bottom: 0; 119 | margin-bottom: -4px; 120 | } 121 | .jcrop-dragbar.ord-e { 122 | margin-right: -4px; 123 | right: 0; 124 | } 125 | .jcrop-dragbar.ord-w { 126 | margin-left: -4px; 127 | } 128 | /* The "jcrop-light" class/extension */ 129 | .jcrop-light .jcrop-vline, 130 | .jcrop-light .jcrop-hline { 131 | background: #ffffff; 132 | filter: alpha(opacity=70) !important; 133 | opacity: .70!important; 134 | } 135 | .jcrop-light .jcrop-handle { 136 | -moz-border-radius: 3px; 137 | -webkit-border-radius: 3px; 138 | background-color: #000000; 139 | border-color: #ffffff; 140 | border-radius: 3px; 141 | } 142 | /* The "jcrop-dark" class/extension */ 143 | .jcrop-dark .jcrop-vline, 144 | .jcrop-dark .jcrop-hline { 145 | background: #000000; 146 | filter: alpha(opacity=70) !important; 147 | opacity: 0.7 !important; 148 | } 149 | .jcrop-dark .jcrop-handle { 150 | -moz-border-radius: 3px; 151 | -webkit-border-radius: 3px; 152 | background-color: #ffffff; 153 | border-color: #000000; 154 | border-radius: 3px; 155 | } 156 | /* Simple macro to turn off the antlines */ 157 | .solid-line .jcrop-vline, 158 | .solid-line .jcrop-hline { 159 | background: #ffffff; 160 | } 161 | /* Fix for twitter bootstrap et al. */ 162 | .jcrop-holder img, 163 | img.jcrop-preview { 164 | max-width: none; 165 | } 166 | -------------------------------------------------------------------------------- /http/imageresponse.php: -------------------------------------------------------------------------------- 1 | setImage($image); 26 | } 27 | } 28 | 29 | /** 30 | * @param OCP\Image $image 31 | */ 32 | public function setImage(\OCP\Image $image) { 33 | if (!$image -> valid()) { 34 | throw new \InvalidArgumentException(__METHOD__ . ' The image resource is not valid.'); 35 | } 36 | $this -> image = $image; 37 | $this -> addHeader('Content-Type', $image -> mimeType()); 38 | return $this; 39 | } 40 | 41 | public function render() { 42 | 43 | if (is_null($this -> image)) { 44 | throw new \BadMethodCallException(__METHOD__ . ' Image must be set either in constructor or with setImage()'); 45 | } 46 | return $this -> image ->data(); 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /img/Jcrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/Jcrop.gif -------------------------------------------------------------------------------- /img/contactsplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/contactsplus.png -------------------------------------------------------------------------------- /img/contactsplus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /img/edge-arrow-marker-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/edge-arrow-marker-black.png -------------------------------------------------------------------------------- /img/edge-arrow-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/edge-arrow-marker.png -------------------------------------------------------------------------------- /img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/favicon.png -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libasys/contactsplus/ab8b1d40ddbe5ba7ce5d218e4266e22979787bc1/img/loading.gif -------------------------------------------------------------------------------- /js/3rdparty/Leaflet.EdgeMarker.js: -------------------------------------------------------------------------------- 1 | (function (L) { 2 | 'use strict'; 3 | 4 | L.EdgeMarker = L.Class.extend({ 5 | 6 | options: { 7 | distanceOpacity: false, 8 | distanceOpacityFactor: 4, 9 | layerGroup: null, 10 | rotateIcons: true, 11 | icon: L.icon({ 12 | iconUrl: L.Icon.Default.imagePath + '/edge-arrow-marker.png', 13 | clickable: true, 14 | iconSize: [48, 48], 15 | iconAnchor: [24, 24] 16 | }) 17 | }, 18 | 19 | initialize: function (options) { 20 | L.setOptions(this, options); 21 | }, 22 | 23 | addTo: function (map) { 24 | this._map = map; 25 | 26 | // add a method to get applicable features 27 | L.extend(map, { 28 | _getFeatures: function () { 29 | var out = []; 30 | for (var l in this._layers) { 31 | if (typeof this._layers[l].getLatLng !== 'undefined') { 32 | out.push(this._layers[l]); 33 | } 34 | } 35 | return out; 36 | } 37 | }); 38 | 39 | map.on('move', this._addEdgeMarkers, this); 40 | map.on('viewreset', this._addEdgeMarkers, this); 41 | 42 | this._addEdgeMarkers(); 43 | 44 | map.addLayer(this); 45 | 46 | return this; 47 | }, 48 | 49 | onClick: function (e) { 50 | this._map.setView(e.target.options.latlng, this._map.getZoom()); 51 | }, 52 | 53 | onAdd: function () {}, 54 | 55 | _borderMarkerLayer: undefined, 56 | 57 | _addEdgeMarkers: function () { 58 | if (typeof this._borderMarkerLayer === 'undefined') { 59 | this._borderMarkerLayer = new L.LayerGroup(); 60 | } 61 | this._borderMarkerLayer.clearLayers(); 62 | 63 | var features = []; 64 | if (this.options.layerGroup != null) { 65 | features = this.options.layerGroup.getLayers(); 66 | } else { 67 | features = this._map._getFeatures(); 68 | } 69 | 70 | var mapPixelBounds = this._map.getSize(); 71 | 72 | var markerWidth = this.options.icon.options.iconSize[0]; 73 | var markerHeight = this.options.icon.options.iconSize[1]; 74 | 75 | for (var i = 0; i < features.length; i++) { 76 | 77 | var currentMarkerPosition = this._map.latLngToContainerPoint( 78 | features[i].getLatLng()); 79 | 80 | if (currentMarkerPosition.y < 0 || 81 | currentMarkerPosition.y > mapPixelBounds.y || 82 | currentMarkerPosition.x > mapPixelBounds.x || 83 | currentMarkerPosition.x < 0) { 84 | 85 | // get pos of marker 86 | var x = currentMarkerPosition.x; 87 | var y = currentMarkerPosition.y; 88 | var markerDistance; 89 | 90 | // bottom out 91 | if (currentMarkerPosition.y < 0) { 92 | y = 0 + markerHeight / 2; 93 | markerDistance = -currentMarkerPosition.y; 94 | // top out 95 | } else if (currentMarkerPosition.y > mapPixelBounds.y) { 96 | y = mapPixelBounds.y - markerHeight / 2; 97 | markerDistance = currentMarkerPosition.y - mapPixelBounds.y; 98 | } 99 | 100 | // right out 101 | if (currentMarkerPosition.x > mapPixelBounds.x) { 102 | x = mapPixelBounds.x - markerWidth / 2; 103 | markerDistance = currentMarkerPosition.x - mapPixelBounds.x; 104 | // left out 105 | } else if (currentMarkerPosition.x < 0) { 106 | x = 0 + markerWidth / 2; 107 | markerDistance = -currentMarkerPosition.x; 108 | } 109 | 110 | // change opacity on distance 111 | var newOptions = this.options; 112 | if (this.options.distanceOpacity) { 113 | newOptions.fillOpacity = (100 - (markerDistance / this.options.distanceOpacityFactor)) / 100; 114 | } 115 | 116 | // rotate markers 117 | if (this.options.rotateIcons) { 118 | var centerX = mapPixelBounds.x / 2; 119 | var centerY = mapPixelBounds.y / 2; 120 | var angle = Math.atan2(centerY - y, centerX - x) / Math.PI * 180; 121 | newOptions.angle = angle; 122 | } 123 | 124 | var ref = {latlng: features[i].getLatLng()}; 125 | newOptions = L.extend({}, newOptions, ref); 126 | 127 | var marker = L.rotatedMarker(this._map.containerPointToLatLng([x, y]), newOptions) 128 | .addTo(this._borderMarkerLayer); 129 | 130 | marker.on('click', this.onClick, marker); 131 | } 132 | } 133 | if (!this._map.hasLayer(this._borderMarkerLayer)) { 134 | this._borderMarkerLayer.addTo(this._map); 135 | } 136 | } 137 | }); 138 | 139 | 140 | /* 141 | * L.rotatedMarker class is taken from https://github.com/bbecquet/Leaflet.PolylineDecorator. 142 | */ 143 | L.RotatedMarker = L.Marker.extend({ 144 | options: { 145 | angle: 0 146 | }, 147 | 148 | _setPos: function (pos) { 149 | L.Marker.prototype._setPos.call(this, pos); 150 | 151 | if (L.DomUtil.TRANSFORM) { 152 | // use the CSS transform rule if available 153 | this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)'; 154 | } else if (L.Browser.ie) { 155 | // fallback for IE6, IE7, IE8 156 | var rad = this.options.angle * (Math.PI / 180), 157 | costheta = Math.cos(rad), 158 | sintheta = Math.sin(rad); 159 | this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + 160 | costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')'; 161 | } 162 | } 163 | }); 164 | 165 | L.rotatedMarker = function (pos, options) { 166 | return new L.RotatedMarker(pos, options); 167 | }; 168 | 169 | L.edgeMarker = function (options) { 170 | return new L.EdgeMarker(options); 171 | }; 172 | 173 | })(L); 174 | -------------------------------------------------------------------------------- /js/3rdparty/jquery-ui.drag-multiple.js: -------------------------------------------------------------------------------- 1 | /*! Drag Multiple Plugin - v0.1.1 - 2014-05-14 2 | * https://github.com/javadoug/jquery.drag-multiple 3 | * Copyright (c) 2014 Doug Ross; Licensed MIT */ 4 | /*globals jQuery */ 5 | (function ($) { 6 | 7 | "use strict"; 8 | 9 | var options = { 10 | 11 | // allow consumer to specify the selection 12 | items: function getSelectedItems() { 13 | return $(".ui-draggable.ui-selected"); 14 | }, 15 | 16 | // allow consumer to cancel drag multiple 17 | beforeStart: function beforeDragMultipleStart() { 18 | // make sure target is selected, otherwise deselect others 19 | if (!(this.is('.ui-draggable') && this.is('.ui-selected'))) { 20 | $(".ui-draggable").removeClass('ui-selected'); 21 | return false; 22 | } 23 | }, 24 | 25 | // notify consumer of drag multiple 26 | beforeDrag: $.noop, 27 | 28 | // notify consumer of drag multiple stop 29 | beforeStop: $.noop 30 | 31 | }; 32 | 33 | function preventDraggableRevert() { 34 | return false; 35 | } 36 | 37 | /** given an instance return the options hash */ 38 | function initOptions(instance) { 39 | return $.extend({}, options, instance.options.multiple); 40 | } 41 | 42 | function callback(handler, element, jqEvent, ui) { 43 | if ($.isFunction(handler)) { 44 | return handler.call(element, jqEvent, ui); 45 | } 46 | } 47 | 48 | function notifyBeforeStart(element, options, jqEvent, ui) { 49 | return callback(options.beforeStart, element, jqEvent, ui); 50 | } 51 | 52 | function notifyBeforeDrag(element, options, jqEvent, ui) { 53 | return callback(options.beforeDrag, element, jqEvent, ui); 54 | } 55 | 56 | function notifyBeforeStop(element, options, jqEvent, ui) { 57 | return callback(options.beforeStop, element, jqEvent, ui); 58 | } 59 | 60 | $.ui.plugin.add("draggable", "multiple", { 61 | 62 | /** initialize the selected elements for dragging as a group */ 63 | start: function (ev, ui) { 64 | 65 | var element, instance, selected, options; 66 | 67 | // the draggable element under the mouse 68 | element = this; 69 | 70 | // the draggable instance 71 | instance = element.data('draggable') || element.data('ui-draggable'); 72 | 73 | // initialize state 74 | instance.multiple = {}; 75 | 76 | // the consumer provided option overrides 77 | options = instance.multiple.options = initOptions(instance); 78 | 79 | // the consumer provided selection 80 | selected = options.items(); 81 | 82 | // notify consumer before starting 83 | if (false === notifyBeforeStart(element, options, ev, ui)) { 84 | options.dragCanceled = true; 85 | return false; 86 | } 87 | 88 | // cache respective origins 89 | selected.each(function () { 90 | var position = $(this).position(); 91 | $(this).data('dragmultiple:originalPosition', $.extend({}, position)); 92 | }); 93 | 94 | // TODO: support the 'valid, invalid and function' values 95 | // currently only supports true 96 | // disable draggable revert, we will handle the revert 97 | instance.originalRevert = options.revert = instance.options.revert; 98 | instance.options.revert = preventDraggableRevert; 99 | }, 100 | 101 | // move the selected draggables 102 | drag: function (ev, ui) { 103 | 104 | var element, instance, options; 105 | 106 | element = this; 107 | instance = element.data('draggable') || element.data('ui-draggable'); 108 | options = instance.multiple.options; 109 | 110 | if (options.dragCanceled) { 111 | return false; 112 | } 113 | 114 | notifyBeforeDrag(element, options, ev, ui); 115 | 116 | // check to see if consumer updated the revert option 117 | if (preventDraggableRevert !== instance.options.revert) { 118 | options.revert = instance.options.revert; 119 | instance.options.revert = preventDraggableRevert; 120 | } 121 | 122 | // TODO: make this as robust as draggable's positioning 123 | options.items().each(function () { 124 | var origPosition = $(this).data('dragmultiple:originalPosition'); 125 | // NOTE: this only works on elements that are already positionable 126 | $(this).css({ 127 | top: origPosition.top + (ui.position.top - ui.originalPosition.top), 128 | left: origPosition.left + (ui.position.left - ui.originalPosition.left) 129 | }); 130 | }); 131 | 132 | }, 133 | 134 | stop: function (ev, ui) { 135 | 136 | var element, instance, options; 137 | 138 | element = this; 139 | instance = element.data('draggable') || element.data('ui-draggable'); 140 | options = instance.multiple.options; 141 | 142 | if (options.dragCanceled) { 143 | return false; 144 | } 145 | 146 | notifyBeforeStop(element, options, ev, ui); 147 | 148 | // TODO: mimic the revert logic from draggable 149 | if (options.revert === true) { 150 | options.items().each(function () { 151 | var position = $(this).data('dragmultiple:originalPosition'); 152 | $(this).css(position); 153 | }); 154 | } 155 | 156 | // clean up 157 | options.items().each(function () { 158 | $(this).removeData('dragmultiple:originalPosition'); 159 | }); 160 | 161 | // restore orignal revert setting 162 | instance.options.revert = instance.originalRevert; 163 | 164 | } 165 | }); 166 | 167 | }(jQuery)); -------------------------------------------------------------------------------- /js/3rdparty/leaflet.awesome-markers.js: -------------------------------------------------------------------------------- 1 | /* 2 | Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons 3 | (c) 2012-2013, Lennard Voogdt 4 | 5 | http://leafletjs.com 6 | https://github.com/lvoogdt 7 | */ 8 | 9 | /*global L*/ 10 | 11 | (function (window, document, undefined) { 12 | "use strict"; 13 | /* 14 | * Leaflet.AwesomeMarkers assumes that you have already included the Leaflet library. 15 | */ 16 | 17 | L.AwesomeMarkers = {}; 18 | 19 | L.AwesomeMarkers.version = '2.0.1'; 20 | 21 | L.AwesomeMarkers.Icon = L.Icon.extend({ 22 | options: { 23 | iconSize: [35, 45], 24 | iconAnchor: [17, 42], 25 | popupAnchor: [1, -32], 26 | shadowAnchor: [10, 12], 27 | shadowSize: [36, 16], 28 | className: 'awesome-marker', 29 | prefix: 'glyphicon', 30 | spinClass: 'fa-spin', 31 | extraClasses: '', 32 | icon: 'home', 33 | markerColor: 'blue', 34 | iconColor: 'white' 35 | }, 36 | 37 | initialize: function (options) { 38 | options = L.Util.setOptions(this, options); 39 | }, 40 | 41 | createIcon: function () { 42 | var div = document.createElement('div'), 43 | options = this.options; 44 | 45 | if (options.icon) { 46 | div.innerHTML = this._createInner(); 47 | } 48 | 49 | if (options.bgPos) { 50 | div.style.backgroundPosition = 51 | (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px'; 52 | } 53 | 54 | this._setIconStyles(div, 'icon-' + options.markerColor); 55 | return div; 56 | }, 57 | 58 | _createInner: function() { 59 | var iconClass, iconSpinClass = "", iconColorClass = "", iconColorStyle = "", options = this.options; 60 | 61 | if(options.icon.slice(0,options.prefix.length+1) === options.prefix + "-") { 62 | iconClass = options.icon; 63 | } else { 64 | iconClass = options.prefix + "-" + options.icon; 65 | } 66 | 67 | if(options.spin && typeof options.spinClass === "string") { 68 | iconSpinClass = options.spinClass; 69 | } 70 | 71 | if(options.iconColor) { 72 | if(options.iconColor === 'white' || options.iconColor === 'black') { 73 | iconColorClass = "icon-" + options.iconColor; 74 | } else { 75 | iconColorStyle = "style='color: " + options.iconColor + "' "; 76 | } 77 | } 78 | 79 | return ""; 80 | }, 81 | 82 | _setIconStyles: function (img, name) { 83 | var options = this.options, 84 | size = L.point(options[name === 'shadow' ? 'shadowSize' : 'iconSize']), 85 | anchor; 86 | 87 | if (name === 'shadow') { 88 | anchor = L.point(options.shadowAnchor || options.iconAnchor); 89 | } else { 90 | anchor = L.point(options.iconAnchor); 91 | } 92 | 93 | if (!anchor && size) { 94 | anchor = size.divideBy(2, true); 95 | } 96 | 97 | img.className = 'awesome-marker-' + name + ' ' + options.className; 98 | 99 | if (anchor) { 100 | img.style.marginLeft = (-anchor.x) + 'px'; 101 | img.style.marginTop = (-anchor.y) + 'px'; 102 | } 103 | 104 | if (size) { 105 | img.style.width = size.x + 'px'; 106 | img.style.height = size.y + 'px'; 107 | } 108 | }, 109 | 110 | createShadow: function () { 111 | var div = document.createElement('div'); 112 | 113 | this._setIconStyles(div, 'shadow'); 114 | return div; 115 | } 116 | }); 117 | 118 | L.AwesomeMarkers.icon = function (options) { 119 | return new L.AwesomeMarkers.Icon(options); 120 | }; 121 | 122 | }(this, document)); 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /js/jquery.scrollTo.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2007-2014 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com 3 | * Licensed under MIT 4 | * @author Ariel Flesler 5 | * @version 1.4.11 6 | */ 7 | ;(function(a){if(typeof define==='function'&&define.amd){define(['jquery'],a)}else{a(jQuery)}}(function($){var j=$.scrollTo=function(a,b,c){return $(window).scrollTo(a,b,c)};j.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};j.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(f,g,h){if(typeof g=='object'){h=g;g=0}if(typeof h=='function')h={onAfter:h};if(f=='max')f=9e9;h=$.extend({},j.defaults,h);g=g||h.duration;h.queue=h.queue&&h.axis.length>1;if(h.queue)g/=2;h.offset=both(h.offset);h.over=both(h.over);return this._scrollable().each(function(){if(f==null)return;var d=this,$elem=$(d),targ=f,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}var e=$.isFunction(h.offset)&&h.offset(d,targ)||h.offset;$.each(h.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=j.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(h.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=e[pos]||0;if(h.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*h.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(h.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&h.queue){if(old!=attr[key])animate(h.onAfterFirst);delete attr[key]}});animate(h.onAfter);function animate(a){$elem.animate(attr,g,h.easing,a&&function(){a.call(this,targ,h)})}}).end()};j.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return $.isFunction(a)||typeof a=='object'?a:{top:a,left:a}};return j})); 8 | -------------------------------------------------------------------------------- /js/loader.js: -------------------------------------------------------------------------------- 1 | 2 | OC.ContactsPlus = OC.ContactsPlus || {}; 3 | OC.ContactsPlus.appname='contactsplus'; 4 | 5 | OC.ContactsPlus.Import = { 6 | Store:{ 7 | file: '', 8 | path: '', 9 | id: 0, 10 | method: '', 11 | overwrite: 0, 12 | addressbookname: '', 13 | progresskey: '', 14 | percentage: 0, 15 | isDragged : false 16 | }, 17 | Dialog:{ 18 | open: function(filename){ 19 | OC.addStyle(OC.ContactsPlus.appname, 'import'); 20 | OC.ContactsPlus.Import.Store.file = filename; 21 | OC.ContactsPlus.Import.Store.path = $('#dir').val(); 22 | 23 | $('body').append('
'); 24 | $('#contacts_import').load(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/getimportdialogtplcontacts'), {filename:OC.ContactsPlus.Import.Store.file, path:OC.ContactsPlus.Import.Store.path},function(){ 25 | OC.ContactsPlus.Import.Dialog.init(); 26 | }); 27 | }, 28 | close: function(){ 29 | OC.ContactsPlus.Import.reset(); 30 | $('#contacts_import_dialog').ocdialog('close'); 31 | $('#contacts_import_dialog').ocdialog('destroy').remove(); 32 | $('#contacts_import_dialog').remove(); 33 | 34 | if($('#cAddressbooks').length > 0){ 35 | OC.ContactsPlus.getAddressBooks(); 36 | } 37 | 38 | }, 39 | init: function(){ 40 | //init dialog 41 | $('#contacts_import_dialog').ocdialog({ 42 | modal: true, 43 | closeOnEscape: true, 44 | height: 'auto', width:380, 45 | close : function() { 46 | OC.ContactsPlus.Import.Dialog.close(); 47 | } 48 | }); 49 | //init buttons 50 | $('#contacts_import_done').click(function(){ 51 | OC.ContactsPlus.Import.Dialog.close(); 52 | }); 53 | $('#contacts_import_submit').click(function(){ 54 | OC.ContactsPlus.Import.Core.process(); 55 | 56 | return false; 57 | }); 58 | $('#contacts_import_mergewarning').click(function(){ 59 | $('#contacts_import_newaddressbook').attr('value', $('#contacts_import_availablename').val()); 60 | OC.ContactsPlus.Import.Dialog.mergewarning($('#contacts_import_newaddressbook').val()); 61 | return false; 62 | }); 63 | $('#contacts_import_addressbook').change(function(){ 64 | if($('#contacts_import_addressbook option:selected').val() == 'newaddressbook'){ 65 | $('#contacts_import_newaddrform').slideDown('slow'); 66 | OC.ContactsPlus.Import.Dialog.mergewarning($('#contacts_import_newaddressbook').val()); 67 | }else{ 68 | $('#contacts_import_newaddrform').slideUp('slow'); 69 | $('#contacts_import_mergewarning').slideUp('slow'); 70 | } 71 | }); 72 | $('#contacts_import_newaddressbook').keyup(function(){ 73 | OC.ContactsPlus.Import.Dialog.mergewarning($.trim($('#contacts_import_newaddressbook').val())); 74 | return false; 75 | }); 76 | if(OC.ContactsPlus.Import.Store.isDragged === true){ 77 | var aktAddrBookId=$('#cAddressbooks li.isActiveABook').attr('data-adrbid'); 78 | $('#contacts_import_addressbook').val(aktAddrBookId); 79 | } 80 | 81 | //init progressbar 82 | $('#contacts_import_progressbar').progressbar({value: OC.ContactsPlus.Import.Store.percentage}); 83 | OC.ContactsPlus.Import.Store.progresskey = $('#contacts_import_progresskey').val(); 84 | }, 85 | mergewarning: function(newaddrname){ 86 | $.post(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/checkaddressbookexists'), {addrbookname: newaddrname}, function(data){ 87 | if(data !== null && data.message == 'exists'){ 88 | $('#contacts_import_mergewarning').slideDown('slow'); 89 | }else{ 90 | $('#contacts_import_mergewarning').slideUp('slow'); 91 | } 92 | }); 93 | return false; 94 | }, 95 | update: function(){ 96 | if(OC.ContactsPlus.Import.Store.percentage === 100){ 97 | return false; 98 | } 99 | $.post(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/importvcards'), {progresskey: OC.ContactsPlus.Import.Store.progresskey, getprogress: true}, function(data){ 100 | if(data.status == 'success'){ 101 | 102 | if(data.percent === null){ 103 | return false; 104 | } 105 | 106 | OC.ContactsPlus.Import.Store.percentage = parseInt(data.percent); 107 | $('#contacts_import_progressbar').progressbar('option', 'value', parseInt(data.percent)); 108 | $('#contacts_import_progressbar > div').css('background-color', '#FF2626'); 109 | $('#contacts_import_process_message').text(data.currentmsg); 110 | if(data.percent < 100 ){ 111 | window.setTimeout('OC.ContactsPlus.Import.Dialog.update()', 100); 112 | }else{ 113 | $('#contacts_import_progressbar').progressbar('option', 'value', 100); 114 | $('#contacts_import_progressbar > div').css('background-color', '#FF2626'); 115 | $('#contacts_import_done').css('display', 'block'); 116 | 117 | } 118 | }else{ 119 | 120 | $('#contacts_import_progressbar').progressbar('option', 'value', 100); 121 | $('#contacts_import_progressbar > div').css('background-color', '#FF2626'); 122 | $('#contacts_import_status').html(data.message); 123 | } 124 | }); 125 | return 0; 126 | }, 127 | warning: function(selector){ 128 | $(selector).addClass('contacts_import_warning'); 129 | $(selector).focus(function(){ 130 | $(selector).removeClass('contacts_import_warning'); 131 | }); 132 | } 133 | }, 134 | Core:{ 135 | process: function(){ 136 | var validation = OC.ContactsPlus.Import.Core.prepare(); 137 | if(validation){ 138 | $('#contacts_import_form').css('display', 'none'); 139 | $('#contacts_import_process').css('display', 'block'); 140 | $('#contacts_import_newaddressbook').attr('readonly', 'readonly'); 141 | $('#contacts_import_addressbook').attr('disabled', 'disabled'); 142 | $('#contacts_import_overwrite').attr('disabled', 'disabled'); 143 | OC.ContactsPlus.Import.Core.send(); 144 | window.setTimeout('OC.ContactsPlus.Import.Dialog.update()', 100); 145 | } 146 | }, 147 | send: function(){ 148 | 149 | $.post(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/importvcards'), 150 | {progresskey: OC.ContactsPlus.Import.Store.progresskey, method: String (OC.ContactsPlus.Import.Store.method), overwrite: String (OC.ContactsPlus.Import.Store.overwrite), addressbookname: String (OC.ContactsPlus.Import.Store.addressbookname), path: String (OC.ContactsPlus.Import.Store.path), file: String (OC.ContactsPlus.Import.Store.file), id: String (OC.ContactsPlus.Import.Store.id), isDragged:String (OC.ContactsPlus.Import.Store.isDragged)}, function(data){ 151 | if(data.status == 'success'){ 152 | $('#contacts_import_progressbar').progressbar('option', 'value', 100); 153 | $('#contacts_import_progressbar > div').css('background-color', '#FF2626'); 154 | OC.ContactsPlus.Import.Store.percentage = 100; 155 | $('#contacts_import_progressbar').hide(); 156 | $('#contacts_import_process_message').text('').hide(); 157 | $('#contacts_import_done').css('display', 'block'); 158 | $('#contacts_import_status').html(data.message); 159 | }else{ 160 | $('#contacts_import_progressbar').progressbar('option', 'value', 100); 161 | $('#contacts_import_progressbar > div').css('background-color', '#FF2626'); 162 | $('#contacts_import_status').html(data.message); 163 | 164 | } 165 | }); 166 | }, 167 | prepare: function(){ 168 | OC.ContactsPlus.Import.Store.id = $('#contacts_import_addressbook option:selected').val(); 169 | 170 | if($('#contacts_import_addressbook option:selected').val() == 'newaddressbook'){ 171 | OC.ContactsPlus.Import.Store.method = 'new'; 172 | OC.ContactsPlus.Import.Store.addressbookname = $.trim($('#contacts_import_newaddressbook').val()); 173 | if(OC.ContactsPlus.Import.Store.addressbookname == ''){ 174 | OC.ContactsPlus.Import.Dialog.warning('#contacts_import_newaddressbook'); 175 | return false; 176 | } 177 | 178 | }else{ 179 | OC.ContactsPlus.Import.Store.method = 'old'; 180 | OC.ContactsPlus.Import.Store.overwrite = $('#contacts_import_overwrite').is(':checked') ? 1 : 0; 181 | } 182 | return true; 183 | } 184 | }, 185 | reset: function(){ 186 | OC.ContactsPlus.Import.Store.file = ''; 187 | OC.ContactsPlus.Import.Store.path = ''; 188 | OC.ContactsPlus.Import.Store.id = 0; 189 | OC.ContactsPlus.Import.Store.method = ''; 190 | OC.ContactsPlus.Import.Store.overwrite = 0; 191 | OC.ContactsPlus.Import.Store.calname = ''; 192 | OC.ContactsPlus.Import.Store.progresskey = ''; 193 | OC.ContactsPlus.Import.Store.percentage = 0; 194 | } 195 | }; 196 | 197 | 198 | 199 | $(document).ready(function(){ 200 | if(typeof FileActions !== 'undefined'){ 201 | FileActions.register('text/vcard','importaddressbook', OC.PERMISSION_READ, '', OC.ContactsPlus.Import.Dialog.open); 202 | FileActions.setDefault('text/vcard','importaddressbook'); 203 | FileActions.register('text/x-vcard','importaddressbook', OC.PERMISSION_READ, '', OC.ContactsPlus.Import.Dialog.open); 204 | FileActions.setDefault('text/x-vcard','importaddressbook'); 205 | } 206 | 207 | }); -------------------------------------------------------------------------------- /js/search.js: -------------------------------------------------------------------------------- 1 | $(window).bind('hashchange', function(event) { 2 | 3 | var urlTest = window.location.hash.substr(1); 4 | var url = urlTest.split('-'); 5 | 6 | if(url[0] === 'contactsplus'){ 7 | 8 | $.getJSON(OC.generateUrl('apps/contactsplus/showcontact/{id}',{id:url[1]}), function(jsondata) { 9 | if($('#SearchView').length === 0){ 10 | $('
').appendTo($('body')[0]); 11 | } 12 | 13 | $('#SearchView').html(jsondata); 14 | window.location.hash = '#'; 15 | }); 16 | } 17 | }); -------------------------------------------------------------------------------- /js/settings.js: -------------------------------------------------------------------------------- 1 | OC.ContactsPlus = OC.ContactsPlus || {}; 2 | OC.ContactsPlus.appname='contactsplus'; 3 | 4 | OC.ContactsPlus.Settings = { 5 | init:function() { 6 | this.Addressbook.adrsettings = $('.addressbooks-settings').first(); 7 | this.Addressbook.adractions = $('#contacts-settings').find('.actions'); 8 | 9 | //console.log('actions: ' + this.Addressbook.adractions.length); 10 | //var ABooksList=$('.addressbook'); 11 | $('#toggleIosSupport').on('change',function(){ 12 | OC.ContactsPlus.addIosSupport(this); 13 | }); 14 | 15 | OC.ContactsPlus.Settings.Addressbook.adrsettings.keydown(function(event) { 16 | if(event.which == 13 || event.which == 32) { 17 | OC.ContactsPlus.Settings.Addressbook.adrsettings.click(); 18 | } 19 | }); 20 | 21 | OC.ContactsPlus.Settings.Addressbook.adractions.find('button.hidden').hide(); 22 | OC.ContactsPlus.Settings.Addressbook.adrsettings.on('click', function(event){ 23 | $('.tipsy').remove(); 24 | var tgt = $(event.target); 25 | 26 | if(tgt.is('i.ioc') || tgt.is(':checkbox')) { 27 | var id = tgt.parents('tr').first().data('id'); 28 | if(!id) { 29 | return; 30 | } 31 | 32 | if(tgt.is('#active_aid_'+id+':checkbox')) { 33 | 34 | OC.ContactsPlus.Settings.Addressbook.doActivate(id, tgt); 35 | 36 | } else if(tgt.is('i.ioc')) { 37 | 38 | if(tgt.hasClass('edit')) { 39 | OC.ContactsPlus.Settings.Addressbook.doEdit(id); 40 | } else if(tgt.hasClass('delete')) { 41 | OC.ContactsPlus.Settings.Addressbook.doDelete(id); 42 | } else if(tgt.hasClass('globe')) { 43 | OC.ContactsPlus.Settings.Addressbook.showCardDAV(id); 44 | } else if(tgt.hasClass('cloud')) { 45 | OC.ContactsPlus.Settings.Addressbook.showVCF(id); 46 | } 47 | else if(tgt.hasClass('abo')) { 48 | OC.ContactsPlus.Settings.Addressbook.showAboBirthday(id); 49 | } 50 | else if(tgt.hasClass('export')) { 51 | OC.ContactsPlus.Settings.Addressbook.VCFExport(id); 52 | } 53 | } 54 | } else if(tgt.is('button')) { 55 | event.preventDefault(); 56 | if(tgt.hasClass('save')) { 57 | OC.ContactsPlus.Settings.Addressbook.doSave(); 58 | } else if(tgt.hasClass('cancel')) { 59 | OC.ContactsPlus.Settings.Addressbook.showActions(['new']); 60 | } else if(tgt.hasClass('new')) { 61 | OC.ContactsPlus.Settings.Addressbook.doEdit('new'); 62 | } 63 | } 64 | }); 65 | 66 | 67 | }, 68 | Addressbook:{ 69 | showActions:function(act) { 70 | this.adractions.children().hide(); 71 | this.adractions.children('.'+act.join(',.')).show(); 72 | }, 73 | doActivate:function(id, tgt) { 74 | var active = tgt.is(':checked'); 75 | 76 | //console.log('doActivate: ', id, active); 77 | $.post(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/activateaddrbook'), {id: id, active: Number(active)}, function(jsondata) { 78 | if (jsondata.status == 'success') { 79 | $(document).trigger('request.addressbook.activate', { 80 | id: id, 81 | activate: active, 82 | }); 83 | 84 | if(active === true){ 85 | OC.ContactsPlus.getAddressBooks(id); 86 | }else{ 87 | $('#cAddressbooks .dropcontainerAddressBook[data-adrbid="'+id+'"]').remove(); 88 | } 89 | 90 | } else { 91 | //console.log('Error:', jsondata.data.message); 92 | OC.ContactsPlus.notify(t(OC.ContactsPlus.appname, 'Error') + ': ' + jsondata.data.message); 93 | tgt.checked = !active; 94 | } 95 | }); 96 | }, 97 | 98 | doDelete:function(id) { 99 | //console.log('doDelete: ', id); 100 | 101 | 102 | var handleDelete=function(YesNo){ 103 | 104 | if(YesNo){ 105 | var row = $('.addressbooks-settings tr[data-id="'+id+'"]'); 106 | 107 | $.post(OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/deleteaddrbook'), { id: id}, function(jsondata) { 108 | if (jsondata.status == 'success'){ 109 | 110 | row.remove(); 111 | OC.ContactsPlus.Settings.Addressbook.showActions(['new',]); 112 | $('#cAddressbooks').find('.dropcontainerAddressBook[data-adrbid="'+id+'"]').remove(); 113 | OC.ContactsPlus.getAddressBooks(0); 114 | } else { 115 | OC.dialogs.alert(jsondata.data.message, t(OC.ContactsPlus.appname, 'Error')); 116 | } 117 | }); 118 | } 119 | }; 120 | 121 | OC.dialogs.confirm(t(OC.ContactsPlus.appname,'Do you really want to delete this address book?'),t(OC.ContactsPlus.appname,'Delete Addressbook'),handleDelete); 122 | 123 | }, 124 | doEdit:function(id) { 125 | //console.log('doEdit: ', id); 126 | 127 | var owner = this.adrsettings.find('[data-id="'+id+'"]').data('owner'); 128 | var actions = ['description', 'save', 'cancel']; 129 | if(owner == OC.currentUser || id === 'new') { 130 | actions.push('active', 'name'); 131 | } 132 | this.showActions(actions); 133 | var name = this.adrsettings.find('[data-id="'+id+'"]').find('.name').text(); 134 | var description = this.adrsettings.find('[data-id="'+id+'"]').find('.description').text(); 135 | var active = true; 136 | //console.log('name, desc', name, description); 137 | this.adractions.find('.active').prop('checked', active); 138 | this.adractions.find('.name').val(name); 139 | this.adractions.find('.description').val(description); 140 | this.adractions.data('id', id); 141 | }, 142 | doSave:function() { 143 | var name = this.adractions.find('.name').val(); 144 | var description = this.adractions.find('.description').val(); 145 | var active = this.adractions.find('.active').is(':checked'); 146 | var id = this.adractions.data('id'); 147 | //console.log('doSave:', id, name, description, active); 148 | 149 | if(name.length == 0) { 150 | OC.dialogs.alert(t(OC.ContactsPlus.appname, 'Displayname cannot be empty.'), t(OC.ContactsPlus.appname, 'Error')); 151 | return false; 152 | } 153 | var url; 154 | if (id == 'new'){ 155 | url = OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/addaddrbook'); 156 | }else{ 157 | url = OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/updateaddrbook'); 158 | } 159 | self = this; 160 | $.post(url, { id: id, name: name, active: Number(active), description: description }, 161 | function(jsondata){ 162 | if(jsondata.status == 'success'){ 163 | self.showActions(['new',]); 164 | self.adractions.removeData('id'); 165 | active = Boolean(Number(jsondata.data.addressbook.active)); 166 | if(id == 'new') { 167 | 168 | 169 | 170 | self.adrsettings.find('table') 171 | .append('' 172 | + '' 173 | + ''+jsondata.data.addressbook.displayname+'' 174 | + '' 175 | + '' 176 | + '' 177 | + '' 178 | + ''); 179 | 180 | OC.ContactsPlus.loadContacts(jsondata.data.addressbook.id,'',0,0); 181 | OC.ContactsPlus.getAddressBooks(jsondata.data.addressbook.id); 182 | 183 | 184 | } else { 185 | var row = self.adrsettings.find('tr[data-id="'+id+'"]'); 186 | row.find('td.name').text(jsondata.data.addressbook.displayname); 187 | row.find('td.description').text(jsondata.data.addressbook.description); 188 | $('#cAddressbooks').find('.dropcontainerAddressBook[data-adrbid="'+id+'"] .groupname').text(jsondata.data.addressbook.displayname); 189 | } 190 | 191 | } else { 192 | OC.dialogs.alert(jsondata.data.message, t(OC.ContactsPlus.appname, 'Error')); 193 | } 194 | }); 195 | }, 196 | showLink:function(id, row, link) { 197 | //console.log('row:', row.length); 198 | row.next('tr.link').remove(); 199 | var linkrow = $('' 200 | + '').insertAfter(row); 201 | linkrow.find('input').focus().select(); 202 | linkrow.find('.ioc').click(function() { 203 | $(this).parents('tr').first().remove(); 204 | }); 205 | }, 206 | showCardDAV:function(id) { 207 | //console.log('showCardDAV: ', id); 208 | var row = this.adrsettings.find('tr[data-id="'+id+'"]'); 209 | var owner = row.data('owner'); 210 | var uri = (owner === oc_current_user ) ? row.data('uri') : row.data('uri') + '_shared_by_' + owner; 211 | this.showLink(id, row, $('#totalurl').val()+'addressbooks/'+encodeURIComponent(oc_current_user)+'/'+encodeURIComponent(uri)); 212 | }, 213 | showVCF:function(id) { 214 | //console.log('showVCF: ', id); 215 | var row = this.adrsettings.find('tr[data-id="'+id+'"]'); 216 | var owner = row.data('owner'); 217 | var uri = (owner === oc_current_user ) ? row.data('uri') : row.data('uri') + '_shared_by_' + owner; 218 | var link = $('#totalurl').val()+'addressbooks/'+encodeURIComponent(oc_current_user)+'/'+encodeURIComponent(uri)+'?export'; 219 | //console.log(link); 220 | this.showLink(id, row, link); 221 | }, 222 | VCFExport:function(id) { 223 | //console.log('showVCF: ', id); 224 | var row = this.adrsettings.find('tr[data-id="'+id+'"]'); 225 | var owner = row.data('owner'); 226 | var uri = (owner === oc_current_user ) ? row.data('uri') : row.data('uri') + '_shared_by_' + owner; 227 | var link = OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/exportcontacts') + '?bookid=' +id; 228 | document.location.href =link; 229 | 230 | }, 231 | showAboBirthday:function(id) { 232 | //console.log('showVCF: ', id); 233 | var row = this.adrsettings.find('tr[data-id="'+id+'"]'); 234 | var owner = row.data('owner'); 235 | var uri = (owner === oc_current_user ) ? row.data('uri') : row.data('uri') + '_shared_by_' + owner; 236 | var link = OC.generateUrl('apps/'+OC.ContactsPlus.appname+'/exportbirthdays')+'?aid=' +id; 237 | document.location.href =link; 238 | 239 | } 240 | } 241 | }; 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /lib/addressbookprovider.php: -------------------------------------------------------------------------------- 1 | addressBook = $addressBook; 18 | 19 | } 20 | 21 | public function getAddressbook() { 22 | return $this->addressBook; 23 | } 24 | 25 | /** 26 | * @return string defining the technical unique key 27 | */ 28 | public function getKey() { 29 | return $this->addressBook['id']; 30 | } 31 | 32 | /** 33 | * In comparison to getKey() this function returns a human readable (maybe translated) name 34 | * @return string 35 | */ 36 | public function getDisplayName() { 37 | return $this->addressBook['displayname']; 38 | } 39 | 40 | /** 41 | * @return integer 42 | */ 43 | public function getPermissions() { 44 | return $this->addressBook['permissions']; 45 | } 46 | 47 | /** 48 | * @param string $pattern 49 | * @param string[] $searchProperties 50 | * @param $options 51 | * @return array|false 52 | */ 53 | public function search($pattern, $searchProperties, $options) { 54 | $propTable = '*PREFIX*conplus_cards_properties'; 55 | $contTable = '*PREFIX*conplus_cards'; 56 | $addrTable = '*PREFIX*conplus_addressbooks'; 57 | $results = array(); 58 | 59 | /** 60 | * This query will fetch all contacts which match the $searchProperties 61 | * It will look up the addressbookid of the contact and the user id of the owner of the contact app 62 | */ 63 | $query = <<getKey(); 84 | 85 | foreach ($searchProperties as $property) { 86 | $params[] = $property; 87 | $params[] = '%' . $pattern . '%'; 88 | $query .= '(`name` = ? AND `value` ILIKE ?) OR '; 89 | } 90 | 91 | $query = substr($query, 0, strlen($query) - 4); 92 | $query .= ')'; 93 | 94 | $stmt = \OCP\DB::prepare($query); 95 | $result = $stmt->execute($params); 96 | 97 | if (\OCP\DB::isError($result)) { 98 | \OCP\Util::writeLog('contactsplus', __METHOD__ . 'DB error: ' . \OC_DB::getErrorMessage($result), 99 | \OCP\Util::ERROR); 100 | return false; 101 | } 102 | 103 | $j = []; 104 | 105 | while ($row = $result->fetchRow()) { 106 | $id = $row['contactid']; 107 | //$addressbookKey = $row['addressbookid']; 108 | $vcard = App::getContactVCard($id); 109 | 110 | $contact = VCard::structureContact($vcard); 111 | 112 | $j['data'] = $contact; 113 | $j['data']['id'] = $id; 114 | $j['data']['metadata'] = $row; 115 | $j['data']['photo'] = false; 116 | if(isset($vcard->BDAY)){ 117 | $j['data']['birthday'] = $vcard->BDAY; 118 | } 119 | if(isset($vcard->PHOTO) || isset($vcard->LOGO)) { 120 | $j['data']['photo'] = true; 121 | $url = \OC::$server->getURLGenerator()->linkToRoute('contactsplus.contacts.getContactPhoto',array('id' => $id)); 122 | $url = \OC::$server->getURLGenerator()->getAbsoluteURL($url); 123 | $j['data']['PHOTO'] = "uri:$url"; 124 | } 125 | 126 | $results[] = $this->convertToSearchResult($j); 127 | 128 | } 129 | return $results; 130 | 131 | } 132 | 133 | /** 134 | * @param $properties 135 | * @return Contact|null 136 | */ 137 | public function createOrUpdate($properties) { 138 | 139 | } 140 | 141 | /** 142 | * @param $id 143 | * @return mixed 144 | */ 145 | public function delete($id) { 146 | 147 | VCard::delete($id); 148 | } 149 | 150 | 151 | 152 | /** 153 | * @param $j 154 | * @return array 155 | */ 156 | private function convertToSearchResult($j) { 157 | $data = $j['data']; 158 | $result = array(); 159 | foreach( $data as $key => $d) { 160 | $d = $data[$key]; 161 | if (in_array($key, App::$multi_properties)) { 162 | $result[$key] = array_map(function($v){ 163 | return $v['value']; 164 | }, $d); 165 | } else { 166 | if (is_array($d)) { 167 | $result[$key] = $d[0]['value']; 168 | } else { 169 | $result[$key] = $d; 170 | } 171 | } 172 | } 173 | 174 | return $result; 175 | } 176 | } -------------------------------------------------------------------------------- /lib/connector/sabre/carddav/addressbook.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | /** 24 | * This class overrides __construct to get access to $addressBookInfo and 25 | * $carddavBackend, Sabre_CardDAV_AddressBook::getACL() to return read/write 26 | * permissions based on user and shared state and it overrides 27 | * Sabre_CardDAV_AddressBook::getChild() and Sabre_CardDAV_AddressBook::getChildren() 28 | * to instantiate OC_Connector_Sabre_CardDAV_Cards. 29 | */ 30 | namespace OCA\ContactsPlus\Connector\Sabre\Carddav; 31 | 32 | use \OCA\ContactsPlus\Addressbook as AddrBook; 33 | use \OCA\ContactsPlus\App as ContactsApp; 34 | 35 | class AddressBook extends \Sabre\CardDAV\AddressBook { 36 | 37 | 38 | 39 | /** 40 | * Returns a list of ACE's for this node. 41 | * 42 | * Each ACE has the following properties: 43 | * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are 44 | * currently the only supported privileges 45 | * * 'principal', a url to the principal who owns the node 46 | * * 'protected' (optional), indicating that this ACE is not allowed to 47 | * be updated. 48 | * 49 | * @return array 50 | */ 51 | public function getACL() { 52 | 53 | $readprincipal = $this->getOwner(); 54 | $writeprincipal = $this->getOwner(); 55 | $createprincipal = $this->getOwner(); 56 | $deleteprincipal = $this->getOwner(); 57 | $uid = AddrBook::extractUserID($this->getOwner()); 58 | 59 | //\OCP\Config::setUserValue($uid, 'contactsplus', 'syncaddrbook', $this->addressBookInfo['uri']); 60 | 61 | $readWriteACL = array( 62 | array( 63 | 'privilege' => '{DAV:}read', 64 | 'principal' => 'principals/' . \OCP\User::getUser(), 65 | 'protected' => true, 66 | ), 67 | array( 68 | 'privilege' => '{DAV:}write', 69 | 'principal' => 'principals/' . \OCP\User::getUser(), 70 | 'protected' => true, 71 | ), 72 | ); 73 | 74 | if($uid !== \OCP\USER::getUser()) { 75 | $sharedAddressbook = \OCP\Share::getItemSharedWithBySource(ContactsApp::SHAREADDRESSBOOK, ContactsApp::SHAREADDRESSBOOKPREFIX.$this->addressBookInfo['id']); 76 | if($sharedAddressbook) { 77 | if(($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) 78 | && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) 79 | && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE) 80 | ) { 81 | return $readWriteACL; 82 | } 83 | if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) { 84 | $createprincipal = 'principals/' . \OCP\USER::getUser(); 85 | } 86 | if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ) { 87 | $readprincipal = 'principals/' . \OCP\USER::getUser(); 88 | } 89 | if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) { 90 | $writeprincipal = 'principals/' . \OCP\USER::getUser(); 91 | } 92 | if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE) { 93 | $deleteprincipal = 'principals/' . \OCP\USER::getUser(); 94 | } 95 | } 96 | } else { 97 | return parent::getACL(); 98 | } 99 | 100 | return array( 101 | array( 102 | 'privilege' => '{DAV:}read', 103 | 'principal' => $readprincipal, 104 | 'protected' => true, 105 | ), 106 | array( 107 | 'privilege' => '{DAV:}write-content', 108 | 'principal' => $writeprincipal, 109 | 'protected' => true, 110 | ), 111 | array( 112 | 'privilege' => '{DAV:}bind', 113 | 'principal' => $createprincipal, 114 | 'protected' => true, 115 | ), 116 | array( 117 | 'privilege' => '{DAV:}unbind', 118 | 'principal' => $deleteprincipal, 119 | 'protected' => true, 120 | ), 121 | ); 122 | 123 | } 124 | 125 | function getSupportedPrivilegeSet() { 126 | 127 | return array( 128 | 'privilege' => '{DAV:}all', 129 | 'abstract' => true, 130 | 'aggregates' => array( 131 | array( 132 | 'privilege' => '{DAV:}read', 133 | 'aggregates' => array( 134 | array( 135 | 'privilege' => '{DAV:}read-acl', 136 | 'abstract' => true, 137 | ), 138 | array( 139 | 'privilege' => '{DAV:}read-current-user-privilege-set', 140 | 'abstract' => true, 141 | ), 142 | ), 143 | ), // {DAV:}read 144 | array( 145 | 'privilege' => '{DAV:}write', 146 | 'aggregates' => array( 147 | array( 148 | 'privilege' => '{DAV:}write-acl', 149 | 'abstract' => true, 150 | ), 151 | array( 152 | 'privilege' => '{DAV:}write-properties', 153 | 'abstract' => true, 154 | ), 155 | array( 156 | 'privilege' => '{DAV:}write-content', 157 | 'abstract' => false, 158 | ), 159 | array( 160 | 'privilege' => '{DAV:}bind', 161 | 'abstract' => false, 162 | ), 163 | array( 164 | 'privilege' => '{DAV:}unbind', 165 | 'abstract' => false, 166 | ), 167 | array( 168 | 'privilege' => '{DAV:}unlock', 169 | 'abstract' => true, 170 | ), 171 | ), 172 | ), // {DAV:}write 173 | ), 174 | ); // {DAV:}all 175 | 176 | } 177 | 178 | /** 179 | * Returns a card 180 | * 181 | * @param string $name 182 | * @return OC_Connector_Sabre_DAV_Card 183 | */ 184 | public function getChild($name) { 185 | 186 | $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); 187 | if (!$obj) throw new \Sabre\DAV\Exception\NotFound('Card not found'); 188 | return new Card($this->carddavBackend,$this->addressBookInfo,$obj); 189 | 190 | } 191 | 192 | /** 193 | * Returns the full list of cards 194 | * 195 | * @return array 196 | */ 197 | public function getChildren() { 198 | 199 | $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); 200 | $children = array(); 201 | foreach($objs as $obj) { 202 | $children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj); 203 | } 204 | return $children; 205 | 206 | } 207 | 208 | /** 209 | * This method receives a list of paths in it's first argument. 210 | * It must return an array with Node objects. 211 | * 212 | * If any children are not found, you do not have to return them. 213 | * 214 | * @return array 215 | */ 216 | function getMultipleChildren(array $paths) { 217 | $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths); 218 | $children = []; 219 | foreach($objs as $obj) { 220 | 221 | $children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj); 222 | } 223 | return $children; 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /lib/connector/sabre/carddav/addressbookroot.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | /** 24 | * This class overrides Sabre_CardDAV_AddressBookRoot::getChildForPrincipal() 25 | * to instantiate OC_Connector_CardDAV_UserAddressBooks. 26 | */ 27 | namespace OCA\ContactsPlus\Connector\Sabre\Carddav; 28 | 29 | class AddressBookRoot extends \Sabre\CardDAV\AddressBookRoot { 30 | 31 | /** 32 | * This method returns a node for a principal. 33 | * 34 | * The passed array contains principal information, and is guaranteed to 35 | * at least contain a uri item. Other properties may or may not be 36 | * supplied by the authentication backend. 37 | * 38 | * @param array $principal 39 | * @return Sabre_DAV_INode 40 | */ 41 | public function getChildForPrincipal(array $principal) { 42 | 43 | return new UserAddressBooks($this->carddavBackend, $principal['uri']); 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /lib/connector/sabre/carddav/backend.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | /** 24 | * This CardDAV backend uses PDO to store addressbooks 25 | */ 26 | namespace OCA\ContactsPlus\Connector\Sabre\Carddav; 27 | 28 | use \OCA\ContactsPlus\Addressbook as AddrBook; 29 | use \OCA\ContactsPlus\VCard; 30 | 31 | class Backend extends \Sabre\CardDAV\Backend\AbstractBackend { 32 | /** 33 | * Returns the list of addressbooks for a specific user. 34 | * 35 | * @param string $principaluri 36 | * @return array 37 | */ 38 | public function getAddressBooksForUser($principaluri) { 39 | $data = AddrBook::allWherePrincipalURIIs($principaluri); 40 | $addressbooks = array(); 41 | 42 | foreach($data as $i) { 43 | if($i['userid'] !== \OCP\USER::getUser()) { 44 | $i['uri'] = $i['uri'] . '_shared_by_' . $i['userid']; 45 | } 46 | $addressbooks[] = array( 47 | 'id' => $i['id'], 48 | 'uri' => $i['uri'], 49 | 'principaluri' => 'principals/'.$i['userid'], 50 | '{DAV:}displayname' => $i['displayname'], 51 | '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => $i['description'], 52 | '{http://calendarserver.org/ns/}getctag' => $i['ctag'], 53 | '{http://sabredav.org/ns}sync-token' => $i['ctag']?$i['ctag']:'0', 54 | ); 55 | 56 | //\OCP\Util::writeLog('kontakte','CARDDAV->:'.$i['displayname'], \OCP\Util::DEBUG); 57 | } 58 | 59 | 60 | return $addressbooks; 61 | } 62 | 63 | 64 | /** 65 | * Updates an addressbook's properties 66 | * 67 | * See Sabre_DAV_IProperties for a description of the mutations array, as 68 | * well as the return value. 69 | * 70 | * @param mixed $addressbookid 71 | * @param array $mutations 72 | * @see Sabre_DAV_IProperties::updateProperties 73 | * @return bool|array 74 | */ 75 | public function updateAddressBook($addressbookid, \Sabre\DAV\PropPatch $mutations) { 76 | 77 | $supportedProperties = [ 78 | '{DAV:}displayname', 79 | '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}addressbook-description', 80 | ]; 81 | 82 | $propPatch->handle($supportedProperties, function($mutations) use ($addressbookid) { 83 | 84 | $updates = []; 85 | foreach($mutations as $property=>$newValue) { 86 | 87 | switch($property) { 88 | case '{DAV:}displayname' : 89 | $updates['displayname'] = $newValue; 90 | break; 91 | case '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' : 92 | $updates['description'] = $newValue; 93 | break; 94 | } 95 | } 96 | 97 | AddrBook::edit($addressbookid, $updates['displayname'], $updates['description']); 98 | return true; 99 | }); 100 | 101 | } 102 | 103 | /** 104 | * Creates a new address book 105 | * 106 | * @param string $principaluri 107 | * @param string $url Just the 'basename' of the url. 108 | * @param array $properties 109 | * @return void 110 | */ 111 | public function createAddressBook($principaluri, $url, array $properties) { 112 | 113 | $displayname = null; 114 | $description = null; 115 | 116 | foreach($properties as $property => $newvalue) { 117 | 118 | switch($property) { 119 | case '{DAV:}displayname' : 120 | $name = $newvalue; 121 | break; 122 | case '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV 123 | . '}addressbook-description' : 124 | $description = $newvalue; 125 | break; 126 | default : 127 | throw new \Sabre\DAV\Exception\BadRequest('Unknown property: ' 128 | . $property); 129 | } 130 | 131 | } 132 | 133 | AddrBook::addFromDAVData( 134 | $principaluri, 135 | $url, 136 | $name, 137 | $description 138 | ); 139 | } 140 | 141 | /** 142 | * Deletes an entire addressbook and all its contents 143 | * 144 | * @param int $addressbookid 145 | * @return void 146 | */ 147 | public function deleteAddressBook($addressbookid) { 148 | AddrBook::delete($addressbookid); 149 | } 150 | 151 | /** 152 | * Returns all cards for a specific addressbook id. 153 | * 154 | * @param mixed $addressbookid 155 | * @return array 156 | */ 157 | public function getCards($addressbookid) { 158 | $data = VCard::all($addressbookid); 159 | $cards = array(); 160 | foreach($data as $i) { 161 | //OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG); 162 | $cards[] = array( 163 | 'id' => $i['id'], 164 | //'carddata' => $i['carddata'], 165 | 'size' => strlen($i['carddata']), 166 | 'etag' => '"' . md5($i['carddata']) . '"', 167 | 'uri' => $i['uri'], 168 | 'lastmodified' => $i['lastmodified'] ); 169 | } 170 | 171 | return $cards; 172 | } 173 | 174 | /** 175 | * Returns a specfic card 176 | * 177 | * @param mixed $addressbookid 178 | * @param string $carduri 179 | * @return array 180 | */ 181 | public function getCard($addressbookid, $carduri) { 182 | return VCard::findWhereDAVDataIs($addressbookid, $carduri); 183 | 184 | } 185 | /** 186 | * Returns a list of cards. 187 | * 188 | * This method should work identical to getCard, but instead return all the 189 | * cards in the list as an array. 190 | * 191 | * If the backend supports this, it may allow for some speed-ups. 192 | * 193 | * @param mixed $addressBookId 194 | * @param array $uris 195 | * @return array 196 | */ 197 | public function getMultipleCards($addressBookId, array $uris) { 198 | 199 | return array_map(function($uri) use ($addressBookId) { 200 | return $this->getCard($addressBookId, $uri); 201 | }, $uris); 202 | 203 | return VCard::getMultipleCardsDavData($addressBookId, $uris); 204 | 205 | } 206 | 207 | 208 | /** 209 | * Creates a new card 210 | * 211 | * @param mixed $addressbookid 212 | * @param string $carduri 213 | * @param string $carddata 214 | * @return bool 215 | */ 216 | public function createCard($addressbookid, $carduri, $carddata) { 217 | VCard::addFromDAVData($addressbookid, $carduri, $carddata); 218 | } 219 | 220 | /** 221 | * Updates a card 222 | * 223 | * @param mixed $addressbookid 224 | * @param string $carduri 225 | * @param string $carddata 226 | * @return bool 227 | */ 228 | public function updateCard($addressbookid, $carduri, $carddata) { 229 | return VCard::editFromDAVData($addressbookid, $carduri, $carddata); 230 | } 231 | 232 | /** 233 | * Deletes a card 234 | * 235 | * @param mixed $addressbookid 236 | * @param string $carduri 237 | * @return bool 238 | */ 239 | public function deleteCard($addressbookid, $carduri) { 240 | return VCard::deleteFromDAVData($addressbookid, $carduri); 241 | } 242 | 243 | 244 | /** 245 | * @brief gets the userid from a principal path 246 | * @param string $principaluri 247 | * @return string 248 | */ 249 | public function userIDByPrincipal($principaluri) { 250 | list(, $userid) = \Sabre\DAV\URLUtil::splitPath($principaluri); 251 | return $userid; 252 | } 253 | 254 | 255 | 256 | } 257 | -------------------------------------------------------------------------------- /lib/connector/sabre/carddav/card.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | /** 24 | * This class overrides Sabre_CardDAV_Card::getACL() 25 | * to return read/write permissions based on user and shared state. 26 | */ 27 | namespace OCA\ContactsPlus\Connector\Sabre\Carddav; 28 | 29 | use \OCA\ContactsPlus\Addressbook as AddrBook; 30 | use \OCA\ContactsPlus\App as ContactsApp; 31 | 32 | class Card extends \Sabre\CardDAV\Card { 33 | 34 | 35 | 36 | 37 | /** 38 | * Returns a list of ACE's for this node. 39 | * 40 | * Each ACE has the following properties: 41 | * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are 42 | * currently the only supported privileges 43 | * * 'principal', a url to the principal who owns the node 44 | * * 'protected' (optional), indicating that this ACE is not allowed to 45 | * be updated. 46 | * 47 | * @return array 48 | */ 49 | public function getACL() { 50 | 51 | $readprincipal = $this->getOwner(); 52 | $writeprincipal = $this->getOwner(); 53 | $uid = AddrBook::extractUserID($this->getOwner()); 54 | 55 | if($uid !== \OCP\USER::getUser()) { 56 | $sharedAddressbook = \OCP\Share::getItemSharedWithBySource(ContactsApp::SHAREADDRESSBOOK,ContactsApp::SHAREADDRESSBOOKPREFIX.$this->addressBookInfo['id']); 57 | if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { 58 | $readprincipal = 'principals/' . \OCP\USER::getUser(); 59 | } 60 | if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) { 61 | $writeprincipal = 'principals/' . \OCP\USER::getUser(); 62 | } 63 | } 64 | 65 | return array( 66 | array( 67 | 'privilege' => '{DAV:}read', 68 | 'principal' => $readprincipal, 69 | 'protected' => true, 70 | ), 71 | array( 72 | 'privilege' => '{DAV:}write', 73 | 'principal' => $writeprincipal, 74 | 'protected' => true, 75 | ), 76 | 77 | ); 78 | 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /lib/connector/sabre/carddav/useraddressbooks.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | /** 24 | * This class overrides Sabre_CardDAV_UserAddressBooks::getChildren() 25 | * to instantiate OC_Connector_Sabre_CardDAV_AddressBooks. 26 | */ 27 | 28 | namespace OCA\ContactsPlus\Connector\Sabre\Carddav; 29 | 30 | class UserAddressBooks extends \Sabre\CardDAV\UserAddressBooks { 31 | 32 | /** 33 | * Returns a list of addressbooks 34 | * 35 | * @return array 36 | */ 37 | public function getChildren() { 38 | 39 | $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri); 40 | $objs = array(); 41 | foreach($addressbooks as $addressbook) { 42 | 43 | $objs[] = new AddressBook($this->carddavBackend, $addressbook); 44 | } 45 | return $objs; 46 | 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /lib/hooks.php: -------------------------------------------------------------------------------- 1 | 4 | * This file is licensed under the Affero General Public License version 3 or 5 | * later. 6 | * See the COPYING-README file. 7 | */ 8 | 9 | /** 10 | * This class contains all hooks. 11 | */ 12 | 13 | namespace OCA\ContactsPlus; 14 | use Sabre\VObject; 15 | 16 | class Hooks{ 17 | 18 | /** 19 | * @brief Deletes all addressbooks of a certain user 20 | * @param parameters parameters from postDeleteUser-Hook 21 | * @return array 22 | */ 23 | public static function deleteUser($parameters) { 24 | \OCP\Util::writeLog('contactsplus', 'Hook DEL ID-> '.$parameters['uid'], \OCP\Util::DEBUG); 25 | $addressbooks = Addressbook::all($parameters['uid']); 26 | 27 | foreach($addressbooks as $addressbook) { 28 | if($parameters['uid'] === $addressbook['userid']) { 29 | Addressbook::delete($addressbook['id']); 30 | } 31 | } 32 | //delete preferences 33 | 34 | return true; 35 | } 36 | 37 | 38 | public static function getBirthdayCalender($params){ 39 | 40 | $isAktiv= 1; 41 | if(\OCP\Config::getUserValue(\OCP\USER::getUser(), 'calendarplus', 'calendar_birthday_'. \OCP\USER::getUser()) !== ''){ 42 | $isAktiv = (int)\OCP\Config::getUserValue(\OCP\USER::getUser(), 'calendarplus','calendar_birthday_'. \OCP\USER::getUser()); 43 | } 44 | $base_url = \OC::$server->getURLGenerator()->linkToRoute('calendarplus.event.getEvents').'?calendar_id='; 45 | 46 | $Calendar=array( 47 | 'url' => $base_url.'birthday_'. \OCP\USER::getUser(), 48 | 'uri' => 'birthday_'. \OCP\USER::getUser(), 49 | 'externuri'=> '', 50 | 'displayname'=> App::$l10n->t('Birthdays'), 51 | 'permissions'=>\OCP\PERMISSION_READ, 52 | 'id'=>'birthday_'. \OCP\USER::getUser(), 53 | 'owner'=>\OCP\USER::getUser(), 54 | 'userid'=>\OCP\USER::getUser(), 55 | 'issubscribe' => 1, 56 | 'cache' => false, 57 | 'ctag'=>1, 58 | 'className' => 'birthday-calendar', 59 | 'editable' => false, 60 | 'startEditable' => false, 61 | 'active' => $isAktiv, 62 | ); 63 | 64 | $params['calendar'][] =$Calendar; 65 | 66 | } 67 | 68 | public static function getCalenderSources($parameters) { 69 | 70 | $base_url = \OC::$server->getURLGenerator()->linkToRoute('calendarplus.event.getEvents').'?calendar_id='; 71 | 72 | $addSource= array( 73 | 'url' => $base_url.'birthday_'. \OCP\USER::getUser(), 74 | 'id'=>'birthday_'. \OCP\USER::getUser(), 75 | 'className' => 'birthday-calendar', 76 | 'permissions'=>\OCP\PERMISSION_READ, 77 | 'cache' => false, 78 | 'ctag'=>2, 79 | 'editable' => false, 80 | 'startEditable' => false, 81 | 'issubscribe' => 1, 82 | ); 83 | 84 | $parameters['sources'] = $addSource; 85 | 86 | 87 | 88 | } 89 | 90 | public static function getBirthdayEvents($params) { 91 | 92 | if(\OCP\Config::getUserValue(\OCP\USER::getUser(), 'calendarplus','calendar_birthday_'. \OCP\USER::getUser())){ 93 | 94 | $name = $params['calendar_id']; 95 | 96 | if (strpos($name, 'birthday_') != 0) { 97 | return; 98 | } 99 | 100 | $info = explode('_', $name); 101 | $aid = $info[1]; 102 | $aDefNArray=array('0'=>'fname','1'=>'lname','3'=>'title','4'=>''); 103 | 104 | foreach(Addressbook::all($aid) as $addressbook) { 105 | 106 | foreach(VCard::all($addressbook['id']) as $contact) { 107 | try { 108 | $vcard = VObject\Reader::read($contact['carddata']); 109 | } catch (Exception $e) { 110 | continue; 111 | } 112 | 113 | 114 | $birthday = $vcard->BDAY; 115 | 116 | if ((string)$birthday) { 117 | $details = VCard::structureContact($vcard); 118 | 119 | $BirthdayTemp = new \DateTime($birthday); 120 | $checkForm=$BirthdayTemp->format('d-m-Y'); 121 | $temp=explode('-',$checkForm); 122 | $getAge=self::getAge($temp[2],$temp[1],$temp[0]); 123 | //$getAge=$BirthdayTemp->format('d-m-Y'); 124 | $title=isset($vcard->FN)?strtr($vcard->FN->getValue(), array('\,' => ',', '\;' => ';')):''; 125 | 126 | $sNameOutput=''; 127 | if(isset($details['N'][0]['value']) && count($details['N'][0]['value'])>0){ 128 | foreach($details['N'][0]['value'] as $key => $val){ 129 | if($val!='') { 130 | $aNameOutput[$aDefNArray[$key]]=$val; 131 | 132 | } 133 | } 134 | //$sNameOutput=isset($aNameOutput['title'])?$aNameOutput['title'].' ':''; 135 | $sNameOutput.=isset($aNameOutput['lname'])?$aNameOutput['lname'].' ':''; 136 | $sNameOutput.=isset($aNameOutput['fname'])?$aNameOutput['fname'].' ':''; 137 | 138 | unset($aNameOutput); 139 | } 140 | if($sNameOutput=='') {$sNameOutput=$title;} 141 | 142 | $sTitle1 =(string)App::$l10n->t('%1$s (%2$s)',array($sNameOutput,$getAge)); 143 | 144 | 145 | $aktYear=$BirthdayTemp->format('d-m'); 146 | $aktYear=$aktYear.date('-Y'); 147 | $start = new \DateTime($aktYear); 148 | $end = new \DateTime($aktYear.' +1 day'); 149 | 150 | $vcalendar = new VObject\Component\VCalendar(); 151 | $vevent = $vcalendar->createComponent('VEVENT'); 152 | $vevent->add('DTSTART'); 153 | $vevent->DTSTART->setDateTime( 154 | $start 155 | ); 156 | $vevent->DTSTART['VALUE'] = 'date'; 157 | $vevent->add('DTEND'); 158 | $vevent->DTEND->setDateTime( 159 | $end 160 | ); 161 | $vevent->DTEND['VALUE'] = 'date'; 162 | $vevent->{'SUMMARY'} = (string)$sTitle1; 163 | $vevent->{'UID'} = substr(md5(rand().time()), 0, 10); 164 | 165 | 166 | 167 | 168 | // DESCRIPTION? 169 | $aktYear1=$BirthdayTemp->format('-m-d'); 170 | $aktYear1=date('Y').$aktYear1; 171 | $params['events'][] = array( 172 | 'id' => 0,//$card['id'], 173 | 'vevent' => $vevent, 174 | 'repeating' => true, 175 | 'calendarid'=>$params['calendar_id'], 176 | 'privat'=>false, 177 | 'bday'=>true, 178 | 'shared'=>false, 179 | 'isalarm'=>false, 180 | 'summary' =>$sTitle1, 181 | 'start' => $aktYear1, 182 | 'allDay'=>true, 183 | 'startlist' =>$aktYear1, 184 | 'editable' => false, 185 | 'className' => 'birthdayevent', 186 | 'startEditable ' => false, 187 | 'durationEditable ' => false, 188 | ); 189 | } 190 | } 191 | } 192 | } 193 | return true; 194 | } 195 | 196 | public static function getAge ($y, $m, $d) { 197 | return date('Y') - $y - (date('n') < (ltrim($m,'0') + (date('j') < ltrim($d,'0')))); 198 | } 199 | } -------------------------------------------------------------------------------- /lib/search/provider.php: -------------------------------------------------------------------------------- 1 | . 17 | * 18 | */ 19 | 20 | namespace OCA\ContactsPlus\Search; 21 | 22 | use \OCA\ContactsPlus\Addressbook; 23 | use \OCA\ContactsPlus\App as ContactsApp; 24 | use \OCA\ContactsPlus\VCard; 25 | /** 26 | * Provide search results from the 'calendar' app 27 | */ 28 | class Provider extends \OCP\Search\Provider { 29 | 30 | /** 31 | * 32 | * @param string $query 33 | * @return \OCP\Search\Result 34 | */ 35 | function search($query) { 36 | $unescape = function($value) { 37 | return strtr($value, array('\,' => ',', '\;' => ';')); 38 | }; 39 | 40 | $searchresults = array( ); 41 | $results = ContactsApp::searchProperties($query); 42 | $l = \OC::$server->getL10N(ContactsApp::$appname); 43 | 44 | foreach($results as $result) { 45 | $vcard = VCard::find($result['id']); 46 | 47 | $link = '#'.intval($vcard['id']); 48 | 49 | $props = ''; 50 | 51 | 52 | foreach(array('EMAIL', 'NICKNAME', 'ORG','TEL') as $searchvar) { 53 | if(isset($result['name']) && $searchvar == $result['name']) { 54 | //\OCP\Util::writeLog(ContactsApp::$appname,'FOUND id: ' . $result['value'], \OCP\Util::DEBUG); 55 | $props .= $searchvar.':'.$result['value'].' '; 56 | } 57 | } 58 | 59 | 60 | $returnData['id']=$vcard['id']; 61 | $returnData['description']=$vcard['fullname'].' '.$props; 62 | $returnData['link']=$link; 63 | 64 | $results[]=new Result($returnData); 65 | 66 | 67 | } 68 | return $results; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/search/result.php: -------------------------------------------------------------------------------- 1 | . 17 | * 18 | */ 19 | 20 | namespace OCA\ContactsPlus\Search; 21 | 22 | /** 23 | * A found file 24 | */ 25 | class Result extends \OCP\Search\Result { 26 | 27 | /** 28 | * Type name; translated in templates 29 | * @var string 30 | */ 31 | public $type = 'contacts'; 32 | 33 | 34 | /** 35 | * Create a new file search result 36 | * @param array $data file data given by provider 37 | */ 38 | public function __construct(array $data = null) { 39 | 40 | $this->id = $data['id']; 41 | $this->name = $data['description']; 42 | $this->link = $data['link']; 43 | $this->icon = 'fa fa-book'; 44 | 45 | 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /lib/share/backend/addressbook.php: -------------------------------------------------------------------------------- 1 | 4 | * This file is licensed under the Affero General Public License version 3 or 5 | * later. 6 | * See the COPYING-README file. 7 | */ 8 | 9 | namespace OCA\ContactsPlus\Share\Backend; 10 | 11 | use \OCA\ContactsPlus\Addressbook as AddrBook; 12 | use \OCA\ContactsPlus\App as ContactsApp; 13 | use \OCA\ContactsPlus\VCard; 14 | 15 | class Addressbook implements \OCP\Share_Backend_Collection { 16 | const FORMAT_ADDRESSBOOKS = 1; 17 | 18 | /** 19 | * @brief Get the source of the item to be stored in the database 20 | * @param string Item 21 | * @param string Owner of the item 22 | * @return mixed|array|false Source 23 | * 24 | * Return an array if the item is file dependent, the array needs two keys: 'item' and 'file' 25 | * Return false if the item does not exist for the user 26 | * 27 | * The formatItems() function will translate the source returned back into the item 28 | */ 29 | public function isValidSource($itemSource, $uidOwner) { 30 | $addressbook = AddrBook::find( $itemSource ); 31 | if( $addressbook === false || $addressbook['userid'] !== $uidOwner) { 32 | return false; 33 | } 34 | return true; 35 | } 36 | 37 | public function isShareTypeAllowed($shareType) { 38 | return true; 39 | } 40 | 41 | /** 42 | * @brief Get a unique name of the item for the specified user 43 | * @param string Item 44 | * @param string|false User the item is being shared with 45 | * @param array|null List of similar item names already existing as shared items 46 | * @return string Target name 47 | * 48 | * This function needs to verify that the user does not already have an item with this name. 49 | * If it does generate a new name e.g. name_# 50 | */ 51 | public function generateTarget($itemSource, $shareWith, $exclude = null) { 52 | $addressbook = AddrBook::find( $itemSource ); 53 | $user_addressbooks = array(); 54 | foreach(AddrBook::all($shareWith) as $user_addressbook) { 55 | $user_addressbooks[] = $user_addressbook['displayname']; 56 | } 57 | $name = $addressbook['displayname']; 58 | $suffix = ''; 59 | while (in_array($name.$suffix, $user_addressbooks)) { 60 | $suffix++; 61 | } 62 | 63 | return $name.$suffix; 64 | } 65 | 66 | /** 67 | * @brief Converts the shared item sources back into the item in the specified format 68 | * @param array Shared items 69 | * @param int Format 70 | * @return ? 71 | * 72 | * The items array is a 3-dimensional array with the item_source as the first key and the share id as the second key to an array with the share info. 73 | * The key/value pairs included in the share info depend on the function originally called: 74 | * If called by getItem(s)Shared: id, item_type, item, item_source, share_type, share_with, permissions, stime, file_source 75 | * If called by getItem(s)SharedWith: id, item_type, item, item_source, item_target, share_type, share_with, permissions, stime, file_source, file_target 76 | * This function allows the backend to control the output of shared items with custom formats. 77 | * It is only called through calls to the public getItem(s)Shared(With) functions. 78 | */ 79 | public function formatItems($items, $format, $parameters = null) { 80 | $addressbooks = array(); 81 | if ($format == self::FORMAT_ADDRESSBOOKS) { 82 | foreach ($items as $item) { 83 | $addressbook = AddrBook::find($item['item_source']); 84 | if ($addressbook) { 85 | $addressbook['displayname'] = $item['item_target']; 86 | $addressbook['permissions'] = $item['permissions']; 87 | $addressbooks[] = $addressbook; 88 | } 89 | } 90 | } 91 | return $addressbooks; 92 | } 93 | 94 | public function getChildren($itemSource) { 95 | $query = \OCP\DB::prepare('SELECT `id`, `fullname` FROM `'.ContactsApp::ContactsTable.'` WHERE `addressbookid` = ?'); 96 | $result = $query->execute(array($itemSource)); 97 | $children = array(); 98 | while ($contact = $result->fetchRow()) { 99 | $children[] = array('source' => $contact['id'], 'target' => $contact['fullname']); 100 | } 101 | return $children; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /lib/share/backend/contact.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace OCA\ContactsPlus\Share\Backend; 23 | 24 | use \OCA\ContactsPlus\Addressbook as AddrBook; 25 | use \OCA\ContactsPlus\App as ContactsApp; 26 | use \OCA\ContactsPlus\VCard; 27 | 28 | class Contact implements \OCP\Share_Backend { 29 | 30 | const FORMAT_CONTACT = 0; 31 | 32 | private static $contact; 33 | 34 | public function isValidSource($itemSource, $uidOwner) { 35 | self::$contact = VCard::find($itemSource); 36 | if (self::$contact) { 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | public function generateTarget($itemSource, $shareWith, $exclude = null) { 43 | // TODO Get default addressbook and check for conflicts 44 | return self::$contact['fullname']; 45 | } 46 | 47 | public function isShareTypeAllowed($shareType) { 48 | return true; 49 | } 50 | 51 | public function formatItems($items, $format, $parameters = null) { 52 | $contacts = array(); 53 | if ($format == self::FORMAT_CONTACT) { 54 | foreach ($items as $item) { 55 | $contact = VCard::find($item['item_source']); 56 | $contact['fullname'] = $item['item_target']; 57 | $contacts[] = $contact; 58 | } 59 | } 60 | return $contacts; 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /lib/vobject/stringpropertycategories.php: -------------------------------------------------------------------------------- 1 | name; 18 | if ($this->group) { 19 | $str = $this->group . '.' . $this->name; 20 | } 21 | 22 | 23 | $src = array( 24 | ';', 25 | ); 26 | $out = array( 27 | ',', 28 | ); 29 | 30 | if(is_array($this->value)){ 31 | $this->value = implode(',',$this->value); 32 | } 33 | 34 | $value = strtr($this->value, array('\,' => ',', '\;' => ';')); 35 | $str.=':' . str_replace($src, $out, $value); 36 | 37 | $out = ''; 38 | while(strlen($str) > 0) { 39 | if (strlen($str) > 75) { 40 | $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; 41 | $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); 42 | } else { 43 | $out .= $str . "\r\n"; 44 | $str = ''; 45 | break; 46 | } 47 | } 48 | 49 | return $out; 50 | 51 | 52 | } 53 | 54 | 55 | } -------------------------------------------------------------------------------- /templates/contact.show.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 18 |
19 | 20 | 21 | 22 | 30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 51 |
52 | 53 | 57 | 58 |
59 |
60 |   61 | 62 | 63 |
'; 69 | } 70 | ?> 71 |   72 | 73 | 74 | 75 |
76 | 77 |   78 | 79 | 80 | '; 86 | } 87 | ?> 88 | 89 |   90 | 91 | 92 | 93 |
94 | 95 |   96 | 97 | 98 | 99 | '; 105 | } 106 | ?> 107 |   108 |
109 | 110 |   111 | 112 | '; 118 | } 119 | ?> 120 |   121 |
122 | 123 |   124 | 125 | '; 131 | } 132 | ?> 133 |   134 |
135 | 136 |   137 | 138 | '; 144 | } 145 | 146 | ?> 147 | 148 | 149 | 150 | 151 |
152 |
153 | 154 |
155 | 156 |   157 | 158 | 159 | 160 | 163 | t("Birthday"));?> 164 |
165 |   166 | 167 | 168 | 169 | 170 |
171 | t("Notice"));?>
172 |
173 | 174 |
175 |
176 | 177 |
178 |
179 | 180 | 183 | 184 | 187 |
188 |
189 | 190 | 191 | 192 | 193 | 194 | 195 |
196 |
197 |
-------------------------------------------------------------------------------- /templates/index.php: -------------------------------------------------------------------------------- 1 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 43 |
44 | 45 | 46 | 47 |
48 | 65 |
66 |
67 | 70 |
71 |
72 | 73 | 74 | 75 |
76 |
t('Primary address (for Contacts or similar)')); ?>
77 |
78 |
t('iOS/OS X')); ?>
79 |
80 |
t('iOS/OS X Support Groups (experimentel)')); ?>
81 |
82 | 88 | /> t('Activition IOS Support for Groups')); ?> 89 | 90 |
91 | 92 |
93 | 94 |
95 |
96 |
97 | 98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 | 106 | 107 | 108 |
109 |
110 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
125 | 126 |
127 | 136 |
137 | t('Displayname')); ?> 138 | t('Phone')); ?> 139 | 140 | t('Group')); ?> 141 |   142 |
143 | 144 |
145 | 146 |
147 |
    148 |
    149 |
    150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /templates/part.cropphoto.php: -------------------------------------------------------------------------------- 1 | getCache()->hasKey($_['tmpkey'])) { 3 | 4 | $imgurl=''; 5 | ?> 6 | 7 |
    13 | 14 | 15 | 16 | 17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 |
    25 | 26 |
    27 | 28 | t('The temporary image has been removed from cache.')); 31 | } 32 | -------------------------------------------------------------------------------- /templates/part.import.php: -------------------------------------------------------------------------------- 1 | '404')); 6 | $file = $_['filename']; 7 | }else{ 8 | $file = \OC\Files\Filesystem::file_get_contents($_['path'] . '/' . $_['filename']); 9 | } 10 | 11 | $import = new OCA\Kontakte\Import($file); 12 | $import->setUserID(OCP\User::getUser()); 13 | //$newaddressbookname = OCP\Util::sanitizeHTML($import->createAddressbookName()); 14 | //$guessedaddressbookname = OCP\Util::sanitizeHTML($import->guessAddressbookName()); 15 | $newaddressbookname = ''; 16 | $guessedaddressbookname = ''; 17 | */ 18 | //loading calendars for select box 19 | $newaddressbookname = ''; 20 | $guessedaddressbookname = ''; 21 | $contacts_options = OCA\ContactsPlus\Addressbook::all(OCP\USER::getUser()); 22 | 23 | ?> 24 |
    "> 25 |
    26 |
    27 | 28 | 29 | 30 | 31 |
    t('Please choose the addressbook')); ?>
    32 | 48 |

    49 |
    50 |
    51 |
    t('A addressbook with this name already exists. If you continue anyhow, these addressbooks will be merged.')); ?>
    52 |
    53 |
    54 | 55 | 56 |
    57 | 58 | 59 |
    60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 | 67 |
    68 |
    69 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | . 10 | 11 | 12 | 13 | 14 | ../contactsplus 15 | 16 | ../contactsplus/l10n 17 | ../contactsplus/tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/unit/importtest.php: -------------------------------------------------------------------------------- 1 | getCount(); 10 | $this->assertEquals(0, $count); 11 | } 12 | 13 | /** 14 | * @expectedException Exception 15 | * @expectedExceptionMessage No user id set 16 | */ 17 | public function testImportNoUserId() { 18 | $file = ''; 19 | $import = new \OCA\ContactsPlus\Import($file); 20 | $import->import(); 21 | } 22 | 23 | } --------------------------------------------------------------------------------