Initiates reminders.
authorStéphane Jacob <jacou@melix.net>
Sat, 2 May 2009 13:13:02 +0000 (15:13 +0200)
committerStéphane Jacob <jacou@melix.net>
Fri, 8 May 2009 19:43:59 +0000 (21:43 +0200)
htdocs/xorg.php
include/reminder.inc.php [new file with mode: 0644]
include/reminder/email_warning.inc.php [new file with mode: 0644]
modules/events.php
modules/reminder.php [new file with mode: 0644]
templates/events/index.tpl
templates/reminder/default.tpl [new file with mode: 0644]
upgrade/0.10.1/00_inscription.sql [new file with mode: 0644]

index ef51214..c177d1c 100644 (file)
@@ -25,7 +25,7 @@ $platal = new Xorg('auth', 'carnet', 'email', 'events', 'forums',
                    'geoloc', 'lists', 'marketing', 'payment', 'platal',
                    'profile', 'register', 'search', 'stats', 'admin',
                    'newsletter', 'axletter', 'bandeau', 'survey',
-                   'gadgets', 'googleapps', 'poison', 'openid');
+                   'gadgets', 'googleapps', 'poison', 'openid', 'reminder');
 
 if (!($path = Env::v('n')) || ($path{0} < 'A' || $path{0} > 'Z')) {
     $platal->run();
diff --git a/include/reminder.inc.php b/include/reminder.inc.php
new file mode 100644 (file)
index 0000000..b689656
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2009 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program 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 General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+// Base class for a reminder; it offers the factory for creating valid reminders
+// tailored for a given user, as well as base methods for reminder impls.
+// Sub-classes should define at least the abstract methods, and the static
+// IsCandidate method (prototype: (User &$user)).
+//
+// Usage:
+//   // Instantiates and returns a valid Reminder object for the user.
+//   $reminder = Reminder::GetCandidateReminder($user);
+//
+//   // Returns the named Reminder object.
+//   $reminder = Reminder::GetByName($user, 'ax_letter');
+abstract class Reminder
+{
+    // Details about the reminder.
+    public $name;
+    protected $type_id;
+    protected $weight;
+    protected $remind_delay_yes;
+    protected $remind_delay_no;
+    protected $remind_delay_dismiss;
+
+    // Details about the user.
+    protected $user;
+    protected $current_status;
+    protected $last_ask;
+
+    // Constructs the Reminder object from a mandatory User instance, a list of
+    // key-value pairs from the `reminder_type` and `reminder` tables.
+    function __construct(User &$user, array $type)
+    {
+        $this->user = &$user;
+
+        $this->type_id              = $type['type_id'];
+        $this->name                 = $type['name'];
+        $this->weight               = $type['weight'];
+        $this->remind_delay_yes     = $type['remind_delay_yes'];
+        $this->remind_delay_no      = $type['remind_delay_no'];
+        $this->remind_delay_dismiss = $type['remind_delay_dismiss'];
+
+        if (isset($type['status'])) {
+            $this->current_status = $type['status'];
+        }
+        if (isset($type['remind_last'])) {
+            $this->last_ask = $type['remind_last'];
+        }
+    }
+
+    // Updates (or creates) the reminder line for the pair (|user|, |reminder_id|)
+    // using the |status| as status, and the |next_ask| as the delay between now
+    // and the next ask (if any).
+    private function UpdateStatus($status, $next_ask)
+    {
+        XDB::execute('REPLACE INTO  reminder
+                               SET  uid = {?}, type_id = {?}, status = {?},
+                                    remind_last = NOW(), remind_next = FROM_UNIXTIME({?})',
+                     $this->user->id(), $this->type_id, $status,
+                     ($next_ask > 0 ? time() + $next_ask * 24 * 60 * 60 : null));
+    }
+
+    // Updates the status of the reminder for the current user.
+    protected function UpdateOnYes()
+    {
+        $this->UpdateStatus('yes', $this->remind_delay_yes);
+    }
+    protected function UpdateOnNo()
+    {
+        $this->UpdateStatus('no', $this->remind_delay_no);
+    }
+    protected function UpdateOnDismiss()
+    {
+        $this->UpdateStatus('dismiss', $this->remind_delay_dismiss);
+    }
+
+    // Display and http handling helpers --------------------------------------
+
+    // Handles a hit on the reminder onebox (for links made using the GetBaseUrl
+    // method below).
+    abstract public function HandleAction($action);
+
+    // Returns the content of the onebox reminder. Default implementation displays
+    // a text and three links (yes, no, dismiss); it uses the text from method
+    // GetDisplayText.
+    public function Display(&$page)
+    {
+        header('Content-Type: text/html; charset=utf-8');
+        $page->changeTpl('reminder/default.tpl', NO_SKIN);
+        $page->assign('text', $this->GetDisplayText());
+        $page->assign('baseurl', $this->GetBaseUrl());
+    }
+
+    // Helper for returning the content as a string, instead of using the existing
+    // globale XorgPage instance.
+    public function GetDisplayAsString()
+    {
+        $page = new XorgPage();
+        $this->Display($page);
+        return $page->raw();
+    }
+
+    // Returns the text to display in the onebox.
+    abstract protected function GetDisplayText();
+
+    // Returns the base url for the reminder module.
+    protected function GetBaseUrl()
+    {
+        return 'ajax/reminder/' . $this->name;
+    }
+
+    // Static factories -------------------------------------------------------
+
+    // Returns a chosen class using the user data from |user|, and from the database.
+    public static function GetCandidateReminder(User &$user)
+    {
+        $res = XDB::query('SELECT  rt.*, r.status, r.remind_last
+                             FROM  reminder_type AS rt
+                        LEFT JOIN  reminder      AS r ON (rt.type_id = r.type_id AND r.uid = {?})
+                            WHERE  r.uid IS NULL OR r.remind_next < NOW()
+                         ORDER BY  RAND()',
+                          $user->id());
+
+        $candidates  = $res->fetchAllAssoc();
+        $priority    = rand(1, 100);
+        while (count($candidates) > 0 && $priority > 0) {
+            foreach ($candidates as $key => $candidate) {
+                if ($candidate['weight'] > $priority) {
+                    $class = self::GetClassName($candidate['name']);
+                    if ($class && call_user_func(array($class, 'IsCandidate'), $user)) {
+                        return new $class($user, $candidate);
+                    }
+                    unset($candidates[$key]);
+                }
+            }
+            $priority = (int) ($priority / 2);
+        }
+
+        return null;
+    }
+
+    // Returns an instantiation of the reminder class which name is |name|, using
+    // user data from |user|, and from the database.
+    public static function GetByName(User &$user, $name)
+    {
+        if (!($class = self::GetClassName($name))) {
+            return null;
+        }
+
+        $res = XDB::query('SELECT  rt.*, r.status, r.remind_last
+                             FROM  reminder_type AS rt
+                        LEFT JOIN  reminder      AS r ON (rt.type_id = r.type_id AND r.uid = {?})
+                            WHERE  rt.name = {?}',
+                          $user->id(), $name);
+        if ($res->numRows() > 0) {
+            return new $class($user, $res->fetchOneAssoc());
+        }
+
+        return null;
+    }
+
+    // Computes the name of the class for reminder named |name|, and preloads
+    // the class.
+    private static function GetClassName($name)
+    {
+        @require_once "reminder/$name.inc.php";
+        $class = 'Reminder' . str_replace(' ', '', ucwords(str_replace('_', ' ', $name)));
+        return (class_exists($class) ? $class : null);
+    }
+}
+
+
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
+?>
diff --git a/include/reminder/email_warning.inc.php b/include/reminder/email_warning.inc.php
new file mode 100644 (file)
index 0000000..886ada8
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2009 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program 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 General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
+?>
index 81dddc4..c614dc6 100644 (file)
@@ -106,6 +106,12 @@ class EventsModule extends PLModule
         $page->addJsLink('ajax.js');
         $page->assign('tips', $this->get_tips());
 
+        require_once 'reminder.inc.php';
+        $user = S::user();
+        if (($new_reminder = Reminder::GetCandidateReminder($user))) {
+            $page->assign('reminder', $new_reminder->GetDisplayAsString());
+        }
+
         // Profile update (appears when profile is > 400d old), and birthday
         // oneboxes.
         $res = XDB::query(
diff --git a/modules/reminder.php b/modules/reminder.php
new file mode 100644 (file)
index 0000000..d150f58
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2009 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program 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 General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+class ReminderModule extends PLModule
+{
+    function handlers()
+    {
+        return array(
+            'ajax/reminder' => $this->make_hook('reminder', AUTH_COOKIE),
+        );
+    }
+
+    function handler_reminder(&$page, $reminder_name = null, $action = null)
+    {
+        require_once 'reminder.inc.php';
+        $user = S::user();
+
+        // If no reminder name was passed, or if we don't know that reminder name,
+        // just drop the request.
+        if (!$reminder_name ||
+            !($reminder = Reminder::GetByName($user, $reminder_name))) {
+            return PL_NOT_FOUND;
+        }
+
+        // Otherwise, the request is dispatched, and a new reminder, if any, is
+        // displayed.
+        $reminder->HandleAction($action);
+
+        if ($new_reminder = Reminder::GetCandidateReminder($user)) {
+            $new_reminder->Display($page);
+        } else {
+            exit;
+        }
+    }
+}
+
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
+?>
index 53d6640..03f96dd 100644 (file)
@@ -41,6 +41,10 @@ Bienvenue {$smarty.session.prenom}{if $birthday}
 </div>
 {/if}
 
+{if $reminder}
+{$reminder|smarty:nodefaults}
+{/if}
+
 {if $smarty.session.no_redirect}
 <div class="errors">
   <ul>
diff --git a/templates/reminder/default.tpl b/templates/reminder/default.tpl
new file mode 100644 (file)
index 0000000..6f3f18d
--- /dev/null
@@ -0,0 +1,25 @@
+{**************************************************************************}
+{*                                                                        *}
+{*  Copyright (C) 2003-2009 Polytechnique.org                             *}
+{*  http://opensource.polytechnique.org/                                  *}
+{*                                                                        *}
+{*  This program is free software; you can redistribute it and/or modify  *}
+{*  it under the terms of the GNU General Public License as published by  *}
+{*  the Free Software Foundation; either version 2 of the License, or     *}
+{*  (at your option) any later version.                                   *}
+{*                                                                        *}
+{*  This program 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 General Public License for more details.                          *}
+{*                                                                        *}
+{*  You should have received a copy of the GNU General Public License     *}
+{*  along with this program; if not, write to the Free Software           *}
+{*  Foundation, Inc.,                                                     *}
+{*  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA               *}
+{*                                                                        *}
+{**************************************************************************}
+
+<div class="warnings reminder">{icon name=error}&nbsp;{$text}</div>
+
+{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
diff --git a/upgrade/0.10.1/00_inscription.sql b/upgrade/0.10.1/00_inscription.sql
new file mode 100644 (file)
index 0000000..a284b57
--- /dev/null
@@ -0,0 +1,32 @@
+DROP TABLE IF EXISTS reminder_type;
+
+CREATE TABLE IF NOT EXISTS reminder_type (
+  type_id INT NOT NULL AUTO_INCREMENT,
+  name VARCHAR(255) NOT NULL,
+  weight INT NOT NULL,
+  remind_delay_yes INT NOT NULL DEFAULT 0,
+  remind_delay_no INT NOT NULL DEFAULT 0,
+  remind_delay_dismiss INT NOT NULL DEFAULT 0,
+  PRIMARY KEY(type_id),
+  UNIQUE KEY(name)
+) CHARSET=utf8;
+
+INSERT INTO  reminder_type (name, weight, remind_delay_yes, remind_delay_no, remind_delay_dismiss)
+     VALUES  ('email_warning', 100, 0, 0,   7),
+             ('profile_update', 90, 0, 0,   2),
+             ('nl',             80, 0, 365, 7),
+             ('promotion_ml',   70, 0, 365, 7),
+             ('ax_letter',      50, 0, 365, 14);
+
+DROP TABLE IF EXISTS reminder;
+
+CREATE TABLE IF NOT EXISTS reminder (
+  uid INT NOT NULL,
+  type_id INT NOT NULL,
+  status ENUM('yes', 'no', 'dismissed') NOT NULL,
+  remind_last TIMESTAMP NOT NULL,
+  remind_next TIMESTAMP NOT NULL,
+  PRIMARY KEY(uid, type_id)
+) CHARSET=utf8;
+
+-- vim:set syntax=mysql: