+ 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($sex = null)
+ {
+ return $this->subscriberCount(true, $sex);
+ }
+
+ /** 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($enforce_xnet=true, $with_group=true)
+ {
+ if (!empty($GLOBALS['IS_XNET_SITE'])) {
+ if ($with_group) {
+ return $this->group . '/nl';
+ } else {
+ return 'nl';
+ }
+ }
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ return 'nl';
+ 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);
+ }
+ }
+
+ /** Get the prefix to use for all 'admin' pages of this NL.
+ */
+ public function adminPrefix($enforce_xnet=true, $with_group=true)
+ {
+ if (!empty($GLOBALS['IS_XNET_SITE'])) {
+ if ($with_group) {
+ return $this->group . '/admin/nl';
+ } else {
+ return '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';
+ 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_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 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 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;
+ }
+
+ public function canSyncWithGroup()
+ {
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ case self::GROUP_AX:
+ case self::GROUP_EP:
+ case self::GROUP_FX:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ // }}}
+}
+
+// }}}
+
+// {{{ 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 $reply_to; // Adress to reply to the message (can be empty)
+ 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, reply_to
+ FROM newsletter_issues
+ WHERE id = {?}',
+ $id);
+ if (!$res->numRows()) {
+ throw new MailNotFound();
+ }
+ $issue = $res->fetchOneAssoc();
+ if ($nl && $nl->id == $issue['nlid']) {
+ $this->nl = $nl;
+ } else {
+ $this->nl = new NewsLetter($issue['nlid']);
+ }
+ $this->id = $id;
+ $this->nlid = $issue['nlid'];
+ $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->reply_to = $issue['reply_to'];
+ $this->sufb = $this->importJSonStoredUFB($issue['sufb_json']);
+
+ if ($fetch_articles) {
+ $this->fetchArticles();
+ }
+ }
+
+ protected function fetchArticles($force = false)
+ {
+ if (count($this->arts) && !$force) {
+ return;
+ }
+
+ // Load the articles