const GROUP_AX = 'AX';
const GROUP_EP = 'Ecole';
+ // Searches on mutiple fields
+ const SEARCH_ALL = 'all';
+ const SEARCH_TITLE = 'title';
+
+
// {{{ Constructor, NewsLetter retrieval (forGroup, getAll)
public function __construct($id)
}
}
- return new NLIssue($id, &$this);
+ return new NLIssue($id, $this);
}
/** Create a new, empty, pending newsletter issue
}
/** 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()
+ public function getPendingIssue($create = false)
{
$res = XDB::query('SELECT MAX(id)
FROM newsletter_issues
WHERE nlid = {?} AND state = \'new\'',
$this->id);
- if ($res->numRows()) {
- $id = $res->fetchOneCell();
+ $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;
}
}
+ /** Returns a list of either issues or articles corresponding to the search.
+ * @p $search The searched pattern.
+ * @p $field The fields where to search, if none given, search in all possible fields.
+ * @return The list of object found.
+ */
+ public function issueSearch($search, $field, $user)
+ {
+ $search = XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $search);
+ if ($field == self::SEARCH_ALL) {
+ $where = '(title ' . $search . ' OR mail_title ' . $search . ' OR head ' . $search . ' OR signature ' . $search . ')';
+ } elseif ($field == self::SEARCH_TITLE) {
+ $where = '(title ' . $search . ' OR mail_title ' . $search . ')';
+ } else {
+ $where = $field . $search;
+ }
+ $list = XDB::fetchColumn('SELECT DISTINCT(id)
+ FROM newsletter_issues
+ WHERE nlid = {?} AND state = \'sent\' AND ' . $where . '
+ ORDER BY date DESC',
+ $this->id);
+
+ $issues = array();
+ foreach ($list as $id) {
+ $issue = new NLIssue($id, $this, false);
+ if ($issue->checkUser($user)) {
+ $issues[] = $issue;
+ }
+ }
+ return $issues;
+ }
+
+ public function articleSearch($search, $field, $user)
+ {
+ $search = XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $search);
+ if ($field == self::SEARCH_ALL) {
+ $where = '(a.title ' . $search . ' OR a.body ' . $search . ' OR a.append ' . $search . ')';
+ } else {
+ $where = 'a.' . $field . $search;
+ }
+ $list = XDB::fetchAllAssoc('SELECT i.short_name, a.aid, i.id, a.title
+ FROM newsletter_art AS a
+ INNER JOIN newsletter_issues AS i ON (a.id = i.id)
+ WHERE i.nlid = {?} AND i.state = \'sent\' AND ' . $where . '
+ GROUP BY a.id, a.aid
+ ORDER BY i.date DESC, a.aid',
+ $this->id);
+
+ $articles = array();
+ foreach ($list as $item) {
+ $issue = new NLIssue($item['id'], $this, false);
+ if ($issue->checkUser($user)) {
+ $articles[] = $item;
+ }
+ }
+ return $articles;
+ }
+
// }}}
// {{{ Subscription related function
* @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)
+ public function unsubscribe($issue_id = null, $uid = null, $hash = false)
{
if (is_null($uid) && $hash) {
// Unable to unsubscribe from an empty hash
XDB::execute('DELETE FROM newsletter_ins
WHERE nlid = {?} AND uid = {?}',
$this->id, $user);
+ if (!is_null($issue_id)) {
+ XDB::execute('UPDATE newsletter_issues
+ SET unsubscribe = unsubscribe + 1
+ WHERE id = {?}',
+ $id);
+ }
return true;
}
/** 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()
+ public function prefix($enforce_xnet=true, $with_group=true)
{
if (!empty($GLOBALS['IS_XNET_SITE'])) {
- return $this->group . '/nl';
+ if ($with_group) {
+ return $this->group . '/nl';
+ } else {
+ return 'nl';
+ }
}
switch ($this->group) {
case self::GROUP_XORG:
return 'epletter';
default:
// Don't display groups NLs on X.org
- assert(false);
+ assert(!$enforce_xnet);
}
}
/** Get the prefix to use for all 'admin' pages of this NL.
*/
- public function adminPrefix()
+ public function adminPrefix($enforce_xnet=true, $with_group=true)
{
if (!empty($GLOBALS['IS_XNET_SITE'])) {
- return $this->group . '/admin/nl';
+ if ($with_group) {
+ return $this->group . '/admin/nl';
+ } else {
+ return 'admin/nl';
+ }
}
switch ($this->group) {
case self::GROUP_XORG:
return 'epletter/admin';
default:
// Don't display groups NLs on X.org
- assert(false);
+ assert(!$enforce_xnet);
}
}
return $this->custom_css;
}
+ public function canSyncWithGroup()
+ {
+ switch ($this->group) {
+ case self::GROUP_XORG:
+ case self::GROUP_AX:
+ case self::GROUP_EP:
+ return false;
+ default:
+ return true;
+ }
+ }
+
// }}}
}
$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'];
WHERE id = {?}',
$this->id);
if ($success) {
+ global $globals;
+ $mailer = new PlMailer('newsletter/notify_scheduled.mail.tpl');
+ $mailer->assign('issue', $this);
+ $mailer->assign('base', $globals->baseurl);
+ $mailer->send();
$this->refresh();
}
return $success;
{
if ($this->state == self::STATE_PENDING) {
$success = XDB::execute('UPDATE newsletter_issues
- SET send_before = NULL, state = \'new\'
+ SET state = \'new\'
WHERE id = {?}', $this->id);
if ($success) {
$this->refresh();
public function last()
{
if (is_null($this->id_last)) {
- $this->id_last = $this->nl->getIssue('last')->id;
+ try {
+ $this->id_last = $this->nl->getIssue('last')->id;
+ } catch (MailNotFound $e) {
+ $this->id_last = null;
+ }
}
return $this->id_last;
}
const ERROR_INVALID_SHORTNAME = 'invalid_shortname';
const ERROR_INVALID_UFC = 'invalid_ufc';
+ const ERROR_TOO_LONG_UFC = 'too_long_ufc';
const ERROR_SQL_SAVE = 'sql_error';
/** Save the global properties of this NL issue (title&co).
}
if ($this->sufb->isValid() || $this->sufb->isEmpty()) {
$fields['sufb_json'] = json_encode($this->sufb->export()->dict());
+ // If sufb_json is too long to be store, we do not store a truncated json and notify the user.
+ // The limit is LONGTEXT's one, ie 2^32 = 4294967296.
+ if (strlen($fields['sufb_json']) > 4294967295) {
+ $errors[] = self::ERROR_TOO_LONG_UFC;
+ }
} else {
$errors[] = self::ERROR_INVALID_UFC;
}
}
/** Save an article
- * @p &$a A reference to a NLArticle object (will be modified once saved)
+ * @p $a A reference to a NLArticle object (will be modified once saved)
*/
- public function saveArticle(&$a)
+ public function saveArticle($a)
{
$this->fetchArticles();
* @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)
+ public function css($page = null)
{
if (!is_null($page)) {
$page->addCssLink($this->nl->cssFile());
* @p $page Smarty object (using the $this->nl->tplFile() template)
* @p $user User to use when rendering the template
*/
- public function toText(&$page, $user)
+ public function toText($page, $user)
{
$this->fetchArticles();
* @p $page Smarty object (using the $this->nl->tplFile() template)
* @p $user User to use when rendering the template
*/
- public function toHtml(&$page, $user)
+ public function toHtml($page, $user)
{
$this->fetchArticles();
/** 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)
+ protected function assignData($smarty)
{
$this->fetchArticles();
// }}}
// {{{ Mailing
-
+
+ /** Check whether this issue is empty
+ * An issue is empty if the email has no title (or the default one), or no articles and an empty head.
+ */
+ public function isEmpty()
+ {
+ return $this->title_mail == '' || $this->title_mail == 'to be continued' || (count($this->arts) == 0 && strlen($this->head) == 0);
+ }
+
/** Retrieve the 'Send before' date, in a clean format.
*/
public function getSendBeforeDate()
WHERE id = {?}',
$this->id);
- $ufc = new PFC_And($this->getRecipientsUFC(), new UFC_NLSubscribed($this->nl->id, $this->id), new UFC_HasEmailRedirect());
+ $ufc = new PFC_And($this->getRecipientsUFC(), new UFC_NLSubscribed($this->nl->id, $this->id), new UFC_HasValidEmail());
$emailsCount = 0;
- $uf = new UserFilter($ufc, new PlLimit(self::BATCH_SIZE));
+ $uf = new UserFilter($ufc, array(new UFO_IsAdmin(), new UFO_Uid()));
+ $limit = new PlLimit(self::BATCH_SIZE);
while (true) {
$sent = array();
- $users = $uf->getUsers();
+ $users = $uf->getUsers($limit);
if (count($users) == 0) {
return $emailsCount;
}
class NLArticle
{
// Maximum number of lines per article
- const MAX_LINES_PER_ARTICLE = 9;
+ const MAX_LINES_PER_ARTICLE = 8;
+ const MAX_CHARACTERS_PER_LINE = 68;
// {{{ properties
public function check()
{
- $text = MiniWiki::WikiToText($this->body);
- $arr = explode("\n",wordwrap($text,68));
- $c = 0;
- foreach ($arr as $line) {
- if (trim($line)) {
- $c++;
+ $rest = $this->remain();
+
+ return $rest['remaining_lines'] >= 0;
+ }
+
+ // }}}
+ // {{{ function remain()
+
+ public function remain()
+ {
+ $text = MiniWiki::WikiToText($this->body);
+ $array = explode("\n", wordwrap($text, self::MAX_CHARACTERS_PER_LINE));
+ $lines_count = 0;
+ foreach ($array as $line) {
+ if (trim($line) != '') {
+ ++$lines_count;
}
}
- return $c < self::MAX_LINES_PER_ARTICLE;
- }
+ return array(
+ 'remaining_lines' => self::MAX_LINES_PER_ARTICLE - $lines_count,
+ 'remaining_characters_for_last_line' => self::MAX_CHARACTERS_PER_LINE - strlen($array[count($array) - 1])
+ );
+ }
// }}}
// {{{ function parseUrlsFromArticle()