PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/jmd_rate.php

https://github.com/jmdeldin/jmd_rate
PHP | 441 lines | 367 code | 43 blank | 31 comment | 24 complexity | 4c801ad2bef07fa91cf699400a4c59c2 MD5 | raw file
  1. <?php
  2. /**
  3. * @name jmd_rate
  4. * @description CSS star rater
  5. * @author Jon-Michael Deldin
  6. * @author_uri http://jmdeldin.com
  7. * @version 0.4-DEV
  8. * @type 1
  9. * @order 5
  10. */
  11. //--------------------------------------
  12. // admin
  13. if (@txpinterface == 'admin')
  14. {
  15. add_privs('jmd_rate_prefs', '1');
  16. register_tab('extensions', 'jmd_rate_prefs', 'jmd_rate');
  17. register_callback('jmd_rate_prefs', 'jmd_rate_prefs');
  18. }
  19. function jmd_rate_prefs($event, $step)
  20. {
  21. ob_start('jmd_rate_prefs_head');
  22. pagetop('jmd_rate_prefs');
  23. echo '<div id="jmd_rate_prefs">';
  24. if (!$step)
  25. {
  26. echo fieldset(
  27. form(
  28. (
  29. fInput('submit', 'install', 'Install', 'publish').
  30. eInput('jmd_rate_prefs').
  31. sInput('install')
  32. )
  33. ).
  34. form(
  35. (
  36. fInput('submit', 'uninstall', 'Uninstall', 'publish').
  37. eInput('jmd_rate_prefs').
  38. sInput('uninstall')
  39. ), '', "verify('Are you sure you want to delete all ratings?');"
  40. ), 'Setup', 'setup'
  41. );
  42. echo fieldset(
  43. form(
  44. '<label>Quantity '.fInput('text', 'qty', 4).'</label><br/>
  45. <label>Path and filename of star image '.fInput('text', 'path', '/stars.png').'</label><br/>
  46. <label>Star width'.fInput('text', 'width', 19).'</label><br/>
  47. <label>Star height'.fInput('text', 'height', 18).'</label><br/>
  48. <label>Container class name'.fInput('text', 'class', 'rating').'</label><br/>'.
  49. fInput('submit', 'generate', 'Generate CSS', 'publish').
  50. eInput('jmd_rate_prefs').
  51. sInput('builder')
  52. ), 'CSS builder'
  53. );
  54. }
  55. elseif ($step == 'install')
  56. {
  57. $sql = "CREATE TABLE ".safe_pfx('jmd_rate')."(
  58. parentid INT,
  59. value INT,
  60. max_value INT,
  61. ip INT UNSIGNED,
  62. PRIMARY KEY(parentid, ip)
  63. )";
  64. $create = safe_query($sql);
  65. if ($create)
  66. {
  67. echo tag('Table created successfully. '.eLink('jmd_rate_prefs', '', '', '', 'Back to preferences?'), 'p', ' class="ok"');
  68. }
  69. else
  70. {
  71. echo tag('Database exists. '.eLink('jmd_rate_prefs', '', '', '', 'Back to preferences?'), 'p', ' class="not-ok"');
  72. }
  73. }
  74. elseif ($step == 'uninstall')
  75. {
  76. safe_query("DROP TABLE IF EXISTS ".safe_pfx('jmd_rate'));
  77. echo tag('Table dropped. '.eLink('jmd_rate_prefs', '', '', '', 'Back to preferences?'), 'p', ' class="ok"');
  78. }
  79. elseif ($step == 'builder')
  80. {
  81. if (is_numeric(gps('qty')) && is_numeric(gps('width')) && is_numeric(gps('height')))
  82. {
  83. $qty = gps('qty');
  84. $w = round(gps('width'));
  85. $h = round(gps('height'));
  86. $path = htmlentities(gps('path'));
  87. $class = '.'.gps('class');
  88. echo tag('CSS', 'h1');
  89. echo "
  90. <textarea class=\"code\" cols=\"78\" rows=\"32\" id=\"jmd_rate_css\">
  91. $class {}
  92. $class, $class * {
  93. margin: 0;
  94. border: 0;
  95. padding: 0;
  96. }
  97. $class ul {
  98. height: ".$h."px;
  99. position: relative;
  100. }
  101. $class ul, $class .current_rating, $class a:hover {
  102. background: url($path);
  103. }
  104. $class li {
  105. list-style: none;
  106. text-indent: -9999px;
  107. }
  108. $class .current_rating {
  109. background-position: 0 -".$h."px;
  110. z-index: 1;
  111. }
  112. $class .current_rating, $class a {
  113. height: ".$h."px;
  114. position: absolute;
  115. top: 0;
  116. left: 0;
  117. }
  118. $class a {
  119. width: ".$w."px;
  120. height: ".$h."px;
  121. overflow: hidden;
  122. z-index: 3;
  123. }
  124. $class a:hover{
  125. background-position: left center;
  126. left: 0;
  127. z-index: 2;
  128. }
  129. ".$class."_1 a:hover { width: ".$w."px }
  130. ";
  131. for ($i = 2; $i <= $qty; $i++)
  132. {
  133. echo '
  134. '.$class.'_'.$i.' a { left: '.($i - 1) * $w.'px }
  135. '.$class.'_'.$i.' a:hover { width: '.$w * $i.'px }
  136. ';
  137. }
  138. echo '</textarea>';
  139. }
  140. echo tag(eLink('jmd_rate_prefs', '', '', '', 'Try again?'), 'p');
  141. }
  142. else
  143. {
  144. echo tag('Error.', 'h1');
  145. }
  146. echo '</div><!--//jmd_rate_prefs-->';
  147. }
  148. // courtesy of upm_savenew <http://utterplush.com/txp-plugins/upm-savenew>
  149. function jmd_rate_prefs_head($buffer)
  150. {
  151. $find = '</head>';
  152. $replace = '
  153. <script type="text/javascript">
  154. function jmd_rate() {
  155. var input = document.getElementById("jmd_rate_prefs").getElementsByTagName("input");
  156. for (i = 0; i < input.length; i++) {
  157. if (input[i].getAttribute("type") == "text") {
  158. input[i].onfocus = function() {
  159. this.select();
  160. };
  161. }
  162. }
  163. var cssOutput = document.getElementById("jmd_rate_css");
  164. if (cssOutput) {
  165. cssOutput.onclick = function() {
  166. this.select();
  167. };
  168. }
  169. }
  170. addEvent(window, "load", jmd_rate);
  171. </script>
  172. <style type="text/css">
  173. #jmd_rate_prefs {
  174. width: 500px;
  175. margin: 20px auto;
  176. }
  177. fieldset label {
  178. display: block;
  179. }
  180. #setup form {
  181. display: inline;
  182. }
  183. p.not-ok {
  184. margin-top: 10px;
  185. }
  186. </style>
  187. ';
  188. return str_replace($find, $replace.$find, $buffer);
  189. }
  190. //--------------------------------------
  191. // public
  192. // instantiates jmd_rate class
  193. function jmd_rate($atts, $thing)
  194. {
  195. extract(lAtts(array(
  196. 'class' => 'rating',
  197. 'stars' => 4,
  198. 'star_width' => 19,
  199. 'wraptag' => 'div',
  200. ), $atts));
  201. global $jmd_rate_instance;
  202. $jmd_rate_instance = new jmd_rate($stars, $star_width);
  203. $out = $jmd_rate_instance->getRating().parse($thing);
  204. return ($wraptag) ? doTag($out, $wraptag, $class) : $out;
  205. }
  206. // displays rater
  207. function jmd_rate_display($atts)
  208. {
  209. return $GLOBALS['jmd_rate_instance']->display();
  210. }
  211. // checks for votes
  212. function if_jmd_rate_votes($atts, $thing)
  213. {
  214. $condition = ($GLOBALS['jmd_rate_instance']->votes > 0);
  215. $out = EvalElse($thing, $condition);
  216. return parse($out);
  217. }
  218. // Max rating possible
  219. function jmd_rate_max($atts)
  220. {
  221. return $GLOBALS['jmd_rate_instance']->maxValue;
  222. }
  223. // returns current rating
  224. function jmd_rate_rating($atts)
  225. {
  226. return $GLOBALS['jmd_rate_instance']->rating;
  227. }
  228. // returns number of votes
  229. function jmd_rate_votes($atts)
  230. {
  231. extract(lAtts(array(
  232. 'singular' => '',
  233. 'plural' => '',
  234. ), $atts));
  235. $votes = $GLOBALS['jmd_rate_instance']->votes;
  236. $out = $votes.' ';
  237. if ($singular && $plural)
  238. {
  239. $out .= (($votes > 1) ? $plural : $singular);
  240. }
  241. return $out;
  242. }
  243. // checks if the user has voted
  244. function if_jmd_rate_voted($atts, $thing)
  245. {
  246. $condition = $GLOBALS['jmd_rate_instance']->voted;
  247. $out = EvalElse($thing, $condition);
  248. return parse($out);
  249. }
  250. // article_custom with sort by rating
  251. function jmd_rate_article($atts)
  252. {
  253. extract(lAtts(array(
  254. 'author' => '',
  255. 'category' => '',
  256. 'form' => 'default',
  257. 'keywords' => '',
  258. 'limit' => '10',
  259. 'max_rating' => '',
  260. 'min_rating' => '',
  261. 'month' => '',
  262. 'section' => '',
  263. 'sort' => 'DESC',
  264. ), $atts));
  265. $matching = getRows("SELECT parentid, avg(value) AS rating FROM ".safe_pfx('jmd_rate')." GROUP BY parentid HAVING rating BETWEEN $min_rating and $max_rating ORDER BY rating $sort LIMIT $limit");
  266. // if no articles match the criteria, exit
  267. if (!$matching)
  268. {
  269. return;
  270. }
  271. $out = '';
  272. foreach ($matching as $article)
  273. {
  274. $out .= article_custom(array(
  275. 'author' => $author,
  276. 'category' => $category,
  277. 'form' => $form,
  278. 'id' => $article['parentid'],
  279. 'keywords' => $keywords,
  280. 'limit' => $limit,
  281. 'month' => $month,
  282. 'section' => $section,
  283. ));
  284. }
  285. return $out;
  286. }
  287. class jmd_rate
  288. {
  289. // input
  290. private $starWidth;
  291. public $maxValue;
  292. // helpers
  293. private $dbTable, $parentid, $ip, $uri;
  294. // rating
  295. private $value, $usedIps;
  296. public $votes, $voted, $rating;
  297. public function __construct($stars, $star_width)
  298. {
  299. ob_start();
  300. $this->dbTable = 'jmd_rate';
  301. $this->parentid = $GLOBALS['thisarticle']['thisid'];
  302. $this->uri = permlinkurl_id($this->parentid);
  303. $this->ip = $_SERVER['REMOTE_ADDR'];
  304. $this->maxValue = $stars;
  305. $this->starWidth = $star_width;
  306. if (gps('rating'))
  307. {
  308. $this->setRating();
  309. }
  310. }
  311. public function getRating()
  312. {
  313. $query = "SELECT sum(value) AS total_value, count(value) AS total_votes, max_value FROM ".safe_pfx($this->dbTable)." WHERE parentid=$this->parentid GROUP BY max_value";
  314. $row = getRows($query);
  315. if ($row)
  316. {
  317. foreach($row as $r)
  318. {
  319. extract($r);
  320. }
  321. $this->value = $r['total_value'];
  322. $this->votes = $r['total_votes'];
  323. // if the # of stars has changed, adjust previous votes
  324. if ($r['max_value'] != $this->maxValue)
  325. {
  326. $scalar = ($this->maxValue/$r['max_value']);
  327. // adjust display value
  328. $this->value *= $scalar;
  329. // adjust previous votes
  330. safe_update($this->dbTable, "value=(value*$scalar), max_value=$this->maxValue", "parentid=$this->parentid");
  331. }
  332. $this->rating = @number_format($this->value/$this->votes, 2);
  333. // see if the visitor has voted
  334. $this->voted = safe_field("ip", $this->dbTable, "ip=INET_ATON('$this->ip') AND parentid=$this->parentid");
  335. }
  336. }
  337. private function setRating()
  338. {
  339. $userRating = intval(gps('rating'));
  340. if ($this->voted)
  341. {
  342. echo tag('You have already voted!', 'p', ' class="error"');
  343. }
  344. elseif ($userRating > $this->maxValue)
  345. {
  346. echo tag('Cheater!', 'p', ' class="error"');
  347. }
  348. else
  349. {
  350. safe_insert($this->dbTable, "parentid=$this->parentid, value=$userRating, max_value=$this->maxValue, ip=INET_ATON('$this->ip')");
  351. while (@ob_end_clean());
  352. if (empty($_SERVER['FCGI_ROLE']) and empty($_ENV['FCGI_ROLE']))
  353. {
  354. txp_status_header('303 See Other');
  355. header('Location: '.$this-> uri);
  356. header('Connection: close');
  357. header('Content-Length: 0');
  358. }
  359. else
  360. {
  361. echo <<<HTML
  362. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  363. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  364. <html lang="en-us" xml:lang="en-us" xmlns="http://www.w3.org/1999/xhtml">
  365. <head>
  366. <meta http-equiv="refresh" content="0;url={$this->uri}"/>
  367. <title>Redirecting...</title>
  368. </head>
  369. <body>
  370. <a href="{$this->uri}">Back to the article</a>
  371. </body>
  372. </html>
  373. HTML;
  374. }
  375. }
  376. }
  377. public function display()
  378. {
  379. $out = '<ul style="width: '.$this->maxValue * $this->starWidth.'px">';
  380. $out .= '<li class="current_rating" style="width: '.$this->rating * $this->starWidth.'px;">Currently rated '.$this->rating.'</li>';
  381. // if they haven't voted and voting is open, link the stars
  382. if (!$this->voted)
  383. {
  384. $getUri = ($GLOBALS['permlink_mode'] == 'messy') ? '&amp;rating=' : '?rating=';
  385. for ($i = 1; $i <= $this->maxValue; $i++)
  386. {
  387. $out .= '<li class="rating_'.$i.'">
  388. <a href="'.$this->uri.$getUri.$i.'" rel="nofollow">
  389. '.$i.'/'.$this->maxValue.'
  390. </a>
  391. </li>';
  392. }
  393. }
  394. $out .= '</ul>';
  395. return $out;
  396. }
  397. }