Documentation is available at tgcSimplePoll.php
1 <?php
2 /**
3 * tgcSimplePoll is a class to create a voting booth
4 *
5 * $Id: tgcSimplePoll.php,v 1.2 2004/07/25 18:34:55 luckec Exp $
6 *
7 * With tgcSimplePoll you can easily include one or more surveys on your website.
8 * You can manage your polls in an admin-area which is password protected.
9 * Changing the layout is pretty easy, as tgcSimplePoll uses a template-engine (patTemplate from <{@link http://www.php-tools.de}>).
10 * Some skins already ship with the original download package.
11 *
12 * @package tgcSimplePoll
13 * @version 2.0.0
14 * @author Carsten Lucke <carsten@tool-garage.de>
15 * @copyright Carsten Lucke <carsten@tool-garage.de>
16 * @link http://www.tool-garage.de
17 */
18
19 /**
20 * poll config
21 */
22 require_once './config/config.php';
23
24 /**
25 * poll config
26 */
27 require_once SP_INCLUDEDIR . '/patForms.php';
28 require_once SP_INCLUDEDIR . '/patForms/Element.php';
29 require_once SP_INCLUDEDIR . '/patForms/Element/RadioGroup.php';
30
31 /**
32 * tgcSimplePoll baseclass
33 */
34 require_once SP_INCLUDEDIR . '/tgcSimplePoll_common.php';
35
36 /**
37 * requires PEAR::DB
38 */
39 require_once SP_INCLUDEDIR . '/pear/DB.php';
40
41 /**
42 * uses patTemplate - a powerful and easy to use template-engine
43 *
44 * @link http://www.php-tools.de
45 */
46 require_once SP_INCLUDEDIR . '/patTemplate.php';
47
48 /**
49 * tgcSession
50 */
51 require_once SP_INCLUDEDIR . '/tgcSession.php';
52
53 /**
54 * patForms
55 *
56 * @link http://www.php-tools.de
57 */
58 require_once SP_INCLUDEDIR . '/patForms.php';
59
60 /**
61 * patErrorManager for patForms
62 *
63 * @link http://www.php-tools.de
64 */
65 require_once SP_INCLUDEDIR . '/patErrorManager.php';
66
67
68 /**
69 * Fatal error occurred, poll won't work properly
70 *
71 * @access public
72 */
73 define('SP_ERROR_FATALERROR', 1);
74
75 /**
76 * tgcSimplePoll is a class to create a voting booth
77 *
78 * With tgcSimplePoll you can easily include one or more surveys on your website.
79 * You can manage your polls in an admin-area which is password protected.
80 * Changing the layout is pretty easy, as tgcSimplePoll uses a template-engine (patTemplate from <{@link http://www.php-tools.de}>).
81 * Some skins already ship with the original download package.
82 *
83 * @package tgcSimplePoll
84 * @access public
85 * @version 2.0.0
86 * @author Carsten Lucke <carsten@tool-garage.de>
87 * @link http://www.tool-garage.de
88 */
89 class tgcSimplePoll extends tgcSimplePoll_common
90 {
91 /**
92 * poll id
93 *
94 * @access private
95 * @var string
96 */
97 var $_id = null;
98
99 /**
100 * valid poll-IDs
101 *
102 * @access private
103 * @var array
104 */
105 var $_validPollIDs = array();
106
107 /**
108 * form-elements
109 *
110 * @access private
111 * @var array
112 */
113 var $_formElements = null;
114
115 /**
116 * tgcSession object
117 *
118 * @access private
119 * @var object
120 */
121 var $_session = null;
122
123 /**
124 * spam delay
125 *
126 * @access private
127 * @var int
128 */
129 var $_delay = 30;
130
131 /**
132 * verbose flag
133 *
134 * @access private
135 * @var boolean
136 */
137 var $_verbose = false;
138
139 /**
140 * intelligent flag
141 *
142 * @access private
143 * @var boolean
144 */
145 var $_intelligent = true;
146
147 /**
148 * fatal error occured in constructor, poll won't work properly
149 *
150 * @access private
151 * @var array
152 */
153 var $_fatalError = array();
154
155
156 /**
157 * Constructor
158 *
159 * Initializes the poll, as the constructor may start a session to prevent spamming (depending on ypur settings)
160 * you have to call it before any header has been sent
161 *
162 * This parameter is an associative array containing the settings you desire.
163 * If you don't want to build an own skin set "skin" to "tg/default" or sth else that already exists.
164 *
165 * <pre>
166 * Following keys are currently possible:
167 *
168 * 'language' => the language file, that shall be used - default language is English
169 * 'delay' => the time in minutes the same ip/host combination cannot answer the same question again (type: integer | default: 30)
170 * 'skin' => skin-name as string (if no skin is specified the default-skin will be used)
171 * 'verbose' => set this true to allow errormessages, else false (type: boolean | default: false)
172 * 'intelligent' => set this true, if you want the script to display a resultpage after a vote has been casted, otherwise set it to false (a new votePage will be displayed) (type: boolean | default: true)
173 * 'useSessions' => set it true to use sessions to avoid flooding, otherwise set it false (type: boolean | default: false)
174 * </pre>
175 *
176 * @access public
177 * @param string $name poll-id
178 * @param array $settings settings
179 */
180 function tgcSimplePoll($name, $settings = array())
181 {
182 $this->_id = $name;
183
184 if (isset($settings['delay'])) {
185 $this->_delay = $settings['delay'];
186 }
187 if (isset($settings['skin'])) {
188 $this->_skin = $settings['skin'];
189 }
190 if (isset($settings['verbose'])) {
191 $this->_verbose = $settings['verbose'];
192 }
193 if (isset($settings['intelligent'])) {
194 $this->_intelligent = $settings['intelligent'];
195 }
196 if (isset($settings['useSessions'])) {
197 $this->_useSessions = $settings['useSessions'];
198 }
199 if (isset($settings['language'])) {
200 $this->_lang = $settings['language'];
201 }
202
203 // init dbc
204 $dbc = &DB::connect(SP_DB_DSN);
205 if (PEAR::isError($dbc)) {
206 $this->_fatalError['message'] = 'Establishing database connection failed';
207 }
208 $dbc->setFetchMode(DB_FETCHMODE_ASSOC);
209 $this->_dbc = &$dbc;
210
211 $this->_validPollIDs = $this->getValidPollIDs();
212 if (PEAR::isError($this->_validPollIDs)) {
213 $this->_fatalError['message'] = 'No questions found';
214 }
215
216 // init template-engine
217 $this->_tmpl = &new patTemplate('html');
218 $this->_tmpl->setBasedir(SP_SKINDIR . '/' . $this->_skin);
219 $this->_tmpl->addGlobalVar('SP_SKINDIR', SP_SKINDIR . '/' . $this->_skin);
220 $this->_tmpl->setOption('translationFolder', SP_DATADIR);
221 $this->_tmpl->setOption('lang', $this->_lang);
222
223 if ($this->_useSessions) {
224 $this->_session = &new tgcSession(array('setSessionName' => 'spSession'));
225 }
226
227 // build dispatcher
228 $this->_dispatcher = $_SERVER['PHP_SELF'] . '?';
229 if ($this->_useSessions) {
230 $this->_dispatcher .= SID;
231 }
232 $this->_tmpl->addGlobalVar('SP_DISPATCHER', $this->_dispatcher);
233 $this->_tmpl->addGlobalVar('POLL_NAME', $this->_id);
234
235 if (isset($_GET['spSite'])) {
236 $this->_site = $_GET['spSite'];
237 }
238 }
239
240 /**
241 * Starts a poll
242 *
243 * Use this function to start the poll and let it choose a random question.
244 * If you call the function with a param, it will be taken as question-id and the markup for
245 * the appropriate votepage will be returned
246 *
247 * @access public
248 * @param int $qId question-id
249 * @return string string, containing the markup-code for the poll, PEAR_Error on errors
250 * @throws object PEAR_Error
251 * @see getValidPollIDs();
252 */
253 function run($qId = null)
254 {
255 if (isset($this->_fatalError['message'])) {
256 return PEAR::raiseError($this->_fatalError['message'], SP_ERROR_FATALERROR);
257 }
258
259 switch ($this->_site) {
260 case 'addVote':
261 $this->_siteAddVote();
262 break;
263 case 'showRes':
264 $this->_siteResultPage();
265 break;
266 default:
267 $this->_siteVotepage($qId);
268 break;
269 }
270
271 return $this->_tmpl->getParsedTemplate('page');
272 }
273
274 /**
275 * Returns html-markup for a random votepage
276 *
277 * @access private
278 * @param int $qId question-id
279 * @return string html-markup
280 * @uses _getRandomPollId()
281 */
282 function _siteVotepage($qId = null)
283 {
284 if (is_null($qId)) {
285 $qId = $this->_getRandomPollId();
286 }
287 $this->_getVotePage($qId);
288 }
289
290 /**
291 * Returns html-markup for a random votepage
292 *
293 * @access private
294 * @param int $qId question-id
295 * @return string html-markup
296 */
297 function _siteResultPage()
298 {
299 /**
300 * pollId defined?
301 */
302 if (! isset($_REQUEST['spId']) || urldecode($_REQUEST['spId']) != $this->_id) {
303 $this->_siteVotepage();
304 return true;
305 }
306 /**
307 * questionId defined?
308 */
309 if (! isset($_REQUEST['spQId'])) {
310 $this->_siteVotepage();
311 return true;
312 }
313
314 $this->_getResultPage($_REQUEST['spQId']);
315 }
316
317 /**
318 * Handles a submitted votepage
319 *
320 * @access private
321 * @return string html-markup
322 */
323 function _siteAddVote()
324 {
325 /**
326 * check if the submit-button for this poll was pressed
327 */
328 if (! isset($_POST[$this->_id . '_spSubmit'])) {
329 $this->_siteVotepage();
330 return true;
331 }
332
333 /**
334 * display votepage and errormessage, if questionId wasn't found
335 */
336 if (! isset($_POST[$this->_id . '_spQId'])) {
337 $this->_siteVotepage();
338 if ($this->_verbose) {
339 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
340 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'Could not cast your vote');
341 }
342 return true;
343 }
344
345 /**
346 * get options from db to rebuild radiogroup elements
347 */
348 $qId = $_POST[$this->_id . '_spQId'];
349 $options = $this->_getOptionsFromDB($qId);
350 if (PEAR::isError($options)) {
351 $this->_siteVotepage();
352 if ($this->_verbose) {
353 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
354 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'Could not cast your vote');
355 }
356 return true;
357 }
358
359 $this->_buildFormElements($options);
360 $this->_formElements->setSubmitted(true);
361 if (! $this->_formElements->validate()) {
362 /**
363 * no need to retrieve the element's validation errors as the only error can be an empty submitted form
364 */
365 $this->_siteVotepage();
366 if ($this->_verbose) {
367 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
368 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'You have to choose an option before submitting the form');
369 }
370 return true;
371 }
372
373 if (! $this->_floodingCheck($qId)) {
374 $this->_siteVotepage();
375 if ($this->_verbose) {
376 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
377 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'You can only vote once for each question');
378 }
379 return true;
380 }
381
382 /**
383 * all values are available and form has been validated -> lets go save the stuff
384 */
385 $oId = $this->_formElements->getValue();
386 $result = $this->_saveVoteToDB($qId, $oId);
387 if (PEAR::isError($result)) {
388 $this->_siteVotepage();
389 if ($this->_verbose) {
390 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
391 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'Could not cast your vote');
392 }
393 return true;
394 }
395
396 $this->_getResultPage($qId);
397 }
398
399 /**
400 * build necessary form elements
401 *
402 * @access private
403 */
404 function _buildFormElements($options)
405 {
406 $attributes = array(
407 'name' => $this->_id . '_spOptions',
408 'required' => 'yes',
409 'display' => 'yes',
410 'edit' => 'yes',
411 'title' => 'Options for this question',
412 'values' => array()
413 );
414
415 foreach ($options as $oId => $option) {
416 array_push($attributes['values'], array('value' => $oId, 'label' => $option));
417 }
418 $this->_formElements = &patForms::createElement($this->_id . '_spOptions', 'RadioGroup', $attributes);
419 }
420
421 /**
422 * Perform check to prevent flooding
423 *
424 * If useSessions is enabled people will be checked twice. Some could have cookies enabled and always remove the SID.
425 *
426 * @access private
427 * @param int $qId question-id
428 * @return boolean true if no flooding was detected, otherwise false (on errors false is returned as well)
429 */
430 function _floodingCheck($qId)
431 {
432 $time = date('YmdHis', time() - $this->_delay * 60);
433 if ($this->_useSessions)
434 {
435 $query = sprintf('SELECT COUNT(*) FROM %s WHERE qId = %d AND sid = %s AND host = %s AND ip = %s AND date >= %s',
436 $this->_prefix('votes'), $qId, $this->_dbc->quote($this->_session->getSessionId()),
437 $this->_dbc->quote(gethostbyaddr($_SERVER['REMOTE_ADDR'])), $this->_dbc->quote($_SERVER['REMOTE_ADDR']),
438 $this->_dbc->quote($time));
439 $result = $this->_dbc->getOne($query);
440 if (PEAR::isError($result) || $result > 0) {
441 return false;
442 }
443 }
444
445 $query = sprintf('SELECT COUNT(*) FROM %s WHERE qId = %d AND host = %s AND ip = %s AND date >= %s',
446 $this->_prefix('votes'), $qId, $this->_dbc->quote(gethostbyaddr($_SERVER['REMOTE_ADDR'])),
447 $this->_dbc->quote($_SERVER['REMOTE_ADDR']), $this->_dbc->quote($time));
448
449 $result = $this->_dbc->getOne($query);
450 if (PEAR::isError($result) || $result > 0) {
451 return false;
452 }
453 return true;
454 }
455
456 /**
457 * Writes a user-voting into the DB
458 *
459 * @access private
460 * @param int $qId question-id
461 * @param int $oId options-id
462 * @return boolean true on success, otherwise a PEAR_Error object
463 * @throws object PEAR_Error
464 */
465 function _saveVoteToDB($qId, $oId)
466 {
467 $vId = $this->_dbc->nextId($this->_prefix('votes'));
468 if ($this->_useSessions) {
469 $query = sprintf('INSERT INTO %s (vId, qId, oId, host, ip, sid) VALUES (%d, %d, %d, %s, %s, %s)',
470 $this->_prefix('votes'), $vId, $qId, $oId, $this->_dbc->quote(gethostbyaddr($_SERVER['REMOTE_ADDR'])),
471 $this->_dbc->quote($_SERVER['REMOTE_ADDR']), $this->_dbc->quote($this->_session->getSessionId()));
472 } else {
473 $query = sprintf('INSERT INTO %s (vId, qId, oId, host, ip) VALUES (%d, %d, %d, %s, %s)',
474 $this->_prefix('votes'), $vId, $qId, $oId, $this->_dbc->quote(gethostbyaddr($_SERVER['REMOTE_ADDR'])),
475 $this->_dbc->quote($_SERVER['REMOTE_ADDR']));
476 }
477 $result = $this->_dbc->query($query);
478 if (PEAR::isError($result)) {
479 return $result;
480 }
481 return true;
482 }
483
484 /**
485 * Determines all valid poll-ids
486 *
487 * @access public
488 * @return array question-ids on success, otherwise a PEAR_Error object
489 * @throws object PEAR_Error
490 */
491 function getValidPollIDs()
492 {
493 $query = sprintf('SELECT qId FROM %s', $this->_prefix('questions'));
494 $result = $this->_dbc->getAll($query);
495 if (PEAR::isError($result)) {
496 return $result;
497 }
498 $ids = array();
499 foreach ($result as $row) {
500 array_push($ids, $row['qId']);
501 }
502 return $ids;
503 }
504
505 /**
506 * Selects a random poll question-id
507 *
508 * @access private
509 * @return int question-id
510 */
511 function _getRandomPollId()
512 {
513 srand((double)microtime() * 10000000);
514 $randIndex = array_rand($this->_validPollIDs, 1);
515 return $this->_validPollIDs[$randIndex];
516 }
517
518 /**
519 * Returns the html-markup for a certain resultpage
520 *
521 * @access private
522 * @param int $qId question-id
523 * @param boolean $returnParsed return parsed template markup
524 * @return mixed string depends on 2nd param
525 ***/
526 function _getResultPage($qId, $returnParsed = false)
527 {
528 $this->_tmpl->readTemplatesFromFile('resultpage.tmpl');
529
530 $votes = $this->_getVoteResults($qId);
531 if (PEAR::isError($votes)) {
532 if ($this->_verbose) {
533 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
534 $this->_tmpl->setAttribute('resulltpage', 'visibility', 'visible');
535 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'No results found for this question');
536 }
537 return true;
538 }
539 $ovVotes = $this->_getOverallVotes($qId);
540 if (PEAR::isError($ovVotes)) {
541 if ($this->_verbose) {
542 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
543 $this->_tmpl->setAttribute('resulltpage', 'visibility', 'visible');
544 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'No results found for this question');
545 }
546 return true;
547 }
548
549 $this->_tmpl->addVar('resultpage', 'QUESTION', $votes[0]['question']);
550 $this->_tmpl->addVar('resultpage', 'OVERALL_VOTES', $ovVotes);
551 for ($i = 0; $i < count($votes); $i++) {
552 $percent = 0;
553 if ($votes[$i]['votes'] > 0) {
554 $percent = ($votes[$i]['votes'] / $ovVotes) * 100;
555 }
556 $this->_tmpl->addVar('options', 'OPTION', $votes[$i]['option']);
557 $this->_tmpl->addVar('options', 'VOTES', $votes[$i]['votes']);
558 $this->_tmpl->addVar('options', 'PERCENT', number_format($percent, 2, ',', '.'));
559 $this->_tmpl->addVar('options', 'BAR_WIDTH', intval($percent) * 2);
560 $this->_tmpl->parseTemplate('options', 'a');
561 }
562
563 if ($returnParsed) {
564 return $this->_tmpl->getParsedTemplate('page');
565 }
566 }
567
568 /**
569 * Returns the html-markup for a certain resultpage
570 *
571 * @access public
572 * @param int $questionId question-id
573 * @return string html markup for the resultpage
574 ***/
575 function getResultPage($qId)
576 {
577 return $this->_getResultPage($qId, true);
578 }
579
580 /**
581 * Returns the html-markup for a certain votepage
582 *
583 * @access private
584 * @param int $qId question-id
585 * @param boolean $returnParsed return parsed template markup
586 * @return mixed string depends on 2nd param
587 ***/
588 function _getVotePage($qId, $returnParsed = false)
589 {
590 $question = $this->_getQuestionFromDB($qId);
591 $options = $this->_getOptionsFromDB($qId);
592 if (PEAR::isError($question)) {
593 if ($this->_verbose) {
594 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
595 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'Database query failed: ' . $question->getMessage());
596 }
597 }
598 if (PEAR::isError($options)) {
599 if ($this->_verbose) {
600 $this->_tmpl->setAttribute('errors', 'visibility', 'visible');
601 $this->_tmpl->addVar('errorEntry', 'ERRORMESSAGE', 'Database query failed: ' . $options->getMessage());
602 }
603 }
604
605 $this->_tmpl->readTemplatesFromFile('votepage.tmpl');
606 $this->_tmpl->addVar('votepage', 'FORM_ACTION', $this->_dispatcher . '&spSite=addVote');
607 $this->_tmpl->addVar('votepage', 'QUESTION_ID', $qId);
608 $this->_tmpl->addVar('votepage', 'QUESTION', $question);
609
610 // build form-elements
611 $this->_buildFormElements($options);
612 $this->_tmpl->addVar('votepage', 'OPTIONS_RADIOGROUP', $this->_formElements->serialize());
613
614 $this->_tmpl->addVar('votepage', 'RESULTPAGE_LINK', $this->_dispatcher . '&spSite=showRes&spQId=' . $qId . '&spId=' . urlencode($this->_id));
615
616 if ($returnParsed) {
617 return $this->_tmpl->getParsedTemplate('page');
618 }
619 }
620 }
621 ?>
Documentation generated on Fri, 19 Nov 2004 23:51:48 +0100 by phpDocumentor 1.2.3