0012290: ics import: support deletion of no longer existing events
authorCornelius Weiß <c.weiss@metaways.de>
Thu, 3 Nov 2016 13:09:44 +0000 (14:09 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Fri, 4 Nov 2016 11:17:19 +0000 (12:17 +0100)
* new import option "deleteMissing"
* delete all future events in the container which are not found in the
  import ics

Change-Id: I92855bba8da0a524cc02b0260bd1324b50f46f06
Reviewed-on: http://gerrit.tine20.com/customers/3724
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Calendar/Import/ICalTest.php
tests/tine20/Calendar/Import/files/current.ics.twig [new file with mode: 0644]
tine20/Calendar/Import/Ical.php
tine20/composer.json
tine20/composer.lock

index 6a3c846..e6b0872 100644 (file)
@@ -156,14 +156,15 @@ class Calendar_Import_ICalTest extends Calendar_TestCase
      * @param boolean $fromString
      * @return Tinebase_Record_RecordSet
      */
-    protected function _importHelper($filename, $expectedNumber = 1, $fromString = false, $additionalOptions = array())
+    protected function _importHelper($filename, $expectedNumber = 1, $fromString = false, $additionalOptions = array(), $failureMessage = 'Events were not imported')
     {
         $importer = new Calendar_Import_Ical(array_merge($additionalOptions,array(
             'container_id' => $this->_getTestCalendar()->getId(),
         )));
         
         if ($fromString) {
-            $icalData = file_get_contents(dirname(__FILE__) . '/files/' . $filename);
+            $path = __DIR__ . '/files/' . $filename;
+            $icalData = file_exists($path) ? file_get_contents($path) : $filename;
             $importer->importData($icalData);
         } else {
             $importer->importFile(dirname(__FILE__) . '/files/' . $filename);
@@ -173,7 +174,7 @@ class Calendar_Import_ICalTest extends Calendar_TestCase
             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_getTestCalendar()->getId())
         )), NULL);
         
-        $this->assertEquals($expectedNumber, $events->count(), 'Events were not imported');
+        $this->assertEquals($expectedNumber, $events->count(), $failureMessage);
         return $events;
     }
     
@@ -290,4 +291,18 @@ class Calendar_Import_ICalTest extends Calendar_TestCase
             $this->assertTrue(empty($event->alarms), 'field ' . $field . ' should be empty in event ' . print_r($event->toArray(), true));
         }
     }
+
+    public function testImportDeleteMissing()
+    {
+        $loader = new Twig_Loader_Filesystem(__DIR__ . '/files');
+        $twig = new Twig_Environment($loader);
+        $icalData = $twig->render('current.ics.twig');
+
+        $this->_importHelper($icalData, 4, true);
+
+        $icalData = $twig->render('current.ics.twig', array('excludes' => array(2, 3)));
+
+        // NOTE: only future event shold be deleted
+        $this->_importHelper($icalData, 3, true, array('deleteMissing' => true), 'missing event was not deleted');
+    }
 }
diff --git a/tests/tine20/Calendar/Import/files/current.ics.twig b/tests/tine20/Calendar/Import/files/current.ics.twig
new file mode 100644 (file)
index 0000000..b3bba6b
--- /dev/null
@@ -0,0 +1,44 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//tine20.com//Tine 2.0 Calendar V8.4//EN
+{% if 1 not in excludes %}
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTEND:{{ "-1 week 12:00"|date("Ymd\\THis\\Z", "UTC") }}
+DTSTART:{{ "-1 week 10:00"|date("Ymd\\THis\\Z", "UTC") }}
+SEQUENCE:0
+SUMMARY:-1 week 10:00
+UID:current-import-test-1
+END:VEVENT
+{% endif %}
+{% if 2 not in excludes %}
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTEND:{{ "-2 days 12:00"|date("Ymd\\THis\\Z", "UTC") }}
+DTSTART:{{ "-2 days 10:00"|date("Ymd\\THis\\Z", "UTC") }}
+SEQUENCE:0
+SUMMARY:-2 days 10:00
+UID:current-import-test-2
+END:VEVENT
+{% endif %}
+{% if 3 not in excludes %}
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTEND:{{ "+2 days 12:00"|date("Ymd\\THis\\Z", "UTC") }}
+DTSTART:{{ "+2 days 10:00"|date("Ymd\\THis\\Z", "UTC") }}
+SEQUENCE:0
+SUMMARY:+2 days 10:00
+UID:current-import-test-3
+END:VEVENT
+{% endif %}
+{% if 4 not in excludes %}
+BEGIN:VEVENT
+CLASS:PUBLIC
+DTEND:{{ "+1 week 12:00"|date("Ymd\\THis\\Z", "UTC") }}
+DTSTART:{{ "+1 week 10:00"|date("Ymd\\THis\\Z", "UTC") }}
+SEQUENCE:0
+SUMMARY:+2 days 10:00
+UID:current-import-test-4
+END:VEVENT
+{% endif %}
+END:VCALENDAR
\ No newline at end of file
index 97ec459..8cb3f81 100644 (file)
@@ -38,6 +38,10 @@ class Calendar_Import_Ical extends Tinebase_Import_Abstract
          */
         'forceUpdateExisting'   => FALSE,
         /**
+         * delete events missing in import file (future only)
+         */
+        'deleteMissing'         => FALSE,
+        /**
          * container the events should be imported in
          * @var string
          */
@@ -57,30 +61,6 @@ class Calendar_Import_Ical extends Tinebase_Import_Abstract
     );
     
     /**
-     * default timezone from VCALENDAR. If not present, users default tz will be taken
-     * @var string
-     */
-    protected $_defaultTimezoneId;
-    
-    /**
-     * maps tine20 propertynames to ical propertynames
-     * @var array
-     */
-    protected $_eventPropertyMap = array(
-        'summary'               => 'SUMMARY',
-        'description'           => 'DESCRIPTION',
-        'class'                 => 'CLASS',
-        'transp'                => 'TRANSP',
-        'seq'                   => 'SEQUENCE',
-        'uid'                   => 'UID',
-        'dtstart'               => 'DTSTART',
-        'dtend'                 => 'DTEND',
-        'rrule'                 => 'RRULE',
-        'creation_time'         => 'CREATED',
-        'last_modified_time'    => 'LAST-MODIFIED',
-    );
-    
-    /**
      * creates a new importer from an importexport definition
      * 
      * @param  Tinebase_Model_ImportExportDefinition $_definition
@@ -203,6 +183,21 @@ class Calendar_Import_Ical extends Tinebase_Import_Abstract
                 $this->_importResult['failcount'] += 1;
             }
         }
+
+        if ($this->_options['deleteMissing']) {
+            $missingEventsFilter = new Calendar_Model_EventFilter(array(
+                array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_options['container_id']),
+                array('field' => 'uid', 'operator' => 'notin', 'value' => array_unique($events->uid)),
+                array('field' => 'period', 'operator' => 'within', 'value' => array(
+                    'from'  => new Tinebase_DateTime('now'),
+                    'until' => new Tinebase_DateTime('+ 100 years'),
+                ))
+            ));
+            $missingEvents = $cc->search($missingEventsFilter);
+
+            $cc->delete($missingEvents->id);
+        }
+
         Calendar_Controller_Event::getInstance()->sendNotifications($sendNotifications);
         
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__ . ' '
index f8eb1bb..3b12ab6 100644 (file)
@@ -30,7 +30,8 @@
         "sabre/vobject": "3.1.3 as 2.1.313",
         "metaways/opendocument": "1.1",
         "metaways/timezoneconvert": "0.2",
-        "zendframework/zend-http": "2.2.8-p3 as 2.2.8"
+        "zendframework/zend-http": "2.2.8-p3 as 2.2.8",
+        "twig/twig": "~1.0"
     },
     "require-dev": {
         "phpunit/phpunit": "3.7.*",
index 44c2994..3e3ad8b 100644 (file)
@@ -4,8 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "8ea441d7e4b25356003f60fc1227c96e",
-    "content-hash": "57a2ee22474a5fa88186da5eb49d49e3",
+    "content-hash": "cedc515f4cee5aedf2ae9b67e20b3d26",
     "packages": [
         {
             "name": "codeplex/phpexcel",
             "time": "2016-10-31 16:16:43"
         },
         {
+            "name": "twig/twig",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/twigphp/Twig.git",
+                "reference": "3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97",
+                "reference": "3c6c0033fd3b5679c6e1cb60f4f9766c2b424d97",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.7"
+            },
+            "require-dev": {
+                "symfony/debug": "~2.7",
+                "symfony/phpunit-bridge": "~2.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.27-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Twig_": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Armin Ronacher",
+                    "email": "armin.ronacher@active-4.com",
+                    "role": "Project Founder"
+                },
+                {
+                    "name": "Twig Team",
+                    "homepage": "http://twig.sensiolabs.org/contributors",
+                    "role": "Contributors"
+                }
+            ],
+            "description": "Twig, the flexible, fast, and secure template language for PHP",
+            "homepage": "http://twig.sensiolabs.org",
+            "keywords": [
+                "templating"
+            ],
+            "time": "2016-10-25 19:17:17"
+        },
+        {
             "name": "zendframework/zend-escaper",
             "version": "2.2.10",
             "source": {