Merge branch 'webapp22' into 'master'

Version 2.2.0 RC1

Release Candidate 1 of 2.2.0 version.

See changelog for changes...

See merge request !1
This commit is contained in:
Christoph Haas 2016-11-01 01:01:03 +01:00
commit 187b2a60f2
41 changed files with 2857 additions and 13066 deletions

View File

@ -0,0 +1,8 @@
<?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" />
</component>
</module>

View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

4
.idea/encodings.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/calendarimporter.iml" filepath="$PROJECT_DIR$/.idea/calendarimporter.iml" />
</modules>
</component>
</project>

4
.idea/php.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpProjectSharedConfiguration" php_language_level="5.5.0" />
</project>

View File

@ -0,0 +1,5 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

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>

677
.idea/workspace.xml Normal file
View File

@ -0,0 +1,677 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="e7c9c2bb-66f1-4e37-904c-d33d8c113bde" name="Default" comment="">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/changelog.txt" afterPath="$PROJECT_DIR$/changelog.txt" />
</list>
<ignored path="calendarimporter.iws" />
<ignored path=".idea/workspace.xml" />
<ignored path=".idea/dataSources.local.xml" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="CreatePatchCommitExecutor">
<option name="PATCH_PATH" value="" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
<component name="FavoritesManager">
<favorites_list name="calendarimporter" />
</component>
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="CalSyncEditPanel.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/js/settings/dialogs/CalSyncEditPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1480">
<caret line="101" column="30" selection-start-line="101" selection-start-column="30" selection-end-line="101" selection-end-column="30" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="CalSyncGrid.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/js/settings/ui/CalSyncGrid.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="85" column="59" selection-start-line="85" selection-start-column="59" selection-end-line="85" selection-end-column="59" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="SettingsCalSyncWidget.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/js/settings/SettingsCalSyncWidget.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="729">
<caret line="104" column="16" selection-start-line="104" selection-start-column="16" selection-end-line="104" selection-end-column="142" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="sync.php" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/backend/sync.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="360">
<caret line="24" column="47" selection-start-line="24" selection-start-column="47" selection-end-line="24" selection-end-column="47" />
<folding>
<element signature="e#21#926#0#PHP" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file leaf-file-name="changelog.txt" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/changelog.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" column="19" selection-start-line="5" selection-start-column="19" selection-end-line="5" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="manifest.xml" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/manifest.xml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="functions.php" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/backend/functions.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" column="26" selection-start-line="5" selection-start-column="26" selection-end-line="5" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="README.txt" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/backend/README.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="config.php" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/backend/config.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="CalSyncEditContentPanel.js" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/js/settings/dialogs/CalSyncEditContentPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="63" selection-start-line="0" selection-start-column="63" selection-end-line="0" selection-end-column="63" />
<folding />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/php/plugin.calendarimporter.php" />
<option value="$PROJECT_DIR$/config.php" />
<option value="$PROJECT_DIR$/js/plugin.calendarimporter.js" />
<option value="$PROJECT_DIR$/php/module.calendar.php" />
<option value="$PROJECT_DIR$/js/data/CalendarHelper.js" />
<option value="$PROJECT_DIR$/js/data/Actions.js" />
<option value="$PROJECT_DIR$/js/dialogs/ImportPanel.js" />
<option value="$PROJECT_DIR$/js/settings/dialogs/CalSyncEditContentPanel.js" />
<option value="$PROJECT_DIR$/js/settings/SettingsWidget.js" />
<option value="$PROJECT_DIR$/js/settings/SettingsCalSyncWidget.js" />
<option value="$PROJECT_DIR$/js/settings/ui/CalSyncGrid.js" />
<option value="$PROJECT_DIR$/js/settings/dialogs/CalSyncEditPanel.js" />
<option value="$PROJECT_DIR$/backend/sync.php" />
<option value="$PROJECT_DIR$/backend/functions.php" />
<option value="$PROJECT_DIR$/changelog.txt" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
<component name="ProjectFrameBounds">
<option name="x" value="65" />
<option name="y" value="24" />
<option name="width" value="1792" />
<option name="height" value="999" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scratches" />
<pane id="ProjectPane">
<subPane>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="ui" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="settings" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="settings" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="ui" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="settings" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="dialogs" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="dialogs" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="js" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="data" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
<PATH>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="calendarimporter" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
<PATH_ELEMENT>
<option name="myItemId" value="backend" />
<option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
</PATH_ELEMENT>
</PATH>
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="js-jscs-nodeInterpreter" value="/usr/bin/node" />
</component>
<component name="RunManager">
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
<method />
</configuration>
<configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir="">
<method />
</configuration>
<configuration default="true" type="PHPUnitRunConfigurationType" factoryName="PHPUnit">
<TestRunner />
<method />
</configuration>
<configuration default="true" type="PhpBehatConfigurationType" factoryName="Behat">
<BehatRunner />
<method />
</configuration>
<configuration default="true" type="PhpLocalRunConfigurationType" factoryName="PHP Console">
<method />
</configuration>
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
<method />
</configuration>
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
<command value="run-script" />
<scripts />
<node-interpreter value="project" />
<envs />
<method />
</configuration>
<configuration default="true" type="mocha-javascript-test-runner" factoryName="Mocha">
<node-interpreter>project</node-interpreter>
<node-options />
<working-directory />
<pass-parent-env>true</pass-parent-env>
<envs />
<ui />
<extra-mocha-options />
<test-kind>DIRECTORY</test-kind>
<test-directory />
<recursive>false</recursive>
<method />
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e7c9c2bb-66f1-4e37-904c-d33d8c113bde" name="Default" comment="" />
<created>1477949602474</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1477949602474</updated>
<workItem from="1477949603566" duration="8606000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="8606000" />
</component>
<component name="ToolWindowManager">
<frame x="65" y="24" width="1792" height="999" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32900432" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32900432" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="-1" side_tool="true" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="Vcs.Log.UiProperties">
<option name="RECENTLY_FILTERED_USER_GROUPS">
<collection />
</option>
<option name="RECENTLY_FILTERED_BRANCH_GROUPS">
<collection />
</option>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<option name="time" value="1" />
</breakpoint-manager>
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/Makefile">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/php/composer.json">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/php/upload.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/data/ResponseHandler.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-456">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="n#!!doc" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/php/module.calendar.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1057">
<caret line="467" column="64" selection-start-line="467" selection-start-column="64" selection-end-line="467" selection-end-column="64" />
<folding>
<element signature="n#__construct#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#execute#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#randomstring#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#getDurationStringFromMintues#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#exportCalendar#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#importCalendar#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#getAttachmentPath#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#loadCalendar#0;n#CalendarModule#0;n#!!top" expanded="false" />
<element signature="n#parseCalendarToArray#0;n#CalendarModule#0;n#!!top" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/config.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="60">
<caret line="4" column="60" selection-start-line="4" selection-start-column="12" selection-end-line="4" selection-end-column="60" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/php/plugin.calendarimporter.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="569">
<caret line="82" column="40" selection-start-line="82" selection-start-column="29" selection-end-line="82" selection-end-column="40" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/dialogs/ImportContentPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-171">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding>
<element signature="n#!!doc" expanded="false" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/data/CalendarHelper.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/dialogs/ImportPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="332">
<caret line="245" column="12" selection-start-line="245" selection-start-column="12" selection-end-line="245" selection-end-column="226" />
<folding>
<element signature="n#!!doc" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/data/Actions.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="144">
<caret line="133" column="67" selection-start-line="133" selection-start-column="22" selection-end-line="133" selection-end-column="67" />
<folding>
<element signature="n#!!doc" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/settings/SettingsWidget.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="83" column="82" selection-start-line="83" selection-start-column="82" selection-end-line="83" selection-end-column="82" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/plugin.calendarimporter.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-51">
<caret line="262" column="12" selection-start-line="262" selection-start-column="12" selection-end-line="262" selection-end-column="12" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/settings/ui/CalSyncGrid.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="504">
<caret line="85" column="59" selection-start-line="85" selection-start-column="59" selection-end-line="85" selection-end-column="59" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/settings/SettingsCalSyncWidget.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="729">
<caret line="104" column="16" selection-start-line="104" selection-start-column="16" selection-end-line="104" selection-end-column="142" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/settings/dialogs/CalSyncEditContentPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="63" selection-start-line="0" selection-start-column="63" selection-end-line="0" selection-end-column="63" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/js/settings/dialogs/CalSyncEditPanel.js">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1480">
<caret line="101" column="30" selection-start-line="101" selection-start-column="30" selection-end-line="101" selection-end-column="30" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/backend/functions.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" column="26" selection-start-line="5" selection-start-column="26" selection-end-line="5" selection-end-column="26" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/backend/config.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/backend/README.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/backend/sync.php">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="360">
<caret line="24" column="47" selection-start-line="24" selection-start-column="47" selection-end-line="24" selection-end-column="47" />
<folding>
<element signature="e#21#926#0#PHP" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/manifest.xml">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/changelog.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="75">
<caret line="5" column="19" selection-start-line="5" selection-start-column="19" selection-end-line="5" selection-end-column="19" />
<folding />
</state>
</provider>
</entry>
</component>
</project>

2
Makefile Normal file
View File

@ -0,0 +1,2 @@
default:
ant deploy; cp -r /home/osboxes/Documents/kopano-webapp-3.2.0.285/deploy/plugins/calendarimporter /usr/share/kopano-webapp/plugins/

View File

@ -3,7 +3,7 @@
* functions.php, zarafa calender to ics im/exporter backend
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2014 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

View File

@ -4,7 +4,7 @@
* sync.php, zarafa calender to ics im/exporter backend
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2014 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
@ -129,7 +129,7 @@ foreach($userList as $userName => $userData) {
if($icsData != NULL) {
file_put_contents($tmpFilename, $icsData);
echo "Got valid data for " . $syncItem["icsurl"] . " stored in " . $tmpFilename . "\n";
$result = upload_ics_to_caldav($tmpFilename, $CALDAVURL, $userName, $syncItem["calendar"], $ADMINUSERNAME, $ADMINPASSWORD);
$result = upload_ics_to_caldav($tmpFilename, $CALDAVURL, $userName, $syncItem["calendarname"], $ADMINUSERNAME, $ADMINPASSWORD);
if(intval($result) == 200) {
echo "Import completed: $result\n";
$result = update_last_sync_date($userStore, $syncItemName);

156
build.xml
View File

@ -1,18 +1,15 @@
<project default="all">
<!--############# CONFIGURE ALL PROPERTIES FOR THE REPLACER HERE ################-->
<property name="plugin_version" value="2.1.0"/>
<!-- 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,27 +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/external/Ext.util.base64.js" />
<fileset file="js/data/timezones.js" />
<fileset file="js/plugin.calendarimporter.js" />
<fileset file="js/data/ResponseHandler.js" />
<fileset file="js/dialogs/ImportContentPanel.js" />
<fileset file="js/dialogs/ImportPanel.js" />
<fileset file="js/settings/SettingsWidget.js" />
<fileset file="js/settings/SettingsCalSyncWidget.js" />
<fileset file="js/settings/ui/CalSyncPanel.js" />
<fileset file="js/settings/ui/CalSyncGrid.js" />
<fileset file="js/settings/dialogs/CalSyncEditContentPanel.js" />
<fileset file="js/settings/dialogs/CalSyncEditPanel.js" />
</concat>
</zConcat>
</then>
</if>
@ -118,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>
@ -164,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>
@ -193,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,3 +1,10 @@
calendarimporter 2.2.0:
- support for Kopano Webapp 3.1.1
- Code rework
- Calendar export improved
- Calendar import improved
- GUI improvements
calendarimporter 2.1.0:
- ics sync is now implemented

View File

@ -1,15 +1,13 @@
<?php
/** Disable the import plugin for all clients */
define('PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE', false);
/** Disable the export feature for all clients */
define('PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE_EXPORT', false);
define('PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE', true);
/** Disable the sync feature for all clients */
define('PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE_SYNC', true); // not yet implemented
/** The default calendar to import to*/
define('PLUGIN_CALENDARIMPORTER_DEFAULT', "calendar");
define('PLUGIN_CALENDARIMPORTER_DEFAULT', "Kalender");
define('PLUGIN_CALENDARIMPORTER_DEFAULT_TIMEZONE', "Europe/Vienna");
/** Tempory path for uploaded files... */
define('PLUGIN_CALENDARIMPORTER_TMP_UPLOAD', "/var/lib/zarafa-webapp/tmp/");
define('PLUGIN_CALENDARIMPORTER_TMP_UPLOAD', "/var/lib/kopano-webapp/tmp/");
?>

View File

@ -1,87 +0,0 @@
/**
* A small tool to create our timezone mappings list =)
*/
import java.util.TimeZone;
public class Main {
static String map[] = new String[51]; // one field = 30 minutes step
private static void initMap() {
map[0] = "Pacific/Midway";
map[1] = "";
map[2] = "Pacific/Fakaofo";
map[3] = "Pacific/Marquesas";
map[4] = "America/Anchorage";
map[5] = "";
map[6] = "America/Dawson";
map[7] = "";
map[8] = "America/Dawson_Creek";
map[9] = "";
map[10] = "America/Chicago";
map[11] = "America/Caracas";
map[12] = "America/Detroit";
map[13] = "America/Caracas";
map[14] = "America/Santiago";
map[15] = "America/St_Johns";
map[16] = "America/Sao_Paulo";
map[17] = "";
map[18] = "America/Noronha";
map[19] = "";
map[20] = "Atlantic/Cape_Verde";
map[21] = "";
map[22] = "Africa/Abidjan";
map[23] = "";
map[24] = "Europe/Vienna";
map[25] = "";
map[26] = "Asia/Jerusalem";
map[27] = "";
map[28] = "Africa/Addis_Ababa";
map[29] = "Asia/Tehran";
map[30] = "Asia/Dubai";
map[31] = "Asia/Kabul";
map[32] = "Antarctica/Mawson";
map[33] = "Asia/Colombo";
map[34] = "Antarctica/Vostok";
map[35] = "Asia/Rangoon";
map[36] = "Antarctica/Davis";
map[37] = "";
map[38] = "Antarctica/Casey";
map[39] = "";
map[40] = "Asia/Dili";
map[41] = "Australia/Darwin";
map[42] = "Australia/Currie";
map[43] = "Australia/Lord_Howe";
map[44] = "Antarctica/Macquarie";
map[45] = "Pacific/Norfolk";
map[46] = "Antarctica/McMurdo";
map[47] = "";
map[48] = "Pacific/Enderbury";
map[49] = "";
map[50] = "Pacific/Kiritimati";
}
/**
* @param args
*/
public static void main(String[] args) {
initMap();
int i = 0;
for(int time = -660; time < 900; time += 30) {
int hours = time / 60;
int minutes = Math.abs(time) % 60;
String[] avaiId = TimeZone.getAvailableIDs(time*60*1000);
if(avaiId.length > 0) {
System.out.printf("\t\t/*%+d:%02d*/\n", hours, minutes);
for (String string : avaiId) {
System.out.println("\t\t'" + string + "' : '" + map[i] + "',");
}
}
i++;
}
}
}

View File

@ -2,7 +2,7 @@
* ABOUT.js zarafa calender to ics 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.calendarimporter');
* The copyright string holding the copyright notice for the Zarafa calendarimporter Plugin.
*/
Zarafa.plugins.calendarimporter.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 "

178
js/data/Actions.js Normal file
View File

@ -0,0 +1,178 @@
/**
* Actions.js zarafa calender to ics 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
*
*/
/**
* ResponseHandler
*
* This class handles all responses from the php backend
*/
Ext.namespace('Zarafa.plugins.calendarimporter.data');
/**
* @class Zarafa.plugins.calendarimporter.data.Actions
* Common actions which can be used within {@link Ext.Button buttons}
* or other {@link Ext.Component components} with action handlers.
* @singleton
*/
Zarafa.plugins.calendarimporter.data.Actions = {
/**
* Callback for the export request.
* @param {Object} response
*/
downloadICS: 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_ics";
link = Ext.urlAppend(link, "token=" + encodeURIComponent(response.download_token));
link = Ext.urlAppend(link, "filename=" + encodeURIComponent(response.filename));
downloadFrame.dom.contentWindow.location = link;
}
},
/**
* Get all calendar folders.
* @param {boolean} asDropdownStore If true, a simple array store will be returned.
* @returns {*}
*/
getAllCalendarFolders: function (asDropdownStore) {
asDropdownStore = Ext.isEmpty(asDropdownStore) ? false : asDropdownStore;
var allFolders = [];
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.Appointment") {
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.Appointment") {
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(Zarafa.plugins.calendarimporter.data.Actions.dynamicSort(1));
} else {
return allFolders;
}
},
/**
* Return a calendar folder element by name.
* @param {string} name
* @returns {*}
*/
getCalendarFolderByName: function (name) {
var folders = Zarafa.plugins.calendarimporter.data.Actions.getAllCalendarFolders(false);
for (var i = 0; i < folders.length; i++) {
if (folders[i].display_name == name) {
return folders[i];
}
}
return container.getHierarchyStore().getDefaultFolder('calendar');
},
/**
* Return a calendar folder element by entryid.
* @param {string} entryid
* @returns {*}
*/
getCalendarFolderByEntryid: function (entryid) {
var folders = Zarafa.plugins.calendarimporter.data.Actions.getAllCalendarFolders(false);
for (var i = 0; i < folders.length; i++) {
if (folders[i].entryid == entryid) {
return folders[i];
}
}
return container.getHierarchyStore().getDefaultFolder('calendar');
},
/**
* 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;
}
}
};

View File

@ -2,7 +2,7 @@
* ResponseHandler.js zarafa calender to ics 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
@ -52,7 +52,7 @@ Zarafa.plugins.calendarimporter.data.ResponseHandler = Ext.extend(Zarafa.core.da
* Call the successCallback callback function.
* @param {Object} response Object contained the response data.
*/
doList : function(response) {
doLoad: function (response) {
this.successCallback(response);
},
@ -68,7 +68,7 @@ Zarafa.plugins.calendarimporter.data.ResponseHandler = Ext.extend(Zarafa.core.da
* Call the successCallback callback function.
* @param {Object} response Object contained the response data.
*/
doAttachmentpath : function(response) {
doImportattachment: function (response) {
this.successCallback(response);
},

View File

@ -2,7 +2,7 @@
* timezones.js zarafa calender to ics 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

View File

@ -2,7 +2,7 @@
* ImportContentPanel.js zarafa calender to ics 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
@ -42,13 +42,9 @@ Zarafa.plugins.calendarimporter.dialogs.ImportContentPanel = Ext.extend(Zarafa.c
*/
constructor : function(config) {
config = config || {};
var title = _('Import Calendar File');
if(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/enable_export")){
title = _('Import/Export Calendar File');
}
Ext.applyIf(config, {
layout : 'fit',
title : title,
title : _('Import Calendar File'),
closeOnSave : true,
width : 800,
height : 700,
@ -56,7 +52,8 @@ Zarafa.plugins.calendarimporter.dialogs.ImportContentPanel = Ext.extend(Zarafa.c
items : [
{
xtype : 'calendarimporter.importpanel',
filename : config.filename
filename : config.filename,
folder : config.folder
}
]
});

View File

@ -2,7 +2,7 @@
* ImportPanel.js zarafa calender to ics 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.calendarimporter.dialogs");
/**
* @class Zarafa.plugins.calendarimporter.dialogs.ImportPanel
* @extends Ext.form.FormPanel
* @extends Ext.Panel
*/
Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
@ -46,7 +46,7 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
loadMask: null,
/* export event buffer */
exportResponse: new Array(),
exportResponse: [],
/* how many requests are still running? */
runningRequests: null,
@ -54,13 +54,8 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
/* The store for the selection grid */
store: null,
/**
* The internal 'iframe' which is hidden from the user, which is used for downloading
* attachments. See {@link #doOpen}.
* @property
* @type Ext.Element
*/
downloadFrame : undefined,
/* selected folder */
folder : null,
/**
* @constructor
@ -71,24 +66,30 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
var self = this;
this.timezone = container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_timezone");
if(typeof config.filename !== "undefined") {
if (!Ext.isEmpty(config.filename)) {
this.icsfile = config.filename;
}
if (!Ext.isEmpty(config.folder)) {
this.folder = config.folder;
}
// create the data store
this.store = new Ext.data.ArrayStore({
fields: [
{name: 'title'},
{name: 'start'},
{name: 'end'},
{name: 'subject'},
{name: 'startdate'},
{name: 'enddate'},
{name: 'location'},
{name: 'description'},
{name: 'body'},
{name: 'priority'},
{name: 'label'},
{name: 'busy'},
{name: 'privatestate'},
{name: 'class'},
{name: 'organizer'},
{name: 'trigger'}
{name: 'alarms'},
{name: 'timezone'},
{name: 'record'}
]
});
@ -114,29 +115,19 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
this.createGrid()
],
buttons: [
this.createExportAllButton(),
this.createSubmitAllButton(),
this.createSubmitButton(),
this.createCancelButton()
],
listeners: {
afterrender: function (cmp) {
Ext.getCmp('importbutton').disable();
this.loadMask = new Ext.LoadMask(Ext.getCmp("importpanel").getEl(), {msg:'Loading...'});
this.loadMask = new Ext.LoadMask(this.getEl(), {msg:'Loading...'});
if(this.icsfile != null) { // if we have got the filename from an attachment
this.parseCalendar(this.icsfile, this.timezone, this.ignoredst);
}
},
close: function (cmp) {
Ext.getCmp('importbutton').enable();
},
hide: function (cmp) {
Ext.getCmp('importbutton').enable();
},
destroy: function (cmp) {
Ext.getCmp('importbutton').enable();
}
scope: this
}
});
@ -174,22 +165,25 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
reloadGridStore: function(eventdata) {
var parsedData = [];
// this is done to get rid of the local browser timezone....
// because all timezone specific stuff is done via php
var local_tz_offset = new Date().getTimezoneOffset() * 60000; // getTimezoneOffset returns minutes... we need milliseconds
if(eventdata !== null) {
parsedData = new Array(eventdata.events.length);
var i = 0;
for(i = 0; i < eventdata.events.length; i++) {
var trigger = null;
if(eventdata.events[i]["VALARM"]) {
trigger = eventdata.events[i]["VALARM"]["TRIGGER"];
trigger = new Date(parseInt(trigger) + local_tz_offset);
}
parsedData[i] = new Array(eventdata.events[i]["SUMMARY"], new Date(parseInt(eventdata.events[i]["DTSTART"]) + local_tz_offset), new Date(parseInt(eventdata.events[i]["DTEND"]) + local_tz_offset), eventdata.events[i]["LOCATION"], eventdata.events[i]["DESCRIPTION"],eventdata.events[i]["PRIORITY"],eventdata.events[i]["X-ZARAFA-LABEL"],eventdata.events[i]["X-MICROSOFT-CDO-BUSYSTATUS"],eventdata.events[i]["CLASS"],eventdata.events[i]["ORGANIZER"],trigger);
parsedData[i] = [
eventdata.events[i]["subject"],
new Date(parseInt(eventdata.events[i]["startdate"]) * 1000),
new Date(parseInt(eventdata.events[i]["duedate"]) * 1000),
eventdata.events[i]["location"],
eventdata.events[i]["body"],
eventdata.events[i]["priority"],
eventdata.events[i]["label"],
eventdata.events[i]["busystatus"],
eventdata.events[i]["private"],
eventdata.events[i]["organizer"],
eventdata.events[i]["alarms"],
eventdata.events[i]["timezone"],
eventdata.events[i]
];
}
} else {
return null;
@ -223,17 +217,18 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
sortable: true
},
columns: [
{id: 'Summary', header: 'Title', width: 200, sortable: true, dataIndex: 'title'},
{header: 'Start', width: 200, sortable: true, dataIndex: 'start', renderer : Zarafa.common.ui.grid.Renderers.datetime},
{header: 'End', width: 200, sortable: true, dataIndex: 'end', renderer : Zarafa.common.ui.grid.Renderers.datetime},
{id: 'Summary', header: 'Title', width: 200, sortable: true, dataIndex: 'subject'},
{header: 'Start', width: 200, sortable: true, dataIndex: 'startdate', renderer : Zarafa.common.ui.grid.Renderers.datetime},
{header: 'End', width: 200, sortable: true, dataIndex: 'enddate', renderer : Zarafa.common.ui.grid.Renderers.datetime},
{header: 'Location', width: 150, sortable: true, dataIndex: 'location'},
{header: 'Description', sortable: true, dataIndex: 'description'},
{header: 'Description', sortable: true, dataIndex: 'body'},
{header: "Priority", dataIndex: 'priority', hidden: true},
{header: "Label", dataIndex: 'label', hidden: true},
{header: "Busystatus", dataIndex: 'busy', hidden: true},
{header: "Privacystatus", dataIndex: 'privatestate', hidden: true},
{header: "Privacystatus", dataIndex: 'class', hidden: true},
{header: "Organizer", dataIndex: 'organizer', hidden: true},
{header: "Alarm", dataIndex: 'trigger', hidden: true, renderer : Zarafa.common.ui.grid.Renderers.datetime}
{header: "Alarm", dataIndex: 'alarms', hidden: true, renderer : Zarafa.common.ui.grid.Renderers.datetime},
{header: "Timezone", dataIndex: 'timezone', hidden: true}
]
}),
sm: new Ext.grid.RowSelectionModel({multiSelect:true})
@ -241,52 +236,23 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
},
createSelectBox: function() {
var defaultFolder = container.getHierarchyStore().getDefaultFolder('calendar'); // @type: Zarafa.hierarchy.data.MAPIFolderRecord
var subFolders = defaultFolder.getChildren();
var myStore = [];
/* add all local calendar 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 calendar 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.Appointment")){
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 = Zarafa.plugins.calendarimporter.data.Actions.getAllCalendarFolders(true);
return {
xtype: "selectbox",
ref: 'calendarselector',
id: 'calendarselector',
editable: false,
name: "choosen_calendar",
value: container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_calendar"),
value: Ext.isEmpty(this.folder) ? Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByName(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_calendar")).entryid : this.folder,
width: 100,
fieldLabel: "Select a calender",
fieldLabel: "Select folder",
store: myStore,
mode: 'local',
labelSeperator: ":",
border: false,
anchor: "100%",
scope: this,
hidden : Ext.isEmpty(this.folder) ? false : true,
allowBlank: false
}
},
@ -295,12 +261,11 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
return {
xtype: "selectbox",
ref: 'timezoneselector',
id: 'timezoneselector',
editable: false,
name: "choosen_timezone",
value: Zarafa.plugins.calendarimporter.data.Timezones.unMap(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_timezone")),
width: 100,
fieldLabel: "Select a timezone (optional)",
fieldLabel: "Timezone",
store: Zarafa.plugins.calendarimporter.data.Timezones.store,
labelSeperator: ":",
mode: 'local',
@ -309,7 +274,7 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
scope: this,
allowBlank: true,
listeners: {
'select': this.onTimezoneSelected,
select: this.onTimezoneSelected,
scope: this
}
}
@ -319,10 +284,9 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
return {
xtype: "checkbox",
ref: 'dstcheck',
id: 'dstcheck',
name: "dst_check",
width: 100,
fieldLabel: "Ignore DST (optional)",
fieldLabel: "Ignore DST",
boxLabel: 'This will ignore "Daylight saving time" offsets.',
labelSeperator: ":",
border: false,
@ -330,7 +294,7 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
scope: this,
allowBlank: true,
listeners: {
'check': this.onDstChecked,
check: this.onDstChecked,
scope: this
}
}
@ -346,10 +310,11 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
emptyText: 'Select an .ics calendar',
border: false,
anchor: "100%",
height : "30",
scope: this,
allowBlank: false,
listeners: {
'fileselected': this.onFileSelected,
fileselected: this.onFileSelected,
scope: this
}
}
@ -358,48 +323,28 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
createSubmitButton: function() {
return {
xtype: "button",
ref: "submitButton",
id: "submitButton",
ref: "../submitButton",
disabled: true,
width: 100,
border: false,
text: _("Import"),
anchor: "100%",
handler: this.importCheckedEvents,
scope: this,
allowBlank: false
scope: this
}
},
createSubmitAllButton: function() {
return {
xtype: "button",
ref: "submitAllButton",
id: "submitAllButton",
ref: "../submitAllButton",
disabled: true,
width: 100,
border: false,
text: _("Import All"),
anchor: "100%",
handler: this.importAllEvents,
scope: this,
allowBlank: false
}
},
createExportAllButton: function() {
return {
xtype: "button",
ref: "exportAllButton",
id: "exportAllButton",
hidden: !container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/enable_export"),
width: 100,
border: false,
text: _("Export All"),
anchor: "100%",
handler: this.exportAllEvents,
scope: this,
allowBlank: false
scope: this
}
},
@ -411,8 +356,7 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
text: _("Cancel"),
anchor: "100%",
handler: this.close,
scope: this,
allowBlank: false
scope: this
}
},
@ -432,7 +376,7 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
/**
* This is called when the dst checkbox has been selected
* @param {Ext.form.CheckBox} combo
* @param {Ext.form.CheckBox} checkbox
* @param {boolean} checked
*/
onDstChecked : function(checkbox, checked) {
@ -456,8 +400,8 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
waitMsg: 'Uploading and parsing calendar...',
url: 'plugins/calendarimporter/php/upload.php',
failure: function(file, action) {
Ext.getCmp('submitButton').disable();
Ext.getCmp('submitAllButton').disable();
this.submitButton.disable();
this.submitAllButton.disable();
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _(action.result.error),
@ -478,14 +422,15 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
parseCalendar: function (icsPath, timezone, ignoredst) {
this.loadMask.show();
// call export function here!
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: this.handleParsingResult.createDelegate(this)
successCallback: this.handleParsingResult,
scope: this
});
container.getRequest().singleRequest(
'calendarmodule',
'import',
'load',
{
ics_filepath: icsPath,
timezone: timezone,
@ -496,20 +441,22 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
},
handleParsingResult: function(response) {
this.loadMask.hide();
var self = this.scope;
self.loadMask.hide();
if(response["status"] == true) {
Ext.getCmp('submitButton').enable();
Ext.getCmp('submitAllButton').enable();
self.submitButton.enable();
self.submitAllButton.enable();
if(typeof response.parsed.calendar["X-WR-TIMEZONE"] !== "undefined") {;
this.timezone = response.parsed.calendar["X-WR-TIMEZONE"];
this.timezoneselector.setValue(Zarafa.plugins.calendarimporter.data.Timezones.unMap(this.timezone));
if(typeof response.parsed.calendar["X-WR-TIMEZONE"] !== "undefined") {
self.timezone = response.parsed.calendar["X-WR-TIMEZONE"];
self.timezoneselector.setValue(Zarafa.plugins.calendarimporter.data.Timezones.unMap(this.timezone));
}
this.reloadGridStore(response.parsed);
self.reloadGridStore(response.parsed);
} else {
Ext.getCmp('submitButton').disable();
Ext.getCmp('submitAllButton').disable();
self.submitButton.disable();
self.submitAllButton.disable();
Zarafa.common.dialogs.MessageBox.show({
title : _('Parser Error'),
msg : _(response["message"]),
@ -524,54 +471,6 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
this.dialog.close()
},
convertToAppointmentRecord: function (calendarFolder,entry) {
var newRecord = Zarafa.core.data.RecordFactory.createRecordObjectByMessageClass('IPM.Appointment', {
startdate: new Date(entry.start),
duedate: (entry.end != null) ?
new Date(entry.end) :
new Date(entry.start).add(Date.HOUR, 1),
location: entry.location,
subject: entry.title,
body: entry.description,
commonstart: new Date(entry.start),
commonend: (entry.end != null) ?
new Date(entry.end) :
new Date(entry.start).add(Date.HOUR, 1),
timezone: this.timezone,
parent_entryid: calendarFolder.get('entryid'),
store_entryid: calendarFolder.get('store_entryid')
});
var busystate = new Array("FREE", "TENTATIVE", "BUSY", "OOF");
var zlabel = new Array("NONE", "IMPORTANT", "WORK", "PERSONAL", "HOLIDAY", "REQUIRED", "TRAVEL REQUIRED", "PREPARATION REQUIERED", "BIRTHDAY", "SPECIAL DATE", "PHONE INTERVIEW");
/* optional fields */
if(entry.priority !== "") {
newRecord.data.importance = entry.priority;
}
if(entry.label !== "") {
newRecord.data.label = zlabel.indexOf(entry.label);
}
if(entry.busy !== "") {
newRecord.data.busystatus = busystate.indexOf(entry.busy);
}
if(entry.privatestate !== "") {
newRecord.data["private"] = entry.privatestate == "PUBLIC" ? false : true;
}
if(entry.organizer !== "") {
newRecord.data.sent_representing_email_address = entry.organizer;
}
if(entry.trigger != null) {
newRecord.data.reminder = true;
newRecord.data.reminder_minutes = new Date((entry.start - entry.trigger)/60);
newRecord.data.reminder_time = new Date(entry.trigger);
} else {
newRecord.data.reminder = false;
}
return newRecord;
},
importCheckedEvents: function () {
var newRecords = this.eventgrid.selModel.getSelections();
this.importEvents(newRecords);
@ -584,295 +483,15 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
this.importEvents(newRecords);
},
exportAllEvents: function () {
//receive existing calendar store
var calValue = this.calendarselector.value;
if(calValue == undefined) { // no calendar choosen
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('You have to choose a calendar!'),
icon : Zarafa.common.dialogs.MessageBox.ERROR,
buttons : Zarafa.common.dialogs.MessageBox.OK
});
} else {
var calexist = true;
var calendarFolder = container.getHierarchyStore().getDefaultFolder('calendar');
var pubStore = container.getHierarchyStore().getPublicStore();
var pubSubFolders = [];
var pubFolder;
if(typeof pubStore !== "undefined") {
try {
pubFolder = pubStore.getDefaultFolder("publicfolders");
pubSubFolders = pubFolder.getChildren();
} catch (e) {
console.log("Error opening the shared folder...");
console.log(e);
}
}
if(calValue != "calendar") {
var subFolders = calendarFolder.getChildren();
var i = 0;
/* add public folders if any exist */
for(i = 0; i < pubSubFolders.length; i++) {
if(pubSubFolders[i].isContainerClass("IPF.Appointment")){
subFolders.push(pubSubFolders[i]);
}
}
for(i=0;i<subFolders.length;i++) {
// loo up right folder
// TODO: improve!!
if(subFolders[i].getDisplayName() == calValue) {
calendarFolder = subFolders[i];
break;
}
}
if(calendarFolder.isDefaultFolder()) {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('Selected calendar does not exist!'),
icon : Zarafa.common.dialogs.MessageBox.ERROR,
buttons : Zarafa.common.dialogs.MessageBox.OK
});
calexist = false;
}
}
if(calexist) {
Zarafa.common.dialogs.MessageBox.show({
title: 'Please wait',
msg: 'Generating ical file...',
progressText: 'Exporting...',
width:300,
progress:true,
closable:false
});
// progress bar... ;)
var updateProgressBar = function(v){
return function(){
if(v == 100){
if(Zarafa.common.dialogs.MessageBox.isVisible()) {
updateTimer();
}
}else{
Zarafa.common.dialogs.MessageBox.updateProgress(v/100, 'Exporting...');
}
};
};
var updateTimer = function() {
for(var i = 1; i < 101; i++){
setTimeout(updateProgressBar(i), 20*i);
}
};
updateTimer();
// call export function here!
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: this.exportPagedEvents.createDelegate(this)
});
container.getRequest().singleRequest(
'appointmentlistmodule',
'list',
{
groupDir: "ASC",
restriction: {
//start: 0,
//limit: 500, // limit to 500 events.... not working because of hardcoded limit in listmodule
//startdate: 0,
//duedate: 2145826800 // 2037... nearly highest unix timestamp
},
sort: [{
"field": "startdate",
"direction": "DESC"
}],
store_entryid : calendarFolder.data.store_entryid,
entryid : calendarFolder.data.entryid
},
responseHandler
);
}
}
},
/**
* Calculate needed Requests for all events
* Needed because the listmodule has hardcoded pageing setting -.-
* @param {Object} response
*/
exportPagedEvents:function(response) {
if(response.page.start = 0 && response.item.length <= 0) {
container.getNotifier().notify('info', 'Export Failed', 'There were no items to export!');
Zarafa.common.dialogs.MessageBox.hide();
} else {
this.exportResponse = response.item;
}
if(this.totalExportCount == null) {
this.totalExportCount = response.page.totalrowcount;
}
var requests = Math.ceil(response.page.totalrowcount / response.page.rowcount);
this.runningRequests = requests;
var i = 0;
//receive existing calendar store
var calValue = this.calendarselector.value;
var calendarFolder = container.getHierarchyStore().getDefaultFolder('calendar');
var pubStore = container.getHierarchyStore().getPublicStore();
var pubSubFolders = [];
var pubFolder;
if(typeof pubStore !== "undefined") {
try {
pubFolder = pubStore.getDefaultFolder("publicfolders");
pubSubFolders = pubFolder.getChildren();
} catch (e) {
console.log("Error opening the shared folder...");
console.log(e);
}
}
if(calValue != "calendar") {
var subFolders = calendarFolder.getChildren();
i = 0;
/* add public folders if any exist */
for(i = 0; i < pubSubFolders.length; i++) {
if(pubSubFolders[i].isContainerClass("IPF.Appointment")){
subFolders.push(pubSubFolders[i]);
}
}
for(i=0;i<subFolders.length;i++) {
// loo up right folder
// TODO: improve!!
if(subFolders[i].getDisplayName() == calValue) {
calendarFolder = subFolders[i];
break;
}
}
if(calendarFolder.isDefaultFolder()) {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('Selected calendar does not exist!'),
icon : Zarafa.common.dialogs.MessageBox.ERROR,
buttons : Zarafa.common.dialogs.MessageBox.OK
});
}
}
for(i = 0; i < requests; i++) {
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: this.storeResult.createDelegate(this)
});
this.requestNext(calendarFolder, responseHandler, response.page.rowcount *(i+1), response.page.rowcount);
}
},
/**
* Responsehandler for the single requests... will merge all events into one array
* @param {Object} response
*/
storeResult: function(response) {
var tmp = this.exportResponse;
this.exportResponse = tmp.concat(response.item);
if(this.runningRequests <= 1) {
// final request =)
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: this.downLoadICS.createDelegate(this)
});
container.getRequest().singleRequest(
'calendarmodule',
'export',
{
data: this.exportResponse,
calendar: this.calendarselector.value
},
responseHandler
);
container.getNotifier().notify('info', 'Exported', 'Found ' + this.exportResponse.length + ' entries to export. Preparing download...');
this.dialog.close();
}
this.runningRequests--;
},
/**
* build a new event request for the listmodule
* @param {Object} calendarFolder the calendarFolder to export
* @param {Object} responseHandler (should be storeResult)
* @param {int} start
* @param {int} limit
*/
requestNext: function(calendarFolder, responseHandler, start, limit) {
container.getRequest().singleRequest(
'appointmentlistmodule',
'list',
{
groupDir: "ASC",
restriction: {
start: start,
limit: limit
//startdate: 0,
//duedate: 2145826800 // 2037... nearly highest unix timestamp
},
sort: [{
"field": "startdate",
"direction": "DESC"
}],
store_entryid : calendarFolder.data.store_entryid,
entryid : calendarFolder.data.entryid
},
responseHandler
);
},
/**
* download ics file =)
* @param {Object} response
* @private
*/
downLoadICS : function(response) {
Zarafa.common.dialogs.MessageBox.hide();
if(response.status === true) {
if(!this.downloadFrame){
this.downloadFrame = Ext.getBody().createChild({
tag: 'iframe',
cls: 'x-hidden'
});
}
var url = 'plugins/calendarimporter/php/download.php?fileid='+response.fileid+'&basedir='+response.basedir+'&secid='+response.secid+'&realname='+response.realname;
this.downloadFrame.dom.contentWindow.location = url;
} else {
container.getNotifier().notify('error', 'Export Failed', 'ICal File creation failed!');
}
},
/**
* This function stores all given events to the appointmentstore
* @param events
*/
importEvents: function (events) {
//receive existing calendar store
var calValue = this.calendarselector.value;
var calValue = this.calendarselector.getValue();
if(calValue == undefined) { // no calendar choosen
if(Ext.isEmpty(calValue)) { // no calendar choosen
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('You have to choose a calendar!'),
@ -880,7 +499,6 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
buttons : Zarafa.common.dialogs.MessageBox.OK
});
} else {
var calexist = true;
if(this.eventgrid.selModel.getCount() < 1) {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
@ -889,53 +507,54 @@ Zarafa.plugins.calendarimporter.dialogs.ImportPanel = Ext.extend(Ext.Panel, {
buttons : Zarafa.common.dialogs.MessageBox.OK
});
} else {
var calendarStore = new Zarafa.calendar.AppointmentStore();
var calendarFolder = container.getHierarchyStore().getDefaultFolder('calendar');
var pubStore = container.getHierarchyStore().getPublicStore();
var pubFolder = pubStore.getDefaultFolder("publicfolders");
var pubSubFolders = pubFolder.getChildren();
var calendarFolder = Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(calValue);
if(calValue != "calendar") {
var subFolders = calendarFolder.getChildren();
var i = 0;
for(i = 0; i < pubSubFolders.length; i++) {
if(pubSubFolders[i].isContainerClass("IPF.Appointment")){
subFolders.push(pubSubFolders[i]);
}
}
for(i=0;i<subFolders.length;i++) {
// look up right folder
// TODO: improve!!
if(subFolders[i].getDisplayName() == calValue) {
calendarFolder = subFolders[i];
break;
}
}
this.loadMask.show();
var uids = [];
if(calendarFolder.isDefaultFolder()) {
//receive Records from grid rows
Ext.each(events, function(newRecord) {
uids.push(newRecord.data.record.internal_fields.event_uid);
}, this);
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: this.importEventsDone,
scope: this
});
container.getRequest().singleRequest(
'calendarmodule',
'import',
{
storeid : calendarFolder.store_entryid,
folderid : calendarFolder.entryid,
uids : uids,
ics_filepath: this.icsfile
},
responseHandler
);
}
}
},
/**
* Callback for the import request.
* @param {Object} response
*/
importEventsDone: function (response) {
var self = this.scope;
self.loadMask.hide();
self.dialog.close();
if (response.status == true) {
container.getNotifier().notify('info', 'Imported', 'Imported ' + response.count + ' events. Please reload your calendar!');
} else {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
msg : _('Selected calendar does not exist!'),
msg : _('Import failed: ') + response.message,
icon : Zarafa.common.dialogs.MessageBox.ERROR,
buttons: Zarafa.common.dialogs.MessageBox.OK
});
calexist = false;
}
}
if(calexist) {
this.loadMask.show();
//receive Records from grid rows
Ext.each(events, function(newRecord) {
var record = this.convertToAppointmentRecord(calendarFolder,newRecord.data);
calendarStore.add(record);
}, this);
calendarStore.save();
this.loadMask.hide();
this.dialog.close();
container.getNotifier().notify('info', 'Imported', 'Imported ' + events.length + ' events. Please reload your calendar!');
}
}
}
}
});

View File

@ -2,7 +2,7 @@
* plugin.calendarimporter.js zarafa calender to ics 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
@ -47,44 +47,69 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
/* directly import received icals */
this.registerInsertionPoint('common.contextmenu.attachment.actions', this.createAttachmentImportButton);
/* add import button to south navigation */
this.registerInsertionPoint("navigation.south", this.createImportButton, this);
/* add settings widget */
this.registerInsertionPoint('context.settings.category.calendar', this.createSettingsWidget);
/* export a calendar entry via rightclick */
this.registerInsertionPoint('context.calendar.contextmenu.actions', this.createItemExportInsertionPoint, this);
/* ical sync stuff */
if (container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/enable_sync") === true) {
/* edit panel */
Zarafa.core.data.SharedComponentType.addProperty('plugins.calendarimporter.settings.dialogs.calsyncedit');
/* enable the settings widget */
this.registerInsertionPoint('context.settings.category.calendar', this.createSettingsCalSyncWidget);
}
},
/**
* 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',
ref : "importbutton",
id : "importbutton",
text : _('Import Calendar'),
iconCls : 'icon_calendarimporter_button',
navigationContext : container.getContextByName('calendar'),
handler : this.onImportButtonClick,
scope : this
createItemExportInsertionPoint: function (include, btn) {
return {
text : dgettext('plugin_files', 'Export Event'),
handler: this.exportToICS.createDelegate(this, [btn]),
scope : this,
iconCls: 'icon_calendarimporter_export'
};
},
if(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/enable_export")) {
button.text = _('Import/Export Calendar');
/**
* Generates a request to download the selected records as vCard.
* @param {Ext.Button} btn
*/
exportToICS: 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.calendarimporter.data.ResponseHandler({
successCallback: Zarafa.plugins.calendarimporter.data.Actions.downloadICS,
scope : this
});
// request attachment preperation
container.getRequest().singleRequest(
'calendarmodule',
'export',
{
storeid: btn.records[0].get("store_entryid"),
records: recordIds
},
responseHandler
);
},
/**
@ -118,17 +143,17 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
*/
createAttachmentImportButton: function (include, btn) {
return {
text : _('Import Calendar'),
handler : this.getAttachmentFileName.createDelegate(this, [btn, this.gotAttachmentFileName]),
text : _('Import to Calendar'),
handler : this.getAttachmentFileName.createDelegate(this, [btn]),
scope : this,
iconCls : 'icon_calendarimporter_button',
beforeShow: function (item, record) {
var extension = record.data.name.split('.').pop().toLowerCase();
if (record.data.filetype == "text/calendar" || extension == "ics" || extension == "ifb" || extension == "ical" || extension == "ifbf") {
item.setDisabled(false);
item.setVisible(true);
} else {
item.setDisabled(true);
item.setVisible(false);
}
}
};
@ -139,10 +164,7 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
*/
gotAttachmentFileName: function (response) {
if (response.status == true) {
Zarafa.core.data.UIFactory.openLayerComponent(Zarafa.core.data.SharedComponentType['plugins.calendarimporter.dialogs.importevents'], undefined, {
manager : Ext.WindowMgr,
filename : response.tmpname
});
this.scope.openImportDialog(response.tmpname);
} else {
Zarafa.common.dialogs.MessageBox.show({
title : _('Error'),
@ -188,21 +210,23 @@ Zarafa.plugins.calendarimporter.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.calendarimporter.data.ResponseHandler({
successCallback: callback
successCallback: this.gotAttachmentFileName,
scope : this
});
// request attachment preperation
container.getRequest().singleRequest(
'calendarmodule',
'attachmentpath',
'importattachment',
{
entryid : entryid,
store : store,
@ -215,12 +239,17 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
},
/**
* Clickhandler for the button
* Open the import dialog.
* @param {String} filename
*/
onImportButtonClick: function () {
Zarafa.core.data.UIFactory.openLayerComponent(Zarafa.core.data.SharedComponentType['plugins.calendarimporter.dialogs.importevents'], undefined, {
manager : Ext.WindowMgr
});
openImportDialog: function (filename) {
var componentType = Zarafa.core.data.SharedComponentType['plugins.calendarimporter.dialogs.importevents'];
var config = {
filename: filename,
modal : true
};
Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
},
/**
@ -240,6 +269,13 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
case Zarafa.core.data.SharedComponentType['plugins.calendarimporter.settings.dialogs.calsyncedit']:
bid = 2;
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.Appointment") {
bid = 2;
}
}
break;
}
return bid;
},
@ -260,6 +296,9 @@ Zarafa.plugins.calendarimporter.ImportPlugin = Ext.extend(Zarafa.core.Plugin, {
case Zarafa.core.data.SharedComponentType['plugins.calendarimporter.settings.dialogs.calsyncedit']:
component = Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditContentPanel;
break;
case Zarafa.core.data.SharedComponentType['common.contextmenu']:
component = Zarafa.plugins.calendarimporter.ui.ContextMenu;
break;
}
return component;

View File

@ -27,6 +27,7 @@ Zarafa.plugins.calendarimporter.settings.SettingsCalSyncWidget = Ext.extend(Zara
{ name : 'pass' },
{ name : 'intervall', type : 'int' },
{ name : 'calendar' },
{ name : 'calendarname' },
{ name : 'lastsync' }
],
sortInfo : {
@ -68,8 +69,10 @@ Zarafa.plugins.calendarimporter.settings.SettingsCalSyncWidget = Ext.extend(Zara
var icslinks = settingsModel.get('zarafa/v1/contexts/calendar/icssync', true);
var syncArray = [];
for (var key in icslinks) {
if(icslinks.hasOwnProperty(key)) { // skip inherited props
syncArray.push(Ext.apply({}, icslinks[key], {id: key}));
}
}
// Load all icslinks into the GridPanel
var store = this.calsyncPanel.calsyncGrid.getStore();
@ -98,7 +101,8 @@ Zarafa.plugins.calendarimporter.settings.SettingsCalSyncWidget = Ext.extend(Zara
'user' : icslink.get('user'),
'pass' : icslink.get('pass'),
'lastsync' : icslink.get('lastsync'),
'calendar' : icslink.get('calendar')
'calendar' : icslink.get('calendar'),
'calendarname' : Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(icslink.get('calendar')).display_name
};
}
settingsModel.set('zarafa/v1/contexts/calendar/icssync', icslinkData);

View File

@ -24,13 +24,6 @@ Zarafa.plugins.calendarimporter.settings.SettingsWidget = Ext.extend(Zarafa.sett
title : _('Calendar Import/Export plugin settings'),
xtype : 'calendarimporter.settingswidget',
items : [
{
xtype : 'checkbox',
name : 'zarafa/v1/plugins/calendarimporter/enable_export',
ref : 'enableExport',
fieldLabel : 'Enable exporter',
lazyInit : false
},
{
xtype : 'checkbox',
name : 'zarafa/v1/plugins/calendarimporter/enable_sync',
@ -47,43 +40,14 @@ Zarafa.plugins.calendarimporter.settings.SettingsWidget = Ext.extend(Zarafa.sett
},
createSelectBox: function() {
var defaultFolder = container.getHierarchyStore().getDefaultFolder('calendar'); // @type: Zarafa.hierarchy.data.MAPIFolderRecord
var subFolders = defaultFolder.getChildren();
var myStore = [];
/* add all local calendar 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 calendar 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.Appointment")){
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 = Zarafa.plugins.calendarimporter.data.Actions.getAllCalendarFolders(true);
return {
xtype: "selectbox",
ref : 'defaultCalendar',
editable: false,
name: "zarafa/v1/plugins/calendarimporter/default_calendar",
value: container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_calendar"),
value: Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByName(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_calendar")).entryid,
width: 100,
fieldLabel: "Default calender",
store: myStore,
@ -123,9 +87,8 @@ Zarafa.plugins.calendarimporter.settings.SettingsWidget = Ext.extend(Zarafa.sett
* @param {Zarafa.settings.SettingsModel} settingsModel The settings to load
*/
update : function(settingsModel) {
this.enableExport.setValue(settingsModel.get(this.enableExport.name));
this.enableSync.setValue(settingsModel.get(this.enableSync.name));
this.defaultCalendar.setValue(settingsModel.get(this.defaultCalendar.name));
this.defaultCalendar.setValue(Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByName(settingsModel.get(this.defaultCalendar.name)).entryid);
this.defaultTimezone.setValue(settingsModel.get(this.defaultTimezone.name));
},
@ -136,10 +99,114 @@ Zarafa.plugins.calendarimporter.settings.SettingsWidget = Ext.extend(Zarafa.sett
* @param {Zarafa.settings.SettingsModel} settingsModel The settings to update
*/
updateSettings : function(settingsModel) {
settingsModel.set(this.enableExport.name, this.enableExport.getValue());
// check if the user changed a value
var changed = false;
if(settingsModel.get(this.enableSync.name) != this.enableSync.getValue()) {
changed = true;
} else if(settingsModel.get(this.defaultCalendar.name) != Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(this.defaultCalendar.getValue()).display_name) {
changed = true;
} else if(settingsModel.get(this.defaultTimezone.name) != this.defaultTimezone.getValue()) {
changed = true;
}
if(changed) {
// Really save changes
settingsModel.set(this.enableSync.name, this.enableSync.getValue());
settingsModel.set(this.defaultCalendar.name, this.defaultCalendar.getValue());
settingsModel.set(this.defaultCalendar.name, Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(this.defaultCalendar.getValue()).display_name); // store name
settingsModel.set(this.defaultTimezone.name, this.defaultTimezone.getValue());
this.onUpdateSettings();
}
},
/**
* Called after the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#save save}
* event to indicate the settings were successfully saved and it will forcefully realod the webapp.
* settings which were saved to the server.
* @private
*/
onUpdateSettings : function()
{
var message = _('Your WebApp needs to be reloaded to make the changes visible!');
message += '<br/><br/>';
message += _('WebApp will automatically restart in order for these changes to take effect');
message += '<br/>';
Zarafa.common.dialogs.MessageBox.addCustomButtons({
title: _('Restart WebApp'),
msg : message,
icon: Ext.MessageBox.QUESTION,
fn : this.restartWebapp,
customButton : [{
text : _('Restart'),
name : 'restart'
}, {
text : _('Cancel'),
name : 'cancel'
}],
scope : this
});
},
/**
* Event handler for {@link #onResetSettings}. This will check if the user
* wishes to reset the default settings or not.
* @param {String} button The button which user pressed.
* @private
*/
restartWebapp : function(button)
{
if (button === 'restart') {
var contextModel = this.ownerCt.settingsContext.getModel();
var realModel = contextModel.getRealSettingsModel();
realModel.save();
this.loadMask = new Zarafa.common.ui.LoadMask(Ext.getBody(), {
msg : '<b>' + _('Webapp is reloading, Please wait.') + '</b>'
});
this.loadMask.show();
this.mon(realModel, 'save', this.onSettingsSave, this);
this.mon(realModel, 'exception', this.onSettingsException, this);
}
},
/**
* Called when the {@link Zarafa.settings.} fires the {@link Zarafa.settings.SettingsModel#save save}
* event to indicate the settings were successfully saved and it will forcefully realod the webapp.
* @param {Zarafa.settings.SettingsModel} model The model which fired the event.
* @param {Object} parameters The key-value object containing the action and the corresponding
* settings which were saved to the server.
* @private
*/
onSettingsSave : function(model, parameters)
{
this.mun(model, 'save', this.onSettingsSave, this);
Zarafa.core.Util.reloadWebapp();
},
/**
* Called when the {@link Zarafa.settings.SettingsModel} fires the {@link Zarafa.settings.SettingsModel#exception exception}
* event to indicate the settings were not successfully saved.
* @param {Zarafa.settings.SettingsModel} model The settings model which fired the event
* @param {String} type The value of this parameter will be either 'response' or 'remote'.
* @param {String} action Name of the action (see {@link Ext.data.Api#actions}).
* @param {Object} options The object containing a 'path' and 'value' field indicating
* respectively the Setting and corresponding value for the setting which was being saved.
* @param {Object} response The response object as received from the PHP-side
* @private
*/
onSettingsException : function(model, type, action, options, response)
{
this.loadMask.hide();
// Remove event handlers
this.mun(model, 'save', this.onSettingsSave, this);
this.mun(model, 'exception', this.onSettingsException, this);
}
});

View File

@ -23,7 +23,7 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditContentPanel = Ext.e
model : true,
autoSave : false,
width : 400,
height : 350,
height : 400,
title : _('ICAL Sync'),
items : [{
xtype : 'calendarimporter.calsynceditpanel',

View File

@ -60,7 +60,6 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditPanel = Ext.extend(E
var id = 0;
var record = undefined;
console.log(this);
if(!this.currentItem) {
record = new store.recordType({
id: this.hashCode(this.icsurl.getValue()),
@ -69,6 +68,7 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditPanel = Ext.extend(E
user: this.user.getValue(),
pass: Ext.util.base64.encode(this.pass.getValue()),
calendar: this.calendar.getValue(),
calendarname : Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(this.calendar.getValue()).display_name,
lastsync: "never"
});
}
@ -82,6 +82,7 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditPanel = Ext.extend(E
this.currentItem.set('user', this.user.getValue());
this.currentItem.set('pass', Ext.util.base64.encode(this.pass.getValue()));
this.currentItem.set('calendar', this.calendar.getValue());
this.currentItem.set('calendarname', Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(this.calendar.getValue()).display_name);
}
this.dialog.close();
}
@ -95,14 +96,12 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditPanel = Ext.extend(E
createPanelItems : function(config)
{
var icsurl = "";
var intervall = "";
var intervall = "15";
var user = "";
var pass = "";
var calendar = "";
var defaultFolder = container.getHierarchyStore().getDefaultFolder('calendar'); // @type: Zarafa.hierarchy.data.MAPIFolderRecord
var subFolders = defaultFolder.getChildren();
var myStore = [];
var calendarname = "";
var calendar = Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByName(container.getSettingsModel().get("zarafa/v1/plugins/calendarimporter/default_calendar")).entryid;
var myStore = Zarafa.plugins.calendarimporter.data.Actions.getAllCalendarFolders(true);
if(config.item){
icsurl = config.item.get('icsurl');
@ -110,33 +109,7 @@ Zarafa.plugins.calendarimporter.settings.dialogs.CalSyncEditPanel = Ext.extend(E
user = config.item.get('user');
pass = Ext.util.base64.decode(config.item.get('pass'));
calendar = config.item.get('calendar');
}
/* add all local calendar 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 calendar 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.Appointment")){
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);
}
calendarname = config.item.get('calendarname');
}

View File

@ -58,6 +58,16 @@ Zarafa.plugins.calendarimporter.settings.ui.CalSyncGrid = Ext.extend(Ext.grid.Gr
return value ? "true" : "false";
},
/**
* Render function
* @return {String}
* @private
*/
renderCalendarColumn : function(value, p, record)
{
return Zarafa.plugins.calendarimporter.data.Actions.getCalendarFolderByEntryid(value).display_name;
},
/**
* Creates a column model object, used in {@link #colModel} config
* @return {Ext.grid.ColumnModel} column model object
@ -71,7 +81,7 @@ Zarafa.plugins.calendarimporter.settings.ui.CalSyncGrid = Ext.extend(Ext.grid.Gr
renderer : Zarafa.common.ui.grid.Renderers.text
},
{
dataIndex : 'calendar',
dataIndex : 'calendarname',
header : _('Destination Calender'),
renderer : Zarafa.common.ui.grid.Renderers.text
},

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

@ -0,0 +1,129 @@
/**
* ContectMenu.js zarafa calender to ics 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
*
*/
Ext.namespace('Zarafa.plugins.calendarimporter.ui');
/**
* @class Zarafa.plugins.calendarimporter.ui.ContextMenu
* @extends Zarafa.hierarchy.ui.ContextMenu
* @xtype calendarimporter.hierarchycontextmenu
*/
Zarafa.plugins.calendarimporter.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.calendarimporter.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.calendarimporter.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.calendarimporter.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.calendarimporter.ui.ContextMenu HierarchyContextMenu}
*/
createAdditionalContextMenuItems: function (config) {
return [{
xtype: 'menuseparator'
}, {
text : _('Import Calendar'),
iconCls : 'icon_calendarimporter_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 Calendar'),
iconCls : 'icon_calendarimporter_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.calendarimporter.ui.ContextMenu ContextMenu}
* @private
*/
onContextItemExport: function () {
var responseHandler = new Zarafa.plugins.calendarimporter.data.ResponseHandler({
successCallback: Zarafa.plugins.calendarimporter.data.Actions.downloadICS,
scope : this
});
// request attachment preperation
container.getRequest().singleRequest(
'calendarmodule',
'export',
{
storeid: this.records.get("store_entryid"),
folder : this.records.get("entryid")
},
responseHandler
);
},
/**
* Fires on selecting 'Open' menu option from {@link Zarafa.plugins.calendarimporter.ui.ContextMenu ContextMenu}
* @private
*/
onContextItemImport: function () {
var componentType = Zarafa.core.data.SharedComponentType['plugins.calendarimporter.dialogs.importevents'];
var config = {
modal : true,
folder: this.records.get("entryid")
};
Zarafa.core.data.UIFactory.openLayerComponent(componentType, undefined, config);
}
});
Ext.reg('calendarimporter.hierarchycontextmenu', Zarafa.plugins.calendarimporter.ui.ContextMenu);

View File

@ -2,7 +2,7 @@
<!DOCTYPE plugin SYSTEM "manifest.dtd">
<plugin version="2">
<info>
<version>@_@PLUGIN_VERSION@_@</version>
<version>2.2.0</version>
<name>calendarimporter</name>
<title>ICS Calendar Importer/Exporter</title>
<author>Christoph Haas</author>
@ -20,13 +20,14 @@
<serverfile type="module" module="calendarmodule">php/module.calendar.php</serverfile>
</server>
<client>
<clientfile load="release">js/calendarimporter.js</clientfile>
<clientfile load="release">js/calendarimporter-debug.js</clientfile>
<clientfile load="debug">js/calendarimporter-debug.js</clientfile>
<clientfile load="source">js/data/timezones.js</clientfile>
<clientfile load="source">js/external/Ext.util.base64.js</clientfile>
<clientfile load="source">js/plugin.calendarimporter.js</clientfile>
<clientfile load="source">js/data/Actions.js</clientfile>
<clientfile load="source">js/data/ResponseHandler.js</clientfile>
<clientfile load="source">js/external/Ext.util.base64.js</clientfile>
<clientfile load="source">js/ui/ContextMenu.js</clientfile>
<clientfile load="source">js/dialogs/ImportContentPanel.js</clientfile>
<clientfile load="source">js/dialogs/ImportPanel.js</clientfile>
<clientfile load="source">js/dialogs/settings/SettingsWidget.js</clientfile>
@ -35,9 +36,10 @@
<clientfile load="source">js/dialogs/settings/ui/CalSyncPanel.js</clientfile>
<clientfile load="source">js/dialogs/settings/dialogs/CalSyncEditContentPanel.js</clientfile>
<clientfile load="source">js/dialogs/settings/dialogs/CalSyncEditPanel.js</clientfile>
<clientfile load="source">js/plugin.calendarimporter.js</clientfile>
</client>
<resources>
<resourcefile load="release">resources/css/calendarimporter-min.css</resourcefile>
<resourcefile load="release">resources/css/calendarimporter.css</resourcefile>
<resourcefile load="debug">resources/css/calendarimporter.css</resourcefile>
<resourcefile load="source">resources/css/calendarimporter-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": {
"sabre/vobject": "4.1"
}
}

View File

@ -1,9 +1,10 @@
<?php
/**
* download.php, zarafa calender to ics im/exporter
* download.php, zarafa calendar to ics im/exporter
*
* Author: Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2014 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,25 +21,54 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
$basedir = $_GET["basedir"];
$secid = $_GET["secid"];
$fileid = $_GET["fileid"];
$realname = $_GET["realname"];
namespace calendarimporter;
$secfile = $basedir . "/secid." . $secid;
$icsfile = $basedir . "/" . $fileid . "." . $secid;
// if the secid file exists -> download!
if(file_exists($secfile)) {
@header("Last-Modified: " . @gmdate("D, d M Y H:i:s",time()) . " GMT");
@header("Content-type: text/calendar");
header("Content-Length: " . filesize($icsfile));
header("Content-Disposition: attachment; filename=" . $realname . ".ics");
//write ics
readfile($icsfile);
unlink($secfile);
unlink($icsfile);
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_CALENDARIMPORTER_TMP_UPLOAD . "ics_" . $token . ".ics";
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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,552 +0,0 @@
<?php
/**
* class.icalparser.php zarafa calender to ics im/exporter
* http://code.google.com/p/ics-parser/
*
* Author: Martin Thoma , Christoph Haas <christoph.h@sprinternet.at>
* Copyright (C) 2012-2014 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
*
*/
/**
* This is the iCal-class
* Parse ics file content to array.
*
* @param {string} filename The name of the file which should be parsed
* @constructor
*/
class ICal {
/* How many ToDos are in this ical? */
public /** @type {int} */ $todo_count = 0;
/* How many events are in this ical? */
public /** @type {int} */ $event_count = 0;
/* Currently editing an alarm? */
private /** @type {boolean} */ $isalarm = false;
/* The parsed calendar */
public /** @type {Array} */ $cal;
/* Error message store... null default */
public /** @type {String} */ $errors;
/* Which keyword has been added to cal at last? */
private /** @type {string} */ $_lastKeyWord;
/* The default timezone, used to convert UTC Time */
private /** @type {string} */ $default_timezone = "Europe/Vienna";
/* The default timezone, used to convert UTC Time */
private /** @type {boolean} */ $timezone_set = false;
/* Ignore Daylight Saving Time */
private /** @type {boolean} */ $ignore_dst = false;
/**
* Creates the iCal-Object
*
* @param {string} $filename The path to the iCal-file
*
* @return Object The iCal-Object
*/
public function __construct($filename, $default_timezone, $timezone = false, $igndst = false) {
if (!$filename) {
$this->errors = "No filename specified";
return false;
}
$this->default_timezone = $default_timezone;
if(isset($timezone) && $timezone != false) {
$this->default_timezone = $timezone;
$this->timezone_set = true;
}
if(isset($igndst) && $igndst != false) {
$this->ignore_dst = true;
}
$lines = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (stristr($lines[0], 'BEGIN:VCALENDAR') === false) {
$this->errors = "Not a valid ical file";
return false;
} else {
foreach ($lines as $line) {
$line = trim($line);
$add = $this->keyValueFromString($line);
if ($add === false) {
$this->addCalendarComponentWithKeyAndValue($type, false, $line);
continue;
}
list($keyword, $dummy, $prop, $propvalue, $value) = $add;
switch ($line) {
// http://www.kanzaki.com/docs/ical/vtodo.html
case "BEGIN:VTODO":
$this->todo_count++;
$type = "VTODO";
break;
case "BEGIN:VALARM":
//echo "vevent gematcht";
$this->isalarm=true;
$type = "VEVENT";
break;
// http://www.kanzaki.com/docs/ical/vevent.html
case "BEGIN:VEVENT":
//echo "vevent gematcht";
$this->event_count++;
$type = "VEVENT";
break;
//all other special strings
case "BEGIN:VCALENDAR":
case "BEGIN:DAYLIGHT":
// http://www.kanzaki.com/docs/ical/vtimezone.html
case "BEGIN:VTIMEZONE":
case "BEGIN:STANDARD":
$type = $value;
break;
case "END:VTODO": // end special text - goto VCALENDAR key
case "END:VEVENT":
case "END:VCALENDAR":
case "END:DAYLIGHT":
case "END:VTIMEZONE":
case "END:STANDARD":
$type = "VCALENDAR";
break;
case "END:VALARM":
$this->isalarm=false;
$type = "VEVENT";
break;
default:
$this->addCalendarComponentWithKeyAndValue($type, $keyword, $value, $prop, $propvalue);
break;
}
}
return $this->cal;
}
}
/**
* Add to $this->ical array one value and key.
*
* @param {string} $component This could be VTODO, VEVENT, VCALENDAR, ...
* @param {string} $keyword The keyword, for example DTSTART
* @param {string} $value The value, for example 20110105T090000Z
*
* @return {None}
*/
public function addCalendarComponentWithKeyAndValue($component, $keyword, $value, $prop = false, $propvalue = false) {
if ($keyword == false) { // multiline value
$keyword = $this->last_keyword;
switch ($component) {
case 'VEVENT':
if (stristr($keyword, "DTSTART") or stristr($keyword, "DTEND") or stristr($keyword, "TRIGGER")) {
$ts = $this->iCalDateToUnixTimestamp($value, $prop, $propvalue);
$value = $ts * 1000;
}
$value = str_replace("\\n", "\n", $value);
$value = $this->customFilters($keyword, $value);
if(!$this->isalarm) {
$value = $this->cal[$component][$this->event_count - 1][$keyword].$value;
} else {
$value = $this->cal[$component][$this->event_count - 1]["VALARM"][$keyword].$value;
}
break;
case 'VTODO' :
$value = $this->cal[$component][$this->todo_count - 1]
[$keyword].$value;
break;
}
}
/* This should not be neccesary anymore*/
//always strip additional content....
//if (stristr($keyword, "DTSTART") or stristr($keyword, "DTEND")) {
//$keyword = explode(";", $keyword);
//$keyword = $keyword[0]; // remove additional content like VALUE=DATE
//}
if ((stristr($keyword, "TIMEZONE") || stristr($keyword, "TZID")) && !$this->timezone_set) { // check if timezone already set...
$this->default_timezone = $this->trimTimeZone($value); // store the calendertimezone
}
switch ($component) {
case "VTODO":
$this->cal[$component][$this->todo_count - 1][$keyword] = $value;
//$this->cal[$component][$this->todo_count]['Unix'] = $unixtime;
break;
case "VEVENT":
if (stristr($keyword, "DTSTART") or stristr($keyword, "DTEND") or stristr($keyword, "TRIGGER")) {
$ts = $this->iCalDateToUnixTimestamp($value, $prop, $propvalue);
$value = $ts * 1000;
}
$value = str_replace("\\n", "\n", $value);
$value = $this->customFilters($keyword, $value);
if(!$this->isalarm) {
$this->cal[$component][$this->event_count - 1][$keyword] = $value;
} else {
$this->cal[$component][$this->event_count - 1]["VALARM"][$keyword] = $value;
}
break;
default:
$this->cal[$component][$keyword] = $value;
break;
}
$this->last_keyword = $keyword;
}
/**
* Filter some chars out of the value.
*
* @param {string} $keyword keyword to which the filter is applied
* @param {string} $value to filter
* @return {string} filtered value
*/
private function customFilters($keyword, $value) {
if (stristr($keyword, "SUMMARY")) {
$value = str_replace("\n", " ", $value); // we don't need linebreaks in the summary...
}
if (stristr($keyword, "SUMMARY")) {
$value = str_replace("\,", ",", $value); // strange escaped comma
}
return $value;
}
/**
* Trim a Timezone String
*
* @param {string} $timezone timezone string which should be trimmed
* @return {string} trimmed value
*/
private function trimTimeZone($timezone) {
if(preg_match('~([?<=/]*)([^/]*[/|-][^/]*$)~', $timezone, $matches)) { // detects tzurls in tzids
if ($matches[2] != "") {
return $matches[2]; // 2 = extracted timezone
} else {
return $timezone;
}
}
return $timezone;
}
/**
* Get a key-value pair of a string.
*
* @param {string} $text which is like "VCALENDAR:Begin" or "LOCATION:"
*
* @return {array} array("Argument", "Optional Arg/Val", "Optional Arg", "Optional Value", "Value")
*/
public function keyValueFromString($text) {
preg_match('/(^[^a-z:;]+)([;]+([a-zA-Z]*)[=]*([^:"]*|"[\w\W]*"))?[:]([\w\W]*)/', $text, $matches);
// this regex has problems with multiple attributes... ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:mailto:jsmith@example.com
// TODO: fix this
if (count($matches) == 0) {
return false;
}
$matches = array_splice($matches, 1, 5); // 0 = Arg, 1 = Complete Optional Arg/Val, 2 = Optional Arg, 3 = Optional Val, 4 = Value
return $matches;
}
/**
* Return UTC Unix timestamp from ical date time format
*
* @param {string} $icalDate A Date in the format YYYYMMDD[T]HHMMSS[Z] or
* YYYYMMDD[T]HHMMSS
*
* @return {int}
*/
private function iCalDateToUTCUnixTimestamp($icalDate, $prop, $propvalue) {
$timezone = false;
$allday = false;
if($prop) {
$pos = strpos("TZIDtzid", $prop);
if($pos !== false && $propvalue != false) {
$timezone = str_replace('"', '', $propvalue);
$timezone = str_replace('\'', '', $timezone);
$timezone = $this->trimTimeZone($timezone);
}
}
/* timestring format */
$utc = strpos("zZ",substr($icalDate, -1)) === false ? false : true;
$icalDate = str_replace('T', '', $icalDate);
$icalDate = str_replace('Z', '', $icalDate);
$pattern = '/([0-9]{4})'; // 1: YYYY
$pattern .= '([0-9]{2})'; // 2: MM
$pattern .= '([0-9]{2})'; // 3: DD
$pattern .= '([0-9]{0,2})'; // 4: HH
$pattern .= '([0-9]{0,2})'; // 5: MM
$pattern .= '([0-9]{0,2})/'; // 6: SS
preg_match($pattern, $icalDate, $date);
// Unix timestamp can't represent dates before 1970
if ($date[1] <= 1970) {
return false;
}
// check if we have a allday event
if((!$date[6] || $date[6] === "") || (!$date[5] || $date[5] === "") || (!$date[4] || $date[4] === "")) {
$date[6] = 0;
$date[5] = 0;
$date[4] = 0;
$allday = true;
$dtz = date_default_timezone_get();
date_default_timezone_set('UTC');
}
// Unix timestamps after 03:14:07 UTC 2038-01-19 might cause an overflow
// if 32 bit integers are used.
$timestamp = mktime((int)$date[4],
(int)$date[5],
(int)$date[6],
(int)$date[2],
(int)$date[3],
(int)$date[1]);
if($allday) {
date_default_timezone_set($dtz);
}
if(!$utc && !$allday) {
$tz = $this->default_timezone;
if($timezone != false) {
$tz = $timezone;
}
$error = false;
$this_tz = false;
try {
$this_tz = new DateTimeZone($tz);
} catch(Exception $e) {
error_log($e->getMessage());
$error = true;
}
if($error) {
try { // Try using the default calendar timezone
$this_tz = new DateTimeZone($this->default_timezone);
} catch(Exception $e) {
error_log($e->getMessage());
$timestamp_utc = $timestamp; // if that fails, we cannot do anymore
}
}
if($this_tz != false) {
$tz_now = new DateTime("now", $this_tz);
$tz_offset = $this_tz->getOffset($tz_now);
$timestamp_utc = $timestamp - $tz_offset;
}
} else {
$timestamp_utc = $timestamp;
}
return array($timestamp_utc,$allday);
}
/**
* Return a timezone specific timestamp
* @param {int} $timestamp_utc UTC Timestamp to convert
* @param {string} $timezone Timezone
* @return {int}
*/
private function UTCTimestampToTZTimestamp($timestamp_utc, $timezone, $ignore_dst = false) {
$this_tz = false;
try { // Try using the default calendar timezone
$this_tz = new DateTimeZone($this->default_timezone);
} catch(Exception $e) {
error_log($e->getMessage());
$timestamp_utc = $timestamp; // if that fails, we cannot do anymore
}
if($this_tz != false) {
$transition = $this_tz->getTransitions($timestamp_utc,$timestamp_utc);
$trans_offset = $transition[0]['offset'];
$isdst = $transition[0]['isdst'];
$tz_now = new DateTime("now", $this_tz);
$tz_offset = $this_tz->getOffset($tz_now);
if(!$ignore_dst) {
$tz_offset = $trans_offset; // normaly use dst
}
return $timestamp_utc + $tz_offset;
}
return $timestamp_utc; // maybe timezone conversion will fail...
}
/**
* Return Timezone specific Unix timestamp from ical date time format
*
* @param {string} $icalDate A Date in the format YYYYMMDD[T]HHMMSS[Z] or
* YYYYMMDD[T]HHMMSS
*
* @return {int}
*/
public function iCalDateToUnixTimestamp($icalDate, $prop, $propvalue) {
list($timestamp, $allday) = $this->iCalDateToUTCUnixTimestamp($icalDate, $prop, $propvalue);
if(!$allday) {
$timestamp = $this->UTCTimestampToTZTimestamp($timestamp, $this->default_timezone, $this->ignore_dst, $allday);
}
return $timestamp;
}
/**
* Returns an array of arrays with all events. Every event is an associative
* array and each property is an element it.
*
* @return {array}
*/
public function events() {
$array = $this->cal;
return $array['VEVENT'];
}
/**
* Returns an array of calendar types.
*
* @return {array}
*/
public function calendar() {
$array = $this->cal;
return $array['VCALENDAR'];
}
/**
* Returns the default or set timezone
*
* @return {string}
*/
public function timezone() {
return $this->default_timezone;
}
/**
* Returns a boolean value whether thr current calendar has events or not
*
* @return {boolean}
*/
public function hasEvents() {
return ( count($this->events()) > 0 ? true : false );
}
/**
* Returns false when the current calendar has no events in range, else the
* events.
*
* Note that this function makes use of a UNIX timestamp. This might be a
* problem on January the 29th, 2038.
* See http://en.wikipedia.org/wiki/Unix_time#Representing_the_number
*
* @param {boolean} $rangeStart Either true or false
* @param {boolean} $rangeEnd Either true or false
*
* @return {mixed}
*/
public function eventsFromRange($rangeStart = false, $rangeEnd = false) {
$events = $this->sortEventsWithOrder($this->events(), SORT_ASC);
if (!$events) {
return false;
}
$extendedEvents = array();
if ($rangeStart !== false) {
$rangeStart = new DateTime();
}
if ($rangeEnd !== false or $rangeEnd <= 0) {
$rangeEnd = new DateTime('2038/01/18');
} else {
$rangeEnd = new DateTime($rangeEnd);
}
$rangeStart = $rangeStart->format('U');
$rangeEnd = $rangeEnd->format('U');
// loop through all events by adding two new elements
foreach ($events as $anEvent) {
$timestamp = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']);
if ($timestamp >= $rangeStart && $timestamp <= $rangeEnd) {
$extendedEvents[] = $anEvent;
}
}
return $extendedEvents;
}
/**
* Returns sorted events
*
* @param {array} $events An array with events.
* @param {array} $sortOrder Either SORT_ASC, SORT_DESC, SORT_REGULAR,
* SORT_NUMERIC, SORT_STRING
*
* @return {array}
*/
public function sortEventsWithOrder($events, $sortOrder = SORT_ASC) {
$extendedEvents = array();
// loop through all events by adding two new elements
foreach ($events as $anEvent) {
if (!array_key_exists('UNIX_TIMESTAMP', $anEvent)) {
$anEvent['UNIX_TIMESTAMP'] = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']);
}
if (!array_key_exists('REAL_DATETIME', $anEvent)) {
$anEvent['REAL_DATETIME'] = date("d.m.Y", $anEvent['UNIX_TIMESTAMP']);
}
$extendedEvents[] = $anEvent;
}
foreach ($extendedEvents as $key => $value) {
$timestamp[$key] = $value['UNIX_TIMESTAMP'];
}
array_multisort($timestamp, $sortOrder, $extendedEvents);
return $extendedEvents;
}
}
?>

View File

@ -21,21 +21,61 @@
*
*/
include_once('mapi/class.recurrence.php');
include_once('plugins/calendarimporter/php/ical/class.icalcreator.php');
include_once('plugins/calendarimporter/php/ical/class.icalparser.php');
include_once('vendor/autoload.php');
class CalendarModule extends Module {
use Sabre\VObject;
private $DEBUG = false; // enable error_log debugging
class CalendarModule extends Module
{
private $DEBUG = true; // enable error_log debugging
private $busystates = null;
private $labels = null;
private $attendeetype = null;
/**
* @constructor
* @param $id
* @param $data
*/
public function __construct($id, $data) {
parent::Module($id, $data);
public function __construct($id, $data)
{
parent::__construct($id, $data);
// init default timezone
date_default_timezone_set(PLUGIN_CALENDARIMPORTER_DEFAULT_TIMEZONE);
// init mappings
$this->busystates = array(
"FREE",
"TENTATIVE",
"BUSY",
"OOF"
);
$this->labels = array(
"NONE",
"IMPORTANT",
"WORK",
"PERSONAL",
"HOLIDAY",
"REQUIRED",
"TRAVEL REQUIRED",
"PREPARATION REQUIERED",
"BIRTHDAY",
"SPECIAL DATE",
"PHONE INTERVIEW"
);
$this->attendeetype = array(
"NON-PARTICIPANT", // needed as zarafa starts counting at 1
"REQ-PARTICIPANT",
"OPT-PARTICIPANT",
"NON-PARTICIPANT"
);
}
/**
@ -43,7 +83,8 @@ class CalendarModule 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) {
@ -58,13 +99,16 @@ class CalendarModule extends Module {
error_log("exec: " . $actionType);
}
switch ($actionType) {
case "load":
$result = $this->loadCalendar($actionType, $actionData);
break;
case "export":
$result = $this->exportCalendar($actionType, $actionData);
break;
case "import":
$result = $this->importCalendar($actionType, $actionData);
break;
case "attachmentpath":
case "importattachment":
$result = $this->getAttachmentPath($actionType, $actionData);
break;
default:
@ -91,7 +135,8 @@ class CalendarModule 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";
@ -108,224 +153,60 @@ class CalendarModule extends Module {
}
/**
* Generates the secid file (used to verify the download path)
* @param $secid the secid, a random security token
* Get a property from the array.
* @param $props
* @param $propname
* @return string
*/
private function createSecIDFile($secid) {
$lockFile = TMP_PATH . "/secid." . $secid;
$fh = fopen($lockFile, 'w') or die("can't open secid file");
$stringData = date(DATE_RFC822);
fwrite($fh, $stringData);
fclose($fh);
private function getProp($props, $propname)
{
if (isset($props["props"][$propname])) {
return $props["props"][$propname];
}
return "";
}
/**
* Generates the secid file (used to verify the download path)
* @param $time a timestamp
* @param $incl_time true if date should include time
* @ return date object
*/
private function getIcalDate($time, $incl_time = true) {
return $incl_time ? date('Ymd\THis', $time) : date('Ymd', $time);
private function getDurationStringFromMintues($minutes, $pos = false) {
$pos = $pos === true ? "+" : "-";
$str = $pos . "P";
// variables for holding values
$mins = intval($minutes);
$hours = 0;
$days = 0;
$weeks = 0;
// calculations
if ( $mins >= 60 ) {
$hours = (int)($mins / 60);
$mins = $mins % 60;
}
if ( $hours >= 24 ) {
$days = (int)($hours / 24);
$hours = $hours % 60;
}
if ( $days >= 7 ) {
$weeks = (int)($days / 7);
$days = $days % 7;
}
/**
* adds an event to the exported calendar)
* @param $vevent pointer to the eventstore
* @param $event the event to add
*/
private function addEvent(&$vevent, $event) {
$busystate = array("FREE", "TENTATIVE", "BUSY", "OOF");
$zlabel = array("NONE", "IMPORTANT", "WORK", "PERSONAL", "HOLIDAY", "REQUIRED", "TRAVEL REQUIRED", "PREPARATION REQUIERED", "BIRTHDAY", "SPECIAL DATE", "PHONE INTERVIEW");
$vevent->setProperty("LOCATION", $event["location"]); // property name - case independent
$vevent->setProperty("SUMMARY", $event["subject"]);
$vevent->setProperty("DESCRIPTION", str_replace("\n", "\\n",$event["description"]));
$vevent->setProperty("COMMENT", "Exported from Zarafa" );
$vevent->setProperty("ORGANIZER", $event["sent_representing_email_address"]);
$vevent->setProperty("DTSTART", $this->getIcalDate($event["commonstart"]) . "Z");
$vevent->setProperty("DTEND", $this->getIcalDate($event["commonend"]) . "Z");
$vevent->setProperty("DTSTAMP", $this->getIcalDate($event["creation_time"]) . "Z");
$vevent->setProperty("CREATED", $this->getIcalDate($event["creation_time"]) . "Z");
$vevent->setProperty("LAST-MODIFIED", $this->getIcalDate($event["last_modification_time"]) . "Z");
$vevent->setProperty("X-MICROSOFT-CDO-BUSYSTATUS", $busystate[$event["busystatus"]]);
$vevent->setProperty("X-ZARAFA-LABEL", $zlabel[$event["label"]]);
$vevent->setProperty("PRIORITY", $event["importance"]);
$vevent->setProperty("CLASS", $event["private"] ? "PRIVATE" : "PUBLIC");
// ATTENDEES
if(count($event["attendees"]) > 0) {
foreach($event["attendees"] as $attendee) {
$vevent->setProperty("ATTENDEE", $attendee["props"]["smtp_address"]);
// format result
if ( $weeks ) {
$str .= "{$weeks}W";
}
if ( $days ) {
$str .= "{$days}D";
}
if ( $hours ) {
$str .= "{$hours}H";
}
if ( $mins ) {
$str .= "{$mins}M";
}
// REMINDERS
if($event["reminder"]) {
$valarm = & $vevent->newComponent("valarm"); // create an event alarm
$valarm->setProperty("action", "DISPLAY" );
$valarm->setProperty("description", $vevent->getProperty("SUMMARY")); // reuse the event summary
$valarm->setProperty("trigger", $this->getIcalDate($event["reminder_time"]) . "Z"); // create alarm trigger (in UTC datetime)
}
}
/**
* Loads the descriptiontext of an event
* @param $event
* @return array with event description/body
*/
private function loadEventDescription($event) {
$entryid = $this->getActionEntryID($event);
$store = $this->getActionStore($event);
$basedate = null;
$properties = $GLOBALS['properties']->getAppointmentProperties();
$plaintext = true;
$data = array();
if($store && $entryid) {
$message = $GLOBALS['operations']->openMessage($store, $entryid);
// add all standard properties from the series/normal message
$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $properties, (isset($plaintext) && $plaintext));
// if appointment is recurring then only we should get properties of occurence if basedate is supplied
if($data['item']['props']['recurring'] === true) {
if(isset($basedate) && $basedate) {
$recur = new Recurrence($store, $message);
$exceptionatt = $recur->getExceptionAttachment($basedate);
// Single occurences are never recurring
$data['item']['props']['recurring'] = false;
if($exceptionatt) {
// Existing exception (open existing item, which includes basedate)
$exceptionattProps = mapi_getprops($exceptionatt, array(PR_ATTACH_NUM));
$exception = mapi_attach_openobj($exceptionatt, 0);
// overwrite properties with the ones from the exception
$exceptionProps = $GLOBALS['operations']->getMessageProps($store, $exception, $properties, (isset($plaintext) && $plaintext));
/**
* If recurring item has set reminder to true then
* all occurrences before the 'flagdueby' value(of recurring item)
* should not show that reminder is set.
*/
if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
if ($flagDueByDay > $basedate) {
$exceptionProps['props']['reminder'] = false;
}
}
// The properties must be merged, if the recipients or attachments are present in the exception
// then that list should be used. Otherwise the list from the series must be applied (this
// corresponds with OL2007).
// @FIXME getMessageProps should not return empty string if exception doesn't contain body
// by this change we can handle a situation where user has set empty string in the body explicitly
if (!empty($exceptionProps['props']['body']) || !empty($exceptionProps['props']['html_body'])) {
if(!empty($exceptionProps['props']['body'])) {
$data['item']['props']['body'] = $exceptionProps['props']['body'];
}
if(!empty($exceptionProps['props']['html_body'])) {
$data['item']['props']['html_body'] = $exceptionProps['props']['html_body'];
}
$data['item']['props']['isHTML'] = $exceptionProps['props']['isHTML'];
}
// remove properties from $exceptionProps so array_merge will not overwrite it
unset($exceptionProps['props']['html_body']);
unset($exceptionProps['props']['body']);
unset($exceptionProps['props']['isHTML']);
$data['item']['props'] = array_merge($data['item']['props'], $exceptionProps['props']);
if (isset($exceptionProps['recipients'])) {
$data['item']['recipients'] = $exceptionProps['recipients'];
}
if (isset($exceptionProps['attachments'])) {
$data['item']['attachments'] = $exceptionProps['attachments'];
}
// Make sure we are using the passed basedate and not something wrong in the opened item
$data['item']['props']['basedate'] = $basedate;
} else {
// opening an occurence of a recurring series (same as normal open, but add basedate, startdate and enddate)
$data['item']['props']['basedate'] = $basedate;
$data['item']['props']['startdate'] = $recur->getOccurrenceStart($basedate);
$data['item']['props']['duedate'] = $recur->getOccurrenceEnd($basedate);
$data['item']['props']['commonstart'] = $data['item']['props']['startdate'];
$data['item']['props']['commonend'] = $data['item']['props']['duedate'];
unset($data['item']['props']['reminder_time']);
/**
* If recurring item has set reminder to true then
* all occurrences before the 'flagdueby' value(of recurring item)
* should not show that reminder is set.
*/
if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
if ($flagDueByDay > $basedate) {
$exceptionProps['props']['reminder'] = false;
}
}
}
} else {
// Opening a recurring series, get the recurrence information
$recur = new Recurrence($store, $message);
$recurpattern = $recur->getRecurrence();
$tz = $recur->tz; // no function to do this at the moment
// Add the recurrence pattern to the data
if(isset($recurpattern) && is_array($recurpattern)) {
$data['item']['props'] += $recurpattern;
}
// Add the timezone information to the data
if(isset($tz) && is_array($tz)) {
$data['item']['props'] += $tz;
}
}
}
}
return $data['item']['props']['body'];
}
/**
* Loads the attendees of an event
* @param $event
* @return array with event attendees
*/
private function loadAttendees($event) {
$entryid = $this->getActionEntryID($event);
$store = $this->getActionStore($event);
$basedate = null;
$properties = $GLOBALS['properties']->getAppointmentProperties();
$plaintext = true;
$data = array();
if($store && $entryid) {
$message = $GLOBALS['operations']->openMessage($store, $entryid);
// add all standard properties from the series/normal message
$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $properties, (isset($plaintext) && $plaintext));
}
return $data['item']['recipients']['item'];
return $str;
}
/**
@ -333,61 +214,137 @@ class CalendarModule extends Module {
* @param $actionType
* @param $actionData
*/
private function exportCalendar($actionType, $actionData) {
$secid = $this->randomstring();
$this->createSecIDFile($secid);
$tmpname = stripslashes($actionData["calendar"] . ".ics." . $this->randomstring(8));
$filename = TMP_PATH . "/" . $tmpname . "." . $secid;
if(!is_writable(TMP_PATH . "/")) {
error_log("could not write to export tmp directory!");
private function exportCalendar($actionType, $actionData)
{
// Get store id
$storeid = false;
if (isset($actionData["storeid"])) {
$storeid = $actionData["storeid"];
}
$tz = date("e"); // use php timezone (maybe set up in php.ini, date.timezone)
if($this->DEBUG) {
error_log("PHP Timezone: " . $tz);
// Get records
$records = array();
if (isset($actionData["records"])) {
$records = $actionData["records"];
}
$config = array(
"language" => substr($GLOBALS["settings"]->get("zarafa/v1/main/language"),0,2),
"directory" => TMP_PATH . "/",
"filename" => $tmpname . "." . $secid,
"unique_id" => "zarafa-export-plugin",
"TZID" => $tz
);
$v = new vcalendar($config);
$v->setProperty("method", "PUBLISH"); // required of some calendar software
$v->setProperty("x-wr-calname", $actionData["calendar"]); // required of some calendar software
$v->setProperty("X-WR-CALDESC", "Exported Zarafa Calendar"); // required of some calendar software
$v->setProperty("X-WR-TIMEZONE", $tz);
$xprops = array("X-LIC-LOCATION" => $tz); // required of some calendar software
iCalUtilityFunctions::createTimezone($v, $tz, $xprops); // create timezone object in calendar
foreach($actionData["data"] as $event) {
$event["props"]["description"] = $this->loadEventDescription($event);
$event["props"]["attendees"] = $this->loadAttendees($event);
$vevent = & $v->newComponent("vevent"); // create a new event object
$this->addEvent($vevent, $event["props"]);
// Get folders
$folder = false;
if (isset($actionData["folder"])) {
$folder = $actionData["folder"];
}
$v->saveCalendar();
$response = array();
$error = false;
$error_msg = "";
// write csv
$token = $this->randomstring(16);
$file = PLUGIN_CALENDARIMPORTER_TMP_UPLOAD . "ics_" . $token . ".ics";
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]);
}
}
$vcalendar = new VObject\Component\VCalendar();
// Add static stuff to vcalendar
$vcalendar->add('METHOD', 'PUBLISH');
$vcalendar->add('X-WR-CALDESC', 'Exported Zarafa Calendar');
$vcalendar->add('X-WR-TIMEZONE', date_default_timezone_get());
// TODO: add VTIMEZONE object to ical.
for ($index = 0, $count = count($records); $index < $count; $index++) {
$message = mapi_msgstore_openentry($store, hex2bin($records[$index]));
// get message properties.
$properties = $GLOBALS['properties']->getAppointmentProperties();
$plaintext = true;
$messageProps = $GLOBALS['operations']->getMessageProps($store, $message, $properties, $plaintext);
$vevent = $vcalendar->add('VEVENT', [
'SUMMARY' => $this->getProp($messageProps, "subject"),
'DTSTART' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "startdate")),
'DTEND' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "duedate")),
'CREATED' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "creation_time")),
'LAST-MODIFIED' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "last_modification_time")),
'PRIORITY' => $this->getProp($messageProps, "importance"),
'X-MICROSOFT-CDO-INTENDEDSTATUS' => $this->busystates[intval($this->getProp($messageProps, "busystatus"))], // both seem to be valid...
'X-MICROSOFT-CDO-BUSYSTATUS' => $this->busystates[intval($this->getProp($messageProps, "busystatus"))], // both seem to be valid...
'X-ZARAFA-LABEL' => $this->labels[intval($this->getProp($messageProps, "label"))],
'CLASS' => $this->getProp($messageProps, "private") ? "PRIVATE" : "PUBLIC",
'COMMENT' => "eid:" . $records[$index]
]);
// Add organizer
$vevent->add('ORGANIZER','mailto:' . $this->getProp($messageProps, "sender_email_address"));
$vevent->ORGANIZER['CN'] = $this->getProp($messageProps, "sender_name");
// Add Attendees
if(isset($messageProps["recipients"]) && count($messageProps["recipients"]["item"]) > 0) {
foreach($messageProps["recipients"]["item"] as $attendee) {
$att = $vevent->add('ATTENDEE', "mailto:" . $this->getProp($attendee, "email_address"));
$att["CN"] = $this->getProp($attendee, "display_name");
$att["ROLE"] = $this->attendeetype[intval($this->getProp($attendee, "recipient_type"))];
}
}
// Add alarms
if(!empty($this->getProp($messageProps, "reminder")) && $this->getProp($messageProps, "reminder") == 1) {
$valarm = $vevent->add('VALARM', [
'ACTION' => 'DISPLAY',
'DESCRIPTION' => $this->getProp($messageProps, "subject") // reuse the event summary
]);
// Add trigger
$durationValue = $this->getDurationStringFromMintues($this->getProp($messageProps, "reminder_minutes"), false);
$valarm->add('TRIGGER', $durationValue); // default trigger type is duration (see 4.8.6.3)
/*
$valarm->add('TRIGGER', date_timestamp_set(new DateTime(), $this->getProp($messageProps, "reminder_time"))); // trigger type "DATE-TIME"
$valarm->TRIGGER['VALUE'] = 'DATE-TIME';
*/
}
// Add location
if(!empty($this->getProp($messageProps, "location"))) {
$vevent->add('LOCATION',$this->getProp($messageProps, "location"));
}
// Add description
$body = $this->getProp($messageProps, "isHTML") ? $this->getProp($messageProps, "html_body") : $this->getProp($messageProps, "body");
if(!empty($body)) {
$vevent->add('DESCRIPTION',$body);
}
}
// write combined ics file
file_put_contents($file, file_get_contents($file) . $vcalendar->serialize());
}
if (count($records) > 0) {
$response['status'] = true;
$response['fileid'] = $tmpname; // number of entries that will be exported
$response['basedir'] = TMP_PATH;
$response['secid'] = $secid;
$response['realname'] = $actionData["calendar"];
$response['download_token'] = $token;
$response['filename'] = count($records) . "events.ics";
} else {
$response['status'] = false;
$response['message'] = "No events found. Export skipped!";
}
$this->addActionData($actionType, $response);
$GLOBALS["bus"]->addData($this->getResponseData());
if($this->DEBUG) {
error_log("export done, bus data written!");
}
}
/**
@ -395,40 +352,111 @@ class CalendarModule extends Module {
* @param $actionType
* @param $actionData
*/
private function importCalendar($actionType, $actionData) {
if($this->DEBUG) {
error_log("PHP Timezone: " . $tz);
private function importCalendar($actionType, $actionData)
{
// Get uploaded vcf path
$icsfile = false;
if (isset($actionData["ics_filepath"])) {
$icsfile = $actionData["ics_filepath"];
}
if(is_readable ($actionData["ics_filepath"])) {
$ical = new ICal($actionData["ics_filepath"], $GLOBALS["settings"]->get("zarafa/v1/plugins/calendarimporter/default_timezone"), $actionData["timezone"], $actionData["ignore_dst"]); // Parse it!
// Get store id
$storeid = false;
if (isset($actionData["storeid"])) {
$storeid = $actionData["storeid"];
}
if(isset($ical->errors)) {
$response['status'] = false;
$response['message']= $ical->errors;
} else if(!$ical->hasEvents()) {
$response['status'] = false;
$response['message']= "No events in ics file";
} else {
$response['status'] = true;
$response['parsed_file']= $actionData["ics_filepath"];
$response['parsed'] = array (
'timezone' => $ical->timezone(),
'calendar' => $ical->calendar(),
'events' => $ical->events()
// Get folder entryid
$folderid = false;
if (isset($actionData["folderid"])) {
$folderid = $actionData["folderid"];
}
// Get uids
$uids = array();
if (isset($actionData["uids"])) {
$uids = $actionData["uids"];
}
$response = array();
$error = false;
$error_msg = "";
// parse the ics file a last time...
$parser = null;
try {
$parser = VObject\Reader::read(
fopen($icsfile,'r')
);
} catch (Exception $e) {
$error = true;
$error_msg = $e->getMessage();
}
$events = array();
if (count($parser->VEVENT) > 0) {
$events = $this->parseCalendarToArray($parser);
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid));
$folder = mapi_msgstore_openentry($store, hex2bin($folderid));
$importall = false;
if (count($uids) == count($events)) {
$importall = true;
}
$propValuesMAPI = array();
$properties = $GLOBALS['properties']->getAppointmentProperties();
// extend properties...
$properties["body"] = PR_BODY;
$count = 0;
// iterate through all events and import them :)
foreach ($events as $event) {
if (isset($event["startdate"]) && ($importall || in_array($event["internal_fields"]["event_uid"], $uids))) {
$message = mapi_folder_createmessage($folder);
// parse the arraykeys
foreach ($event as $key => $value) {
if ($key !== "internal_fields") {
if(isset($properties[$key])) {
$propValuesMAPI[$properties[$key]] = $value;
}
}
}
$propValuesMAPI[$properties["commonstart"]] = $propValuesMAPI[$properties["startdate"]];
$propValuesMAPI[$properties["commonend"]] = $propValuesMAPI[$properties["duedate"]];
$propValuesMAPI[$properties["duration"]] = ($propValuesMAPI[$properties["duedate"]] - $propValuesMAPI[$properties["startdate"]]) / 60; // Minutes needed
$propValuesMAPI[$properties["reminder"]] = false; // needed, overwritten if there is a timer
$propValuesMAPI[$properties["message_class"]] = "IPM.Appointment";
$propValuesMAPI[$properties["icon_index"]] = "1024";
// TODO: set attendees and alarms
mapi_setprops($message, $propValuesMAPI);
mapi_savechanges($message);
if ($this->DEBUG) {
error_log("New event added: \"" . $event["subject"] . "\".\n");
}
$count++;
}
}
$response['status'] = true;
$response['count'] = $count;
$response['message'] = "";
} else {
$response['status'] = false;
$response['message']= "File could not be read by server";
$response['count'] = 0;
$response['message'] = $error ? $error_msg : "ICS file empty!";
}
$this->addActionData($actionType, $response);
$GLOBALS["bus"]->addData($this->getResponseData());
if($this->DEBUG) {
error_log("parsing done, bus data written!");
}
}
/**
@ -437,7 +465,8 @@ class CalendarModule extends Module {
* @param $actionData
* @private
*/
private function getAttachmentPath($actionType, $actionData) {
private function getAttachmentPath($actionType, $actionData)
{
// Get store id
$storeid = false;
if (isset($actionData["store"])) {
@ -474,8 +503,7 @@ class CalendarModule 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) {
@ -501,11 +529,15 @@ class CalendarModule 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])) {
@ -569,6 +601,129 @@ class CalendarModule extends Module {
$GLOBALS["bus"]->addData($this->getResponseData());
}
}
};
/**
* Function that parses the uploaded ics file and posts it via json
* @param $actionType
* @param $actionData
*/
private function loadCalendar($actionType, $actionData)
{
$error = false;
$error_msg = "";
if (is_readable($actionData["ics_filepath"])) {
$parser = null;
try {
$parser = VObject\Reader::read(
fopen($actionData["ics_filepath"],'r')
);
//error_log(print_r($parser->VTIMEZONE, true));
} catch (Exception $e) {
$error = true;
$error_msg = $e->getMessage();
}
if ($error) {
$response['status'] = false;
$response['message'] = $error_msg;
} else {
if (count($parser->VEVENT) == 0) {
$response['status'] = false;
$response['message'] = "No event in ics file";
} else {
$response['status'] = true;
$response['parsed_file'] = $actionData["ics_filepath"];
$response['parsed'] = array(
'events' => $this->parseCalendarToArray($parser),
'timezone' => isset($parser->VTIMEZONE->TZID) ? (string)$parser->VTIMEZONE->TZID : (string)$parser->{'X-WR-TIMEZONE'},
'calendar' => (string)$parser->PRODID
);
}
}
} else {
$response['status'] = false;
$response['message'] = "File could not be read by server";
}
$this->addActionData($actionType, $response);
$GLOBALS["bus"]->addData($this->getResponseData());
if ($this->DEBUG) {
error_log("parsing done, bus data written!");
}
}
/**
* Create a array with contacts
*
* @param {VObject} $calendar ics parser object
* @return array parsed events
* @private
*/
private function parseCalendarToArray($calendar)
{
$events = array();
foreach ($calendar->VEVENT as $Index => $vEvent) {
// Sabre\VObject\Parser\XML\Element\VEvent
$properties = array();
//uid - used for front/backend communication
$properties["internal_fields"] = array();
$properties["internal_fields"]["event_uid"] = base64_encode($Index . $vEvent->UID);
$properties["startdate"] = (string)$vEvent->DTSTART->getDateTime()->getTimestamp();
$properties["duedate"] = (string)$vEvent->DTEND->getDateTime()->getTimestamp();
$properties["location"] = (string)$vEvent->LOCATION;
$properties["subject"] = (string)$vEvent->SUMMARY;
$properties["body"] = (string)$vEvent->DESCRIPTION;
$properties["comment"] = (string)$vEvent->COMMENT;
$properties["timezone"] = (string)$vEvent->DTSTART["TZID"];
$properties["organizer"] = (string)$vEvent->ORGANIZER;
$properties["busystatus"] = array_search((string)$vEvent->{'X-MICROSOFT-CDO-INTENDEDSTATUS'}, $this->busystates); // X-MICROSOFT-CDO-BUSYSTATUS
$properties["transp"] = (string)$vEvent->TRANSP;
//$properties["trigger"] = (string)$vEvent->COMMENT;
$properties["priority"] = (string)$vEvent->PRIORITY;
$properties["private"] = ((string)$vEvent->CLASS) == "PRIVATE" ? true : false;
if(!empty((string)$vEvent->{'X-ZARAFA-LABEL'})) {
$properties["label"] = array_search((string)$vEvent->{'X-ZARAFA-LABEL'}, $this->labels);
}
$properties["last_modification_time"] = (string)$vEvent->{'LAST-MODIFIED'}->getDateTime()->getTimestamp();
$properties["creation_time"] = (string)$vEvent->CREATED->getDateTime()->getTimestamp();
$properties["rrule"] = (string)$vEvent->RRULE;
// Attendees
$properties["attendees"] = array();
if(isset($vEvent->ATTENDEE) && count($vEvent->ATTENDEE) > 0) {
foreach($vEvent->ATTENDEE as $attendee) {
$properties["attendees"][] = array(
"name" => (string)$attendee["CN"],
"mail" => (string)$attendee,
"status" => (string)$attendee["PARTSTAT"],
"role" => (string)$attendee["ROLE"]
);
}
}
// Alarms
$properties["alarms"] = array();
if(isset($vEvent->VALARM) && count($vEvent->VALARM) > 0) {
foreach($vEvent->VALARM as $alarm) {
$properties["alarms"][] = array(
"description" => (string)$alarm->DESCRIPTION,
"trigger" => (string)$alarm->TRIGGER,
"type" => (string)$alarm->TRIGGER["VALUE"]
);
}
}
array_push($events, $properties);
}
return $events;
}
}
;
?>

View File

@ -19,25 +19,31 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*/
require_once __DIR__ . "/download.php";
/**
* calendarimporter Plugin
*
* With this plugin you can import a ics file to your zarafa calendar
*
*/
class Plugincalendarimporter extends Plugin {
class Plugincalendarimporter extends Plugin
{
/**
* Constructor
*/
function Plugincalendarimporter() {}
function __construct() {}
/**
* 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');
}
/**
@ -47,11 +53,17 @@ class Plugincalendarimporter 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_ics') {
calendarimporter\DownloadHandler::doDownload();
}
break;
}
}
@ -60,14 +72,14 @@ class Plugincalendarimporter 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(
'calendarimporter' => Array(
'enable' => PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE,
'enable_export' => PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE_EXPORT,
'enable_sync' => PLUGIN_CALENDARIMPORTER_USER_DEFAULT_ENABLE_SYNC,
'default_calendar' => PLUGIN_CALENDARIMPORTER_DEFAULT,
'default_timezone' => PLUGIN_CALENDARIMPORTER_DEFAULT_TIMEZONE
@ -78,4 +90,5 @@ class Plugincalendarimporter extends Plugin {
));
}
}
?>

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_calendarimporter_export {
background: url(../images/download.png) no-repeat;
background-repeat: no-repeat;
background-position: center;
}
.icon_calendarimporter_import {
background: url(../images/upload.png) no-repeat;
background-repeat: no-repeat;
background-position: center;
}
.zarafa-caiplg-container {
width: 100%;
height: 50px;
}
.zarafa-caiplg-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.

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.