1<?php
2
3namespace dokuwiki\plugin\struct\test\types;
4
5use dokuwiki\plugin\struct\meta\Value;
6use dokuwiki\plugin\struct\test\mock\Search;
7use dokuwiki\plugin\struct\test\StructTest;
8use dokuwiki\plugin\struct\types\Page;
9
10/**
11 * Testing the Page Type
12 *
13 * @group plugin_struct
14 * @group plugins
15 */
16class PageTest extends StructTest
17{
18
19    public function setUp(): void
20    {
21        parent::setUp();
22
23        saveWikiText('syntax', 'dummy', 'test');
24        saveWikiText('foo:syntax:test_special.characters', 'dummy text', 'dummy summary');
25
26        // make sure the search index is initialized
27        idx_addPage('wiki:syntax');
28        idx_addPage('syntax');
29        idx_addPage('wiki:welcome');
30        idx_addPage('wiki:dokuwiki');
31        idx_addPage('foo:syntax:test_special.characters');
32    }
33
34    public function test_sort()
35    {
36
37        saveWikiText('title1', 'test', 'test');
38        $pageMeta = new \dokuwiki\plugin\struct\meta\PageMeta('title1');
39        $pageMeta->setTitle('This is a title');
40
41        saveWikiText('title2', 'test', 'test');
42        $pageMeta = new \dokuwiki\plugin\struct\meta\PageMeta('title2');
43        $pageMeta->setTitle('This is a title');
44
45        saveWikiText('title3', 'test', 'test');
46        $pageMeta = new \dokuwiki\plugin\struct\meta\PageMeta('title3');
47        $pageMeta->setTitle('Another Title');
48
49
50        $this->loadSchemaJSON('pageschema');
51        $this->saveData('test1', 'pageschema', ['singletitle' => 'title1']);
52        $this->saveData('test2', 'pageschema', ['singletitle' => 'title2']);
53        $this->saveData('test3', 'pageschema', ['singletitle' => 'title3']);
54
55        $search = new Search();
56        $search->addSchema('pageschema');
57        $search->addColumn('%pageid%');
58        $search->addColumn('singletitle');
59        $search->addSort('singletitle', true);
60        /** @var Value[][] $result */
61        $result = $search->getRows();
62
63        $this->assertEquals(3, count($result));
64        $this->assertEquals('test3', $result[0][0]->getValue());
65        $this->assertEquals('test1', $result[1][0]->getValue());
66        $this->assertEquals('test2', $result[2][0]->getValue());
67    }
68
69
70    public function test_search()
71    {
72        // prepare some data
73        $this->loadSchemaJSON('pageschema');
74        $this->saveData(
75            'syntax',
76            'pageschema',
77            [
78                'singlepage' => 'wiki:dokuwiki',
79                'multipage' => ['wiki:dokuwiki', 'wiki:syntax', 'wiki:welcome'],
80                'singletitle' => 'wiki:dokuwiki',
81                'multititle' => ['wiki:dokuwiki', 'wiki:syntax', 'wiki:welcome'],
82            ]
83        );
84
85        // make sure titles for some pages are known (not for wiki:welcome)
86        $pageMeta = new \dokuwiki\plugin\struct\meta\PageMeta('wiki:dokuwiki');
87        $pageMeta->setTitle('DokuWiki Overview');
88        $pageMeta = new \dokuwiki\plugin\struct\meta\PageMeta('wiki:syntax');
89        $pageMeta->setTitle('DokuWiki Foobar Syntax');
90        $pageMeta->savePageData();
91
92        // search
93        $search = new Search();
94        $search->addSchema('pageschema');
95        $search->addColumn('singlepage');
96        $search->addColumn('multipage');
97        $search->addColumn('singletitle');
98        $search->addColumn('multititle');
99
100        /** @var Value[][] $result */
101        $result = $search->getRows();
102
103        // no titles:
104        $this->assertEquals('wiki:dokuwiki', $result[0][0]->getValue());
105        $this->assertEquals(['wiki:dokuwiki', 'wiki:syntax', 'wiki:welcome'], $result[0][1]->getValue());
106        // titles as JSON:
107        $this->assertEquals('["wiki:dokuwiki","DokuWiki Overview"]', $result[0][2]->getValue());
108        $this->assertEquals(
109            [
110                '["wiki:dokuwiki","DokuWiki Overview"]',
111                '["wiki:syntax","DokuWiki Foobar Syntax"]',
112                '["wiki:welcome",null]' // no title for this
113            ],
114            $result[0][3]->getValue()
115        );
116
117        // if there is no title in the database display the pageid
118        $this->assertEquals(
119            [
120                'DokuWiki Overview',
121                'DokuWiki Foobar Syntax',
122                'wiki:welcome'
123            ],
124            $result[0][3]->getDisplayValue()
125        );
126
127        // search single with title
128        $single = clone $search;
129        $single->addFilter('singletitle', 'Overview', '*~', 'AND');
130        $result = $single->getRows();
131        $this->assertTrue(is_array($result));
132        $this->assertEquals(1, count($result));
133
134        // search multi with title
135        $multi = clone $search;
136        $multi->addFilter('multititle', 'Foobar', '*~', 'AND');
137        $result = $multi->getRows();
138        $this->assertTrue(is_array($result));
139        $this->assertEquals(1, count($result));
140
141        // search single with page
142        $single = clone $search;
143        $single->addFilter('singletitle', 'wiki:dokuwiki', '*~', 'AND');
144        $result = $single->getRows();
145        $this->assertTrue(is_array($result));
146        $this->assertEquals(1, count($result));
147
148        // search multi with page
149        $multi = clone $search;
150        $multi->addFilter('multititle', 'welcome', '*~', 'AND');
151        $result = $multi->getRows();
152        $this->assertTrue(is_array($result));
153        $this->assertEquals(1, count($result));
154    }
155
156
157    /**
158     * This provides the testdata for @see Type_Page_struct_test::test_validate
159     */
160    public static function validate_testdata()
161    {
162        return [
163            [
164                'namespace:page',
165                'namespace:page',
166                'do not change clean valid page'
167            ],
168            [
169                'namespace:page#headline',
170                'namespace:page#headline',
171                'keep fragments'
172            ],
173            [
174                'namespace:page#headline#second',
175                'namespace:page#headline_second',
176                'keep fragments, but only the first one'
177            ],
178            [
179                'namespace:page?do=something',
180                'namespace:page_do_something',
181                'clean query strings'
182            ]
183        ];
184    }
185
186    /**
187     * @param string $rawvalue
188     * @param string $validatedValue
189     * @param string $msg
190     *
191     * @dataProvider validate_testdata
192     */
193    public function test_validate($rawvalue, $validatedValue, $msg)
194    {
195        $page = new Page();
196        $this->assertEquals($validatedValue, $page->validate($rawvalue), $msg);
197    }
198
199    public function test_ajax_default()
200    {
201        global $INPUT;
202
203        $page = new Page(
204            [
205                'autocomplete' => [
206                    'mininput' => 2,
207                    'maxresult' => 5,
208                    'filter' => '',
209                    'postfix' => '',
210                ],
211            ]
212        );
213
214        $INPUT->set('search', 'syntax');
215        $this->assertEquals(
216            [
217                ['label' => 'syntax', 'value' => 'syntax'],
218                ['label' => 'syntax (wiki)', 'value' => 'wiki:syntax'],
219                ['label' => 'test_special.characters (foo:syntax)', 'value' => 'foo:syntax:test_special.characters'],
220            ], $page->handleAjax()
221        );
222
223        $INPUT->set('search', 'ynt');
224        $this->assertEquals(
225            [
226                ['label' => 'syntax', 'value' => 'syntax'],
227                ['label' => 'syntax (wiki)', 'value' => 'wiki:syntax'],
228                ['label' => 'test_special.characters (foo:syntax)', 'value' => 'foo:syntax:test_special.characters'],
229            ], $page->handleAjax()
230        );
231
232        $INPUT->set('search', 's'); // under mininput
233        $this->assertEquals([], $page->handleAjax());
234
235        $INPUT->set('search', 'test_special.char'); // special characters in id
236        $this->assertEquals([
237            ['label' => 'test_special.characters (foo:syntax)', 'value' => 'foo:syntax:test_special.characters']
238        ], $page->handleAjax());
239    }
240
241    /**
242     * Test deprecated option namespace
243     * @return void
244     */
245    public function test_ajax_namespace()
246    {
247        global $INPUT;
248
249        $page = new Page(
250            [
251                'autocomplete' => [
252                    'mininput' => 2,
253                    'maxresult' => 5,
254                    'namespace' => 'wiki',
255                    'postfix' => '',
256                ],
257            ]
258        );
259
260        $INPUT->set('search', 'ynt');
261        $this->assertEquals([['label' => 'syntax (wiki)', 'value' => 'wiki:syntax']], $page->handleAjax());
262    }
263
264    public function test_ajax_filter_multiple()
265    {
266        global $INPUT;
267
268        $page = new Page(
269            [
270                'autocomplete' => [
271                    'mininput' => 2,
272                    'maxresult' => 5,
273                    'filter' => '(wiki|foo)',
274                    'postfix' => '',
275                ],
276            ]
277        );
278
279        $INPUT->set('search', 'ynt');
280        $this->assertEquals([
281            ['label' => 'syntax (wiki)', 'value' => 'wiki:syntax'],
282            ['label' => 'test_special.characters (foo:syntax)', 'value' => 'foo:syntax:test_special.characters']
283        ], $page->handleAjax());
284    }
285
286    /**
287     * Test deprecated option postfix
288     * @return void
289     */
290    public function test_ajax_postfix()
291    {
292        global $INPUT;
293
294        $page = new Page(
295            [
296                'autocomplete' => [
297                    'mininput' => 2,
298                    'maxresult' => 5,
299                    'namespace' => '',
300                    'postfix' => 'iki',
301                ],
302            ]
303        );
304
305        $INPUT->set('search', 'oku');
306        $this->assertEquals([['label' => 'dokuwiki (wiki)', 'value' => 'wiki:dokuwiki']], $page->handleAjax());
307
308        $page = new Page(
309            [
310                'autocomplete' => [
311                    'mininput' => 2,
312                    'maxresult' => 5,
313                    'namespace' => 'wiki',
314                    'postfix' => 'iki',
315                ],
316            ]
317        );
318
319        $INPUT->set('search', 'oku');
320        $this->assertEquals([['label' => 'dokuwiki (wiki)', 'value' => 'wiki:dokuwiki']], $page->handleAjax());
321    }
322
323    /**
324     * Test simple filter matching in autocompletion
325     *
326     * @return void
327     */
328    public function test_filter_matching_simple()
329    {
330        $page = new Page();
331
332        $this->assertTrue($page->filterMatch('foo:start', 'foo'));
333        $this->assertTrue($page->filterMatch('start#foo', 'foo'));
334        $this->assertFalse($page->filterMatch('ns:foo', ':foo'));
335        $this->assertTrue($page->filterMatch('foo-bar:start', 'foo-bar'));
336        $this->assertTrue($page->filterMatch('foo-bar:start-with_special.chars', 'foo-bar'));
337        $this->assertTrue($page->filterMatch('foo.bar:start', 'foo.bar'));
338        $this->assertTrue($page->filterMatch('ns:foo.bar', 'foo.bar'));
339        $this->assertTrue($page->filterMatch('ns:foo.bar:start', 'foo.bar'));
340        $this->assertFalse($page->filterMatch('ns:foo_bar:start', ':foo_bar'));
341        $this->assertTrue($page->filterMatch('8bar:start', '8bar'));
342        $this->assertTrue($page->filterMatch('ns:8bar:start', '8bar'));
343        $this->assertTrue($page->filterMatch('ns:98bar:start', '8bar'));
344    }
345
346    /**
347     * Test pattern matching in autocompletion
348     *
349     * @return void
350     */
351    public function test_filter_matching_regex()
352    {
353        $page = new Page();
354
355        $filter = '(foo:|^:foo:|(?::|^)bar:|foo:bar|foo-bar:|^:foo_bar:|foo\.bar:|(?::|^)8bar:)';
356
357        $this->assertTrue($page->filterMatch('foo:start', $filter));
358        $this->assertFalse($page->filterMatch('start#foo', $filter));
359        $this->assertFalse($page->filterMatch('ns:foo', $filter));
360        $this->assertTrue($page->filterMatch('bar:foo', $filter));
361        $this->assertTrue($page->filterMatch('ns:foo:start', $filter));
362        $this->assertTrue($page->filterMatch('ns:foo:start#headline', $filter));
363        $this->assertTrue($page->filterMatch('foo-bar:start', $filter));
364        $this->assertTrue($page->filterMatch('foo-bar:start-with_special.chars', $filter));
365        $this->assertTrue($page->filterMatch('foo.bar:start', $filter));
366        $this->assertFalse($page->filterMatch('ns:foo.bar', $filter));
367        $this->assertTrue($page->filterMatch('ns:foo.bar:start', $filter));
368        $this->assertFalse($page->filterMatch('ns:foo_bar:start', $filter));
369        $this->assertTrue($page->filterMatch('8bar:start', $filter));
370        $this->assertTrue($page->filterMatch('ns:8bar:start', $filter));
371        $this->assertFalse($page->filterMatch('ns:98bar:start', $filter));
372
373        $filter = '^:systems:[^:]+:components:([^:]+:){1,2}[^:]+$';
374        $this->assertTrue($page->filterMatch('systems:system1:components:sub1:sub2:start', $filter));
375        $this->assertFalse($page->filterMatch('systems:system1:components:sub1:sub2:sub3:start', $filter));
376    }
377}
378