xref: /plugin/publish/helper.php (revision b725e83821493aebed9798a4aeef9a4678bb8a9b)
1<?php
2/**
3 * DokuWiki Plugin publish (Helper Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Jarrod Lowe <dokuwiki@rrod.net>
7 * @author  Andreas Gohr <gohr@cosmocode.de>
8 */
9
10// must be run within Dokuwiki
11if (!defined('DOKU_INC')) die();
12
13class helper_plugin_publish extends DokuWiki_Plugin {
14
15    private $sortedApprovedRevisions = null;
16
17    /**
18     * checks if an id is within one of the namespaces in $namespace_list
19     *
20     * @param string $namespace_list
21     * @param string $id
22     *
23     * @return bool
24     */
25    function in_namespace($namespace_list, $id) {
26        // PHP apparantly does not have closures -
27        // so we will parse $valid ourselves. Wasteful.
28        $namespace_list = preg_split('/\s+/', $namespace_list);
29        //if(count($valid) == 0) { return true; }//whole wiki matches
30        if((count($namespace_list)==1) and ($namespace_list[0]=="")) { return true; }//whole wiki matches
31        $id = trim($id, ':');
32        $id = explode(':', $id);
33
34        // Check against all possible namespaces
35        foreach($namespace_list as $namespace) {
36            $namespace = explode(':', $namespace);
37            $current_ns_depth = 0;
38            $total_ns_depth = count($namespace);
39            $matching = true;
40
41            // Check each element, untill all elements of $v satisfied
42            while($current_ns_depth < $total_ns_depth) {
43                if($namespace[$current_ns_depth] != $id[$current_ns_depth]) {
44                    // not a match
45                    $matching = false;
46                    break;
47                }
48                $current_ns_depth += 1;
49            }
50            if($matching) { return true; } // a match
51        }
52        return false;
53    }
54
55    /**
56     * check if given $dir contains a valid namespace or is contained in a valid namespace
57     *
58     * @param $valid_namespaces_list
59     * @param $dir
60     *
61     * @return bool
62     */
63    function is_dir_valid($valid_namespaces_list, $dir) {
64        $valid_namespaces_list = preg_split('/\s+/', $valid_namespaces_list);
65        //if(count($valid) == 0) { return true; }//whole wiki matches
66        if((count($valid_namespaces_list)==1) && ($valid_namespaces_list[0]=="")) { return true; }//whole wiki matches
67        $dir = trim($dir, ':');
68        $dir = explode(':', $dir);
69
70        // Check against all possible namespaces
71        foreach($valid_namespaces_list as $valid_namespace) {
72            $valid_namespace = explode(':', $valid_namespace);
73            $current_depth = 0;
74            $dir_depth = count($dir); //this is what is different from above!
75            $matching = true;
76
77            // Check each element, untill all elements of $v satisfied
78            while($current_depth < $dir_depth) {
79                if (empty($valid_namespace[$current_depth])) {
80                    break;
81                }
82                if($valid_namespace[$current_depth] != $dir[$current_depth]) {
83                    // not a match
84                    $matching = false;
85                    break;
86                }
87                $current_depth += 1;
88            }
89            if($matching) { return true; } // a match
90        }
91        return false;
92    }
93
94    function canApprove() {
95        global $INFO;
96        global $ID;
97
98        if (!$this->in_namespace($this->getConf('apr_namespaces'), $ID)) {
99            return false;
100        }
101
102        return ($INFO['perm'] >= AUTH_DELETE);
103    }
104
105    function getRevision($id = null) {
106        global $REV;
107        if (isset($REV) && !empty($REV)) {
108            return $REV;
109        }
110        $meta = $this->getMeta($id);
111        if (isset($meta['last_change']['date'])) {
112            return $meta['last_change']['date'];
113        }
114        if($meta) {
115            return $meta['date']['modified'];
116        }
117        return null;
118    }
119
120    function getApprovals($id = null) {
121        $meta = $this->getMeta($id);
122        if (!isset($meta['approval'])) {
123            return array();
124        }
125        $approvals = $meta['approval'];
126        if (!is_array($approvals)) {
127            return array();
128        }
129        return $approvals;
130    }
131
132    function getMeta($id = null) {
133        global $ID;
134        global $INFO;
135
136        if ($id === null) $id = $ID;
137
138        if($ID === $id && $INFO['meta']) {
139            $meta = $INFO['meta'];
140        } else {
141            $meta = p_get_metadata($id);
142        }
143
144        $this->checkApprovalFormat($meta, $id);
145
146        return $meta;
147    }
148
149    function checkApprovalFormat($meta, $id) {
150        if (isset($meta['approval_version']) && $meta['approval_version'] >= 2) {
151            return;
152        }
153
154        if (!$this->hasApprovals($meta)) {
155            return;
156        }
157
158        $approvals = $meta['approval'];
159        foreach (array_keys($approvals) as $approvedId) {
160            $keys = array_keys($approvals[$approvedId]);
161
162            if (is_array($approvals[$approvedId][$keys[0]])) {
163                continue; // current format
164            }
165
166            $newEntry = $approvals[$approvedId];
167            if (count($newEntry) !== 3) {
168                //continue; // some messed up format...
169            }
170            $newEntry[] = intval($approvedId); // revision is the time of page edit
171
172            $approvals[$approvedId] = array();
173            $approvals[$approvedId][$newEntry[0]] = $newEntry;
174        }
175        p_set_metadata($id, array('approval' => $approvals), true, true);
176        p_set_metadata($id, array('approval_version' => 2), true, true);
177    }
178
179    function hasApprovals($meta) {
180        return isset($meta['approval']) && !empty($meta['approval']);
181    }
182
183    function getApprovalsOnRevision($revision) {
184        $approvals = $this->getApprovals();
185
186        if (isset($approvals[$revision])) {
187            return $approvals[$revision];
188        }
189        return array();
190    }
191
192    function getSortedApprovedRevisions($id = null) {
193        if ($id === null) {
194            global $ID;
195            $id = $ID;
196        }
197
198        static $sortedApprovedRevisions = array();
199        if (!isset($sortedApprovedRevisions[$id])) {
200            $approvals = $this->getApprovals($id);
201            krsort($approvals);
202            $sortedApprovedRevisions[$id] = $approvals;
203        }
204
205        return $sortedApprovedRevisions[$id];
206    }
207
208    function isRevisionApproved($revision, $id = null) {
209        $approvals = $this->getApprovals($id);
210        if (!isset($approvals[$revision])) {
211            return false;
212        }
213        return (count($approvals[$revision]) >= $this->getConf('number_of_approved'));
214    }
215
216    function isCurrentRevisionApproved($id = null) {
217        return $this->isRevisionApproved($this->getRevision($id), $id);
218    }
219
220    function getLatestApprovedRevision($id = null) {
221        $approvals = $this->getSortedApprovedRevisions($id);
222        foreach ($approvals as $revision => $ignored) {
223            if ($this->isRevisionApproved($revision, $id)) {
224                return $revision;
225            }
226        }
227        return 0;
228    }
229
230    function getLastestRevision() {
231        global $INFO;
232        return $INFO['meta']['date']['modified'];
233    }
234
235    function getApprovalDate() {
236        if (!$this->isCurrentRevisionApproved()) {
237            return -1;
238        }
239
240        $approvals = $this->getApprovalsOnRevision($this->getRevision());
241        uasort($approvals, array(&$this, 'cmpApprovals'));
242        $keys = array_keys($approvals);
243        return $approvals[$keys[$this->getConf('number_of_approved') -1]][3];
244
245    }
246
247    function cmpApprovals($left, $right) {
248        if ($left[3] == $right[3]) {
249            return 0;
250        }
251        return ($left[3] < $right[3]) ? -1 : 1;
252    }
253
254    function getApprovers() {
255        $approvers = $this->getApprovalsOnRevision($this->getRevision());
256        if (count($approvers) === 0) {
257            return;
258        }
259
260        $result = array();
261        foreach ($approvers as $approver) {
262            $result[] = editorinfo($this->getApproverName($approver));
263        }
264        return $result;
265    }
266
267    function getApproverName($approver) {
268        if ($approver[1]) {
269            return $approver[1];
270        }
271        if ($approver[2]) {
272            return $approver[2];
273        }
274        return $approver[0];
275    }
276
277    function getPreviousApprovedRevision() {
278        $currentRevision = $this->getRevision();
279        $approvals = $this->getSortedApprovedRevisions();
280        foreach ($approvals as $revision => $ignored) {
281            if ($revision >= $currentRevision) {
282                continue;
283            }
284            if ($this->isRevisionApproved($revision)) {
285                return $revision;
286            }
287        }
288        return 0;
289    }
290
291    function isHidden($id = null) {
292        if (!$this->getConf('hide drafts')) {
293            return false;
294        }
295
296        // needs to check if the actual namespace belongs to the apr_namespaces
297        if ($id == null) {
298            global $ID;
299            $id = $ID;
300        }
301        if (!$this->isActive($id)) {
302            return false;
303        }
304
305        if ($this->getLatestApprovedRevision($id)) {
306            return false;
307        }
308        return true;
309    }
310
311    function isHiddenForUser($id = null) {
312        if (!$this->isHidden($id)) {
313            return false;
314        }
315
316        if ($id == null) {
317            global $ID;
318            $id = $ID;
319        }
320
321        $allowedGroups = array_filter(explode(' ', trim($this->getConf('author groups'))));
322        if (empty($allowedGroups)) {
323            return auth_quickaclcheck($id) < AUTH_EDIT;
324        }
325
326        if (!$_SERVER['REMOTE_USER']) {
327            return true;
328        }
329
330        global $USERINFO;
331        foreach ($allowedGroups as $allowedGroup) {
332            $allowedGroup = trim($allowedGroup);
333            if (in_array($allowedGroup, $USERINFO['grps'])) {
334                return false;
335            }
336        }
337        return true;
338    }
339
340    function isActive($id = null) {
341        if ($id == null) {
342            global $ID;
343            $id = $ID;
344        }
345        if (!$this->in_namespace($this->getConf('apr_namespaces'), $id)) {
346            return false;
347        }
348
349        $no_apr_namespaces = $this->getConf('no_apr_namespaces');
350        if (!empty($no_apr_namespaces)) {
351            if ($this->in_namespace($no_apr_namespaces, $id)) {
352                return false;
353            }
354        }
355        return true;
356    }
357
358    /**
359     * Create absolute diff-link between the two given revisions
360     *
361     * @param string $id
362     * @param int $rev1
363     * @param int $rev2
364     * @return string Diff-Link or empty string if $rev1 == $rev2
365     */
366    public function getDifflink($id, $rev1, $rev2) {
367        if($rev1 == $rev2) {
368            return '';
369        }
370        $params = 'do=diff,rev2[0]=' . $rev1 . ',rev2[1]=' . $rev2 . ',difftype=sidebyside';
371        $difflink = wl($id, $params,true,'&');
372        return $difflink;
373    }
374
375    function getPagesFromNamespace($namespace) {
376        global $conf;
377        $dir = $conf['datadir'] . '/' . str_replace(':', '/', $namespace);
378        $pages = array();
379        search($pages, $dir, array($this,'_search_helper'), array($namespace, $this->getConf('apr_namespaces'),
380                                                                  $this->getConf('no_apr_namespaces')));
381        return $pages;
382    }
383
384    /**
385     * search callback function
386     *
387     * filter out pages which can't be approved by the current user
388     * then check if they need approving
389     */
390    function _search_helper(&$data, $base, $file, $type, $lvl, $opts) {
391        $ns = $opts[0];
392        $valid_ns = $opts[1];
393        $invalid_ns = $opts[2];
394
395        if ($type == 'd') {
396            return $this->is_dir_valid($valid_ns, $ns . ':' . str_replace('/', ':', $file));
397        }
398
399        if (!preg_match('#\.txt$#', $file)) {
400            return false;
401        }
402
403        $id = pathID($ns . $file);
404        if (!empty($valid_ns) && !$this->in_namespace($valid_ns, $id)) {
405            return false;
406        }
407
408        if (!empty($invalid_ns) && $this->in_namespace($invalid_ns, $id)) {
409            return false;
410        }
411
412        if (auth_quickaclcheck($id) < AUTH_DELETE) {
413            return false;
414        }
415
416        $meta = $this->getMeta($id);
417        if ($this->isCurrentRevisionApproved($id)) {
418
419            // Already approved
420            return false;
421        }
422
423        $data[] = array($id, $meta['approval'], $meta['last_change']['date']);
424        return false;
425    }
426
427    public function removeSubnamespacePages ($pages, $namespace) {
428        $cleanpages = array();
429        foreach ($pages as $page) {
430            if (getNS($page[0]) == $namespace) {
431                $cleanpages[] = $page;
432            }
433        }
434        return $cleanpages;
435    }
436
437}
438