Merge branch 'v2' into 'master'

V2

Merge v2 to master. Final 2.0.0

See merge request !1
This commit is contained in:
Christoph Haas 2016-06-19 00:30:47 +02:00
commit c2f26697fb
24 changed files with 1613 additions and 1587 deletions

18
.idea/contactimporter.iml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library name="PHARS">
<CLASSES>
<root url="phar://$MODULE_DIR$/php/composer.phar" />
</CLASSES>
<SOURCES>
<root url="phar://$MODULE_DIR$/php/composer.phar" />
</SOURCES>
</library>
</orderEntry>
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

148
build.xml
View File

@ -1,18 +1,15 @@
<project default="all">
<!--############# CONFIGURE ALL PROPERTIES FOR THE REPLACER HERE ################-->
<property name="plugin_version" value="1.0.3"/>
<!-- EOC -->
<property name="root-folder" value="${basedir}/../"/>
<property name="tools-folder" value="${root-folder}/TOOLS/"/>
<property name="target-folder" value="${root-folder}/DEPLOY/plugins"/>
<property environment="env"/>
<property name="root-folder" value="${basedir}/../../"/>
<property name="tools-folder" value="${root-folder}/tools/"/>
<property name="target-folder" value="${root-folder}/deploy/plugins"/>
<property name="server-folder" value="${root-folder}/server"/>
<import file="${tools-folder}/antutil.xml"/>
<typedef file="${tools-folder}/antlib.xml">
<classpath>
<pathelement location="${tools-folder}/tools.jar"/>
<pathelement location="${tools-folder}/lib/compiler.jar"/>
</classpath>
</typedef>
@ -22,29 +19,7 @@
</classpath>
</taskdef>
<!-- os checks for xmllint... -->
<condition property="isWindows" value="true">
<os family="windows" />
</condition>
<!-- define nicknames for libraries -->
<property name="yui-compressor" location="${tools-folder}/lib/yuicompressor-2.4.2.jar" />
<property name="yui-compressor-ant-task" location="${tools-folder}/lib/yui-compressor-ant-task-0.5.jar" />
<!-- adds libraries to the classpath -->
<path id="yui.classpath">
<pathelement location="${yui-compressor}" />
<pathelement location="${yui-compressor-ant-task}" />
</path>
<!-- define tasks -->
<taskdef name="yui-compressor" classname="net.noha.tools.ant.yuicompressor.tasks.YuiCompressorTask">
<classpath refid="yui.classpath" />
</taskdef>
<!-- Determine plugin name -->
<var name="plugin" unset="true"/>
<basename file="${basedir}" property="plugin"/>
<!-- The Plugin distribution files -->
@ -54,8 +29,7 @@
<!-- The Plugin CSS files -->
<property name="plugin-css-folder" value="resources/css"/>
<property name="plugin-css-file" value="${plugin}-min.css"/>
<property name="plugin-css-debug-file" value="${plugin}.css"/>
<property name="plugin-css-file" value="${plugin}.css"/>
<!-- Meta target -->
<target name="all" depends="concat, compress"/>
@ -68,16 +42,6 @@
<include name="${plugin-file}"/>
<include name="${plugin-debugfile}"/>
</fileset>
<fileset dir="${target-folder}/${plugin-folder}/php">
<include name="**/*.php"/>
</fileset>
<fileset dir="${target-folder}/${plugin-folder}/resources">
<include name="**/*"/>
</fileset>
<fileset dir="${target-folder}/${plugin-folder}/${plugin-css-folder}">
<include name="${plugin-css-debug-file}"/>
<include name="${plugin-css-file}"/>
</fileset>
</delete>
</target>
@ -89,19 +53,11 @@
<then>
<mkdir dir="${target-folder}/${plugin-folder}/js"/>
<echo message="Concatenating: ${plugin-debugfile}"/>
<!-- TODO: fix JS files for zConcat -->
<!--zConcat outputFolder="${target-folder}/${plugin-folder}/js" outputFile="${plugin-debugfile}" prioritize="\w+">
<zConcat outputFolder="${target-folder}/${plugin-folder}/js" outputFile="${plugin-debugfile}" prioritize="\w+">
<concatfiles>
<fileset dir="js" includes="**/*.js" />
</concatfiles>
</zConcat-->
<concat destfile="${target-folder}/${plugin-folder}/js/${plugin-debugfile}">
<fileset file="js/ABOUT.js" />
<fileset file="js/plugin.contactimporter.js" />
<fileset file="js/data/ResponseHandler.js" />
<fileset file="js/dialogs/ImportContentPanel.js" />
<fileset file="js/dialogs/ImportPanel.js" />
</concat>
</zConcat>
</then>
</if>
@ -110,8 +66,8 @@
<available file="${plugin-css-folder}" type="dir" />
<then>
<mkdir dir="${target-folder}/${plugin-folder}/${plugin-css-folder}"/>
<echo message="Concatenating: ${plugin-css-debug-file}"/>
<zConcat outputFolder="${target-folder}/${plugin-folder}/${plugin-css-folder}" outputFile="${plugin-css-debug-file}">
<echo message="Concatenating: ${plugin-css-file}"/>
<zConcat outputFolder="${target-folder}/${plugin-folder}/${plugin-css-folder}" outputFile="${plugin-css-file}">
<concatfiles>
<fileset dir="${plugin-css-folder}" includes="**/*.css" />
</concatfiles>
@ -156,21 +112,15 @@
var pgettext = function(msgctxt, msgid) {};
</externs>
</zCompile>
<!--yui-compressor
warn="false"
munge="true"
preserveallsemicolons="false"
fromdir="${target-folder}/${plugin-folder}/js"
todir="${target-folder}/${plugin-folder}/js">
<include name="${plugin-debugfile}" />
</yui-compressor-->
</then>
</if>
</target>
<!-- syntax check all PHP files -->
<target name="validate">
<if>
<available file="php" filepath="${env.PATH}" />
<then>
<if>
<available file="config.php" type="file" />
<then>
@ -185,92 +135,66 @@
<foreach target="syntax-check" param="file">
<path>
<fileset dir=".">
<exclude name="php/vendor/**" />
<include name="**/*.php"/>
</fileset>
</path>
</foreach>
</then>
</if>
</then>
<else>
<echo message="WARNING: PHP not available, not performing syntax-check on php files"/>
</else>
</if>
</target>
<target name="syntax-check">
<echo message="validating ${file}"/>
<exec executable="php" failonerror="true" failifexecutionfails="false">
<exec executable="php" failonerror="true">
<arg value="-l"/>
<arg value="${file}"/>
</exec>
</target>
<!-- on windows we do not check the xml file -->
<target name="xml-os-sel" depends="xml-check,xml-copy">
<echo>Processing manifest.xml</echo>
</target>
<!-- Install all files into the target folder -->
<target name="deploy" depends="compress, validate">
<mkdir dir="${target-folder}/${plugin-folder}"/>
<!-- check manifest.xml if we are on windows... -->
<target name="xml-check" unless="isWindows">
<echo message="Checking xml: manifest.xml" />
<!-- Copy (and validate) manifest.xml -->
<exec executable="xmllint" output="${target-folder}/${plugin-folder}/manifest.xml" failonerror="true" error="/dev/stdout" failifexecutionfails="false">
<if>
<available file="xmllint" filepath="${env.PATH}" />
<then>
<exec executable="xmllint" output="${target-folder}/${plugin-folder}/manifest.xml" failonerror="true">
<arg value="--valid"/>
<arg value="--path"/>
<arg value="${root-folder}/server"/>
<arg value="${server-folder}"/>
<arg value="manifest.xml"/>
</exec>
</target>
<!-- check manifest.xml if we are on windows... -->
<target name="xml-copy" if="isWindows">
<echo message="Copying xml: manifest.xml" />
<!-- Copy manifest.xml -->
</then>
<else>
<echo message="WARNING: xmllint not available, not performing syntax-check on manifest.xml"/>
<!-- xmllint is not available, so we must copy the file manually -->
<copy todir="${target-folder}/${plugin-folder}">
<fileset dir=".">
<include name="manifest.xml"/>
</fileset>
</copy>
</target>
<!-- Install all files into the target folder -->
<target name="deploy" depends="clean, compress, compresscss, validate, xml-os-sel">
<mkdir dir="${target-folder}/${plugin-folder}"/>
</else>
</if>
<!-- copy files -->
<copy todir="${target-folder}/${plugin-folder}">
<fileset dir=".">
<include name="resources/**/*"/>
<include name="resources/**/*.*"/>
<include name="external/**/*.*"/>
<include name="php/**/*.php"/>
<include name="config.php"/>
<include name="changelog.txt"/>
<!-- exclude the ant script -->
<exclude name="build.xml"/>
<!-- CSS is generated during build -->
<exclude name="resources/css/*.*"/>
</fileset>
</copy>
<!-- replace all variables... -->
<replace file="${target-folder}/${plugin-folder}/manifest.xml" token="@_@PLUGIN_VERSION@_@" value="${plugin_version}" />
</target>
<!-- compresses each CSS file -->
<target name="compresscss" depends="concat">
<available file="${tools-folder}/lib/yui-compressor-ant-task-0.5.jar" property="YUIANT_AVAILABLE" />
<fail unless="YUIANT_AVAILABLE" message="yui-compressor-ant-task-0.5.jar not found" />
<if>
<available file="${target-folder}/${plugin-folder}/${plugin-css-folder}/${plugin-css-debug-file}" type="file" />
<then>
<yui-compressor
warn="false"
munge="true"
preserveallsemicolons="false"
fromdir="${target-folder}/${plugin-folder}/${plugin-css-folder}"
todir="${target-folder}/${plugin-folder}/${plugin-css-folder}">
<include name="${plugin-css-debug-file}" />
</yui-compressor>
</then>
</if>
</target>
</project>

View File

@ -1,11 +1,9 @@
<?php
/** Disable the import plugin for all clients */
define('PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE', false);
/** Disable the export feature for all clients */
define('PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE_EXPORT', false); // currently not available
/** The default addressbook to import to (default: contact)*/
define('PLUGIN_CONTACTIMPORTER_DEFAULT', "contact");
/** The default addressbook to import to (default: Kontakte or Contacts - depending on your language)*/
define('PLUGIN_CONTACTIMPORTER_DEFAULT', "Kontakte");
/** Tempory path for uploaded files... */
define('PLUGIN_CONTACTIMPORTER_TMP_UPLOAD', "/var/lib/zarafa-webapp/tmp/");

View File

@ -2,7 +2,7 @@
* ABOUT.js zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -29,7 +29,7 @@ Ext.namespace('Zarafa.plugins.contactimporter');
* The copyright string holding the copyright notice for the Zarafa contactimporter Plugin.
*/
Zarafa.plugins.contactimporter.ABOUT = ""
+ "<p>Copyright (C) 2012-2013 Christoph Haas &lt;christoph.h@sprinternet.at&gt;</p>"
+ "<p>Copyright (C) 2012-2016 Christoph Haas &lt;christoph.h@sprinternet.at&gt;</p>"
+ "<p>This program is free software; you can redistribute it and/or "
+ "modify it under the terms of the GNU Lesser General Public "
@ -51,7 +51,7 @@ Zarafa.plugins.contactimporter.ABOUT = ""
+ "<h1>vCard-parser</h1>"
+ "<p>Copyright (C) 2012 Nuovo</p>"
+ "<p>Copyright (C) 2016 Jeroen Desloovere</p>"
+ "<p>Licensed under the MIT License.</p>"

View File

@ -2,7 +2,7 @@
* ResponseHandler.js zarafa contact im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -56,6 +56,14 @@ Zarafa.plugins.contactimporter.data.ResponseHandler = Ext.extend(Zarafa.core.dat
this.successCallback(response);
},
/**
* Call the successCallback callback function.
* @param {Object} response Object contained the response data.
*/
doExport: function (response) {
this.successCallback(response);
},
/**
* Call the successCallback callback function.
* @param {Object} response Object contained the response data.

View File

@ -2,7 +2,7 @@
* ImportContentPanel.js zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -43,9 +43,6 @@ Zarafa.plugins.contactimporter.dialogs.ImportContentPanel = Ext.extend(Zarafa.co
constructor: function (config) {
config = config || {};
var title = _('Import Contacts');
if(container.getSettingsModel().get("zarafa/v1/plugins/contactimporter/enable_export")){
title = _('Import/Export Contacts');
}
Ext.applyIf(config, {
layout : 'fit',
title : title,
@ -56,7 +53,8 @@ Zarafa.plugins.contactimporter.dialogs.ImportContentPanel = Ext.extend(Zarafa.co
items : [
{
xtype : 'contactimporter.importcontactpanel',
filename : config.filename
filename: config.filename,
folder : config.folder
}
]
});

View File

@ -2,7 +2,7 @@
* ImportPanel.js zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -29,7 +29,7 @@ Ext.namespace("Zarafa.plugins.contactimporter.dialogs");
/**
* @class Zarafa.plugins.contactimporter.dialogs.ImportPanel
* @extends Ext.form.FormPanel
* @extends Ext.Panel
*/
Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
@ -39,6 +39,9 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
/* The store for the selection grid */
store : null,
/* selected folder */
folder : null,
/**
* @constructor
* @param {object} config
@ -47,10 +50,14 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
config = config || {};
var self = this;
if(typeof config.filename !== "undefined") {
if (!Ext.isEmpty(config.filename)) {
this.vcffile = config.filename;
}
if (!Ext.isEmpty(config.folder)) {
this.folder = config.folder;
}
// create the data store
// we only display the firstname, lastname, homephone and primary email address in our grid
this.store = new Ext.data.ArrayStore({
@ -94,15 +101,6 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
this.parseContacts(this.vcffile);
}
},
close: function (cmp) {
Ext.getCmp("importcontactsbutton").enable();
},
hide: function (cmp) {
Ext.getCmp("importcontactsbutton").enable();
},
destroy: function (cmp) {
Ext.getCmp("importcontactsbutton").enable();
},
scope : this
}
});
@ -134,6 +132,121 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
};
},
/**
* Get all contact folders.
* @param {boolean} asDropdownStore If true, a simple array store will be returned.
* @returns {*}
*/
getAllContactFolders: function (asDropdownStore) {
asDropdownStore = Ext.isEmpty(asDropdownStore) ? false : asDropdownStore;
var allFolders = [];
var defaultContactFolder = container.getHierarchyStore().getDefaultFolder('contact');
var inbox = container.getHierarchyStore().getDefaultStore();
var pub = container.getHierarchyStore().getPublicStore();
if (!Ext.isEmpty(inbox.subStores) && inbox.subStores.folders.totalLength > 0) {
for (var i = 0; i < inbox.subStores.folders.totalLength; i++) {
var folder = inbox.subStores.folders.getAt(i);
if (folder.get("container_class") == "IPF.Contact") {
if (asDropdownStore) {
allFolders.push([
folder.get("entryid"),
folder.get("display_name")
]);
} else {
allFolders.push({
display_name : folder.get("display_name"),
entryid : folder.get("entryid"),
store_entryid: folder.get("store_entryid"),
is_public : false
});
}
}
}
}
if (!Ext.isEmpty(pub.subStores) && pub.subStores.folders.totalLength > 0) {
for (var j = 0; j < pub.subStores.folders.totalLength; j++) {
var folder = pub.subStores.folders.getAt(j);
if (folder.get("container_class") == "IPF.Contact") {
if (asDropdownStore) {
allFolders.push([
folder.get("entryid"),
folder.get("display_name") + " (Public)"
]);
} else {
allFolders.push({
display_name : folder.get("display_name"),
entryid : folder.get("entryid"),
store_entryid: folder.get("store_entryid"),
is_public : true
});
}
}
}
}
if (asDropdownStore) {
return allFolders.sort(this.dynamicSort(1));
} else {
return allFolders;
}
},
/**
* Dynamic sort function, sorts by property name.
* @param {string|int} property
* @returns {Function}
*/
dynamicSort: function (property) {
var sortOrder = 1;
if (property[0] === "-") {
sortOrder = -1;
property = property.substr(1);
}
return function (a, b) {
var result = (a[property].toLowerCase() < b[property].toLowerCase()) ? -1 : (a[property].toLowerCase() > b[property].toLowerCase()) ? 1 : 0;
return result * sortOrder;
}
},
/**
* Return a contact folder element by name.
* @param {string} name
* @returns {*}
*/
getContactFolderByName: function (name) {
var folders = this.getAllContactFolders(false);
for (var i = 0; i < folders.length; i++) {
if (folders[i].display_name == name) {
return folders[i];
}
}
return container.getHierarchyStore().getDefaultFolder('contact');
},
/**
* Return a contact folder element by entryid.
* @param {string} entryid
* @returns {*}
*/
getContactFolderByEntryid: function (entryid) {
var folders = this.getAllContactFolders(false);
for (var i = 0; i < folders.length; i++) {
if (folders[i].entryid == entryid) {
return folders[i];
}
}
return container.getHierarchyStore().getDefaultFolder('contact');
},
/**
* Reloads the data of the grid
* @private
@ -146,13 +259,13 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
var i = 0;
for (i = 0; i < contactdata.contacts.length; i++) {
parsedData[i] = new Array(
parsedData[i] = [
contactdata.contacts[i]["display_name"],
contactdata.contacts[i]["given_name"],
contactdata.contacts[i]["surname"],
contactdata.contacts[i]["company_name"],
contactdata.contacts[i]
);
];
}
} else {
return null;
@ -195,55 +308,36 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Generate the UI calendar select box.
* @returns {*}
*/
createSelectBox: function () {
var defaultFolder = container.getHierarchyStore().getDefaultFolder('contact'); // @type: Zarafa.hierarchy.data.MAPIFolderRecord
var subFolders = defaultFolder.getChildren();
var myStore = [];
/* add all local contact folders */
var i = 0;
myStore.push(new Array(defaultFolder.getDefaultFolderKey(), defaultFolder.getDisplayName()));
for(i = 0; i < subFolders.length; i++) {
/* Store all subfolders */
myStore.push(new Array(subFolders[i].getDisplayName(), subFolders[i].getDisplayName(), false)); // 3rd field = isPublicfolder
}
/* add all shared contact folders */
var pubStore = container.getHierarchyStore().getPublicStore();
if(typeof pubStore !== "undefined") {
try {
var pubFolder = pubStore.getDefaultFolder("publicfolders");
var pubSubFolders = pubFolder.getChildren();
for(i = 0; i < pubSubFolders.length; i++) {
if(pubSubFolders[i].isContainerClass("IPF.Contact")){
myStore.push(new Array(pubSubFolders[i].getDisplayName(), pubSubFolders[i].getDisplayName() + " [Shared]", true)); // 3rd field = isPublicfolder
}
}
} catch (e) {
console.log("Error opening the shared folder...");
console.log(e);
}
}
var myStore = this.getAllContactFolders(true);
return {
xtype : "selectbox",
ref : 'addressbookSelector',
editable : false,
name : "choosen_addressbook",
value: container.getSettingsModel().get("zarafa/v1/plugins/contactimporter/default_addressbook"),
value : Ext.isEmpty(this.folder) ? this.getContactFolderByName(container.getSettingsModel().get("zarafa/v1/plugins/contactimporter/default_addressbook")).entryid : this.folder,
width : 100,
fieldLabel: "Select an addressbook",
fieldLabel : "Select folder",
store : myStore,
mode : 'local',
labelSeperator: ":",
border : false,
anchor : "100%",
scope : this,
hidden : Ext.isEmpty(this.folder) ? false : true,
allowBlank : false
}
},
/**
* Generate the UI upload field.
* @returns {*}
*/
createUploadField: function () {
return {
xtype : "fileuploadfield",
@ -254,6 +348,7 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
emptyText : 'Select an .vcf addressbook',
border : false,
anchor : "100%",
height : "30",
scope : this,
allowBlank : false,
listeners : {
@ -263,6 +358,10 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Generate the UI submit button.
* @returns {*}
*/
createSubmitButton: function () {
return {
xtype : "button",
@ -278,6 +377,10 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Generate the UI submit all button.
* @returns {*}
*/
createSubmitAllButton: function () {
return {
xtype : "button",
@ -293,6 +396,10 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Generate the UI cancel button.
* @returns {*}
*/
createCancelButton: function () {
return {
xtype : "button",
@ -339,6 +446,10 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Start request to server to parse the given vCard file.
* @param {string} vcfPath
*/
parseContacts: function (vcfPath) {
this.loadMask.show();
@ -357,6 +468,10 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
);
},
/**
* Callback for the parsing request.
* @param {Object} response
*/
handleParsingResult: function (response) {
this.loadMask.hide();
@ -377,16 +492,25 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
}
},
/**
* Close the UI dialog.
*/
close: function () {
this.addContactFormPanel.getForm().reset();
this.dialog.close()
},
/**
* Create a request to import all selected contacts.
*/
importCheckedContacts: function () {
var newRecords = this.contactGrid.selModel.getSelections();
this.importContacts(newRecords);
},
/**
* Check all contacts and import them.
*/
importAllContacts: function () {
//receive Records from grid rows
this.contactGrid.selModel.selectAll(); // select all entries
@ -395,8 +519,8 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
},
/**
* This function stores all given events to the appointmentstore
* @param events
* This function stores all given events to the contact store
* @param {array} contacts
*/
importContacts: function (contacts) {
//receive existing contact store
@ -410,7 +534,6 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
buttons: Zarafa.common.dialogs.MessageBox.OK
});
} else {
var addressbookexist = true;
if (this.contactGrid.selModel.getCount() < 1) {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
@ -419,50 +542,15 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
buttons: Zarafa.common.dialogs.MessageBox.OK
});
} else {
var contactStore = new Zarafa.contact.ContactStore();
var contactFolder = container.getHierarchyStore().getDefaultFolder('contact');
var pubStore = container.getHierarchyStore().getPublicStore();
var pubFolder = pubStore.getDefaultFolder("publicfolders");
var pubSubFolders = pubFolder.getChildren();
var contactFolder = this.getContactFolderByEntryid(folderValue);
if(folderValue != "contact") {
var subFolders = contactFolder.getChildren();
var i = 0;
for(i = 0; i < pubSubFolders.length; i++) {
if(pubSubFolders[i].isContainerClass("IPF.Contact")){
subFolders.push(pubSubFolders[i]);
}
}
for(i=0;i<subFolders.length;i++) {
// look up right folder
// TODO: improve!!
if(subFolders[i].getDisplayName() == folderValue) {
contactFolder = subFolders[i];
break;
}
}
if(contactFolder.isDefaultFolder()) {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('Selected addressbook does not exist!'),
icon : Zarafa.common.dialogs.MessageBox.ERROR,
buttons : Zarafa.common.dialogs.MessageBox.OK
});
addressbookexist = false;
}
}
if(addressbookexist) {
this.loadMask.show();
var uids = new Array();
var store_entryid = "";
var uids = [];
//receive Records from grid rows
Ext.each(contacts, function (newRecord) {
uids.push(newRecord.data.record.internal_fields.contact_uid);
}, this);
store_entryid = contactFolder.get('store_entryid');
var responseHandler = new Zarafa.plugins.contactimporter.data.ResponseHandler({
successCallback: this.importContactsDone.createDelegate(this)
@ -472,21 +560,22 @@ Zarafa.plugins.contactimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
'contactmodule',
'import',
{
storeid: contactFolder.get("store_entryid"),
folderid: contactFolder.get("entryid"),
storeid : contactFolder.store_entryid,
folderid : contactFolder.entryid,
uids : uids,
vcf_filepath: this.vcffile
},
responseHandler
);
}
}
}
},
/**
* Callback for the import request.
* @param {Object} response
*/
importContactsDone: function (response) {
console.log(response);
this.loadMask.hide();
this.dialog.close();
if (response.status == true) {

View File

@ -2,7 +2,7 @@
* plugin.contactimporter.js zarafa contactimporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -46,33 +46,87 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
Zarafa.core.data.SharedComponentType.addProperty('plugins.contactimporter.dialogs.importcontacts');
/* directly import received vcfs */
this.registerInsertionPoint('common.contextmenu.attachment.actions', this.createAttachmentImportButton);
/* add import button to south navigation */
this.registerInsertionPoint("navigation.south", this.createImportButton, this);
this.registerInsertionPoint('common.contextmenu.attachment.actions', this.createAttachmentImportButton, this);
/* export a contact via rightclick */
this.registerInsertionPoint('context.contact.contextmenu.actions', this.createItemExportInsertionPoint, this);
},
/**
* Creates the button
*
* @return {Object} Configuration object for a {@link Ext.Button button}
* This method hooks to the contact context menu and allows users to export users to vcf.
*
* @param include
* @param btn
* @returns {Object}
*/
createImportButton: function () {
var button = {
xtype : 'button',
id : "importcontactsbutton",
text : _('Import Contacts'),
iconCls : 'icon_contactimporter_button',
navigationContext : container.getContextByName('contact'),
handler : this.onImportButtonClick,
scope : this
createItemExportInsertionPoint: function (include, btn) {
return {
text : dgettext('plugin_files', 'Export vCard'),
handler: this.exportToVCF.createDelegate(this, [btn]),
scope : this,
iconCls: 'icon_contactimporter_export'
};
},
if(container.getSettingsModel().get("zarafa/v1/plugins/contactimporter/enable_export")) {
button.text = _('Import/Export Contacts');
/**
* Generates a request to download the selected records as vCard.
* @param {Ext.Button} btn
*/
exportToVCF: function (btn) {
if (btn.records.length == 0) {
return; // skip if no records where given!
}
return button;
var recordIds = [];
for (var i = 0; i < btn.records.length; i++) {
recordIds.push(btn.records[i].get("entryid"));
}
var responseHandler = new Zarafa.plugins.contactimporter.data.ResponseHandler({
successCallback: this.downloadVCF,
scope : this
});
// request attachment preperation
container.getRequest().singleRequest(
'contactmodule',
'export',
{
storeid: btn.records[0].get("store_entryid"),
records: recordIds
},
responseHandler
);
},
/**
* Callback for the export request.
* @param {Object} response
*/
downloadVCF: function (response) {
if (response.status == false) {
Zarafa.common.dialogs.MessageBox.show({
title : dgettext('plugin_files', 'Warning'),
msg : dgettext('plugin_files', response.message),
icon : Zarafa.common.dialogs.MessageBox.WARNING,
buttons: Zarafa.common.dialogs.MessageBox.OK
});
} else {
var downloadFrame = Ext.getBody().createChild({
tag: 'iframe',
cls: 'x-hidden'
});
var url = document.URL;
var link = url.substring(0, url.lastIndexOf('/') + 1);
link += "index.php?sessionid=" + container.getUser().getSessionId() + "&load=custom&name=download_vcf";
link = Ext.urlAppend(link, "token=" + encodeURIComponent(response.download_token));
link = Ext.urlAppend(link, "filename=" + encodeURIComponent(response.filename));
downloadFrame.dom.contentWindow.location = link;
}
},
/**
@ -82,17 +136,17 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
*/
createAttachmentImportButton: function (include, btn) {
return {
text : _('Import Contacts'),
handler : this.getAttachmentFileName.createDelegate(this, [btn, this.gotAttachmentFileName]),
text : _('Import to Contacts'),
handler : this.getAttachmentFileName.createDelegate(this, [btn]),
scope : this,
iconCls : 'icon_contactimporter_button',
beforeShow: function (item, record) {
var extension = record.data.name.split('.').pop().toLowerCase();
if(record.data.filetype == "text/vcard" || extension == "vcf" || extension == "vcard") {
item.setDisabled(false);
if (record.data.filetype == "text/vcard" || record.data.filetype == "text/x-vcard" || extension == "vcf" || extension == "vcard") {
item.setVisible(true);
} else {
item.setDisabled(true);
item.setVisible(false);
}
}
};
@ -100,13 +154,11 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
/**
* Callback for getAttachmentFileName
* @param {Object} response
*/
gotAttachmentFileName: function (response) {
if (response.status == true) {
Zarafa.core.data.UIFactory.openLayerComponent(Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts'], undefined, {
manager : Ext.WindowMgr,
filename : response.tmpname
});
this.openImportDialog(response.tmpname);
} else {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
@ -119,8 +171,9 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
/**
* Clickhandler for the button
* @param {Ext.Button} btn
*/
getAttachmentFileName: function (btn, callback) {
getAttachmentFileName: function (btn) {
Zarafa.common.dialogs.MessageBox.show({
title : 'Please wait',
msg : 'Loading attachment...',
@ -152,15 +205,17 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
var store = attachmentStore.getParentRecord().get('store_entryid');
var entryid = attachmentStore.getAttachmentParentRecordEntryId();
var attachNum = new Array(1);
if (attachmentRecord.get('attach_num') != -1)
if (attachmentRecord.get('attach_num') != -1) {
attachNum[0] = attachmentRecord.get('attach_num');
else
} else {
attachNum[0] = attachmentRecord.get('tmpname');
}
var dialog_attachments = attachmentStore.getId();
var filename = attachmentRecord.data.name;
var responseHandler = new Zarafa.plugins.contactimporter.data.ResponseHandler({
successCallback: callback
successCallback: this.gotAttachmentFileName.createDelegate(this),
scope : this
});
// request attachment preperation
@ -179,13 +234,17 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
},
/**
* Clickhandler for the button
* Open the import dialog.
* @param {String} filename
*/
onImportButtonClick: function () {
Ext.getCmp("importcontactsbutton").disable();
Zarafa.core.data.UIFactory.openLayerComponent(Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts'], undefined, {
manager : Ext.WindowMgr
});
openImportDialog: function (filename) {
var componentType = Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts'];
var config = {
filename: filename,
modal : true
};
Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
},
/**
@ -200,7 +259,14 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
var bid = -1;
switch (type) {
case Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts']:
bid = 1;
break;
case Zarafa.core.data.SharedComponentType['common.contextmenu']:
if (record instanceof Zarafa.core.data.MAPIRecord) {
if (record.get('object_type') == Zarafa.core.mapi.ObjectType.MAPI_FOLDER && record.get('container_class') == "IPF.Contact") {
bid = 2;
}
}
break;
}
return bid;
@ -219,6 +285,9 @@ Zarafa.plugins.contactimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
case Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts']:
component = Zarafa.plugins.contactimporter.dialogs.ImportContentPanel;
break;
case Zarafa.core.data.SharedComponentType['common.contextmenu']:
component = Zarafa.plugins.contactimporter.ui.ContextMenu;
break;
}
return component;

136
js/ui/ContextMenu.js Normal file
View File

@ -0,0 +1,136 @@
Ext.namespace('Zarafa.plugins.contactimporter.ui');
/**
* @class Zarafa.plugins.contactimporter.ui.ContextMenu
* @extends Zarafa.hierarchy.ui.ContextMenu
* @xtype contactimporter.hierarchycontextmenu
*/
Zarafa.plugins.contactimporter.ui.ContextMenu = Ext.extend(Zarafa.hierarchy.ui.ContextMenu, {
/**
* @constructor
* @param {Object} config Configuration object
*/
constructor: function (config) {
config = config || {};
if (config.contextNode) {
config.contextTree = config.contextNode.getOwnerTree();
}
Zarafa.plugins.contactimporter.ui.ContextMenu.superclass.constructor.call(this, config);
// add item to menu
var additionalItems = this.createAdditionalContextMenuItems(config);
for (var i = 0; i < additionalItems.length; i++) {
config.items[0].push(additionalItems[i]);
}
Zarafa.plugins.contactimporter.ui.ContextMenu.superclass.constructor.call(this, config); // redo ... otherwise menu does not get published
},
/**
* Create the Action context menu items.
* @param {Object} config Configuration object for the {@link Zarafa.plugins.contactimporter.ui.ContextMenu ContextMenu}
* @return {Zarafa.core.ui.menu.ConditionalItem[]} The list of Action context menu items
* @private
*
* Note: All handlers are called within the scope of {@link Zarafa.plugins.contactimporter.ui.ContextMenu HierarchyContextMenu}
*/
createAdditionalContextMenuItems: function (config) {
return [{
xtype: 'menuseparator'
}, {
text : _('Import vCard'),
iconCls : 'icon_contactimporter_import',
handler : this.onContextItemImport,
beforeShow: function (item, record) {
var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_MODIFY;
if (!access || (record.isIPMSubTree() && !record.getMAPIStore().isDefaultStore())) {
item.setDisabled(true);
} else {
item.setDisabled(false);
}
}
}, {
text : _('Export vCard'),
iconCls : 'icon_contactimporter_export',
handler : this.onContextItemExport,
beforeShow: function (item, record) {
var access = record.get('access') & Zarafa.core.mapi.Access.ACCESS_READ;
if (!access || (record.isIPMSubTree() && !record.getMAPIStore().isDefaultStore())) {
item.setDisabled(true);
} else {
item.setDisabled(false);
}
}
}];
},
/**
* Fires on selecting 'Open' menu option from {@link Zarafa.plugins.contactimporter.ui.ContextMenu ContextMenu}
* @private
*/
onContextItemExport: function () {
var responseHandler = new Zarafa.plugins.contactimporter.data.ResponseHandler({
successCallback: this.downloadVCF,
scope : this
});
// request attachment preperation
container.getRequest().singleRequest(
'contactmodule',
'export',
{
storeid: this.records.get("store_entryid"),
folder : this.records.get("entryid")
},
responseHandler
);
},
/**
* Fires on selecting 'Open' menu option from {@link Zarafa.plugins.contactimporter.ui.ContextMenu ContextMenu}
* @private
*/
onContextItemImport: function () {
var componentType = Zarafa.core.data.SharedComponentType['plugins.contactimporter.dialogs.importcontacts'];
var config = {
modal : true,
folder: this.records.get("entryid")
};
Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
},
/**
* Callback for the export request.
* @param {Object} response
*/
downloadVCF: function (response) {
if (response.status == false) {
Zarafa.common.dialogs.MessageBox.show({
title : dgettext('plugin_files', 'Warning'),
msg : dgettext('plugin_files', response.message),
icon : Zarafa.common.dialogs.MessageBox.WARNING,
buttons: Zarafa.common.dialogs.MessageBox.OK
});
} else {
var downloadFrame = Ext.getBody().createChild({
tag: 'iframe',
cls: 'x-hidden'
});
var url = document.URL;
var link = url.substring(0, url.lastIndexOf('/') + 1);
link += "index.php?sessionid=" + container.getUser().getSessionId() + "&load=custom&name=download_vcf";
link = Ext.urlAppend(link, "token=" + encodeURIComponent(response.download_token));
link = Ext.urlAppend(link, "filename=" + encodeURIComponent(response.filename));
downloadFrame.dom.contentWindow.location = link;
}
}
});
Ext.reg('contactimporter.hierarchycontextmenu', Zarafa.plugins.contactimporter.ui.ContextMenu);

View File

@ -2,7 +2,7 @@
<!DOCTYPE plugin SYSTEM "manifest.dtd">
<plugin version="2">
<info>
<version>@_@PLUGIN_VERSION@_@</version>
<version>2.0.0</version>
<name>contactimporter</name>
<title>VCF Contact Importer/Exporter</title>
<author>Christoph Haas</author>
@ -20,16 +20,18 @@
<serverfile type="module" module="contactmodule">php/module.contact.php</serverfile>
</server>
<client>
<clientfile load="release">js/contactimporter.js</clientfile>
<clientfile load="release">js/contactimporter-debug.js</clientfile>
<clientfile load="debug">js/contactimporter-debug.js</clientfile>
<clientfile load="source">js/plugin.contactimporter.js</clientfile>
<clientfile load="source">js/ABOUT.js</clientfile>
<clientfile load="source">js/data/ResponseHandler.js</clientfile>
<clientfile load="source">js/dialogs/ImportContentPanel.js</clientfile>
<clientfile load="source">js/dialogs/ImportPanel.js</clientfile>
<clientfile load="source">js/ui/ContextMenu.js</clientfile>
</client>
<resources>
<resourcefile load="release">resources/css/contactimporter-min.css</resourcefile>
<resourcefile load="release">resources/css/contactimporter.css</resourcefile>
<resourcefile load="debug">resources/css/contactimporter.css</resourcefile>
<resourcefile load="source">resources/css/contactimporter-main.css</resourcefile>
</resources>

5
php/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
composer.phar
.composer.lock
composer.lock
vendor
vendor/*

5
php/composer.json Normal file
View File

@ -0,0 +1,5 @@
{
"require": {
"jeroendesloovere/vcard": "1.2.*"
}
}

72
php/download.php Normal file
View File

@ -0,0 +1,72 @@
<?php
/**
* download.php, zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
class DownloadHandler
{
/**
* Download the given vcf file.
* @return bool
*/
public static function doDownload()
{
if (isset($_GET["token"])) {
$token = $_GET["token"];
} else {
return false;
}
if (isset($_GET["filename"])) {
$filename = $_GET["filename"];
} else {
return false;
}
// validate token
if (!ctype_alnum($token)) { // token is a md5 hash
return false;
}
$file = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "vcf_" . $token . ".vcf";
if (!file_exists($file)) { // invalid token
return false;
}
// set headers here
header('Content-Disposition: attachment; filename="' . $filename . '"');
// no caching
header('Expires: 0'); // set expiration time
header('Content-Description: File Transfer');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Length: ' . filesize($file));
header('Content-Type: application/octet-stream');
header('Pragma: public');
flush();
// print the downloaded file
readfile($file);
ignore_user_abort(true);
unlink($file);
}
}

View File

@ -3,7 +3,7 @@
* class.calendar.php, zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -21,10 +21,13 @@
*
*/
include_once('vcf/class.vCard.php');
require_once('mapi/mapitags.php' );
include_once('vendor/autoload.php');
class ContactModule extends Module {
use JeroenDesloovere\VCard\VCard;
use JeroenDesloovere\VCard\VCardParser;
class ContactModule extends Module
{
private $DEBUG = false; // enable error_log debugging
@ -33,7 +36,8 @@ class ContactModule extends Module {
* @param $id
* @param $data
*/
public function __construct($id, $data) {
public function __construct($id, $data)
{
parent::Module($id, $data);
}
@ -42,7 +46,8 @@ class ContactModule extends Module {
* Exception part is used for authentication errors also
* @return boolean true on success or false on failure.
*/
public function execute() {
public function execute()
{
$result = false;
if (!$this->DEBUG) {
@ -63,6 +68,9 @@ class ContactModule extends Module {
case "import":
$result = $this->importContacts($actionType, $actionData);
break;
case "export":
$result = $this->exportContacts($actionType, $actionData);
break;
case "importattachment":
$result = $this->getAttachmentPath($actionType, $actionData);
break;
@ -90,7 +98,8 @@ class ContactModule extends Module {
* @param $length the lenght of the generated string
* @return string a random string
*/
private function randomstring($length = 6) {
private function randomstring($length = 6)
{
// $chars - all allowed charakters
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
@ -111,7 +120,8 @@ class ContactModule extends Module {
* @param $actionType
* @param $actionData
*/
private function importContacts($actionType, $actionData) {
private function importContacts($actionType, $actionData)
{
// Get uploaded vcf path
$vcffile = false;
@ -142,8 +152,9 @@ class ContactModule extends Module {
$error_msg = "";
// parse the vcf file a last time...
$parser = null;
try {
$vcard = new vCard($vcffile, false, array('Collapse' => false)); // Parse it!
$parser = VCardParser::parseFromFile($vcffile);
} catch (Exception $e) {
$error = true;
$error_msg = $e->getMessage();
@ -151,13 +162,8 @@ class ContactModule extends Module {
$contacts = array();
if(!$error && count($vcard) > 0) {
$vCard = $vcard;
if (count($vCard) == 1) {
$vCard = array($vcard);
}
$contacts = $this->parseContactsToArray($vCard);
if (!$error && iterator_count($parser) > 0) {
$contacts = $this->parseContactsToArray($parser);
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid));
$folder = mapi_msgstore_openentry($store, hex2bin($folderid));
@ -243,7 +249,258 @@ class ContactModule extends Module {
$GLOBALS["bus"]->addData($this->getResponseData());
}
private function replaceStringPropertyTags($store, $properties) {
/**
* Get a property from the array.
* @param $props
* @param $propname
* @return string
*/
private function getProp($props, $propname)
{
$p = $this->getProperties();
if (isset($props["props"][$propname])) {
return $props["props"][$propname];
}
return "";
}
/**
* Export selected contacts to vCard.
* @param $actionType
* @param $actionData
* @return bool
*/
private function exportContacts($actionType, $actionData)
{
// Get store id
$storeid = false;
if (isset($actionData["storeid"])) {
$storeid = $actionData["storeid"];
}
// Get records
$records = array();
if (isset($actionData["records"])) {
$records = $actionData["records"];
}
// Get folders
$folder = false;
if (isset($actionData["folder"])) {
$folder = $actionData["folder"];
}
$response = array();
$error = false;
$error_msg = "";
// write csv
$token = $this->randomstring(16);
$file = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "vcf_" . $token . ".vcf";
file_put_contents($file, "");
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid));
if ($store) {
// load folder first
if ($folder !== false) {
$mapifolder = mapi_msgstore_openentry($store, hex2bin($folder));
$table = mapi_folder_getcontentstable($mapifolder);
$list = mapi_table_queryallrows($table, array(PR_ENTRYID));
foreach ($list as $item) {
$records[] = bin2hex($item[PR_ENTRYID]);
}
}
for ($index = 0, $count = count($records); $index < $count; $index++) {
// define vcard
$vcard = new VCard();
$message = mapi_msgstore_openentry($store, hex2bin($records[$index]));
// get message properties.
$properties = $GLOBALS['properties']->getContactProperties();
$plaintext = true;
$messageProps = $GLOBALS['operations']->getMessageProps($store, $message, $properties, $plaintext);
// define variables
$firstname = $this->getProp($messageProps, "given_name");
$lastname = $this->getProp($messageProps, "surname");
$additional = $this->getProp($messageProps, "middle_name");
$prefix = $this->getProp($messageProps, "display_name_prefix");
$suffix = '';
// add personal data
$vcard->addName($lastname, $firstname, $additional, $prefix, $suffix);
$company = $this->getProp($messageProps, "company_name");
if (!empty($company)) {
$vcard->addCompany($company);
}
$jobtitle = $this->getProp($messageProps, "title");
if (!empty($jobtitle)) {
$vcard->addJobtitle($jobtitle);
}
// MAIL
$mail = $this->getProp($messageProps, "email_address_1");
if (!empty($mail)) {
$vcard->addEmail($mail);
}
$mail = $this->getProp($messageProps, "email_address_2");
if (!empty($mail)) {
$vcard->addEmail($mail);
}
$mail = $this->getProp($messageProps, "email_address_3");
if (!empty($mail)) {
$vcard->addEmail($mail);
}
// PHONE
$wphone = $this->getProp($messageProps, "business_telephone_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'WORK');
}
$wphone = $this->getProp($messageProps, "home_telephone_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'HOME');
}
$wphone = $this->getProp($messageProps, "cellular_telephone_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'CELL');
}
$wphone = $this->getProp($messageProps, "business_fax_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'FAX');
}
$wphone = $this->getProp($messageProps, "pager_telephone_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'PAGER');
}
$wphone = $this->getProp($messageProps, "car_telephone_number");
if (!empty($wphone)) {
$vcard->addPhoneNumber($wphone, 'CAR');
}
// ADDRESS
$addr = $this->getProp($messageProps, "business_address");
if (!empty($addr)) {
$vcard->addAddress(null, null, $this->getProp($messageProps, "business_address_street"), $this->getProp($messageProps, "business_address_city"), $this->getProp($messageProps, "business_address_state"), $this->getProp($messageProps, "business_address_postal_code"), $this->getProp($messageProps, "business_address_country"), "WORK");
}
$addr = $this->getProp($messageProps, "home_address");
if (!empty($addr)) {
$vcard->addAddress(null, null, $this->getProp($messageProps, "home_address_street"), $this->getProp($messageProps, "home_address_city"), $this->getProp($messageProps, "home_address_state"), $this->getProp($messageProps, "home_address_postal_code"), $this->getProp($messageProps, "home_address_country"), "HOME");
}
$addr = $this->getProp($messageProps, "other_address");
if (!empty($addr)) {
$vcard->addAddress(null, null, $this->getProp($messageProps, "other_address_street"), $this->getProp($messageProps, "other_address_city"), $this->getProp($messageProps, "other_address_state"), $this->getProp($messageProps, "other_address_postal_code"), $this->getProp($messageProps, "other_address_country"), "OTHER");
}
// MISC
$url = $this->getProp($messageProps, "webpage");
if (!empty($url)) {
$vcard->addURL($url);
}
$bday = $this->getProp($messageProps, "birthday");
if (!empty($bday)) {
$vcard->addBirthday(date("Y-m-d", $bday));
}
$notes = $this->getProp($messageProps, "body");
if (!empty($notes)) {
$vcard->addNote($notes);
}
$haspicture = $this->getProp($messageProps, "has_picture");
if (!empty($haspicture) && $haspicture === true) {
$attachnum = -1;
if (isset($messageProps["attachments"]) && isset($messageProps["attachments"]["item"])) {
foreach ($messageProps["attachments"]["item"] as $attachment) {
if ($attachment["props"]["attachment_contactphoto"] == true) {
$attachnum = $attachment["props"]["attach_num"];
break;
}
}
}
if ($attachnum >= 0) {
$attachment = $this->getAttachmentByAttachNum($message, $attachnum); // get first attachment only
$phototoken = $this->randomstring(16);
$tmpphoto = PLUGIN_CONTACTIMPORTER_TMP_UPLOAD . "photo_" . $phototoken . ".jpg";
$this->storeSavedAttachment($tmpphoto, $attachment);
$vcard->addPhoto($tmpphoto, true);
unlink($tmpphoto);
}
}
// write combined vcf
file_put_contents($file, file_get_contents($file) . $vcard->getOutput());
}
} else {
return false;
}
if (count($records) > 0) {
$response['status'] = true;
$response['download_token'] = $token;
$response['filename'] = count($records) . "contacts.vcf";
} else {
$response['status'] = false;
$response['message'] = "No contacts found. Export skipped!";
}
$this->addActionData($actionType, $response);
$GLOBALS["bus"]->addData($this->getResponseData());
}
/**
* Returns attachment based on specified attachNum, additionally it will also get embedded message
* if we want to get the inline image attachment.
* @param $message
* @param array $attachNum
* @return MAPIAttach embedded message attachment or attachment that is requested
*/
private function getAttachmentByAttachNum($message, $attachNum)
{
// open the attachment
$attachment = mapi_message_openattach($message, $attachNum);
return $attachment;
}
/**
* Function will open passed attachment and generate response for that attachment to send it to client.
* This should only be used to download attachment that is already saved in MAPIMessage.
* @param MAPIAttach $attachment attachment which will be dumped to client side
* @return Response response to sent to client including attachment data
*/
private function storeSavedAttachment($temppath, $attachment)
{
// Check if the attachment is opened
if ($attachment) {
// Open a stream to get the attachment data
$stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
$stat = mapi_stream_stat($stream);
// Read the attachment content from the stream
$body = '';
for ($i = 0; $i < $stat['cb']; $i += BLOCK_SIZE) {
$body .= mapi_stream_read($stream, BLOCK_SIZE);
}
file_put_contents($temppath, $body);
}
}
/**
* Replace String Property Tags
* @param $store
* @param $properties
* @return array
*/
private function replaceStringPropertyTags($store, $properties)
{
$newProperties = array();
$ids = array("name" => array(), "id" => array(), "guid" => array(), "type" => array()); // this array stores all the information needed to retrieve a named property
@ -307,10 +564,12 @@ class ContactModule extends Module {
*
* @return [array] the propertyarray
*/
private function getProperties() {
private function getProperties()
{
$properties = array();
$properties["subject"] = PR_SUBJECT;
$properties["hide_attachments"] = "PT_BOOLEAN:PSETID_Common:0x851";
$properties["icon_index"] = PR_ICON_INDEX;
$properties["message_class"] = PR_MESSAGE_CLASS;
$properties["display_name"] = PR_DISPLAY_NAME;
@ -445,13 +704,16 @@ class ContactModule extends Module {
* @param $actionType
* @param $actionData
*/
private function loadContacts($actionType, $actionData) {
private function loadContacts($actionType, $actionData)
{
$error = false;
$error_msg = "";
if (is_readable($actionData["vcf_filepath"])) {
$parser = null;
try {
$vcard = new vCard($actionData["vcf_filepath"], false, array('Collapse' => false)); // Parse it!
$parser = VCardParser::parseFromFile($actionData["vcf_filepath"]);
} catch (Exception $e) {
$error = true;
$error_msg = $e->getMessage();
@ -460,19 +722,14 @@ class ContactModule extends Module {
$response['status'] = false;
$response['message'] = $error_msg;
} else {
if(count($vcard) == 0) {
if (iterator_count($parser) == 0) {
$response['status'] = false;
$response['message'] = "No contacts in vcf file";
} else {
$vCard = $vcard;
if (count($vCard) == 1) {
$vCard = array($vcard);
}
$response['status'] = true;
$response['parsed_file'] = $actionData["vcf_filepath"];
$response['parsed'] = array(
'contacts' => $this->parseContactsToArray($vCard)
'contacts' => $this->parseContactsToArray($parser)
);
}
}
@ -497,148 +754,207 @@ class ContactModule extends Module {
* @return array parsed contacts
* @private
*/
private function parseContactsToArray($contacts, $csv = false) {
private function parseContactsToArray($contacts, $csv = false)
{
$carr = array();
if (!$csv) {
foreach ($contacts as $Index => $vCard) {
$properties = array();
$properties["display_name"] = $vCard -> FN[0];
$properties["fileas"] = $vCard -> FN[0];
if (isset($vCard->fullname)) {
$properties["display_name"] = $vCard->fullname;
$properties["fileas"] = $vCard->fullname;
} elseif (!isset($vCard->organization)) {
error_log("Skipping entry! No fullname/organization given.");
continue;
}
$properties["hide_attachments"] = true;
//uid - used for front/backend communication
$properties["internal_fields"] = array();
$properties["internal_fields"]["contact_uid"] = base64_encode($Index . $properties["fileas"]);
foreach ($vCard -> N as $Name) {
$properties["given_name"] = $Name['FirstName'];
$properties["middle_name"] = $Name['AdditionalNames'];
$properties["surname"] = $Name['LastName'];
$properties["display_name_prefix"] = $Name['Prefixes'];
}
if ($vCard -> TEL) {
foreach ($vCard -> TEL as $Tel) {
if(!is_scalar($Tel)) {
if(in_array("home", $Tel['Type'])) {
$properties["home_telephone_number"] = $Tel['Value'];
} else if(in_array("cell", $Tel['Type'])) {
$properties["cellular_telephone_number"] = $Tel['Value'];
} else if(in_array("work", $Tel['Type'])) {
$properties["business_telephone_number"] = $Tel['Value'];
} else if(in_array("fax", $Tel['Type'])) {
$properties["business_fax_number"] = $Tel['Value'];
} else if(in_array("pager", $Tel['Type'])) {
$properties["pager_telephone_number"] = $Tel['Value'];
} else if(in_array("isdn", $Tel['Type'])) {
$properties["isdn_number"] = $Tel['Value'];
} else if(in_array("car", $Tel['Type'])) {
$properties["car_telephone_number"] = $Tel['Value'];
} else if(in_array("modem", $Tel['Type'])) {
$properties["ttytdd_telephone_number"] = $Tel['Value'];
$properties["given_name"] = $vCard->firstname;
$properties["middle_name"] = $vCard->additional;
$properties["surname"] = $vCard->lastname;
$properties["display_name_prefix"] = $vCard->prefix;
if (isset($vCard->phone) && count($vCard->phone) > 0) {
foreach ($vCard->phone as $type => $number) {
$number = $number[0]; // we only can store one number
if ($this->startswith(strtolower($type), "home") || strtolower($type) === "default") {
$properties["home_telephone_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "cell")) {
$properties["cellular_telephone_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "work")) {
$properties["business_telephone_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "fax")) {
$properties["business_fax_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "pager")) {
$properties["pager_telephone_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "isdn")) {
$properties["isdn_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "car")) {
$properties["car_telephone_number"] = $number;
} else {
if ($this->startswith(strtolower($type), "modem")) {
$properties["ttytdd_telephone_number"] = $number;
}
}
}
}
if ($vCard -> EMAIL) {
$e=0;
foreach ($vCard -> EMAIL as $Email) {
$fileas = $Email['Value'];
}
}
}
}
}
}
if (isset($vCard->email) && count($vCard->email) > 0) {
$emailcount = 0;
$properties["address_book_long"] = 0;
foreach ($vCard->email as $type => $email) {
foreach ($email as $mail) {
$fileas = $mail;
if (isset($properties["fileas"]) && !empty($properties["fileas"])) {
$fileas = $properties["fileas"];
$fileas = $properties["fileas"]; // set to real name
}
if(!is_scalar($Email)) {
switch($e) {
// we only have storage for 3 mail addresses!
/**
* type of email address address_book_mv address_book_long
* email1 0 1 (0x00000001)
* email2 1 2 (0x00000002)
* email3 2 4 (0x00000004)
* fax2(business fax) 3 8 (0x00000008)
* fax3(home fax) 4 16 (0x00000010)
* fax1(primary fax) 5 32 (0x00000020)
*
* address_book_mv is a multivalued property so all the values are passed in array
* address_book_long stores sum of the flags
* these both properties should be in sync always
*/
switch ($emailcount) {
case 0:
$properties["email_address_1"] = $Email['Value'];
$properties["email_address_display_name_1"] = $fileas . " (" . $Email['Value'] . ")";
$properties["email_address_1"] = $mail;
$properties["email_address_display_name_1"] = $fileas . " (" . $mail . ")";
$properties["email_address_display_name_email_1"] = $mail;
$properties["address_book_mv"][] = 0; // this is needed for adding the contact to the email address book, 0 = email 1
$properties["address_book_long"] += 1; // this specifies the number of elements in address_book_mv
break;
case 1:
$properties["email_address_2"] = $Email['Value'];
$properties["email_address_display_name_2"] = $fileas . " (" . $Email['Value'] . ")";
$properties["email_address_2"] = $mail;
$properties["email_address_display_name_2"] = $fileas . " (" . $mail . ")";
$properties["email_address_display_name_email_2"] = $mail;
$properties["address_book_mv"][] = 1; // this is needed for adding the contact to the email address book, 1 = email 2
$properties["address_book_long"] += 2; // this specifies the number of elements in address_book_mv
break;
case 2:
$properties["email_address_3"] = $Email['Value'];
$properties["email_address_display_name_3"] = $fileas . " (" . $Email['Value'] . ")";
$properties["email_address_3"] = $mail;
$properties["email_address_display_name_3"] = $fileas . " (" . $mail . ")";
$properties["email_address_display_name_email_3"] = $mail;
$properties["address_book_mv"][] = 2; // this is needed for adding the contact to the email address book, 2 = email 3
$properties["address_book_long"] += 4; // this specifies the number of elements in address_book_mv
break;
default:
break;
default: break;
}
$e++;
$emailcount++;
}
}
}
if ($vCard -> ORG) {
foreach ($vCard -> ORG as $Organization) {
$properties["company_name"] = $Organization['Name'];
if (isset($vCard->organization)) {
$properties["company_name"] = $vCard->organization;
if (empty($properties["display_name"])) {
$properties["display_name"] = $Organization['Name']; // if we have no displayname - use the company name as displayname
$properties["fileas"] = $Organization['Name'];
$properties["display_name"] = $vCard->organization; // if we have no displayname - use the company name as displayname
$properties["fileas"] = $vCard->organization;
}
}
if (isset($vCard->title)) {
$properties["title"] = $vCard->title;
}
if (isset($vCard->url) && count($vCard->url) > 0) {
foreach ($vCard->url as $type => $url) {
$url = $url[0]; // only 1 webaddress per type
$properties["webpage"] = $url;
break; // we can only store on url
}
}
if (isset($vCard->address) && count($vCard->address) > 0) {
foreach ($vCard->address as $type => $address) {
$address = $address[0]; // we only can store one address per type
if ($this->startswith(strtolower($type), "work")) {
$properties["business_address_street"] = $address->street;
if (!empty($address->extended)) {
$properties["business_address_street"] .= "\n" . $address->extended;
}
$properties["business_address_city"] = $address->city;
$properties["business_address_state"] = $address->region;
$properties["business_address_postal_code"] = $address->zip;
$properties["business_address_country"] = $address->country;
$properties["business_address"] = $this->buildAddressString($properties["business_address_street"], $address->zip, $address->city, $address->region, $address->country);
} else {
if ($this->startswith(strtolower($type), "home")) {
$properties["home_address_street"] = $address->street;
if (!empty($address->extended)) {
$properties["home_address_street"] .= "\n" . $address->extended;
}
$properties["home_address_city"] = $address->city;
$properties["home_address_state"] = $address->region;
$properties["home_address_postal_code"] = $address->zip;
$properties["home_address_country"] = $address->country;
$properties["home_address"] = $this->buildAddressString($properties["home_address_street"], $address->zip, $address->city, $address->region, $address->country);
} else {
$properties["other_address_street"] = $address->street;
if (!empty($address->extended)) {
$properties["other_address_street"] .= "\n" . $address->extended;
}
$properties["other_address_city"] = $address->city;
$properties["other_address_state"] = $address->region;
$properties["other_address_postal_code"] = $address->zip;
$properties["other_address_country"] = $address->country;
$properties["other_address"] = $this->buildAddressString($properties["other_address_street"], $address->zip, $address->city, $address->region, $address->country);
}
}
}
if ($vCard -> TITLE) {
$title = $vCard -> TITLE[0];
$properties["title"] = is_array($title) ? $title["Value"] : $title;
}
if ($vCard -> URL) {
$url = $vCard -> URL[0]; // only 1 webaddress
$properties["webpage"] = is_array($url) ? $url["Value"] : $url;
if (isset($vCard->birthday)) {
$properties["birthday"] = $vCard->birthday->getTimestamp();
}
if ($vCard -> IMPP) {
foreach ($vCard -> IMPP as $IMPP) {
if (!is_scalar($IMPP)) {
$properties["im"] = $IMPP['Value'];
if (isset($vCard->note)) {
$properties["notes"] = $vCard->note;
}
}
}
if ($vCard -> ADR) {
foreach ($vCard -> ADR as $Address) {
if(in_array("work", $Address['Type'])) {
$properties["business_address_street"] = $Address['StreetAddress'];
$properties["business_address_city"] = $Address['Locality'];
$properties["business_address_state"] = $Address['Region'];
$properties["business_address_postal_code"] = $Address['PostalCode'];
$properties["business_address_country"] = $Address['Country'];
$properties["business_address"] = $this->buildAddressString($Address['StreetAddress'], $Address['PostalCode'], $Address['Locality'], $Address['Region'], $Address['Country']);
} else if(in_array("home", $Address['Type'])) {
$properties["home_address_street"] = $Address['StreetAddress'];
$properties["home_address_city"] = $Address['Locality'];
$properties["home_address_state"] = $Address['Region'];
$properties["home_address_postal_code"] = $Address['PostalCode'];
$properties["home_address_country"] = $Address['Country'];
$properties["home_address"] = $this->buildAddressString($Address['StreetAddress'], $Address['PostalCode'], $Address['Locality'], $Address['Region'], $Address['Country']);
} else if(in_array("postal", $Address['Type'])||in_array("parcel", $Address['Type'])||in_array("intl", $Address['Type'])||in_array("dom", $Address['Type'])) {
$properties["other_address_street"] = $Address['StreetAddress'];
$properties["other_address_city"] = $Address['Locality'];
$properties["other_address_state"] = $Address['Region'];
$properties["other_address_postal_code"] = $Address['PostalCode'];
$properties["other_address_country"] = $Address['Country'];
$properties["other_address"] = $this->buildAddressString($Address['StreetAddress'], $Address['PostalCode'], $Address['Locality'], $Address['Region'], $Address['Country']);
}
}
}
if ($vCard -> BDAY) {
$properties["birthday"] = strtotime($vCard -> BDAY[0]);
}
if ($vCard -> NOTE) {
$properties["notes"] = $vCard -> NOTE[0];
}
if ($vCard -> PHOTO) {
if (isset($vCard->rawPhoto) || isset($vCard->photo)) {
if (!is_writable(TMP_PATH . "/")) {
error_log("could not write to export tmp directory!: " . $E);
error_log("Can not write to export tmp directory!");
} else {
$tmppath = TMP_PATH . "/" . $this->randomstring(15);
try {
if($vCard -> SaveFile('photo', 0, $tmppath)) {
if (isset($vCard->rawPhoto)) {
if (file_put_contents($tmppath, $vCard->rawPhoto)) {
$properties["internal_fields"]["x_photo_path"] = $tmppath;
}
} elseif (isset($vCard->photo)) {
if ($this->startswith(strtolower($vCard->photo), "http://") || $this->startswith(strtolower($vCard->photo), "https://")) { // check if it starts with http
$ctx = stream_context_create(array('http' =>
array(
'timeout' => 3, //3 Seconds timout
)
));
if (file_put_contents($tmppath, file_get_contents($vCard->photo, false, $ctx))) {
$properties["internal_fields"]["x_photo_path"] = $tmppath;
}
} else {
if($this->DEBUG) {
error_log("remote imagefetching not implemented");
error_log("Invalid photo url: " . $vCard->photo);
}
}
} catch (Exception $E) {
error_log("Image exception: " . $E);
}
}
}
array_push($carr, $properties);
@ -661,18 +977,31 @@ class ContactModule extends Module {
* @return string the concatinated address string
* @private
*/
private function buildAddressString($street, $zip, $city, $state, $country) {
private function buildAddressString($street, $zip, $city, $state, $country)
{
$out = "";
if (isset($country) && $street != "") $out = $country;
if (isset($country) && $street != "") {
$out = $country;
}
$zcs = "";
if (isset($zip) && $zip != "") $zcs = $zip;
if (isset($city) && $city != "") $zcs .= (($zcs)?" ":"") . $city;
if (isset($state) && $state != "") $zcs .= (($zcs)?" ":"") . $state;
if ($zcs) $out = $zcs . "\r\n" . $out;
if (isset($zip) && $zip != "") {
$zcs = $zip;
}
if (isset($city) && $city != "") {
$zcs .= (($zcs) ? " " : "") . $city;
}
if (isset($state) && $state != "") {
$zcs .= (($zcs) ? " " : "") . $state;
}
if ($zcs) {
$out = $zcs . "\n" . $out;
}
if (isset($street) && $street != "") $out = $street . (($out)?"\r\n". $out: "") ;
if (isset($street) && $street != "") {
$out = $street . (($out) ? "\n\n" . $out : "");
}
return $out;
}
@ -683,7 +1012,8 @@ class ContactModule extends Module {
* @param $actionData
* @private
*/
private function getAttachmentPath($actionType, $actionData) {
private function getAttachmentPath($actionType, $actionData)
{
// Get store id
$storeid = false;
if (isset($actionData["store"])) {
@ -720,8 +1050,7 @@ class ContactModule extends Module {
// Check if attachNum isset
if ($attachNum) {
// Loop through the attachNums, message in message in message ...
for($i = 0; $i < (count($attachNum) - 1); $i++)
{
for ($i = 0; $i < (count($attachNum) - 1); $i++) {
// Open the attachment
$tempattach = mapi_message_openattach($message, (int)$attachNum[$i]);
if ($tempattach) {
@ -747,11 +1076,15 @@ class ContactModule extends Module {
// Set filename
if (isset($props[PR_ATTACH_LONG_FILENAME])) {
$filename = $props[PR_ATTACH_LONG_FILENAME];
} else if(isset($props[PR_ATTACH_FILENAME])) {
} else {
if (isset($props[PR_ATTACH_FILENAME])) {
$filename = $props[PR_ATTACH_FILENAME];
} else if(isset($props[PR_DISPLAY_NAME])) {
} else {
if (isset($props[PR_DISPLAY_NAME])) {
$filename = $props[PR_DISPLAY_NAME];
}
}
}
// Set content type
if (isset($props[PR_ATTACH_MIME_TAG])) {
@ -815,6 +1148,20 @@ class ContactModule extends Module {
$GLOBALS["bus"]->addData($this->getResponseData());
}
}
};
/**
* Check if string starts with other string.
* @param $haystack
* @param $needle
* @return bool
*/
private function startswith($haystack, $needle)
{
$haystack = str_replace("type=", "", $haystack); // remove type from string
return substr($haystack, 0, strlen($needle)) === $needle;
}
}
;
?>

View File

@ -3,7 +3,7 @@
* plugin.contactimporter.php, zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -20,6 +20,7 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
require_once __DIR__ . "/download.php";
/**
* contactimporter Plugin
@ -27,19 +28,24 @@
* With this plugin you can import a vcf file to your zarafa addressbook
*
*/
class Plugincontactimporter extends Plugin {
class Plugincontactimporter extends Plugin
{
/**
* Constructor
*/
function Plugincontactimporter() {}
function Plugincontactimporter()
{
}
/**
* Function initializes the Plugin and registers all hooks
*
* @return void
*/
function init() {
function init()
{
$this->registerHook('server.core.settings.init.before');
$this->registerHook('server.index.load.custom');
}
/**
@ -49,11 +55,17 @@ class Plugincontactimporter extends Plugin {
* @param mixed $data object(s) related to the hook
* @return void
*/
function execute($eventID, &$data) {
function execute($eventID, &$data)
{
switch ($eventID) {
case 'server.core.settings.init.before' :
$this->injectPluginSettings($data);
break;
case 'server.index.load.custom':
if ($data['name'] == 'download_vcf') {
DownloadHandler::doDownload();
}
break;
}
}
@ -62,14 +74,14 @@ class Plugincontactimporter extends Plugin {
* settings.
* @param Array $data Reference to the data of the triggered hook
*/
function injectPluginSettings(&$data) {
function injectPluginSettings(&$data)
{
$data['settingsObj']->addSysAdminDefaults(Array(
'zarafa' => Array(
'v1' => Array(
'plugins' => Array(
'contactimporter' => Array(
'enable' => PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE,
'enable_export' => PLUGIN_CONTACTIMPORTER_USER_DEFAULT_ENABLE_EXPORT,
'default_addressbook' => PLUGIN_CONTACTIMPORTER_DEFAULT
)
)
@ -78,4 +90,5 @@ class Plugincontactimporter extends Plugin {
));
}
}
?>

View File

@ -3,7 +3,7 @@
* upload.php, zarafa contact to vcf im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2013 Christoph Haas
* Copyright (C) 2012-2016 Christoph Haas
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@ -30,7 +30,8 @@ ini_set('display_errors', '0');
* respond/echo JSON
* @param $arr
*/
function respondJSON($arr) {
function respondJSON($arr)
{
echo json_encode($arr);
}
@ -39,7 +40,8 @@ function respondJSON($arr) {
* @param $length the lenght of the generated string
* @return string a random string
*/
function randomstring($length = 6) {
function randomstring($length = 6)
{
// $chars - all allowed charakters
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

View File

@ -1,690 +0,0 @@
<?php
/**
* vCard class for parsing a vCard and/or creating one
*
* @link https://github.com/nuovo/vCard-parser
* @author Martins Pilsetnieks, Roberts Bruveris
* @see RFC 2426, RFC 2425
* @version 0.4.8
*/
class vCard implements Countable, Iterator
{
const MODE_ERROR = 'error';
const MODE_SINGLE = 'single';
const MODE_MULTIPLE = 'multiple';
const endl = "\n";
/**
* @var string Current object mode - error, single or multiple (for a single vCard within a file and multiple combined vCards)
*/
private $Mode; //single, multiple, error
private $Path = '';
private $RawData = '';
/**
* @var array Internal options container. Options:
* bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
* If false, an array is returned even if it has only one value.
*/
private $Options = array(
'Collapse' => false
);
/**
* @var array Internal data container. Contains vCard objects for multiple vCards and just the data for single vCards.
*/
private $Data = array();
/**
* @static Parts of structured elements according to the spec.
*/
private static $Spec_StructuredElements = array(
'n' => array('LastName', 'FirstName', 'AdditionalNames', 'Prefixes', 'Suffixes'),
'adr' => array('POBox', 'ExtendedAddress', 'StreetAddress', 'Locality', 'Region', 'PostalCode', 'Country'),
'geo' => array('Latitude', 'Longitude'),
'org' => array('Name', 'Unit1', 'Unit2')
);
private static $Spec_MultipleValueElements = array('nickname', 'categories');
private static $Spec_ElementTypes = array(
'email' => array('internet', 'x400', 'pref'),
'adr' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
'label' => array('dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'),
'tel' => array('home', 'msg', 'work', 'pref', 'voice', 'fax', 'cell', 'video', 'pager', 'bbs', 'modem', 'car', 'isdn', 'pcs'),
'impp' => array('personal', 'business', 'home', 'work', 'mobile', 'pref')
);
private static $Spec_FileElements = array('photo', 'logo', 'sound');
/**
* vCard constructor
*
* @param string Path to file, optional.
* @param string Raw data, optional.
* @param array Additional options, optional. Currently supported options:
* bool Collapse: If true, elements that can have multiple values but have only a single value are returned as that value instead of an array
* If false, an array is returned even if it has only one value.
*
* One of these parameters must be provided, otherwise an exception is thrown.
*/
public function __construct($Path = false, $RawData = false, array $Options = null)
{
// Checking preconditions for the parser.
// If path is given, the file should be accessible.
// If raw data is given, it is taken as it is.
// In both cases the real content is put in $this -> RawData
if ($Path)
{
if (!is_readable($Path))
{
throw new Exception('vCard: Path not accessible ('.$Path.')');
}
$this -> Path = $Path;
$this -> RawData = file_get_contents($this -> Path);
}
elseif ($RawData)
{
$this -> RawData = $RawData;
}
else
{
//throw new Exception('vCard: No content provided');
// Not necessary anymore as possibility to create vCards is added
}
if (!$this -> Path && !$this -> RawData)
{
return true;
}
if ($Options)
{
$this -> Options = array_merge($this -> Options, $Options);
}
// Counting the begin/end separators. If there aren't any or the count doesn't match, there is a problem with the file.
// If there is only one, this is a single vCard, if more, multiple vCards are combined.
$Matches = array();
$vCardBeginCount = preg_match_all('{^BEGIN\:VCARD}miS', $this -> RawData, $Matches);
$vCardEndCount = preg_match_all('{^END\:VCARD}miS', $this -> RawData, $Matches);
if (($vCardBeginCount != $vCardEndCount) || !$vCardBeginCount)
{
$this -> Mode = vCard::MODE_ERROR;
throw new Exception('vCard: invalid vCard');
}
$this -> Mode = $vCardBeginCount == 1 ? vCard::MODE_SINGLE : vCard::MODE_MULTIPLE;
// Removing/changing inappropriate newlines, i.e., all CRs or multiple newlines are changed to a single newline
$this -> RawData = str_replace("\r", "\n", $this -> RawData);
$this -> RawData = preg_replace('{(\n+)}', "\n", $this -> RawData);
// In multiple card mode the raw text is split at card beginning markers and each
// fragment is parsed in a separate vCard object.
if ($this -> Mode == self::MODE_MULTIPLE)
{
$this -> RawData = explode('BEGIN:VCARD', $this -> RawData);
$this -> RawData = array_filter($this -> RawData);
foreach ($this -> RawData as $SinglevCardRawData)
{
// Prepending "BEGIN:VCARD" to the raw string because we exploded on that one.
// If there won't be the BEGIN marker in the new object, it will fail.
$SinglevCardRawData = 'BEGIN:VCARD'."\n".$SinglevCardRawData;
$ClassName = get_class($this);
$this -> Data[] = new $ClassName(false, $SinglevCardRawData);
}
}
else
{
// Protect the BASE64 final = sign (detected by the line beginning with whitespace), otherwise the next replace will get rid of it
$this -> RawData = preg_replace('{(\n\s.+)=(\n)}', '$1-base64=-$2', $this -> RawData);
// Joining multiple lines that are split with a hard wrap and indicated by an equals sign at the end of line
// (quoted-printable-encoded values in v2.1 vCards)
$this -> RawData = str_replace("=\n", '', $this -> RawData);
// Joining multiple lines that are split with a soft wrap (space or tab on the beginning of the next line
$this -> RawData = str_replace(array("\n ", "\n\t"), '-wrap-', $this -> RawData);
// Restoring the BASE64 final equals sign (see a few lines above)
$this -> RawData = str_replace("-base64=-\n", "=\n", $this -> RawData);
$Lines = explode("\n", $this -> RawData);
foreach ($Lines as $Line)
{
// Lines without colons are skipped because, most likely, they contain no data.
if (strpos($Line, ':') === false)
{
continue;
}
// Each line is split into two parts. The key contains the element name and additional parameters, if present,
// value is just the value
list($Key, $Value) = explode(':', $Line, 2);
// Key is transformed to lowercase because, even though the element and parameter names are written in uppercase,
// it is quite possible that they will be in lower- or mixed case.
// The key is trimmed to allow for non-significant WSP characters as allowed by v2.1
$Key = strtolower(trim(self::Unescape($Key)));
// These two lines can be skipped as they aren't necessary at all.
if ($Key == 'begin' || $Key == 'end')
{
continue;
}
if ((strpos($Key, 'agent') === 0) && (stripos($Value, 'begin:vcard') !== false))
{
$ClassName = get_class($this);
$Value = new $ClassName(false, str_replace('-wrap-', "\n", $Value));
if (!isset($this -> Data[$Key]))
{
$this -> Data[$Key] = array();
}
$this -> Data[$Key][] = $Value;
continue;
}
else
{
$Value = str_replace('-wrap-', '', $Value);
}
$Value = trim(self::Unescape($Value));
$Type = array();
// Here additional parameters are parsed
$KeyParts = explode(';', $Key);
$Key = $KeyParts[0];
$Encoding = false;
if (strpos($Key, 'item') === 0)
{
$TmpKey = explode('.', $Key, 2);
$Key = $TmpKey[1];
$ItemIndex = (int)str_ireplace('item', '', $TmpKey[0]);
}
if (count($KeyParts) > 1)
{
$Parameters = self::ParseParameters($Key, array_slice($KeyParts, 1));
foreach ($Parameters as $ParamKey => $ParamValue)
{
switch ($ParamKey)
{
case 'encoding':
$Encoding = $ParamValue;
if (in_array($ParamValue, array('b', 'base64')))
{
//$Value = base64_decode($Value);
}
elseif ($ParamValue == 'quoted-printable') // v2.1
{
$Value = quoted_printable_decode($Value);
}
break;
case 'charset': // v2.1
if ($ParamValue != 'utf-8' && $ParamValue != 'utf8')
{
$Value = mb_convert_encoding($Value, 'UTF-8', $ParamValue);
}
break;
case 'type':
$Type = $ParamValue;
break;
}
}
}
// Checking files for colon-separated additional parameters (Apple's Address Book does this), for example, "X-ABCROP-RECTANGLE" for photos
if (in_array($Key, self::$Spec_FileElements) && isset($Parameters['encoding']) && in_array($Parameters['encoding'], array('b', 'base64')))
{
// If colon is present in the value, it must contain Address Book parameters
// (colon is an invalid character for base64 so it shouldn't appear in valid files)
if (strpos($Value, ':') !== false)
{
$Value = explode(':', $Value);
$Value = array_pop($Value);
}
}
// Values are parsed according to their type
if (isset(self::$Spec_StructuredElements[$Key]))
{
$Value = self::ParseStructuredValue($Value, $Key);
if ($Type)
{
$Value['Type'] = $Type;
}
}
else
{
if (in_array($Key, self::$Spec_MultipleValueElements))
{
$Value = self::ParseMultipleTextValue($Value, $Key);
}
if ($Type)
{
$Value = array(
'Value' => $Value,
'Type' => $Type
);
}
}
if (is_array($Value) && $Encoding)
{
$Value['Encoding'] = $Encoding;
}
if (!isset($this -> Data[$Key]))
{
$this -> Data[$Key] = array();
}
$this -> Data[$Key][] = $Value;
}
}
}
/**
* Magic method to get the various vCard values as object members, e.g.
* a call to $vCard -> N gets the "N" value
*
* @param string Key
*
* @return mixed Value
*/
public function __get($Key)
{
$Key = strtolower($Key);
if (isset($this -> Data[$Key]))
{
if ($Key == 'agent')
{
return $this -> Data[$Key];
}
elseif (in_array($Key, self::$Spec_FileElements))
{
$Value = $this -> Data[$Key];
foreach ($Value as $K => $V)
{
if (stripos($V['Value'], 'uri:') === 0)
{
$Value[$K]['Value'] = substr($V, 4);
$Value[$K]['Encoding'] = 'uri';
}
}
return $Value;
}
if ($this -> Options['Collapse'] && is_array($this -> Data[$Key]) && (count($this -> Data[$Key]) == 1))
{
return $this -> Data[$Key][0];
}
return $this -> Data[$Key];
}
elseif ($Key == 'Mode')
{
return $this -> Mode;
}
return array();
}
/**
* Saves an embedded file
*
* @param string Key
* @param int Index of the file, defaults to 0
* @param string Target path where the file should be saved, including the filename
*
* @return bool Operation status
*/
public function SaveFile($Key, $Index = 0, $TargetPath = '')
{
if (!isset($this -> Data[$Key]))
{
return false;
}
if (!isset($this -> Data[$Key][$Index]))
{
return false;
}
// Returing false if it is an image URL
if (stripos($this -> Data[$Key][$Index]['Value'], 'uri:') === 0)
{
return false;
}
if (is_writable($TargetPath) || (!file_exists($TargetPath) && is_writable(dirname($TargetPath))))
{
$RawContent = $this -> Data[$Key][$Index]['Value'];
if (isset($this -> Data[$Key][$Index]['Encoding']) && $this -> Data[$Key][$Index]['Encoding'] == 'b')
{
$RawContent = base64_decode($RawContent);
}
$Status = file_put_contents($TargetPath, $RawContent);
return (bool)$Status;
}
else
{
throw new Exception('vCard: Cannot save file ('.$Key.'), target path not writable ('.$TargetPath.')');
}
return false;
}
/**
* Magic method for adding data to the vCard
*
* @param string Key
* @param string Method call arguments. First element is value.
*
* @return vCard Current object for method chaining
*/
public function __call($Key, $Arguments)
{
$Key = strtolower($Key);
if (!isset($this -> Data[$Key]))
{
$this -> Data[$Key] = array();
}
$Value = isset($Arguments[0]) ? $Arguments[0] : false;
if (count($Arguments) > 1)
{
$Types = array_values(array_slice($Arguments, 1));
if (isset(self::$Spec_StructuredElements[$Key]) &&
in_array($Arguments[1], self::$Spec_StructuredElements[$Key])
)
{
$LastElementIndex = 0;
if (count($this -> Data[$Key]))
{
$LastElementIndex = count($this -> Data[$Key]) - 1;
}
if (isset($this -> Data[$Key][$LastElementIndex]))
{
if (empty($this -> Data[$Key][$LastElementIndex][$Types[0]]))
{
$this -> Data[$Key][$LastElementIndex][$Types[0]] = $Value;
}
else
{
$LastElementIndex++;
}
}
if (!isset($this -> Data[$Key][$LastElementIndex]))
{
$this -> Data[$Key][$LastElementIndex] = array(
$Types[0] => $Value
);
}
}
elseif (isset(self::$Spec_ElementTypes[$Key]))
{
$this -> Data[$Key][] = array(
'Value' => $Value,
'Type' => $Types
);
}
}
elseif ($Value)
{
$this -> Data[$Key][] = $Value;
}
return $this;
}
/**
* Magic method for getting vCard content out
*
* @return string Raw vCard content
*/
public function __toString()
{
$Text = 'BEGIN:VCARD'.self::endl;
$Text .= 'VERSION:3.0'.self::endl;
foreach ($this -> Data as $Key => $Values)
{
$KeyUC = strtoupper($Key);
$Key = strtolower($Key);
if (in_array($KeyUC, array('PHOTO', 'VERSION')))
{
continue;
}
foreach ($Values as $Index => $Value)
{
$Text .= $KeyUC;
if (is_array($Value) && isset($Value['Type']))
{
$Text .= ';TYPE='.self::PrepareTypeStrForOutput($Value['Type']);
}
$Text .= ':';
if (isset(self::$Spec_StructuredElements[$Key]))
{
$PartArray = array();
foreach (self::$Spec_StructuredElements[$Key] as $Part)
{
$PartArray[] = isset($Value[$Part]) ? $Value[$Part] : '';
}
$Text .= implode(';', $PartArray);
}
elseif (is_array($Value) && isset(self::$Spec_ElementTypes[$Key]))
{
$Text .= $Value['Value'];
}
else
{
$Text .= $Value;
}
$Text .= self::endl;
}
}
$Text .= 'END:VCARD'.self::endl;
return $Text;
}
// !Helper methods
private static function PrepareTypeStrForOutput($Type)
{
return implode(',', array_map('strtoupper', $Type));
}
/**
* Removes the escaping slashes from the text.
*
* @access private
*
* @param string Text to prepare.
*
* @return string Resulting text.
*/
private static function Unescape($Text)
{
return str_replace(array('\:', '\;', '\,', "\n"), array(':', ';', ',', ''), $Text);
}
/**
* Separates the various parts of a structured value according to the spec.
*
* @access private
*
* @param string Raw text string
* @param string Key (e.g., N, ADR, ORG, etc.)
*
* @return array Parts in an associative array.
*/
private static function ParseStructuredValue($Text, $Key)
{
$Text = array_map('trim', explode(';', $Text));
$Result = array();
$Ctr = 0;
foreach (self::$Spec_StructuredElements[$Key] as $Index => $StructurePart)
{
$Result[$StructurePart] = isset($Text[$Index]) ? $Text[$Index] : null;
}
return $Result;
}
/**
* @access private
*/
private static function ParseMultipleTextValue($Text)
{
return explode(',', $Text);
}
/**
* @access private
*/
private static function ParseParameters($Key, array $RawParams = null)
{
if (!$RawParams)
{
return array();
}
// Parameters are split into (key, value) pairs
$Parameters = array();
foreach ($RawParams as $Item)
{
$Parameters[] = explode('=', strtolower($Item));
}
$Type = array();
$Result = array();
// And each parameter is checked whether anything can/should be done because of it
foreach ($Parameters as $Index => $Parameter)
{
// Skipping empty elements
if (!$Parameter)
{
continue;
}
// Handling type parameters without the explicit TYPE parameter name (2.1 valid)
if (count($Parameter) == 1)
{
// Checks if the type value is allowed for the specific element
// The second part of the "if" statement means that email elements can have non-standard types (see the spec)
if (
(isset(self::$Spec_ElementTypes[$Key]) && in_array($Parameter[0], self::$Spec_ElementTypes[$Key])) ||
($Key == 'email' && is_scalar($Parameter[0]))
)
{
$Type[] = $Parameter[0];
}
}
elseif (count($Parameter) > 2)
{
$TempTypeParams = self::ParseParameters($Key, explode(',', $RawParams[$Index]));
if ($TempTypeParams['type'])
{
$Type = array_merge($Type, $TempTypeParams['type']);
}
}
else
{
switch ($Parameter[0])
{
case 'encoding':
if (in_array($Parameter[1], array('quoted-printable', 'b', 'base64')))
{
$Result['encoding'] = $Parameter[1] == 'base64' ? 'b' : $Parameter[1];
}
break;
case 'charset':
$Result['charset'] = $Parameter[1];
break;
case 'type':
$Type = array_merge($Type, explode(',', $Parameter[1]));
break;
case 'value':
if (strtolower($Parameter[1]) == 'url')
{
$Result['encoding'] = 'uri';
}
break;
}
}
}
$Result['type'] = $Type;
return $Result;
}
// !Interface methods
// Countable interface
public function count()
{
switch ($this -> Mode)
{
case self::MODE_ERROR:
return 0;
break;
case self::MODE_SINGLE:
return 1;
break;
case self::MODE_MULTIPLE:
return count($this -> Data);
break;
}
return 0;
}
// Iterator interface
public function rewind()
{
reset($this -> Data);
}
public function current()
{
return current($this -> Data);
}
public function next()
{
return next($this -> Data);
}
public function valid()
{
return ($this -> current() !== false);
}
public function key()
{
return key($this -> Data);
}
}
?>

View File

@ -2,5 +2,29 @@
background: url(../images/import_icon.png) no-repeat !important;
background-repeat: no-repeat;
background-position: center;
background-size: 18px!important;
}
.icon_contactimporter_export {
background: url(../images/download.png) no-repeat;
background-repeat: no-repeat;
background-position: center;
}
.icon_contactimporter_import {
background: url(../images/upload.png) no-repeat;
background-repeat: no-repeat;
background-position: center;
}
.zarafa-ciplg-container {
width: 100%;
height: 50px;
}
.zarafa-ciplg-button .x-btn-small {
width: 80%;
height: 30px;
margin-left: 10%;
margin-right: 10%;
margin-top: 10px;
}

BIN
resources/images/download.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

BIN
resources/images/download.xcf Executable file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 407 B

BIN
resources/images/upload.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

BIN
resources/images/upload.xcf Executable file

Binary file not shown.