1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Gerrit Uitslag <klapinklapin@gmail.com>
5 */
6
7/**
8 * Show book bar and pagetool button at a wiki page
9 */
10class action_plugin_bookcreator_handleselection extends DokuWiki_Action_Plugin {
11
12    /** @var helper_plugin_bookcreator */
13    protected $hlp;
14
15    /**
16     * Constructor
17     */
18    public function __construct() {
19        $this->hlp = plugin_load('helper', 'bookcreator');
20    }
21
22    /**
23     * Registers a callback function for a given event
24     *
25     * @param Doku_Event_Handler $controller
26     */
27    public function register(Doku_Event_Handler $controller) {
28        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_handle_ajax');
29    }
30
31
32    /**
33     * Handle ajax requests for the book manager
34     *
35     * @param Doku_Event $event
36     */
37    public function _handle_ajax(Doku_Event $event) {
38        if ($event->data !== 'plugin_bookcreator_call') {
39            return;
40        }
41        $event->stopPropagation();
42        $event->preventDefault();
43
44        global $INPUT;
45
46        try {
47            if(!checkSecurityToken()) {
48                throw new Exception('Security Token did not match. Possible CSRF attack.');
49            }
50
51            $action = $INPUT->post->str('action', '', true);
52            switch($action) {
53                case 'retrievePageinfo':
54                    $response = $this->retrievePageInfo($this->getPOSTedSelection());
55                    break;
56                case 'saveSelection':
57                    $title = $INPUT->post->str('savedselectionname');
58                    $response = $this->saveSelection($title, $this->getPOSTedSelection());
59                    break;
60                case 'loadSavedSelection':
61                    $page = $INPUT->post->str('savedselectionname');
62                    $response = $this->loadSavedSelection($page);
63                    break;
64                case 'deleteSavedSelection':
65                    $page = $INPUT->post->str('savedselectionname');
66                    $response = $this->deleteSavedSelection($page);
67                    break;
68                case 'searchPages':
69                    $namespace = $INPUT->post->str('ns');
70                    $recursive = $INPUT->post->str('r');
71                    $response = $this->searchPages($namespace, $recursive);
72                    break;
73                default:
74                    $response['error'] = 'unknown action ';
75            }
76        } catch(Exception $e){
77            $response['error'] = $e->getMessage();
78        }
79
80        header('Content-Type: application/json');
81        echo json_encode($response);
82    }
83
84    /**
85     * Get POSTed selection
86     *
87     * @return array
88     */
89    protected function getPOSTedSelection() {
90        global $INPUT;
91
92        $selection = json_decode($INPUT->post->str('selection', '', true), true);
93        if(!is_array($selection)) {
94            $selection = array();
95        }
96        return $selection;
97    }
98
99    /**
100     * Return the titles and urls to given pageids
101     *
102     * @param array $selection
103     * @return array with slection
104     */
105    private function retrievePageInfo($selection) {
106        $response['selection'] = array();
107        foreach($selection as $pageid) {
108            $page = cleanID($pageid);
109            if(auth_quickaclcheck($pageid) < AUTH_READ) {
110                continue;
111            }
112            $response['selection'][$page] = array(wl($page, false, true, "&"), $this->getTitle($page));
113        }
114        return $response;
115    }
116
117    /**
118     * Construct a link title
119     *
120     * @see Doku_Renderer_xhtml::_getLinkTitle
121     *
122     * @param string $pageid
123     * @return string
124     */
125    protected function getTitle($pageid) {
126        global $conf;
127
128        if(useHeading('navigation') && $pageid) {
129            $heading = p_get_first_heading($pageid);
130            if($heading) {
131                return $this->_xmlEntities($heading);
132            }
133        }
134
135        // Removes any Namespace from the given name but keeps casing and special chars
136        if($conf['useslash']) {
137            $pageid = strtr($pageid, ';/', ';:');
138        } else {
139            $pageid = strtr($pageid, ';', ':');
140        }
141
142        $name = noNSorNS($pageid);
143        return $this->_xmlEntities($name);
144    }
145
146    /**
147     * Escape string for output
148     *
149     * @see Doku_Renderer_xhtml::_xmlEntities
150     *
151     * @param string $string
152     * @return string
153     */
154    protected function _xmlEntities($string) {
155        return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
156    }
157
158    /**
159     * Handle request for saving the selection list
160     *
161     * Selection is saved as bullet list on a wikipage
162     *
163     * @param string $savedSelectionName Title for saved selection
164     * @param array $selection
165     * @return array with message and item for the list of saved selections
166     * @throws Exception
167     */
168    private function saveSelection($savedSelectionName, $selection) {
169        if(auth_quickaclcheck($this->getConf('save_namespace').':*') < AUTH_CREATE) {
170            throw new Exception('no access to namespace: ' . $this->getConf('save_namespace'));
171        }
172        if(empty($selection)) {
173            throw new Exception($this->getLang('empty'));
174        }
175        if(empty($savedSelectionName)){
176            throw new Exception($this->getLang('needtitle'));
177        }
178
179        //generate content
180        $content = "====== ".$savedSelectionName." ======".DOKU_LF;
181
182        foreach($selection as $pageid) {
183            $content .= "  * [[:$pageid]]".DOKU_LF;
184        }
185
186        $save_pageid = cleanID($this->getConf('save_namespace') . ":" . $savedSelectionName);
187        saveWikiText($save_pageid, $content, $this->getLang('selectionstored'));
188
189        $response['success'] = sprintf($this->getLang('saved'), $save_pageid);
190
191        $item = array(
192            'id' => $save_pageid,
193            'mtime' => filemtime(wikiFN($save_pageid))
194        );
195        $response['item'] = $this->hlp->createListitem($item, true);
196        return $response;
197    }
198
199    /**
200     * Handle request for deleting of selection list
201     *
202     * @param string $page with saved selection
203     * @return array with message and deleted page name
204     * @throws Exception
205     */
206    private function deleteSavedSelection($page) {
207        if(auth_quickaclcheck($this->getConf('save_namespace').':*') < AUTH_CREATE) {
208            throw new Exception('no access to namespace: ' . $this->getConf('save_namespace'));
209        }
210
211        $pageid = cleanID($this->getConf('save_namespace') . ":" . $page);
212
213        if(!file_exists(wikiFN($pageid))){
214            throw new Exception(sprintf($this->getLang('selectiondontexist'), $pageid));
215        }
216
217        saveWikiText($pageid, '', $this->getLang('selectiondeleted'));
218        $response['success'] = sprintf($this->getLang('deleted'), $pageid);
219        $response['deletedpage'] = noNS($pageid);
220        return $response;
221    }
222
223    /**
224     * Load the specified saved selection
225     *
226     * @param string $page with saved selection
227     * @return array with title and a list of pages
228     * @throws Exception
229     */
230    public function loadSavedSelection($page) {
231        $pageid = cleanID($this->getConf('save_namespace') . ":" . $page);
232
233        if($page === '') {
234            throw new Exception(sprintf($this->getLang('selectiondontexist'), $pageid .':'));
235        }
236
237        if(auth_quickaclcheck($pageid) < AUTH_READ) {
238            throw new Exception(sprintf($this->getLang('selectionforbidden'), $pageid));
239        }
240
241        if(!file_exists(wikiFN($pageid))) {
242            throw new Exception(sprintf($this->getLang('selectiondontexist'), $pageid));
243        }
244
245        list($title, $list) = $this->getSavedSelection($pageid);
246        $response['title'] = $title;
247        $response['selection'] = $list;
248        return $response;
249    }
250
251    /**
252     * Returns title and list of pages from a Saved Selection
253     *
254     * @param string $pageid pagename containing the selection
255     * @return array(title, array(list))
256     */
257    protected function getSavedSelection($pageid) {
258        $title = '';
259        $list  = array();
260
261        $pagecontent = rawWiki($pageid);
262        $lines       = explode("\n", $pagecontent);
263
264        foreach($lines as $i => $line) {
265            //skip nonsense
266            if(trim($line) == '') continue;
267            if((($i > 0) && substr($line, 0, 7) != "  * [[:")) continue;
268
269            //read title and list
270            if($i === 0) {
271                $title = trim(str_replace("======", '', $line));
272            } else {
273                $line        = str_replace("  * [[:", '', $line);
274                $line        = str_replace("]]", '', $line);
275                list($id, /* $title */) = explode('|', $line, 2);
276                $id = cleanID($id);
277                if($id == '') {
278                    continue;
279                }
280                $list[] = $id;
281            }
282        }
283
284        return array($title, $list);
285    }
286
287    /**
288     * Returns an array of pages in the given namespace.
289     *
290     * @param string $ns The namespace to search in
291     * @param boolean $recursive Search in sub-namespaces too?
292     * @return array with a list pages
293     */
294    protected function searchPages($ns, $recursive) {
295        global $conf;
296
297        // Use inc/search.php
298        if ($recursive == 'true') {
299            $opts = array();
300        } else {
301            $count = substr_count($ns, ':');
302            $opts = array('depth' => 1+$count);
303        }
304        $items = array();
305        $ns = trim($ns, ':');
306        $ns = utf8_encodeFN(str_replace(':', '/', $ns));
307        search($items, $conf['datadir'], 'search_allpages', $opts, $ns);
308
309        // Generate result.
310        $pages = array();
311        foreach ($items as $item) {
312            $pages [] = $item['id'];
313        }
314
315        $response['pages'] = $pages;
316        return $response;
317    }
318}
319