1<?php
2
3use dokuwiki\Extension\Plugin;
4
5/**
6 * DokuWiki Plugin struct (Helper Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Szymon Olewniczak <dokuwiki@cosmocode.de>
10 */
11class helper_plugin_watchcycle extends Plugin
12{
13    public const MAINTAINERS_RAW = 0;
14    public const MAINTAINERS_FLAT = 1;
15    public const MAINTAINERS_EXPANDED = 2;
16
17    /**
18     * Create HTML for an icon showing the maintenance status of the provided pageid
19     *
20     * @param string $pageid the full pageid
21     *
22     * @return string span with inline svg icon and classes
23     */
24    public function getSearchResultIconHTML($pageid)
25    {
26        /* @var \DokuWiki_Auth_Plugin $auth */
27        global $auth;
28        if ($auth === null) return '';
29
30        /* @var \helper_plugin_watchcycle $helper */
31        $helper = plugin_load('helper', 'watchcycle');
32        $watchcycle = p_get_metadata($pageid, 'plugin watchcycle');
33        if (!$watchcycle) {
34            return '';
35        }
36
37        $days_ago = $helper->daysAgo($watchcycle['last_maintainer_rev']);
38
39        $check_needed = false;
40        if ($days_ago > $watchcycle['cycle']) {
41            $check_needed = true;
42        }
43
44        $all = $this->getMaintainers($watchcycle['maintainer']);
45        $title = $this->getLang('maintained by') . implode(', ', array_keys($all)) . ' ';
46
47        if ($watchcycle['changes'] === -1) {
48            $title .= $this->getLang('never checked');
49        } else {
50            $title .= sprintf($this->getLang('last check'), $days_ago);
51        }
52
53        $class = ['plugin__watchcycle_searchresult_icon'];
54        if ($check_needed) {
55            $class[] = 'check_needed';
56            $title .= ' (' . $this->getLang('check needed') . ')';
57        }
58        $icon = '<span class="' . implode(' ', $class) . '" title="' . $title . '">';
59        $icon .= inlineSVG(DOKU_PLUGIN . 'watchcycle/admin.svg');
60        $icon .= '</span>';
61        return $icon;
62    }
63
64    /**
65     * @param $time
66     * @param $now
67     *
68     * @return int
69     */
70    public function daysAgo($time, $now = false)
71    {
72        if (!$now) {
73            $now = time();
74        }
75
76        $diff = ($now - $time) / (60 * 60 * 24);
77        return (int)$diff;
78    }
79
80
81    /**
82     * Returns true if the maintainer definition matches existing users and groups
83     *
84     * @param string $def
85     * @return bool
86     */
87    public function validateMaintainerString($def)
88    {
89        /* @var DokuWiki_Auth_Plugin $auth */
90        global $auth;
91        if ($auth === null) return false; // no valid auth setup
92
93        $all = explode(',', $def);
94        foreach ($all as $item) {
95            $item = trim($item);
96            if (strpos($item, '@') !== false) {
97                // check if group exists
98                if (empty($auth->retrieveUsers(0, 1, ['grps' => ltrim($item, '@')]))) {
99                    return false;
100                }
101            } elseif ($auth->getUserData($item) === false) {
102                return false;
103            }
104        }
105        return true;
106    }
107
108    /**
109     * Returns a parsed representation of the maintainer string
110     *
111     * keys are the user and group names, value is either:
112     *
113     *  - the user data array
114     *  - null for groups
115     *  - false for unknown users
116     *
117     * @param string $def maintainer definition as given in the syntax
118     * @return array
119     */
120    public function getMaintainers($def)
121    {
122        /* @var DokuWiki_Auth_Plugin $auth */
123        global $auth;
124
125        $found = [];
126        if ($auth === null) return $found;
127
128        $all = explode(',', $def);
129        foreach ($all as $item) {
130            $item = trim($item);
131            if ($item[0] === '@') {
132                $found[$item] = null; // no detail info on groups
133            } else {
134                $found[$item] = $auth->getUserData($item);
135            }
136        }
137
138        return $found;
139    }
140
141    /**
142     * @param string $def maintainer definition as given in the syntax
143     * @return string[] list of email addresses to inform
144     */
145    public function getMaintainerMails($def)
146    {
147        /* @var \dokuwiki\Extension\AuthPlugin $auth */
148        global $auth;
149        $auth ??= auth_setup();
150        if (!$auth) return [];
151
152        $data = $this->getMaintainers($def);
153        $mails = [];
154        foreach ($data as $name => $info) {
155            if (is_array($info)) {
156                $mails[] = $info['mail'];
157            } elseif ($name[0] === '@' && $auth->canDo('getUsers')) {
158                $members = $auth->retrieveUsers(0, 0, ['grps' => ltrim($name, '@')]);
159                foreach ($members as $user) {
160                    $mails[] = $user['mail'];
161                }
162            }
163        }
164
165        return array_values(array_unique($mails));
166    }
167
168    /**
169     * @param string $user
170     * @param string $def
171     * @return bool
172     */
173    public function isMaintainer($user, $def)
174    {
175        /* @var DokuWiki_Auth_Plugin $auth */
176        global $auth;
177        if ($auth === null) return false;
178        if ($user === '') return false;
179        $userData = $auth->getUserData($user);
180
181        $all = explode(',', $def);
182        foreach ($all as $item) {
183            $item = trim($item);
184            if (strpos($item, '@') !== false && in_array(ltrim($item, '@'), $userData['grps'])) {
185                return true;
186            } elseif ($item === $user) {
187                return true;
188            }
189        }
190
191        return false;
192    }
193
194    /**
195     * Inform all maintainers that the page needs checking
196     *
197     * @param string $def defined maintainers
198     * @param string $page that needs checking
199     */
200    public function informMaintainer($def, $page)
201    {
202        $mails = $this->getMaintainerMails($def);
203        foreach ($mails as $mail) {
204            $this->sendMail($mail, $page);
205        }
206    }
207
208    /**
209     * Sends an email
210     *
211     * @param array $mail
212     * @param string $page
213     */
214    protected function sendMail($mail, $page)
215    {
216        $mailer = new Mailer();
217        $mailer->to($mail);
218        $mailer->subject($this->getLang('mail subject'));
219
220        $text = sprintf($this->getLang('mail body'), $page);
221        $link = '<a href="' . wl($page, '', true) . '">' . $page . '</a>';
222        $html = sprintf($this->getLang('mail body'), $link);
223        $mailer->setBody($text, null, null, $html);
224
225        if (!$mailer->send()) {
226            msg($this->getLang('error mail'), -1);
227        }
228    }
229
230    /**
231     * Puts users and groups into a flat array; useful for simple string output
232     * @param array $all
233     * @return array
234     */
235    protected function flattenMaintainers($all)
236    {
237        if (empty($all['users'])) {
238            return $all;
239        }
240
241        $users = array_map(static fn($user) => $user['name'], $all['users']);
242
243        return array_merge($users, $all['groups']);
244    }
245
246    /**
247     * Expands groups into users; useful for email notification
248     *
249     * @param array $all
250     * @return array
251     */
252    protected function expandMaintainers($all)
253    {
254        if (empty($all['groups'])) {
255            return $all;
256        }
257
258        /* @var DokuWiki_Auth_Plugin $auth */
259        global $auth;
260        if ($auth === null) return [];
261
262        $members = [];
263        foreach ($all['groups'] as $group) {
264            $members = array_merge($members, $auth->retrieveUsers(0, 0, ['grps' => ltrim($group, '@')]));
265        }
266
267        // merge eliminates any duplicates since we use string keys
268        return array_merge($all['users'], $members);
269    }
270}
271