1<?php
2/**
3 * RRDGraph Plugin: Action Plugin
4 *
5 * @author Daniel Goß <developer@flashsystems.de>
6 * @license MIT
7 */
8
9if (! defined ( 'DOKU_INC' )) die ();
10
11/**
12 * Structure for storing the media information passed to the media manager.
13 * This contains the path within the configured virtual namespace.
14 *
15 */
16class rrdMediaInfo
17{
18    /** @var string PageID of the page containing the image. */
19    public $pageId;
20
21    /** @var string ImageID of the image on the page referenced by $pageID. */
22    public $imageId;
23}
24
25/**
26 * Action Plugin Class for RRDGraph
27 *
28 */
29class action_plugin_rrdgraph extends DokuWiki_Action_Plugin {
30
31    /**
32     * Checks if the necessary dependencies for this plugin are installed.
33     * @return Array Returns an array of error messages for missing dependencies. If all dependencies are installed an empty array is returned.
34     */
35    private function &getMissingDependencies()
36    {
37        $deps = array();
38
39        if (!function_exists("imagecreatetruecolor")) array_push($deps, $this->getLang('gd_missing'));
40        if (!function_exists("rrd_graph")) array_push($deps, $this->getLang('rrd_missing'));
41
42        return $deps;
43    }
44
45    /**
46     * Creates a rrdMediaInfo instance from a given wiki path.
47     * @param string $media Wiki path of the media file including namespace.
48     * @return Returns a rrdMediaInfo-Instance if the supplied wiki path is within the virtual media namespace. If it's outside the namespace false is returned. If it's wihin the namespace but the ImageId is invalid NULl is returned.
49     */
50    private function &parseMediaPath($media)
51    {
52        $parts = explode(":", $media);
53        if (count($parts) < 3) return false;
54
55        $result = new rrdMediaInfo();
56
57        //-- Check if we are within the rrdgraph virtual namespace.
58        $mediaNamespace = $this->getConf('graph_media_namespace');
59        if (strcmp(array_shift($parts), $mediaNamespace) != 0) return false;
60
61        $result->imageId = array_pop($parts);
62        $result->pageId = implode(':', $parts);
63
64        //-- Verify the imageId
65        if (strspn($result->imageId, '0123456789abcdef') != strlen($result->imageId)) return null;
66
67        return $result;
68    }
69
70	/**
71	 * Register the necessary events.
72	 * @param Doku_Event_Handler $controller Event-Handler for registering the necessary events.
73	 */
74	public function register(Doku_Event_Handler $controller) {
75	    //-- Performe some checks and show an error message (if an admin is loged in) to inform him that
76	    //   some key parts are missing.
77	    //   This uses the msg-Function. I don't know if this function is part of the public API. So beware!
78        foreach ($this->getMissingDependencies() as $missingDepMessage) {
79            msg($missingDepMessage, -1, '', '', MSG_ADMINS_ONLY);
80        }
81
82        //-- Register the callback hooks
83        $controller->register_hook ( 'PARSER_CACHE_USE', 'BEFORE', $this, '_handle_cache_use' );
84        $controller->register_hook ( 'MEDIA_SENDFILE', 'BEFORE', $this, '_handle_media_sendfile' );
85        $controller->register_hook ( 'FETCH_MEDIA_STATUS', 'BEFORE', $this, '_handle_fetch_media_status' );
86	}
87
88	/**
89	 * Event-Handler for PARSER_CACHE_USE.
90	 * This handler is called BEFORE the cache is used and determins the dependencies of the page. It checks the metadata
91	 * if an RRD graph is present and retrieves the dependencies of the graphs on this page from the metadata.
92	 *
93	 * @param Doku_Event $event The Doku_Event object
94	 * @param mixed      $param Value provided as fifth argument to register_hook()
95	 */
96	public function _handle_cache_use(&$event, $param) {
97		$cache = &$event->data;
98
99		if (! (isset ( $cache->page ) && isset ( $cache->mode )))
100			return;
101
102		$dependencies = p_get_metadata ( $cache->page, 'plugin_' . $this->getPluginName () . ' dependencies' );
103
104		if (! empty ( $dependencies )) {
105			foreach ( $dependencies as $dependency ) {
106				$cache->depends ['files'] [] = wikiFN ( $dependency );
107			}
108		}
109	}
110
111	/**
112	 * Event-Handler for MEDIA_SENDFILE.
113	 * This handler ist called BEFORE media files are sent to the user. If the configured virtual media namespace
114	 * is detected. The graph renderer is called and a virtual rrdgraph file ist sent.
115	 * @param Doku_Event $event The Doku_Event object
116	 * @param mixed      $param Value provided as fifth argument to register_hook()
117	 * @throws Exception
118	 */
119	public function _handle_media_sendfile(&$event, $param) {
120	    global $INPUT;
121
122	    $data = &$event->data;
123	    $mediaPath = $this->parseMediaPath($data['media']);
124
125	    if ($mediaPath !== false) {
126
127    	    //-- Load the rrdgraph helper. This helper contains the cache manager and other stuff used here.
128    	    $rrdGraphHelper = &plugin_load('helper', 'rrdgraph');
129    	    if ($rrdGraphHelper === null) throw new Exception("rrdgraph helper not found.");
130
131    	    //-- Read some more parameters
132    	    $rangeNr = $INPUT->int('range', 0, true);
133    	    $mode = $INPUT->str('mode', helper_plugin_rrdgraph::MODE_GRAPH_EMBEDDED, true);
134    	    $bindingSource = $INPUT->str('bind');
135
136    	    //-- Call the helper function to render and send the graph.
137    	    $rrdGraphHelper->sendRrdImage($mediaPath->pageId, $mediaPath->imageId, $rangeNr, $mode, $bindingSource);
138
139            //-- The graph was successfully send. Suppress any more processing
140            $event->preventDefault();
141	    }
142	}
143
144	/**
145	 * Event-Handler for FETCH_MEDIA_STATUS.
146	 * This handler ist called BEFORE the status of the media file is retrieved. If the configured virtual meida
147	 * namespace is detected the method checks if the parameters look valid. If that's the case, the status code
148	 * is set to 200 and DokuWiki calls the MEDIA_SENDFILE event hook. if the virtual namespace is not detected
149	 * the method falls through and the normal media processing takes place.
150	 * @param Doku_Event $event The Doku_Event object
151	 * @param mixed      $param Value provided as fifth argument to register_hook()
152	 * @throws Exception
153	 */
154	public function _handle_fetch_media_status(&$event, $param) {
155	    $data = &$event->data;
156	    $mediaPath = $this->parseMediaPath($data['media']);
157
158	    if ($mediaPath !== false)
159	    {
160	        //-- Check if the key features (GD and RRD) are installed and fail with a 500 error if they are missing.
161	        if (count($this->getMissingDependencies()) == 0) {
162        	    if ($mediaPath === null) {
163        	        $data['status'] = 404;
164        	        $data['statusmessage'] = "Invalid graph request.";
165        	    } else {
166                    $data['status'] = 200;
167                    $data['statusmessage'] = "";
168        	    }
169	        } else {
170	            $data['status'] = 500;
171	            $data['statusmessage'] = "Could not render RRD graph. GD or rrd extension missing.";
172	        }
173	    }
174	}
175}
176
177