1 | <?php |
---|
2 | # -- BEGIN LICENSE BLOCK ---------------------------------- |
---|
3 | # This file is part of pollsFactory, a plugin for Dotclear 2. |
---|
4 | # |
---|
5 | # Copyright (c) 2009-2010 JC Denis and contributors |
---|
6 | # jcdenis@gdwd.com |
---|
7 | # |
---|
8 | # Licensed under the GPL version 2.0 license. |
---|
9 | # A copy of this license is available in LICENSE file or at |
---|
10 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
---|
11 | # -- END LICENSE BLOCK ------------------------------------ |
---|
12 | |
---|
13 | if (!defined('DC_RC_PATH')){return;} |
---|
14 | |
---|
15 | class pollsFactoryChart |
---|
16 | { |
---|
17 | public $core; |
---|
18 | public $factory; |
---|
19 | protected $cache_dir; |
---|
20 | protected $prop; |
---|
21 | protected $trigger; |
---|
22 | public $cache_folder = 'polls'; |
---|
23 | |
---|
24 | public $api_url = 'http://chart.apis.google.com/chart'; |
---|
25 | |
---|
26 | public function __construct($core) |
---|
27 | { |
---|
28 | $this->core = $core; |
---|
29 | $this->factory = new pollsFactory($core); |
---|
30 | # Cache directory |
---|
31 | $this->cache_dir = path::real(DC_TPL_CACHE); |
---|
32 | $use_cache = (boolean) $core->blog->settings->pollsFactory->pollsFactory_graph_cache; |
---|
33 | if (!$use_cache || !is_dir($this->cache_dir) || !is_writable($this->cache_dir)) { |
---|
34 | $this->cache_dir = null; |
---|
35 | } |
---|
36 | # Image properties |
---|
37 | $this->prop = @unserialize($core->blog->settings->pollsFactory->pollsFactory_graph_options); |
---|
38 | if (!is_array($this->prop) || empty($this->prop)){ |
---|
39 | $this->prop = self::defaultOptions(); |
---|
40 | } else { |
---|
41 | self::cleanOptions($this->prop); |
---|
42 | } |
---|
43 | # Last update |
---|
44 | $this->trigger = (integer) $core->blog->settings->pollsFactory->pollsFactory_graph_trigger; |
---|
45 | } |
---|
46 | |
---|
47 | public static function defaultOptions() |
---|
48 | { |
---|
49 | return array( |
---|
50 | 'width' => '400', |
---|
51 | 'ttcolor' => '#000000', |
---|
52 | 'txcolor' => '#000000', |
---|
53 | 'bgcolor' => '#FFFFFF', |
---|
54 | 'chcolor' => '#F8F8F8', |
---|
55 | 'barcolor' => '#48D9F9' |
---|
56 | ); |
---|
57 | } |
---|
58 | |
---|
59 | public static function cleanOptions(&$prop) |
---|
60 | { |
---|
61 | $prop['width'] = isset($prop['width']) ? self::size($prop['width'],'400') : '400'; |
---|
62 | $prop['ttcolor'] = isset($prop['ttcolor']) ? self::color($prop['ttcolor'],'000000') : '000000'; |
---|
63 | $prop['txcolor'] = isset($prop['txcolor']) ? self::color($prop['txcolor'],'000000') : '000000'; |
---|
64 | $prop['bgcolor'] = isset($prop['bgcolor']) ? self::color($prop['bgcolor'],'FFFFFF') : 'FFFFFF'; |
---|
65 | $prop['chcolor'] = isset($prop['chcolor']) ? self::color($prop['chcolor'],'F8F8F8') : 'F8F8F8'; |
---|
66 | $prop['barcolor'] = isset($prop['barcolor']) ? self::color($prop['barcolor'],'4D89F9') : '4D89F9'; |
---|
67 | } |
---|
68 | |
---|
69 | public function serveChart($poll_id,$query_id,$http_cache=true,$http_etag=true) |
---|
70 | { |
---|
71 | $poll_id = (integer) $poll_id; |
---|
72 | $query_id = (integer) $query_id; |
---|
73 | $now = $ts = time(); |
---|
74 | |
---|
75 | # Prepare file path |
---|
76 | $file_md5 = md5($this->core->blog->id.$poll_id.$query_id); |
---|
77 | $file_path = sprintf('%s/%s/%s/%s/%s.png', |
---|
78 | $this->cache_dir, |
---|
79 | $this->cache_folder, |
---|
80 | substr($file_md5,0,2), |
---|
81 | substr($file_md5,2,2), |
---|
82 | $file_md5 |
---|
83 | ); |
---|
84 | # File modify time |
---|
85 | clearstatcache(); |
---|
86 | $file_time = false; |
---|
87 | if (file_exists($file_path)) { |
---|
88 | $file_time = filemtime($file_path); |
---|
89 | $ts = $file_time; |
---|
90 | } |
---|
91 | # Last admin update time |
---|
92 | $trig_time = $this->trigger; |
---|
93 | # Last vote time |
---|
94 | $updt_params['option_type'] = 'pollsresponse'; |
---|
95 | $updt_params['limit'] = 1; |
---|
96 | $updt_params['order'] = 'option_upddt DESC '; |
---|
97 | $updt_params['post_id'] = $poll_id; |
---|
98 | $updt = $this->factory->getOptions($updt_params); |
---|
99 | $updt_time = $updt->isEmpty() ? 0 : strtotime($updt->option_upddt); |
---|
100 | |
---|
101 | # Check if need update |
---|
102 | if (!$file_time || $file_time < $trig_time || $file_time < $updt_time) |
---|
103 | { |
---|
104 | $ts = $now; |
---|
105 | $content = $this->sendRequest($poll_id,$query_id); |
---|
106 | |
---|
107 | # Use cache |
---|
108 | if ($this->cache_dir !== null) |
---|
109 | { |
---|
110 | files::makeDir(dirname($file_path),true); |
---|
111 | |
---|
112 | if (($fp = @fopen($file_path,'wb')) === false) { |
---|
113 | //throw new Exception('Unable to create cache file'); |
---|
114 | } |
---|
115 | else { |
---|
116 | fwrite($fp,$content); |
---|
117 | fclose($fp); |
---|
118 | files::inheritChmod($file_path); |
---|
119 | } |
---|
120 | } |
---|
121 | } |
---|
122 | else { |
---|
123 | $content = file_get_contents($file_path); |
---|
124 | } |
---|
125 | # Parse content |
---|
126 | if (!empty($content)) { |
---|
127 | http::head(200,'OK'); |
---|
128 | header('Last-Modified: '.gmdate('D, d M Y H:i:s',$ts).' GMT'); |
---|
129 | header('Cache-Control: must-revalidate, max-age=7200'); |
---|
130 | header('Pragma:'); |
---|
131 | header('Date: '.gmdate('D, d M Y H:i:s',$now).' GMT'); |
---|
132 | header('Content-Type: image/png;'); |
---|
133 | echo $content; |
---|
134 | } |
---|
135 | # Error occured |
---|
136 | else { |
---|
137 | http::head(404,'Not Found'); |
---|
138 | header('Content-Type: text/plain;'); |
---|
139 | echo 'file not found'; |
---|
140 | } |
---|
141 | exit(1); |
---|
142 | } |
---|
143 | |
---|
144 | public function sendRequest($poll_id,$query_id) |
---|
145 | { |
---|
146 | $poll_id = (integer) $poll_id; |
---|
147 | $query_id = (integer) $query_id; |
---|
148 | |
---|
149 | # Get query infos |
---|
150 | $query_params['option_type'] = 'pollsquery'; |
---|
151 | $query_params['post_id'] = $poll_id; |
---|
152 | $query_params['option_id'] = $query_id; |
---|
153 | $query = $this->factory->getOptions($query_params); |
---|
154 | if ($query->isEmpty()) { |
---|
155 | return false; |
---|
156 | } |
---|
157 | |
---|
158 | # Get responses to this query |
---|
159 | $responses_params['option_type'] = 'pollsresponse'; |
---|
160 | $responses_params['post_id'] = $poll_id; |
---|
161 | $responses_params['option_meta'] = $query_id; |
---|
162 | $responses = $this->factory->getOptions($responses_params); |
---|
163 | if ($responses->isEmpty()) { |
---|
164 | return false; |
---|
165 | } |
---|
166 | |
---|
167 | $chd = $chxl = array(); |
---|
168 | $max = 0; |
---|
169 | # Loop through responses and count results |
---|
170 | while($responses->fetch()) |
---|
171 | { |
---|
172 | $key = (integer) $responses->option_content; |
---|
173 | |
---|
174 | # Bar width (values) |
---|
175 | if (!isset($chd[$key])) { |
---|
176 | $chd[$key] = 0; |
---|
177 | } |
---|
178 | $chd[$key] += 1; |
---|
179 | # Set maximal value |
---|
180 | if ($max < $chd[$key]) { |
---|
181 | $max = $chd[$key]; |
---|
182 | } |
---|
183 | } |
---|
184 | # Loop through options |
---|
185 | $selections_params['option_type'] = 'pollsselection'; |
---|
186 | $selections_params['post_id'] = $poll_id; |
---|
187 | $selections_params['option_meta'] = $query_id; |
---|
188 | $selections = $this->factory->getOptions($selections_params); |
---|
189 | while($selections->fetch()) |
---|
190 | { |
---|
191 | $key = (integer) $selections->option_id; |
---|
192 | # Set bars with no responses |
---|
193 | if (!isset($chd[$key])) { |
---|
194 | $chd[$key] = 0; |
---|
195 | } |
---|
196 | # Bar title |
---|
197 | $chxl[$key] = html::escapeHTML($selections->option_title); |
---|
198 | } |
---|
199 | # Sort every tables to preserve title/responses |
---|
200 | ksort($chd);ksort($chxl); |
---|
201 | array_multisort($chd,$chxl); |
---|
202 | krsort($chd);ksort($chxl); |
---|
203 | |
---|
204 | # go go go |
---|
205 | $data = array(); |
---|
206 | $data['chd'] = 't:'.implode(',',$chd); // Bar values |
---|
207 | $data['chxt'] = 'x,y'; // Show axis |
---|
208 | $data['chxl'] = '1:|'.implode('|',$chxl); // Axis labels |
---|
209 | $data['chxs'] = '0,'.$this->prop['txcolor'].'|1,'.$this->prop['txcolor']; |
---|
210 | $data['chds'] = '0,'.$max; // Scale of max value |
---|
211 | $data['chm'] = 'N*,'.$this->prop['txcolor'].',0,-1,11'; // Bars labels |
---|
212 | $data['chs'] = $this->prop['width'].'x'.(count($chd) * 22 + 50); // Size = width x height(num_bar * bar_width + title_width) |
---|
213 | $data['chbh'] = '20,2'; // Bar width and space |
---|
214 | $data['chtt'] = $query->option_title; // Title = query_title |
---|
215 | $data['chts'] = $this->prop['ttcolor'].',12'; // title size and color |
---|
216 | $data['cht'] = 'bhs'; // Type = Horizontal bar charts |
---|
217 | $data['chco'] = $this->prop['barcolor']; // Bar Color = #4D89F9 |
---|
218 | $data['chf'] = 'c,s,'.$this->prop['chcolor'].'|bg,s,'.$this->prop['bgcolor']; // Chart color and background color; |
---|
219 | |
---|
220 | return $this->send($this->api_url,$data); |
---|
221 | } |
---|
222 | |
---|
223 | private static function send($url,$data) |
---|
224 | { |
---|
225 | try { |
---|
226 | $path = ''; |
---|
227 | $client = netHttp::initClient($url,$path); |
---|
228 | $client->setUserAgent('pollsFactory - http://dotclear.jcdenis.com/go/pollsFactory'); |
---|
229 | $client->useGzip(false); |
---|
230 | $client->setPersistReferers(false); |
---|
231 | $client->post($url,$data); |
---|
232 | |
---|
233 | $response = $client->getContent(); |
---|
234 | } |
---|
235 | catch (Exception $e) { |
---|
236 | throw new Exception('Failed to send request: '.$e->getMessage()); |
---|
237 | } |
---|
238 | if ($client->getStatus() != 200) { |
---|
239 | throw new Exception('Failed to get content: '.$client->getContent()); |
---|
240 | return false; |
---|
241 | } |
---|
242 | else { |
---|
243 | return $response; |
---|
244 | } |
---|
245 | } |
---|
246 | |
---|
247 | public static function size($s,$default=350) |
---|
248 | { |
---|
249 | return preg_match('/^([0-9]+)$/',$s) ? $s : $default; |
---|
250 | } |
---|
251 | |
---|
252 | public static function color($c,$default='CCCCCC') |
---|
253 | { |
---|
254 | if ($c === '') { |
---|
255 | return $default; |
---|
256 | } |
---|
257 | $c = strtoupper($c); |
---|
258 | |
---|
259 | if (preg_match('/^[A-F0-9]{6}$/',$c)) { |
---|
260 | return $c; |
---|
261 | } |
---|
262 | if (preg_match('/^[A-F0-9]{3}$/',$c)) { |
---|
263 | $e = explode('',$c); |
---|
264 | return $e[0].$e[0].$e[1].$e[1].$e[2].$e[2]; |
---|
265 | } |
---|
266 | if (preg_match('/^#[A-F0-9]{6}$/',$c)) { |
---|
267 | return substr($c,1); |
---|
268 | } |
---|
269 | if (preg_match('/^#[A-F0-9]{3}$/',$c)) { |
---|
270 | $e = explode('',$c); |
---|
271 | return $e[1].$e[1].$e[2].$e[2].$e[3].$e[3]; |
---|
272 | } |
---|
273 | return $default; |
---|
274 | } |
---|
275 | } |
---|
276 | ?> |
---|