+ /** 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.
+ * @p $create Whether to create an empty issue if no pending issue exist.
+ * @return Either null, or a NL object.
+ */
+ public function getPendingIssue($create = false)
+ {
+ $res = XDB::query('SELECT MAX(id)
+ FROM newsletter_issues
+ WHERE nlid = {?} AND state = \'new\'',
+ $this->id);
+ $id = $res->fetchOneCell();
+ if ($id != null) {
+ return new NLIssue($id, $this);
+ } else if ($create) {
+ $id = $this->createPending();
+ 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($enforce_xnet=true)
+ {
+ 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(!$enforce_xnet);