1<?php
2
3// must be run within Dokuwiki
4if (!defined('DOKU_INC')) die();
5class helper_plugin_approve extends DokuWiki_Plugin {
6
7    /**
8     * @param helper_plugin_sqlite $sqlite
9     * @return string
10     */
11    public function no_apr_namespace(helper_plugin_sqlite $sqlite) {
12        //check for config update
13        $key = 'no_apr_namespaces';
14        $res = $sqlite->query('SELECT value FROM config WHERE key=?', $key);
15        $no_apr_namespaces_db = $sqlite->res2single($res);
16        $no_apr_namespaces_conf = $this->getConf($key);
17        //update internal config
18        if ($no_apr_namespaces_db != $no_apr_namespaces_conf) {
19            $sqlite->query('UPDATE config SET value=? WHERE key=?', $no_apr_namespaces_conf, $key);
20
21            $res = $sqlite->query('SELECT page, hidden FROM page');
22            $pages = $sqlite->res2arr($res);
23            foreach ($pages as $page) {
24                $id = $page['page'];
25                $hidden = $page['hidden'];
26                $in_hidden_namespace = $this->in_hidden_namespace($sqlite, $id, $no_apr_namespaces_conf);
27                $new_hidden = $in_hidden_namespace ? '1' : '0';
28
29                if ($hidden != $new_hidden) {
30                    $sqlite->query('UPDATE page SET hidden=? WHERE page=?', $new_hidden, $id);
31                }
32            }
33        }
34
35        return $no_apr_namespaces_conf;
36    }
37
38    /**
39     * @param helper_plugin_sqlite $sqlite
40     * @param $id
41     * @param null $approver
42     * @return bool
43     */
44    public function use_approve_here(helper_plugin_sqlite $sqlite, $id, &$approver=null) {
45
46        //check if we should update no_apr_namespace
47        $this->no_apr_namespace($sqlite);
48
49        $res = $sqlite->query('SELECT page, approver FROM page WHERE page=? AND hidden=0', $id);
50        $row = $sqlite->res2row($res);
51        if ($row) {
52            $approver = $row['approver'];
53            return true;
54        }
55        return false;
56    }
57
58    /**
59     * @param helper_plugin_sqlite $sqlite
60     * @param $id
61     * @return bool|string
62     */
63    public function find_last_approved(helper_plugin_sqlite $sqlite, $id) {
64        $res = $sqlite->query('SELECT rev FROM revision
65                                WHERE page=? AND approved IS NOT NULL
66                                ORDER BY rev DESC LIMIT 1', $id);
67        return $sqlite->res2single($res);
68    }
69
70    /**
71     * @param helper_plugin_sqlite $sqlite
72     * @param null $no_apr_namespaces
73     * @return array|array[]|false|string[]
74     */
75    public function get_hidden_namespaces_list(helper_plugin_sqlite $sqlite, $no_apr_namespaces=null) {
76        if (!$no_apr_namespaces) {
77            $no_apr_namespaces = $this->no_apr_namespace($sqlite);
78        }
79
80        $no_apr_namespaces_list = preg_split('/\s+/', $no_apr_namespaces,-1,
81            PREG_SPLIT_NO_EMPTY);
82        $no_apr_namespaces_list = array_map(function ($namespace) {
83            return ltrim($namespace, ':');
84        }, $no_apr_namespaces_list);
85
86        return $no_apr_namespaces_list;
87    }
88
89    /**
90     * @param helper_plugin_sqlite $sqlite
91     * @param $id
92     * @param null $no_apr_namespaces
93     * @return bool|string
94     */
95    public function in_hidden_namespace(helper_plugin_sqlite $sqlite, $id, $no_apr_namespaces=null) {
96        $no_apr_namespaces_list = $this->get_hidden_namespaces_list($sqlite, $no_apr_namespaces);
97        $id = ltrim($id, ':');
98        foreach ($no_apr_namespaces_list as $namespace) {
99            if (substr($id, 0, strlen($namespace)) == $namespace) {
100                return true;
101            }
102        }
103        return false;
104    }
105
106    /**
107     * @param helper_plugin_sqlite $sqlite
108     * @return array
109     */
110    public function weighted_assignments(helper_plugin_sqlite $sqlite) {
111        $res = $sqlite->query('SELECT id,namespace,approver FROM maintainer');
112        $assignments = $sqlite->res2arr($res);
113
114        $weighted_assignments = [];
115        foreach ($assignments as $assignment) {
116            $ns = $assignment['namespace'];
117            //more general namespaces are overridden by more specific ones.
118            if (substr($ns, -1) == '*') {
119                $weight = substr_count($ns, ':');
120            } else {
121                $weight = PHP_INT_MAX;
122            }
123
124            $assignment['weight'] = $weight;
125            $weighted_assignments[] = $assignment;
126        }
127        array_multisort(array_column($weighted_assignments, 'weight'), $weighted_assignments);
128
129        return $weighted_assignments;
130    }
131
132    /**
133     * @param helper_plugin_sqlite $sqlite
134     * @param $id
135     * @param null $pageApprover
136     * @param null $weighted_assignments
137     * @return bool
138     */
139    public function isPageAssigned(helper_plugin_sqlite $sqlite, $id, &$pageApprover=null, $weighted_assignments=null) {
140        if (!$weighted_assignments) {
141            $weighted_assignments = $this->weighted_assignments($sqlite);
142        }
143        foreach ($weighted_assignments as $assignment) {
144            $ns = ltrim($assignment['namespace'], ':');
145            $approver = $assignment['approver'];
146            if (substr($ns, -2) == '**') {
147                //remove '**'
148                $ns = substr($ns, 0, -2);
149                if (substr($id, 0, strlen($ns)) == $ns) {
150                    $newAssignment = true;
151                    $pageApprover = $approver;
152                }
153            } elseif (substr($ns, -1) == '*') {
154                //remove '*'
155                $ns = substr($ns, 0, -1);
156                $noNS = substr($id, strlen($ns));
157                if (strpos($noNS, ':') === FALSE &&
158                    substr($id, 0, strlen($ns)) == $ns) {
159                    $newAssignment = true;
160                    $pageApprover = $approver;
161                }
162            } elseif($id == $ns) {
163                $newAssignment = true;
164                $pageApprover = $approver;
165            }
166        }
167        return $newAssignment;
168    }
169
170    /**
171     * @param helper_plugin_sqlite $sqlite
172     */
173    public function updatePagesAssignments(helper_plugin_sqlite $sqlite)
174    {
175        //clean current settings
176        $sqlite->query('DELETE FROM page');
177
178        $wikiPages = $this->getPages();
179        $no_apr_namespace = $this->no_apr_namespace($sqlite);
180        $weighted_assignments = $this->weighted_assignments($sqlite);
181        foreach ($wikiPages as $id) {
182            if ($this->isPageAssigned($sqlite, $id, $approver, $weighted_assignments)) {
183                $data = [
184                    'page' => $id,
185                    'hidden' => $this->in_hidden_namespace($sqlite, $id, $no_apr_namespace) ? '1' : '0'
186                ];
187                if (!blank($approver)) {
188                    $data['approver'] = $approver;
189                }
190                $sqlite->storeEntry('page', $data);
191            }
192        }
193    }
194
195    /**
196     * @param string $approver
197     * @return bool
198     */
199    public function isGroup($approver) {
200	if (!$approver) return false;
201        if (strncmp($approver, "@", 1) === 0) return true;
202        return false;
203    }
204
205    /**
206     * @param $userinfo
207     * @param string $group
208     * @return bool
209     */
210    public function isInGroup($userinfo, $group) {
211        $groupname = substr($group, 1);
212        if (in_array($groupname, $userinfo['grps'])) return true;
213        return false;
214    }
215
216    /**
217     * @param $id
218     * @param string $pageApprover
219     * @return bool
220     */
221    public function client_can_approve($id, $pageApprover) {
222        global $INFO;
223        //user not log in
224        if (!isset($INFO['userinfo'])) return false;
225
226        if ($pageApprover == $INFO['client']) {
227            return true;
228        } elseif ($this->isGroup($pageApprover) && $this->isInGroup($INFO['userinfo'], $pageApprover)) {
229            return true;
230        //no approver provided, check if approve plugin apply here
231        } elseif (auth_quickaclcheck($id) >= AUTH_DELETE &&
232            (!$pageApprover || !$this->getConf('strict_approver'))) {
233            return true;
234        }
235
236        return false;
237    }
238
239    /**
240     * @param $id
241     * @return bool
242     */
243    public function client_can_mark_ready_for_approval($id) {
244        global $INFO;
245
246        $ready_for_approval_acl = preg_split('/\s+/', $this->getConf('ready_for_approval_acl'), -1, PREG_SPLIT_NO_EMPTY);
247        if (count($ready_for_approval_acl) == 0) return auth_quickaclcheck($id) >= AUTH_EDIT; // empty
248        foreach ($ready_for_approval_acl as $user_or_group) {
249            if ($user_or_group[0] == '@' && $this->isInGroup($INFO['userinfo'], $user_or_group)) {
250                return true;
251            } elseif ($user_or_group == $INFO['client']) {
252                return true;
253            }
254        }
255        return false;
256    }
257
258    /**
259     * @param $id
260     * @return bool
261     */
262    public function client_can_see_drafts($id, $pageApprover) {
263        // in view mode no one can see drafts
264        if ($this->getConf('viewmode') && get_doku_pref('approve_viewmode', false)) return false;
265
266        if (!$this->getConf('hide_drafts_for_viewers')) return true;
267
268        if (auth_quickaclcheck($id) >= AUTH_EDIT) return true;
269        if ($this->client_can_approve($id, $pageApprover)) return true;
270
271        return false;
272    }
273
274    /**
275     * Get the array of all pages ids in wiki
276     *
277     * @return array
278     */
279    public function getPages() {
280        global $conf;
281
282        $datadir = realpath($conf['datadir']);  // path without ending "/"
283        $directory = new RecursiveDirectoryIterator($datadir, FilesystemIterator::SKIP_DOTS);
284        $iterator = new RecursiveIteratorIterator($directory);
285
286        $pages = [];
287        /** @var SplFileInfo $fileinfo */
288        foreach ($iterator as $fileinfo) {
289            if (!$fileinfo->isFile()) continue;
290
291            $path = $fileinfo->getRealPath(); // it should return "/" both on windows and linux
292            //remove dir part
293            $path = substr($path, strlen($datadir));
294            //make file a dokuwiki path
295            $id = $this->pathID($path);
296            $pages[] = $id;
297        }
298
299        return $pages;
300    }
301
302    /**
303     * translates a document path to an ID
304     *
305     * fixes dokuwiki pathID - support for Windows enviroment
306     *
307     * @param string $path
308     * @param bool $keeptxt
309     *
310     * @return mixed|string
311     */
312    public function pathID($path,$keeptxt=false){
313        $id = utf8_decodeFN($path);
314        $id = str_replace(DIRECTORY_SEPARATOR,':',$id);
315        if(!$keeptxt) $id = preg_replace('#\.txt$#','',$id);
316        $id = trim($id, ':');
317        return $id;
318    }
319}
320