Notifies profile's owner when profile modified by a third party (Closes #1038).
authorStéphane Jacob <sj@m4x.org>
Sun, 4 Jul 2010 17:57:07 +0000 (19:57 +0200)
committerStéphane Jacob <sj@m4x.org>
Mon, 12 Jul 2010 13:26:34 +0000 (15:26 +0200)
Signed-off-by: Stéphane Jacob <sj@m4x.org>
14 files changed:
bin/cron/profile_modification.php [new file with mode: 0755]
configs/mails.conf
configs/platal.cron.in
include/validations.inc.php
modules/profile/addresses.inc.php
modules/profile/decos.inc.php
modules/profile/general.inc.php
modules/profile/groups.inc.php
modules/profile/jobs.inc.php
modules/profile/mentor.inc.php
modules/profile/page.inc.php
modules/profile/skills.inc.php
templates/profile/notification.mail.tpl [new file with mode: 0644]
upgrade/1.0.1/01_profiles.sql [new file with mode: 0644]

diff --git a/bin/cron/profile_modification.php b/bin/cron/profile_modification.php
new file mode 100755 (executable)
index 0000000..0aa2172
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/php5 -q
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2010 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                *
+ ***************************************************************************/
+
+require_once 'connect.db.inc.php';
+require_once 'plmailer.php';
+global $globals;
+
+$res = XDB::iterator('SELECT  p.hrpid, pm.pid, a.full_name, pm.field, pm.oldText, pm.newText, p.sex, pd.yourself, al.alias
+                        FROM  profile_modifications AS pm
+                  INNER JOIN  accounts              AS a  ON (pm.uid = a.uid)
+                  INNER JOIN  profiles              AS p  ON (pm.pid = p.pid)
+                  INNER JOIN  profile_display       AS pd ON (pm.pid = pd.pid)
+                  INNER JOIN  account_profiles      AS ap ON (pm.pid = ap.pid AND FIND_IN_SET(\'owner\', ap.perms))
+                  INNER JOIN  aliases               AS al ON (ap.uid = al.uid AND FIND_IN_SET(\'bestalias\', al.flags))
+                    ORDER BY  pm.pid, pm.field');
+
+$date = time();
+$values = $res->next();
+
+$pid = $values['pid'];
+$sex = ($values['sex'] == 'female') ? 1 : 0;
+$yourself = $values['yourself'];
+$alias = $values['alias'];
+$hrpid = $values['hrpid'];
+$modifications = array();
+$modifications[] = array(
+    'full_name' => $values['full_name'],
+    'field'     => $values['field'],
+    'oldText'   => $values['oldText'],
+    'newText'   => $values['newText'],
+);
+
+while ($values = $res->next()) {
+    if ($values['pid'] != $pid) {
+        $mailer = new PlMailer('profile/notification.mail.tpl');
+        $mailer->addTo($alias . '@' . $globals->mail->domain);
+        $mailer->assign('modifications', $modifications);
+        $mailer->assign('yourself', $yourself);
+        $mailer->assign('hrpid', $hrpid);
+        $mailer->assign('sex', $sex);
+        $mailer->assign('date', $date);
+        $mailer->send();
+        $modifications = array();
+    }
+    $pid = $values['pid'];
+    $sex = ($values['sex'] == 'female') ? 1 : 0;
+    $yourself = $values['yourself'];
+    $alias = $values['alias'];
+    $hrpid = $values['hrpid'];
+    $modifications[] = array(
+        'full_name' => $values['full_name'],
+        'field'     => $values['field'],
+        'oldText'   => $values['oldText'],
+        'newText'   => $values['newText'],
+    );
+}
+$mailer = new PlMailer('profile/notification.mail.tpl');
+$mailer->addTo($alias . '@' . $globals->mail->domain);
+$mailer->assign('modifications', $modifications);
+$mailer->assign('yourself', $yourself);
+$mailer->assign('hrpid', $hrpid);
+$mailer->assign('sex', $sex);
+$mailer->assign('date', $date);
+$mailer->send();
+
+XDB::execute('DELETE FROM  profile_modifications');
+
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
+?>
index de5ac1b..059b0d9 100644 (file)
@@ -66,3 +66,8 @@ from="Gestion des groupes X sur Polytechnique.net" <support@polytechnique.org>
 from="Webmaster Polytechnique.org" <web@polytechnique.org>
 to=registration+watch@staff.m4x.org
 replyto=registration+watch@staff.m4x.org
+
+[profile]
+from="Polytechnique.org" <validation_modification@polytechnique.org>
+cc="Polytechnique.org" <validation_modification@polytechnique.org>
+
index f445da4..2984ef9 100644 (file)
@@ -17,6 +17,9 @@ WD=/home/web/prod/platal/bin/cron
 0  2 * * *     web     cd $WD; ./notifs.birthday.php
 0  4 * * 6     web     cd $WD; ./notifs.send.php
 
+# profile modification notifications
+0 23 * * *     web     cd $WD; ./profile_modification.php
+
 # validations
 0 */3 * * *    web     cd $WD; ./cron_validations.php
 
index c3af12e..e04596a 100644 (file)
@@ -437,6 +437,7 @@ abstract class ProfileValidate extends Validate
     public $profile;
     public $profileOwner;
     public $userIsProfileOwner;
+    public $ownerIsRegistered;
 
     // }}}
     // {{{ constructor
@@ -453,11 +454,9 @@ abstract class ProfileValidate extends Validate
         parent::__construct($_user, $_unique, $_type);
         $this->profile = &$_profile;
         $this->profileOwner = $this->profile->owner();
-        if (!is_null($this->profileOwner) && $this->profileOwner->id() == $this->user->id()) {
-            $this->userIsProfileOwner = true;
-        } else {
-            $this->userIsProfileOwner = false;
-        }
+        $this->userIsProfileOwner = (!is_null($this->profileOwner)
+                                     && $this->profileOwner->id() == $this->user->id());
+        $this->ownerIsRegistered = $this->profile->isActive();
     }
 
     // }}}
@@ -525,24 +524,23 @@ abstract class ProfileValidate extends Validate
 
     protected function sendmail($isok)
     {
-        global $globals;
-        $mailer = new PlMailer();
-        $mailer->setSubject($this->_mail_subj());
-        $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
-        $mailer->addTo("\"{$this->profile->fullName()}\" <{$this->profileOwner->bestEmail()}>");
-        if (!$this->userIsProfileOwner) {
-            $mailer->addCc("\"{$this->user->fullName()}\" <{$this->user->bestEmail()}>");
-        }
-        $mailer->addCc("validation+{$this->type}@{$globals->mail->domain}");
-
-        $body = ($this->profile->isFemale() ? "Chère camarade,\n\n" : "Cher camarade,\n\n")
-              . $this->_mail_body($isok)
-              . (Env::has('comm') ? "\n\n" . Env::v('comm') : '')
-              . "\n\nCordialement,\n-- \nL'équipe de Polytechnique.org\n"
-              . $this->_mail_ps($isok);
+        // Only sends email if the profile's owner exists and is registered.
+        if ($this->ownerIsRegistered) {
+            global $globals;
 
-        $mailer->setTxtBody(wordwrap($body));
-        $mailer->send();
+            $mailer = new PlMailer();
+            $mailer->setSubject($this->_mail_subj());
+            $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
+            $mailer->addTo("\"{$this->profile->fullName()}\" <{$this->profileOwner->bestEmail()}>");
+            $mailer->addCc("validation+{$this->type}@{$globals->mail->domain}");
+            $body = ($this->profile->isFemale() ? "Chère camarade,\n\n" : "Cher camarade,\n\n")
+                  . $this->_mail_body($isok)
+                  . (Env::has('comm') ? "\n\n" . Env::v('comm') : '')
+                  . "\n\nCordialement,\n-- \nL'équipe de Polytechnique.org\n"
+                  . $this->_mail_ps($isok);
+            $mailer->setTxtBody(wordwrap($body));
+            $mailer->send();
+        }
     }
 
     // }}}
index 4e6d51b..37deea8 100644 (file)
@@ -127,6 +127,19 @@ class ProfileSettingAddress extends ProfileSettingGeocoding
             $profiletel->saveTels($page->pid(), 'tel', $address['tel']);
         }
     }
+
+    public function getText($value) {
+        $addresses = array();
+        foreach ($value as $addrid => $address) {
+            $phones = new ProfileSettingPhones('address', $addrid);
+            $addresses[] = 'Adresse : ' . $address['text'] . ', affichage : ' . $address['pub']
+                         . ', commentaire : ' . $address['comment'] . ', actuelle : ' . ($address['current'] ? 'oui' : 'non')
+                         . ', temporaire : ' . ($address['temporary'] ? 'oui' : 'non') . ', secondaire : '
+                         . ($address['secondary'] ? 'oui' : 'non') . ', conctactable par courier : '
+                         . ($address['mail'] ? 'oui' : 'non') . ', ' . $phones->getText($address['tel']);
+        }
+        return implode(' ; ' , $addresses);
+    }
 }
 
 class ProfileSettingAddresses extends ProfilePage
index 6dfc8e6..a6cfabc 100644 (file)
@@ -82,6 +82,15 @@ class ProfileSettingDeco implements ProfileSetting
             }
         }
     }
+
+    public function getText($value) {
+        $medalsList = DirEnum::getOptions(DirEnum::MEDALS);
+        $medals = array();
+        foreach ($value as $id => $medal) {
+            $medals[] = $medalsList[$id];
+        }
+        return implode(', ', $medals);
+    }
 }
 
 class ProfileSettingDecos extends ProfilePage
index 09c7bae..362c5eb 100644 (file)
@@ -234,6 +234,16 @@ class ProfileSettingSearchNames implements ProfileSetting
             set_profile_display($display_names, $page->pid());
         }
     }
+
+    public function getText($value) {
+        $names = array();
+        foreach ($value as $name) {
+            if ($name['name'] != '') {
+                $names[] = $name['type_name'] . ' : ' . $name['name'];
+            }
+        }
+        return implode(', ' , $names);
+    }
 }
 
 class ProfileSettingEdu implements ProfileSetting
@@ -296,6 +306,21 @@ class ProfileSettingEdu implements ProfileSetting
             }
         }
     }
+
+    public function getText($value) {
+        $schoolsList = DirEnum::getOptions(DirEnum::EDUSCHOOLS);
+        $degreesList = DirEnum::getOptions(DirEnum::EDUDEGREES);
+        $fieldsList = DirEnum::getOptions(DirEnum::EDUFIELDS);
+        $educations = array();
+        foreach ($value as $education) {
+            $educations[] = 'Université : ' . $schoolsList[$education['eduid']]
+                          . ', diplôme : ' . $degreesList[$education['degreeid']]
+                          . ', domaine : ' . $fieldsList[$education['fieldid']]
+                          . ', année d\'obtention : ' . $education['grad_year']
+                          . ', intitulé : ' . $education['program'];
+        }
+        return implode(', ', $educations);
+    }
 }
 
 class ProfileSettingEmailDirectory implements ProfileSetting
@@ -321,6 +346,10 @@ class ProfileSettingEmailDirectory implements ProfileSetting
         }
         return $value;
     }
+
+    public function getText($value) {
+        return $value;
+    }
 }
 
 class ProfileSettingNetworking implements ProfileSetting
@@ -394,6 +423,15 @@ class ProfileSettingNetworking implements ProfileSetting
                          $page->pid(), $id, $network['type'], $network['address'], $network['pub']);
         }
     }
+
+    public function getText($value) {
+        $networkings = array();
+        foreach ($value as $network) {
+            $networkings[] = 'nom : ' . $network['name'] . ', adresse : ' . $network['address']
+                           . ', affichage : ' . $network['pub'];
+        }
+        return implode(' ; ' , $networkings);
+    }
 }
 
 class ProfileSettingPromo implements ProfileSetting
@@ -467,6 +505,10 @@ class ProfileSettingPromo implements ProfileSetting
         }
         return intval($value);
     }
+
+    public function getText($value) {
+        return $value;
+    }
 }
 
 
index 62489ed..0d2df30 100644 (file)
@@ -41,6 +41,11 @@ class ProfileSettingSection implements ProfileSetting
                        WHERE  pid = {?}",
                      $value, $page->pid());
     }
+
+    public function getText($value) {
+        $sectionsList = DirEnum::getOptions(DirEnum::SECTIONS);
+        return $sectionsList[$value];
+    }
 }
 
 class ProfileSettingBinets implements ProfileSetting
@@ -85,6 +90,10 @@ class ProfileSettingBinets implements ProfileSetting
         XDB::execute("INSERT INTO  profile_binets (pid, binet_id)
                            VALUES  " . implode(',', $insert));
     }
+
+    public function getText($value) {
+        return implode(', ', $value);
+    }
 }
 
 class ProfileSettingGroups extends ProfilePage
index 1fa65fc..924ebac 100644 (file)
@@ -264,6 +264,19 @@ class ProfileSettingJob extends ProfileSettingGeocoding
             }
         }
     }
+
+    public function getText($value) {
+        $jobs = array();
+        foreach ($value as $id => $job) {
+            $address = new ProfileSettingAddress();
+            $phones = new ProfileSettingPhones('pro', $id);
+            $jobs[] = 'Entreprise : ' . $job['name'] . ', secteur : ' . $job['subSubSectorName']
+                    . ', description : ' . $job['description'] . ', web : ' . $job['w_url']
+                    . ', email : ' . $job['w_email']
+                    . ', ' . $phones->getText($job['w_phone']) . ', ' .  $address->getText($job['w_address']);
+        }
+        return implode(' ; ' , $jobs);
+    }
 }
 
 class ProfileSettingJobs extends ProfilePage
@@ -274,6 +287,7 @@ class ProfileSettingJobs extends ProfilePage
     {
         parent::__construct($wiz);
         $this->settings['cv'] = null;
+        /* TODO: ProfileSettingCorps, required for notifications. */
         $this->settings['corps'] = null;
         $this->settings['jobs'] = new ProfileSettingJob();
         $this->watched = array('cv' => true, 'jobs' => true, 'corps' => true);
index 035bb47..dc0492a 100644 (file)
@@ -69,6 +69,16 @@ class ProfileSettingSectors implements ProfileSetting
             }
         }
     }
+
+    public function getText($value) {
+        $sectors = array();
+        foreach ($value as $sector) {
+            foreach ($sector as $subsector) {
+                $sectors[] = $subsector;
+            }
+        }
+        return implode(', ', $sectors);
+    }
 }
 
 class ProfileSettingCountry implements ProfileSetting
@@ -107,6 +117,10 @@ class ProfileSettingCountry implements ProfileSetting
                          $page->pid(), $id);
         }
     }
+
+    public function getText($value) {
+        return implode(', ', $value);
+    }
 }
 
 
index 9d0521e..8c17a05 100644 (file)
@@ -36,11 +36,19 @@ interface ProfileSetting
     /** Save the new value for the given field.
      */
     public function save(ProfilePage &$page, $field, $new_value);
+
+    /** Get text from the value.
+     */
+    public function getText($value);
 }
 
 abstract class ProfileNoSave implements ProfileSetting
 {
     public function save(ProfilePage &$page, $field, $new_value) { }
+
+    public function getText($value) {
+        return $value;
+    }
 }
 
 class ProfileSettingWeb extends ProfileNoSave
@@ -200,6 +208,17 @@ class ProfileSettingPhones implements ProfileSetting
             $this->saveTel($pid, $telid, $phone);
         }
     }
+
+    public function getText($value) {
+        $phones = array();
+        foreach ($value as $phone) {
+            if ($phone['tel'] != '') {
+                $phones[] = 'type : ' . $phone['type'] .', numéro : ' . $phone['tel']
+                          . ', commentaire : « ' . $phone['comment'] . ' », affichage : ' . $phone['pub'];
+            }
+        }
+        return implode(' ; ' , $phones);
+    }
 }
 
 class ProfileSettingPub extends ProfileNoSave
@@ -217,6 +236,10 @@ class ProfileSettingPub extends ProfileNoSave
         }
         return $value;
     }
+
+    public function getText($value) {
+        return $value;
+    }
 }
 
 class ProfileSettingBool extends ProfileNoSave
@@ -281,6 +304,10 @@ abstract class ProfileSettingGeocoding implements ProfileSetting
             $address = $gmapsGeocoder->stripGeocodingFromAddress($address);
         }
     }
+
+    public function getText($value) {
+        return $value;
+    }
 }
 
 
@@ -336,12 +363,26 @@ abstract class ProfilePage implements PlWizardPage
     protected function saveData()
     {
         require_once 'notifs.inc.php';
+        $changedFields = array();
         foreach ($this->settings as $field=>&$setting) {
-            if (!is_null($setting) && $this->changed[$field]) {
-                $setting->save($this, $field, $this->values[$field]);
-            }
-            if ($this->changed[$field] && @$this->watched[$field]) {
-                WatchProfileUpdate::register($this->profile, $field);
+            if ($this->changed[$field]) {
+                if (!is_null($setting)) {
+                    $changedFields[$field] = array(
+                        str_replace("\n", " - ", $setting->getText($this->orig[$field])),
+                        str_replace("\n", " - ", $setting->getText($this->values[$field])),
+                    );
+                } else {
+                    $changedFields[$field] = array(
+                        str_replace("\n", " - ", $this->orig[$field]),
+                        str_replace("\n", " - ", $this->values[$field]),
+                    );
+                }
+                if (!is_null($setting)) {
+                    $setting->save($this, $field, $this->values[$field]);
+                }
+                if (isset($this->watched[$field]) && $this->watched[$field]) {
+                    WatchProfileUpdate::register($this->profile, $field);
+                }
             }
         }
         $this->_saveData();
@@ -352,6 +393,20 @@ abstract class ProfilePage implements PlWizardPage
                        WHERE  pid = {?}', $this->pid());
         global $platal;
         S::logger()->log('profil', $platal->pl_self(2));
+
+        /** If the update was made by a third party and the profile corresponds
+         * to a registered user, stores both former and new text.
+         * This will be daily sent to the user.
+         */
+        $owner = $this->profile->owner();
+        $user = S::user();
+        if ($owner->isActive() && $owner->id() != $user->id()) {
+            foreach ($changedFields as $field => $values) {
+                XDB::execute('REPLACE INTO  profile_modifications (pid, uid, field, oldText, newText)
+                                    VALUES  ({?}, {?}, {?}, {?}, {?})',
+                             $this->pid(), $user->id(), $field, $values[0], $values[1]);
+            }
+        }
     }
 
     protected function checkChanges()
index 4a8c579..1f18133 100644 (file)
@@ -76,6 +76,14 @@ class ProfileSettingSkill implements ProfileSetting
                          $page->pid(), $id, $skill['level']);
         }
     }
+
+    public function getText($value) {
+        $skills = array();
+        foreach ($value as $skill) {
+            $skills[] = 'Compétance : ' . $skill['text'] . ', niveau : ' . $skill['level'];
+        }
+        return implode(' ; ' , $skills);
+    }
 }
 
 class ProfileSettingSkills extends ProfilePage
diff --git a/templates/profile/notification.mail.tpl b/templates/profile/notification.mail.tpl
new file mode 100644 (file)
index 0000000..26c3cb4
--- /dev/null
@@ -0,0 +1,45 @@
+{**************************************************************************}
+{*                                                                        *}
+{*  Copyright (C) 2003-2010 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               *}
+{*                                                                        *}
+{**************************************************************************}
+
+{config_load file="mails.conf" section="profile"}
+{if $mail_part eq 'head'}
+{from full=#from#}
+{cc full=#cc#}
+{subject text="Modification de ton profil"}
+{elseif $mail_part eq 'wiki'}
+{if $sex}Chère{else}Cher{/if} {$yourself},
+
+Les champs suivants de ton profil ont été modifiés le {$date|date_format:"%x"} :
+{foreach from=$modifications item=modification}
+*{$modification.field} : « {$modification.oldText} » est devenu « {$modification.newText} » (effectuée par {$modification.full_name}).
+{/foreach}
+
+Tu peux voir ta fiche là :
+*{$globals->baseurl}/profile/{$hrpid}
+Tu peux aussi l'éditer toi-même là :
+*{$globals->baseurl}/profile/edit/{$hrpid}
+
+{include file="signature.mail.tpl"}
+
+{/if}
+
+{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
diff --git a/upgrade/1.0.1/01_profiles.sql b/upgrade/1.0.1/01_profiles.sql
new file mode 100644 (file)
index 0000000..c7187ae
--- /dev/null
@@ -0,0 +1,13 @@
+DROP TABLE IF EXISTS profile_modifications;
+
+CREATE TABLE profile_modifications (
+  pid INT(11) NOT NULL,
+  uid INT(11) NOT NULL,
+  field VARCHAR(60) NOT NULL,
+  oldText TEXT NOT NULL,
+  newText TEXT NOT NULL,
+  pub ENUM('private', 'ax', 'public') NOT NULL DEFAULT 'private',
+  PRIMARY KEY(pid, uid, field)
+) ENGINE=InnoDB, CHARSET=utf8;
+
+-- vim:set syntax=mysql: