1<?php 2 3namespace dokuwiki\Subscriptions; 4 5use dokuwiki\Input\Input; 6use DokuWiki_Auth_Plugin; 7use Exception; 8 9class SubscriberManager 10{ 11 12 /** 13 * Check if subscription system is enabled 14 * 15 * @return bool 16 */ 17 public function isenabled() { 18 return actionOK('subscribe'); 19 } 20 21 /** 22 * Adds a new subscription for the given page or namespace 23 * 24 * This will automatically overwrite any existent subscription for the given user on this 25 * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. 26 * 27 * @param string $id The target page or namespace, specified by id; Namespaces 28 * are identified by appending a colon. 29 * @param string $user 30 * @param string $style 31 * @param string $data 32 * @throws Exception when user or style is empty 33 * @return bool 34 */ 35 public function add($id, $user, $style, $data = '') { 36 if(!$this->isenabled()) return false; 37 38 // delete any existing subscription 39 $this->remove($id, $user); 40 41 $user = auth_nameencode(trim($user)); 42 $style = trim($style); 43 $data = trim($data); 44 45 if(!$user) throw new Exception('no subscription user given'); 46 if(!$style) throw new Exception('no subscription style given'); 47 if(!$data) $data = time(); //always add current time for new subscriptions 48 49 $line = "$user $style $data\n"; 50 $file = $this->file($id); 51 return io_saveFile($file, $line, true); 52 } 53 54 55 /** 56 * Removes a subscription for the given page or namespace 57 * 58 * This removes all subscriptions matching the given criteria on the given page or 59 * namespace. It will *not* modify any subscriptions that may exist in higher 60 * namespaces. 61 * 62 * @param string $id The target object’s (namespace or page) id 63 * @param string|array $user 64 * @param string|array $style 65 * @param string|array $data 66 * @return bool 67 */ 68 public function remove($id, $user = null, $style = null, $data = null) { 69 if(!$this->isenabled()) return false; 70 71 $file = $this->file($id); 72 if(!file_exists($file)) return true; 73 74 $regexBuilder = new SubscriberRegexBuilder(); 75 $re = $regexBuilder->buildRegex($user, $style, $data); 76 return io_deleteFromFile($file, $re, true); 77 } 78 79 /** 80 * Get data for $INFO['subscribed'] 81 * 82 * $INFO['subscribed'] is either false if no subscription for the current page 83 * and user is in effect. Else it contains an array of arrays with the fields 84 * “target”, “style”, and optionally “data”. 85 * 86 * @param string $id Page ID, defaults to global $ID 87 * @param string $user User, defaults to $_SERVER['REMOTE_USER'] 88 * @return array|false 89 * @author Adrian Lang <lang@cosmocode.de> 90 */ 91 public function userSubscription($id = '', $user = '') { 92 if(!$this->isenabled()) return false; 93 94 global $ID; 95 /** @var Input $INPUT */ 96 global $INPUT; 97 if(!$id) $id = $ID; 98 if(!$user) $user = $INPUT->server->str('REMOTE_USER'); 99 100 $subs = $this->subscribers($id, $user); 101 if(!count($subs)) return false; 102 103 $result = array(); 104 foreach($subs as $target => $info) { 105 $result[] = array( 106 'target' => $target, 107 'style' => $info[$user][0], 108 'data' => $info[$user][1] 109 ); 110 } 111 112 return $result; 113 } 114 115 /** 116 * Recursively search for matching subscriptions 117 * 118 * This function searches all relevant subscription files for a page or 119 * namespace. 120 * 121 * @author Adrian Lang <lang@cosmocode.de> 122 * 123 * @param string $page The target object’s (namespace or page) id 124 * @param string|array $user 125 * @param string|array $style 126 * @param string|array $data 127 * @return array 128 */ 129 public function subscribers($page, $user = null, $style = null, $data = null) { 130 if(!$this->isenabled()) return array(); 131 132 // Construct list of files which may contain relevant subscriptions. 133 $files = array(':' => $this->file(':')); 134 do { 135 $files[$page] = $this->file($page); 136 $page = getNS(rtrim($page, ':')).':'; 137 } while($page !== ':'); 138 139 $regexBuilder = new SubscriberRegexBuilder(); 140 $re = $regexBuilder->buildRegex($user, $style, $data); 141 142 // Handle files. 143 $result = array(); 144 foreach($files as $target => $file) { 145 if(!file_exists($file)) continue; 146 147 $lines = file($file); 148 foreach($lines as $line) { 149 // fix old style subscription files 150 if(strpos($line, ' ') === false) $line = trim($line)." every\n"; 151 152 // check for matching entries 153 if(!preg_match($re, $line, $m)) continue; 154 155 $u = rawurldecode($m[1]); // decode the user name 156 if(!isset($result[$target])) $result[$target] = array(); 157 $result[$target][$u] = array($m[2], $m[3]); // add to result 158 } 159 } 160 return array_reverse($result); 161 } 162 163 /** 164 * Default callback for COMMON_NOTIFY_ADDRESSLIST 165 * 166 * Aggregates all email addresses of user who have subscribed the given page with 'every' style 167 * 168 * @author Steven Danz <steven-danz@kc.rr.com> 169 * @author Adrian Lang <lang@cosmocode.de> 170 * 171 * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, 172 * use an array for the addresses within it 173 * 174 * @param array &$data Containing the entries: 175 * - $id (the page id), 176 * - $self (whether the author should be notified, 177 * - $addresslist (current email address list) 178 * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) 179 */ 180 public function notifyAddresses(&$data) { 181 if(!$this->isenabled()) return; 182 183 /** @var DokuWiki_Auth_Plugin $auth */ 184 global $auth; 185 global $conf; 186 /** @var \Input $INPUT */ 187 global $INPUT; 188 189 $id = $data['id']; 190 $self = $data['self']; 191 $addresslist = $data['addresslist']; 192 193 $subscriptions = $this->subscribers($id, null, 'every'); 194 195 $result = array(); 196 foreach($subscriptions as $target => $users) { 197 foreach($users as $user => $info) { 198 $userinfo = $auth->getUserData($user); 199 if($userinfo === false) continue; 200 if(!$userinfo['mail']) continue; 201 if(!$self && $user == $INPUT->server->str('REMOTE_USER')) continue; //skip our own changes 202 203 $level = auth_aclcheck($id, $user, $userinfo['grps']); 204 if($level >= AUTH_READ) { 205 if(strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere 206 $result[$user] = $userinfo['mail']; 207 } 208 } 209 } 210 } 211 $data['addresslist'] = trim($addresslist.','.implode(',', $result), ','); 212 } 213 214 /** 215 * Return the subscription meta file for the given ID 216 * 217 * @author Adrian Lang <lang@cosmocode.de> 218 * 219 * @param string $id The target page or namespace, specified by id; Namespaces 220 * are identified by appending a colon. 221 * @return string 222 */ 223 protected function file($id) { 224 $meta_fname = '.mlist'; 225 if((substr($id, -1, 1) === ':')) { 226 $meta_froot = getNS($id); 227 $meta_fname = '/'.$meta_fname; 228 } else { 229 $meta_froot = $id; 230 } 231 return metaFN((string) $meta_froot, $meta_fname); 232 } 233 234} 235