1479c05b1SMichael Große<?php 2479c05b1SMichael Große 3479c05b1SMichael Großenamespace dokuwiki\Subscriptions; 4479c05b1SMichael Große 551ee2399SGerrit Uitslaguse dokuwiki\Extension\AuthPlugin; 6479c05b1SMichael Großeuse dokuwiki\Input\Input; 7479c05b1SMichael Großeuse Exception; 8479c05b1SMichael Große 9479c05b1SMichael Großeclass SubscriberManager 10479c05b1SMichael Große{ 11479c05b1SMichael Große /** 12479c05b1SMichael Große * Check if subscription system is enabled 13479c05b1SMichael Große * 14479c05b1SMichael Große * @return bool 15479c05b1SMichael Große */ 169c22b77cSMichael Große public function isenabled() 179c22b77cSMichael Große { 18479c05b1SMichael Große return actionOK('subscribe'); 19479c05b1SMichael Große } 20479c05b1SMichael Große 21479c05b1SMichael Große /** 22479c05b1SMichael Große * Adds a new subscription for the given page or namespace 23479c05b1SMichael Große * 24479c05b1SMichael Große * This will automatically overwrite any existent subscription for the given user on this 25479c05b1SMichael Große * *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces. 26479c05b1SMichael Große * 279c22b77cSMichael Große * @throws Exception when user or style is empty 289c22b77cSMichael Große * 29479c05b1SMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 30479c05b1SMichael Große * are identified by appending a colon. 31479c05b1SMichael Große * @param string $user 32479c05b1SMichael Große * @param string $style 33479c05b1SMichael Große * @param string $data 349c22b77cSMichael Große * 35479c05b1SMichael Große * @return bool 36479c05b1SMichael Große */ 379c22b77cSMichael Große public function add($id, $user, $style, $data = '') 389c22b77cSMichael Große { 399c22b77cSMichael Große if (!$this->isenabled()) { 409c22b77cSMichael Große return false; 419c22b77cSMichael Große } 42479c05b1SMichael Große 43479c05b1SMichael Große // delete any existing subscription 44479c05b1SMichael Große $this->remove($id, $user); 45479c05b1SMichael Große 46479c05b1SMichael Große $user = auth_nameencode(trim($user)); 47479c05b1SMichael Große $style = trim($style); 48479c05b1SMichael Große $data = trim($data); 49479c05b1SMichael Große 509c22b77cSMichael Große if (!$user) { 519c22b77cSMichael Große throw new Exception('no subscription user given'); 529c22b77cSMichael Große } 539c22b77cSMichael Große if (!$style) { 549c22b77cSMichael Große throw new Exception('no subscription style given'); 559c22b77cSMichael Große } 569c22b77cSMichael Große if (!$data) { 579c22b77cSMichael Große $data = time(); 589c22b77cSMichael Große } //always add current time for new subscriptions 59479c05b1SMichael Große 60479c05b1SMichael Große $line = "$user $style $data\n"; 61479c05b1SMichael Große $file = $this->file($id); 62479c05b1SMichael Große return io_saveFile($file, $line, true); 63479c05b1SMichael Große } 64479c05b1SMichael Große 65479c05b1SMichael Große 66479c05b1SMichael Große /** 67479c05b1SMichael Große * Removes a subscription for the given page or namespace 68479c05b1SMichael Große * 69479c05b1SMichael Große * This removes all subscriptions matching the given criteria on the given page or 70479c05b1SMichael Große * namespace. It will *not* modify any subscriptions that may exist in higher 71479c05b1SMichael Große * namespaces. 72479c05b1SMichael Große * 73479c05b1SMichael Große * @param string $id The target object’s (namespace or page) id 74479c05b1SMichael Große * @param string|array $user 75479c05b1SMichael Große * @param string|array $style 76479c05b1SMichael Große * @param string|array $data 779c22b77cSMichael Große * 78479c05b1SMichael Große * @return bool 7951ee2399SGerrit Uitslag * @throws Exception 80479c05b1SMichael Große */ 819c22b77cSMichael Große public function remove($id, $user = null, $style = null, $data = null) 829c22b77cSMichael Große { 839c22b77cSMichael Große if (!$this->isenabled()) { 849c22b77cSMichael Große return false; 859c22b77cSMichael Große } 86479c05b1SMichael Große 87479c05b1SMichael Große $file = $this->file($id); 889c22b77cSMichael Große if (!file_exists($file)) { 899c22b77cSMichael Große return true; 909c22b77cSMichael Große } 91479c05b1SMichael Große 92479c05b1SMichael Große $regexBuilder = new SubscriberRegexBuilder(); 93479c05b1SMichael Große $re = $regexBuilder->buildRegex($user, $style, $data); 94479c05b1SMichael Große return io_deleteFromFile($file, $re, true); 95479c05b1SMichael Große } 96479c05b1SMichael Große 97479c05b1SMichael Große /** 98479c05b1SMichael Große * Get data for $INFO['subscribed'] 99479c05b1SMichael Große * 100479c05b1SMichael Große * $INFO['subscribed'] is either false if no subscription for the current page 101479c05b1SMichael Große * and user is in effect. Else it contains an array of arrays with the fields 102479c05b1SMichael Große * “target”, “style”, and optionally “data”. 103479c05b1SMichael Große * 104479c05b1SMichael Große * @param string $id Page ID, defaults to global $ID 105479c05b1SMichael Große * @param string $user User, defaults to $_SERVER['REMOTE_USER'] 1069c22b77cSMichael Große * 107479c05b1SMichael Große * @return array|false 10851ee2399SGerrit Uitslag * @throws Exception 10951ee2399SGerrit Uitslag * 11051ee2399SGerrit Uitslag * @author Adrian Lang <lang@cosmocode.de> 111479c05b1SMichael Große */ 1129c22b77cSMichael Große public function userSubscription($id = '', $user = '') 1139c22b77cSMichael Große { 1149c22b77cSMichael Große if (!$this->isenabled()) { 1159c22b77cSMichael Große return false; 1169c22b77cSMichael Große } 117479c05b1SMichael Große 118479c05b1SMichael Große global $ID; 119479c05b1SMichael Große /** @var Input $INPUT */ 120479c05b1SMichael Große global $INPUT; 1219c22b77cSMichael Große if (!$id) { 1229c22b77cSMichael Große $id = $ID; 1239c22b77cSMichael Große } 1249c22b77cSMichael Große if (!$user) { 1259c22b77cSMichael Große $user = $INPUT->server->str('REMOTE_USER'); 1269c22b77cSMichael Große } 127479c05b1SMichael Große 1289efb6f4aSPhy if (empty($user)) { 1299efb6f4aSPhy // not logged in 1309efb6f4aSPhy return false; 1319efb6f4aSPhy } 1329efb6f4aSPhy 133479c05b1SMichael Große $subs = $this->subscribers($id, $user); 1345983e241SAndreas Gohr if ($subs === []) { 1359c22b77cSMichael Große return false; 1369c22b77cSMichael Große } 137479c05b1SMichael Große 1389c22b77cSMichael Große $result = []; 139479c05b1SMichael Große foreach ($subs as $target => $info) { 1409c22b77cSMichael Große $result[] = [ 141479c05b1SMichael Große 'target' => $target, 142479c05b1SMichael Große 'style' => $info[$user][0], 1439c22b77cSMichael Große 'data' => $info[$user][1], 1449c22b77cSMichael Große ]; 145479c05b1SMichael Große } 146479c05b1SMichael Große 147479c05b1SMichael Große return $result; 148479c05b1SMichael Große } 149479c05b1SMichael Große 150479c05b1SMichael Große /** 151479c05b1SMichael Große * Recursively search for matching subscriptions 152479c05b1SMichael Große * 153479c05b1SMichael Große * This function searches all relevant subscription files for a page or 154479c05b1SMichael Große * namespace. 155479c05b1SMichael Große * 156479c05b1SMichael Große * @param string $page The target object’s (namespace or page) id 157479c05b1SMichael Große * @param string|array $user 158479c05b1SMichael Große * @param string|array $style 159479c05b1SMichael Große * @param string|array $data 1609c22b77cSMichael Große * 161479c05b1SMichael Große * @return array 16251ee2399SGerrit Uitslag * @throws Exception 16351ee2399SGerrit Uitslag * 16451ee2399SGerrit Uitslag * @author Adrian Lang <lang@cosmocode.de> 16551ee2399SGerrit Uitslag * 166479c05b1SMichael Große */ 1679c22b77cSMichael Große public function subscribers($page, $user = null, $style = null, $data = null) 1689c22b77cSMichael Große { 1699c22b77cSMichael Große if (!$this->isenabled()) { 1709c22b77cSMichael Große return []; 1719c22b77cSMichael Große } 172479c05b1SMichael Große 173479c05b1SMichael Große // Construct list of files which may contain relevant subscriptions. 1749c22b77cSMichael Große $files = [':' => $this->file(':')]; 175479c05b1SMichael Große do { 176479c05b1SMichael Große $files[$page] = $this->file($page); 177479c05b1SMichael Große $page = getNS(rtrim($page, ':')) . ':'; 178479c05b1SMichael Große } while ($page !== ':'); 179479c05b1SMichael Große 180479c05b1SMichael Große $regexBuilder = new SubscriberRegexBuilder(); 181479c05b1SMichael Große $re = $regexBuilder->buildRegex($user, $style, $data); 182479c05b1SMichael Große 183479c05b1SMichael Große // Handle files. 1849c22b77cSMichael Große $result = []; 185479c05b1SMichael Große foreach ($files as $target => $file) { 1869c22b77cSMichael Große if (!file_exists($file)) { 1879c22b77cSMichael Große continue; 1889c22b77cSMichael Große } 189479c05b1SMichael Große 190479c05b1SMichael Große $lines = file($file); 191479c05b1SMichael Große foreach ($lines as $line) { 192479c05b1SMichael Große // fix old style subscription files 1939c22b77cSMichael Große if (strpos($line, ' ') === false) { 1949c22b77cSMichael Große $line = trim($line) . " every\n"; 1959c22b77cSMichael Große } 196479c05b1SMichael Große 197479c05b1SMichael Große // check for matching entries 1989c22b77cSMichael Große if (!preg_match($re, $line, $m)) { 1999c22b77cSMichael Große continue; 2009c22b77cSMichael Große } 201479c05b1SMichael Große 202062cf88bSAndreas Gohr // if no last sent is set, use 0 203062cf88bSAndreas Gohr if (!isset($m[3])) { 204062cf88bSAndreas Gohr $m[3] = 0; 205062cf88bSAndreas Gohr } 206062cf88bSAndreas Gohr 207479c05b1SMichael Große $u = rawurldecode($m[1]); // decode the user name 2089c22b77cSMichael Große if (!isset($result[$target])) { 2099c22b77cSMichael Große $result[$target] = []; 2109c22b77cSMichael Große } 2119c22b77cSMichael Große $result[$target][$u] = [$m[2], $m[3]]; // add to result 212479c05b1SMichael Große } 213479c05b1SMichael Große } 214479c05b1SMichael Große return array_reverse($result); 215479c05b1SMichael Große } 216479c05b1SMichael Große 217479c05b1SMichael Große /** 218479c05b1SMichael Große * Default callback for COMMON_NOTIFY_ADDRESSLIST 219479c05b1SMichael Große * 220479c05b1SMichael Große * Aggregates all email addresses of user who have subscribed the given page with 'every' style 221479c05b1SMichael Große * 222479c05b1SMichael Große * @param array &$data Containing the entries: 223479c05b1SMichael Große * - $id (the page id), 224479c05b1SMichael Große * - $self (whether the author should be notified, 225479c05b1SMichael Große * - $addresslist (current email address list) 226479c05b1SMichael Große * - $replacements (array of additional string substitutions, @KEY@ to be replaced by value) 22751ee2399SGerrit Uitslag * @throws Exception 22851ee2399SGerrit Uitslag * 22951ee2399SGerrit Uitslag * @author Adrian Lang <lang@cosmocode.de> 23051ee2399SGerrit Uitslag * @author Steven Danz <steven-danz@kc.rr.com> 23151ee2399SGerrit Uitslag * 23251ee2399SGerrit Uitslag * @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead, 23351ee2399SGerrit Uitslag * use an array for the addresses within it 234479c05b1SMichael Große */ 2359c22b77cSMichael Große public function notifyAddresses(&$data) 2369c22b77cSMichael Große { 2379c22b77cSMichael Große if (!$this->isenabled()) { 2389c22b77cSMichael Große return; 2399c22b77cSMichael Große } 240479c05b1SMichael Große 24151ee2399SGerrit Uitslag /** @var AuthPlugin $auth */ 242479c05b1SMichael Große global $auth; 243479c05b1SMichael Große global $conf; 244479c05b1SMichael Große /** @var \Input $INPUT */ 245479c05b1SMichael Große global $INPUT; 246479c05b1SMichael Große 247479c05b1SMichael Große $id = $data['id']; 248479c05b1SMichael Große $self = $data['self']; 249479c05b1SMichael Große $addresslist = $data['addresslist']; 250479c05b1SMichael Große 251479c05b1SMichael Große $subscriptions = $this->subscribers($id, null, 'every'); 252479c05b1SMichael Große 2539c22b77cSMichael Große $result = []; 2545983e241SAndreas Gohr foreach ($subscriptions as $users) { 255479c05b1SMichael Große foreach ($users as $user => $info) { 256479c05b1SMichael Große $userinfo = $auth->getUserData($user); 2579c22b77cSMichael Große if ($userinfo === false) { 2589c22b77cSMichael Große continue; 2599c22b77cSMichael Große } 2609c22b77cSMichael Große if (!$userinfo['mail']) { 2619c22b77cSMichael Große continue; 2629c22b77cSMichael Große } 2639c22b77cSMichael Große if (!$self && $user == $INPUT->server->str('REMOTE_USER')) { 2649c22b77cSMichael Große continue; 2659c22b77cSMichael Große } //skip our own changes 266479c05b1SMichael Große 267479c05b1SMichael Große $level = auth_aclcheck($id, $user, $userinfo['grps']); 268479c05b1SMichael Große if ($level >= AUTH_READ) { 269479c05b1SMichael Große if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere 270479c05b1SMichael Große $result[$user] = $userinfo['mail']; 271479c05b1SMichael Große } 272479c05b1SMichael Große } 273479c05b1SMichael Große } 274479c05b1SMichael Große } 275479c05b1SMichael Große $data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ','); 276479c05b1SMichael Große } 277479c05b1SMichael Große 278479c05b1SMichael Große /** 279479c05b1SMichael Große * Return the subscription meta file for the given ID 280479c05b1SMichael Große * 281479c05b1SMichael Große * @author Adrian Lang <lang@cosmocode.de> 282479c05b1SMichael Große * 283479c05b1SMichael Große * @param string $id The target page or namespace, specified by id; Namespaces 284479c05b1SMichael Große * are identified by appending a colon. 2859c22b77cSMichael Große * 286479c05b1SMichael Große * @return string 287479c05b1SMichael Große */ 2889c22b77cSMichael Große protected function file($id) 2899c22b77cSMichael Große { 290479c05b1SMichael Große $meta_fname = '.mlist'; 291*6c16a3a9Sfiwswe if (str_ends_with($id, ':')) { 292479c05b1SMichael Große $meta_froot = getNS($id); 293479c05b1SMichael Große $meta_fname = '/' . $meta_fname; 294479c05b1SMichael Große } else { 295479c05b1SMichael Große $meta_froot = $id; 296479c05b1SMichael Große } 297479c05b1SMichael Große return metaFN((string)$meta_froot, $meta_fname); 298479c05b1SMichael Große } 299479c05b1SMichael Große} 300