1<?php
2
3use dokuwiki\Extension\Plugin;
4use dokuwiki\Logger;
5
6/*
7 * Copyright (c) 2014-2023 Mark C. Prins <mprins@users.sf.net>
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
22/**
23 * DokuWiki Plugin spatialhelper (sitemap Component).
24 *
25 * @license BSD license
26 * @author  Mark Prins
27 */
28class helper_plugin_spatialhelper_sitemap extends Plugin
29{
30    /**
31     * spatial index.
32     */
33    private $spatial_idx;
34
35    /**
36     * constructor, load spatial index.
37     */
38    public function __construct()
39    {
40        global $conf;
41        $idx_dir = $conf['indexdir'];
42        if (!@file_exists($idx_dir . '/spatial.idx')) {
43            $indexer = plugin_load('helper', 'spatialhelper_index');
44            if ($indexer !== null) {
45                $indexer->generateSpatialIndex();
46            }
47        }
48        $this->spatial_idx = unserialize(
49            io_readFile($idx_dir . '/spatial.idx', false),
50            ['allowed_classes' => false]
51        );
52    }
53
54    final public function getMethods(): array
55    {
56        $result[] = [
57            'name' => 'createGeoRSSSitemap',
58            'desc' => 'create a spatial sitemap in GeoRSS format.',
59            'params' => ['path' => 'string'], 'return' => ['success' => 'boolean']
60        ];
61        $result[] = [
62            'name' => 'createKMLSitemap',
63            'desc' => 'create a spatial sitemap in KML format.',
64            'params' => ['path' => 'string'], 'return' => ['success' => 'boolean']
65        ];
66        return $result;
67    }
68
69    /**
70     * Create a GeoRSS Simple sitemap (Atom).
71     *
72     * @param string $mediaID id
73     *                        for the GeoRSS file
74     */
75    final public function createGeoRSSSitemap(string $mediaID): bool
76    {
77        global $conf;
78        $namespace = getNS($mediaID);
79
80        $idTag = 'tag:' . parse_url(DOKU_URL, PHP_URL_HOST) . ',';
81
82        $RSSstart = '<?xml version="1.0" encoding="UTF-8"?>' . DOKU_LF;
83        $RSSstart .= '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss" ';
84        $RSSstart .= 'xmlns:dc="http://purl.org/dc/elements/1.1/">' . DOKU_LF;
85        $RSSstart .= '<title>' . $conf['title'] . ' spatial feed</title>' . DOKU_LF;
86        if (!empty($conf['tagline'])) {
87            $RSSstart .= '<subtitle>' . $conf['tagline'] . '</subtitle>' . DOKU_LF;
88        }
89        $RSSstart .= '<dc:publisher>' . $conf['title'] . '</dc:publisher>' . DOKU_LF;
90        $RSSstart .= '<link href="' . DOKU_URL . '" />' . DOKU_LF;
91        $RSSstart .= '<link href="' . ml($mediaID, '', true, '&amp;', true) . '" rel="self" />' . DOKU_LF;
92        $RSSstart .= '<updated>' . date(DATE_ATOM) . '</updated>' . DOKU_LF;
93        $RSSstart .= '<id>' . $idTag . date("Y-m-d") . ':' . parse_url(ml($mediaID), PHP_URL_PATH)
94            . '</id>' . DOKU_LF;
95        $RSSstart .= '<rights>' . $conf['license'] . '</rights>' . DOKU_LF;
96
97        $RSSend = '</feed>' . DOKU_LF;
98
99        io_createNamespace($mediaID, 'media');
100        @touch(mediaFN($mediaID));
101        @chmod(mediaFN($mediaID), $conf['fmode']);
102        $fh = fopen(mediaFN($mediaID), 'wb');
103        fwrite($fh, $RSSstart);
104
105        foreach ($this->spatial_idx as $idxEntry) {
106            // get list of id's
107            foreach ($idxEntry as $id) {
108                // for document item in the index
109                if (strpos($id, 'media__') !== 0) {
110                    if ($this->skipPage($id, $namespace)) {
111                        continue;
112                    }
113
114                    $meta = p_get_metadata($id);
115
116                    // $desc = p_render('xhtmlsummary', p_get_instructions($meta['description']['abstract']), $info);
117                    $desc = strip_tags($meta['description']['abstract']);
118
119                    $entry = '<entry>' . DOKU_LF;
120                    $entry .= '  <title>' . $meta['title'] . '</title>' . DOKU_LF;
121                    $entry .= '  <summary>' . $desc . '</summary>' . DOKU_LF;
122                    $entry .= '  <georss:point>' . $meta['geo']['lat'] . ' ' . $meta['geo']['lon']
123                        . '</georss:point>' . DOKU_LF;
124                    if (!empty($meta['geo']['alt'])) {
125                        $entry .= '  <georss:elev>' . $meta['geo']['alt'] . '</georss:elev>' . DOKU_LF;
126                    }
127                    $entry .= '  <link href="' . wl($id) . '" rel="alternate" type="text/html" />' . DOKU_LF;
128                    if (empty($meta['creator'])) {
129                        $meta['creator'] = $conf['title'];
130                    }
131                    $entry .= '  <author><name>' . $meta['creator'] . '</name></author>' . DOKU_LF;
132                    $entry .= '  <updated>' . date_iso8601($meta['date']['modified']) . '</updated>' . DOKU_LF;
133                    $entry .= '  <published>' . date_iso8601($meta['date']['created']) . '</published>' . DOKU_LF;
134                    $entry .= '  <id>' . $idTag . date("Y-m-d", $meta['date']['modified']) . ':'
135                        . parse_url(wl($id), PHP_URL_PATH) . '</id>' . DOKU_LF;
136                    $entry .= '</entry>' . DOKU_LF;
137                    fwrite($fh, $entry);
138                }
139            }
140        }
141
142        fwrite($fh, $RSSend);
143        return fclose($fh);
144    }
145
146    /**
147     * will return true for non-public or hidden pages or pages that are not below or in the namespace.
148     */
149    private function skipPage(string $id, string $namespace): bool
150    {
151        Logger::debug("helper_plugin_spatialhelper_sitemap::skipPage, skipping $id, not in $namespace");
152        if (isHiddenPage($id)) {
153            return true;
154        }
155        if (auth_aclcheck($id, '', null) < AUTH_READ) {
156            return true;
157        }
158
159        if (!empty($namespace)) {
160            // only if id is in or below namespace
161            if (0 !== strpos(getNS($id), $namespace)) {
162                return true;
163            }
164        }
165        return false;
166    }
167
168    /**
169     * Create a KML sitemap.
170     *
171     * @param string $mediaID id for the KML file
172     */
173    final public function createKMLSitemap(string $mediaID): bool
174    {
175        global $conf;
176        $namespace = getNS($mediaID);
177
178        $KMLstart = '<?xml version="1.0" encoding="UTF-8"?>' . DOKU_LF;
179        $KMLstart .= '<kml xmlns="http://www.opengis.net/kml/2.2" ';
180        $KMLstart .= 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
181        $KMLstart .= 'xmlns:atom="http://www.w3.org/2005/Atom"';
182        $KMLstart .= ' xsi:schemaLocation="http://www.opengis.net/kml/2.2 ';
183        $KMLstart .= 'http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd">' . DOKU_LF;
184        $KMLstart .= '<Document id="root_doc">' . DOKU_LF;
185        $KMLstart .= '<name>' . $conf['title'] . ' spatial sitemap</name>' . DOKU_LF;
186        $KMLstart .= '<atom:link href="' . DOKU_URL . '" rel="related" type="text/html" />' . DOKU_LF;
187        $KMLstart .= '<!-- atom:updated>' . date(DATE_ATOM) . '</atom:updated -->' . DOKU_LF;
188        $KMLstart .= '<Style id="icon"><IconStyle><color>ffffffff</color><scale>1</scale>';
189        $KMLstart .= '<Icon><href>'
190            . DOKU_BASE . 'lib/plugins/spatialhelper/wikiitem.png</href></Icon></IconStyle></Style>' . DOKU_LF;
191
192        $KMLend = '</Document>' . DOKU_LF . '</kml>';
193
194        io_createNamespace($mediaID, 'media');
195        @touch(mediaFN($mediaID));
196        @chmod(mediaFN($mediaID), $conf['fmode']);
197
198        $fh = fopen(mediaFN($mediaID), 'wb');
199        fwrite($fh, $KMLstart);
200
201        foreach ($this->spatial_idx as $idxEntry) {
202            // get list of id's
203            foreach ($idxEntry as $id) {
204                // for document item in the index
205                if (strpos($id, 'media__') !== 0) {
206                    if ($this->skipPage($id, $namespace)) {
207                        continue;
208                    }
209
210                    $meta = p_get_metadata($id);
211
212                    // $desc = p_render('xhtmlsummary', p_get_instructions($meta['description']['abstract']), $info);
213                    $desc = '<p>' . strip_tags($meta['description']['abstract']) . '</p>';
214                    $desc .= '<p><a href="' . wl($id, '', true) . '">' . $meta['title'] . '</a></p>';
215
216                    // create an entry and store it
217                    $plcm = '<Placemark id="crc32-' . hash('crc32', $id) . '">' . DOKU_LF;
218                    $plcm .= '  <name>' . $meta['title'] . '</name>' . DOKU_LF;
219                    // TODO escape quotes in: title="' . $meta['title'] . '"
220                    $plcm .= '  <atom:link href="' . wl($id, '' . true) . '" rel="alternate" type="text/html" />'
221                        . DOKU_LF;
222                    if (!empty($meta['creator'])) {
223                        $plcm .= '  <atom:author><atom:name>' . $meta['creator'] . '</atom:name></atom:author>'
224                            . DOKU_LF;
225                    }
226
227                    $plcm .= '  <description><![CDATA[' . $desc . ']]></description>' . DOKU_LF;
228                    $plcm .= '  <styleUrl>#icon</styleUrl>' . DOKU_LF;
229
230                    $plcm .= '  <Point><coordinates>' . $meta['geo']['lon'] . ',' . $meta['geo']['lat'];
231                    if (!empty($meta['geo']['alt'])) {
232                        $plcm .= ',' . $meta['geo']['alt'];
233                    }
234                    $plcm .= '</coordinates></Point>' . DOKU_LF;
235                    $plcm .= '</Placemark>' . DOKU_LF;
236
237                    fwrite($fh, $plcm);
238                }
239            }
240        }
241        fwrite($fh, $KMLend);
242        return fclose($fh);
243    }
244}
245