***************************************************************************/
require_once './connect.db.inc.php';
-require_once '../../modules/axletter/axletter.inc.php';
+require_once 'newsletter.inc.php';
ini_set('memory_limit', '128M');
-$al = AXLetter::toSend();
-if ($al) {
- echo "Envoi de la lettre \"{$al->title()}\"\n\n";
+$nls = NewsLetter::getIssuesToSend();
+foreach ($nls as $nl) {
+ echo "Envoi de la lettre \"{$nl->title()}\" (Groupe {$nl->group})\n\n";
echo ' ' . date("H:i:s") . " -> début de l'envoi\n";
- $emailsCount = $al->sendToAll();
+ $emailsCount = $nl->sendToAll();
echo ' ' . date("H:i:s") . " -> fin de l'envoi\n\n";
- echo $emailsCount . " emails ont été envoyés lors de cet envoi.\n";
+ echo $emailsCount . " emails ont été envoyés lors de cet envoi.\n\n";
}
// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
public function __construct()
{
parent::__construct('xnet', 'xnetgrp', 'xnetlists', 'xnetevents',
- 'payment', 'bandeau');
+ 'payment', 'bandeau', 'xnetnl');
}
public function hook_map($name)
# flux rss de banana
*/5 * * * * www-data cd $WD; ./banana.feedgen.php > /dev/null
-# AX spammer
-15 * * * * web cd $WD; ./axletter.send.php | mail -e -s "Envoi d'un email de l'AX" br@staff.m4x.org
+# Send group Newsletters
+15 * * * * web cd $WD; ./newsletters.send.php | mail -e -s "Envoi des NLs des groupes" br@staff.m4x.org
# homonymes
0 0 4 * * web cd $WD; ./homonyms.php
+++ /dev/null
-<?php
-/***************************************************************************
- * Copyright (C) 2003-2011 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 MailNotFound
-
-class MailNotFound extends Exception {
-}
-
-// }}}
-
-// {{{ class MassMailer
-
-abstract class MassMailer
-{
- private $_tpl;
- private $_css;
- private $_prefix;
-
- public $_id;
- public $_shortname;
- public $_title;
- public $_title_mail;
-
- public $_head;
-
- protected $_table;
- protected $_subscriptionTable;
-
- function __construct($tpl, $css, $prefix, $tbl, $stbl)
- {
- $this->_tpl = $tpl;
- $this->_css = $css;
- $this->_prefix = $prefix;
- $this->_table = $tbl;
- $this->_subscriptionTable = $stbl;
- }
-
- public function id()
- {
- return is_null($this->_shortname) ? $this->_id : $this->_shortname;
- }
-
- private function selectId($where)
- {
- $res = XDB::query("SELECT IF (n.short_name IS NULL, n.id, n.short_name)
- FROM {$this->_table} AS n
- WHERE n.bits != 'new' AND {$where}
- LIMIT 1");
- if ($res->numRows() != 1) {
- return null;
- }
- return $res->fetchOneCell();
- }
-
- public function prev()
- {
- static $val;
- if (!isset($val)) {
- $val = $this->selectId("n.id < {$this->_id} ORDER BY n.id DESC");
- }
- return $val;
- }
-
- public function next()
- {
- static $val;
- if (!isset($val)) {
- $val = $this->selectId("n.id > {$this->_id} ORDER BY n.id");
- }
- return $val;
- }
-
- public function last()
- {
- static $val;
- if (!isset($val)) {
- $res = XDB::query("SELECT MAX(n.id)
- FROM {$this->_table} AS n
- WHERE n.bits != 'new' AND n.id > {?}",
- $this->_id);
- if ($res->numRows() != 1) {
- $val = null;
- } else {
- $val = $res->fetchOneCell();
- }
- }
- return $val;
- }
-
- public function title($mail = false)
- {
- return $mail ? $this->_title_mail : $this->_title;
- }
-
- public function head($user = null, $type = 'text')
- {
- if (is_null($user)) {
- return $this->_head;
- } else {
- $head = $this->_head;
- $head = str_replace('<cher>', $user->isFemale() ? 'Chère' : 'Cher', $head);
- $head = str_replace('<prenom>', $user->displayName(), $head);
- $head = str_replace('<nom>', '', $head);
- return format_text($head, $type, 2, 64);
- }
- }
-
- public function css(&$page = null)
- {
- if (!is_null($page)) {
- $page->addCssLink($this->_css);
- return true;
- } else {
- $css = file_get_contents(dirname(__FILE__) . '/../htdocs/css/' . $this->_css);
- return preg_replace('@/\*.*?\*/@us', '', $css);
- }
- }
-
- public function toText(&$page, $user)
- {
- $this->css($page);
- $page->assign('is_mail', false);
- $page->assign('mail_part', 'text');
- $page->assign('user', $user);
- $this->assignData($page);
- }
-
- public function toHtml(&$page, $user)
- {
- $this->css($page);
- $page->assign('prefix', $this->_prefix . '/' . $this->id());
- $page->assign('is_mail', false);
- $page->assign('mail_part', 'html');
- $page->assign('user', $user);
- $this->assignData($page);
- }
-
- private function createHash($line, $key = null)
- {
- $hash = implode(time(), $line) . rand();
- $hash = md5($hash);
- return $hash;
- }
-
- public function sendTo($user, $hash = null)
- {
- if (is_null($hash)) {
- $hash = XDB::fetchOneCell("SELECT hash
- FROM {$this->_subscriptionTable}
- WHERE uid = {?}", $user->id());
- }
- if (is_null($hash)) {
- $hash = $this->createHash(array($user->displayName(), $user->fullName(),
- $user->isFemale(), $user->isEmailFormatHtml(),
- rand(), "X.org rulez"));
- XDB::execute("UPDATE {$this->_subscriptionTable} as ni
- SET ni.hash = {?}
- WHERE ni.uid = {?}",
- $hash, $user->id());
- }
-
- $mailer = new PlMailer($this->_tpl);
- $this->assignData($mailer);
- $mailer->assign('is_mail', true);
- $mailer->assign('user', $user);
- $mailer->assign('prefix', null);
- $mailer->assign('hash', $hash);
- $mailer->addTo('"' . $user->fullName() . '" <' . $user->bestEmail() . '>');
- $mailer->send($user->isEmailFormatHtml());
- }
-
- protected function getAllRecipients()
- {
- global $globals;
- return "SELECT a.uid
- FROM {$this->_subscriptionTable} AS ni
- INNER JOIN accounts AS a ON (ni.uid = a.uid)
- LEFT JOIN email_options AS eo ON (eo.uid = a.uid)
- LEFT JOIN emails AS e ON (e.uid = a.uid AND e.flags='active')
- LEFT JOIN account_profiles AS ap ON (a.uid = ap.uid AND FIND_IN_SET('owner', ap.perms))
- LEFT JOIN profile_display AS pd ON (ap.pid = pd.pid)
- WHERE ni.last < {?} AND ({$this->subscriptionWhere()}) AND
- (e.email IS NOT NULL OR FIND_IN_SET('googleapps', eo.storage))
- GROUP BY a.uid";
- }
-
- public function sendToAll()
- {
- $this->setSent();
- $query = XDB::format($this->getAllRecipients(), $this->_id) . ' LIMIT 60';
- $emailsCount = 0;
-
- while (true) {
- $sent = array();
- $users = User::getBulkUsersWithUIDs(XDB::fetchColumn($query));
- if (count($users) == 0) {
- return $emailsCount;
- }
- foreach ($users as $user) {
- $sent[] = $user->id();
- $this->sendTo($user, $hash);
- ++$emailsCount;
- }
- XDB::execute("UPDATE {$this->_subscriptionTable}
- SET last = {?}
- WHERE uid IN {?}", $this->_id, $sent);
-
- sleep(60);
- }
- return $emailsCount;
- }
-
- abstract protected function assignData(&$smarty);
- abstract protected function setSent();
-
- abstract protected function subscriptionWhere();
-}
-
-// }}}
-// {{{ Functions
-
-function format_text($input, $format, $indent = 0, $width = 68)
-{
- if ($format == 'text') {
- return MiniWiki::WikiToText($input, true, $indent, $width, "title");
- }
- return MiniWiki::WikiToHTML($input, "title");
-}
-
-// function enriched_to_text($input,$html=false,$just=false,$indent=0,$width=68)
-
-// }}}
-
-// vim:set et sw=4 sts=4 sws=4 enc=utf-8:
-?>
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
***************************************************************************/
-require_once("massmailer.inc.php");
+// {{{ class MailNotFound
+
+class MailNotFound extends Exception {
+}
+
+// }}}
// {{{ class NewsLetter
-class NewsLetter extends MassMailer
+class NewsLetter
{
- public $_date;
- public $_cats = array();
- public $_arts = array();
+ public $id; // ID of the NL (in table newsletters)
+ public $group; // Short name of the group corresponding to the NL
+ public $group_id; // ID of that group
+ public $name; // Name of the NL (e.g "Lettre de Polytechnique.org", ...)
+ public $cats; // List of all categories for this NL
+ public $criteria; // PlFlagSet of allowed filters for recipient selection
+
+ protected $custom_css = false;
- function __construct($id = null)
+ // Base name to use instead of the group short name for NLs without a custom CSS
+ const FORMAT_DEFAULT_GROUP = 'default';
+
+ // Diminutif of X.net groups with a specific NL view
+ const GROUP_XORG = 'Polytechnique.org';
+ const GROUP_AX = 'AX';
+ const GROUP_EP = 'Ecole';
+
+ // {{{ Constructor, NewsLetter retrieval (forGroup, getAll)
+
+ public function __construct($id)
{
- parent::__construct('newsletter/nl.mail.tpl', 'nl.css', 'nl/show', 'newsletter', 'newsletter_ins');
- if (isset($id)) {
- if ($id == 'last') {
- $res = XDB::query("SELECT MAX(id) FROM newsletter WHERE bits!='new'");
- $id = $res->fetchOneCell();
+ // Load NL data
+ $res = XDB::query('SELECT nls.group_id, g.diminutif AS group_name,
+ nls.name AS nl_name, nls.custom_css, nls.criteria
+ FROM newsletters AS nls
+ LEFT JOIN groups AS g ON (nls.group_id = g.id)
+ WHERE nls.id = {?}',
+ $id);
+ if (!$res->numRows()) {
+ throw new MailNotFound();
+ }
+
+ $data = $res->fetchOneAssoc();
+ $this->id = $id;
+ $this->group_id = $data['group_id'];
+ $this->group = $data['group_name'];
+ $this->name = $data['nl_name'];
+ $this->custom_css = $data['custom_css'];
+ $this->criteria = new PlFlagSet($data['criteria']);
+
+ // Load the categories
+ $res = XDB::iterRow(
+ 'SELECT cid, title
+ FROM newsletter_cat
+ WHERE nlid = {?}
+ ORDER BY pos', $id);
+ while (list($cid, $title) = $res->next()) {
+ $this->cats[$cid] = $title;
+ }
+ }
+
+ /** Retrieve the NL associated with a given group.
+ * @p $group Short name of the group
+ * @return A NewsLetter object, or null if the group doesn't have a NL.
+ */
+ public static function forGroup($group)
+ {
+ $res = XDB::query('SELECT nls.id
+ FROM newsletters AS nls
+ LEFT JOIN groups AS g ON (nls.group_id = g.id)
+ WHERE g.diminutif = {?}', $group);
+ if (!$res->numRows()) {
+ return null;
+ }
+ return new NewsLetter($res->fetchOneCell());
+ }
+
+ /** Retrieve all newsletters
+ * @return An array of $id => NewsLetter objects
+ */
+ public static function getAll()
+ {
+ $res = XDB::query('SELECT id
+ FROM newsletters');
+ $nls = array();
+ foreach ($res->fetchColumn() as $id) {
+ $nls[$id] = new NewsLetter($id);
+ }
+ return $nls;
+ }
+
+ // }}}
+ // {{{ Issue retrieval
+
+ /** Retrieve all issues which should be sent
+ * @return An array of NLIssue objects to send (i.e state = 'new' and send_before <= today)
+ */
+ public static function getIssuesToSend()
+ {
+ $res = XDB::query('SELECT id
+ FROM newsletter_issues
+ WHERE state = \'pending\' AND send_before <= NOW()');
+ $issues = array();
+ foreach ($res->fetchColumn() as $id) {
+ $issues[$id] = new NLIssue($id);
+ }
+ return $issues;
+ }
+
+ /** Retrieve a given issue of this NewsLetter
+ * @p $name Name or ID of the issue to retrieve.
+ * @return A NLIssue object.
+ *
+ * $name may be either a short_name, an ID or the special value 'last' which
+ * selects the latest sent NL.
+ * If $name is null, this will retrieve the current pending NL.
+ */
+ public function getIssue($name = null, $only_sent = true)
+ {
+ if ($name) {
+ if ($name == 'last') {
+ if ($only_sent) {
+ $where = 'state = \'sent\' AND ';
+ } else {
+ $where = '';
+ }
+ $res = XDB::query('SELECT MAX(id)
+ FROM newsletter_issues
+ WHERE ' . $where . ' nlid = {?}',
+ $this->id);
+ } else {
+ $res = XDB::query('SELECT id
+ FROM newsletter_issues
+ WHERE nlid = {?} AND (id = {?} OR short_name = {?})',
+ $this->id, $name, $name);
}
- $res = XDB::query("SELECT * FROM newsletter WHERE id={?} OR short_name={?} LIMIT 1", $id, $id);
- } else {
- $res = XDB::query("SELECT * FROM newsletter WHERE bits='new'");
if (!$res->numRows()) {
- NewsLetter::create();
+ throw new MailNotFound();
+ }
+ $id = $res->fetchOneCell();
+ } else {
+ $query = XDB::format('SELECT id
+ FROM newsletter_issues
+ WHERE nlid = {?} AND state = \'new\'
+ ORDER BY id DESC', $this->id);
+ $res = XDB::query($query);
+ if ($res->numRows()) {
+ $id = $res->fetchOneCell();
+ } else {
+ // Create a new, empty issue, and return it
+ $id = $this->createPending();
}
- $res = XDB::query("SELECT * FROM newsletter WHERE bits='new' ORDER BY id DESC LIMIT 1");
}
- if ($res->numRows() != 1) {
+
+ return new NLIssue($id, &$this);
+ }
+
+ /** Create a new, empty, pending newsletter issue
+ * @p $nlid The id of the NL for which a new pending issue should be created.
+ * @return Id of the newly created issue.
+ */
+ public function createPending()
+ {
+ XDB::execute('INSERT INTO newsletter_issues
+ SET nlid = {?}, state=\'new\', date=NOW(),
+ title=\'to be continued\',
+ mail_title=\'to be continued\'',
+ $this->id);
+ return XDB::insertId();
+ }
+
+ /** Return all sent issues of this newsletter.
+ * @return An array of (id => NLIssue)
+ */
+ public function listSentIssues($check_user = false, $user = null)
+ {
+ if ($check_user && $user == null) {
+ $user = S::user();
+ }
+
+ $res = XDB::query('SELECT id
+ FROM newsletter_issues
+ WHERE nlid = {?} AND state = \'sent\'
+ ORDER BY date DESC', $this->id);
+ $issues = array();
+ foreach ($res->fetchColumn() as $id) {
+ $issue = new NLIssue($id, $this, false);
+ if (!$check_user || $issue->checkUser($user)) {
+ $issues[$id] = $issue;
+ }
+ }
+ return $issues;
+ }
+
+ /** Return all issues of this newsletter, including invalid and sent.
+ * @return An array of (id => NLIssue)
+ */
+ public function listAllIssues()
+ {
+ $res = XDB::query('SELECT id
+ FROM newsletter_issues
+ WHERE nlid = {?}
+ ORDER BY FIELD(state, \'pending\', \'new\') DESC, date DESC', $this->id);
+ $issues = array();
+ foreach ($res->fetchColumn() as $id) {
+ $issues[$id] = new NLIssue($id, $this, false);
+ }
+ return $issues;
+ }
+
+ /** Return the latest pending issue of the newsletter.
+ * @return Either null, or a NL object.
+ */
+ public function getPendingIssue()
+ {
+ $res = XDB::query('SELECT MAX(id)
+ FROM newsletter_issues
+ WHERE nlid = {?} AND state = \'new\'',
+ $this->id);
+ if ($res->numRows()) {
+ $id = $res->fetchOneCell();
+ return new NLIssue($id, $this);
+ } else {
+ return null;
+ }
+ }
+
+ // }}}
+ // {{{ Subscription related function
+
+ /** Unsubscribe a user from this newsletter
+ * @p $uid UID to unsubscribe from the newsletter; if null, use current user.
+ * @p $hash True if the uid is actually a hash.
+ * @return True if the user was successfully unsubscribed.
+ */
+ public function unsubscribe($uid = null, $hash = false)
+ {
+ if (is_null($uid) && $hash) {
+ // Unable to unsubscribe from an empty hash
+ return false;
+ }
+ $user = is_null($uid) ? S::user()->id() : $uid;
+ $field = $hash ? 'hash' : 'uid';
+ $res = XDB::query('SELECT uid
+ FROM newsletter_ins
+ WHERE nlid = {?} AND ' . $field . ' = {?}',
+ $this->id, $user);
+ if (!$res->numRows()) {
+ // No subscribed user with that UID/hash
+ return false;
+ }
+ $user = $res->fetchOneCell();
+
+ XDB::execute('DELETE FROM newsletter_ins
+ WHERE nlid = {?} AND uid = {?}',
+ $this->id, $user);
+ return true;
+ }
+
+ /** Subscribe a user to a newsletter
+ * @p $user User to subscribe to the newsletter; if null, use current user.
+ */
+ public function subscribe($user = null)
+ {
+ if (is_null($user)) {
+ $user = S::user();
+ }
+ if (self::maySubscribe($user)) {
+ XDB::execute('INSERT IGNORE INTO newsletter_ins (nlid, uid, last, hash)
+ VALUES ({?}, {?}, NULL, hash)',
+ $this->id, $user->id());
+ }
+ }
+
+ /** Retrieve subscription state of a user
+ * @p $user Target user; if null, use current user.
+ * @return Boolean: true if the user has subscribed to the NL.
+ */
+ public function subscriptionState($user = null)
+ {
+ if (is_null($user)) {
+ $user = S::user();
+ }
+ $res = XDB::query('SELECT 1
+ FROM newsletter_ins
+ WHERE nlid = {?} AND uid = {?}',
+ $this->id, $user->id());
+ return ($res->numRows() == 1);
+ }
+
+ /** Get the count of subscribers to the NL.
+ * @return Number of subscribers.
+ */
+ public function subscriberCount()
+ {
+ return XDB::fetchOneCell('SELECT COUNT(uid)
+ FROM newsletter_ins
+ WHERE nlid = {?}', $this->id);
+ }
+
+ /** Get the number of subscribers to the NL whose last received mailing was $last.
+ * @p $last ID of the issue for which subscribers should be counted.
+ * @return Number of subscribers
+ */
+ public function subscriberCountForLast($last)
+ {
+ return XDB::fetchOneCell('SELECT COUNT(uid)
+ FROM newsletter_ins
+ WHERE nlid = {?} AND last = {?}', $this->id, $last);
+ }
+
+ /** Retrieve the list of newsletters a user has subscribed to
+ * @p $user User whose subscriptions should be retrieved (if null, use session user).
+ * @return Array of newsletter IDs
+ */
+ public static function getUserSubscriptions($user = null)
+ {
+ if (is_null($user)) {
+ $user = S::user();
+ }
+ $res = XDB::query('SELECT nlid
+ FROM newsletter_ins
+ WHERE uid = {?}',
+ $user->id());
+ return $res->fetchColumn();
+ }
+
+ /** Retrieve the UserFilterBuilder for subscribers to this NL.
+ * This is the place where NL-specific filters may be allowed or prevented.
+ * @p $envprefix Prefix to use for env fields (cf. UserFilterBuilder)
+ * @return A UserFilterBuilder object using the given env prefix
+ */
+ public function getSubscribersUFB($envprefix = '')
+ {
+ require_once 'ufbuilder.inc.php';
+ return new UFB_NewsLetter($this->criteria, $envprefix);
+ }
+
+ // }}}
+ // {{{ Permissions related functions
+
+ /** For later use: check whether a given user may subscribe to this newsletter.
+ * @p $user User whose access should be checked
+ * @return Boolean: whether the user may subscribe to the NL.
+ */
+ public function maySubscribe($user = null)
+ {
+ return true;
+ }
+
+ /** Whether a given user may edit this newsletter
+ * @p $uid UID of the user whose perms should be checked (if null, use current user)
+ * @return Boolean: whether the user may edit the NL
+ */
+ public function mayEdit($user = null)
+ {
+ if (is_null($user)) {
+ $user = S::user();
+ }
+ if ($user->checkPerms('admin')) {
+ return true;
+ }
+ $res = XDB::query('SELECT perms
+ FROM group_members
+ WHERE asso_id = {?} AND uid = {?}',
+ $this->group_id, $user->id());
+ return ($res->numRows() && $res->fetchOneCell() == 'admin');
+ }
+
+ /** Whether a given user may submit articles to this newsletter using X.org validation system
+ * @p $user User whose access should be checked (if null, use current user)
+ * @return Boolean: whether the user may submit articles
+ */
+ public function maySubmit($user = null)
+ {
+ // Submission of new articles is only enabled for the X.org NL (and forbidden when viewing issues on X.net)
+ return ($this->group == self::GROUP_XORG && !isset($GLOBALS['IS_XNET_SITE']));
+ }
+
+ // }}}
+ // {{{ Display-related functions: cssFile, tplFile, prefix, admin_prefix, admin_links_enabled, automatic_mailings_enabled
+
+ /** Get the name of the css file used to display this newsletter.
+ */
+ public function cssFile()
+ {
+ if ($this->custom_css) {
+ $base = $this->group;
+ } else {
+ $base = self::FORMAT_DEFAULT_GROUP;
+ }
+ return 'nl.' . $base . '.css';
+ }
+
+ /** Get the name of the template file used to display this newsletter.
+ */
+ public function tplFile()
+ {
+ if ($this->custom_css) {
+ $base = $this->group;
+ } else {
+ $base = self::FORMAT_DEFAULT_GROUP;
+ }
+ return 'newsletter/nl.' . $base . '.mail.tpl';
+ }
+
+ /** Get the prefix leading to the page for this NL
+ * Only X.org / AX / X groups may be seen on X.org.
+ */
+ public function prefix()
+ {
+ if (!empty($GLOBALS['IS_XNET_SITE'])) {
+ return $this->group . '/nl';
+ }
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ return 'nl';
+ case self::GROUP_AX:
+ return 'ax';
+ case self::GROUP_EP:
+ return 'epletter';
+ default:
+ // Don't display groups NLs on X.org
+ assert(false);
+ }
+ }
+
+ /** Get the prefix to use for all 'admin' pages of this NL.
+ */
+ public function adminPrefix()
+ {
+ if (!empty($GLOBALS['IS_XNET_SITE'])) {
+ return $this->group . '/admin/nl';
+ }
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ return 'admin/newsletter';
+ case self::GROUP_AX:
+ return 'ax/admin';
+ case self::GROUP_EP:
+ return 'epletter/admin';
+ default:
+ // Don't display groups NLs on X.org
+ assert(false);
+ }
+ }
+
+ /** Hack used to remove "admin" links on X.org page on X.net
+ * The 'admin' links are enabled for all pages, except for X.org when accessing NL through X.net
+ */
+ public function adminLinksEnabled()
+ {
+ return ($this->group != self::GROUP_XORG || !isset($GLOBALS['IS_XNET_SITE']));
+ }
+
+ /** Automatic mailings are disabled for X.org NL.
+ */
+ public function automaticMailingEnabled()
+ {
+ return $this->group != self::GROUP_XORG;
+ }
+
+ public function hasCustomCss()
+ {
+ return $this->custom_css;
+ }
+
+ // }}}
+}
+
+// }}}
+
+// {{{ class NLIssue
+
+// A NLIssue is an issue of a given NewsLetter
+class NLIssue
+{
+ protected $nlid; // Id of the newsletter
+
+ const STATE_NEW = 'new'; // New, currently being edited
+ const STATE_PENDING = 'pending'; // Ready for mailing
+ const STATE_SENT = 'sent'; // Sent
+
+ public $nl; // Related NL
+
+ public $id; // Id of this issue of the newsletter
+ public $shortname; // Shortname for this issue
+ public $title; // Title of this issue
+ public $title_mail; // Title of the email
+ public $state; // State of the issue (one of the STATE_ values)
+ public $sufb; // Environment to use to generate the UFC through an UserFilterBuilder
+
+ public $date; // Date at which this issue was sent
+ public $send_before; // Date at which issue should be sent
+ public $head; // Foreword of the issue (or body for letters with no articles)
+ public $signature; // Signature of the letter
+ public $arts = array(); // Articles of the issue
+
+ const BATCH_SIZE = 60; // Number of emails to send every minute.
+
+ // {{{ Constructor, id-related functions
+
+ /** Build a NewsLetter.
+ * @p $id: ID of the issue (unique among all newsletters)
+ * @p $nl: Optional argument containing an already built NewsLetter object.
+ */
+ function __construct($id, $nl = null, $fetch_articles = true)
+ {
+ return $this->fetch($id, $nl, $fetch_articles);
+ }
+
+ protected function refresh()
+ {
+ return $this->fetch($this->id, $this->nl, false);
+ }
+
+ protected function fetch($id, $nl = null, $fetch_articles = true)
+ {
+ // Load this issue
+ $res = XDB::query('SELECT nlid, short_name, date, send_before, state, sufb_json,
+ title, mail_title, head, signature
+ FROM newsletter_issues
+ WHERE id = {?}',
+ $id);
+ if (!$res->numRows()) {
throw new MailNotFound();
}
- $nl = $res->fetchOneAssoc();
+ $issue = $res->fetchOneAssoc();
+ if ($nl && $nl->id == $issue['nlid']) {
+ $this->nl = $nl;
+ } else {
+ $this->nl = new NewsLetter($issue['nlid']);
+ }
+ $this->id = $id;
+ $this->shortname = $issue['short_name'];
+ $this->date = $issue['date'];
+ $this->send_before = $issue['send_before'];
+ $this->state = $issue['state'];
+ $this->title = $issue['title'];
+ $this->title_mail = $issue['mail_title'];
+ $this->head = $issue['head'];
+ $this->signature = $issue['signature'];
+ $this->sufb = $this->importJSonStoredUFB($issue['sufb_json']);
- $this->_id = $nl['id'];
- $this->_shortname = $nl['short_name'];
- $this->_date = $nl['date'];
- $this->_title = $nl['titre'];
- $this->_title_mail = $nl['titre_mail'];
- $this->_head = $nl['head'];
+ if ($fetch_articles) {
+ $this->fetchArticles();
+ }
+ }
- $res = XDB::iterRow("SELECT cid,titre FROM newsletter_cat ORDER BY pos");
- while (list($cid, $title) = $res->next()) {
- $this->_cats[$cid] = $title;
+ protected function fetchArticles($force = false)
+ {
+ if (count($this->arts) && !$force) {
+ return;
}
+ // Load the articles
$res = XDB::iterRow(
- "SELECT a.title,a.body,a.append,a.aid,a.cid,a.pos
- FROM newsletter_art AS a
- INNER JOIN newsletter AS n USING(id)
- LEFT JOIN newsletter_cat AS c ON(a.cid=c.cid)
- WHERE a.id={?}
- ORDER BY c.pos,a.pos", $this->_id);
+ 'SELECT a.title, a.body, a.append, a.aid, a.cid, a.pos
+ FROM newsletter_art AS a
+ INNER JOIN newsletter_issues AS ni USING(id)
+ LEFT JOIN newsletter_cat AS c ON (a.cid = c.cid)
+ WHERE a.id = {?}
+ ORDER BY c.pos, a.pos',
+ $this->id);
while (list($title, $body, $append, $aid, $cid, $pos) = $res->next()) {
- $this->_arts[$cid]["a$aid"] = new NLArticle($title, $body, $append, $aid, $cid, $pos);
+ $this->arts[$cid][$aid] = new NLArticle($title, $body, $append, $aid, $cid, $pos);
+ }
+ }
+
+ protected function importJSonStoredUFB($json = null)
+ {
+ require_once 'ufbuilder.inc.php';
+ $ufb = $this->nl->getSubscribersUFB();
+ if (is_null($json)) {
+ return new StoredUserFilterBuilder($ufb, new PFC_True());
+ }
+ $export = json_decode($json, true);
+ if (is_null($export)) {
+ PlErrorReport::report("Invalid json while reading NL {$this->nlid}, issue {$this->id}: failed to import '''{$json}'''.");
+ return new StoredUserFilterBuilder($ufb, new PFC_True());
+ }
+ $sufb = new StoredUserFilterBuilder($ufb);
+ $sufb->fillFromExport($export);
+ return $sufb;
+ }
+
+ protected function exportStoredUFBAsJSon()
+ {
+ return json_encode($this->sufb->export());
+ }
+
+ public function id()
+ {
+ return is_null($this->shortname) ? $this->id : $this->shortname;
+ }
+
+ protected function selectId($where)
+ {
+ $res = XDB::query("SELECT IFNULL(ni.short_name, ni.id)
+ FROM newsletter_issues AS ni
+ WHERE ni.state != 'new' AND ni.nlid = {?} AND ${where}
+ LIMIT 1", $this->nl->id);
+ if ($res->numRows() != 1) {
+ return null;
+ }
+ return $res->fetchOneCell();
+ }
+
+ /** Delete this issue
+ * @return True if the issue could be deleted, false otherwise.
+ * Related articles will be deleted through cascading FKs.
+ * If this issue was the last issue for at least one subscriber, the deletion will be aborted.
+ */
+ public function delete()
+ {
+ if ($this->state == self::STATE_NEW) {
+ $res = XDB::query('SELECT COUNT(*)
+ FROM newsletter_ins
+ WHERE last = {?}', $this->id);
+ if ($res->fetchOneCell() > 0) {
+ return false;
+ }
+
+ return XDB::execute('DELETE FROM newsletter_issues
+ WHERE id = {?}', $this->id);
+ } else {
+ return false;
+ }
+ }
+
+ /** Schedule a mailing of this NL
+ * If the 'send_before' field was NULL, it is set to the current time.
+ * @return Boolean Whether the date could be set (false if trying to schedule an already sent NL)
+ */
+ public function scheduleMailing()
+ {
+ if ($this->state == self::STATE_NEW) {
+ $success = XDB::execute('UPDATE newsletter_issues
+ SET state = \'pending\', send_before = IFNULL(send_before, NOW())
+ WHERE id = {?}',
+ $this->id);
+ if ($success) {
+ $this->refresh();
+ }
+ return $success;
+ } else {
+ return false;
+ }
+ }
+
+ /** Cancel the scheduled mailing of this NL
+ * @return Boolean: whether the mailing could be cancelled.
+ */
+ public function cancelMailing()
+ {
+ if ($this->state == self::STATE_PENDING) {
+ $success = XDB::execute('UPDATE newsletter_issues
+ SET send_before = NULL, state = \'new\'
+ WHERE id = {?}', $this->id);
+ if ($success) {
+ $this->refresh();
+ }
+ return $success;
+ } else {
+ return false;
+ }
+ }
+
+ /** Helper function for smarty templates: is this issue editable ?
+ */
+ public function isEditable()
+ {
+ return $this->state == self::STATE_NEW;
+ }
+
+ /** Helper function for smarty templates: is the mailing of this issue scheduled ?
+ */
+ public function isPending()
+ {
+ return $this->state == self::STATE_PENDING;
+ }
+
+ /** Helper function for smarty templates: has this issue been sent ?
+ */
+ public function isSent()
+ {
+ return $this->state == self::STATE_SENT;
+ }
+
+ // }}}
+ // {{{ Navigation
+
+ private $id_prev = null;
+ private $id_next = null;
+ private $id_last = null;
+
+ /** Retrieve ID of the previous issue
+ * That value, once fetched, is cached in the private $id_prev variable.
+ * @return ID of the previous issue.
+ */
+ public function prev()
+ {
+ if (is_null($this->id_prev)) {
+ $this->id_prev = $this->selectId(XDB::format("ni.id < {?} ORDER BY ni.id DESC", $this->id));
+ }
+ return $this->id_prev;
+ }
+
+ /** Retrieve ID of the following issue
+ * That value, once fetched, is cached in the private $id_next variable.
+ * @return ID of the following issue.
+ */
+ public function next()
+ {
+ if (is_null($this->id_next)) {
+ $this->id_next = $this->selectId(XDB::format("ni.id > {?} ORDER BY ni.id", $this->id));
+ }
+ return $this->id_next;
+ }
+
+ /** Retrieve ID of the last issue
+ * That value, once fetched, is cached in the private $id_last variable.
+ * @return ID of the last issue.
+ */
+ public function last()
+ {
+ if (is_null($this->id_last)) {
+ $this->id_last = $this->nl->getIssue('last')->id;
}
+ return $this->id_last;
}
+ // }}}
+ // {{{ Edition, articles
+
+ const ERROR_INVALID_SHORTNAME = 'invalid_shortname';
+ const ERROR_INVALID_UFC = 'invalid_ufc';
+ const ERROR_SQL_SAVE = 'sql_error';
+
+ /** Save the global properties of this NL issue (title&co).
+ */
public function save()
{
- XDB::execute('UPDATE newsletter SET date={?},titre={?},titre_mail={?},head={?},short_name={?} WHERE id={?}',
- $this->_date, $this->_title, $this->_title_mail, $this->_head, $this->_shortname,$this->_id);
+ $errors = array();
+
+ // Fill the list of fields to update
+ $fields = array(
+ 'title' => $this->title,
+ 'mail_title' => $this->title_mail,
+ 'head' => $this->head,
+ 'signature' => $this->signature,
+ );
+
+ if ($this->isEditable()) {
+ $fields['date'] = $this->date;
+ if (!preg_match('/^[-a-z0-9]+$/i', $this->shortname) || is_numeric($this->shortname)) {
+ $errors[] = self::ERROR_INVALID_SHORTNAME;
+ } else {
+ $fields['short_name'] = $this->shortname;
+ }
+ if ($this->sufb->isValid() || $this->sufb->isEmpty()) {
+ $fields['sufb_json'] = json_encode($this->sufb->export()->dict());
+ } else {
+ $errors[] = self::ERROR_INVALID_UFC;
+ }
+
+ if ($this->nl->automaticMailingEnabled()) {
+ $fields['send_before'] = ($this->send_before ? $this->send_before : null);
+ }
+ }
+
+ if (count($errors)) {
+ return $errors;
+ }
+ $field_sets = array();
+ foreach ($fields as $key => $value) {
+ $field_sets[] = XDB::format($key . ' = {?}', $value);
+ }
+ XDB::execute('UPDATE newsletter_issues
+ SET ' . implode(', ', $field_sets) . '
+ WHERE id={?}',
+ $this->id);
+ if (XDB::affectedRows()) {
+ $this->refresh();
+ } else {
+ $errors[] = self::ERROR_SQL_SAVE;
+ }
+ return $errors;
}
+ /** Get an article by number
+ * @p $aid Article ID (among articles of the issue)
+ * @return A NLArticle object, or null if there is no article by that number
+ */
public function getArt($aid)
{
- foreach ($this->_arts as $key=>$artlist) {
- if (isset($artlist["a$aid"])) {
- return $artlist["a$aid"];
+ $this->fetchArticles();
+
+ foreach ($this->arts as $category => $artlist) {
+ if (isset($artlist[$aid])) {
+ return $artlist[$aid];
}
}
return null;
}
+ /** Save an article
+ * @p &$a A reference to a NLArticle object (will be modified once saved)
+ */
public function saveArticle(&$a)
{
- $a->_cid = ($a->_cid == 0) ? null : $a->_cid;
- if ($a->_aid >= 0) {
+ $this->fetchArticles();
+
+ // Prevent cid to be 0 (use NULL instead)
+ $a->cid = ($a->cid == 0) ? null : $a->cid;
+ if ($a->aid >= 0) {
+ // Article already exists in DB
XDB::execute('UPDATE newsletter_art
SET cid = {?}, pos = {?}, title = {?}, body = {?}, append = {?}
WHERE id = {?} AND aid = {?}',
- $a->_cid, $a->_pos, $a->_title, $a->_body, $a->_append, $this->_id, $a->_aid);
+ $a->cid, $a->pos, $a->title, $a->body, $a->append, $this->id, $a->aid);
} else {
+ // New article
XDB::startTransaction();
list($aid, $pos) = XDB::fetchOneRow('SELECT MAX(aid) AS aid, MAX(pos) AS pos
FROM newsletter_art AS a
WHERE a.id = {?}',
- $this->_id);
- $a->_aid = ++$aid;
- $a->_pos = ($a->_pos ? $a->_pos : ++$pos);
+ $this->id);
+ $a->aid = ++$aid;
+ $a->pos = ($a->pos ? $a->pos : ++$pos);
XDB::execute('INSERT INTO newsletter_art (id, aid, cid, pos, title, body, append)
VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?})',
- $this->_id, $a->_aid, $a->_cid, $a->_pos,
- $a->_title, $a->_body, $a->_append);
+ $this->id, $a->aid, $a->cid, $a->pos,
+ $a->title, $a->body, $a->append);
XDB::commit();
}
- $this->_arts['a' . $a->_aid] = $a;
+ // Update local ID of article
+ $this->arts[$a->aid] = $a;
}
+ /** Delete an article by its ID
+ * @p $aid ID of the article to delete
+ */
public function delArticle($aid)
{
- XDB::execute('DELETE FROM newsletter_art WHERE id={?} AND aid={?}', $this->_id, $aid);
- foreach ($this->_arts as $key=>$art) {
- unset($this->_arts[$key]["a$aid"]);
+ $this->fetchArticles();
+
+ XDB::execute('DELETE FROM newsletter_art WHERE id={?} AND aid={?}', $this->id, $aid);
+ foreach ($this->arts as $key=>$art) {
+ unset($this->arts[$key][$aid]);
}
}
- protected function assignData(&$smarty)
+ // }}}
+ // {{{ Display
+
+ /** Retrieve the title of this issue
+ * @p $mail Whether we want the normal title or the email subject
+ * @return Title of the issue
+ */
+ public function title($mail = false)
{
- $smarty->assign_by_ref('nl', $this);
+ return $mail ? $this->title_mail : $this->title;
}
- protected function setSent()
+ /** Retrieve the head of this issue
+ * @p $user User for <dear> customization (may be null: no customization)
+ * @p $type Either 'text' or 'html'
+ * @return Formatted head of the issue.
+ */
+ public function head($user = null, $type = 'text')
{
- XDB::execute("UPDATE newsletter SET bits='sent' WHERE id={?}", $this->_id);
+ if (is_null($user)) {
+ return $this->head;
+ } else {
+ $head = $this->head;
+ $head = str_replace(array('<cher>', '<prenom>', '<nom>'),
+ array(($user->isFemale() ? 'Chère' : 'Cher'), $user->displayName(), ''),
+ $head);
+ return format_text($head, $type, 2, 64);
+ }
}
- static public function subscriptionState($uid = null)
+ /** Retrieve the formatted signature of this issue.
+ */
+ public function signature($type = 'text')
{
- $user = is_null($uid) ? S::v('uid') : $uid;
- $res = XDB::query("SELECT 1
- FROM newsletter_ins
- WHERE uid={?}", $user);
- return $res->fetchOneCell();
+ return format_text($this->signature, $type, 2, 64);
+ }
+
+ /** Get the title of a given category
+ * @p $cid ID of the category to retrieve
+ * @return Name of the category
+ */
+ public function category($cid)
+ {
+ return $this->nl->cats[$cid];
+ }
+
+ /** Add required data to the given $page for proper CSS display
+ * @p $page Smarty object
+ * @return Either 'true' (if CSS was added to a page) or the raw CSS to add (when $page is null)
+ */
+ public function css(&$page = null)
+ {
+ if (!is_null($page)) {
+ $page->addCssLink($this->nl->cssFile());
+ return true;
+ } else {
+ $css = file_get_contents(dirname(__FILE__) . '/../htdocs/css/' . $this->nl->cssFile());
+ return preg_replace('@/\*.*?\*/@us', '', $css);
+ }
+ }
+
+ /** Set up a smarty page for a 'text' mode render of the issue
+ * @p $page Smarty object (using the $this->nl->tplFile() template)
+ * @p $user User to use when rendering the template
+ */
+ public function toText(&$page, $user)
+ {
+ $this->fetchArticles();
+
+ $this->css($page);
+ $page->assign('prefix', null);
+ $page->assign('is_mail', false);
+ $page->assign('mail_part', 'text');
+ $page->assign('user', $user);
+ $page->assign('hash', null);
+ $this->assignData($page);
}
- static public function unsubscribe($uid = null)
+ /** Set up a smarty page for a 'html' mode render of the issue
+ * @p $page Smarty object (using the $this->nl->tplFile() template)
+ * @p $user User to use when rendering the template
+ */
+ public function toHtml(&$page, $user)
{
- $user = is_null($uid) ? S::v('uid') : $uid;
- XDB::execute("DELETE FROM newsletter_ins
- WHERE uid={?}", $user);
+ $this->fetchArticles();
+
+ $this->css($page);
+ $page->assign('prefix', $this->nl->prefix() . '/show/' . $this->id());
+ $page->assign('is_mail', false);
+ $page->assign('mail_part', 'html');
+ $page->assign('user', $user);
+ $page->assign('hash', null);
+ $this->assignData($page);
}
- static public function subscribe($uid = null)
+ /** Set all 'common' data for the page (those which are required for both web and email rendering)
+ * @p $smarty Smarty object (e.g page) which should be filled
+ */
+ protected function assignData(&$smarty)
{
- $user = is_null($uid) ? S::v('uid') : $uid;
- XDB::execute('INSERT IGNORE INTO newsletter_ins (uid, last, hash)
- VALUES ({?}, NULL, NULL)', $user);
+ $this->fetchArticles();
+
+ $smarty->assign_by_ref('issue', $this);
+ $smarty->assign_by_ref('nl', $this->nl);
}
- protected function subscriptionWhere()
+ // }}}
+ // {{{ Mailing
+
+ /** Retrieve the 'Send before' date, in a clean format.
+ */
+ public function getSendBeforeDate()
{
- return '1';
+ return strftime('%Y-%m-%d', strtotime($this->send_before));
}
- static public function create()
+ /** Retrieve the 'Send before' time (i.e hour), in a clean format.
+ */
+ public function getSendBeforeTime()
{
- XDB::execute("INSERT INTO newsletter
- SET bits='new',date=NOW(),titre='to be continued',titre_mail='to be continued'");
+ return strtotime($this->send_before);
}
- static public function listSent()
+ /** Create a hash based on some additional data
+ * $line Line-specific data (to prevent two hashes generated at the same time to be the same)
+ */
+ protected static function createHash($line)
{
- $res = XDB::query("SELECT IF(short_name IS NULL, id,short_name) as id,date,titre_mail AS titre
- FROM newsletter
- WHERE bits!='new'
- ORDER BY date DESC");
- return $res->fetchAllAssoc();
+ $hash = implode(time(), $line) . rand();
+ $hash = md5($hash);
+ return $hash;
}
- static public function listAll()
+ /** Send this issue to the given user, reusing an existing hash if provided.
+ * @p $user User to whom the issue should be mailed
+ * @p $hash Optional hash to use in the 'unsubscribe' link; if null, another one will be generated.
+ */
+ public function sendTo($user, $hash = null)
{
- $res = XDB::query("SELECT IF(short_name IS NULL, id,short_name) as id,date,titre_mail AS titre
- FROM newsletter
- ORDER BY date DESC");
- return $res->fetchAllAssoc();
+ $this->fetchArticles();
+
+ if (is_null($hash)) {
+ $hash = XDB::fetchOneCell("SELECT hash
+ FROM newsletter_ins
+ WHERE uid = {?} AND nlid = {?}",
+ $user->id(), $this->nl->id);
+ }
+ if (is_null($hash)) {
+ $hash = self::createHash(array($user->displayName(), $user->fullName(),
+ $user->isFemale(), $user->isEmailFormatHtml(),
+ rand(), "X.org rulez"));
+ XDB::execute("UPDATE newsletter_ins as ni
+ SET ni.hash = {?}
+ WHERE ni.uid = {?} AND ni.nlid = {?}",
+ $hash, $user->id(), $this->nl->id);
+ }
+
+ $mailer = new PlMailer($this->nl->tplFile());
+ $this->assignData($mailer);
+ $mailer->assign('is_mail', true);
+ $mailer->assign('user', $user);
+ $mailer->assign('prefix', null);
+ $mailer->assign('hash', $hash);
+ $mailer->sendTo($user);
}
+
+ /** Select a subset of subscribers which should receive the newsletter.
+ * NL-Specific selections (not yet received, is subscribed) are done when sending.
+ * @return A PlFilterCondition.
+ */
+ protected function getRecipientsUFC()
+ {
+ return $this->sufb->getUFC();
+ }
+
+ /** Check whether a given user may see this issue.
+ * @p $user User whose access should be checked
+ * @return Whether he may access the issue
+ */
+ public function checkUser($user = null)
+ {
+ if ($user == null) {
+ $user = S::user();
+ }
+ $uf = new UserFilter($this->getRecipientsUFC());
+ return $uf->checkUser($user);
+ }
+
+ /** Sent this issue to all valid recipients
+ * @return Number of issues sent
+ */
+ public function sendToAll()
+ {
+ $this->fetchArticles();
+
+ XDB::execute('UPDATE newsletter_issues
+ SET state = \'sent\', date=CURDATE()
+ WHERE id = {?}',
+ $this->id);
+
+ $ufc = new PFC_And($this->getRecipientsUFC(), new UFC_NLSubscribed($this->nl->id, $this->id), new UFC_HasEmailRedirect());
+ $emailsCount = 0;
+ $uf = new UserFilter($ufc, new PlLimit(self::BATCH_SIZE));
+
+ while (true) {
+ $sent = array();
+ $users = $uf->getUsers();
+ if (count($users) == 0) {
+ return $emailsCount;
+ }
+ foreach ($users as $user) {
+ $sent[] = $user->id();
+ $this->sendTo($user, $hash);
+ ++$emailsCount;
+ }
+ XDB::execute("UPDATE newsletter_ins
+ SET last = {?}
+ WHERE nlid = {?} AND uid IN {?}", $this->id, $this->nl->id, $sent);
+
+ sleep(60);
+ }
+ return $emailsCount;
+ }
+
+ // }}}
}
// }}}
class NLArticle
{
+ // Maximum number of lines per article
+ const MAX_LINES_PER_ARTICLE = 9;
+
// {{{ properties
- var $_aid;
- var $_cid;
- var $_pos;
- var $_title;
- var $_body;
- var $_append;
+ public $aid;
+ public $cid;
+ public $pos;
+ public $title;
+ public $body;
+ public $append;
// }}}
// {{{ constructor
function __construct($title='', $body='', $append='', $aid=-1, $cid=0, $pos=0)
{
- $this->_body = $body;
- $this->_title = $title;
- $this->_append = $append;
- $this->_aid = $aid;
- $this->_cid = $cid;
- $this->_pos = $pos;
+ $this->body = $body;
+ $this->title = $title;
+ $this->append = $append;
+ $this->aid = $aid;
+ $this->cid = $cid;
+ $this->pos = $pos;
}
// }}}
// {{{ function title()
public function title()
- { return trim($this->_title); }
+ { return trim($this->title); }
// }}}
// {{{ function body()
public function body()
- { return trim($this->_body); }
+ { return trim($this->body); }
// }}}
// {{{ function append()
public function append()
- { return trim($this->_append); }
+ { return trim($this->append); }
// }}}
// {{{ function toText()
public function toText($hash = null, $login = null)
{
$title = '*'.$this->title().'*';
- $body = MiniWiki::WikiToText($this->_body, true);
- $app = MiniWiki::WikiToText($this->_append,false,4);
+ $body = MiniWiki::WikiToText($this->body, true);
+ $app = MiniWiki::WikiToText($this->append, false, 4);
$text = trim("$title\n\n$body\n\n$app")."\n";
if (!is_null($hash) && !is_null($login)) {
$text = str_replace('%HASH%', "$hash/$login", $text);
public function toHtml($hash = null, $login = null)
{
- $title = "<h2 class='xorg_nl'><a id='art{$this->_aid}'></a>".pl_entities($this->title()).'</h2>';
- $body = MiniWiki::WikiToHTML($this->_body);
- $app = MiniWiki::WikiToHTML($this->_append);
+ $title = "<h2 class='xorg_nl'><a id='art{$this->aid}'></a>".pl_entities($this->title()).'</h2>';
+ $body = MiniWiki::WikiToHTML($this->body);
+ $app = MiniWiki::WikiToHTML($this->append);
$art = "$title\n";
$art .= "<div class='art'>\n$body\n";
public function check()
{
- $text = MiniWiki::WikiToText($this->_body);
+ $text = MiniWiki::WikiToText($this->body);
$arr = explode("\n",wordwrap($text,68));
$c = 0;
foreach ($arr as $line) {
$c++;
}
}
- return $c<9;
+ return $c < self::MAX_LINES_PER_ARTICLE;
}
// }}}
// {{{ function parseUrlsFromArticle()
- private function parseUrlsFromArticle()
+ protected function parseUrlsFromArticle()
{
$email_regex = '([a-z0-9.\-+_\$]+@([\-.+_]?[a-z0-9])+)';
$url_regex = '((https?|ftp)://[a-zA-Z0-9._%#+/?=&~-]+)';
// }}}
+// {{{ Functions
+
+function format_text($input, $format, $indent = 0, $width = 68)
+{
+ if ($format == 'text') {
+ return MiniWiki::WikiToText($input, true, $indent, $width, "title");
+ }
+ return MiniWiki::WikiToHTML($input, "title");
+}
+
+// function enriched_to_text($input,$html=false,$just=false,$indent=0,$width=68)
+
+// }}}
+
// vim:set et sw=4 sts=4 sws=4 enc=utf-8:
?>
protected function handle_editor()
{
- $this->art->_body = Env::v('nl_body');
- $this->art->_title = Env::v('nl_title');
- $this->art->_append = Env::v('nl_append');
+ $this->art->body = Env::v('nl_body');
+ $this->art->title = Env::v('nl_title');
+ $this->art->append = Env::v('nl_append');
return true;
}
public function commit()
{
- $nl = new NewsLetter();
+ $nl = NewsLetter::forGroup(NewsLetter::GROUP_XORG)->getPendingIssue();
$nl->saveArticle($this->art);
return true;
}
{
$page->changeTpl('admin/validation.tpl');
$page->setTitle('Administration - Valider une demande');
- $page->addCssLink('nl.css');
+ $page->addCssLink('nl.Polytechnique.org.css');
if ($action == 'edit' && !is_null($id)) {
$page->assign('preview_id', $id);
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
***************************************************************************/
-class AXLetterModule extends PLModule
+Platal::load('newsletter');
+
+class AXLetterModule extends NewsletterModule
{
function handlers()
{
return array(
- 'ax' => $this->make_hook('index', AUTH_COOKIE),
- 'ax/out' => $this->make_hook('out', AUTH_PUBLIC),
- 'ax/show' => $this->make_hook('show', AUTH_COOKIE),
- 'ax/edit' => $this->make_hook('submit', AUTH_MDP),
- 'ax/edit/cancel' => $this->make_hook('cancel', AUTH_MDP),
- 'ax/edit/valid' => $this->make_hook('valid', AUTH_MDP),
- 'admin/axletter' => $this->make_hook('admin', AUTH_MDP, 'admin'),
+ 'ax' => $this->make_hook('nl', AUTH_COOKIE),
+ 'ax/out' => $this->make_hook('out', AUTH_PUBLIC),
+ 'ax/show' => $this->make_hook('nl_show', AUTH_COOKIE),
+ 'ax/admin' => $this->make_hook('admin_nl', AUTH_MDP),
+ 'ax/admin/edit' => $this->make_hook('admin_nl_edit', AUTH_MDP),
+ 'ax/admin/edit/valid' => $this->make_hook('admin_nl_valid', AUTH_MDP),
+ 'ax/admin/edit/cancel' => $this->make_hook('admin_nl_cancel', AUTH_MDP),
+ 'ax/admin/edit/delete' => $this->make_hook('admin_nl_delete', AUTH_MDP),
);
}
+ protected function getNl()
+ {
+ require_once 'newsletter.inc.php';
+ return NewsLetter::forGroup(NewsLetter::GROUP_AX);
+ }
+
function handler_out(&$page, $hash = null)
{
if (!$hash) {
if (!S::logged()) {
return PL_DO_AUTH;
- } else {
- return $this->handler_index($page, 'out');
- }
- }
- $this->load('axletter.inc.php');
- $page->changeTpl('axletter/unsubscribe.tpl');
- $page->assign('success', AXLetter::unsubscribe($hash, true));
- }
-
- function handler_index(&$page, $action = null)
- {
- $this->load('axletter.inc.php');
-
- $page->changeTpl('axletter/index.tpl');
- $page->setTitle('Envois de l\'AX');
-
- switch ($action) {
- case 'in': AXLetter::subscribe(); break;
- case 'out': AXLetter::unsubscribe(); break;
- }
-
- $perm = AXLetter::hasPerms();
- if ($perm) {
- $res = XDB::query("SELECT * FROM axletter_ins");
- $page->assign('count', $res->numRows());
- $page->assign('new', AXLetter::awaiting());
- }
- $page->assign('axs', AXLetter::subscriptionState());
- $page->assign('ax_list', AXLetter::listSent());
- $page->assign('ax_rights', $perm);
- }
-
- function handler_submit(&$page, $action = null)
- {
- $this->load('axletter.inc.php');
- if (!AXLetter::hasPerms()) {
- return PL_FORBIDDEN;
- }
-
- $page->changeTpl('axletter/edit.tpl');
-
- $saved = Post::i('saved');
- $new = false;
- $id = Post::i('id');
- $short_name = trim(Post::v('short_name'));
- $subject = trim(Post::v('subject'));
- $title = trim(Post::v('title'));
- $body = rtrim(Post::v('body'));
- $signature = trim(Post::v('signature'));
- $promo_min = Post::i('promo_min');
- $promo_max = Post::i('promo_max');
- $subset_to = preg_split("/[ ,;\:\n\r]+/", Post::v('subset_to'), -1, PREG_SPLIT_NO_EMPTY);
- $subset = (count($subset_to) > 0);
- $subset_rm = Post::b('subset_rm');
- $echeance = Post::has('echeance_date') ?
- preg_replace('/^(\d\d\d\d)(\d\d)(\d\d)$/', '\1-\2-\3', Post::v('echeance_date')) . ' ' . Post::v('echeance_time')
- : Post::v('echeance');
- $echeance_date = Post::v('echeance_date');
- $echeance_time = Post::v('echeance_time');
-
- if (!$id) {
- $res = XDB::query("SELECT * FROM axletter WHERE FIND_IN_SET('new', bits)");
- if ($res->numRows()) {
- extract($res->fetchOneAssoc(), EXTR_OVERWRITE);
- $subset_to = ($subset ? explode("\n", $subset) : array());
- $subset = (count($subset_to) > 0);
- $saved = true;
- } else {
- XDB::execute("INSERT INTO axletter SET id = NULL");
- $id = XDB::insertId();
- }
- if (!$echeance || $echeance == '0000-00-00 00:00:00') {
- $saved = false;
- $new = true;
- }
- } elseif (Post::has('valid')) {
- S::assert_xsrf_token();
-
- if (!$subject && $title) {
- $subject = $title;
- }
- if (!$title && $subject) {
- $title = $subject;
- }
- if (!$subject || !$title || !$body) {
- $page->trigError("L'article doit avoir un sujet et un contenu");
- Post::kill('valid');
- }
- if (($promo_min > $promo_max && $promo_max != 0)||
- ($promo_min != 0 && ($promo_min <= 1900 || $promo_min >= 2020)) ||
- ($promo_max != 0 && ($promo_max <= 1900 || $promo_max >= 2020)))
- {
- $page->trigError("L'intervalle de promotions n'est pas valide");
- Post::kill('valid');
- }
- if (empty($short_name)) {
- $page->trigError("L'annonce doit avoir un nom raccourci pour simplifier la navigation dans les archives");
- Post::kill('valid');
- } elseif (!preg_match('/^[a-z][-a-z0-9]*[a-z0-9]$/', $short_name)) {
- $page->trigError("Le nom raccourci n'est pas valide, il doit comporter au moins 2 caractères et n'être composé "
- . "que de chiffres, lettres et tirets");
- Post::kill('valid');
- } elseif ($short_name != Post::v('old_short_name')) {
- $res = XDB::query("SELECT id FROM axletter WHERE short_name = {?}", $short_name);
- if ($res->numRows() && $res->fetchOneCell() != $id) {
- $page->trigError("Le nom $short_name est déjà utilisé, merci d'en choisir un autre");
- $short_name = Post::v('old_short_name');
- if (empty($short_name)) {
- Post::kill('valid');
- }
- }
- }
-
- switch (@Post::v('valid')) {
- case 'Vérifier les emails':
- // Same as 'preview', but performs a test of all provided emails
- if ($subset) {
- require_once 'emails.inc.php';
- $ids = ids_from_mails($subset_to);
- $nb_error = 0;
- foreach ($subset_to as $e) {
- if (!array_key_exists($e, $ids)) {
- if ($nb_error == 0) {
- $page->trigError("Emails inconnus :");
- }
- $nb_error++;
- $page->trigError($e);
- }
- }
- if ($nb_error == 0) {
- if (count($subset_to) == 1) {
- $page->trigSuccess("L'email soumis a été reconnu avec succès.");
- } else {
- $page->trigSuccess("Les " . count($subset_to) . " emails soumis ont été reconnus avec succès.");
- }
- } else {
- $page->trigError("Total : $nb_error erreur" . ($nb_error > 1 ? "s" : "") . " sur " . count($subset_to) . " adresses mail soumises.");
- }
- $page->trigSuccess("Les adresses soumises correspondent à un total de " . count(array_unique($ids)) . " camarades.");
- }
- // No break here, since Vérifier is a subcase of Aperçu.
- case 'Aperçu':
- $this->load('axletter.inc.php');
- $al = new AXLetter(array($id, $short_name, $subject, $title, $body, $signature,
- $promo_min, $promo_max, $subset, $subset_rm, $echeance, 0, 'new'));
- $al->toHtml($page, S::user());
- break;
-
- case 'Confirmer':
- XDB::execute('INSERT INTO axletter (id, short_name, subject, title, body, signature,
- promo_min, promo_max, echeance, subset, subset_rm)
- VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})
- ON DUPLICATE KEY UPDATE short_name = VALUES(short_name), subject = VALUES(subject), title = VALUES(title),
- body = VALUES(body), signature = VALUES(signature), promo_min = VALUES(promo_min),
- promo_max = VALUES(promo_max), echeance = VALUES(echeance), subset = VALUES(subset),
- subset_rm = VALUES(subset_rm)',
- $id, $short_name, $subject, $title, $body, $signature, $promo_min, $promo_max, $echeance,
- $subset ? implode("\n", $subset_to) : null, $subset_rm);
- if (!$saved) {
- global $globals;
- $mailer = new PlMailer();
- $mailer->setFrom("support@" . $globals->mail->domain);
- $mailer->setSubject("Un nouveau projet d'email de l'AX vient d'être proposé");
- $mailer->setTxtBody("Un nouvel email vient d'être rédigé en prévision d'un envoi prochain. Vous pouvez "
- . "le modifier jusqu'à ce qu'il soit verrouillé pour l'envoi\n\n"
- . "Le sujet de l'email : $subject\n"
- . "L'échéance d'envoi est fixée à $echeance.\n"
- . "L'email pourra néanmoins partir avant cette échéance si un administrateur de "
- . "Polytechnique.org le valide.\n\n"
- . "Pour modifier, valider ou annuler l'email :\n"
- . "https://www.polytechnique.org/ax/edit\n"
- . "-- \n"
- . "Association Polytechnique.org\n");
- $users = User::getBulkUsersWithUIDs(XDB::fetchColumn('SELECT uid
- FROM axletter_rights'));
- foreach ($users as $user) {
- $mailer->addTo($user);
- }
- $mailer->send();
- }
- $saved = true;
- $echeance_date = null;
- $echeance_time = null;
- pl_redirect('ax');
- break;
- }
- }
- $page->assign('id', $id);
- $page->assign('short_name', $short_name);
- $page->assign('subject', $subject);
- $page->assign('title', $title);
- $page->assign('body', $body);
- $page->assign('signature', $signature);
- $page->assign('promo_min', $promo_min);
- $page->assign('promo_max', $promo_max);
- $page->assign('subset_to', implode("\n", $subset_to));
- $page->assign('subset', $subset);
- $page->assign('subset_rm', $subset_rm);
- $page->assign('echeance', $echeance);
- $page->assign('echeance_date', $echeance_date);
- $page->assign('echeance_time', $echeance_time);
- $page->assign('saved', $saved);
- $page->assign('new', $new);
- $page->assign('is_xorg', S::admin());
-
- if (!$saved) {
- $select = '';
- for ($i = 0 ; $i < 24 ; $i++) {
- $stamp = sprintf('%02d:00:00', $i);
- if ($stamp == $echeance_time) {
- $sel = ' selected="selected"';
- } else {
- $sel = '';
- }
- $select .= "<option value=\"$stamp\"$sel>{$i}h</option>\n";
- }
- $page->assign('echeance_time', $select);
- }
- }
-
- function handler_cancel(&$page, $force = null)
- {
- $this->load('axletter.inc.php');
- if (!AXLetter::hasPerms() || !S::has_xsrf_token()) {
- return PL_FORBIDDEN;
- }
-
- $al = AXLetter::awaiting();
- if (!$al) {
- $page->kill("Aucune lettre en attente");
- return;
- }
- if (!$al->invalid()) {
- $page->kill("Une erreur est survenue lors de l'annulation de l'envoi");
- return;
- }
-
- $page->killSuccess("L'envoi de l'annonce {$al->title()} est annulé.");
- }
-
- function handler_valid(&$page, $force = null)
- {
- $this->load('axletter.inc.php');
- if (!AXLetter::hasPerms() || !S::has_xsrf_token()) {
- return PL_FORBIDDEN;
- }
-
- $al = AXLetter::awaiting();
- if (!$al) {
- $page->kill("Aucune lettre en attente");
- return;
- }
- if (!$al->valid()) {
- $page->kill("Une erreur est survenue lors de la validation de l'envoi");
- return;
- }
-
- $page->killSuccess("L'envoi de l'annonce aura lieu dans l'heure qui vient.");
- }
-
- function handler_show(&$page, $nid = 'last')
- {
- $this->load('axletter.inc.php');
- $page->changeTpl('axletter/show.tpl');
-
- try {
- $nl = new AXLetter($nid);
- $user =& S::user();
- if (Get::has('text')) {
- $nl->toText($page, $user);
- } else {
- $nl->toHtml($page, $user);
- }
- if (Post::has('send')) {
- $nl->sendTo($user);
- }
- } catch (MailNotFound $e) {
- return PL_NOT_FOUND;
- }
- }
-
- function handler_admin(&$page, $action = null, $uid = null)
- {
- $this->load('axletter.inc.php');
- if (Post::has('action')) {
- $action = Post::v('action');
- $uid = Post::v('uid');
- }
- if ($uid) {
- S::assert_xsrf_token();
-
- $uids = preg_split('/ *[,;\: ] */', $uid);
- foreach ($uids as $uid) {
- switch ($action) {
- case 'add':
- $res = AXLetter::grantPerms($uid);
- break;
- case 'del';
- $res = AXLetter::revokePerms($uid);
- break;
- }
- if (!$res) {
- $page->trigError("Personne ne correspond à l'identifiant '$uid'");
- }
- }
- }
-
- $page->changeTpl('axletter/admin.tpl');
- $page->assign('admins', User::getBulkUsersWithUIDs(XDB::fetchColumn('SELECT uid
- FROM axletter_rights')));
-
- $importer = new CSVImporter('axletter_ins');
- $importer->registerFunction('uid', 'email vers Id X.org', array($this, 'idFromMail'));
- $importer->forceValue('hash', array($this, 'createHash'));
- $importer->apply($page, "admin/axletter", array('uid', 'email', 'prenom', 'nom', 'promo', 'flag', 'hash'));
- }
-
- function idFromMail($line, $key, $relation = null)
- {
- static $field;
- global $globals;
- if (!isset($field)) {
- $field = array('email', 'mail', 'login', 'bestalias', 'forlife', 'flag');
- foreach ($field as $fld) {
- if (isset($line[$fld])) {
- $field = $fld;
- break;
- }
}
}
- $uf = new UserFilter(new UFC_Email($line[$field]));
- $id = $uf->getUIDs();
- return count($id) == 1 ? $id[0] : 0;
- }
-
- function createHash($line, $key, $relation)
- {
- $hash = implode(time(), $line) . rand();
- $hash = md5($hash);
- return $hash;
+ return $this->handler_nl($page, 'out', $hash);
}
}
+++ /dev/null
-<?php
-/***************************************************************************
- * Copyright (C) 2003-2011 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("massmailer.inc.php");
-
-class AXLetter extends MassMailer
-{
- public $_body;
- public $_signature;
- public $_promo_min;
- public $_promo_max;
- public $_subset;
- public $_subset_to;
- public $_subset_rm;
- public $_echeance;
- public $_date;
- public $_bits;
-
- function __construct($id)
- {
- parent::__construct('axletter/letter.mail.tpl', 'ax.css', 'ax/show', 'axletter', 'axletter_ins');
- $this->_head = '<cher> <prenom>,';
-
- if (!is_array($id)) {
- if ($id == 'last') {
- $res = XDB::query("SELECT *
- FROM axletter
- WHERE FIND_IN_SET('sent', bits)
- ORDER BY id DESC");
- } else {
- $res = XDB::query("SELECT *
- FROM axletter
- WHERE id = {?} OR short_name = {?}", $id, $id);
- }
- if (!$res->numRows()) {
- throw new MailNotFound();
- }
- $id = $res->fetchOneRow();
- }
- list($this->_id, $this->_shortname, $this->_title_mail, $this->_title,
- $this->_body, $this->_signature, $this->_promo_min, $this->_promo_max,
- $this->_subset_to, $this->_subset_rm, $this->_echeance, $this->_date, $this->_bits) = $id;
- if ($this->_date == '0000-00-00') {
- $this->_date = 0;
- }
- $this->_subset_to = ($this->_subset_to ? explode("\n", $this->_subset_to) : null);
- $this->_subset = (count($this->_subset_to) > 0);
- }
-
- protected function assignData(&$smarty)
- {
- $smarty->assign_by_ref('am', $this);
- }
-
- public function body($format)
- {
- return format_text($this->_body, $format);
- }
-
- public function signature($format)
- {
- return format_text($this->_signature, $format, 10);
- }
-
- public function valid()
- {
- return XDB::execute("UPDATE axletter
- SET echeance = NOW()
- WHERE id = {?}", $this->_id);
- }
-
- public function invalid()
- {
- return XDB::execute("UPDATE axletter
- SET bits = 'invalid', date = CURDATE()
- WHERE id = {?}", $this->_id);
- }
-
- protected function setSent()
- {
- XDB::execute("UPDATE axletter
- SET bits='sent', date=CURDATE()
- WHERE id={?}", $this->_id);
- }
-
- static public function subscriptionState($uid = null)
- {
- $user = is_null($uid) ? S::v('uid') : $uid;
- $res = XDB::query("SELECT 1
- FROM axletter_ins
- WHERE uid={?}", $user);
- return $res->fetchOneCell();
- }
-
- static public function unsubscribe($uid = null, $hash = false)
- {
- $user = is_null($uid) ? S::v('uid') : $uid;
- $field = !$hash ? 'uid' : 'hash';
- if (is_null($uid) && $hash) {
- return false;
- }
- $res = XDB::query("SELECT uid
- FROM axletter_ins
- WHERE $field={?}", $user);
- if ($res->numRows() != 1) {
- return false;
- }
- XDB::execute("DELETE FROM axletter_ins
- WHERE $field = {?}", $user);
- return true;
- }
-
- static public function subscribe($uid = null)
- {
- $user = is_null($uid) ? S::v('uid') : $uid;
- XDB::execute('INSERT IGNORE INTO axletter_ins (uid, last)
- VALUES ({?}, 0)', $user);
- }
-
- static public function hasPerms()
- {
- if (S::admin()) {
- return true;
- }
- $res = XDB::query("SELECT COUNT(*)
- FROM axletter_rights
- WHERE uid = {?}", S::i('uid'));
- return ($res->fetchOneCell() > 0);
- }
-
- static public function grantPerms($uid)
- {
- if (!is_numeric($uid)) {
- $res = XDB::query("SELECT uid FROM aliases WHERE alias = {?}", $uid);
- $uid = $res->fetchOneCell();
- }
- if (!$uid) {
- return false;
- }
- return XDB::execute("INSERT IGNORE INTO axletter_rights SET uid = {?}", $uid);
- }
-
- static public function revokePerms($uid)
- {
- if (!is_numeric($uid)) {
- $res = XDB::query("SELECT uid FROM aliases WHERE alias = {?}", $uid);
- $uid = $res->fetchOneCell();
- }
- if (!$uid) {
- return false;
- }
- return XDB::execute("DELETE FROM axletter_rights WHERE uid = {?}", $uid);
- }
-
- protected function subscriptionWhere()
- {
- if (!$this->_promo_min && !$this->_promo_max && !$this->_subset) {
- return '1';
- }
- /* TODO: refines this filter on promotions by using userfilter. */
- $where = array();
- if ($this->_promo_min) {
- $where[] = "((ni.uid = 0 AND ni.promo >= {$this->_promo_min}) OR (ni.uid != 0 AND pd.promo >= 'X{$this->_promo_min}'))";
- }
- if ($this->_promo_max) {
- $where[] = "((ni.uid = 0 AND ni.promo <= {$this->_promo_max}) OR (ni.uid != 0 AND pd.promo <= 'X{$this->_promo_max}'))";
- }
- if ($this->_subset) {
- require_once("emails.inc.php");
- $ids = ids_from_mails($this->_subset_to);
- $ids_list = implode(',', $ids);
- if(count($ids) > 0) {
- if ($this->_subset_rm) {
- $where[] = "ni.uid NOT IN ($ids_list)";
- } else {
- $where[] = "ni.uid IN ($ids_list)";
- }
- } else {
- // No valid email
- $where[] = "0";
- }
- }
- return implode(' AND ', $where);
- }
-
- static public function awaiting()
- {
- $res = XDB::query("SELECT *
- FROM axletter
- WHERE FIND_IN_SET('new', bits)");
- if ($res->numRows()) {
- return new AXLetter($res->fetchOneRow());
- }
- return null;
- }
-
- static public function toSend()
- {
- $res = XDB::query("SELECT *
- FROM axletter
- WHERE FIND_IN_SET('new', bits) AND echeance <= NOW() AND echeance != 0");
- if ($res->numRows()) {
- return new AXLetter($res->fetchOneRow());
- }
- return null;
- }
-
- static public function listSent()
- {
- $res = XDB::query("SELECT IF(short_name IS NULL, id, short_name) as id, date, subject AS titre
- FROM axletter
- WHERE NOT FIND_IN_SET('new', bits) AND NOT FIND_IN_SET('invalid', bits)
- ORDER BY date DESC");
- return $res->fetchAllAssoc();
- }
-
- static public function listAll()
- {
- $res = XDB::query("SELECT IF(short_name IS NULL, id, short_name) as id, date, subject AS titre
- FROM axletter
- ORDER BY date DESC");
- return $res->fetchAllAssoc();
- }
-}
-
-// vim:set et sw=4 sts=4 sws=4 enc=utf-8:
-?>
function handlers()
{
return array(
- 'nl' => $this->make_hook('nl', AUTH_COOKIE),
- 'nl/show' => $this->make_hook('nl_show', AUTH_COOKIE),
- 'nl/submit' => $this->make_hook('nl_submit', AUTH_MDP),
- 'admin/newsletter' => $this->make_hook('admin_nl', AUTH_MDP, 'admin'),
- 'admin/newsletter/categories' => $this->make_hook('admin_nl_cat', AUTH_MDP, 'admin'),
- 'admin/newsletter/edit' => $this->make_hook('admin_nl_edit', AUTH_MDP, 'admin'),
+ 'nl' => $this->make_hook('nl', AUTH_COOKIE),
+ 'nl/show' => $this->make_hook('nl_show', AUTH_COOKIE),
+ 'nl/submit' => $this->make_hook('nl_submit', AUTH_MDP),
+ 'admin/nls' => $this->make_hook('admin_nl_groups', AUTH_MDP, 'admin'),
+ 'admin/newsletter' => $this->make_hook('admin_nl', AUTH_MDP, 'admin'),
+ 'admin/newsletter/categories' => $this->make_hook('admin_nl_cat', AUTH_MDP, 'admin'),
+ 'admin/newsletter/edit' => $this->make_hook('admin_nl_edit', AUTH_MDP, 'admin'),
+ 'admin/newsletter/edit/delete' => $this->make_hook('delete', AUTH_MDP, 'admin'),
+ // Automatic mailing is disabled for X.org NL
+// 'admin/newsletter/edit/cancel' => $this->make_hook('cancel', AUTH_MDP, 'admin'),
+// 'admin/newsletter/edit/valid' => $this->make_hook('valid', AUTH_MDP, 'admin'),
);
}
- function handler_nl(&$page, $action = null)
+ /** This function should return the adequate NewsLetter object for the current module.
+ */
+ protected function getNl()
{
require_once 'newsletter.inc.php';
+ return NewsLetter::forGroup(NewsLetter::GROUP_XORG);
+ }
+
+ function handler_nl(&$page, $action = null, $hash = null)
+ {
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
$page->changeTpl('newsletter/index.tpl');
$page->setTitle('Lettres mensuelles');
switch ($action) {
- case 'out': NewsLetter::unsubscribe(); break;
- case 'in': NewsLetter::subscribe(); break;
+ case 'out': $nl->unsubscribe($hash, $hash != null); break;
+ case 'in': $nl->subscribe(); break;
default: ;
}
- $page->assign('nls', NewsLetter::subscriptionState());
- $page->assign('nl_list', NewsLetter::listSent());
+ $page->assign_by_ref('nl', $nl);
+ $page->assign('nls', $nl->subscriptionState());
+ $page->assign('nl_list', $nl->listSentIssues(true));
}
function handler_nl_show(&$page, $nid = 'last')
{
$page->changeTpl('newsletter/show.tpl');
-
- require_once 'newsletter.inc.php';
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
try {
- $nl = new NewsLetter($nid);
+ $issue = $nl->getIssue($nid);
$user =& S::user();
if (Get::has('text')) {
- $nl->toText($page, $user);
+ $issue->toText($page, $user);
} else {
- $nl->toHtml($page, $user);
+ $issue->toHtml($page, $user);
}
if (Post::has('send')) {
- $nl->sendTo($user);
+ $issue->sendTo($user);
}
} catch (MailNotFound $e) {
return PL_NOT_FOUND;
{
$page->changeTpl('newsletter/submit.tpl');
- require_once 'newsletter.inc.php';
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
+
$wp = new PlWikiPage('Xorg.LettreMensuelle');
$wp->buildCache();
$art->submit();
$page->assign('submited', true);
}
- $page->addCssLink('nl.css');
+ $page->addCssLink($nl->cssFile());
}
function handler_admin_nl(&$page, $new = false) {
$page->changeTpl('newsletter/admin.tpl');
$page->setTitle('Administration - Newsletter : liste');
- require_once("newsletter.inc.php");
+
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
if($new) {
- NewsLetter::create();
- pl_redirect("admin/newsletter");
+ $id = $nl->createPending();
+ pl_redirect($nl->adminPrefix() . '/edit/' . $id);
}
- $page->assign('nl_list', NewsLetter::listAll());
+ $page->assign_by_ref('nl', $nl);
+ $page->assign('nl_list', $nl->listAllIssues());
+ }
+
+ function handler_admin_nl_groups(&$page)
+ {
+ require_once 'newsletter.inc.php';
+
+ $page->changeTpl('newsletter/admin_all.tpl');
+ $page->setTitle('Administration - Newsletters : Liste des Newsletters');
+
+ $page->assign('nls', Newsletter::getAll());
}
function handler_admin_nl_edit(&$page, $nid = 'last', $aid = null, $action = 'edit') {
$page->changeTpl('newsletter/edit.tpl');
- $page->addCssLink('nl.css');
+ $page->addCssLink('nl.Polytechnique.org.css');
$page->setTitle('Administration - Newsletter : Édition');
- require_once 'newsletter.inc.php';
- $nl = new NewsLetter($nid);
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
- if($action == 'delete') {
- $nl->delArticle($aid);
- pl_redirect("admin/newsletter/edit/$nid");
+ try {
+ $issue = $nl->getIssue($nid, false);
+ } catch (MailNotFound $e) {
+ return PL_NOT_FOUND;
}
+ $ufb = $nl->getSubscribersUFB();
+ $ufb_keepenv = false; // Will be set to True if there were invalid modification to the UFB.
+
+ // Convert NLIssue error messages to human-readable errors
+ $error_msgs = array(
+ NLIssue::ERROR_INVALID_SHORTNAME => "Le nom court est invalide ou vide.",
+ NLIssue::ERROR_INVALID_UFC => "Le filtre des destinataires est invalide.",
+ NLIssue::ERROR_SQL_SAVE => "Une erreur est survenue en tentant de sauvegarder la lettre, merci de réessayer.",
+ );
+
+ // Update the current issue
if($aid == 'update') {
- $nl->_title = Post::v('title');
- $nl->_title_mail = Post::v('title_mail');
- $nl->_date = Post::v('date');
- $nl->_head = Post::v('head');
- $nl->_shortname = strlen(Post::v('shortname')) ? Post::v('shortname') : null;
- if (preg_match('/^[-a-z0-9]*$/i', $nl->_shortname) && !is_numeric($nl->_shortname)) {
- $nl->save();
- } else {
- $page->trigError("Le nom de la NL n'est pas valide.");
- pl_redirect('admin/newsletter/edit/' . $nl->_id);
+
+ // Save common fields
+ $issue->title = Post::s('title');
+ $issue->title_mail = Post::s('title_mail');
+ $issue->head = Post::s('head');
+ $issue->signature = Post::s('signature');
+
+ if ($issue->isEditable()) {
+ // Date and shortname may only be modified for pending NLs, otherwise all links get broken.
+ $issue->date = Post::s('date');
+ $issue->shortname = strlen(Post::blank('shortname')) ? null : Post::s('shortname');
+ $issue->sufb->updateFromEnv($ufb->getEnv());
+
+ if ($nl->automaticMailingEnabled()) {
+ $issue->send_before = preg_replace('/^(\d\d\d\d)(\d\d)(\d\d)$/', '\1-\2-\3', Post::v('send_before_date')) . ' ' . Post::i('send_before_time_Hour') . ':00:00';
+ }
+ }
+ $errors = $issue->save();
+ if (count($errors)) {
+ foreach ($errors as $error_code) {
+ $page->trigError($error_msgs[$error_code]);
+ }
}
}
+ // Delete an article
+ if($action == 'delete') {
+ $issue->delArticle($aid);
+ pl_redirect($nl->adminPrefix() . "/edit/$nid");
+ }
+
+ // Save an article
if(Post::v('save')) {
$art = new NLArticle(Post::v('title'), Post::v('body'), Post::v('append'),
$aid, Post::v('cid'), Post::v('pos'));
- $nl->saveArticle($art);
- pl_redirect("admin/newsletter/edit/$nid");
+ $issue->saveArticle($art);
+ pl_redirect($nl->adminPrefix() . "/edit/$nid");
}
+ // Edit an article
if ($action == 'edit' && $aid != 'update') {
$eaid = $aid;
if (Post::has('title')) {
$art = new NLArticle(Post::v('title'), Post::v('body'), Post::v('append'),
$eaid, Post::v('cid'), Post::v('pos'));
} else {
- $art = ($eaid == 'new') ? new NLArticle() : $nl->getArt($eaid);
+ $art = ($eaid == 'new') ? new NLArticle() : $issue->getArt($eaid);
}
if ($art && !$art->check()) {
$page->trigError("Cet article est trop long.");
$page->assign('art', $art);
}
+ // Check blacklisted IPs
if ($aid == 'blacklist_check') {
global $globals;
$ips_to_check = array();
$blacklist_host_resolution_count = 0;
- foreach ($nl->_arts as $key => $articles) {
+ foreach ($issue->arts as $key => $articles) {
foreach ($articles as $article) {
$article_ips = $article->getLinkIps($blacklist_host_resolution_count);
if (!empty($article_ips)) {
}
}
+ if ($issue->state == NLIssue::STATE_SENT) {
+ $page->trigWarning("Cette lettre a déjà été envoyée ; il est recommandé de limiter les modifications au maximum (orthographe, adresses web et mail).");
+ }
+
+ $ufb->setEnv($issue->sufb->getEnv());
$page->assign_by_ref('nl', $nl);
+ $page->assign_by_ref('issue', $issue);
+ }
+
+ /** This handler will cancel the sending of the currently pending issue
+ * It is disabled for X.org mailings.
+ */
+ function handler_admin_nl_cancel(&$page, $nid, $force = null)
+ {
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
+
+ if (!$nl->mayEdit() || !S::has_xsrf_token()) {
+ return PL_FORBIDDEN;
+ }
+
+ if (!$nid) {
+ $page->kill("La lettre n'a pas été spécifiée");
+ }
+
+ $issue = $nl->getIssue($nid);
+ if (!$issue) {
+ $page->kill("La lettre {$nid} n'existe pas.");
+ }
+ if (!$issue->cancelMailing()) {
+ $page->trigErrorRedirect("Une erreur est survenue lors de l'annulation de l'envoi.", $nl->adminPrefix());
+ }
+
+ $page->trigSuccessRedirect("L'envoi de l'annonce {$issue->title()} est annulé.", $nl->adminPrefix());
+ }
+
+ /** This handler will enable the sending of the currently pending issue
+ * It is disabled for X.org mailings.
+ */
+ function handler_admin_nl_valid(&$page, $nid, $force = null)
+ {
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
+
+ if (!$nl->mayEdit() || !S::has_xsrf_token()) {
+ return PL_FORBIDDEN;
+ }
+
+ if (!$nid) {
+ $page->kill("La lettre n'a pas été spécifiée.");
+ }
+
+ $issue = $nl->getIssue($nid);
+ if (!$issue) {
+ $page->kill("La lettre {$nid} n'existe pas.");
+ }
+ if (!$issue->scheduleMailing()) {
+ $page->trigErrorRedirect("Une erreur est survenue lors de la validation de l'envoi.", $nl->adminPrefix());
+ }
+
+ $page->trigSuccessRedirect("L'envoi de la newsletter {$issue->title()} a été validé.", $nl->adminPrefix());
+ }
+
+ /** This handler will remove the given issue.
+ */
+ function handler_admin_nl_delete(&$page, $nid, $force = null)
+ {
+ $nl = $this->getNl();
+ if (!$nl) {
+ return PL_NOT_FOUND;
+ }
+
+ if (!$nl->mayEdit() || !S::has_xsrf_token()) {
+ return PL_FORBIDDEN;
+ }
+
+ if (!$nid) {
+ $page->kill("La lettre n'a pas été spécifiée.");
+ }
+
+ $issue = $nl->getIssue($nid);
+ if (!$issue) {
+ $page->kill("La lettre {$nid} n'existe pas");
+ }
+ if (!$issue->isEditable()) {
+ $page->trigErrorRedirect("La lette a été envoyée ou est en cours d'envoi, elle ne peut être supprimée.", $nl->adminPrefix());
+ }
+ if (!$issue->delete()) {
+ $page->trigErrorRedirect("Une erreur est survenue lors de la suppression de la lettre.", $nl->adminPrefix());
+ }
+
+ $page->trigSuccessRedirect("La lettre a bien été supprimée.", $nl->adminPrefix());
}
function handler_admin_nl_cat(&$page, $action = 'list', $id = null) {
$page->setTitle('Administration - Newsletter : Catégories');
$page->assign('title', 'Gestion des catégories de la newsletter');
$table_editor = new PLTableEditor('admin/newsletter/categories','newsletter_cat','cid');
- $table_editor->describe('titre','intitulé',true);
+ $table_editor->describe('title','intitulé',true);
$table_editor->describe('pos','position',true);
$table_editor->apply($page, $action, $id);
}
--- /dev/null
+<?php
+/***************************************************************************
+ * Copyright (C) 2003-2011 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 *
+ ***************************************************************************/
+
+Platal::load('newsletter');
+
+class XnetNlModule extends NewsletterModule
+{
+ function handlers()
+ {
+ return array(
+ '%grp/nl' => $this->make_hook('nl', AUTH_MDP),
+ '%grp/nl/show' => $this->make_hook('nl_show', AUTH_MDP),
+ '%grp/admin/nl' => $this->make_hook('admin_nl', AUTH_MDP, 'groupadmin'),
+ '%grp/admin/nl/edit' => $this->make_hook('admin_nl_edit', AUTH_MDP, 'groupadmin'),
+ '%grp/admin/nl/edit/cancel' => $this->make_hook('admin_nl_cancel', AUTH_MDP, 'groupadmin'),
+ '%grp/admin/nl/edit/valid' => $this->make_hook('admin_nl_valid', AUTH_MDP, 'groupadmin'),
+ '%grp/admin/nl/categories' => $this->make_hook('admin_nl_cat', AUTH_MDP, 'groupadmin'),
+ );
+ }
+
+ protected function getNl()
+ {
+ global $globals;
+ $group = $globals->asso('shortname');
+ return NewsLetter::forGroup($group);
+ }
+}
+
+// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
+?>
</td>
</tr>
<tr class="pair">
- <td class="titre">Newsletter</td>
+ <td class="titre">Newsletters</td>
<td>
- <a href="admin/newsletter">Liste</a>
+ <a href="admin/nls">Liste des NLs groupes</a>
|
- <a href="admin/newsletter/categories">Catégories</a>
+ <a href="admin/newsletter/">NL de X.org</a>
</td>
</tr>
<tr class="impair">
+++ /dev/null
-{**************************************************************************}
-{* *}
-{* Copyright (C) 2003-2011 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 *}
-{* *}
-{**************************************************************************}
-
-<h1>Droits d'administration des lettres de l'AX</h1>
-
-<form action="admin/axletter" method="post">
- {xsrf_token_field}
- <table class="tinybicol">
- <tr>
- <th>Nom</th>
- <th>Action</th>
- </tr>
- <tr class="pair">
- <td colspan="2" class="center">
- <input type="text" name="uid" value="" />
- <input type="submit" name="action" value="add" />
- </td>
- </tr>
- {foreach item=a from=$admins}
- <tr class="{cycle values="impair, pair"}">
- <td>{profile user=$a promo=true}</td>
- <td class="right"><a href="admin/axletter/del/{$a->login()}?token={xsrf_token}">{icon name=cross title="Retirer"}</a></td>
- </tr>
- {/foreach}
- </table>
-</form>
-
-<h1>Ajout d'utilisateurs</h1>
-
-{include core=csv-importer.tpl}
-
-{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
+++ /dev/null
-{**************************************************************************}
-{* *}
-{* Copyright (C) 2003-2011 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 *}
-{* *}
-{**************************************************************************}
-
-<h1>Édition de message</h1>
-
-<form action="{$platal->pl_self()}" method="post">
- {xsrf_token_field}
- {if $am}
- {include file="axletter/letter.mail.tpl"}
-
- <p class="center">
- <input type="hidden" name="id" value="{$id}" />
- <input type="hidden" name="old_short_name" value="{$short_name}" />
- <input type="hidden" name="saved" value="{$saved}" />
- {if $echeance}
- <input type="hidden" name="echeance" value="{$echeance}" />
- {/if}
- {if !$new}
- <input type="submit" name="valid" value="Confirmer" />
- {/if}
- </p>
- {/if}
-
- <fieldset>
- <legend>Sujet de l'email : <input type="text" name="subject" value="{$subject}" size="60"/></legend>
- <p class="center">
- <a href="wiki_help" class="popup3">
- {icon name=information title="Syntaxe wiki"} Voir les marqueurs de mise en forme autorisés
- </a><br />
- <strong>Titre : </strong><input type="text" name="title" value="{$title}" size="60" /><br />
- <textarea name="body" rows="30" cols="78">{$body}</textarea><br />
- <strong>Signature : </strong><input type="text" name="signature" value="{$signature}" size="60" />
- </p>
- </fieldset>
-
- <table class="bicol">
- <tr>
- <th colspan="2">Options du message</th>
- </tr>
- <tr>
- <td class="titre">Nom raccourci</td>
- <td>
- <input type="text" name="short_name" value="{$short_name}" size="16" maxlength="16" />
- <span class="smaller">(uniquement lettres, chiffres ou -)</span>
- </td>
- </tr>
- {include file="include/field.promo.tpl" prefix=""}
- <tr>
- <td class="titre">Envoyer à une liste d'adresses</td>
- <td>
- <textarea name="subset_to" rows="7" cols="78">{$subset_to}</textarea><br />
- <span class="smaller">Indiquez une liste d'adresses emails : la lettre sera envoyée uniquement aux personnes des promotions sélectionnées, dont l'adresse figure dans la liste, et qui souhaitent recevoir les emails de l'AX.</span>
- </td>
- </tr>
- <tr>
- <td class="titre">Sélection inversée</td>
- <td>
- <input type="checkbox" name="subset_rm" {if $subset_rm}checked{/if} /><span class="smaller">En cochant cette case, la liste sera envoyée à tous les inscrits de l'intervalle de promotions sélectionné, sauf ceux indiqués dans la liste ci-dessus.</span>
- </td>
- </tr>
- {if !$saved}
- <tr>
- <td class="titre">Echéance d'envoi</td>
- <td>
- le {valid_date name="echeance_date" value=$echeance_date from=3 to=15}
- vers <select name="echeance_time">{$echeance_time|smarty:nodefaults}</select>
- </td>
- </tr>
- {else}
- <tr>
- <td colspan="2" class="center">
- Envoi au plus tard le {$echeance|date_format:"%x vers %Hh"}<br />
- {if $is_xorg}
- [<a href="ax/edit/valid?token={xsrf_token}" onclick="return confirm('Es-tu sûr de vouloir valider l\'envoi de ce message ?');">{*
- *}{icon name=thumb_up} Valider l'envoi</a>]
- {else}
- [<a href="ax/edit/cancel?token={xsrf_token}" onclick="return confirm('Es-tu sûr de vouloir annuler l\'envoi de ce message ?');">{*
- *}{icon name=thumb_down} Annuler l'envoi</a>]
- {/if}
- </td>
- </tr>
- {/if}
- </table>
-
- <p class="center">
- <input type="hidden" name="id" value="{$id}" />
- <input type="hidden" name="old_short_name" value="{$short_name}" />
- <input type="hidden" name="saved" value="{$saved}" />
- {if $echeance}
- <input type="hidden" name="echeance" value="{$echeance}" />
- {/if}
- <input type="submit" name="valid" value="Aperçu" />
- {if $subset}
- <input type="submit" name="valid" value="Vérifier les emails" />
- {/if}
- {if !$new}
- <input type="submit" name="valid" value="Confirmer" />
- {/if}
- </p>
-</form>
-
-{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
+++ /dev/null
-{**************************************************************************}
-{* *}
-{* Copyright (C) 2003-2011 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 *}
-{* *}
-{**************************************************************************}
-
-
-<h1>
- Envoi exceptionnel de l'AX
-</h1>
-
-<h2>Ton statut</h2>
-
-{if $axs}
-<p>
-Tu es actuellement inscrit aux envois exceptionnels de l'AX (pour choisir le format HTML ou texte, rends toi sur la page <a href='prefs'>des préférences</a>).
-</p>
-<div class='center'>
- [<a href='ax/out'>{icon name=delete} me désinscrire des envois exceptionnels</a>]
-</div>
-{else}
-<p>
-Tu n'es actuellement pas inscrit aux envois exceptionnels de l'AX.
-</p>
-<div class='center'>
- [<a href='ax/in'>{icon name=add} m'inscrire</a>]
-</div>
-{/if}
-
-<h2>Les archives</h2>
-
-<table class="bicol" cellpadding="3" cellspacing="0" summary="liste des NL">
- <tr>
- <th>date</th>
- <th>titre</th>
- </tr>
- {if $ax_rights && !$new}
- <tr class="pair">
- <td colspan="2" class="center">
- <a href="ax/edit">{icon name=page_edit} Proposer un nouvel email</a>
- </td>
- </tr>
- {elseif $ax_rights && $new}
- <tr class="pair">
- <td><a href="ax/edit">{icon name=page_edit} Éditer la demande</a></td>
- <td>
- {if $new->title()}
- <a href="ax/show/{$new->id()}"><strong>{$new->title(true)}</strong></a>
- {/if}
- </td>
- </tr>
- {/if}
- {foreach item=al from=$ax_list}
- <tr class="{cycle values="impair,pair"}">
- <td>{$al.date|date_format}</td>
- <td>
- <a href="ax/show/{$al.id}">{$al.titre}</a>
- </td>
- </tr>
- {/foreach}
-</table>
-
-{if $ax_rights}
-<p>Il y a actuellement {$count} inscrits aux envois.</p>
-{/if}
-
-{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
+++ /dev/null
-{**************************************************************************}
-{* *}
-{* Copyright (C) 2003-2011 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 *}
-{* *}
-{**************************************************************************}
-
-<h1 style="clear: both">
- {if $am->_date}
- Lettre de l'AX du {$am->_date|date_format}
- {else}
- Lettre de l'AX en préparation
- {/if}
-</h1>
-
-<p style="float: left">
-{if $smarty.get.text}
-[<a href='{$platal->pl_self()}'>version HTML</a>]
-{else}
-[<a href='{$platal->pl_self()}?text=1'>version Texte</a>]
-{/if}
-{if !$am->_date}
-[<a href='ax/edit'>éditer</a>]
-{/if}
-</p>
-
-{include file="include/massmailer-nav.tpl" mm=$am base=ax}
-
-<form method="post" action="{$platal->path}">
- <div class='center' style="clear: both">
- <input type='submit' value="me l'envoyer" name='send' />
- </div>
-</form>
-
-<table class="bicol">
- <tr><th>{$am->title(true)}</th></tr>
- <tr>
- <td>
- {include file="axletter/letter.mail.tpl"}
- </td>
- </tr>
-</table>
-
-{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
+++ /dev/null
-{**************************************************************************}
-{* *}
-{* Copyright (C) 2003-2011 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 *}
-{* *}
-{**************************************************************************}
-
-<h1>Désinscription des envois de l'AX</h1>
-
-{if $success}
-<p>
- Votre désinscription aux envois exceptionnels de l'AX a été réalisée avec
- succès. Si vous désirez vous réinscrire, merci de contacter
- <a href="mailto:info@amicale.polytechnique.org">l'AX</a>. Vous pouvez également
- le faire en vous <a href="register">inscrivant à Polytechnique.org</a> (pour
- les X uniquement).
-</p>
-{else}
-<p>
- Votre inscription aux envois de l'AX n'a pu être résiliée. Merci de contacter
- au plus vite <a href="mailto:info@amicale.polytechnique.org">l'AX</a> pour faire
- part de ce problème.
-</p>
-{/if}
-
-{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
{* *}
{**************************************************************************}
-<input type="text" name="nl_title" size="50" maxlength="200" value="{$valid->art->_title}" />
+<input type="text" name="nl_title" size="50" maxlength="200" value="{$valid->art->title}" />
<br />
-<textarea rows="10" cols="60" name="nl_body">{$valid->art->_body}</textarea>
+<textarea rows="10" cols="60" name="nl_body">{$valid->art->body}</textarea>
<br />
-<textarea rows="3" cols="60" name="nl_append">{$valid->art->_append}</textarea>
+<textarea rows="3" cols="60" name="nl_append">{$valid->art->append}</textarea>
{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
<table style="float: right; text-align: center" id="letter_nav">
<tr>
- {if $mm->prev() neq null}
+ {if $issue->prev() neq null}
<td rowspan="2" style="vertical-align: middle">
- <a href="{$base}/show/{$mm->prev()}">
+ <a href="{$nl->prefix()}/show/{$issue->prev()}">
{icon name=resultset_previous title="Lettre précédente"}Lettre précédente
</a>
</td>
{/if}
<td>
- [<a href="{$base}">Liste des lettres</a>]
+ [<a href="{$nl->prefix()}">Liste des lettres</a>]
</td>
- {if $mm->next() neq null}
+ {if $issue->next() neq null}
<td rowspan="2" style="vertical-align: middle">
- <a href="{$base}/show/{$mm->next()}">
+ <a href="{$nl->prefix()}/show/{$issue->next()}">
Lettre suivante{icon name=resultset_next title="Lettre suivante"}
</a>
</td>
{/if}
</tr>
- {if $mm->last() neq null}
+ {if $issue->last() neq null}
<tr>
<td>
- <a href="{$base}/show/{$mm->last()}">
+ <a href="{$nl->prefix()}/show/{$issue->last()}">
<img src="images/up.png" alt="" title="Liste des lettres" />Dernière lettre<img src="images/up.png" alt="" title="Liste des lettres" />
</a>
</td>
<h1>
- Lettre de Polytechnique.org
+ {$nl->name}
</h1>
<table class="bicol" cellpadding="3" cellspacing="0" summary="liste des NL">
<tr>
- <th>date</th>
- <th>titre</th>
+ <th>Date</th>
+ <th>État</th>
+ <th>Titre</th>
</tr>
<tr>
- <td colspan='2'><a href='admin/newsletter/new'>Créer une nouvelle lettre</a></td>
+ <td colspan='3'><a href='{$nl->adminPrefix()}/new'>Créer une nouvelle lettre</a></td>
</tr>
- {foreach item=nl from=$nl_list}
+ {foreach item=nli from=$nl_list}
<tr class="{cycle values="pair,impair"}">
- <td>{$nl.date|date_format}</td>
+ <td>{$nli->date|date_format}</td>
+ <td>{$nli->state}</td>
<td>
- <a href="admin/newsletter/edit/{$nl.id}">{$nl.titre|default:"[no title]"}</a>
+ <a href="{$nl->adminPrefix()}/edit/{$nli->id()}">{$nli->title()|default:"[Sans titre]"}</a>
</td>
</tr>
{/foreach}
{**************************************************************************}
<h1>
- Lettre de Polytechnique.org de {$nl->_date|date_format:"%B %Y"}
+ {$nl->name} de {$issue->date|date_format:"%B %Y"}
</h1>
{if !$art}
<p>
-[<a href="admin/newsletter">liste</a>]
-[<a href="nl/show/{$nl->id()}">visualiser</a>]
+[<a href="{$nl->adminPrefix()}">liste</a>]
+[<a href="{$nl->prefix()}/show/{$issue->id()}">visualiser</a>]
+
</p>
-<form action='admin/newsletter/edit/{$nl->id(true)}/update' method='post'>
+<form action='{$nl->adminPrefix()}/edit/{$issue->id(true)}/update' method='post'>
<table class="bicol" cellpadding="3" cellspacing="0">
<tr>
<th colspan='2'>
</tr>
<tr>
<td class='titre'>
+ État
+ </td>
+ <td>
+{if $issue->isPending()}
+ En attente d'envoi
+ {if $nl->automaticMailingEnabled()}
+ [<a href="{$nl->adminPrefix()}/edit/cancel/{$issue->id()}?token={xsrf_token}" onclick="return confirm('Es-tu sûr de vouloir annuler l\'envoi de ce message ?');">{*
+ *}{icon name=delete} Annuler l'envoi</a>]
+ {/if}
+{elseif $issue->isEditable()}
+ En cours d'édition
+
+ {if $nl->automaticMailingEnabled()}
+ [<a href="{$nl->adminPrefix()}/edit/valid/{$issue->id()}?token={xsrf_token}" onclick="return confirm('Es-tu sûr de vouloir déclencher l\'envoi de ce message ? Tu ne pourras plus le modifier après cela.');">{*
+ *}{icon name=tick} Valider l'envoi</a>]
+ {/if}
+
+ [<a href="{$nl->adminPrefix()}/edit/delete/{$issue->id()}?token={xsrf_token}" onclick="return confirm('Es-tu sûr de vouloir supprimer cette lettre ? Toutes les données en seront perdues.');">{*
+ *}{icon name=cross} Supprimer</a>]
+{else}
+ Envoyée
+{/if}
+
+ <tr>
+ <td class='titre'>
ID
</td>
<td>
- {$nl->_id}
+ {$issue->id}
</td>
</tr>
<tr>
Nom
</td>
<td>
- <input type='text' size='16' name='shortname' value="{$nl->_shortname}" />
- <span class="smaller">(Ex : 2006-06 pour la NL de juin 2006)</span>
+ {if $issue->isEditable()}
+ <input type='text' size='16' name='shortname' value="{$issue->shortname}" />
+ <span class="smaller">(Ex : 2006-06 pour la NL de juin 2006)</span>
+ {else}
+ {$issue->shortname}
+ {/if}
</td>
</tr>
<tr>
Titre de l'email
</td>
<td>
- <input type='text' size='60' name='title_mail' value="{$nl->title(true)}" />
+ <input type='text' size='60' name='title_mail' value="{$issue->title(true)}" />
</td>
</tr>
<tr>
Titre
</td>
<td>
- <input type='text' size='60' name='title' value="{$nl->title()}" />
+ <input type='text' size='60' name='title' value="{$issue->title()}" />
</td>
</tr>
<tr>
<td class='titre'>
- Date d'envoi
+ Date
+ </td>
+ <td>
+ {if $issue->isEditable()}
+ {valid_date name="date" value=$issue->date from=0 to=60}
+ {else}
+ {$issue->date}
+ {/if}
+ </td>
+ </tr>
+ <tr>
+ <td class='titre'>
+ Intro de la lettre<br />(ou contenu pour les lettres exceptionnelles)
</td>
<td>
- <input type='text' size='60' name='date' value="{$nl->_date}" />
+ <textarea name='head' cols='60' rows='20'>{$issue->head()}</textarea>
</td>
</tr>
<tr>
<td class='titre'>
- Intro de la lettre
+ Signature de la lettre
</td>
<td>
- <textarea name='head' cols='60' rows='6'>{$nl->head()}</textarea>
+ <input type='text' size='60' name='signature' value="{$issue->signature}"</input>
</td>
</tr>
+ {if $nl->automaticMailingEnabled() && ($issue->isEditable() || $issue->isPending())}
+ <tr>
+ <td class='titre'>
+ Date d'envoi
+ </td>
+ <td>
+ {if $issue->isEditable()}
+ Le {valid_date name="send_before_date" value=$issue->getSendBeforeDate() from=3 to=15} vers {html_select_time prefix="send_before_time_" time=$issue->getSendBeforeTime() display_hours=true display_minutes=false display_seconds=false display_meridian=false use_24_hours=true} heures
+ {else}
+ Le {$issue->send_before|date_format:"%X vers %Hh"}
+ {/if}
+ </td>
+ </tr>
+ {/if}
+ {if $issue->isEditable()}
+ {if $nl->criteria->hasFlag('promo')}
+ <tr>
+ <td class='titre'>
+ Promotions
+ </td>
+ <td>
+ <script type="text/javascript">/*<![CDATA[*/
+ {literal}
+ function updatepromofields(egal1) {
+ var f = egal1.form;
+ f.egal2.disabled = f.promo2.disabled = egal1.value == '=';
+ f.egal2.readOnly = true;
+ if (f.egal1.value == '>=') {
+ f.egal2.value = '<=';
+ } else {
+ f.egal2.value = '>=';
+ }
+ }
+ $(function() { updatepromofields($('select[name=egal1]')[0]); });
+ {/literal}
+ /*]]>*/</script>
+ <select name="egal1" onchange="updatepromofields(this)" style="text-align:center">
+ <option value="=" {if $smarty.request.egal1 eq "="}selected="selected"{/if}> = </option>
+ <option value=">=" {if $smarty.request.egal1 eq ">="}selected="selected"{/if}> >= </option>
+ <option value="<=" {if $smarty.request.egal1 eq "<="}selected="selected"{/if}> <= </option>
+ </select>
+ <input type="text" name="promo1" size="4" maxlength="4" value="{$smarty.request.promo1}" />
+ et
+ <input type="text" name="egal2" size="1" style="text-align:center" value="{if t($smarty.request.egal2) eq '<'}<{else}>{/if}" readonly="readonly" />
+ <input type="text" name="promo2" size="4" maxlength="4" value="{$smarty.request.promo2}" />
+ </td>
+ </tr>
+ {/if}
+ {if $nl->criteria->hasFlag('axid')}
+ <tr>
+ <td>Matricule AX</td>
+ <td>
+ <textarea name="axid" rows="10" cols="12">{$smarty.request.axid}</textarea>
+ <br />
+ <i>Entrer une liste de matricules AX (un par ligne)</i><br />
+ <input type="checkbox" name="axid_reversed" id="axid_reversed" {if $smarty.request.axid_reversed}checked="checked"{/if} value="1" />
+ </td>
+ </tr>
+ {/if}
+ {/if}
<tr class='center'>
<td colspan='2'>
- <input type='submit' value='sauver' />
+ <input type='submit' value='Sauver' />
</td>
</tr>
</table>
Créer un nouvel article…
</td>
<td style='vertical-align:middle; border-left: 1px gray solid' class="center">
- <a href="admin/newsletter/edit/{$nl->_id}/new#edit">{icon name=add title="créer"}</a>
+ <a href="{$nl->adminPrefix()}/edit/{$issue->id}/new#edit">{icon name=add title="créer"}</a>
</td>
</tr>
- {foreach from=$nl->_arts item=arts key=cat}
+ {foreach from=$issue->arts item=arts key=cat}
<tr>
<th>
- {$nl->_cats[$cat]|default:"[no cat]"}
+ {$issue->category($cat)|default:"[no category]"}
</th>
<th></th>
</tr>
<pre>{$art->toText('%hash%','%login%')}</pre>
</td>
<td style="vertical-align: middle; border-left: 1px gray solid; text-align: center">
- <small><strong>Pos: {$art->_pos}</strong></small><br />
- <a href="admin/newsletter/edit/{$nl->_id}/{$art->_aid}/edit#edit">
+ <small><strong>Pos: {$art->pos}</strong></small><br />
+ <a href="{$nl->adminPrefix()}/edit/{$issue->id}/{$art->aid}/edit#edit">
{icon name="page_edit" title="Editer"}
</a>
<br /><br /><br />
- <a href="admin/newsletter/edit/{$nl->_id}/{$art->_aid}/delete"
+ <a href="{$nl->adminPrefix()}/edit/{$issue->id}/{$art->aid}/delete"
onclick="return confirm('Es-tu sûr de vouloir supprimer cet article ?')">
{icon name="delete" title="Supprimer"}
</a>
<br />
-<form action="admin/newsletter/edit/{$nl->id(true)}/blacklist_check" method="post">
+<form action="{$nl->adminPrefix()}/edit/{$issue->id(true)}/blacklist_check" method="post">
<table class="bicol" cellpadding="3" cellspacing="0">
<tr>
<th colspan="2">
{else}
<p>
-[<a href="admin/newsletter/edit/{$nl->_id}">retour</a>]
+[<a href="{$nl->adminPrefix()}/edit/{$issue->id}">retour</a>]
</p>
<table class='bicol'>
<br />
-<form action="admin/newsletter/edit/{$nl->_id}/{$art->_aid}/edit#edit" method="post">
+<form action="{$nl->adminPrefix()}/edit/{$issue->id}/{$art->aid}/edit#edit" method="post">
<table class='bicol'>
<tr>
<th colspan='2'>
<td>
<select name='cid'>
<option value='0'>-- none --</option>
- {foreach from=$nl->_cats item=text key=cid}
- <option value='{$cid}' {if $art->_cid eq $cid}selected="selected"{/if}>{$text}</option>
+ {foreach from=$nl->cats item=text key=cid}
+ <option value='{$cid}' {if $art->cid eq $cid}selected="selected"{/if}>{$text}</option>
{/foreach}
</select>
</td>
<tr class="impair">
<td class='titre'>Position</td>
<td>
- <input type='text' value='{$art->_pos}' name='pos' />
+ <input type='text' value='{$art->pos}' name='pos' />
</td>
</tr>
<tr class="pair">
<h1>
- Lettre de Polytechnique.org
+ {$nl->name}
+{if $nl->mayEdit() && $nl->adminLinksEnabled()}
+ [<a href="{$nl->adminPrefix()}">Administrer</a>]
+{/if}
</h1>
-
+{if $nl->maySubmit()}
<p class="center">
- <a href="nl/submit">{icon name=page_edit value="Proposer un article"} Proposer un article pour la lettre mensuelle</a>
+ <a href="{$nl->prefix()}/submit">{icon name=page_edit value="Proposer un article"} Proposer un article pour la {$nl->name}</a>
</p>
+{/if}
<h2>Ton statut</h2>
-{if $nls}
+{if $nl->subscriptionState()}
<p>
-Tu es actuellement inscrit à la lettre mensuelle de Polytechnique.org (pour choisir le format HTML ou texte, rends toi sur la page <a href='prefs'>des préférences</a>).
+Tu es actuellement inscrit à la {$nl->name} (pour choisir le format HTML ou texte, rends toi sur la page <a href='prefs'>des préférences</a>).
</p>
<div class='center'>
- [<a href='nl/out'>{icon name=delete} me désinscrire de la lettre mensuelle</a>]
+ [<a href='{$nl->prefix()}/out'>{icon name=delete} me désinscrire de la {$nl->name}</a>]
</div>
{else}
<p>
-Tu n'es actuellement pas inscrit à la lettre mensuelle de Polytechnique.org.
+Tu n'es actuellement pas inscrit à la {$nl->name}.
</p>
<div class='center'>
- [<a href='nl/in'>{icon name=add} m'inscrire à la lettre mensuelle</a>]
+ [<a href='{$nl->prefix()}/in'>{icon name=add} m'inscrire à la {$nl->name}</a>]
</div>
{/if}
<th>date</th>
<th>titre</th>
</tr>
- {foreach item=nl from=$nl_list}
+ {foreach item=nli from=$nl_list}
<tr class="{cycle values="impair,pair"}">
- <td>{$nl.date|date_format}</td>
+ <td>{$nli.date|date_format}</td>
<td>
- <a href="nl/show/{$nl.id}">{$nl.titre}</a>
+ <a href="{$nl->prefix()}/show/{$nli.id}">{$nli.title|default:"[Sans titre]"}</a>
</td>
</tr>
{/foreach}
</table>
+{if $nl->mayEdit()}
+<p>Il y a actuellement {$nl->subscriberCount()} inscrits aux envois.</p>
+{/if}
+
{* vim:set et sw=2 sts=2 sws=2 enc=utf-8: *}
{config_load file="mails.conf" section="mails_ax"}
{if $mail_part eq 'head'}
{from full=#from#}
-{subject text=$am->title(true)}
+{subject text=$issue->title(true)}
{if isset(#replyto#)}{add_header name='Reply-To' value=#replyto#}{/if}
{if isset(#retpath#)}{add_header name='Return-Path' value=#retpath#}{/if}
{elseif $mail_part eq 'text'}
<pre style="width : 72ex; margin: auto">
{/if}
====================================================================
-{$am->title()}
+{$issue->title()}
====================================================================
-{$am->head($user, 'text')}
+{$issue->head($user, 'text')}
-{$am->body('text')}
-
-{$am->signature('text')}
+{$issue->signature('text')}
--------------------------------------------------------------------
Cette lettre est envoyée par l'AX grâce aux outils de Polytechnique.org.
body { background-color: #ddd; color: #000; }
{/literal}
<!--
- {$am->css()}
+ {$issue->css()}
-->
</style>
</head>
<div class="ax_background">
{/if}
<div class='ax_mail'>
- <div class="title">{$am->title()}</div>
- <div class="intro">{$am->head($user, 'html')|smarty:nodefaults}</div>
- <div class="body">{$am->body('html')|smarty:nodefaults}</div>
- <div class="signature">{$am->signature('html')|smarty:nodefaults}</div>
+ <div class="title">{$issue->title()}</div>
+ <div class="intro">{$issue->head($user, 'html')|smarty:nodefaults}</div>
+ <div class="signature">{$issue->signature('html')|smarty:nodefaults}</div>
<div class="foot1">
Cette lettre est envoyée par l'AX grâce aux outils de Polytechnique.org.
</div>
{config_load file="mails.conf" section="newsletter"}
{if $mail_part eq 'head'}
{from full=#from#}
-{subject text=$nl->title(true)}
+{subject text=$issue->title(true)}
{if isset(#replyto#)}{add_header name='Reply-To' value=#replyto#}{/if}
{if isset(#retpath#)}{add_header name='Return-Path' value=#retpath#}{/if}
{elseif $mail_part eq 'text'}
<pre style="width : 72ex; margin: auto">
{/if}
====================================================================
-{$nl->title()}
+{$issue->title()}
====================================================================
-{$nl->head($user, 'text')}
+{$issue->head($user, 'text')}
-{foreach from=$nl->_arts key=cid item=arts name=cats}
-{$smarty.foreach.cats.iteration} *{$nl->_cats[$cid]}*
+{foreach from=$issue->arts key=cid item=arts name=cats}
+{$smarty.foreach.cats.iteration} *{$issue->category($cid)}*
{foreach from=$arts item=art}
- {$art->title()}
{/foreach}
{/foreach}
-{foreach from=$nl->_arts key=cid item=arts}
+{foreach from=$issue->arts key=cid item=arts}
--------------------------------------------------------------------
-*{$nl->_cats[$cid]}*
+*{$issue->category($cid)}*
--------------------------------------------------------------------
{foreach from=$arts item=art}
body { background-color: #ddd; color: #000; }
{/literal}
<!--
- {$nl->css()}
+ {$issue->css()}
-->
</style>
</head>
<div class='nl_background'>
{/if}
<div class='nl'>
- <div class="title"><a href="{$globals->baseurl}">{$nl->title()}</a></div>
- <div class="intro">{$nl->head($user, 'html')|smarty:nodefaults}</div>
+ <div class="title"><a href="{$globals->baseurl}">{$issue->title()}</a></div>
+ <div class="intro">{$issue->head($user, 'html')|smarty:nodefaults}</div>
<a id="top_lnk"></a>
- {foreach from=$nl->_arts key=cid item=arts name=cats}
+ {foreach from=$issue->arts key=cid item=arts name=cats}
<div class="lnk">
- <a href="{$prefix}#cat{$cid}"><strong>{$smarty.foreach.cats.iteration}. {$nl->_cats[$cid]}</strong></a><br />
+ <a href="{$prefix}#cat{$cid}"><strong>{$smarty.foreach.cats.iteration}. {$issue->category($cid)}</strong></a><br />
{foreach from=$arts item=art}
- <a href="{$prefix}#art{$art->_aid}"> - {$art->title()}</a><br />
+ <a href="{$prefix}#art{$art->aid}"> - {$art->title()}</a><br />
{/foreach}
</div>
{/foreach}
- {foreach from=$nl->_arts key=cid item=arts name=cats}
+ {foreach from=$issue->arts key=cid item=arts name=cats}
<h1 class="xorg_nl"><a id="cat{$cid}"></a>
- {$nl->_cats[$cid]}
+ {$issue->category($cid)}
</h1>
{foreach from=$arts item=art}
{$art->toHtml($hash, $user->login())|smarty:nodefaults}
{**************************************************************************}
<h1 style="clear: both">
- Lettre de Polytechnique.org du {$nl->_date|date_format}
+ {$issue->nl->name} du {$issue->date|date_format}
</h1>
<p style="float: left">
{if $smarty.get.text}
- [<a href='nl/show/{$nl->id()}'>version HTML</a>]
+ [<a href='nl/show/{$issue->id()}'>version HTML</a>]
{else}
- [<a href='nl/show/{$nl->id()}?text=1'>version Texte</a>]
+ [<a href='nl/show/{$issue->id()}?text=1'>version Texte</a>]
{/if}
- {if hasPerm('admin')}
- [<a href='admin/newsletter/edit/{$nl->id()}'>Éditer</a>]
+ {if $nl->mayEdit()}
+ [<a href='{$nl->adminPrefix()}/edit/{$issue->id()}'>Éditer</a>]
{/if}
</p>
-{include file="include/massmailer-nav.tpl" mm=$nl base=nl}
+{include file="include/massmailer-nav.tpl" issue=$issue nl=$nl}
<form method="post" action="{$platal->path}">
<div class='center' style="clear: both">
</form>
<table class="bicol">
- <tr><th>{$nl->title(true)}</th></tr>
+ <tr><th>{$issue->title(true)}</th></tr>
<tr>
<td>
- {include file="newsletter/nl.mail.tpl" escape=true}
+ {include file=$nl->tplFile() escape=true}
</td>
</tr>
</table>
--- /dev/null
+DROP TABLE IF EXISTS newsletter_issues;
+DROP TABLE IF EXISTS newsletters;
+
+-----------------
+-- newsletters --
+-----------------
+
+CREATE TABLE newsletters (
+ id int(11) unsigned NOT NULL auto_increment,
+ group_id smallint(5) UNSIGNED NOT NULL,
+ name varchar(255) NOT NULL,
+ custom_css BOOL NOT NULL DEFAULT FALSE,
+ criteria SET('axid', 'promo', 'geo') DEFAUL NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY (group_id),
+ FOREIGN KEY (group_id) REFERENCES groups (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Lists all newsletters';
+
+-- Filling it with default values for X.org / AX / Ecole
+INSERT INTO newsletters (group_id, name, custom_css)
+ ( SELECT groups.id, CONCAT("Lettre de ", groups.nom), TRUE
+ FROM groups
+ WHERE groups.diminutif IN ('Polytechnique.org', 'Ecole', 'AX')
+ );
+
+-- Set variables for simpler queries later on
+SET @idnl_xorg = (SELECT nls.id FROM newsletters AS nls LEFT JOIN groups AS g ON (g.id = nls.group_id) WHERE g.diminutif = 'Polytechnique.org');
+SET @idnl_ax = (SELECT nls.id FROM newsletters AS nls LEFT JOIN groups AS g ON (g.id = nls.group_id) WHERE g.diminutif = 'AX');
+SET @idnl_ecole = (SELECT nls.id FROM newsletters AS nls LEFT JOIN groups AS g ON (g.id = nls.group_id) WHERE g.diminutif = 'Ecole');
+
+UPDATE newsletters SET name = "Lettre de l'AX", criteria = 'promo,axid' WHERE id = @idnl_ax;
+UPDATE newsletters SET name = "Lettre mensuelle de Polytechnique.org", criteria = 'promo' WHERE id = @idnl_xorg;
+UPDATE newsletters SET name = "DiXit, lettre de l'École polytechnique", criteria = 'promo' WHERE id = @idnl_ecole;
+
+-----------------------
+-- newsletter_issues --
+-----------------------
+
+CREATE TABLE newsletter_issues (
+ nlid int(11) unsigned NOT NULL,
+ id int(11) unsigned NOT NULL auto_increment,
+ date date NOT NULL default '0000-00-00',
+ send_before date default NULL,
+ state enum('sent','new','pending') NOT NULL default 'new',
+ sufb_json text default NULL,
+ title varchar(255) NOT NULL default '',
+ head mediumtext NOT NULL,
+ signature mediumtext NOT NULL,
+ short_name varchar(16) default NULL,
+ mail_title varchar(255) NOT NULL default '',
+ PRIMARY KEY (id),
+ UNIQUE KEY (nlid, short_name),
+ FOREIGN KEY (nlid) REFERENCES newsletters (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Lists issues of all newsletters';
+
+-- Fill with all X.org nls
+INSERT INTO newsletter_issues (nlid, date, title, state, head, short_name, mail_title)
+ (
+ SELECT @idnl_xorg, n.date, n.titre, n.bits, n.head, n.short_name, n.titre_mail
+ FROM newsletter AS n
+ );
+
+-- Fill with all AX nls
+INSERT INTO newsletter_issues (nlid, date, title, state, head, signature, short_name, mail_title)
+ (
+ SELECT @idnl_ax, a.date, a.title, a.bits, CONCAT("<cher> <prenom>,\n\n", a.body), a.signature, a.short_name, a.subject
+ FROM axletter AS a
+ WHERE bits != 'invalid'
+ );
+
+--------------------
+-- newsletter_cat --
+--------------------
+
+-- Fix newsletter_cat: add nlid, add FK, rename title
+ALTER TABLE newsletter_cat ADD COLUMN nlid INT(11) UNSIGNED NOT NULL AFTER cid;
+
+UPDATE newsletter_cat SET nlid = @idnl_xorg;
+
+ALTER TABLE newsletter_cat ADD FOREIGN KEY (nlid) REFERENCES newsletters (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE;
+ALTER TABLE newsletter_cat CHANGE titre title varchar(128) NOT NULL DEFAULT '';
+
+-- Final state:
+--
+-- CREATE TABLE `newsletter_cat` (
+-- `cid` tinyint(3) unsigned NOT NULL auto_increment,
+-- `nlid` int(11) unsigned NOT NULL,
+-- `pos` tinyint(3) unsigned NOT NULL default '0',
+-- `title` varchar(128) NOT NULL default '',
+-- PRIMARY KEY (`cid`),
+-- KEY `pos` (`pos`),
+-- KEY `nlid` (`nlid`),
+-- CONSTRAINT `newsletter_cat_ibfk_1` FOREIGN KEY (`nlid`) REFERENCES `newsletters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+-- ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8
+
+
+--------------------
+-- newsletter_art --
+--------------------
+
+-- Fix newsletter_cat: add nlid, add FK, rename title
+ALTER TABLE newsletter_art DROP FOREIGN KEY newsletter_art_ibfk_1;
+ UPDATE newsletter_art AS na
+LEFT JOIN newsletter AS n ON na.id = n.id
+LEFT JOIN newsletter_issues AS ni ON (ni.nlid = @idnl_xorg AND ni.short_name = n.short_name)
+ SET na.id = ni.id;
+
+ALTER TABLE newsletter_art ADD FOREIGN KEY (id) REFERENCES newsletter_issues (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE
+
+--------------------
+-- newsletter_ins --
+--------------------
+
+-- Fix newsletter_ins: add nlid column, fix FK
+ALTER TABLE newsletter_ins ADD COLUMN nlid INT(11) UNSIGNED NOT NULL AFTER uid;
+
+UPDATE newsletter_ins SET nlid = @idnl_xorg;
+
+-- We have to drop all FKs in order to update 'last' indexes.
+ALTER TABLE newsletter_ins DROP FOREIGN KEY newsletter_ins_ibfk_1;
+ALTER TABLE newsletter_ins DROP FOREIGN KEY newsletter_ins_ibfk_2;
+ALTER TABLE newsletter_ins DROP PRIMARY KEY;
+
+ UPDATE newsletter_ins AS ni
+LEFT JOIN newsletter AS n ON (ni.last = n.id)
+LEFT JOIN newsletter_issues AS ns ON (n.short_name = ns.short_name)
+ SET ni.last = ns.id;
+
+ALTER TABLE newsletter_ins ADD PRIMARY KEY (uid, nlid);
+ALTER TABLE newsletter_ins ADD FOREIGN KEY (uid) REFERENCES accounts (uid)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE;
+ALTER TABLE newsletter_ins ADD FOREIGN KEY (last) REFERENCES newsletter_issues (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE;
+ALTER TABLE newsletter_ins ADD FOREIGN KEY (nlid) REFERENCES newsletters (id)
+ ON UPDATE CASCADE
+ ON DELETE CASCADE;
+
+-- Add AXletter subscribers.
+INSERT INTO newsletter_ins (nlid, uid, last, hash)
+ (
+ SELECT @idnl_ax, ai.uid, MAX(ni.id), ai.hash
+ FROM axletter_ins AS ai
+ LEFT JOIN axletter AS a ON (ai.last = a.id)
+ LEFT JOIN newsletter_issues AS ni ON (ni.nlid = @idnl_ax AND ni.short_name = a.short_name)
+ GROUP BY ai.uid
+ );
+
+-- Final state:
+--
+-- CREATE TABLE `newsletter_ins` (
+-- `uid` int(11) unsigned NOT NULL default '0',
+-- `nlid` int(11) unsigned NOT NULL,
+-- `last` int(11) unsigned default NULL,
+-- `hash` varchar(32) default NULL,
+-- PRIMARY KEY (`uid`,`nlid`),
+-- KEY `last` (`last`),
+-- KEY `nlid` (`nlid`),
+-- CONSTRAINT `newsletter_ins_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `accounts` (`uid`) ON DELETE CASCADE ON UPDATE CASCADE,
+-- CONSTRAINT `newsletter_ins_ibfk_2` FOREIGN KEY (`last`) REFERENCES `newsletter_issues` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+-- CONSTRAINT `newsletter_ins_ibfk_3` FOREIGN KEY (`nlid`) REFERENCES `newsletters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='liste des abonnés à la newsletter'