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' => "“<a href=\"${url}\">${page_title}</a>”", 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