xref: /plugin/tagging/action/search.php (revision 581cea88d94e2cb1ad61d4cf5b086723e105cdc4)
14a5f6748SAnna Dabrowska<?php
24a5f6748SAnna Dabrowska
3df43a7beSAndreas Gohruse dokuwiki\Extension\ActionPlugin;
4df43a7beSAndreas Gohruse dokuwiki\Extension\EventHandler;
5df43a7beSAndreas Gohruse dokuwiki\Extension\Event;
6df43a7beSAndreas Gohruse dokuwiki\Ui\SearchState;
7df43a7beSAndreas Gohruse dokuwiki\Form\InputElement;
8df43a7beSAndreas Gohr
94a5f6748SAnna Dabrowska/**
104a5f6748SAnna Dabrowska * Class action_plugin_tagging_search
114a5f6748SAnna Dabrowska */
12df43a7beSAndreas Gohrclass action_plugin_tagging_search extends ActionPlugin
134a5f6748SAnna Dabrowska{
144a5f6748SAnna Dabrowska    /**
154a5f6748SAnna Dabrowska     * @var array
164a5f6748SAnna Dabrowska     */
174a5f6748SAnna Dabrowska    protected $tagFilter = [];
184a5f6748SAnna Dabrowska
194a5f6748SAnna Dabrowska    /**
204a5f6748SAnna Dabrowska     * @var array
214a5f6748SAnna Dabrowska     */
224a5f6748SAnna Dabrowska    protected $allTagsByPage = [];
234a5f6748SAnna Dabrowska
244a5f6748SAnna Dabrowska    /**
254a5f6748SAnna Dabrowska     * @var string
264a5f6748SAnna Dabrowska     */
274a5f6748SAnna Dabrowska    protected $originalQuery = '';
284a5f6748SAnna Dabrowska
294a5f6748SAnna Dabrowska    /**
304a5f6748SAnna Dabrowska     * Register handlers
314a5f6748SAnna Dabrowska     *
32df43a7beSAndreas Gohr     * @param EventHandler $controller
334a5f6748SAnna Dabrowska     */
34df43a7beSAndreas Gohr    public function register(EventHandler $controller)
354a5f6748SAnna Dabrowska    {
364a5f6748SAnna Dabrowska        $controller->register_hook(
37df43a7beSAndreas Gohr            'TPL_CONTENT_DISPLAY',
38df43a7beSAndreas Gohr            'BEFORE',
39df43a7beSAndreas Gohr            $this,
404a5f6748SAnna Dabrowska            'echo_searchresults'
414a5f6748SAnna Dabrowska        );
424a5f6748SAnna Dabrowska
434a5f6748SAnna Dabrowska        $controller->register_hook(
44df43a7beSAndreas Gohr            'TPL_ACT_RENDER',
45df43a7beSAndreas Gohr            'BEFORE',
46df43a7beSAndreas Gohr            $this,
474a5f6748SAnna Dabrowska            'setupTagSearchDoku'
484a5f6748SAnna Dabrowska        );
494a5f6748SAnna Dabrowska
504a5f6748SAnna Dabrowska        $controller->register_hook(
51df43a7beSAndreas Gohr            'SEARCH_QUERY_FULLPAGE',
52df43a7beSAndreas Gohr            'AFTER',
53df43a7beSAndreas Gohr            $this,
544a5f6748SAnna Dabrowska            'filterSearchResults'
554a5f6748SAnna Dabrowska        );
564a5f6748SAnna Dabrowska
574a5f6748SAnna Dabrowska        $controller->register_hook(
58df43a7beSAndreas Gohr            'SEARCH_RESULT_FULLPAGE',
59df43a7beSAndreas Gohr            'AFTER',
60df43a7beSAndreas Gohr            $this,
614a5f6748SAnna Dabrowska            'tagResults'
624a5f6748SAnna Dabrowska        );
634a5f6748SAnna Dabrowska
644a5f6748SAnna Dabrowska        $controller->register_hook(
65df43a7beSAndreas Gohr            'FORM_SEARCH_OUTPUT',
66df43a7beSAndreas Gohr            'BEFORE',
67df43a7beSAndreas Gohr            $this,
684a5f6748SAnna Dabrowska            'addSwitchToSearchForm'
694a5f6748SAnna Dabrowska        );
704a5f6748SAnna Dabrowska    }
714a5f6748SAnna Dabrowska
724a5f6748SAnna Dabrowska    /**
734a5f6748SAnna Dabrowska     * Add AND/OR switch to advanced search tools
744a5f6748SAnna Dabrowska     *
75df43a7beSAndreas Gohr     * @param Event $event
764a5f6748SAnna Dabrowska     * @param            $param
774a5f6748SAnna Dabrowska     */
78df43a7beSAndreas Gohr    public function addSwitchToSearchForm(Event $event, $param)
794a5f6748SAnna Dabrowska    {
804a5f6748SAnna Dabrowska        global $INPUT;
814a5f6748SAnna Dabrowska
82df43a7beSAndreas Gohr        /* @var dokuwiki\Form\Form $searchForm */
834a5f6748SAnna Dabrowska        $searchForm = $event->data;
844a5f6748SAnna Dabrowska        $currElemPos = $searchForm->findPositionByAttribute('class', 'advancedOptions');
854a5f6748SAnna Dabrowska
864a5f6748SAnna Dabrowska        // the actual filter is built in Javascript
874a5f6748SAnna Dabrowska        $searchForm->addTagOpen('div', ++$currElemPos)
884a5f6748SAnna Dabrowska            ->addClass('toggle')
894a5f6748SAnna Dabrowska            ->attr('aria-haspopup', 'true')
904a5f6748SAnna Dabrowska            ->id('plugin__tagging-tags');
914a5f6748SAnna Dabrowska        // this element needs to be rendered by the backend so that all JS events properly attach
924a5f6748SAnna Dabrowska        $searchForm->addTagOpen('div', ++$currElemPos)
934a5f6748SAnna Dabrowska            ->addClass('current');
944a5f6748SAnna Dabrowska        $searchForm->addHTML($this->getLang('search_filter_label'), ++$currElemPos);
954a5f6748SAnna Dabrowska        $searchForm->addTagClose('div', ++$currElemPos);
964a5f6748SAnna Dabrowska        $searchForm->addTagClose('div', ++$currElemPos);
974a5f6748SAnna Dabrowska
984a5f6748SAnna Dabrowska        // set active setting
994a5f6748SAnna Dabrowska        $active = '';
1004a5f6748SAnna Dabrowska        if ($INPUT->has('tagging-logic')) {
1014a5f6748SAnna Dabrowska            $active = $INPUT->str('tagging-logic');
1024a5f6748SAnna Dabrowska        }
1034a5f6748SAnna Dabrowska        $searchForm->setHiddenField('tagging-logic', $active);
1044a5f6748SAnna Dabrowska
1054a5f6748SAnna Dabrowska        $searchForm->addTagOpen('div', ++$currElemPos)
1064a5f6748SAnna Dabrowska            ->addClass('toggle')
1074a5f6748SAnna Dabrowska            ->attr('aria-haspopup', 'true')
1084a5f6748SAnna Dabrowska            ->id('plugin__tagging-logic');
1094a5f6748SAnna Dabrowska
1104a5f6748SAnna Dabrowska        // popup toggler
1114a5f6748SAnna Dabrowska        $toggler = $searchForm->addTagOpen('div', ++$currElemPos)->addClass('current');
1124a5f6748SAnna Dabrowska
1134a5f6748SAnna Dabrowska        // current item
114df43a7beSAndreas Gohr        if ($active === 'and') {
1154a5f6748SAnna Dabrowska            $currentLabel = $this->getLang('search_all_tags');
1164a5f6748SAnna Dabrowska            $toggler->addClass('changed');
1174a5f6748SAnna Dabrowska        } else {
1184a5f6748SAnna Dabrowska            $currentLabel = $this->getLang('search_any_tag');
1194a5f6748SAnna Dabrowska        }
1204a5f6748SAnna Dabrowska
1214a5f6748SAnna Dabrowska        $searchForm->addHTML($currentLabel, ++$currElemPos);
1224a5f6748SAnna Dabrowska        $searchForm->addTagClose('div', ++$currElemPos);
1234a5f6748SAnna Dabrowska
1244a5f6748SAnna Dabrowska        // options
1254a5f6748SAnna Dabrowska        $options = [
1264a5f6748SAnna Dabrowska            'or' => $this->getLang('search_any_tag'),
1274a5f6748SAnna Dabrowska            'and' => $this->getLang('search_all_tags'),
1284a5f6748SAnna Dabrowska        ];
1294a5f6748SAnna Dabrowska        $searchForm->addTagOpen('ul', ++$currElemPos)->attr('aria-expanded', 'false');
1304a5f6748SAnna Dabrowska        foreach ($options as $key => $label) {
1314a5f6748SAnna Dabrowska            $listItem = $searchForm->addTagOpen('li', ++$currElemPos);
1324a5f6748SAnna Dabrowska            if ($active && $key === $active) {
1334a5f6748SAnna Dabrowska                $listItem->addClass('active');
1344a5f6748SAnna Dabrowska            }
1354a5f6748SAnna Dabrowska            $link = $this->getSettingsLink($label, 'tagging-logic', $key);
1364a5f6748SAnna Dabrowska            $searchForm->addHTML($link, ++$currElemPos);
1374a5f6748SAnna Dabrowska            $searchForm->addTagClose('li', ++$currElemPos);
1384a5f6748SAnna Dabrowska        }
1394a5f6748SAnna Dabrowska        $searchForm->addTagClose('ul', ++$currElemPos);
1404a5f6748SAnna Dabrowska
1414a5f6748SAnna Dabrowska        $searchForm->addTagClose('div', ++$currElemPos);
1424a5f6748SAnna Dabrowska
1434a5f6748SAnna Dabrowska        // restore query with tags in the search form
1444a5f6748SAnna Dabrowska        if ($this->tagFilter) {
145df43a7beSAndreas Gohr            /** @var InputElement $q */
1464a5f6748SAnna Dabrowska            $q = $searchForm->getElementAt($searchForm->findPositionByAttribute('name', 'q'));
1474a5f6748SAnna Dabrowska            $q->val($this->originalQuery);
1484a5f6748SAnna Dabrowska        }
1494a5f6748SAnna Dabrowska    }
1504a5f6748SAnna Dabrowska
1514a5f6748SAnna Dabrowska    /**
1524a5f6748SAnna Dabrowska     * Extracts tags from query and temporarily removes them
1534a5f6748SAnna Dabrowska     * to prevent running fulltext search on tags as simple terms.
1544a5f6748SAnna Dabrowska     *
155df43a7beSAndreas Gohr     * @param Event $event
1564a5f6748SAnna Dabrowska     * @param $param
1574a5f6748SAnna Dabrowska     */
158df43a7beSAndreas Gohr    public function setupTagSearchDoku(Event $event, $param)
1594a5f6748SAnna Dabrowska    {
1604a5f6748SAnna Dabrowska        if ($event->data !== 'search' || !plugin_isdisabled('elasticsearch')) {
1614a5f6748SAnna Dabrowska            return;
1624a5f6748SAnna Dabrowska        }
1634a5f6748SAnna Dabrowska
1644a5f6748SAnna Dabrowska        // allTagsByPage will be accessed by individual search results in SEARCH_RESULT_FULLPAGE event
1654a5f6748SAnna Dabrowska        // and when displaying tag suggestions
1664a5f6748SAnna Dabrowska        /** @var helper_plugin_tagging $hlp */
1674a5f6748SAnna Dabrowska        $hlp = plugin_load('helper', 'tagging');
1684a5f6748SAnna Dabrowska        $this->allTagsByPage = $hlp->getAllTagsByPage();
1694a5f6748SAnna Dabrowska
1704a5f6748SAnna Dabrowska        global $QUERY;
171df43a7beSAndreas Gohr        if (!str_contains($QUERY, '#')) {
1724a5f6748SAnna Dabrowska            return;
1734a5f6748SAnna Dabrowska        }
1744a5f6748SAnna Dabrowska
1754a5f6748SAnna Dabrowska        $this->originalQuery = $QUERY;
1764a5f6748SAnna Dabrowska
1774a5f6748SAnna Dabrowska        // get (hash)tags from query
1784a5f6748SAnna Dabrowska        preg_match_all('/(?:#)(\w+)/u', $QUERY, $matches);
1794a5f6748SAnna Dabrowska        if (isset($matches[1])) {
1804a5f6748SAnna Dabrowska            $this->tagFilter += array_map([$hlp, 'cleanTag'], $matches[1]);
1814a5f6748SAnna Dabrowska        }
1824a5f6748SAnna Dabrowska
1834a5f6748SAnna Dabrowska        // remove tags from query before search is executed
1844a5f6748SAnna Dabrowska        self::removeTagsFromQuery($QUERY);
1854a5f6748SAnna Dabrowska    }
1864a5f6748SAnna Dabrowska
1874a5f6748SAnna Dabrowska    /**
1884a5f6748SAnna Dabrowska     * If tags are found in query, the results are filtered,
1894a5f6748SAnna Dabrowska     * or, with an empty query, tag search results are returned.
1904a5f6748SAnna Dabrowska     *
191df43a7beSAndreas Gohr     * @param Event $event
1924a5f6748SAnna Dabrowska     * @param $param
1934a5f6748SAnna Dabrowska     */
194df43a7beSAndreas Gohr    public function filterSearchResults(Event $event, $param)
1954a5f6748SAnna Dabrowska    {
1964a5f6748SAnna Dabrowska        if (!$this->tagFilter) {
1974a5f6748SAnna Dabrowska            return;
1984a5f6748SAnna Dabrowska        }
1994a5f6748SAnna Dabrowska
2004a5f6748SAnna Dabrowska        /** @var helper_plugin_tagging $hlp */
2014a5f6748SAnna Dabrowska        $hlp = plugin_load('helper', 'tagging');
2024a5f6748SAnna Dabrowska
2034a5f6748SAnna Dabrowska        // search for tagged pages
2044a5f6748SAnna Dabrowska        $pages = $hlp->searchPages($this->tagFilter);
2054a5f6748SAnna Dabrowska        if (!$pages) {
2064a5f6748SAnna Dabrowska            $event->result = [];
2074a5f6748SAnna Dabrowska            return;
2084a5f6748SAnna Dabrowska        }
2094a5f6748SAnna Dabrowska
2104a5f6748SAnna Dabrowska        // tag search only, without additional terms
2114a5f6748SAnna Dabrowska        if (!trim($event->data['query'])) {
2124a5f6748SAnna Dabrowska            $event->result = $pages;
2134a5f6748SAnna Dabrowska        }
2144a5f6748SAnna Dabrowska
2154a5f6748SAnna Dabrowska        // apply filter
2164a5f6748SAnna Dabrowska        $tagged = array_keys($pages);
2174a5f6748SAnna Dabrowska        foreach ($event->result as $id => $count) {
2184a5f6748SAnna Dabrowska            if (!in_array($id, $tagged)) {
2194a5f6748SAnna Dabrowska                unset($event->result[$id]);
2204a5f6748SAnna Dabrowska            }
2214a5f6748SAnna Dabrowska        }
2224a5f6748SAnna Dabrowska    }
2234a5f6748SAnna Dabrowska
2244a5f6748SAnna Dabrowska    /**
2254a5f6748SAnna Dabrowska     * Add tag links to all search results
2264a5f6748SAnna Dabrowska     *
227df43a7beSAndreas Gohr     * @param Event $event
2284a5f6748SAnna Dabrowska     * @param $param
2294a5f6748SAnna Dabrowska     */
230df43a7beSAndreas Gohr    public function tagResults(Event $event, $param)
2314a5f6748SAnna Dabrowska    {
2324a5f6748SAnna Dabrowska        $page = $event->data['page'];
2330801e51aSAnna Dabrowska        $tags = $this->allTagsByPage[$page] ?? null;
2344a5f6748SAnna Dabrowska        if ($tags) {
2354a5f6748SAnna Dabrowska            foreach ($tags as $tag) {
2364a5f6748SAnna Dabrowska                $event->data['resultHeader'][] = $this->getSettingsLink('#' . $tag, 'q', '#' . $tag);
2374a5f6748SAnna Dabrowska            }
2384a5f6748SAnna Dabrowska        }
2394a5f6748SAnna Dabrowska    }
2404a5f6748SAnna Dabrowska
2414a5f6748SAnna Dabrowska    /**
2424a5f6748SAnna Dabrowska     * Show tags that are similar to the terms used in search
2434a5f6748SAnna Dabrowska     *
244df43a7beSAndreas Gohr     * @param Event $event
2454a5f6748SAnna Dabrowska     * @param $param
2464a5f6748SAnna Dabrowska     */
247df43a7beSAndreas Gohr    public function echo_searchresults(Event $event, $param)
248df43a7beSAndreas Gohr    {
2494a5f6748SAnna Dabrowska        global $ACT;
2504a5f6748SAnna Dabrowska        global $QUERY;
2514a5f6748SAnna Dabrowska
2524a5f6748SAnna Dabrowska        if ($ACT !== 'search') {
2534a5f6748SAnna Dabrowska            return;
2544a5f6748SAnna Dabrowska        }
2554a5f6748SAnna Dabrowska
2564a5f6748SAnna Dabrowska        if (!plugin_isdisabled('elasticsearch')) return;
2574a5f6748SAnna Dabrowska
2584a5f6748SAnna Dabrowska        /** @var helper_plugin_tagging $hlp */
2594a5f6748SAnna Dabrowska        $hlp = plugin_load('helper', 'tagging');
2604a5f6748SAnna Dabrowska
2614a5f6748SAnna Dabrowska        $terms = $hlp->extractFromQuery(ft_queryParser(idx_get_indexer(), $QUERY));
2624a5f6748SAnna Dabrowska        if (!$terms) {
2634a5f6748SAnna Dabrowska            return;
2644a5f6748SAnna Dabrowska        }
2654a5f6748SAnna Dabrowska
2664a5f6748SAnna Dabrowska        $allTags = [];
267df43a7beSAndreas Gohr        foreach ($this->allTagsByPage as $tags) {
2684a5f6748SAnna Dabrowska            $allTags = array_merge($allTags, $tags);
2694a5f6748SAnna Dabrowska        }
2704a5f6748SAnna Dabrowska        $allTags = array_unique($allTags);
2714a5f6748SAnna Dabrowska
2724a5f6748SAnna Dabrowska        $suggestedTags = [];
2734a5f6748SAnna Dabrowska        foreach ($terms as $term) {
2744a5f6748SAnna Dabrowska            $term = str_replace('*', '', $term);
275*581cea88SAnna Dabrowska            $suggestedTags = array_merge($suggestedTags, preg_grep('/' . preg_quote($term) . '/i', $allTags));
2764a5f6748SAnna Dabrowska        }
2774a5f6748SAnna Dabrowska        sort($suggestedTags);
2784a5f6748SAnna Dabrowska
2794a5f6748SAnna Dabrowska        if (!$suggestedTags) {
2804a5f6748SAnna Dabrowska            $this->originalQuery && $this->restoreSearchQuery();
2814a5f6748SAnna Dabrowska            return;
2824a5f6748SAnna Dabrowska        }
2834a5f6748SAnna Dabrowska
2844a5f6748SAnna Dabrowska        // create output HTML: tag search links
2854a5f6748SAnna Dabrowska        $results = '<div class="search_quickresult">';
2864a5f6748SAnna Dabrowska        $results .= '<h2>' . $this->getLang('search_suggestions')  . '</h2>';
2874a5f6748SAnna Dabrowska        $results .= '<ul class="search_quickhits">';
2884a5f6748SAnna Dabrowska
2894a5f6748SAnna Dabrowska        foreach ($suggestedTags as $tag) {
2904a5f6748SAnna Dabrowska            $results .= '<li><div class="li">';
2914a5f6748SAnna Dabrowska            $results .= $this->getSettingsLink('#' . $tag, 'q', '#' . $tag);
2924a5f6748SAnna Dabrowska            $results .= '</div></li>';
2934a5f6748SAnna Dabrowska        }
2944a5f6748SAnna Dabrowska        $results .= '</ul>';
2954a5f6748SAnna Dabrowska        $results .= '<div class="clearer"></div>';
2964a5f6748SAnna Dabrowska        $results .= '</div>';
2974a5f6748SAnna Dabrowska
2984a5f6748SAnna Dabrowska        if (preg_match('/<div class="nothing">.*?<\/div>/', $event->data)) {
2994a5f6748SAnna Dabrowska            // there are no other hits, replace the nothing found
3004a5f6748SAnna Dabrowska            $event->data = preg_replace('/<div class="nothing">.*?<\/div>/', $results, $event->data, 1);
3014a5f6748SAnna Dabrowska        } elseif (preg_match('/(<\/h2>)/', $event->data)) {
3024a5f6748SAnna Dabrowska            // insert it right before second level headline
3034a5f6748SAnna Dabrowska            $event->data = preg_replace('/(<h2>)/', $results . "\\1\n", $event->data, 1);
3044a5f6748SAnna Dabrowska        } else {
3054a5f6748SAnna Dabrowska            // unclear what happened, let's just append
3064a5f6748SAnna Dabrowska            $event->data .= $results;
3074a5f6748SAnna Dabrowska        }
3084a5f6748SAnna Dabrowska
3094a5f6748SAnna Dabrowska        // all done, finally restore the original query
3104a5f6748SAnna Dabrowska        $this->restoreSearchQuery();
3114a5f6748SAnna Dabrowska    }
3124a5f6748SAnna Dabrowska
3134a5f6748SAnna Dabrowska    /**
3144a5f6748SAnna Dabrowska     * Remove tags from query
3154a5f6748SAnna Dabrowska     *
3164a5f6748SAnna Dabrowska     * @param string $q
3174a5f6748SAnna Dabrowska     */
3184a5f6748SAnna Dabrowska    public static function removeTagsFromQuery(&$q)
3194a5f6748SAnna Dabrowska    {
3204a5f6748SAnna Dabrowska        $q = preg_replace('/#\w+/u', '', $q);
3214a5f6748SAnna Dabrowska    }
3224a5f6748SAnna Dabrowska
3234a5f6748SAnna Dabrowska    /**
3244a5f6748SAnna Dabrowska     * Restore original query on exit
3254a5f6748SAnna Dabrowska     */
3264a5f6748SAnna Dabrowska    protected function restoreSearchQuery()
3274a5f6748SAnna Dabrowska    {
3284a5f6748SAnna Dabrowska        global $QUERY;
3294a5f6748SAnna Dabrowska        $QUERY = $this->originalQuery;
3304a5f6748SAnna Dabrowska    }
3314a5f6748SAnna Dabrowska
3324a5f6748SAnna Dabrowska
3334a5f6748SAnna Dabrowska    /**
3344a5f6748SAnna Dabrowska     * Returns a link that includes all parameters set by inbuilt search tools
3354a5f6748SAnna Dabrowska     * and an optional additional parameter.
3364a5f6748SAnna Dabrowska     * If the passed parameter is q, its value will be REPLACED.
3374a5f6748SAnna Dabrowska     *
3384a5f6748SAnna Dabrowska     * @param string $label
3394a5f6748SAnna Dabrowska     * @param string $param
3404a5f6748SAnna Dabrowska     * @param string $value
3414a5f6748SAnna Dabrowska     * @return string
3424a5f6748SAnna Dabrowska     */
3434a5f6748SAnna Dabrowska    protected function getSettingsLink($label, $param = '', $value = '')
3444a5f6748SAnna Dabrowska    {
3454a5f6748SAnna Dabrowska        global $QUERY;
3464a5f6748SAnna Dabrowska
3474a5f6748SAnna Dabrowska        $Indexer = idx_get_indexer();
3484a5f6748SAnna Dabrowska        $parsedQuery = ft_queryParser($Indexer, $QUERY);
349df43a7beSAndreas Gohr        $searchState = new SearchState($parsedQuery);
3504a5f6748SAnna Dabrowska        $linkTag =  $searchState->getSearchLink($label);
3514a5f6748SAnna Dabrowska
3524a5f6748SAnna Dabrowska        // manipulate the link string because there is yet no way for inbuilt search to allow plugins
3534a5f6748SAnna Dabrowska        // to extend search queries
3544a5f6748SAnna Dabrowska        if ($param === '') {
3554a5f6748SAnna Dabrowska            return $linkTag;
3564a5f6748SAnna Dabrowska        } elseif ($param === 'q') {
3574a5f6748SAnna Dabrowska            return preg_replace('/q=[^&\'" ]*/', 'q=' . urlencode($value), $linkTag);
3584a5f6748SAnna Dabrowska        }
359df43a7beSAndreas Gohr        // FIXME current links have a strange format where href is set in single quotes
360df43a7beSAndreas Gohr        // and followed by a space so preg_replace would make more sense
3614a5f6748SAnna Dabrowska        return str_replace("' >", '&' . $param . '=' . $value . "'> ", $linkTag);
3624a5f6748SAnna Dabrowska    }
3634a5f6748SAnna Dabrowska}
364