291 lines
8.5 KiB
PHP
291 lines
8.5 KiB
PHP
<?php
|
||
|
||
namespace dokuwiki\Subscriptions;
|
||
|
||
use dokuwiki\Input\Input;
|
||
use DokuWiki_Auth_Plugin;
|
||
use Exception;
|
||
|
||
class SubscriberManager
|
||
{
|
||
|
||
/**
|
||
* Check if subscription system is enabled
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isenabled()
|
||
{
|
||
return actionOK('subscribe');
|
||
}
|
||
|
||
/**
|
||
* Adds a new subscription for the given page or namespace
|
||
*
|
||
* This will automatically overwrite any existent subscription for the given user on this
|
||
* *exact* page or namespace. It will *not* modify any subscription that may exist in higher namespaces.
|
||
*
|
||
* @throws Exception when user or style is empty
|
||
*
|
||
* @param string $id The target page or namespace, specified by id; Namespaces
|
||
* are identified by appending a colon.
|
||
* @param string $user
|
||
* @param string $style
|
||
* @param string $data
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function add($id, $user, $style, $data = '')
|
||
{
|
||
if (!$this->isenabled()) {
|
||
return false;
|
||
}
|
||
|
||
// delete any existing subscription
|
||
$this->remove($id, $user);
|
||
|
||
$user = auth_nameencode(trim($user));
|
||
$style = trim($style);
|
||
$data = trim($data);
|
||
|
||
if (!$user) {
|
||
throw new Exception('no subscription user given');
|
||
}
|
||
if (!$style) {
|
||
throw new Exception('no subscription style given');
|
||
}
|
||
if (!$data) {
|
||
$data = time();
|
||
} //always add current time for new subscriptions
|
||
|
||
$line = "$user $style $data\n";
|
||
$file = $this->file($id);
|
||
return io_saveFile($file, $line, true);
|
||
}
|
||
|
||
|
||
/**
|
||
* Removes a subscription for the given page or namespace
|
||
*
|
||
* This removes all subscriptions matching the given criteria on the given page or
|
||
* namespace. It will *not* modify any subscriptions that may exist in higher
|
||
* namespaces.
|
||
*
|
||
* @param string $id The target object’s (namespace or page) id
|
||
* @param string|array $user
|
||
* @param string|array $style
|
||
* @param string|array $data
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function remove($id, $user = null, $style = null, $data = null)
|
||
{
|
||
if (!$this->isenabled()) {
|
||
return false;
|
||
}
|
||
|
||
$file = $this->file($id);
|
||
if (!file_exists($file)) {
|
||
return true;
|
||
}
|
||
|
||
$regexBuilder = new SubscriberRegexBuilder();
|
||
$re = $regexBuilder->buildRegex($user, $style, $data);
|
||
return io_deleteFromFile($file, $re, true);
|
||
}
|
||
|
||
/**
|
||
* Get data for $INFO['subscribed']
|
||
*
|
||
* $INFO['subscribed'] is either false if no subscription for the current page
|
||
* and user is in effect. Else it contains an array of arrays with the fields
|
||
* “target”, “style”, and optionally “data”.
|
||
*
|
||
* @author Adrian Lang <lang@cosmocode.de>
|
||
*
|
||
* @param string $id Page ID, defaults to global $ID
|
||
* @param string $user User, defaults to $_SERVER['REMOTE_USER']
|
||
*
|
||
* @return array|false
|
||
*/
|
||
public function userSubscription($id = '', $user = '')
|
||
{
|
||
if (!$this->isenabled()) {
|
||
return false;
|
||
}
|
||
|
||
global $ID;
|
||
/** @var Input $INPUT */
|
||
global $INPUT;
|
||
if (!$id) {
|
||
$id = $ID;
|
||
}
|
||
if (!$user) {
|
||
$user = $INPUT->server->str('REMOTE_USER');
|
||
}
|
||
|
||
if (empty($user)) {
|
||
// not logged in
|
||
return false;
|
||
}
|
||
|
||
$subs = $this->subscribers($id, $user);
|
||
if (!count($subs)) {
|
||
return false;
|
||
}
|
||
|
||
$result = [];
|
||
foreach ($subs as $target => $info) {
|
||
$result[] = [
|
||
'target' => $target,
|
||
'style' => $info[$user][0],
|
||
'data' => $info[$user][1],
|
||
];
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Recursively search for matching subscriptions
|
||
*
|
||
* This function searches all relevant subscription files for a page or
|
||
* namespace.
|
||
*
|
||
* @author Adrian Lang <lang@cosmocode.de>
|
||
*
|
||
* @param string $page The target object’s (namespace or page) id
|
||
* @param string|array $user
|
||
* @param string|array $style
|
||
* @param string|array $data
|
||
*
|
||
* @return array
|
||
*/
|
||
public function subscribers($page, $user = null, $style = null, $data = null)
|
||
{
|
||
if (!$this->isenabled()) {
|
||
return [];
|
||
}
|
||
|
||
// Construct list of files which may contain relevant subscriptions.
|
||
$files = [':' => $this->file(':')];
|
||
do {
|
||
$files[$page] = $this->file($page);
|
||
$page = getNS(rtrim($page, ':')) . ':';
|
||
} while ($page !== ':');
|
||
|
||
$regexBuilder = new SubscriberRegexBuilder();
|
||
$re = $regexBuilder->buildRegex($user, $style, $data);
|
||
|
||
// Handle files.
|
||
$result = [];
|
||
foreach ($files as $target => $file) {
|
||
if (!file_exists($file)) {
|
||
continue;
|
||
}
|
||
|
||
$lines = file($file);
|
||
foreach ($lines as $line) {
|
||
// fix old style subscription files
|
||
if (strpos($line, ' ') === false) {
|
||
$line = trim($line) . " every\n";
|
||
}
|
||
|
||
// check for matching entries
|
||
if (!preg_match($re, $line, $m)) {
|
||
continue;
|
||
}
|
||
|
||
$u = rawurldecode($m[1]); // decode the user name
|
||
if (!isset($result[$target])) {
|
||
$result[$target] = [];
|
||
}
|
||
$result[$target][$u] = [$m[2], $m[3]]; // add to result
|
||
}
|
||
}
|
||
return array_reverse($result);
|
||
}
|
||
|
||
/**
|
||
* Default callback for COMMON_NOTIFY_ADDRESSLIST
|
||
*
|
||
* Aggregates all email addresses of user who have subscribed the given page with 'every' style
|
||
*
|
||
* @author Adrian Lang <lang@cosmocode.de>
|
||
* @author Steven Danz <steven-danz@kc.rr.com>
|
||
*
|
||
* @todo move the whole functionality into this class, trigger SUBSCRIPTION_NOTIFY_ADDRESSLIST instead,
|
||
* use an array for the addresses within it
|
||
*
|
||
* @param array &$data Containing the entries:
|
||
* - $id (the page id),
|
||
* - $self (whether the author should be notified,
|
||
* - $addresslist (current email address list)
|
||
* - $replacements (array of additional string substitutions, @KEY@ to be replaced by value)
|
||
*/
|
||
public function notifyAddresses(&$data)
|
||
{
|
||
if (!$this->isenabled()) {
|
||
return;
|
||
}
|
||
|
||
/** @var DokuWiki_Auth_Plugin $auth */
|
||
global $auth;
|
||
global $conf;
|
||
/** @var \Input $INPUT */
|
||
global $INPUT;
|
||
|
||
$id = $data['id'];
|
||
$self = $data['self'];
|
||
$addresslist = $data['addresslist'];
|
||
|
||
$subscriptions = $this->subscribers($id, null, 'every');
|
||
|
||
$result = [];
|
||
foreach ($subscriptions as $target => $users) {
|
||
foreach ($users as $user => $info) {
|
||
$userinfo = $auth->getUserData($user);
|
||
if ($userinfo === false) {
|
||
continue;
|
||
}
|
||
if (!$userinfo['mail']) {
|
||
continue;
|
||
}
|
||
if (!$self && $user == $INPUT->server->str('REMOTE_USER')) {
|
||
continue;
|
||
} //skip our own changes
|
||
|
||
$level = auth_aclcheck($id, $user, $userinfo['grps']);
|
||
if ($level >= AUTH_READ) {
|
||
if (strcasecmp($userinfo['mail'], $conf['notify']) != 0) { //skip user who get notified elsewhere
|
||
$result[$user] = $userinfo['mail'];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
$data['addresslist'] = trim($addresslist . ',' . implode(',', $result), ',');
|
||
}
|
||
|
||
/**
|
||
* Return the subscription meta file for the given ID
|
||
*
|
||
* @author Adrian Lang <lang@cosmocode.de>
|
||
*
|
||
* @param string $id The target page or namespace, specified by id; Namespaces
|
||
* are identified by appending a colon.
|
||
*
|
||
* @return string
|
||
*/
|
||
protected function file($id)
|
||
{
|
||
$meta_fname = '.mlist';
|
||
if ((substr($id, -1, 1) === ':')) {
|
||
$meta_froot = getNS($id);
|
||
$meta_fname = '/' . $meta_fname;
|
||
} else {
|
||
$meta_froot = $id;
|
||
}
|
||
return metaFN((string)$meta_froot, $meta_fname);
|
||
}
|
||
}
|