1<?php
2/**
3 * DokuWiki Plugin structtasks
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Chris MacMackin <cmacmackin@gmail.com>
7 *
8 */
9
10namespace dokuwiki\plugin\structtasks\meta;
11
12/**
13 * Abstract base class to handle sending emails about changes to task
14 * state. Each subclass will provide a function which returns a list
15 * of users to be notified (empty if no notification is required) and
16 * template text for the email message.
17 *
18 * @package dokuwiki\plugin\structtasks\meta
19 *
20 */
21abstract class AbstractNotifier
22{
23    /**
24     * First part of keys in the localisation files, corresponding to
25     * content of emails. The following keys are expected to be
26     * present: `PREFIX_subject` (email subject), `PREFIX_text`
27     * (plain-text email content), and `PREFIX_html` (HTML email
28     * content).
29     *
30     * The text of these localisations can make use of the following
31     * macros:
32     *
33     *   - @TITLE@
34     *   - @TITLELINK@
35     *   - @EDITURL@
36     *   - @EDITOR@
37     *   - @STATUS@
38     *   - @PREVSTATUS@
39     *   - @DUEDATE@
40     *   - @PREVDUEDATE@
41     *   - @WIKINAME@
42     *   - @DUEIN@
43     *
44     * Note: DUEIN is the number of days before the task is due or the
45     * number of days elapsed since the due-date.
46     */
47    const lang_key_prefix = 'DUMMY';
48
49    /**
50     * Callable to get configurations for this plugin.
51     */
52    protected $getConf;
53    /**
54     * Callable to get text in current language.
55     */
56    protected $getLang;
57
58    public function __construct(callable $getConf, callable $getLang) {
59        $this->getConf = $getConf;
60        $this->getLang = $getLang;
61  }
62
63    abstract function getNotifiableUsers($page, $editor_email, $new_data, $old_data);
64
65    static protected function timeFromLastMidnight($date) {
66        $today = date_create();
67        $today->setTime(0, 0);
68        $date->setTime(0, 0);
69        // For some reason, diff always seems to return absolute
70        // value, so just handling that manually here
71        $diff = $date->diff($today, true);
72        $factor = ($today < $date) ? 1 : -1;
73        return [$factor * $diff->y, $factor * $diff->m, $factor * $diff->d];
74    }
75
76    /**
77     * Works out how many days until the due-date (or since the
78     * due-date, as appropriate) and returns it in a nicely-formatted
79     * string.
80     */
81    static function dueIn($duedate) {
82        if (is_null($duedate)) {
83            return '';
84        }
85        list($y, $m, $d) = array_map('abs', self::timeFromLastMidnight($duedate));
86        $components = [];
87        if ($y != 0) {
88            $val = "{$y} year";
89            if ($y > 1) $val .= 's';
90            $components[] = $val;
91        }
92        if ($m != 0) {
93            $val = "{$m} month";
94            if ($m > 1) $val .= 's';
95            $components[] = $val;
96        }
97        if ($d != 0) {
98            $val = "{$d} day";
99            if ($d > 1) $val .= 's';
100            $components[] = $val;
101        }
102        switch (count($components)) {
103        case 0:
104            return '0 days';
105        case 1:
106            return $components[0];
107        case 2:
108            return $components[0] . ' and ' . $components[1];
109        case 3:
110            return $components[0] . ', ' . $components[1] . ', and ' . $components[2];
111        default:
112            throw new Exception("Invalid number of date components");
113        }
114    }
115
116    /**
117     * Returns true if the regular expression for closed tasks matches
118     * $status.
119     */
120    function isCompleted($status) {
121        $getConf = $this->getConf;
122        $completed_pattern = $getConf('completed');
123        return preg_match($completed_pattern, $status);
124    }
125
126    /**
127     * (Possibly) send a message for revisions to the given page, if
128     * necessary. $old_data and $new_data are associative arrays with
129     * the following keys:
130     *
131     *    - content: The page content.
132     *    - duedate: A DateTime object specifying when the task is
133     *      due. If no date was specified for this page, will be NULL.
134     *    - duedate_formatted: A string with the due-date formatted
135     *      according to the struct schema. Empty if no date specified.
136     *    - assignees: An array of email addresses for the people this
137     *      task has been assigned to.
138     *    - status: The completion status of the task.
139     */
140    public function sendMessage($page_id, $page_title, $editor, $editor_email, $new_data,
141                                $old_data, $mailer = NULL) {
142        if (is_null($mailer)) $mailer = new \Mailer();
143        $notifiable_users = $this->getNotifiableUsers($page_id, $editor_email, $new_data, $old_data);
144        if (count($notifiable_users) == 0) return;
145        global $conf;
146        $getLang = $this->getLang;
147        if ($page_title == '') $page_title = $page_id;
148        $url = wl($page_id, [], true);
149        $text_subs = [
150            'TITLE' => $page_title,
151            'TITLELINK' => "\"${page_title}\" <${url}>",
152            'EDITURL' => wl($page_id, ['do' => 'edit'], true, '&'),
153            'EDITOR' => $editor,
154            'STATUS' => $new_data['status'],
155            'PREVSTATUS' => $old_data['status'],
156            'DUEDATE' => $new_data['duedate_formatted'],
157            'PREVDUEDATE' => $old_data['duedate_formatted'],
158            'WIKINAME' => $conf['title'],
159            'DUEIN' => $this->dueIn($new_data['duedate']),
160        ];
161        $html_subs = [
162            'TITLELINK' => "&ldquo;<a href=\"${url}\">${page_title}</a>&rdquo;",
163            'EDITURL' => "<a href=\"{$text_subs['EDITURL']}\">edit the page</a>"
164        ];
165        $subject = str_replace(
166            array_map(function ($x) {return "@$x@";}, array_keys($text_subs)),
167            $text_subs,
168            $getLang($this::lang_key_prefix . '_subject'));
169        $mailer->setBody($getLang($this::lang_key_prefix . '_text'),
170                         $text_subs, $html_subs,
171                         $getLang($this::lang_key_prefix . '_html'));
172        foreach ($notifiable_users as $user) {
173            $mailer->to($user);
174            $mailer->subject($subject);
175            $mailer->send();
176        }
177    }
178}
179