1<?php
2
3/**
4 * Action component of diagrams plugin
5 *
6 * This handles general operations independent of the configured mode
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Innovakom + CosmoCode <dokuwiki@cosmocode.de>
10 */
11class action_plugin_diagrams_action extends DokuWiki_Action_Plugin
12{
13    /** @var helper_plugin_diagrams */
14    protected $helper;
15
16    /**@inheritDoc */
17    public function register(Doku_Event_Handler $controller)
18    {
19        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'addJsinfo');
20        $controller->register_hook('MEDIAMANAGER_STARTED', 'AFTER', $this, 'addJsinfo');
21        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'checkConf');
22        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleCache');
23        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handlePNGDownload');
24
25        $this->helper = plugin_load('helper', 'diagrams');
26    }
27
28    /**
29     * Add data to JSINFO
30     *
31     * full service URL
32     * digram mode
33     * security token used for uploading
34     *
35     * @param Doku_Event $event DOKUWIKI_STARTED|MEDIAMANAGER_STARTED
36     */
37    public function addJsinfo(Doku_Event $event)
38    {
39        global $JSINFO;
40        $JSINFO['sectok'] = getSecurityToken();
41        $JSINFO['plugins']['diagrams'] = [
42            'service_url' => $this->getConf('service_url'),
43            'mode' => $this->getConf('mode'),
44        ];
45    }
46
47    /**
48     * Check if DokuWiki is properly configured to handle SVG diagrams
49     *
50     * @param Doku_Event $event DOKUWIKI_STARTED
51     */
52    public function checkConf(Doku_Event $event)
53    {
54        $mime = getMimeTypes();
55        if (!array_key_exists('svg', $mime) || $mime['svg'] !== 'image/svg+xml') {
56            msg($this->getLang('missingConfig'), -1);
57        }
58    }
59
60    /**
61     * Save the PNG cache of a diagram
62     *
63     * @param Doku_Event $event AJAX_CALL_UNKNOWN
64     */
65    public function handleCache(Doku_Event $event)
66    {
67        if ($event->data !== 'plugin_diagrams_savecache') return;
68        $event->preventDefault();
69        $event->stopPropagation();
70
71        // to not further complicate the JavaScript and because creating the PNG is essentially free,
72        // we always create the PNG but only save it if the cache is enabled
73        if (!$this->getConf('pngcache')) {
74            echo 'PNG cache disabled, call ignored';
75            return;
76        }
77
78        global $INPUT;
79
80        $svg = $INPUT->str('svg'); // raw svg
81        $png = $INPUT->str('png'); // data uri
82
83        if (!checkSecurityToken()) {
84            http_status(403);
85            return;
86        }
87
88        if (!$this->helper->isDiagram($svg)) {
89            http_status(400);
90            return;
91        }
92
93        if (!preg_match('/^data:image\/png;base64,/', $png)) {
94            http_status(400);
95            return;
96        }
97        $png = base64_decode(explode(',', $png)[1]);
98
99        if (substr($png, 1, 3) !== 'PNG') {
100            http_status(400);
101            return;
102        }
103
104        $cacheName = getCacheName($svg, '.diagrams.png');
105        if (io_saveFile($cacheName, $png)) {
106            echo 'OK';
107        } else {
108            http_status(500);
109        }
110    }
111
112    /**
113     * PNG download available via link created in JS (only if PNG caching is enabled)
114     *
115     * @param Doku_Event $event
116     * @return void
117     */
118    public function handlePNGDownload(Doku_Event $event)
119    {
120        if ($event->data !== 'plugin_diagrams_pngdownload') return;
121        $event->preventDefault();
122        $event->stopPropagation();
123
124        global $INPUT;
125        global $conf;
126
127        $cacheName = $INPUT->str('pngcache');
128        $media = cleanID($INPUT->str('media'));
129        $id = cleanID($INPUT->str('id'));
130
131        // check ACLs to original file or page
132        if (
133            ($id && auth_quickaclcheck($id) < AUTH_READ) ||
134            ($media && auth_quickaclcheck($media) < AUTH_READ)
135        ) {
136            http_status(403);
137            return;
138        }
139
140        // check if download target exists
141        if (
142            ($id && !page_exists($id)) ||
143            ($media && !media_exists($media))
144        ) {
145            http_status(404);
146            return;
147        }
148
149        // serve cached PNG file
150        $file = $conf['cachedir'] . $cacheName . \dokuwiki\plugin\diagrams\Diagrams::CACHE_EXT;
151        if (file_exists($file)) {
152            // correct file extension
153            $download = $media ? str_replace('.svg', '.png', $media) : $id . ".png";
154            $download = noNS($download);
155            header('Content-Type: image/png');
156            header("Content-Disposition: attachment; filename=$download;");
157            http_sendfile($file);
158            readfile($file);
159        } else {
160            http_status(404);
161        }
162    }
163}
164