├── .bowerrc ├── .gitignore ├── .jshintrc ├── .scrutinizer.yml ├── .travis.yml ├── CONTRIBUTING.md ├── COPYING-README ├── Changelog ├── Makefile ├── README.md ├── admin.php ├── appinfo ├── app.php ├── database.xml ├── info.xml ├── remote.php ├── routes.php └── update.php ├── carddav.php ├── composer.json ├── controller ├── pagecontroller.php └── settingscontroller.php ├── css ├── contacts.css ├── jquery.Jcrop.min.css ├── jquery.ocaddnew.css ├── multi-select.css ├── placeholder_polyfill.css ├── placeholder_polyfill.min.css └── share.css ├── factory └── utilfactory.php ├── formats ├── backend_ldap_inet_org_person_connector.xml ├── backend_ldap_vcard_unassigned_connector.xml ├── import_csv_gmail_connector.xml ├── import_csv_outlook_connector.xml ├── import_csv_thunderbird_connector.xml ├── import_csv_yahoo_connector.xml ├── import_ldif_phpldapadmin_connector.xml ├── import_vcard_gmail_connector.xml ├── import_vcard_standard_connector.xml ├── import_vcard_yahoo_connector.xml └── vcardunassigned.schema ├── img ├── 170x170.gif ├── contacts.png ├── contacts.svg ├── globe.svg └── person_large.png ├── js ├── addressbooks.js ├── admin.js ├── app.js ├── config.php ├── contacts.js ├── groups.js ├── jquery.multi-autocomplete.js ├── jquery.ocaddnew.js ├── loader.js ├── modernizr.custom.js ├── otherbackendconfig.js ├── placeholder_polyfill.jquery.js ├── placeholder_polyfill.jquery.min.combo.js ├── share.js └── storage.js ├── l10n ├── .gitkeep ├── .tx │ └── config ├── ar.js ├── ar.json ├── ast.js ├── ast.json ├── bg_BG.js ├── bg_BG.json ├── bs.js ├── bs.json ├── ca.js ├── ca.json ├── cs_CZ.js ├── cs_CZ.json ├── da.js ├── da.json ├── de.js ├── de.json ├── de_DE.js ├── de_DE.json ├── el.js ├── el.json ├── en_GB.js ├── en_GB.json ├── eo.js ├── eo.json ├── es.js ├── es.json ├── es_AR.js ├── es_AR.json ├── es_MX.js ├── es_MX.json ├── et_EE.js ├── et_EE.json ├── eu.js ├── eu.json ├── fi_FI.js ├── fi_FI.json ├── fr.js ├── fr.json ├── gl.js ├── gl.json ├── he.js ├── he.json ├── hr.js ├── hr.json ├── hu_HU.js ├── hu_HU.json ├── id.js ├── id.json ├── it.js ├── it.json ├── ja.js ├── ja.json ├── ko.js ├── ko.json ├── lt_LT.js ├── lt_LT.json ├── lv.js ├── lv.json ├── mk.js ├── mk.json ├── nb_NO.js ├── nb_NO.json ├── nl.js ├── nl.json ├── no-php ├── oc.js ├── oc.json ├── owncloud_contacts_2016-02-29T12_46_35Z_ecbb7e13aa9e49399fee7072086f8eb0.zip ├── pl.js ├── pl.json ├── pt_BR.js ├── pt_BR.json ├── pt_PT.js ├── pt_PT.json ├── ro.js ├── ro.json ├── ru.js ├── ru.json ├── sk_SK.js ├── sk_SK.json ├── sl.js ├── sl.json ├── sq.js ├── sq.json ├── sr.js ├── sr.json ├── sv.js ├── sv.json ├── th_TH.js ├── th_TH.json ├── tr.js ├── tr.json ├── uk.js ├── uk.json ├── xgettextfiles ├── zh_CN.js ├── zh_CN.json ├── zh_TW.js └── zh_TW.json ├── lib ├── abstractpimcollection.php ├── abstractpimobject.php ├── addressbook.php ├── addressbookprovider.php ├── app.php ├── backend │ ├── abstractbackend.php │ ├── database.php │ ├── ldap.php │ └── shared.php ├── carddav │ ├── addressbook.php │ ├── addressbookroot.php │ ├── backend.php │ ├── card.php │ ├── plugin.php │ └── useraddressbooks.php ├── connector │ ├── importconnector.php │ ├── importcsvconnector.php │ ├── importldifconnector.php │ ├── importvcardconnector.php │ └── ldapconnector.php ├── contact.php ├── controller.php ├── controller │ ├── addressbookcontroller.php │ ├── backendcontroller.php │ ├── contactcontroller.php │ ├── contactphotocontroller.php │ ├── exportcontroller.php │ ├── groupcontroller.php │ └── importcontroller.php ├── dispatcher.php ├── hooks.php ├── imageresponse.php ├── importmanager.php ├── ipimobject.php ├── jsonresponse.php ├── middleware │ └── http.php ├── sabre │ ├── appenabledplugin.php │ ├── auth.php │ ├── exceptionloggerplugin.php │ ├── maintenanceplugin.php │ └── principal.php ├── search │ ├── contact.php │ └── provider.php ├── share │ ├── addressbook.php │ └── contact.php ├── textdownloadresponse.php ├── utils │ ├── jsonserializer.php │ ├── properties.php │ ├── temporaryphoto.php │ └── temporaryphoto │ │ ├── contact.php │ │ ├── filesystem.php │ │ ├── uploaded.php │ │ └── user.php └── vobject │ ├── groupproperty.php │ └── vcard.php ├── settings.php ├── templates ├── admin.php ├── contacts.php └── importdialog.html ├── tests ├── bootstrap.php ├── clover.xml ├── phpunit.xml ├── preseed-config.php └── unit │ ├── controller │ ├── PageControllerTest.php │ └── SettingsControllerTest.php │ ├── data │ ├── test1.vcf │ ├── test2.vcf │ ├── test3.vcf │ ├── test4.vcf │ ├── test5.vcf │ └── test6.vcf │ └── lib │ ├── addressbookTest.php │ ├── addressbookproviderTest.php │ ├── backend │ ├── backend_test.php │ └── mock.php │ └── vobjectTest.php └── vendor ├── blueimp-canvas-to-blob ├── .bower.json ├── bower.json └── js │ ├── canvas-to-blob.js │ └── canvas-to-blob.min.js ├── blueimp-file-upload ├── .bower.json ├── bower.json ├── css │ ├── jquery.fileupload-noscript.css │ ├── jquery.fileupload-ui-noscript.css │ ├── jquery.fileupload-ui.css │ └── jquery.fileupload.css ├── img │ ├── loading.gif │ └── progressbar.gif └── js │ ├── cors │ ├── jquery.postmessage-transport.js │ └── jquery.xdr-transport.js │ ├── jquery.fileupload-angular.js │ ├── jquery.fileupload-audio.js │ ├── jquery.fileupload-image.js │ ├── jquery.fileupload-jquery-ui.js │ ├── jquery.fileupload-process.js │ ├── jquery.fileupload-ui.js │ ├── jquery.fileupload-validate.js │ ├── jquery.fileupload-video.js │ ├── jquery.fileupload.js │ ├── jquery.iframe-transport.js │ └── vendor │ └── jquery.ui.widget.js ├── blueimp-load-image ├── .bower.json ├── bower.json └── js │ ├── load-image-exif-map.js │ ├── load-image-exif.js │ ├── load-image-ios.js │ ├── load-image-meta.js │ ├── load-image-orientation.js │ ├── load-image.all.min.js │ └── load-image.js ├── blueimp-md5 ├── .bower.json ├── bower.json └── js │ ├── md5.js │ └── md5.min.js ├── blueimp-tmpl ├── .bower.json ├── bower.json └── js │ ├── compile.js │ ├── runtime.js │ ├── tmpl.js │ └── tmpl.min.js ├── combobox.js ├── jcrop ├── .bower.json ├── MIT-LICENSE.txt ├── README.md ├── css │ ├── Jcrop.gif │ ├── jquery.Jcrop.css │ └── jquery.Jcrop.min.css └── js │ ├── jquery.Jcrop.js │ ├── jquery.Jcrop.min.js │ ├── jquery.color.js │ └── jquery.min.js ├── jquery.onfontresize ├── .bower.json └── index.js ├── jquery ├── .bower.json ├── MIT-LICENSE.txt ├── bower.json ├── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map └── src │ ├── ajax.js │ ├── ajax │ ├── jsonp.js │ ├── load.js │ ├── parseJSON.js │ ├── parseXML.js │ ├── script.js │ ├── var │ │ ├── nonce.js │ │ └── rquery.js │ └── xhr.js │ ├── attributes.js │ ├── attributes │ ├── attr.js │ ├── classes.js │ ├── prop.js │ ├── support.js │ └── val.js │ ├── callbacks.js │ ├── core.js │ ├── core │ ├── access.js │ ├── init.js │ ├── parseHTML.js │ ├── ready.js │ └── var │ │ └── rsingleTag.js │ ├── css.js │ ├── css │ ├── addGetHookIf.js │ ├── curCSS.js │ ├── defaultDisplay.js │ ├── hiddenVisibleSelectors.js │ ├── support.js │ ├── swap.js │ └── var │ │ ├── cssExpand.js │ │ ├── getStyles.js │ │ ├── isHidden.js │ │ ├── rmargin.js │ │ └── rnumnonpx.js │ ├── data.js │ ├── data │ ├── Data.js │ ├── accepts.js │ └── var │ │ ├── data_priv.js │ │ └── data_user.js │ ├── deferred.js │ ├── deprecated.js │ ├── dimensions.js │ ├── effects.js │ ├── effects │ ├── Tween.js │ └── animatedSelector.js │ ├── event.js │ ├── event │ ├── alias.js │ └── support.js │ ├── exports │ ├── amd.js │ └── global.js │ ├── intro.js │ ├── jquery.js │ ├── manipulation.js │ ├── manipulation │ ├── _evalUrl.js │ ├── support.js │ └── var │ │ └── rcheckableType.js │ ├── offset.js │ ├── outro.js │ ├── queue.js │ ├── queue │ └── delay.js │ ├── selector-native.js │ ├── selector-sizzle.js │ ├── selector.js │ ├── serialize.js │ ├── sizzle │ └── dist │ │ ├── sizzle.js │ │ ├── sizzle.min.js │ │ └── sizzle.min.map │ ├── traversing.js │ ├── traversing │ ├── findFilter.js │ └── var │ │ └── rneedsContext.js │ ├── var │ ├── arr.js │ ├── class2type.js │ ├── concat.js │ ├── hasOwn.js │ ├── indexOf.js │ ├── pnum.js │ ├── push.js │ ├── rnotwhite.js │ ├── slice.js │ ├── strundefined.js │ ├── support.js │ └── toString.js │ └── wrap.js └── ui-multiselect ├── .bower.json ├── CHANGELOG ├── GPL-LICENSE ├── MIT-LICENSE ├── README.markdown ├── i18n ├── jquery.multiselect.br.js ├── jquery.multiselect.cs.js ├── jquery.multiselect.de.js ├── jquery.multiselect.es.js ├── jquery.multiselect.filter.br.js ├── jquery.multiselect.filter.cs.js ├── jquery.multiselect.filter.de.js ├── jquery.multiselect.filter.es.js ├── jquery.multiselect.filter.fr.js ├── jquery.multiselect.filter.it.js ├── jquery.multiselect.filter.ja.js ├── jquery.multiselect.filter.tr.js ├── jquery.multiselect.filter.zh-cn.js ├── jquery.multiselect.filter.zh-tw.js ├── jquery.multiselect.fr.js ├── jquery.multiselect.it.js ├── jquery.multiselect.ja.js ├── jquery.multiselect.tr.js ├── jquery.multiselect.zh-cn.js └── jquery.multiselect.zh-tw.js ├── jquery.multiselect.css ├── jquery.multiselect.filter.css ├── screenshot.gif └── src ├── jquery.multiselect.filter.js ├── jquery.multiselect.filter.min.js ├── jquery.multiselect.js └── jquery.multiselect.min.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /3rdparty 2 | # just sane ignores 3 | .*.sw[po] 4 | *.bak 5 | *.BAK 6 | *~ 7 | *.orig 8 | *.class 9 | .cvsignore 10 | Thumbs.db 11 | *.py[co] 12 | _darcs/* 13 | CVS/* 14 | .svn/* 15 | RCS/* 16 | *.backup* 17 | 18 | # kdevelop 19 | .kdev 20 | *.kdev4 21 | *.kate-swp 22 | 23 | # kate editor 24 | .kateproject* 25 | 26 | # Lokalize 27 | *lokalize* 28 | 29 | # eclipse 30 | .project 31 | .settings 32 | 33 | # netbeans 34 | nbproject 35 | 36 | # phpStorm 37 | .idea 38 | *.iml 39 | 40 | # geany 41 | *.geany 42 | 43 | # Cloud9IDE 44 | .settings.xml 45 | .c9revisions 46 | 47 | # vim ex mode 48 | .vimrc 49 | 50 | # Mac OS 51 | .DS_Store 52 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "laxbreak": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "newcap": true, 6 | "forin": true, 7 | "quotmark": "single", 8 | "unused": true, 9 | "jquery": true, 10 | "browser": true, 11 | "devel": true, 12 | "globals": { 13 | "OC": false, 14 | "Modernizr": false, 15 | "error": false 16 | }, 17 | "maxerr": 100 18 | } -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - 'js/modernizr.custom.js' 4 | - 'js/jquery.onfontresize.js' 5 | - 'js/placeholder*' 6 | - 'css/placeholder*' 7 | - 'l10n/*' 8 | - 'templates/*' 9 | - 'vendor/*' 10 | - 'css/jquery*' 11 | - 'css/multi-select.css' 12 | 13 | imports: 14 | - php 15 | - javascript 16 | 17 | 18 | tools: 19 | external_code_coverage: true 20 | 21 | js_hint: 22 | use_native_config: true 23 | 24 | php_mess_detector: 25 | config: 26 | controversial_rules: 27 | camel_case_class_name: true 28 | camel_case_property_name: true 29 | camel_case_method_name: true 30 | camel_case_parameter_name: true 31 | camel_case_variable_name: true 32 | superglobals: false 33 | 34 | php_code_sniffer: 35 | config: 36 | sniffs: 37 | generic: 38 | white_space: 39 | disallow_space_indent_sniff: true 40 | 41 | # squiz: 42 | # white_space: 43 | # logical_operator_spacing_sniff: false 44 | # language_construct_spacing_sniff: true 45 | # operator_spacing_sniff: true 46 | # control_structure_spacing_sniff: true 47 | # function_spacing_sniff: false 48 | # scope_keyword_spacing_sniff: true 49 | # semicolon_spacing_sniff: true 50 | # object_operator_spacing_sniff: false 51 | # functions: 52 | # lowercase_function_keywords_sniff: true 53 | # commenting: 54 | # class_comment_sniff: true 55 | # doc_comment_alignment_sniff: false 56 | # function_comment_throw_tag_sniff: true 57 | # strings: 58 | # double_quote_usage_sniff: true 59 | # operators: 60 | # valid_logical_operators_sniff: true 61 | # psr1: 62 | # files: 63 | # side_effects_sniff: false 64 | # psr2: 65 | # classes: 66 | # class_declaration_sniff: false 67 | # namespaces: 68 | # namespace_declaration_sniff: true 69 | # pear: 70 | # commenting: 71 | # inline_comment_sniff: true 72 | # control_structures: 73 | # control_signature_sniff: true 74 | # naming_conventions: 75 | # valid_function_name_sniff: true 76 | # valid_class_name_sniff: true 77 | 78 | -------------------------------------------------------------------------------- /.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 | - APP_NAME=contacts 14 | matrix: 15 | - DB=sqlite 16 | 17 | branches: 18 | only: 19 | - master 20 | - /^stable\d*$/ 21 | 22 | before_install: 23 | - wget https://raw.githubusercontent.com/owncloud/administration/master/travis-ci/before_install.sh 24 | - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB 25 | - cd ../core 26 | - php occ app:enable $APP_NAME 27 | 28 | script: 29 | # Test lint 30 | - cd apps/$APP_NAME 31 | - find . -name \*.php -exec php -l "{}" \; 32 | 33 | # Run phpunit tests 34 | - cd tests 35 | - phpunit --configuration phpunit.xml --testsuite unit-tests 36 | 37 | # Create coverage report 38 | - sh -c "if [ '$TRAVIS_PHP_VERSION' != 'hhvm' ]; then wget https://scrutinizer-ci.com/ocular.phar; fi" 39 | - sh -c "if [ '$TRAVIS_PHP_VERSION' != 'hhvm' ]; then php ocular.phar code-coverage:upload --format=php-clover clover.xml; fi" 40 | 41 | matrix: 42 | include: 43 | - php: 5.4 44 | env: DB=mysql 45 | - php: 5.4 46 | env: DB=pgsql 47 | - php: 5.4 48 | env: "DB=mysql CORE_BRANCH=stable8.2" 49 | allow_failures: 50 | - php: hhvm 51 | - php: 7 52 | fast_finish: true 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting issues 2 | 3 | If you have questions about how to install or use ownCloud, please direct these to the [mailing list][mailinglist] or our [forum][forum]. We are also available on [IRC][irc]. 4 | 5 | ### Short version 6 | 7 | * The [**issue template can be found here**][template]. Please always use the issue template when reporting issues. 8 | 9 | ### Guidelines 10 | * Please search the existing issues first, it's likely that your issue was already reported or even fixed. 11 | - Go to one of the repositories, click "issues" and type any word in the top search/command bar. 12 | - You can also filter by appending e. g. "state:open" to the search string. 13 | - More info on [search syntax within github](https://help.github.com/articles/searching-issues) 14 | * This repository ([contacts](https://github.com/owncloud/contacts/issues)) is *only* for issues within the ownCloud contacts code. 15 | * __SECURITY__: Report any potential security bug to security@owncloud.com following our [security policy](https://owncloud.org/security/) instead of filing an issue in our bug tracker 16 | * Report the issue using our [template][template], it includes all the information we need to track down the issue. 17 | 18 | Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. 19 | 20 | [template]: https://raw.github.com/owncloud/core/master/issue_template.md 21 | [mailinglist]: https://mailman.owncloud.org/mailman/listinfo/owncloud 22 | [forum]: https://forum.owncloud.org/ 23 | [irc]: https://webchat.freenode.net/?channels=owncloud&uio=d4 24 | 25 | ### Contribute Code and translations 26 | Please check [core's contribution guidelines](https://github.com/owncloud/core/blob/master/CONTRIBUTING.md) for further information about contributing code and translations. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building the project 2 | 3 | app_name=contacts 4 | project_dir=$(CURDIR)/../$(app_name) 5 | build_dir=$(CURDIR)/build/artifacts 6 | appstore_dir=$(build_dir)/appstore 7 | source_dir=$(build_dir)/source 8 | package_name=$(app_name) 9 | 10 | all: dist 11 | 12 | clean: 13 | rm -rf $(build_dir) 14 | 15 | appstore: clean 16 | mkdir -p $(appstore_dir) 17 | tar cvzf $(appstore_dir)/$(package_name).tar.gz $(project_dir) \ 18 | --exclude-vcs \ 19 | --exclude=$(project_dir)/build \ 20 | --exclude=$(project_dir)/build/artifacts \ 21 | --exclude=$(project_dir)/js/node_modules \ 22 | --exclude=$(project_dir)/js/.bowerrc \ 23 | --exclude=$(project_dir)/.jshintrc \ 24 | --exclude=$(project_dir)/.jshintignore \ 25 | --exclude=$(project_dir)/.travis.yml \ 26 | --exclude=$(project_dir)/.scrutinizer.yml \ 27 | --exclude=$(project_dir)/phpunit*xml \ 28 | --exclude=$(project_dir)/Makefile \ 29 | --exclude=$(project_dir)/tests \ 30 | --exclude=$(project_dir)/l10n/.tx \ 31 | --exclude=$(project_dir)/l10n/no-php \ 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #The Contacts app 2 | 3 | Build status 4 | ------------ 5 | 6 | | branch | status | 7 | | ----------- | ------ | 8 | | master | [![Build Status](http://img.shields.io/travis/owncloud/contacts.svg)](https://travis-ci.org/owncloud/contacts) | 9 | 10 | 11 | ##Maintainer: 12 | - unmaintained 13 | 14 | ###Design / front-end: 15 | - [Jan ten Bokkel](https://github.com/jbtbnl) 16 | 17 | ###Additional maintainers: 18 | - [Bart Visscher](https://github.com/bartv2) 19 | 20 | ###Original author: 21 | - Jakob Sack 22 | 23 | Developer setup info: 24 | --------------------- 25 | 26 | This repository contains the Contacts app >= ownCloud 6. Earlier versions are in 27 | https://github.com/owncloud/apps/ in the `stable5` and `stable45` branches. 28 | Any issues should be posted in [this repository](https://github.com/owncloud/contacts/issues), no matter what version. 29 | 30 | ### Master branch: 31 | This branch is still in development. Do not use it in a production environment. 32 | To test it you will need the master branch of [owncloud/core](https://github.com/owncloud/core) and 33 | remember to [update the 3rdparty submodule](http://blog.jacius.info/git-submodule-cheat-sheet/) 34 | 35 | -------------------------------------------------------------------------------- /admin.php: -------------------------------------------------------------------------------- 1 | fetchPage(); -------------------------------------------------------------------------------- /appinfo/app.php: -------------------------------------------------------------------------------- 1 | getNavigationManager()->add(array( 16 | 'id' => 'contacts', 17 | 'order' => 10, 18 | 'href' => \OCP\Util::linkToRoute('contacts_index'), 19 | 'icon' => \OCP\Util::imagePath( 'contacts', 'contacts.svg' ), 20 | 'name' => \OCP\Util::getL10N('contacts')->t('Contacts') 21 | ) 22 | ); 23 | 24 | \OCP\Util::connectHook('OC_User', 'post_createUser', '\OCA\Contacts\Hooks', 'userCreated'); 25 | \OCP\Util::connectHook('OC_User', 'post_deleteUser', '\OCA\Contacts\Hooks', 'userDeleted'); 26 | \OCP\Util::connectHook('OCA\Contacts', 'pre_deleteAddressBook', '\OCA\Contacts\Hooks', 'addressBookDeletion'); 27 | \OCP\Util::connectHook('OCA\Contacts', 'pre_deleteContact', '\OCA\Contacts\Hooks', 'contactDeletion'); 28 | \OCP\Util::connectHook('OCA\Contacts', 'post_createContact', 'OCA\Contacts\Hooks', 'contactAdded'); 29 | \OCP\Util::connectHook('OCA\Contacts', 'post_updateContact', '\OCA\Contacts\Hooks', 'contactUpdated'); 30 | \OCP\Util::connectHook('OCA\Contacts', 'scanCategories', '\OCA\Contacts\Hooks', 'scanCategories'); 31 | \OCP\Util::connectHook('OCA\Contacts', 'indexProperties', '\OCA\Contacts\Hooks', 'indexProperties'); 32 | \OCP\Util::connectHook('OC_Calendar', 'getEvents', 'OCA\Contacts\Hooks', 'getBirthdayEvents'); 33 | \OCP\Util::connectHook('OC_Calendar', 'getSources', 'OCA\Contacts\Hooks', 'getCalenderSources'); 34 | 35 | $request = \OC::$server->getRequest(); 36 | if (isset($request->server['REQUEST_URI'])) { 37 | $url = $request->server['REQUEST_URI']; 38 | 39 | if (preg_match('%index.php/apps/files(/.*)?%', $url)) { 40 | \OCP\Util::addscript('contacts', 'loader'); 41 | } 42 | } 43 | 44 | \OC::$server->getSearch()->registerProvider('OCA\Contacts\Search\Provider', array('apps' => array('contacts'))); 45 | \OCP\Share::registerBackend('contact', 'OCA\Contacts\Share\Contact'); 46 | \OCP\Share::registerBackend('addressbook', 'OCA\Contacts\Share\Addressbook', 'contact'); 47 | //\OCP\App::registerPersonal('contacts','personalsettings'); 48 | \OCP\App::registerAdmin('contacts', 'admin'); 49 | 50 | if (\OCP\User::isLoggedIn()) { 51 | $cm = \OC::$server->getContactsManager(); 52 | $cm->register(function() use ($cm) { 53 | $userId = \OC::$server->getUserSession()->getUser()->getUID(); 54 | $app = new App($userId); 55 | $addressBooks = $app->getAddressBooksForUser(); 56 | foreach ($addressBooks as $addressBook) { 57 | if ($addressBook->isActive()) { 58 | $cm->registerAddressBook($addressBook->getSearchProvider()); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /appinfo/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | contacts 4 | Contacts 5 | AGPL 6 | Jakob Sack,Thomas Tanghus 7 | Address book with CardDAV support. 8 | 9 | 10 | http://doc.owncloud.org/server/8.0/user_manual/pim/contacts.html 11 | 12 | 13 | appinfo/remote.php 14 | appinfo/remote.php 15 | 16 | 17 | 18 | 19 | 20 | 168708 21 | 0.5.0.0 22 | 23 | -------------------------------------------------------------------------------- /appinfo/remote.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | */ 24 | 25 | if (!\OC::$server->getAppManager()->isInstalled('contacts')) { 26 | throw new Exception('App not installed: contacts'); 27 | } 28 | 29 | if (substr(OCP\Util::getRequestUri(), 0, strlen(OC_App::getAppWebPath('contacts').'/carddav.php')) 30 | === OC_App::getAppWebPath('contacts').'/carddav.php' 31 | ) { 32 | $baseuri = OC_App::getAppWebPath('contacts').'/carddav.php'; 33 | } 34 | 35 | // only need authentication apps 36 | $RUNTIME_APPTYPES = array('authentication'); 37 | OC_App::loadApps($RUNTIME_APPTYPES); 38 | 39 | // Backends 40 | $authBackend = new \OCA\Contacts\Sabre\Auth( 41 | \OC::$server->getSession(), 42 | \OC::$server->getUserSession() 43 | ); 44 | $principalBackend = new \OCA\Contacts\Sabre\Principal( 45 | \OC::$server->getConfig(), 46 | \OC::$server->getUserManager() 47 | ); 48 | 49 | $addressbookbackends = array(); 50 | $addressbookbackends[] = new OCA\Contacts\Backend\Database(\OCP\User::getUser()); 51 | $backends = array('local', 'shared'); 52 | if (\OCP\Config::getAppValue('contacts', 'backend_ldap', "false") === "true") { 53 | $backends[] = 'ldap'; 54 | } 55 | $carddavBackend = new OCA\Contacts\CardDAV\Backend($backends); 56 | 57 | // Root nodes 58 | $principalCollection = new \Sabre\CalDAV\Principal\Collection($principalBackend); 59 | $principalCollection->disableListing = true; // Disable listing 60 | 61 | $addressBookRoot = new OCA\Contacts\CardDAV\AddressBookRoot($principalBackend, $carddavBackend); 62 | $addressBookRoot->disableListing = true; // Disable listing 63 | 64 | $nodes = array( 65 | $principalCollection, 66 | $addressBookRoot, 67 | ); 68 | 69 | // Fire up server 70 | $server = new \Sabre\DAV\Server($nodes); 71 | $server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); 72 | $server->setBaseUri($baseuri); 73 | // Add plugins 74 | $server->addPlugin(new \OCA\Contacts\Sabre\MaintenancePlugin()); 75 | $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud')); 76 | $server->addPlugin(new OCA\Contacts\CardDAV\Plugin()); 77 | $server->addPlugin(new \Sabre\DAVACL\Plugin()); 78 | $server->addPlugin(new \Sabre\CardDAV\VCFExportPlugin()); 79 | $server->addPlugin(new \OCA\Contacts\Sabre\ExceptionLoggerPlugin('carddav', \OC::$server->getLogger())); 80 | $server->addPlugin(new \OCA\Contacts\Sabre\AppEnabledPlugin( 81 | 'contacts', 82 | OC::$server->getAppManager() 83 | )); 84 | 85 | 86 | // And off we go! 87 | $server->exec(); 88 | -------------------------------------------------------------------------------- /appinfo/update.php: -------------------------------------------------------------------------------- 1 | =')) { 13 | // Set all address books active as (de)activating went awol at rewrite. 14 | $stmt = OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `active`= 1'); 15 | $result = $stmt->execute(array()); 16 | } elseif (version_compare($installedVersion, '0.2.4', '==')) { 17 | // First set all address books in-active. 18 | $stmt = OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `active`=0'); 19 | $result = $stmt->execute(array()); 20 | 21 | // Then get all the active address books. 22 | $stmt = OCP\DB::prepare('SELECT `userid`,`configvalue` FROM `*PREFIX*preferences` WHERE `appid`=\'contacts\' AND `configkey`=\'openaddressbooks\''); 23 | $result = $stmt->execute(array()); 24 | 25 | // Prepare statement for updating the new 'active' field. 26 | $stmt = OCP\DB::prepare('UPDATE `*PREFIX*contacts_addressbooks` SET `active`=? WHERE `id`=? AND `userid`=?'); 27 | while ($row = $result->fetchRow()) { 28 | $ids = explode(';', $row['configvalue']); 29 | foreach ($ids as $id) { 30 | $r = $stmt->execute(array(1, $id, $row['userid'])); 31 | } 32 | } 33 | 34 | // Remove the old preferences. 35 | $stmt = OCP\DB::prepare( 'DELETE FROM `*PREFIX*preferences` WHERE `appid`=\'contacts\' AND `configkey`=\'openaddressbooks\''); 36 | $result = $stmt->execute(array()); 37 | } 38 | if (version_compare($installedVersion, '0.3.0.14', '==')) { 39 | // Rebuild this while we can 40 | $stmt = OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_ocu_cards`'); 41 | $result = $stmt->execute(array()); 42 | $stmt = OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_ocu_cards_properties`'); 43 | $result = $stmt->execute(array()); 44 | } 45 | 46 | if(version_compare($installedVersion, '0.3.0.18', '<')){ 47 | $stmt = OCP\DB::prepare('DELETE FROM `*PREFIX*contacts_cards_properties` 48 | WHERE NOT EXISTS(SELECT NULL 49 | FROM `*PREFIX*contacts_cards` 50 | WHERE `*PREFIX*contacts_cards`.id = `*PREFIX*contacts_cards_properties`.contactid)'); 51 | $result = $stmt->execute(array()); 52 | } 53 | -------------------------------------------------------------------------------- /carddav.php: -------------------------------------------------------------------------------- 1 | importManager = $importManager; 41 | $this->utilFactory = $utilFactory; 42 | } 43 | 44 | /** 45 | * @NoAdminRequired 46 | * @NoCSRFRequired 47 | */ 48 | public function index() { 49 | $imppTypes = Properties::getTypesForProperty('IMPP'); 50 | $adrTypes = Properties::getTypesForProperty('ADR'); 51 | $phoneTypes = Properties::getTypesForProperty('TEL'); 52 | $emailTypes = Properties::getTypesForProperty('EMAIL'); 53 | $cloudTypes = Properties::getTypesForProperty('CLOUD'); 54 | $ims = Properties::getIMOptions(); 55 | $imProtocols = array(); 56 | foreach($ims as $name => $values) { 57 | $imProtocols[$name] = $values['displayname']; 58 | } 59 | 60 | $maxUploadFilesize = $this->utilFactory->maxUploadFilesize('/'); 61 | 62 | \OCP\Util::addScript('placeholder', null); 63 | \OCP\Util::addScript('../vendor/blueimp-md5/js/md5', null); 64 | \OCP\Util::addScript('jquery.avatar', null); 65 | \OCP\Util::addScript('avatar', null); 66 | 67 | $response = new TemplateResponse($this->appName, 'contacts'); 68 | $response->setParams([ 69 | 'uploadMaxFilesize' => $maxUploadFilesize, 70 | 'uploadMaxHumanFilesize' => $this->utilFactory->humanFileSize($maxUploadFilesize), 71 | 'phoneTypes' => $phoneTypes, 72 | 'emailTypes' => $emailTypes, 73 | 'cloudTypes' => $cloudTypes, 74 | 'adrTypes' => $adrTypes, 75 | 'imppTypes' => $imppTypes, 76 | 'imProtocols' => $imProtocols, 77 | 'importManager' => $this->importManager, 78 | ]); 79 | 80 | return $response; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /controller/settingscontroller.php: -------------------------------------------------------------------------------- 1 | config = $config; 41 | $this->userId = $UserId; 42 | } 43 | 44 | /** 45 | * @NoAdminRequired 46 | * 47 | * @param string $key 48 | * @param string $value 49 | * @return JSONResponse 50 | */ 51 | public function set($key = '', $value = '') { 52 | $response = new JSONResponse(); 53 | 54 | if($key === '' || $value === '') { 55 | $response->setStatus(Http::STATUS_PRECONDITION_FAILED); 56 | return $response; 57 | } 58 | 59 | try { 60 | $this->config->setUserValue($this->userId, $this->appName, $key, $value); 61 | $response->setData([ 62 | 'key' => $key, 63 | 'value' => $value, 64 | ]); 65 | return $response; 66 | } catch (\Exception $e) { 67 | $response->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR); 68 | return $response; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /css/jquery.Jcrop.min.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.min.css v0.9.12 (build:20130126) */ 2 | .jcrop-holder{direction:ltr;text-align:left;} 3 | .jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;} 4 | .jcrop-vline{height:100%;width:1px!important;} 5 | .jcrop-vline.right{right:0;} 6 | .jcrop-hline{height:1px!important;width:100%;} 7 | .jcrop-hline.bottom{bottom:0;} 8 | .jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;} 9 | .jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;} 10 | .jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;} 11 | .jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;} 12 | .jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;} 13 | .jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;} 14 | .jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;} 15 | .jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;} 16 | .jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;} 17 | .jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;} 18 | .jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;} 19 | .jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;} 20 | .jcrop-dragbar.ord-n{margin-top:-4px;} 21 | .jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;} 22 | .jcrop-dragbar.ord-e{margin-right:-4px;right:0;} 23 | .jcrop-dragbar.ord-w{margin-left:-4px;} 24 | .jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;} 25 | .jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;} 26 | .jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;} 27 | .jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;} 28 | .solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;} 29 | .jcrop-holder img,img.jcrop-preview{max-width:none;} 30 | -------------------------------------------------------------------------------- /css/jquery.ocaddnew.css: -------------------------------------------------------------------------------- 1 | 2 | .oc-addnew { 3 | display: block !important; 4 | padding: 0 !important; 5 | } 6 | .oc-addnew li:first-child { 7 | display: block; 8 | } 9 | .oc-addnew li:not(:first-child) { 10 | display: none; 11 | } 12 | .oc-addnew.open li:first-child { 13 | display: none; 14 | } 15 | .oc-addnew.open li:not(:first-child) { 16 | display: block; 17 | } 18 | .oc-addnew > li { 19 | } 20 | .oc-addnew > li > a.oc-addnew-init { 21 | } 22 | .oc-addnew > li > a.oc-addnew-init:before { 23 | position: absolute; 24 | content: '+'; 25 | font-weight: bold; 26 | font-size: 150%; 27 | left: 12px; 28 | } 29 | #app-settings .oc-addnew > li > a.oc-addnew-init:before { 30 | position: absolute; 31 | content: '+'; 32 | font-weight: bold; 33 | font-size: 150%; 34 | left: 0; 35 | } 36 | #app-settings .oc-addnew > li > a.oc-addnew-init { 37 | padding-left: 17px; 38 | } 39 | 40 | #app-navigation .oc-addnew input[type=text] { 41 | margin-top: 5px; 42 | margin-left: 6px; 43 | height: 34px !important; 44 | } 45 | 46 | .oc-addnew select { 47 | width: 133px; 48 | margin-top: 1px !important; 49 | height: 30px; 50 | background-color: #eee; 51 | } 52 | 53 | .oc-addnew input, 54 | .oc-addnew select { 55 | margin: 0 0 5px 0; 56 | display: inline-block; 57 | float: left; 58 | border-top-right-radius: 0; 59 | border-bottom-right-radius: 0; 60 | border-right: 0; 61 | } 62 | 63 | .oc-addnew button { 64 | margin: 5px 0; 65 | display: inline-block; 66 | float: left; 67 | border-bottom-left-radius: 0; 68 | border-top-left-radius: 0; 69 | height: 34px !important; 70 | } 71 | 72 | .oc-addnew .action-button { 73 | width: 30px; 74 | background-position: center; 75 | background-repeat: no-repeat; 76 | } 77 | 78 | #app-navigation .oc-addnew .new-button { 79 | width: 32px; 80 | margin-top: 5px; 81 | background-size: 16px; 82 | background-position: center; 83 | background-repeat: no-repeat; 84 | } 85 | 86 | #app-settings .oc-addnew .new-button { 87 | margin-left: -8px; 88 | } 89 | 90 | .oc-addnew .create-button { 91 | border-radius: 0; 92 | background-image: url('../img/checkmark-gray.svg'); 93 | border-top-right-radius: 5px; 94 | border-bottom-right-radius: 5px; 95 | } -------------------------------------------------------------------------------- /css/multi-select.css: -------------------------------------------------------------------------------- 1 | .ms-container{ 2 | background: transparent url('../img/switch.png') no-repeat 50% 50%; 3 | width: 370px; 4 | } 5 | 6 | .ms-container:after{ 7 | content: "."; 8 | display: block; 9 | height: 0; 10 | line-height: 0; 11 | font-size: 0; 12 | clear: both; 13 | min-height: 0; 14 | visibility: hidden; 15 | } 16 | 17 | .ms-container .ms-selectable, .ms-container .ms-selection{ 18 | background: #fff; 19 | color: #555555; 20 | float: left; 21 | width: 45%; 22 | } 23 | .ms-container .ms-selection{ 24 | float: right; 25 | } 26 | 27 | .ms-container .ms-list{ 28 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 29 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 30 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 31 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 32 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 33 | -ms-transition: border linear 0.2s, box-shadow linear 0.2s; 34 | -o-transition: border linear 0.2s, box-shadow linear 0.2s; 35 | transition: border linear 0.2s, box-shadow linear 0.2s; 36 | border: 1px solid #ccc; 37 | -webkit-border-radius: 3px; 38 | -moz-border-radius: 3px; 39 | border-radius: 3px; 40 | position: relative; 41 | height: 200px; 42 | padding: 0; 43 | overflow-y: auto; 44 | } 45 | 46 | .ms-container .ms-list.ms-focus{ 47 | border-color: rgba(82, 168, 236, 0.8); 48 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 49 | -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 50 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); 51 | outline: 0; 52 | outline: thin dotted \9; 53 | } 54 | 55 | .ms-container ul{ 56 | margin: 0; 57 | list-style-type: none; 58 | padding: 0; 59 | } 60 | 61 | .ms-container .ms-optgroup-container{ 62 | width: 100%; 63 | } 64 | 65 | .ms-container .ms-optgroup-label{ 66 | margin: 0; 67 | padding: 5px 0px 0px 5px; 68 | cursor: pointer; 69 | color: #999; 70 | } 71 | 72 | .ms-container .ms-selectable li.ms-elem-selectable, 73 | .ms-container .ms-selection li.ms-elem-selection{ 74 | border-bottom: 1px #eee solid; 75 | padding: 2px 10px; 76 | color: #555; 77 | font-size: 14px; 78 | } 79 | 80 | .ms-container .ms-selectable li.ms-hover, 81 | .ms-container .ms-selection li.ms-hover{ 82 | cursor: pointer; 83 | color: #fff; 84 | text-decoration: none; 85 | background-color: #08c; 86 | } 87 | 88 | .ms-container .ms-selectable li.disabled, 89 | .ms-container .ms-selection li.disabled{ 90 | background-color: #eee; 91 | color: #aaa; 92 | cursor: text; 93 | } -------------------------------------------------------------------------------- /css/placeholder_polyfill.css: -------------------------------------------------------------------------------- 1 | span.placeholder{ 2 | position:absolute; 3 | font-size:75%; 4 | color:#999; 5 | font-family:sans-serif; 6 | padding:4px 3px; 7 | z-index:1; 8 | cursor:text; 9 | } 10 | 11 | span.placeholder-hide-except-screenreader { 12 | clip: rect(1px 1px 1px 1px); 13 | clip: rect(1px, 1px, 1px, 1px); 14 | padding:0 !important; 15 | border:0 !important; 16 | height: 1px !important; 17 | width: 1px !important; 18 | overflow: hidden; 19 | } 20 | 21 | span.placeholder-hide{ 22 | display:none; 23 | } 24 | 25 | /* overwrite for the HTML5 Boilerplate way to hide labels */ 26 | label.visuallyhidden-with-placeholder{ 27 | /*clip: auto !important;*/ 28 | height:auto !important; 29 | overflow: visible !important; 30 | position:absolute !important; 31 | left:-999em; 32 | } -------------------------------------------------------------------------------- /css/placeholder_polyfill.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Html5 Placeholder Polyfill - v2.0.3 - 2012-08-21 3 | * web: * http://blog.ginader.de/dev/jquery/HTML5-placeholder-polyfill/ 4 | * issues: * https://github.com/ginader/HTML5-placeholder-polyfill/issues 5 | * Copyright (c) 2012 Dirk Ginader; Licensed MIT, GPL 6 | */ 7 | label span.placeholder{position:absolute;font-size:75%;color:#999;font-family:sans-serif;padding:4px 3px;z-index:1;cursor:text}label span.placeholder-hide-except-screenreader{clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px);padding:0!important;border:0!important;height:1px!important;width:1px!important;overflow:hidden} 8 | label span.placeholder-hide{display:none}label.visuallyhidden-with-placeholder{height:auto!important;overflow:visible!important;position:absolute!important;left:-999em} -------------------------------------------------------------------------------- /factory/utilfactory.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 60 | 64 | 65 | 66 | 67 | vcard_standard 68 | Standard VCard 69 | vcard 70 | 1 71 | BEGIN:VCARD 72 | END:VCARD 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /formats/import_vcard_yahoo_connector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 60 | 64 | 65 | 66 | 67 | vcard_yahoo 68 | Yahoo VCard 69 | vcard 70 | 1 71 | BEGIN:VCARD 72 | END:VCARD 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /formats/vcardunassigned.schema: -------------------------------------------------------------------------------- 1 | attributetype ( 1.3.6.1.4.1.4203.666.1.41 2 | NAME 'vcardcountry' 3 | DESC 'RFC2256: ISO-3166 country 2-letter code multiple values allowed' 4 | SUP name 5 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{2} ) 6 | 7 | attributetype ( 1.3.6.1.4.1.4203.666.1.42 8 | NAME 'unassignedproperty' 9 | EQUALITY caseIgnoreMatch 10 | SUBSTR caseIgnoreSubstringsMatch 11 | SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} ) 12 | 13 | objectClass ( 1.3.6.1.4.1.4203.666.1.100 14 | NAME 'VCardUnassigned' 15 | DESC 'VCard unassigned properties' 16 | SUP inetOrgPerson 17 | STRUCTURAL 18 | MAY ( unassignedproperty $ vcardcountry ) 19 | ) 20 | -------------------------------------------------------------------------------- /img/170x170.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/img/170x170.gif -------------------------------------------------------------------------------- /img/contacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/img/contacts.png -------------------------------------------------------------------------------- /img/contacts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/person_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/img/person_large.png -------------------------------------------------------------------------------- /js/config.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE 10 | * License as published by the Free Software Foundation; either 11 | * version 3 of the License, or any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU AFFERO GENERAL PUBLIC LICENSE for more details. 17 | * 18 | * You should have received a copy of the GNU Affero General Public 19 | * License along with this library. If not, see . 20 | * 21 | */ 22 | 23 | OCP\JSON::setContentTypeHeader('text/javascript'); 24 | OCP\JSON::checkLoggedIn(); 25 | OCP\JSON::checkAppEnabled('contacts'); 26 | 27 | $user = \OC::$server->getUserSession()->getUser()->getUId(); 28 | 29 | $groupsort = OCP\Config::getUserValue($user, 'contacts', 'groupsort', ''); 30 | $groupsort = explode(',', $groupsort); 31 | $tmp = array(); 32 | foreach ($groupsort as $group) { 33 | if (is_int($group)) { 34 | $tmp[] = $group; 35 | } 36 | } 37 | 38 | $groupsort = implode(',', $tmp); 39 | 40 | echo 'var contacts_groups_sortorder=[' . $groupsort . '],'; 41 | echo 'contacts_lastgroup=\'' . OCP\Config::getUserValue($user, 'contacts', 'lastgroup', 'all') . '\','; 42 | echo 'contacts_sortby=\'' . OCP\Config::getUserValue($user, 'contacts', 'sortby', 'fn') . '\','; 43 | echo 'contacts_properties_indexed = ' 44 | . (OCP\Config::getUserValue($user, 'contacts', 'contacts_properties_indexed', 'no') === 'no' 45 | ? 'false' : 'true') . ','; 46 | echo 'contacts_categories_indexed = ' 47 | . (OCP\Config::getUserValue($user, 'contacts', 'contacts_categories_indexed', 'no') === 'no' 48 | ? 'false' : 'true') . ','; 49 | echo 'lang=\'' . OCP\Config::getUserValue($user, 'core', 'lang', 'en') . '\';'; 50 | -------------------------------------------------------------------------------- /js/jquery.multi-autocomplete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inspired by http://jqueryui.com/demos/autocomplete/#multiple 3 | */ 4 | 5 | (function( $ ) { 6 | $.widget('ui.multiple_autocomplete', { 7 | _create: function() { 8 | var self = this; 9 | function split( val ) { 10 | return val.split( /,\s*/ ); 11 | } 12 | function extractLast( term ) { 13 | return split( term ).pop(); 14 | } 15 | function showOptions() { 16 | if(!self.element.autocomplete('widget').is(':visible') && self.element.val().trim() == '') { 17 | self.element.autocomplete('search', ''); 18 | } 19 | } 20 | //console.log('_create: ' + this.options['id']); 21 | this.element.bind('click', function( event ) { 22 | showOptions(); 23 | }); 24 | this.element.bind('input', function( event ) { 25 | showOptions(); 26 | }); 27 | this.element.bind('blur', function( event ) { 28 | var tmp = self.element.val().trim(); 29 | if(tmp[tmp.length-1] == ',') { 30 | self.element.val(tmp.substring(0, tmp.length-1)); 31 | } else { 32 | self.element.val(tmp); 33 | } 34 | if(self.element.val().trim() != '') { 35 | self.element.trigger('change'); // Changes wasn't saved when only using the dropdown. 36 | } 37 | }); 38 | this.element.bind( "keydown", function( event ) { 39 | if ( event.keyCode === $.ui.keyCode.TAB && 40 | $( this ).data( "autocomplete" ).menu.active ) { 41 | event.preventDefault(); 42 | } 43 | }) 44 | .autocomplete({ 45 | minLength: 0, 46 | source: function( request, response ) { 47 | // delegate back to autocomplete, but extract the last term 48 | response( $.ui.autocomplete.filter( 49 | self.options.source, extractLast( request.term ) ) ); 50 | }, 51 | focus: function() { 52 | // prevent value inserted on focus 53 | return false; 54 | }, 55 | select: function( event, ui ) { 56 | var terms = split( this.value ); 57 | // remove the current input 58 | terms.pop(); 59 | // add the selected item 60 | terms.push( ui.item.value ); 61 | // add placeholder to get the comma-and-space at the end 62 | terms.push( "" ); 63 | this.value = terms.join( ", " ); 64 | return false; 65 | } 66 | }); 67 | /*this.button = $( "" ) 68 | .attr( "tabIndex", -1 ) 69 | .attr( "title", "Show All Items" ) 70 | .insertAfter( this.element ) 71 | .addClass('svg') 72 | .addClass('action') 73 | .addClass('combo-button') 74 | .click(function() { 75 | // close if already visible 76 | if ( self.element.autocomplete( "widget" ).is( ":visible" ) ) { 77 | self.element.autocomplete( "close" ); 78 | return; 79 | } 80 | 81 | // work around a bug (likely same cause as #5265) 82 | $( this ).blur(); 83 | 84 | var tmp = self.element.val().trim(); 85 | if(tmp[tmp.length-1] != ',') { 86 | self.element.val(tmp+', '); 87 | } 88 | // pass empty string as value to search for, displaying all results 89 | self.element.autocomplete( "search", "" ); 90 | self.element.focus(); 91 | });*/ 92 | }, 93 | }); 94 | })( jQuery ); 95 | -------------------------------------------------------------------------------- /l10n/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/l10n/.gitkeep -------------------------------------------------------------------------------- /l10n/.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | lang_map = ja_JP: ja 4 | 5 | 6 | [owncloud.contacts] 7 | file_filter = /contacts.po 8 | source_file = templates/contacts.pot 9 | source_lang = en 10 | type = PO 11 | 12 | -------------------------------------------------------------------------------- /l10n/no-php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /l10n/owncloud_contacts_2016-02-29T12_46_35Z_ecbb7e13aa9e49399fee7072086f8eb0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/l10n/owncloud_contacts_2016-02-29T12_46_35Z_ecbb7e13aa9e49399fee7072086f8eb0.zip -------------------------------------------------------------------------------- /l10n/xgettextfiles: -------------------------------------------------------------------------------- 1 | ../appinfo/app.php 2 | ../ajax/activation.php 3 | ../ajax/addbook.php 4 | ../ajax/addproperty.php 5 | ../ajax/createaddressbook.php 6 | ../ajax/deletebook.php 7 | ../ajax/deleteproperty.php 8 | ../ajax/contactdetails.php 9 | ../ajax/saveproperty.php 10 | ../ajax/updateaddressbook.php 11 | ../lib/app.php 12 | ../templates/index.php 13 | ../templates/part.chooseaddressbook.php 14 | ../templates/part.chooseaddressbook.rowfields.php 15 | ../templates/part.editaddressbook.php 16 | ../templates/part.property.php 17 | ../templates/part.setpropertyform.php 18 | ../templates/settings.php 19 | -------------------------------------------------------------------------------- /lib/abstractpimobject.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts; 24 | 25 | /** 26 | * Subclass this class or implement IPIMObject interface for PIM objects. 27 | */ 28 | 29 | abstract class AbstractPIMObject implements IPIMObject { 30 | 31 | /** 32 | * If this object is part of a collection return a reference 33 | * to the parent object, otherwise return null. 34 | * @return IPIMObject|null 35 | */ 36 | public function getParent() { 37 | } 38 | 39 | /** 40 | * @param integer $permission 41 | * @return integer 42 | */ 43 | public function hasPermission($permission) { 44 | return $this->getPermissions() & $permission; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /lib/carddav/addressbookroot.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\CardDAV; 24 | 25 | /** 26 | * This class overrides \Sabre\CardDAV\AddressBookRoot::getChildForPrincipal() 27 | * to instantiate OC_Connector_CardDAV_UserAddressBooks. 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 | } 48 | -------------------------------------------------------------------------------- /lib/carddav/card.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\CardDAV; 24 | 25 | use OCA\Contacts; 26 | 27 | /** 28 | * This class overrides \Sabre\CardDAV\Card::getACL() 29 | * to return read/write permissions based on user and shared state. 30 | */ 31 | class Card extends \Sabre\CardDAV\Card { 32 | 33 | /** 34 | * Array with information about the containing addressbook 35 | * 36 | * @var array 37 | */ 38 | protected $addressBookInfo; 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param \Sabre\CardDAV\Backend\AbstractBackend $carddavBackend 44 | * @param array $addressBookInfo 45 | * @param array $cardData 46 | */ 47 | public function __construct(\Sabre\CardDAV\Backend\AbstractBackend $carddavBackend, array $addressBookInfo, array $cardData) { 48 | 49 | $this->addressBookInfo = $addressBookInfo; 50 | parent::__construct($carddavBackend, $addressBookInfo, $cardData); 51 | 52 | } 53 | 54 | /** 55 | * Returns a list of ACE's for this node. 56 | * 57 | * Each ACE has the following properties: 58 | * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are 59 | * currently the only supported privileges 60 | * * 'principal', a url to the principal who owns the node 61 | * * 'protected' (optional), indicating that this ACE is not allowed to 62 | * be updated. 63 | * 64 | * @return array 65 | */ 66 | public function getACL() { 67 | 68 | $readprincipal = $this->getOwner(); 69 | $writeprincipal = $this->getOwner(); 70 | $uid = $this->carddavBackend->userIDByPrincipal($this->getOwner()); 71 | $currentUid = \OC::$server->getUserSession()->getUser()->getUId(); 72 | 73 | if($uid != $currentUid) { 74 | list(, $id) = explode('::', $this->addressBookInfo['id']); 75 | $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id); 76 | if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { 77 | $readprincipal = 'principals/' . $currentUid; 78 | } 79 | if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) { 80 | $writeprincipal = 'principals/' . $currentUid; 81 | } 82 | } 83 | 84 | return array( 85 | array( 86 | 'privilege' => '{DAV:}read', 87 | 'principal' => $readprincipal, 88 | 'protected' => true, 89 | ), 90 | array( 91 | 'privilege' => '{DAV:}write', 92 | 'principal' => $writeprincipal, 93 | 'protected' => true, 94 | ), 95 | 96 | ); 97 | 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /lib/carddav/plugin.php: -------------------------------------------------------------------------------- 1 | . 22 | * 23 | */ 24 | 25 | namespace OCA\Contacts\CardDAV; 26 | 27 | use Sabre\VObject; 28 | use OCA\Contacts\VObject\VCard; 29 | 30 | /** 31 | * This class overrides \Sabre\CardDAV\Plugin::validateVCard() to be able 32 | * to import partially invalid vCards by ignoring invalid lines and to 33 | * validate and upgrade using \OCA\Contacts\VCard. 34 | */ 35 | class Plugin extends \Sabre\CardDAV\Plugin { 36 | 37 | /** 38 | * Checks if the submitted vCard data is in fact, valid. 39 | * 40 | * An exception is thrown if it's not. 41 | * 42 | * @param resource|string $data 43 | * @param boolean $modified whether the data was modified 44 | * @return void 45 | */ 46 | protected function validateVCard(&$data, &$modified) { 47 | 48 | // If it's a stream, we convert it to a string first. 49 | if (is_resource($data)) { 50 | $data = stream_get_contents($data); 51 | } elseif (!is_string($data)) { 52 | throw new \Exception(__METHOD__ . ' argument 1 only supports string or stream resource.'); 53 | } 54 | 55 | try { 56 | $vobj = VObject\Reader::read($data, VObject\Reader::OPTION_IGNORE_INVALID_LINES); 57 | } catch (VObject\ParseException $e) { 58 | throw new \Sabre\DAV\Exception\UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage()); 59 | } 60 | 61 | if ($vobj->name !== 'VCARD') { 62 | throw new \Sabre\DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.'); 63 | } 64 | 65 | $modified = true; // FIXME: set to false if neither repair nor upgrade was done 66 | $vobj->validate(VCard::REPAIR|VCard::UPGRADE); 67 | $data = $vobj->serialize(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/carddav/useraddressbooks.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\CardDAV; 24 | 25 | /** 26 | * This class overrides \Sabre\CardDAV\UserAddressBooks::getChildren() 27 | * to instantiate \OCA\Contacts\CardDAV\AddressBooks. 28 | */ 29 | class UserAddressBooks extends \Sabre\CardDAV\UserAddressBooks { 30 | 31 | /** 32 | * Returns a list of addressbooks 33 | * 34 | * @return array 35 | */ 36 | public function getChildren() { 37 | 38 | $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri); 39 | $objs = array(); 40 | foreach($addressbooks as $addressbook) { 41 | $objs[] = new AddressBook($this->carddavBackend, $addressbook); 42 | } 43 | return $objs; 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /lib/controller.php: -------------------------------------------------------------------------------- 1 | app = $app; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/controller/backendcontroller.php: -------------------------------------------------------------------------------- 1 | $formatId, 'name' => (string)$format['name'], 'xml' => $format->asXML()); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | return $response->setData($formats); 48 | } 49 | 50 | /** 51 | * @NoAdminRequired 52 | * @NoCSRFRequired 53 | */ 54 | public function enableBackend() { 55 | $response = new JSONResponse(); 56 | $params = $this->request->urlParams; 57 | $backend = $params['backend']; 58 | $enable = $params['enable']; 59 | return $response->setData(\OCP\Config::setAppValue('contacts', 'backend_'.$backend, $enable)); 60 | } 61 | /** 62 | * @NoAdminRequired 63 | * @NoCSRFRequired 64 | */ 65 | public function backendStatus() { 66 | $response = new JSONResponse(); 67 | $params = $this->request->urlParams; 68 | $backend = $params['backend']; 69 | $enabled = \OCP\Config::getAppValue('contacts', 'backend_'.$backend, "false"); 70 | return $response->setData($enabled); 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /lib/controller/exportcontroller.php: -------------------------------------------------------------------------------- 1 | request->urlParams; 33 | 34 | $addressBook = $this->app->getAddressBook($params['backend'], $params['addressBookId']); 35 | $lastModified = $addressBook->lastModified(); 36 | 37 | $contacts = ''; 38 | foreach($addressBook->getChildren() as $i => $contact) { 39 | $contacts .= $contact->serialize() . "\r\n"; 40 | } 41 | $name = str_replace(' ', '_', $addressBook->getDisplayName()) . '.vcf'; 42 | $response = new TextDownloadResponse($contacts, $name, 'text/directory'); 43 | if(!is_null($lastModified)) { 44 | $response->addHeader('Cache-Control', 'private, must-revalidate'); 45 | $response->setLastModified(\DateTime::createFromFormat('U', $lastModified) ?: null); 46 | $response->setETag(md5($lastModified)); 47 | } 48 | 49 | return $response; 50 | } 51 | 52 | /** 53 | * Export a single contact. 54 | * 55 | * @NoAdminRequired 56 | * @NoCSRFRequired 57 | */ 58 | public function exportContact() { 59 | 60 | $params = $this->request->urlParams; 61 | 62 | $addressBook = $this->app->getAddressBook($params['backend'], $params['addressBookId']); 63 | $contact = $addressBook->getChild($params['contactId']); 64 | 65 | if(!$contact) { 66 | $response = new JSONResponse(); 67 | $response->bailOut(App::$l10n->t('Couldn\'t find contact.')); 68 | return $response; 69 | } 70 | 71 | $name = str_replace(' ', '_', $contact->getDisplayName()) . '.vcf'; 72 | return new TextDownloadResponse($contact->serialize(), $name, 'text/vcard'); 73 | } 74 | 75 | /** 76 | * Export a selected range of contacts potentially from different backends and address books. 77 | * 78 | * @NoAdminRequired 79 | * @NoCSRFRequired 80 | */ 81 | public function exportSelected() { 82 | $targets = json_decode($this->request['t']); 83 | 84 | $exports = ''; 85 | foreach($targets as $backend => $addressBooks) { 86 | foreach($addressBooks as $addressBookId => $contacts) { 87 | $addressBook = $this->app->getAddressBook($backend, $addressBookId); 88 | foreach($contacts as $contactId) { 89 | $contact = $addressBook->getChild($contactId); 90 | $exports .= $contact->serialize() . "\r\n"; 91 | } 92 | } 93 | } 94 | 95 | $name = 'Selected_contacts' . '.vcf'; 96 | return new TextDownloadResponse($exports, $name, 'text/vcard'); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /lib/imageresponse.php: -------------------------------------------------------------------------------- 1 | setImage($image); 31 | } 32 | } 33 | 34 | /** 35 | * @param OCP\Image $image 36 | */ 37 | public function setImage(\OCP\Image $image) { 38 | if(!$image->valid()) { 39 | throw new \InvalidArgumentException(__METHOD__. ' The image resource is not valid.'); 40 | } 41 | $this->image = $image; 42 | $this->addHeader('Content-Type', $image->mimeType()); 43 | return $this; 44 | } 45 | 46 | /** 47 | * Return the image data stream 48 | * @return Image data 49 | */ 50 | public function render() { 51 | if(is_null($this->image)) { 52 | throw new \BadMethodCallException(__METHOD__. ' Image must be set either in constructor or with setImage()'); 53 | } 54 | return $this->image->data(); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /lib/jsonresponse.php: -------------------------------------------------------------------------------- 1 | data = $params; 24 | } 25 | 26 | /** 27 | * Sets values in the data json array 28 | * @param array|object $params an array or object which will be transformed 29 | * to JSON 30 | */ 31 | public function setParams(array $params) { 32 | $this->setData($params); 33 | return $this; 34 | } 35 | 36 | public function setData($data) { 37 | $this->data = $data; 38 | return $this; 39 | } 40 | 41 | public function setStatus($status) { 42 | parent::setStatus($status); 43 | return $this; 44 | } 45 | 46 | /** 47 | * in case we want to render an error message, also logs into the owncloud log 48 | * @param string $message the error message 49 | */ 50 | public function setErrorMessage($message){ 51 | $this->error = true; 52 | $this->data = array('status' => 'error', 'data' => array('message' => $message)); 53 | return $this; 54 | } 55 | 56 | public function bailOut($msg, $tracelevel = 1, $debuglevel = \OCP\Util::ERROR) { 57 | if($msg instanceof \Exception) { 58 | $this->setStatus($msg->getCode()); 59 | $msg = $msg->getMessage(); 60 | } 61 | $this->setErrorMessage($msg); 62 | return $this->debug($msg, $tracelevel, $debuglevel); 63 | } 64 | 65 | public function debug($msg, $tracelevel = 0, $debuglevel = \OCP\Util::DEBUG) { 66 | if(!is_numeric($tracelevel)) { 67 | return $this; 68 | } 69 | 70 | if(PHP_VERSION >= "5.4") { 71 | $call = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $tracelevel + 1); 72 | } else { 73 | $call = debug_backtrace(false); 74 | } 75 | 76 | $call = $call[$tracelevel]; 77 | if($debuglevel !== false) { 78 | \OCP\Util::writeLog('contacts', 79 | $call['file'].'. Line: '.$call['line'].': '.$msg, 80 | $debuglevel); 81 | } 82 | return $this; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /lib/middleware/http.php: -------------------------------------------------------------------------------- 1 | . 21 | * 22 | */ 23 | 24 | 25 | namespace OCA\Contacts\Middleware; 26 | 27 | use OCA\Contacts\Controller, 28 | OCA\Contacts\JSONResponse, 29 | OCP\AppFramework\Middleware, 30 | OCP\AppFramework\Http\Response, 31 | OCP\AppFramework\Http as HttpStatus; 32 | 33 | /** 34 | * Used to intercept exceptions thrown in controllers and backends 35 | * and transform them into valid HTTP responses. 36 | */ 37 | class Http extends Middleware { 38 | 39 | /** 40 | * If an Exception is being caught, return a JSON error response with 41 | * a suitable status code 42 | * @param Controller $controller the controller that is being called 43 | * @param string $methodName the name of the method that will be called on 44 | * the controller 45 | * @param \Exception $exception the thrown exception 46 | * @return Response a Response object 47 | */ 48 | public function afterException($controller, $methodName, \Exception $exception) { 49 | \OCP\Util::writeLog('contacts', __METHOD__.' method: '.$methodName, \OCP\Util::DEBUG); 50 | // If there's no proper status code associated, set it to 500. 51 | $response = new JSONResponse(); 52 | if($exception->getCode() < 100) { 53 | $response->setStatus(HttpStatus::STATUS_INTERNAL_SERVER_ERROR); 54 | } else { 55 | $response->setStatus($exception->getCode()); 56 | } 57 | 58 | $response->setErrorMessage($exception->getMessage()); 59 | 60 | \OCP\Util::logException('contacts', $exception); 61 | return $response; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /lib/sabre/appenabledplugin.php: -------------------------------------------------------------------------------- 1 | 4 | * @author Robin Appelman 5 | * @author Vincent Petry 6 | * 7 | * @copyright Copyright (c) 2015, ownCloud, Inc. 8 | * @license AGPL-3.0 9 | * 10 | * This code is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU Affero General Public License, version 3, 12 | * as published by the Free Software Foundation. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU Affero General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Affero General Public License, version 3, 20 | * along with this program. If not, see 21 | * 22 | */ 23 | 24 | namespace OCA\Contacts\Sabre; 25 | 26 | use OCP\App\IAppManager; 27 | use Sabre\DAV\Exception\Forbidden; 28 | use Sabre\DAV\ServerPlugin; 29 | 30 | /** 31 | * Plugin to check if an app is enabled for the current user 32 | */ 33 | class AppEnabledPlugin extends ServerPlugin { 34 | 35 | /** 36 | * Reference to main server object 37 | * 38 | * @var \Sabre\DAV\Server 39 | */ 40 | private $server; 41 | 42 | /** 43 | * @var string 44 | */ 45 | private $app; 46 | 47 | /** 48 | * @var \OCP\App\IAppManager 49 | */ 50 | private $appManager; 51 | 52 | /** 53 | * @param string $app 54 | * @param \OCP\App\IAppManager $appManager 55 | */ 56 | public function __construct($app, IAppManager $appManager) { 57 | $this->app = $app; 58 | $this->appManager = $appManager; 59 | } 60 | 61 | /** 62 | * This initializes the plugin. 63 | * 64 | * This function is called by \Sabre\DAV\Server, after 65 | * addPlugin is called. 66 | * 67 | * This method should set up the required event subscriptions. 68 | * 69 | * @param \Sabre\DAV\Server $server 70 | * @return void 71 | */ 72 | public function initialize(\Sabre\DAV\Server $server) { 73 | 74 | $this->server = $server; 75 | $this->server->on('beforeMethod', array($this, 'checkAppEnabled'), 30); 76 | } 77 | 78 | /** 79 | * This method is called before any HTTP after auth and checks if the user has access to the app 80 | * 81 | * @throws \Sabre\DAV\Exception\Forbidden 82 | * @return bool 83 | */ 84 | public function checkAppEnabled() { 85 | if (!$this->appManager->isEnabledForUser($this->app)) { 86 | throw new Forbidden(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/sabre/maintenanceplugin.php: -------------------------------------------------------------------------------- 1 | 4 | * @author Joas Schilling 5 | * @author Morris Jobke 6 | * @author Robin Appelman 7 | * @author Thomas Müller 8 | * @author Vincent Petry 9 | * 10 | * @copyright Copyright (c) 2015, ownCloud, Inc. 11 | * @license AGPL-3.0 12 | * 13 | * This code is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU Affero General Public License, version 3, 15 | * as published by the Free Software Foundation. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU Affero General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU Affero General Public License, version 3, 23 | * along with this program. If not, see 24 | * 25 | */ 26 | 27 | namespace OCA\Contacts\Sabre; 28 | 29 | use OCP\IConfig; 30 | use Sabre\DAV\Exception\ServiceUnavailable; 31 | use Sabre\DAV\ServerPlugin; 32 | 33 | class MaintenancePlugin extends ServerPlugin { 34 | 35 | /** @var IConfig */ 36 | private $config; 37 | 38 | /** 39 | * Reference to main server object 40 | * 41 | * @var \Sabre\DAV\Server 42 | */ 43 | private $server; 44 | 45 | /** 46 | * @param IConfig $config 47 | */ 48 | public function __construct(IConfig $config = null) { 49 | $this->config = $config; 50 | if (is_null($config)) { 51 | $this->config = \OC::$server->getConfig(); 52 | } 53 | } 54 | 55 | 56 | /** 57 | * This initializes the plugin. 58 | * 59 | * This function is called by \Sabre\DAV\Server, after 60 | * addPlugin is called. 61 | * 62 | * This method should set up the required event subscriptions. 63 | * 64 | * @param \Sabre\DAV\Server $server 65 | * @return void 66 | */ 67 | public function initialize(\Sabre\DAV\Server $server) { 68 | $this->server = $server; 69 | $this->server->on('beforeMethod', array($this, 'checkMaintenanceMode'), 1); 70 | } 71 | 72 | /** 73 | * This method is called before any HTTP method and returns http status code 503 74 | * in case the system is in maintenance mode. 75 | * 76 | * @throws ServiceUnavailable 77 | * @return bool 78 | */ 79 | public function checkMaintenanceMode() { 80 | if ($this->config->getSystemValue('singleuser', false)) { 81 | throw new ServiceUnavailable('System in single user mode.'); 82 | } 83 | if ($this->config->getSystemValue('maintenance', false)) { 84 | throw new ServiceUnavailable('System in maintenance mode.'); 85 | } 86 | if (\OC::checkUpgrade(false)) { 87 | throw new ServiceUnavailable('Upgrade needed'); 88 | } 89 | 90 | return true; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/search/contact.php: -------------------------------------------------------------------------------- 1 | . 18 | * 19 | */ 20 | 21 | namespace OCA\Contacts\Search; 22 | 23 | /** 24 | * A contact search result 25 | */ 26 | class Contact extends \OCP\Search\Result { 27 | 28 | /** 29 | * Type name; translated in templates 30 | * 31 | * @var string 32 | */ 33 | public $type = 'contact'; 34 | 35 | /** 36 | * Contact address 37 | * 38 | * @var string 39 | */ 40 | public $address; 41 | 42 | /** 43 | * Contact phone numbers 44 | * 45 | * @var string 46 | */ 47 | public $phone; 48 | 49 | /** 50 | * Contact e-mail 51 | * 52 | * @var string 53 | */ 54 | public $email; 55 | 56 | /** 57 | * Contact nickname 58 | * 59 | * @var string 60 | */ 61 | public $nickname; 62 | 63 | /** 64 | * Contact organization 65 | * 66 | * @var string 67 | */ 68 | public $organization; 69 | 70 | /** 71 | * Constructor 72 | * 73 | * @param array $data 74 | * @return \OCA\Contacts\Search\Contact 75 | */ 76 | public function __construct(array $data = null) { 77 | $this->id = $data['id']; 78 | $this->name = stripcslashes($data['FN']); 79 | $this->link = \OCP\Util::linkToRoute('contacts_index') . '#' . $data['id']; 80 | $this->address = $this->checkAndMerge($data, 'ADR'); 81 | $this->phone = $this->checkAndMerge($data, 'TEL'); 82 | $this->email = $this->checkAndMerge($data, 'EMAIL'); 83 | $this->nickname = $this->checkAndMerge($data, 'NICKNAME'); 84 | $this->organization = $this->checkAndMerge($data, 'ORG'); 85 | } 86 | 87 | /** 88 | * Check a contact property and return its value; handles properties with 89 | * multiple values by merging them into a comma-separated list 90 | * 91 | * @param array $data 92 | * @param string $property 93 | * @return string or null 94 | */ 95 | private function checkAndMerge($data, $property) { 96 | // check property 97 | if (!is_array($data) || !array_key_exists($property, $data)) { 98 | return null; 99 | } 100 | // check value 101 | if (!is_array($data[$property])) { 102 | return stripcslashes($data[$property]); 103 | } 104 | // or merge array 105 | if (count($data[$property]) > 0) { 106 | $list = array(); 107 | foreach ($data[$property] as $value) { 108 | $list[] = stripcslashes($value); 109 | } 110 | return implode(', ', $list); 111 | } 112 | // default 113 | return null; 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /lib/search/provider.php: -------------------------------------------------------------------------------- 1 | . 18 | * 19 | */ 20 | 21 | namespace OCA\Contacts\Search; 22 | 23 | /** 24 | * The updated contacts search provider 25 | */ 26 | class Provider extends \OCP\Search\Provider { 27 | 28 | /** 29 | * Search for contacts 30 | * 31 | * @param string $query 32 | * @return array list of \OCA\Calendar\Search\Contact 33 | */ 34 | function search($query) { 35 | $_results = \OCP\Contacts::search($query, array('N', 'FN', 'EMAIL', 'NICKNAME', 'ORG')); 36 | $results = array(); 37 | foreach ($_results as $_result) { 38 | $results[] = new \OCA\Contacts\Search\Contact($_result); 39 | } 40 | return $results; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/share/contact.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace OCA\Contacts\Share; 23 | use OCA\Contacts\App; 24 | 25 | class Contact implements \OCP\Share_Backend { 26 | 27 | const FORMAT_CONTACT = 0; 28 | 29 | /** 30 | * @var \OCA\Contacts\App; 31 | */ 32 | public $app; 33 | 34 | /** 35 | * @var \OCA\Contacts\Backend\Database; 36 | */ 37 | public $backend; 38 | 39 | public function __construct() { 40 | $this->app = new App(\OC::$server->getUserSession()->getUser()->getUId()); 41 | $this->backend = $this->app->getBackend('local'); 42 | } 43 | 44 | public function isValidSource($itemSource, $uidOwner) { 45 | // TODO: Cache address books. 46 | $app = new App($uidOwner); 47 | $userAddressBooks = $app->getAddressBooksForUser(); 48 | 49 | foreach ($userAddressBooks as $addressBook) { 50 | if ($addressBook->childExists($itemSource)) { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | public function generateTarget($itemSource, $shareWith, $exclude = null) { 58 | // TODO Get default addressbook and check for conflicts 59 | $contact = $this->backend->getContact(null, $itemSource, 60 | array('noCollection' => true)); 61 | return $contact['fullname']; 62 | } 63 | 64 | public function formatItems($items, $format, $parameters = null) { 65 | $contacts = array(); 66 | if ($format == self::FORMAT_CONTACT) { 67 | foreach ($items as $item) { 68 | $contacts[] = $this->backend->getContact(null, $item, 69 | array('noCollection' => true)); 70 | } 71 | } 72 | return $contacts; 73 | } 74 | 75 | public function isShareTypeAllowed($shareType) { 76 | return true; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /lib/textdownloadresponse.php: -------------------------------------------------------------------------------- 1 | . 21 | * 22 | */ 23 | 24 | 25 | namespace OCA\Contacts; 26 | 27 | use OCP\AppFramework\Http\DownloadResponse; 28 | 29 | /** 30 | * Prompts the user to download the a textfile. 31 | */ 32 | class TextDownloadResponse extends DownloadResponse { 33 | 34 | private $content; 35 | 36 | /** 37 | * Creates a response that prompts the user to download a file which 38 | * contains the passed string 39 | * @param string $content the content that should be written into the file 40 | * @param string $filename the name that the downloaded file should have 41 | * @param string $contentType the mimetype that the downloaded file should have 42 | */ 43 | public function __construct($content, $filename, $contentType){ 44 | parent::__construct($filename, $contentType); 45 | $this->content = $content; 46 | } 47 | 48 | 49 | /** 50 | * Simply sets the headers and returns the file contents 51 | * @return string the file contents 52 | */ 53 | public function render(){ 54 | return $this->content; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /lib/utils/temporaryphoto/contact.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\Utils\TemporaryPhoto; 24 | 25 | use OCA\Contacts\Contact as ContactObject; 26 | use OCA\Contacts\Utils\TemporaryPhoto as BaseTemporaryPhoto; 27 | use OCP\ICache; 28 | 29 | /** 30 | * This class loads the PHOTO or LOGO property from a contact. 31 | */ 32 | class Contact extends BaseTemporaryPhoto { 33 | 34 | /** 35 | * The Contact object to load the image from 36 | * 37 | * @var \OCA\Contacts\Contact 38 | */ 39 | protected $contact; 40 | 41 | public function __construct(ICache $cache, $contact) { 42 | if (!$contact instanceof ContactObject) { 43 | throw new \Exception( 44 | __METHOD__ 45 | . ' Second argument must be an instance of OCA\\Contacts\\Contact' 46 | ); 47 | } 48 | 49 | parent::__construct($cache); 50 | $this->contact = $contact; 51 | $this->processImage(); 52 | } 53 | 54 | /** 55 | * Do what's needed to get the image from storage 56 | * depending on the type. 57 | */ 58 | protected function processImage() { 59 | $this->image = $this->contact->getPhoto(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/utils/temporaryphoto/filesystem.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\Utils\TemporaryPhoto; 24 | 25 | use OCA\Contacts\Contact as ContactObject, 26 | OCA\Contacts\Utils\TemporaryPhoto as BaseTemporaryPhoto, 27 | OCP\AppFramework\Http, 28 | OCP\ICache; 29 | 30 | /** 31 | * This class loads an image from the virtual file system. 32 | */ 33 | class FileSystem extends BaseTemporaryPhoto { 34 | 35 | /** 36 | * The virtual file system path to load the image from 37 | * 38 | * @var string 39 | */ 40 | protected $path; 41 | 42 | public function __construct(ICache $cache, $path) { 43 | \OCP\Util::writeLog('contacts', __METHOD__.' path: ' . $path, \OCP\Util::DEBUG); 44 | if (!is_string($path)) { 45 | throw new \Exception( 46 | __METHOD__ . ' Second argument must a string' 47 | ); 48 | } 49 | 50 | parent::__construct($cache); 51 | $this->path = $path; 52 | $this->processImage(); 53 | } 54 | 55 | /** 56 | * Load the image. 57 | */ 58 | protected function processImage() { 59 | $localPath = \OC\Files\Filesystem::getLocalFile($this->path); 60 | 61 | if (!file_exists($localPath)) { 62 | throw new \Exception( 63 | 'The file does not exist: ' . $localPath, 64 | Http::STATUS_NOT_FOUND 65 | ); 66 | } 67 | 68 | $this->image = new \OCP\Image(); 69 | $this->image->loadFromFile($localPath); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /lib/utils/temporaryphoto/uploaded.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\Utils\TemporaryPhoto; 24 | 25 | use OCA\Contacts\Utils\TemporaryPhoto as BaseTemporaryPhoto; 26 | use OCP\IRequest; 27 | use OCP\AppFramework\Http; 28 | use OCP\Image; 29 | use OCP\ICache; 30 | 31 | /** 32 | * This class loads an image from the virtual file system. 33 | */ 34 | class Uploaded extends BaseTemporaryPhoto { 35 | 36 | /** 37 | * The request to read the data from 38 | * 39 | * @var \OCP\IRequest 40 | */ 41 | protected $request; 42 | 43 | public function __construct(ICache $cache, IRequest $request) { 44 | \OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); 45 | if (!$request instanceOf IRequest) { 46 | throw new \Exception( 47 | __METHOD__ . ' Second argument must be an instance of \\OCP\\IRequest' 48 | ); 49 | } 50 | 51 | parent::__construct($cache); 52 | $this->request = $request; 53 | $this->processImage(); 54 | } 55 | 56 | /** 57 | * Load the image. 58 | */ 59 | protected function processImage() { 60 | // If image has already been read return 61 | if ($this->image instanceOf Image) { 62 | return; 63 | } 64 | $this->image = new Image(); 65 | \OCP\Util::writeLog('contacts', __METHOD__ . ', Content-Type: ' . $this->request->getHeader('Content-Type'), \OCP\Util::DEBUG); 66 | \OCP\Util::writeLog('contacts', __METHOD__ . ', Content-Length: ' . $this->request->getHeader('Content-Length'), \OCP\Util::DEBUG); 67 | 68 | if (substr($this->request->getHeader('Content-Type'), 0, 6) !== 'image/') { 69 | throw new \Exception( 70 | 'Only images can be used as contact photo', 71 | Http::STATUS_UNSUPPORTED_MEDIA_TYPE 72 | ); 73 | } 74 | 75 | $maxSize = \OCP\Util::maxUploadFilesize('/'); 76 | if ($this->request->getHeader('Content-Length') > $maxSize) { 77 | throw new \Exception( 78 | sprintf( 79 | 'The size of the file exceeds the maximum allowed %s', 80 | \OCP\Util::humanFileSize($maxSize) 81 | ), 82 | Http::STATUS_REQUEST_ENTITY_TOO_LARGE 83 | ); 84 | } 85 | 86 | $this->image->loadFromFileHandle($this->request->put); 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /lib/utils/temporaryphoto/user.php: -------------------------------------------------------------------------------- 1 | . 20 | * 21 | */ 22 | 23 | namespace OCA\Contacts\Utils\TemporaryPhoto; 24 | 25 | use OCA\Contacts\Contact as ContactObject, 26 | OCA\Contacts\Utils\TemporaryPhoto as BaseTemporaryPhoto, 27 | OCP\ICache; 28 | 29 | /** 30 | * This class loads the PHOTO or LOGO property from a contact. 31 | */ 32 | class User extends BaseTemporaryPhoto { 33 | 34 | /** 35 | * The Contact object to load the image from 36 | * 37 | * @var OCA\Contacts\Contact 38 | */ 39 | protected $userId; 40 | 41 | public function __construct(ICache $cache, $userId) { 42 | parent::__construct($cache); 43 | // check if userId is a real ownCloud user 44 | if(!in_array($userId, \OCP\User::getUsers())){ 45 | throw new \Exception('Second argument must be an ownCloud user ID'); 46 | } 47 | $this->userId = $userId; 48 | $this->processImage(); 49 | } 50 | 51 | /** 52 | * Do what's needed to get the image from storage 53 | * depending on the type. 54 | */ 55 | protected function processImage() { 56 | $localPath = \OCP\Config::getSystemValue('datadirectory') . '/' . $this->userId . '/avatar.'; 57 | if (file_exists($localPath . 'png')){ 58 | $localPath .= 'png'; 59 | } else if (file_exists($localPath . 'jpg')){ 60 | $localPath .= 'jpg'; 61 | } 62 | 63 | $this->image = new \OCP\Image(); 64 | $this->image->loadFromFile($localPath); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /settings.php: -------------------------------------------------------------------------------- 1 | assign('addressbooks', OCA\Contacts\Addressbook::all(\OC::$server->getUserSession()->getUser()->getUId())); 5 | 6 | $tmpl->printPage(); 7 | -------------------------------------------------------------------------------- /templates/admin.php: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 |

t('Contacts')) ?>

18 | /> 19 |
20 | t('Enable LDAP backend for the contacts application')) ?> 21 |
t('Warning: LDAP Backend is in beta mode, use with precautions')) ?> 22 |
23 | -------------------------------------------------------------------------------- /templates/importdialog.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{selectText}

4 | 8 | 11 |
12 | 20 |
-------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | =')) { 19 | \OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests'); 20 | \OC_App::loadApp('contacts'); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | unit 11 | 12 | 13 | 14 | 15 | 16 | ../../contacts 17 | 18 | ../../contacts/l10n 19 | ../../contacts/templates 20 | ../../contacts/tests 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/preseed-config.php: -------------------------------------------------------------------------------- 1 | '1', 4 | 'appstoreenabled' => false, 5 | 'apps_paths' => 6 | array ( 7 | 0 => 8 | array ( 9 | 'path' => OC::$SERVERROOT.'/apps', 10 | 'url' => '/apps', 11 | 'writable' => false, 12 | ), 13 | 1 => 14 | array ( 15 | 'path' => OC::$SERVERROOT.'/apps2', 16 | 'url' => '/apps2', 17 | 'writable' => false, 18 | ) 19 | ), 20 | ); 21 | -------------------------------------------------------------------------------- /tests/unit/controller/PageControllerTest.php: -------------------------------------------------------------------------------- 1 | request = $this->getMockBuilder('OCP\IRequest') 36 | ->disableOriginalConstructor() 37 | ->getMock(); 38 | $this->importManager = $this->getMockBuilder('\OCA\Contacts\ImportManager') 39 | ->disableOriginalConstructor() 40 | ->getMock(); 41 | $this->utilFactory = $this->getMockBuilder('\OCA\Contacts\Factory\UtilFactory') 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | 45 | $this->appName = 'contacts'; 46 | $this->controller = new PageController( 47 | $this->appName, 48 | $this->request, 49 | $this->importManager, 50 | $this->utilFactory 51 | ); 52 | } 53 | 54 | public function testIndex() { 55 | $expected = new TemplateResponse($this->appName, 'contacts'); 56 | $expected->setParams([ 57 | 'uploadMaxFilesize' => null, 58 | 'uploadMaxHumanFilesize' => null, 59 | 'phoneTypes' => [ 60 | 'HOME' => 'Home', 61 | 'CELL' => 'Mobile', 62 | 'WORK' => 'Work', 63 | 'TEXT' => 'Text', 64 | 'VOICE' => 'Voice', 65 | 'MSG' => 'Message', 66 | 'FAX' => 'Fax', 67 | 'VIDEO' => 'Video', 68 | 'PAGER' => 'Pager', 69 | 'OTHER' => 'Other', 70 | ], 71 | 'emailTypes' => [ 72 | 'WORK' => 'Work', 73 | 'HOME' => 'Home', 74 | 'INTERNET' => 'Internet', 75 | 'OTHER' => 'Other', 76 | ], 77 | 'adrTypes' => [ 78 | 'WORK' => 'Work', 79 | 'HOME' => 'Home', 80 | 'OTHER' => 'Other', 81 | ], 82 | 'imppTypes' => [ 83 | 'WORK' => 'Work', 84 | 'HOME' => 'Home', 85 | 'OTHER' => 'Other', 86 | ], 87 | 'imProtocols' => [ 88 | 'jabber' => 'Jabber', 89 | 'sip' => 'Internet call', 90 | 'aim' => 'AIM', 91 | 'msn' => 'MSN', 92 | 'twitter' => 'Twitter', 93 | 'googletalk' => 'GoogleTalk', 94 | 'facebook' => 'Facebook', 95 | 'xmpp' => 'XMPP', 96 | 'icq' => 'ICQ', 97 | 'yahoo' => 'Yahoo', 98 | 'skype' => 'Skype', 99 | 'qq' => 'QQ', 100 | 'gadugadu' => 'GaduGadu', 101 | 'owncloud-handle' => 'ownCloud', 102 | ], 103 | 'importManager' => $this->importManager, 104 | 'cloudTypes' => [ 105 | 'HOME' => 'Home', 106 | 'WORK' => 'Work', 107 | 'OTHER' => 'Other', 108 | ], 109 | ]); 110 | 111 | $this->assertEquals($expected, $this->controller->index()); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /tests/unit/controller/SettingsControllerTest.php: -------------------------------------------------------------------------------- 1 | request = $this->getMockBuilder('OCP\IRequest') 37 | ->disableOriginalConstructor() 38 | ->getMock(); 39 | $this->config = $this->getMockBuilder('\OCP\IConfig') 40 | ->disableOriginalConstructor() 41 | ->getMock(); 42 | $this->userId = 'JohnDoe'; 43 | 44 | $this->appName = 'contacts'; 45 | $this->controller = new SettingsController( 46 | $this->appName, 47 | $this->request, 48 | $this->config, 49 | $this->userId 50 | ); 51 | } 52 | 53 | public function testSetWithMissingValues() { 54 | $expected = new JSONResponse(); 55 | $expected->setStatus(Http::STATUS_PRECONDITION_FAILED); 56 | 57 | $this->assertEquals($expected, $this->controller->set()); 58 | $this->assertEquals($expected, $this->controller->set('key')); 59 | $this->assertEquals($expected, $this->controller->set('', 'value')); 60 | } 61 | 62 | public function testSetWorking() { 63 | $this->config->expects($this->once()) 64 | ->method('setUserValue') 65 | ->with('JohnDoe', 'contacts', 'keyValue', 'valueValue'); 66 | 67 | $expected = new JSONResponse(); 68 | $expected->setData(['key' => 'keyValue', 'value' => 'valueValue']); 69 | 70 | $this->assertEquals($expected, $this->controller->set('keyValue', 'valueValue')); 71 | } 72 | 73 | public function testSetException() { 74 | $this->config->expects($this->once()) 75 | ->method('setUserValue') 76 | ->with('JohnDoe', 'contacts', 'keyValue', 'valueValue') 77 | ->will($this->throwException(new \Exception())); 78 | 79 | $expected = new JSONResponse(); 80 | $expected->setStatus(Http::STATUS_INTERNAL_SERVER_ERROR); 81 | 82 | $this->assertEquals($expected, $this->controller->set('keyValue', 'valueValue')); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/unit/data/test1.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:3.0 3 | N:Mustermann;Max;;; 4 | FN:Max Mustermann 5 | ORG:ownCloud 6 | URL:http://owncloud.org/ 7 | UID:e472ea61 8 | EMAIL;TYPE=INTERNET:max.mustermann@example.org 9 | TEL;TYPE=voice,work,pref:+49 1234 56788 10 | ADR;TYPE=intl,work,postal,parcel:;;Musterstraße 1;Musterstadt;;12345;Germany 11 | END:VCARD 12 | -------------------------------------------------------------------------------- /tests/unit/data/test2.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:3.0 3 | N:Public;John;Quinlan;; 4 | FN:John Q. Public 5 | ORG:ownCloud 6 | URL:http://owncloud.org/ 7 | UID:e472ea61 8 | EMAIL;TYPE=INTERNET:joe.public@example.org 9 | TEL;TYPE=voice,work,pref:+49 1234 56788 10 | ADR;TYPE=intl,work,postal,parcel:;;Elm Street 1;Metropolis;;12345;USA 11 | END:VCARD 12 | -------------------------------------------------------------------------------- /tests/unit/data/test3.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:2.1 3 | FN;QUOTED-PRINTABLE:Ad=C3=A8le=20Ferm=C3=A9e 4 | TEL;CELL;VOICE:+123456789 5 | END:VCARD 6 | -------------------------------------------------------------------------------- /tests/unit/data/test4.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:2.1 3 | FN:Adieu Demain 4 | NOTE;ENCODING=QUOTED-PRINTABLE:Ouverture =E0 partir de 19h=0D=0AFerm=E9e le mardi hors saison 5 | TEL;WORK;VOICE:12 34 56 78 6 | ADR;HOME;ENCODING=QUOTED-PRINTABLE:;;666=0D=0ARue Vauban;Antibes;;666 7 | LABEL;HOME;ENCODING=QUOTED-PRINTABLE:666=0D=0ARue Vauban=0D=0AAntibes 666 8 | REV:20120610T192843Z 9 | END:VCARD 10 | -------------------------------------------------------------------------------- /tests/unit/data/test5.vcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/tests/unit/data/test5.vcf -------------------------------------------------------------------------------- /tests/unit/data/test6.vcf: -------------------------------------------------------------------------------- 1 | BEGIN:VCARD 2 | VERSION:3.0 3 | PRODID://WebDAV Collaborator 1.1.35-615//Import from Outlook//EN 4 | CLASS:PUBLIC 5 | UID:00000000CBAB1D2F0B42AE46B5131D1FA629CACD84082100 6 | FN:Escaped Parameters 7 | TEL;TYPE=PREF\,WORK\,VOICE:123456789 8 | END:VCARD -------------------------------------------------------------------------------- /tests/unit/lib/vobjectTest.php: -------------------------------------------------------------------------------- 1 | validate($obj::REPAIR); 29 | 30 | $this->assertEquals('2.1', (string)$obj->VERSION); 31 | $this->assertEquals('Adèle Fermée', (string)$obj->FN); 32 | $this->assertEquals('Fermée;Adèle;;;', (string)$obj->N); 33 | } 34 | 35 | public function testEscapedParameters() { 36 | $cardData = file_get_contents(__DIR__ . '/../data/test6.vcf'); 37 | $obj = \Sabre\VObject\Reader::read( 38 | $cardData, 39 | \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES 40 | ); 41 | $obj->validate($obj::REPAIR); 42 | 43 | $this->assertEquals('3.0', (string)$obj->VERSION); 44 | $this->assertEquals('Parameters;Escaped;;;', (string)$obj->N); 45 | $this->assertEquals('TEL;TYPE=PREF\,WORK\,VOICE:123456789' . "\r\n", $obj->TEL->serialize()); 46 | } 47 | 48 | public function testGroupProperty() { 49 | $arr = array( 50 | 'Home', 51 | 'work', 52 | 'Friends, Family', 53 | ); 54 | 55 | $vcard = new \OCA\Contacts\VObject\VCard(); 56 | 57 | $property = $vcard->createProperty('CATEGORIES'); 58 | $property->setParts($arr); 59 | 60 | // Test parsing and serializing 61 | $this->assertEquals('Home,work,Friends\, Family', $property->getValue()); 62 | $this->assertEquals('CATEGORIES:Home,work,Friends\, Family' . "\r\n", $property->serialize()); 63 | $this->assertEquals(3, count($property->getParts())); 64 | 65 | // Test add 66 | $property->addGroup('Coworkers'); 67 | $this->assertTrue($property->hasGroup('coworkers')); 68 | $this->assertEquals(4, count($property->getParts())); 69 | $this->assertEquals('Home,work,Friends\, Family,Coworkers', $property->getValue()); 70 | 71 | // Test remove 72 | $this->assertTrue($property->hasGroup('Friends, fAmIlY')); 73 | $property->removeGroup('Friends, fAmIlY'); 74 | $this->assertEquals(3, count($property->getParts())); 75 | $parts = $property->getParts(); 76 | $this->assertEquals('Coworkers', $parts[2]); 77 | 78 | // Test rename 79 | $property->renameGroup('work', 'Work'); 80 | $parts = $property->getParts(); 81 | $this->assertEquals('Work', $parts[1]); 82 | //$this->assertEquals(true, false); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /vendor/blueimp-canvas-to-blob/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-canvas-to-blob", 3 | "version": "2.1.1", 4 | "title": "JavaScript Canvas to Blob", 5 | "description": "JavaScript Canvas to Blob is a function to convert canvas elements into Blob objects.", 6 | "keywords": [ 7 | "javascript", 8 | "canvas", 9 | "blob", 10 | "convert", 11 | "conversion" 12 | ], 13 | "homepage": "https://github.com/blueimp/JavaScript-Canvas-to-Blob", 14 | "author": { 15 | "name": "Sebastian Tschan", 16 | "url": "https://blueimp.net" 17 | }, 18 | "maintainers": [ 19 | { 20 | "name": "Sebastian Tschan", 21 | "url": "https://blueimp.net" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/blueimp/JavaScript-Canvas-to-Blob.git" 27 | }, 28 | "bugs": "https://github.com/blueimp/JavaScript-Canvas-to-Blob/issues", 29 | "licenses": [ 30 | { 31 | "type": "MIT", 32 | "url": "http://www.opensource.org/licenses/MIT" 33 | } 34 | ], 35 | "main": "js/canvas-to-blob.js", 36 | "ignore": [ 37 | "/*.*", 38 | "test" 39 | ], 40 | "_release": "2.1.1", 41 | "_resolution": { 42 | "type": "version", 43 | "tag": "2.1.1", 44 | "commit": "659a452dfdfbc6f03794fe96bc19bd2b7cd87f03" 45 | }, 46 | "_source": "git://github.com/blueimp/JavaScript-Canvas-to-Blob.git", 47 | "_target": ">=2.1.1", 48 | "_originalSource": "blueimp-canvas-to-blob" 49 | } -------------------------------------------------------------------------------- /vendor/blueimp-canvas-to-blob/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-canvas-to-blob", 3 | "version": "2.1.1", 4 | "title": "JavaScript Canvas to Blob", 5 | "description": "JavaScript Canvas to Blob is a function to convert canvas elements into Blob objects.", 6 | "keywords": [ 7 | "javascript", 8 | "canvas", 9 | "blob", 10 | "convert", 11 | "conversion" 12 | ], 13 | "homepage": "https://github.com/blueimp/JavaScript-Canvas-to-Blob", 14 | "author": { 15 | "name": "Sebastian Tschan", 16 | "url": "https://blueimp.net" 17 | }, 18 | "maintainers": [ 19 | { 20 | "name": "Sebastian Tschan", 21 | "url": "https://blueimp.net" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/blueimp/JavaScript-Canvas-to-Blob.git" 27 | }, 28 | "bugs": "https://github.com/blueimp/JavaScript-Canvas-to-Blob/issues", 29 | "licenses": [ 30 | { 31 | "type": "MIT", 32 | "url": "http://www.opensource.org/licenses/MIT" 33 | } 34 | ], 35 | "main": "js/canvas-to-blob.js", 36 | "ignore": [ 37 | "/*.*", 38 | "test" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /vendor/blueimp-canvas-to-blob/js/canvas-to-blob.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;h=1.6", 51 | "blueimp-tmpl": ">=2.5.4", 52 | "blueimp-load-image": ">=1.13.0", 53 | "blueimp-canvas-to-blob": ">=2.1.1" 54 | }, 55 | "main": [ 56 | "css/jquery.fileupload.css", 57 | "css/jquery.fileupload-ui.css", 58 | "css/jquery.fileupload-noscript.css", 59 | "css/jquery.fileupload-ui-noscript.css", 60 | "js/cors/jquery.postmessage-transport.js", 61 | "js/cors/jquery.xdr-transport.js", 62 | "js/vendor/jquery.ui.widget.js", 63 | "js/jquery.fileupload.js", 64 | "js/jquery.fileupload-process.js", 65 | "js/jquery.fileupload-validate.js", 66 | "js/jquery.fileupload-image.js", 67 | "js/jquery.fileupload-audio.js", 68 | "js/jquery.fileupload-video.js", 69 | "js/jquery.fileupload-ui.js", 70 | "js/jquery.fileupload-jquery-ui.js", 71 | "js/jquery.fileupload-angular.js", 72 | "js/jquery.iframe-transport.js" 73 | ], 74 | "ignore": [ 75 | "/*.*", 76 | "/cors", 77 | "css/demo-ie8.css", 78 | "css/demo.css", 79 | "css/style.css", 80 | "js/app.js", 81 | "js/main.js", 82 | "server", 83 | "test" 84 | ], 85 | "_release": "9.8.0", 86 | "_resolution": { 87 | "type": "version", 88 | "tag": "9.8.0", 89 | "commit": "0742a6283492b86f80abe935e4348e56ee4f6815" 90 | }, 91 | "_source": "git://github.com/blueimp/jQuery-File-Upload.git", 92 | "_target": "~9.8.0", 93 | "_originalSource": "blueimp-file-upload", 94 | "_direct": true 95 | } -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-file-upload", 3 | "version": "9.8.0", 4 | "title": "jQuery File Upload", 5 | "description": "File Upload widget with multiple file selection, drag&drop support, progress bar, validation and preview images, audio and video for jQuery. Supports cross-domain, chunked and resumable file uploads. Works with any server-side platform (Google App Engine, PHP, Python, Ruby on Rails, Java, etc.) that supports standard HTML form file uploads.", 6 | "keywords": [ 7 | "jquery", 8 | "file", 9 | "upload", 10 | "widget", 11 | "multiple", 12 | "selection", 13 | "drag", 14 | "drop", 15 | "progress", 16 | "preview", 17 | "cross-domain", 18 | "cross-site", 19 | "chunk", 20 | "resume", 21 | "gae", 22 | "go", 23 | "python", 24 | "php", 25 | "bootstrap" 26 | ], 27 | "homepage": "https://github.com/blueimp/jQuery-File-Upload", 28 | "author": { 29 | "name": "Sebastian Tschan", 30 | "url": "https://blueimp.net" 31 | }, 32 | "maintainers": [ 33 | { 34 | "name": "Sebastian Tschan", 35 | "url": "https://blueimp.net" 36 | } 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git://github.com/blueimp/jQuery-File-Upload.git" 41 | }, 42 | "bugs": "https://github.com/blueimp/jQuery-File-Upload/issues", 43 | "licenses": [ 44 | { 45 | "type": "MIT", 46 | "url": "http://www.opensource.org/licenses/MIT" 47 | } 48 | ], 49 | "dependencies": { 50 | "jquery": ">=1.6", 51 | "blueimp-tmpl": ">=2.5.4", 52 | "blueimp-load-image": ">=1.13.0", 53 | "blueimp-canvas-to-blob": ">=2.1.1" 54 | }, 55 | "main": [ 56 | "css/jquery.fileupload.css", 57 | "css/jquery.fileupload-ui.css", 58 | "css/jquery.fileupload-noscript.css", 59 | "css/jquery.fileupload-ui-noscript.css", 60 | "js/cors/jquery.postmessage-transport.js", 61 | "js/cors/jquery.xdr-transport.js", 62 | "js/vendor/jquery.ui.widget.js", 63 | "js/jquery.fileupload.js", 64 | "js/jquery.fileupload-process.js", 65 | "js/jquery.fileupload-validate.js", 66 | "js/jquery.fileupload-image.js", 67 | "js/jquery.fileupload-audio.js", 68 | "js/jquery.fileupload-video.js", 69 | "js/jquery.fileupload-ui.js", 70 | "js/jquery.fileupload-jquery-ui.js", 71 | "js/jquery.fileupload-angular.js", 72 | "js/jquery.iframe-transport.js" 73 | ], 74 | "ignore": [ 75 | "/*.*", 76 | "/cors", 77 | "css/demo-ie8.css", 78 | "css/demo.css", 79 | "css/style.css", 80 | "js/app.js", 81 | "js/main.js", 82 | "server", 83 | "test" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/css/jquery.fileupload-noscript.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin NoScript CSS 1.2.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button input { 14 | position: static; 15 | opacity: 1; 16 | filter: none; 17 | font-size: inherit; 18 | direction: inherit; 19 | } 20 | .fileinput-button span { 21 | display: none; 22 | } 23 | -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/css/jquery.fileupload-ui-noscript.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin NoScript CSS 8.8.5 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2012, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button i, 14 | .fileupload-buttonbar .delete, 15 | .fileupload-buttonbar .toggle { 16 | display: none; 17 | } 18 | -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/css/jquery.fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 9.0.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileupload-buttonbar .btn, 14 | .fileupload-buttonbar .toggle { 15 | margin-bottom: 5px; 16 | } 17 | .progress-animated .progress-bar, 18 | .progress-animated .bar { 19 | background: url("../img/progressbar.gif") !important; 20 | filter: none; 21 | } 22 | .fileupload-process { 23 | float: right; 24 | display: none; 25 | } 26 | .fileupload-processing .fileupload-process, 27 | .files .processing .preview { 28 | display: block; 29 | width: 32px; 30 | height: 32px; 31 | background: url("../img/loading.gif") center no-repeat; 32 | background-size: contain; 33 | } 34 | .files audio, 35 | .files video { 36 | max-width: 300px; 37 | } 38 | 39 | @media (max-width: 767px) { 40 | .fileupload-buttonbar .toggle, 41 | .files .toggle, 42 | .files .btn span { 43 | display: none; 44 | } 45 | .files .name { 46 | width: 80px; 47 | word-wrap: break-word; 48 | } 49 | .files audio, 50 | .files video { 51 | max-width: 80px; 52 | } 53 | .files img, 54 | .files canvas { 55 | max-width: 100%; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/css/jquery.fileupload.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 1.3.0 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | } 17 | .fileinput-button input { 18 | position: absolute; 19 | top: 0; 20 | right: 0; 21 | margin: 0; 22 | opacity: 0; 23 | -ms-filter: 'alpha(opacity=0)'; 24 | font-size: 200px; 25 | direction: ltr; 26 | cursor: pointer; 27 | } 28 | 29 | /* Fixes for IE < 8 */ 30 | @media screen\9 { 31 | .fileinput-button input { 32 | filter: alpha(opacity=0); 33 | font-size: 100%; 34 | height: 100%; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/vendor/blueimp-file-upload/img/loading.gif -------------------------------------------------------------------------------- /vendor/blueimp-file-upload/img/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/vendor/blueimp-file-upload/img/progressbar.gif -------------------------------------------------------------------------------- /vendor/blueimp-load-image/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-load-image", 3 | "version": "1.13.0", 4 | "title": "JavaScript Load Image", 5 | "description": "JavaScript Load Image is a library to load images provided as File or Blob objects or via URL. It returns an optionally scaled and/or cropped HTML img or canvas element. It also provides a method to parse image meta data to extract Exif tags and thumbnails and to restore the complete image header after resizing.", 6 | "keywords": [ 7 | "javascript", 8 | "load", 9 | "loading", 10 | "image", 11 | "file", 12 | "blob", 13 | "url", 14 | "scale", 15 | "crop", 16 | "img", 17 | "canvas", 18 | "meta", 19 | "exif", 20 | "thumbnail", 21 | "resizing" 22 | ], 23 | "homepage": "https://github.com/blueimp/JavaScript-Load-Image", 24 | "author": { 25 | "name": "Sebastian Tschan", 26 | "url": "https://blueimp.net" 27 | }, 28 | "maintainers": [ 29 | { 30 | "name": "Sebastian Tschan", 31 | "url": "https://blueimp.net" 32 | } 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/blueimp/JavaScript-Load-Image.git" 37 | }, 38 | "bugs": "https://github.com/blueimp/JavaScript-Load-Image/issues", 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "http://www.opensource.org/licenses/MIT" 43 | } 44 | ], 45 | "main": [ 46 | "js/load-image.js", 47 | "js/load-image-ios.js", 48 | "js/load-image-orientation.js", 49 | "js/load-image-meta.js", 50 | "js/load-image-exif.js", 51 | "js/load-image-exif-map.js" 52 | ], 53 | "ignore": [ 54 | "/*.*", 55 | "css", 56 | "js/vendor", 57 | "js/demo.js", 58 | "test" 59 | ], 60 | "_release": "1.13.0", 61 | "_resolution": { 62 | "type": "version", 63 | "tag": "1.13.0", 64 | "commit": "a38af6ff57c3661a3c21d22bae79640982582841" 65 | }, 66 | "_source": "git://github.com/blueimp/JavaScript-Load-Image.git", 67 | "_target": ">=1.13.0", 68 | "_originalSource": "blueimp-load-image" 69 | } -------------------------------------------------------------------------------- /vendor/blueimp-load-image/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-load-image", 3 | "version": "1.13.0", 4 | "title": "JavaScript Load Image", 5 | "description": "JavaScript Load Image is a library to load images provided as File or Blob objects or via URL. It returns an optionally scaled and/or cropped HTML img or canvas element. It also provides a method to parse image meta data to extract Exif tags and thumbnails and to restore the complete image header after resizing.", 6 | "keywords": [ 7 | "javascript", 8 | "load", 9 | "loading", 10 | "image", 11 | "file", 12 | "blob", 13 | "url", 14 | "scale", 15 | "crop", 16 | "img", 17 | "canvas", 18 | "meta", 19 | "exif", 20 | "thumbnail", 21 | "resizing" 22 | ], 23 | "homepage": "https://github.com/blueimp/JavaScript-Load-Image", 24 | "author": { 25 | "name": "Sebastian Tschan", 26 | "url": "https://blueimp.net" 27 | }, 28 | "maintainers": [ 29 | { 30 | "name": "Sebastian Tschan", 31 | "url": "https://blueimp.net" 32 | } 33 | ], 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/blueimp/JavaScript-Load-Image.git" 37 | }, 38 | "bugs": "https://github.com/blueimp/JavaScript-Load-Image/issues", 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "http://www.opensource.org/licenses/MIT" 43 | } 44 | ], 45 | "main": [ 46 | "js/load-image.js", 47 | "js/load-image-ios.js", 48 | "js/load-image-orientation.js", 49 | "js/load-image-meta.js", 50 | "js/load-image-exif.js", 51 | "js/load-image-exif-map.js" 52 | ], 53 | "ignore": [ 54 | "/*.*", 55 | "css", 56 | "js/vendor", 57 | "js/demo.js", 58 | "test" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /vendor/blueimp-md5/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-md5", 3 | "version": "1.1.0", 4 | "title": "JavaScript MD5", 5 | "description": "JavaScript MD5 implementation.", 6 | "keywords": [ 7 | "javascript", 8 | "md5" 9 | ], 10 | "homepage": "https://github.com/blueimp/JavaScript-MD5", 11 | "author": { 12 | "name": "Sebastian Tschan", 13 | "url": "https://blueimp.net" 14 | }, 15 | "maintainers": [ 16 | { 17 | "name": "Sebastian Tschan", 18 | "url": "https://blueimp.net" 19 | } 20 | ], 21 | "contributors": [ 22 | { 23 | "name": "Paul Johnston", 24 | "url": "http://pajhome.org.uk/crypt/md5" 25 | } 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/blueimp/JavaScript-MD5.git" 30 | }, 31 | "bugs": "https://github.com/blueimp/JavaScript-MD5/issues", 32 | "licenses": [ 33 | { 34 | "type": "MIT", 35 | "url": "http://www.opensource.org/licenses/MIT" 36 | } 37 | ], 38 | "main": "js/md5.js", 39 | "ignore": [ 40 | "/*.*", 41 | "css", 42 | "js/demo.js", 43 | "test" 44 | ], 45 | "_release": "1.1.0", 46 | "_resolution": { 47 | "type": "version", 48 | "tag": "1.1.0", 49 | "commit": "b187bf0abe24bacbca83ea4799978b78829e7914" 50 | }, 51 | "_source": "git://github.com/blueimp/JavaScript-MD5.git", 52 | "_target": "~1.1.0", 53 | "_originalSource": "blueimp-md5", 54 | "_direct": true 55 | } -------------------------------------------------------------------------------- /vendor/blueimp-md5/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-md5", 3 | "version": "1.1.0", 4 | "title": "JavaScript MD5", 5 | "description": "JavaScript MD5 implementation.", 6 | "keywords": [ 7 | "javascript", 8 | "md5" 9 | ], 10 | "homepage": "https://github.com/blueimp/JavaScript-MD5", 11 | "author": { 12 | "name": "Sebastian Tschan", 13 | "url": "https://blueimp.net" 14 | }, 15 | "maintainers": [ 16 | { 17 | "name": "Sebastian Tschan", 18 | "url": "https://blueimp.net" 19 | } 20 | ], 21 | "contributors": [ 22 | { 23 | "name": "Paul Johnston", 24 | "url": "http://pajhome.org.uk/crypt/md5" 25 | } 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/blueimp/JavaScript-MD5.git" 30 | }, 31 | "bugs": "https://github.com/blueimp/JavaScript-MD5/issues", 32 | "licenses": [ 33 | { 34 | "type": "MIT", 35 | "url": "http://www.opensource.org/licenses/MIT" 36 | } 37 | ], 38 | "main": "js/md5.js", 39 | "ignore": [ 40 | "/*.*", 41 | "css", 42 | "js/demo.js", 43 | "test" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /vendor/blueimp-tmpl/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-tmpl", 3 | "version": "2.5.4", 4 | "title": "JavaScript Templates", 5 | "description": "< 1KB lightweight, fast & powerful JavaScript templating engine with zero dependencies. Compatible with server-side environments like node.js, module loaders like RequireJS and all web browsers.", 6 | "keywords": [ 7 | "javascript", 8 | "templates", 9 | "templating" 10 | ], 11 | "homepage": "https://github.com/blueimp/JavaScript-Templates", 12 | "author": { 13 | "name": "Sebastian Tschan", 14 | "url": "https://blueimp.net" 15 | }, 16 | "maintainers": [ 17 | { 18 | "name": "Sebastian Tschan", 19 | "url": "https://blueimp.net" 20 | } 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/blueimp/JavaScript-Templates.git" 25 | }, 26 | "bugs": "https://github.com/blueimp/JavaScript-Templates/issues", 27 | "licenses": [ 28 | { 29 | "type": "MIT", 30 | "url": "http://www.opensource.org/licenses/MIT" 31 | } 32 | ], 33 | "main": "js/tmpl.js", 34 | "ignore": [ 35 | "/*.*", 36 | "css", 37 | "js/demo.js", 38 | "test" 39 | ], 40 | "_release": "2.5.4", 41 | "_resolution": { 42 | "type": "version", 43 | "tag": "2.5.4", 44 | "commit": "6be58f3f1c1083ef52b4e61a985709b489758f35" 45 | }, 46 | "_source": "git://github.com/blueimp/JavaScript-Templates.git", 47 | "_target": ">=2.5.4", 48 | "_originalSource": "blueimp-tmpl" 49 | } -------------------------------------------------------------------------------- /vendor/blueimp-tmpl/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueimp-tmpl", 3 | "version": "2.5.4", 4 | "title": "JavaScript Templates", 5 | "description": "< 1KB lightweight, fast & powerful JavaScript templating engine with zero dependencies. Compatible with server-side environments like node.js, module loaders like RequireJS and all web browsers.", 6 | "keywords": [ 7 | "javascript", 8 | "templates", 9 | "templating" 10 | ], 11 | "homepage": "https://github.com/blueimp/JavaScript-Templates", 12 | "author": { 13 | "name": "Sebastian Tschan", 14 | "url": "https://blueimp.net" 15 | }, 16 | "maintainers": [ 17 | { 18 | "name": "Sebastian Tschan", 19 | "url": "https://blueimp.net" 20 | } 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/blueimp/JavaScript-Templates.git" 25 | }, 26 | "bugs": "https://github.com/blueimp/JavaScript-Templates/issues", 27 | "licenses": [ 28 | { 29 | "type": "MIT", 30 | "url": "http://www.opensource.org/licenses/MIT" 31 | } 32 | ], 33 | "main": "js/tmpl.js", 34 | "ignore": [ 35 | "/*.*", 36 | "css", 37 | "js/demo.js", 38 | "test" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /vendor/blueimp-tmpl/js/runtime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Templates Runtime 2.4.1 3 | * https://github.com/blueimp/JavaScript-Templates 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /*jslint sloppy: true */ 13 | /*global define */ 14 | 15 | (function ($) { 16 | var tmpl = function (id, data) { 17 | var f = tmpl.cache[id]; 18 | return data ? f(data, tmpl) : function (data) { 19 | return f(data, tmpl); 20 | }; 21 | }; 22 | tmpl.cache = {}; 23 | tmpl.encReg = /[<>&"'\x00]/g; 24 | tmpl.encMap = { 25 | "<" : "<", 26 | ">" : ">", 27 | "&" : "&", 28 | "\"" : """, 29 | "'" : "'" 30 | }; 31 | tmpl.encode = function (s) { 32 | /*jshint eqnull:true */ 33 | return (s == null ? "" : "" + s).replace( 34 | tmpl.encReg, 35 | function (c) { 36 | return tmpl.encMap[c] || ""; 37 | } 38 | ); 39 | }; 40 | if (typeof define === "function" && define.amd) { 41 | define(function () { 42 | return tmpl; 43 | }); 44 | } else { 45 | $.tmpl = tmpl; 46 | } 47 | }(this)); 48 | -------------------------------------------------------------------------------- /vendor/blueimp-tmpl/js/tmpl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Templates 2.4.1 3 | * https://github.com/blueimp/JavaScript-Templates 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Inspired by John Resig's JavaScript Micro-Templating: 12 | * http://ejohn.org/blog/javascript-micro-templating/ 13 | */ 14 | 15 | /*jslint evil: true, regexp: true, unparam: true */ 16 | /*global document, define */ 17 | 18 | (function ($) { 19 | "use strict"; 20 | var tmpl = function (str, data) { 21 | var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] || 22 | tmpl(tmpl.load(str)) : 23 | new Function( 24 | tmpl.arg + ',tmpl', 25 | "var _e=tmpl.encode" + tmpl.helper + ",_s='" + 26 | str.replace(tmpl.regexp, tmpl.func) + 27 | "';return _s;" 28 | ); 29 | return data ? f(data, tmpl) : function (data) { 30 | return f(data, tmpl); 31 | }; 32 | }; 33 | tmpl.cache = {}; 34 | tmpl.load = function (id) { 35 | return document.getElementById(id).innerHTML; 36 | }; 37 | tmpl.regexp = /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g; 38 | tmpl.func = function (s, p1, p2, p3, p4, p5) { 39 | if (p1) { // whitespace, quote and backspace in HTML context 40 | return { 41 | "\n": "\\n", 42 | "\r": "\\r", 43 | "\t": "\\t", 44 | " " : " " 45 | }[p1] || "\\" + p1; 46 | } 47 | if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%} 48 | if (p2 === "=") { 49 | return "'+_e(" + p3 + ")+'"; 50 | } 51 | return "'+(" + p3 + "==null?'':" + p3 + ")+'"; 52 | } 53 | if (p4) { // evaluation start tag: {% 54 | return "';"; 55 | } 56 | if (p5) { // evaluation end tag: %} 57 | return "_s+='"; 58 | } 59 | }; 60 | tmpl.encReg = /[<>&"'\x00]/g; 61 | tmpl.encMap = { 62 | "<" : "<", 63 | ">" : ">", 64 | "&" : "&", 65 | "\"" : """, 66 | "'" : "'" 67 | }; 68 | tmpl.encode = function (s) { 69 | /*jshint eqnull:true */ 70 | return (s == null ? "" : "" + s).replace( 71 | tmpl.encReg, 72 | function (c) { 73 | return tmpl.encMap[c] || ""; 74 | } 75 | ); 76 | }; 77 | tmpl.arg = "o"; 78 | tmpl.helper = ",print=function(s,e){_s+=e?(s==null?'':s):_e(s);}" + 79 | ",include=function(s,d){_s+=tmpl(s,d);}"; 80 | if (typeof define === "function" && define.amd) { 81 | define(function () { 82 | return tmpl; 83 | }); 84 | } else { 85 | $.tmpl = tmpl; 86 | } 87 | }(this)); 88 | -------------------------------------------------------------------------------- /vendor/blueimp-tmpl/js/tmpl.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";var b=function(a,c){var d=/[^\w\-\.:]/.test(a)?new Function(b.arg+",tmpl","var _e=tmpl.encode"+b.helper+",_s='"+a.replace(b.regexp,b.func)+"';return _s;"):b.cache[a]=b.cache[a]||b(b.load(a));return c?d(c,b):function(a){return d(a,b)}};b.cache={},b.load=function(a){return document.getElementById(a).innerHTML},b.regexp=/([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g,b.func=function(a,b,c,d,e,f){return b?{"\n":"\\n","\r":"\\r"," ":"\\t"," ":" "}[b]||"\\"+b:c?"="===c?"'+_e("+d+")+'":"'+("+d+"==null?'':"+d+")+'":e?"';":f?"_s+='":void 0},b.encReg=/[<>&"'\x00]/g,b.encMap={"<":"<",">":">","&":"&",'"':""","'":"'"},b.encode=function(a){return(null==a?"":""+a).replace(b.encReg,function(a){return b.encMap[a]||""})},b.arg="o",b.helper=",print=function(s,e){_s+=e?(s==null?'':s):_e(s);},include=function(s,d){_s+=tmpl(s,d);}","function"==typeof define&&define.amd?define(function(){return b}):a.tmpl=b}(this); -------------------------------------------------------------------------------- /vendor/jcrop/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jcrop", 3 | "homepage": "https://github.com/tapmodo/Jcrop", 4 | "version": "0.9.12", 5 | "_release": "0.9.12", 6 | "_resolution": { 7 | "type": "version", 8 | "tag": "v0.9.12", 9 | "commit": "1902fbc6afa14481dc019a17e0dcb7d62b808a98" 10 | }, 11 | "_source": "git://github.com/tapmodo/Jcrop.git", 12 | "_target": "~0.9.12", 13 | "_originalSource": "jcrop", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /vendor/jcrop/MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Tapmodo Interactive LLC, 2 | http://github.com/tapmodo/Jcrop 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/jcrop/README.md: -------------------------------------------------------------------------------- 1 | Jcrop Image Cropping Plugin 2 | =========================== 3 | 4 | Jcrop is the quick and easy way to add image cropping functionality to 5 | your web application. It combines the ease-of-use of a typical jQuery 6 | plugin with a powerful cross-platform DHTML cropping engine that is 7 | faithful to familiar desktop graphics applications. 8 | 9 | Cross-platform Compatibility 10 | ---------------------------- 11 | 12 | * Firefox 2+ 13 | * Safari 3+ 14 | * Opera 9.5+ 15 | * Google Chrome 0.2+ 16 | * Internet Explorer 6+ 17 | 18 | Feature Overview 19 | ---------------- 20 | 21 | * Attaches unobtrusively to any image 22 | * Supports aspect ratio locking 23 | * Supports minSize/maxSize setting 24 | * Callbacks for selection done, or while moving 25 | * Keyboard support for nudging selection 26 | * API features to create interactivity, including animation 27 | * Support for CSS styling 28 | * Experimental touch-screen support (iOS, Android, etc) 29 | 30 | Contributors 31 | ============ 32 | 33 | **Special thanks to the following contributors:** 34 | 35 | * [Bruno Agutoli](mailto:brunotla1@gmail.com) 36 | * dhorrigan 37 | * Phil-B 38 | * jaymecd 39 | * all others who have committed their time and effort to help improve Jcrop 40 | 41 | MIT License 42 | =========== 43 | 44 | **Jcrop is free software under MIT License.** 45 | 46 | #### Copyright (c) 2008-2012 Tapmodo Interactive LLC,
http://github.com/tapmodo/Jcrop 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of this software and associated documentation files (the 50 | "Software"), to deal in the Software without restriction, including 51 | without limitation the rights to use, copy, modify, merge, publish, 52 | distribute, sublicense, and/or sell copies of the Software, and to 53 | permit persons to whom the Software is furnished to do so, subject to 54 | the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be 57 | included in all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 60 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 61 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 62 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 63 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 64 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 65 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 66 | 67 | -------------------------------------------------------------------------------- /vendor/jcrop/css/Jcrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owncloud-archive/contacts/9ba2e667ae8c7ea36d8c4a4c3413c374beb24b1b/vendor/jcrop/css/Jcrop.gif -------------------------------------------------------------------------------- /vendor/jcrop/css/jquery.Jcrop.min.css: -------------------------------------------------------------------------------- 1 | /* jquery.Jcrop.min.css v0.9.12 (build:20130126) */ 2 | .jcrop-holder{direction:ltr;text-align:left;} 3 | .jcrop-vline,.jcrop-hline{background:#FFF url(Jcrop.gif);font-size:0;position:absolute;} 4 | .jcrop-vline{height:100%;width:1px!important;} 5 | .jcrop-vline.right{right:0;} 6 | .jcrop-hline{height:1px!important;width:100%;} 7 | .jcrop-hline.bottom{bottom:0;} 8 | .jcrop-tracker{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;-webkit-user-select:none;height:100%;width:100%;} 9 | .jcrop-handle{background-color:#333;border:1px #EEE solid;font-size:1px;height:7px;width:7px;} 10 | .jcrop-handle.ord-n{left:50%;margin-left:-4px;margin-top:-4px;top:0;} 11 | .jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px;} 12 | .jcrop-handle.ord-e{margin-right:-4px;margin-top:-4px;right:0;top:50%;} 13 | .jcrop-handle.ord-w{left:0;margin-left:-4px;margin-top:-4px;top:50%;} 14 | .jcrop-handle.ord-nw{left:0;margin-left:-4px;margin-top:-4px;top:0;} 15 | .jcrop-handle.ord-ne{margin-right:-4px;margin-top:-4px;right:0;top:0;} 16 | .jcrop-handle.ord-se{bottom:0;margin-bottom:-4px;margin-right:-4px;right:0;} 17 | .jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px;} 18 | .jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{height:7px;width:100%;} 19 | .jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{height:100%;width:7px;} 20 | .jcrop-dragbar.ord-n{margin-top:-4px;} 21 | .jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px;} 22 | .jcrop-dragbar.ord-e{margin-right:-4px;right:0;} 23 | .jcrop-dragbar.ord-w{margin-left:-4px;} 24 | .jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#FFF;filter:alpha(opacity=70)!important;opacity:.70!important;} 25 | .jcrop-light .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#000;border-color:#FFF;border-radius:3px;} 26 | .jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;filter:alpha(opacity=70)!important;opacity:.7!important;} 27 | .jcrop-dark .jcrop-handle{-moz-border-radius:3px;-webkit-border-radius:3px;background-color:#FFF;border-color:#000;border-radius:3px;} 28 | .solid-line .jcrop-vline,.solid-line .jcrop-hline{background:#FFF;} 29 | .jcrop-holder img,img.jcrop-preview{max-width:none;} 30 | -------------------------------------------------------------------------------- /vendor/jquery.onfontresize/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.onfontresize", 3 | "_cacheHeaders": { 4 | "ETag": "\"876e3-733-4f611014f2a87\"", 5 | "Last-Modified": "Wed, 02 Apr 2014 15:33:23 GMT", 6 | "Content-Length": "1843", 7 | "Content-Type": "text/javascript" 8 | }, 9 | "_release": "e-tag:876e3-733", 10 | "main": "index.js", 11 | "_source": "http://www.tomdeater.com/jquery/onfontresize/jquery.onfontresize.js", 12 | "_target": "*", 13 | "_originalSource": "http://www.tomdeater.com/jquery/onfontresize/jquery.onfontresize.js", 14 | "_direct": true 15 | } -------------------------------------------------------------------------------- /vendor/jquery.onfontresize/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Copyright (c) 2008 Tom Deater (http://www.tomdeater.com) 4 | * Licensed under the MIT License: 5 | * http://www.opensource.org/licenses/mit-license.php 6 | * 7 | * uses an iframe, sized in ems, to detect font size changes then trigger a "fontresize" event 8 | * heavily based on code by Hedger Wang: http://www.hedgerwow.com/360/dhtml/js-onfontresize.html 9 | * 10 | * "fontresize" event is triggered on the document object 11 | * subscribe to event using: $(document).bind("fontresize", function (event, data) {}); 12 | * "data" contains the current size of 1 em unit (in pixels) 13 | * 14 | */ 15 | 16 | jQuery.onFontResize = (function ($) { 17 | // initialize 18 | $(document).ready(function () { 19 | var $resizeframe = $("