From 6ad190a3409f7edfc9240a8ca84bf70ddb7e4cda Mon Sep 17 00:00:00 2001
From: diosmosis <benakamoorthi@fastmail.fm>
Date: Fri, 1 Nov 2013 13:21:56 +0000
Subject: [PATCH] Refs #4200, documented ScheduledTime a bit and added a
 factory method that can be used to create ScheduledTime instances w/ one line
 of code.

---
 core/ScheduledTime.php                        |  44 +++++++-
 core/ScheduledTime/Monthly.php                |  27 ++++-
 core/ScheduledTime/Weekly.php                 |  25 +++--
 core/TaskScheduler.php                        |  14 +++
 .../Core/ScheduledTime/MonthlyTest.php        |  81 ++++++++++----
 .../PHPUnit/Core/ScheduledTime/WeeklyTest.php | 100 +++++++-----------
 6 files changed, 198 insertions(+), 93 deletions(-)

diff --git a/core/ScheduledTime.php b/core/ScheduledTime.php
index ec4d3fcab8..54616d276a 100644
--- a/core/ScheduledTime.php
+++ b/core/ScheduledTime.php
@@ -12,6 +12,7 @@
 namespace Piwik;
 
 use Exception;
+use Piwik\ScheduledTime\Hourly;
 use Piwik\ScheduledTime\Daily;
 use Piwik\ScheduledTime\Monthly;
 use Piwik\ScheduledTime\Weekly;
@@ -127,4 +128,45 @@ abstract class ScheduledTime
         }
         return $rescheduledTime;
     }
-}
+
+    /**
+     * Returns a new ScheduledTime instance using a string description of the scheduled period type
+     * and a string description of the day within the period to execute the task on.
+     * 
+     * @param string $periodType The scheduled period type. Can be `'hourly'`, `'daily'`, `'weekly'`,
+     *                           or `'monthly'`.
+     * @param string|int|false $periodDay A string describing the day within the scheduled period to execute
+     *                                    the task on. Only valid for week and month periods.
+     *                               
+     *                                    If `'weekly'` is supplied for `$periodType`, this should be a day
+     *                                    of the week, for example, `'monday'` or `'tuesday'`.
+     * 
+     *                                    If `'monthly'` is supplied for `$periodType`, this can be a numeric
+     *                                    day in the month or a day in one week of the month. For example,
+     *                                    `12`, `23`, `'first sunday'` or `'fourth tuesday'`.
+     */
+    public static function factory($periodType, $periodDay = false)
+    {
+        switch ($periodType) {
+            case 'hourly':
+                return new Hourly();
+            case 'daily':
+                return new Daily();
+            case 'weekly':
+                $result = new Weekly();
+                $result->setDay($periodDay);
+                return $result;
+            case 'monthly':
+                $result = new Monthly($periodDay);
+                if (is_int($periodDay)) {
+                    $result->setDay($periodDay);
+                } else {
+                    $result->setDayOfWeekFromString($periodDay);
+                }
+                return $result;
+            default:
+                throw new Exception("Unsupported scheduled period type: '$periodType'. Supported values are"
+                                  . " 'hourly', 'daily', 'weekly' or 'monthly'.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/ScheduledTime/Monthly.php b/core/ScheduledTime/Monthly.php
index 58f2b86b78..b7a5d5c709 100644
--- a/core/ScheduledTime/Monthly.php
+++ b/core/ScheduledTime/Monthly.php
@@ -24,6 +24,11 @@ use Piwik\ScheduledTime;
  */
 class Monthly extends ScheduledTime
 {
+    /**
+     * List of available week number strings used in setDayOfWeekFromString.
+     */
+    private static $weekNumberStringToInt = array('first' => 0, 'second' => 1, 'third' => 2, 'fourth' => 3);
+
     /**
      * Day of the week for scheduled time.
      *
@@ -38,6 +43,26 @@ class Monthly extends ScheduledTime
      */
     private $week = null;
 
+    public function setDayOfWeekFromString($day)
+    {
+        @list($weekNumberString, $dayNumberString) = explode(' ', $day);
+
+        // get day number
+        $day = Weekly::getDayIntFromString($dayNumberString) % 7;
+
+        // get week number
+        $week = false;
+        $weekNumberString = strtolower($weekNumberString);
+        if (isset(self::$weekNumberStringToInt[$weekNumberString])) {
+            $week = self::$weekNumberStringToInt[$weekNumberString];
+        } else {
+            throw new Exception("Invalid week describer in ScheduledTime\\Monthly::setDayOfWeekFromString: '$weekNumberString'. "
+                              . "Supported values are 'first', 'second', 'third', 'fourth'.");
+        }
+
+        $this->setDayOfWeek($day, $week);
+    }
+
     /**
      * @return int
      */
@@ -121,4 +146,4 @@ class Monthly extends ScheduledTime
         $this->dayOfWeek = $_day;
         $this->week = $_week;
     }
-}
+}
\ No newline at end of file
diff --git a/core/ScheduledTime/Weekly.php b/core/ScheduledTime/Weekly.php
index 14910b4c3b..7bb3fea50f 100644
--- a/core/ScheduledTime/Weekly.php
+++ b/core/ScheduledTime/Weekly.php
@@ -24,7 +24,6 @@ use Piwik\ScheduledTime;
  */
 class Weekly extends ScheduledTime
 {
-
     /**
      * @see ScheduledTime::getRescheduledTime
      * @return int
@@ -61,15 +60,29 @@ class Weekly extends ScheduledTime
     }
 
     /**
-     * @param int $_day the day to set, has to be >= 1 and < 8
+     * @param int $day the day to set, has to be >= 1 and < 8
      * @throws Exception if parameter _day is invalid
      */
-    public function setDay($_day)
+    public function setDay($day)
     {
-        if (!($_day >= 1 && $_day < 8)) {
+        if (!is_int($day)) {
+            $day = self::getDayIntFromString($day);
+        }
+
+        if (!($day >= 1 && $day < 8)) {
             throw new Exception ("Invalid day parameter, must be >=1 and < 8");
         }
 
-        $this->day = $_day;
+        $this->day = $day;
+    }
+
+    public static function getDayIntFromString($dayString)
+    {
+        $time = strtotime($dayString);
+        if ($time === false) {
+            throw new Exception("Invalid day string '$dayString'. Must be 'monday', 'tuesday', etc.");
+        }
+
+        return date("N", $time);
     }
-}
+}
\ No newline at end of file
diff --git a/core/TaskScheduler.php b/core/TaskScheduler.php
index 4ab6d0122a..d474206b0e 100644
--- a/core/TaskScheduler.php
+++ b/core/TaskScheduler.php
@@ -25,6 +25,20 @@ define('DEBUG_FORCE_SCHEDULED_TASKS', false);
  * 
  * Tasks are executed when the cron archive.php script is executed.
  * 
+ * ### Scheduling Tasks
+ * 
+ * **TODO**
+ * 
+ * ### Examples
+ * 
+ * **Scheduling a task**
+ * 
+ *     // TODO
+ * 
+ * **Executing all pending tasks**
+ * 
+ *     // TODO
+ * 
  * @package Piwik
  * @api
  */
diff --git a/tests/PHPUnit/Core/ScheduledTime/MonthlyTest.php b/tests/PHPUnit/Core/ScheduledTime/MonthlyTest.php
index c13a7d825d..a58e7f44a4 100644
--- a/tests/PHPUnit/Core/ScheduledTime/MonthlyTest.php
+++ b/tests/PHPUnit/Core/ScheduledTime/MonthlyTest.php
@@ -9,28 +9,19 @@ use Piwik\ScheduledTime\Monthly;
 
 class ScheduledTime_MonthlyTest extends PHPUnit_Framework_TestCase
 {
-    private static $_JANUARY_01_1971_09_00_00;
-    private static $_JANUARY_02_1971_09_00_00;
-    private static $_JANUARY_05_1971_09_00_00;
-    private static $_JANUARY_15_1971_09_00_00;
-    private static $_FEBRUARY_01_1971_00_00_00;
-    private static $_FEBRUARY_02_1971_00_00_00;
-    private static $_FEBRUARY_03_1971_09_00_00;
-    private static $_FEBRUARY_21_1971_09_00_00;
-    private static $_FEBRUARY_28_1971_00_00_00;
+    public static $_JANUARY_01_1971_09_00_00; // initialized below class definition
+    public static $_JANUARY_02_1971_09_00_00;
+    public static $_JANUARY_05_1971_09_00_00;
+    public static $_JANUARY_15_1971_09_00_00;
+    public static $_FEBRUARY_01_1971_00_00_00;
+    public static $_FEBRUARY_02_1971_00_00_00;
+    public static $_FEBRUARY_03_1971_09_00_00;
+    public static $_FEBRUARY_21_1971_09_00_00;
+    public static $_FEBRUARY_28_1971_00_00_00;
 
     public static function setUpBeforeClass()
     {
         parent::setUpBeforeClass();
-        self::$_JANUARY_01_1971_09_00_00 = mktime(9, 00, 00, 1, 1, 1971);
-        self::$_JANUARY_02_1971_09_00_00 = mktime(9, 00, 00, 1, 2, 1971);
-        self::$_JANUARY_05_1971_09_00_00 = mktime(9, 00, 00, 1, 5, 1971);
-        self::$_JANUARY_15_1971_09_00_00 = mktime(9, 00, 00, 1, 15, 1971);
-        self::$_FEBRUARY_01_1971_00_00_00 = mktime(0, 00, 00, 2, 1, 1971);
-        self::$_FEBRUARY_02_1971_00_00_00 = mktime(0, 00, 00, 2, 2, 1971);
-        self::$_FEBRUARY_03_1971_09_00_00 = mktime(0, 00, 00, 2, 3, 1971);
-        self::$_FEBRUARY_21_1971_09_00_00 = mktime(0, 00, 00, 2, 21, 1971);
-        self::$_FEBRUARY_28_1971_00_00_00 = mktime(0, 00, 00, 2, 28, 1971);
     }
 
     /**
@@ -212,19 +203,53 @@ class ScheduledTime_MonthlyTest extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * Returns the data used to test the setDayOfWeek method.
+     */
+    public function getValuesToTestSetDayOfWeek()
+    {
+        return array(
+            array(3, 0, self::$_FEBRUARY_03_1971_09_00_00),
+            array(0, 2, self::$_FEBRUARY_21_1971_09_00_00),
+        );
+    }
+
+    /**
+     * Returns the data used to test the setDayOfWeekFromString method.
+     */
+    public function getValuesToTestSetDayOfWeekByString()
+    {
+        return array(
+            array('first wednesday', self::$_FEBRUARY_03_1971_09_00_00),
+            array('ThIrD sUnDaY', self::$_FEBRUARY_21_1971_09_00_00)
+        );
+    }
+
+    /**
+     * @dataProvider getValuesToTestSetDayOfWeek
      * @group Core
      */
-    public function testMonthlyDayOfWeek()
+    public function testMonthlyDayOfWeek($day, $week, $expectedTime)
     {
         $mock = $this->getMock('\Piwik\ScheduledTime\Monthly', array('getTime'));
         $mock->expects($this->any())
             ->method('getTime')
             ->will($this->returnValue(self::$_JANUARY_15_1971_09_00_00));
-        $mock->setDayOfWeek(3, 0); // first wednesday
-        $this->assertEquals(self::$_FEBRUARY_03_1971_09_00_00, $mock->getRescheduledTime());
+        $mock->setDayOfWeek($day, $week);
+        $this->assertEquals($expectedTime, $mock->getRescheduledTime());
+    }
 
-        $mock->setDayOfWeek(0, 2); // third sunday
-        $this->assertEquals(self::$_FEBRUARY_21_1971_09_00_00, $mock->getRescheduledTime());
+    /**
+     * @dataProvider getValuesToTestSetDayOfWeekByString
+     * @group Core
+     */
+    public function testMonthlyDayOfWeekByString($dayOfWeekStr, $expectedTime)
+    {
+        $mock = $this->getMock('\Piwik\ScheduledTime\Monthly', array('getTime'));
+        $mock->expects($this->any())
+            ->method('getTime')
+            ->will($this->returnValue(self::$_JANUARY_15_1971_09_00_00));
+        $mock->setDayOfWeekFromString($dayOfWeekStr);
+        $this->assertEquals($expectedTime, $mock->getRescheduledTime());
     }
 
     /**
@@ -266,3 +291,13 @@ class ScheduledTime_MonthlyTest extends PHPUnit_Framework_TestCase
         );
     }
 }
+
+ScheduledTime_MonthlyTest::$_JANUARY_01_1971_09_00_00 = mktime(9, 00, 00, 1, 1, 1971);
+ScheduledTime_MonthlyTest::$_JANUARY_02_1971_09_00_00 = mktime(9, 00, 00, 1, 2, 1971);
+ScheduledTime_MonthlyTest::$_JANUARY_05_1971_09_00_00 = mktime(9, 00, 00, 1, 5, 1971);
+ScheduledTime_MonthlyTest::$_JANUARY_15_1971_09_00_00 = mktime(9, 00, 00, 1, 15, 1971);
+ScheduledTime_MonthlyTest::$_FEBRUARY_01_1971_00_00_00 = mktime(0, 00, 00, 2, 1, 1971);
+ScheduledTime_MonthlyTest::$_FEBRUARY_02_1971_00_00_00 = mktime(0, 00, 00, 2, 2, 1971);
+ScheduledTime_MonthlyTest::$_FEBRUARY_03_1971_09_00_00 = mktime(0, 00, 00, 2, 3, 1971);
+ScheduledTime_MonthlyTest::$_FEBRUARY_21_1971_09_00_00 = mktime(0, 00, 00, 2, 21, 1971);
+ScheduledTime_MonthlyTest::$_FEBRUARY_28_1971_00_00_00 = mktime(0, 00, 00, 2, 28, 1971);
\ No newline at end of file
diff --git a/tests/PHPUnit/Core/ScheduledTime/WeeklyTest.php b/tests/PHPUnit/Core/ScheduledTime/WeeklyTest.php
index 4eb5a96e41..e13bf0ab04 100644
--- a/tests/PHPUnit/Core/ScheduledTime/WeeklyTest.php
+++ b/tests/PHPUnit/Core/ScheduledTime/WeeklyTest.php
@@ -9,22 +9,16 @@ use Piwik\ScheduledTime\Weekly;
 
 class ScheduledTime_WeeklyTest extends PHPUnit_Framework_TestCase
 {
-    private static $_JANUARY_01_1971_09_10_00;
-    private static $_JANUARY_04_1971_00_00_00;
-    private static $_JANUARY_04_1971_09_00_00;
-    private static $_JANUARY_05_1971_09_00_00;
-    private static $_JANUARY_11_1971_00_00_00;
-    private static $_JANUARY_15_1971_00_00_00;
+    public static $_JANUARY_01_1971_09_10_00; // initialized below class declaration
+    public static $_JANUARY_04_1971_00_00_00;
+    public static $_JANUARY_04_1971_09_00_00;
+    public static $_JANUARY_05_1971_09_00_00;
+    public static $_JANUARY_11_1971_00_00_00;
+    public static $_JANUARY_15_1971_00_00_00;
 
     public static function setUpBeforeClass()
     {
         parent::setUpBeforeClass();
-        self::$_JANUARY_01_1971_09_10_00 = mktime(9, 10, 00, 1, 1, 1971);
-        self::$_JANUARY_04_1971_00_00_00 = mktime(0, 00, 00, 1, 4, 1971);
-        self::$_JANUARY_04_1971_09_00_00 = mktime(9, 00, 00, 1, 4, 1971);
-        self::$_JANUARY_05_1971_09_00_00 = mktime(9, 00, 00, 1, 5, 1971);
-        self::$_JANUARY_11_1971_00_00_00 = mktime(0, 00, 00, 1, 11, 1971);
-        self::$_JANUARY_15_1971_00_00_00 = mktime(0, 00, 00, 1, 15, 1971);
     }
 
     /**
@@ -136,64 +130,46 @@ class ScheduledTime_WeeklyTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(self::$_JANUARY_04_1971_09_00_00, $mock->getRescheduledTime());
     }
 
+    /**
+     * Returns data used in testGetRescheduledTimeWeeklyUnspecifiedHourSpecifiedDay test.
+     */
+    public function getSetDayParametersToTest()
+    {
+        return array(
+            array(1, self::$_JANUARY_11_1971_00_00_00),
+            array(5, self::$_JANUARY_15_1971_00_00_00),
+            array('monday', self::$_JANUARY_11_1971_00_00_00),
+            array('Monday', self::$_JANUARY_11_1971_00_00_00),
+            array('FRIDAY', self::$_JANUARY_15_1971_00_00_00),
+            array('FrIdAy', self::$_JANUARY_15_1971_00_00_00)
+        );
+    }
+
     /**
      * Tests getRescheduledTime on Weekly with unspecified hour and specified day
+     * 
+     * Context :
+     *  - getRescheduledTime called Monday January 4 1971 09:00:00 UTC
+     *  - setHour is not called, defaulting to midnight
+     *  - setDay is set to $dayToSet
+     * 
      * @group Core
+     * @dataProvider getSetDayParametersToTest
      */
-    public function testGetRescheduledTimeWeeklyUnspecifiedHourSpecifiedDay()
+    public function testGetRescheduledTimeWeeklyUnspecifiedHourSpecifiedDay($dayToSet, $expectedRescheduledTime)
     {
-        /*
-         * Test 1
-         *
-         * Context :
-         *  - getRescheduledTime called Monday January 4 1971 09:00:00 UTC
-         *  - setHour is not called, defaulting to midnight
-         *  - setDay is set to 1, Monday
-         *
-         * Expected :
-         *  getRescheduledTime returns Monday January 11 1971 00:00:00 UTC
-         */
         $mock = $this->getMock('\Piwik\ScheduledTime\Weekly', array('getTime'));
         $mock->expects($this->any())
             ->method('getTime')
             ->will($this->returnValue(self::$_JANUARY_04_1971_09_00_00));
-        $mock->setDay(1);
-        $this->assertEquals(self::$_JANUARY_11_1971_00_00_00, $mock->getRescheduledTime());
-
-        /*
-         * Test 2
-         *
-         * Context :
-         *  - getRescheduledTime called Tuesday 5 1971 09:00:00 UTC
-         *  - setHour is not called, defaulting to midnight
-         *  - setDay is set to 1, Monday
-         *
-         * Expected :
-         *  getRescheduledTime returns Monday January 11 1971 00:00:00 UTC
-         */
-        $mock = $this->getMock('\Piwik\ScheduledTime\Weekly', array('getTime'));
-        $mock->expects($this->any())
-            ->method('getTime')
-            ->will($this->returnValue(self::$_JANUARY_05_1971_09_00_00));
-        $mock->setDay(1);
-        $this->assertEquals(self::$_JANUARY_11_1971_00_00_00, $mock->getRescheduledTime());
-
-        /*
-         * Test 3
-         *
-         * Context :
-         *  - getRescheduledTime called Monday January 4 1971 09:00:00 UTC
-         *  - setHour is not called, defaulting to midnight
-         *  - setDay is set to 1, Friday
-         *
-         * Expected :
-         *  getRescheduledTime returns Friday January 15 1971 00:00:00 UTC
-         */
-        $mock = $this->getMock('\Piwik\ScheduledTime\Weekly', array('getTime'));
-        $mock->expects($this->any())
-            ->method('getTime')
-            ->will($this->returnValue(self::$_JANUARY_04_1971_09_00_00));
-        $mock->setDay(5);
-        $this->assertEquals(self::$_JANUARY_15_1971_00_00_00, $mock->getRescheduledTime());
+        $mock->setDay($dayToSet);
+        $this->assertEquals($expectedRescheduledTime, $mock->getRescheduledTime());
     }
 }
+
+ScheduledTime_WeeklyTest::$_JANUARY_01_1971_09_10_00 = mktime(9, 10, 00, 1, 1, 1971);
+ScheduledTime_WeeklyTest::$_JANUARY_04_1971_00_00_00 = mktime(0, 00, 00, 1, 4, 1971);
+ScheduledTime_WeeklyTest::$_JANUARY_04_1971_09_00_00 = mktime(9, 00, 00, 1, 4, 1971);
+ScheduledTime_WeeklyTest::$_JANUARY_05_1971_09_00_00 = mktime(9, 00, 00, 1, 5, 1971);
+ScheduledTime_WeeklyTest::$_JANUARY_11_1971_00_00_00 = mktime(0, 00, 00, 1, 11, 1971);
+ScheduledTime_WeeklyTest::$_JANUARY_15_1971_00_00_00 = mktime(0, 00, 00, 1, 15, 1971);
\ No newline at end of file
-- 
GitLab