<?php
/***************************************************************************
- * Copyright (C) 2003-2011 Polytechnique.org *
+ * Copyright (C) 2003-2014 Polytechnique.org *
* http://opensource.polytechnique.org/ *
* *
* This program is free software; you can redistribute it and/or modify *
public $cats; // List of all categories for this NL
public $criteria; // PlFlagSet of allowed filters for recipient selection
- protected $custom_css = false;
-
// 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_COMMUNITY = 'Annonces';
const GROUP_AX = 'AX';
const GROUP_EP = 'Ecole';
+ const GROUP_FX = 'FX';
// Searches on mutiple fields
const SEARCH_ALL = 'all';
{
// 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
+ nls.name AS nl_name, nls.criteria
FROM newsletters AS nls
LEFT JOIN groups AS g ON (nls.group_id = g.id)
WHERE nls.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
/** Retrieve all newsletters
* @return An array of $id => NewsLetter objects
*/
- public static function getAll()
+ public static function getAll($sort = 'id', $order = 'ASC')
{
- $res = XDB::query('SELECT id
- FROM newsletters');
- $nls = array();
- foreach ($res->fetchColumn() as $id) {
- $nls[$id] = new NewsLetter($id);
- }
- return $nls;
+ $res = XDB::fetchAllAssoc('SELECT n.id, g.nom AS group_name, n.name, n.criteria, g.diminutif AS group_link
+ FROM newsletters AS n
+ INNER JOIN groups AS g ON (n.group_id = g.id)
+ ORDER BY ' . $sort . ' ' . $order);
+ return $res;
}
// }}}
/** Get the count of subscribers to the NL.
* @return Number of subscribers.
*/
- public function subscriberCount()
+ public function subscriberCount($lost = null, $sex = null, $grade = null, $first_promo = null, $last_promo = null)
{
- return XDB::fetchOneCell('SELECT COUNT(uid)
- FROM newsletter_ins
- WHERE nlid = {?}', $this->id);
+ $cond = new PFC_And(new UFC_NLSubscribed($this->id));
+ if (!is_null($sex)) {
+ $cond->addChild(new UFC_Sex($sex));
+ }
+ if (!is_null($grade)) {
+ $cond->addChild(new UFC_Promo('>=', $grade, $first_promo));
+ $cond->addChild(new UFC_Promo('<=', $grade, $last_promo));
+ }
+ if (!($lost === null)) {
+ if ($lost === true) {
+ $cond->addChild(new PFC_Not(new UFC_HasEmailRedirect()));
+ } else {
+ $cond->addChild(new UFC_HasEmailRedirect());
+ }
+ }
+ $uf = new UserFilter($cond);
+ return $uf->getTotalCount();
}
/** Get the count of subscribers with non valid redirection.
*/
- public function lostSubscriberCount()
- {
- return XDB::fetchOneCell('SELECT COUNT(DISTINCT(n.uid))
- FROM newsletter_ins AS n
- INNER JOIN accounts AS a ON (n.uid = a.uid)
- INNER JOIN account_types AS t ON (t.type = a.type)
- LEFT JOIN email_redirect_account AS r ON (r.uid = a.uid AND r.flags = \'active\' AND r.broken_level < 3
- AND r.type != \'imap\' AND r.type != \'homonym\')
- WHERE n.nlid = {?} AND r.redirect IS NULL AND a.state = \'active\' AND FIND_IN_SET(\'mail\', t.perms)',
- $this->id);
+ public function lostSubscriberCount($sex = null)
+ {
+ return $this->subscriberCount(true, $sex);
}
/** Get the number of subscribers to the NL whose last received mailing was $last.
*/
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']));
+ // Submission of new articles is only enabled for the X.org NL and the
+ // community letter (and forbidden when viewing issues on X.net)
+ return (
+ ($this->group == self::GROUP_XORG || $this->group == self::GROUP_COMMUNITY)
+ && !isset($GLOBALS['IS_XNET_SITE']));
}
// }}}
*/
public function cssFile()
{
- if ($this->custom_css) {
+ if ($this->hasCustomCss()) {
$base = $this->group;
} else {
$base = self::FORMAT_DEFAULT_GROUP;
*/
public function tplFile()
{
- if ($this->custom_css) {
+ if ($this->hasCustomCss()) {
$base = $this->group;
} else {
$base = self::FORMAT_DEFAULT_GROUP;
switch ($this->group) {
case self::GROUP_XORG:
return 'nl';
+ case self::GROUP_COMMUNITY:
+ return 'comletter';
case self::GROUP_AX:
return 'ax';
case self::GROUP_EP:
return 'epletter';
+ case self::GROUP_FX:
+ return 'fxletter';
default:
// Don't display groups NLs on X.org
assert(!$enforce_xnet);
switch ($this->group) {
case self::GROUP_XORG:
return 'admin/newsletter';
+ case self::GROUP_COMMUNITY:
+ return 'comletter/admin';
case self::GROUP_AX:
return 'ax/admin';
case self::GROUP_EP:
return 'epletter/admin';
+ case self::GROUP_FX:
+ return 'fxletter/admin';
default:
// Don't display groups NLs on X.org
assert(!$enforce_xnet);
}
}
+ /** Get the prefix to use for all 'stat' pages of this NL.
+ */
+ public function statPrefix($enforce_xnet = true, $with_group = true)
+ {
+ if (!empty($GLOBALS['IS_XNET_SITE'])) {
+ if ($with_group) {
+ return $this->group . '/stat/nl';
+ } else {
+ return 'stat/nl';
+ }
+ }
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ return 'stat/newsletter';
+ case self::GROUP_COMMUNITY:
+ return 'comletter/stat';
+ case self::GROUP_AX:
+ return 'ax/stat';
+ case self::GROUP_EP:
+ return 'epletter/stat';
+ case self::GROUP_FX:
+ return 'fxletter/stat';
+ default:
+ // Don't display groups NLs on X.org
+ assert(!$enforce_xnet);
+ }
+ }
+
+ /** Get a full URL to a newsletter
+ */
+ public function fullUrl()
+ {
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ return 'https://www.polytechnique.org/nl';
+ case self::GROUP_COMMUNITY:
+ return 'https://www.polytechnique.org/comletter';
+ case self::GROUP_AX:
+ return 'https://www.polytechnique.org/ax';
+ case self::GROUP_EP:
+ return 'https://www.polytechnique.org/epletter';
+ case self::GROUP_FX:
+ return 'https://www.polytechnique.org/fxletter';
+ default:
+ return 'http://www.polytechnique.net/' . $this->group . '/nl';
+ }
+ }
+
+ /** Get links for nl pages.
+ */
+ public function adminLinks()
+ {
+ return array(
+ 'index' => array('link' => $this->prefix(), 'title' => 'Archives'),
+ 'admin' => array('link' => $this->adminPrefix(), 'title' => 'Administrer'),
+ 'stats' => array('link' => $this->statPrefix(), 'title' => 'Statistiques')
+ );
+ }
+
/** 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 hasCustomCss()
{
- return $this->custom_css;
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ case self::GROUP_COMMUNITY:
+ case self::GROUP_AX:
+ case self::GROUP_EP:
+ case self::GROUP_FX:
+ return true;
+ default:
+ return false;
+ }
}
public function canSyncWithGroup()
{
switch ($this->group) {
case self::GROUP_XORG:
+ case self::GROUP_COMMUNITY:
case self::GROUP_AX:
case self::GROUP_EP:
+ case self::GROUP_FX:
return false;
default:
return true;
*/
public function sendTo($user, $hash = null)
{
+ global $globals;
+
+ // Don't send email to users without an address
+ // Note: this would never happen when using sendToAll
+ if (!$user->bestEmail()) {
+ return;
+ }
+
$this->fetchArticles();
if (is_null($hash)) {
if (!empty($this->reply_to)) {
$mailer->addHeader('Reply-To', $this->reply_to);
}
+
+ // Add mailing list headers
+ // Note: "Precedence: bulk" is known to cause issues on some clients
+ $mailer->addHeader('Precedence', 'list');
+ // RFC 2919 header
+ $mailer->addHeader('List-Id', $this->nl->group .
+ ' <' . $this->nl->group . '.newsletter.' . $globals->mail->domain . '>');
+ // RFC 2369 headers
+ $listurl = $this->nl->fullUrl();
+ $mailer->addHeader('List-Unsubscribe', '<' . $listurl . '/out/nohash/' . $this->id . '>');
+ $mailer->addHeader('List-Subscribe', '<' . $listurl. '/in/nohash/' . $this->id . '>');
+ $mailer->addHeader('List-Archive', '<' . $listurl . '>');
+ $mailer->addHeader('List-Help', '<' . $listurl . '>');
+ $mailer->addHeader('List-Owner', '<mailto:support@' . $globals->mail->domain . '>');
+
$mailer->sendTo($user);
}
WHERE id = {?}',
$this->id);
+ // Every minute, select BATCH_SIZE users who:
+ // * are subscribed to the newsletter
+ // * have not yet been mailed this issue of the newsletter
+ // * have a valid email address
+ // ... and send them the current issue.
+ // Once a mail is sent, newsletter_ins is updated to prevent selecting again the same user a minute later.
$ufc = new PFC_And($this->getRecipientsUFC(), new UFC_NLSubscribed($this->nl->id, $this->id), new UFC_HasValidEmail());
- $uf = new UserFilter($ufc, array(new UFO_IsAdmin(), new UFO_Uid()));
+ $uf = new UserFilter($ufc, array(new UFO_IsAdmin(true), new UFO_Uid()));
$limit = new PlLimit(self::BATCH_SIZE);
$global_sent = array();
-
while (true) {
$sent = array();
$users = $uf->getUsers($limit);
}
foreach ($users as $user) {
if (array_key_exists($user->id(), $global_sent)) {
+ // Such a condition may happen if an user:
+ // 1. was mailed the issue,
+ // 2. unsubscribed the newsletter,
+ // 3. subscribed again before the sending was done.
+ // Such a case is reported by mail to people who monitor the website.
+ // If you are reading this comment because of such a mail and the lines above explain what happened,
+ // you only need to reset the state of the issue to "pending".
+ // A cron script will then restart the mailing from where it stopped and only the problematic user will reveive the issue twice.
Platal::page()->kill('Sending the same newsletter issue ' . $this->id . ' to user ' . $user->id() . ' twice, something must be wrong.');
}
$sent[] = $user->id();
// }}}
-// vim:set et sw=4 sts=4 sws=4 enc=utf-8:
+// vim:set et sw=4 sts=4 sws=4 fenc=utf-8:
?>