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