add('name', PLUGIN_KARMA_NAME); $propbag->add('description', PLUGIN_KARMA_BLAHBLAH); $propbag->add('stackable', false); $propbag->add('author', 'Garvin Hicking, Grischa Brockhaus, Judebert, Gregor Voeltz, Ian'); $propbag->add('version', '2.12'); $propbag->add('requirements', array( 'serendipity' => '1.6', 'smarty' => '2.6.7', 'php' => '4.1.0' )); $propbag->add('event_hooks', array( 'external_plugin' => true, 'frontend_footer' => true, 'frontend_configure' => true, 'entry_display' => true, 'css' => true, 'css_backend' => true, 'backend_header' => true, 'backend_sidebar_admin_appearance' => true, 'backend_sidebar_entries_event_display_karmalog' => true, 'event_additional_statistics' => true )); $propbag->add('groups', array('STATISTICS')); $propbag->add('configuration', array( // Functionality options 'karma_active', 'karma_active_registered', 'extended_only', 'max_karmatime', 'max_votetime', 'max_entrytime', 'min_disp_votes', 'visits_active', 'track_visits_of_loggedin_users', 'min_disp_visits', 'exits_active', 'logging', 'ajax', // Appearance options 'alignment', 'rate_with_words', 'textual_msg', 'textual_current', 'textual_visits', 'preview_bg', 'base_image', // Text/Language options 'rate_msg', 'curr_msg', 'rate_best', 'rate_good', 'rate_okay', 'rate_poor', 'rate_vile', 'text_best', 'text_good', 'text_okay', 'text_poor', 'text_vile', )); $propbag->add('config_groups', array( PLUGIN_KARMA_TAB_OPTIONS => array( 'karma_active', 'karma_active_registered', 'extended_only', 'max_karmatime', 'max_votetime', 'max_entrytime', 'min_disp_votes', 'visits_active', 'track_visits_of_loggedin_users', 'min_disp_visits', 'exits_active', 'logging', 'ajax', ), PLUGIN_KARMA_TAB_APPEARANCE => array( 'alignment', 'rate_with_words', 'textual_msg', 'textual_current', 'textual_visits', 'preview_bg', 'base_image', ), PLUGIN_KARMA_TAB_TEXT => array( 'rate_msg', 'curr_msg', 'rate_best', 'rate_good', 'rate_okay', 'rate_poor', 'rate_vile', 'text_best', 'text_good', 'text_okay', 'text_poor', 'text_vile', ) )); } function introspect_config_item($name, &$propbag) { switch($name) { // Oldest entry age still valid for unrestricted voting case 'max_entrytime': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_ENTRYTIME); $propbag->add('description', PLUGIN_KARMA_ENTRYTIME_BLAHBLAH); $propbag->add('default', 1440); break; // Min time between votes case 'max_votetime': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTINGTIME); $propbag->add('description', PLUGIN_KARMA_VOTINGTIME_BLAHBLAH); $propbag->add('default', 5); break; // Oldest entry age still valid for karma voting (normal, restricted voting) case 'max_karmatime': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_MAXKARMA); $propbag->add('description', PLUGIN_KARMA_MAXKARMA_BLAHBLAH); $propbag->add('default', 7); break; // Is karma voting allowed? case 'karma_active': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_ACTIVE); $propbag->add('description', PLUGIN_KARMA_ACTIVE_BLAHBLAH); $propbag->add('default', 'true'); break; // Is karma voting allowed only for registered authors? case 'karma_active_registered': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_ACTIVE_REGISTERED); $propbag->add('description', ''); $propbag->add('default', 'false'); break; // Min votes to display case 'min_disp_votes': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_MIN_DISPLAYABLE_VOTES); $propbag->add('description', PLUGIN_KARMA_MIN_DISPLAYABLE_VOTES_BLAHBLAH); $propbag->add('default', '0'); break; // Is extended article visit tracking allowed? case 'visits_active': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_VISITS); $propbag->add('description', PLUGIN_KARMA_VISITS_BLAHBLAH); $propbag->add('default', 'true'); break; // Keep or ignore visits for logged in users (like admins/authors)? case 'track_visits_of_loggedin_users': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_VISITS_LOGGEDIN_USERS); $propbag->add('description', PLUGIN_KARMA_VISITS_LOGGEDIN_USERS_BLAHBLAH); $propbag->add('default', 'true'); break; // Min visits to display case 'min_disp_visits': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_MIN_DISPLAYABLE_VISITS); $propbag->add('description', PLUGIN_KARMA_MIN_DISPLAYABLE_VISITS_BLAHBLAH); $propbag->add('default', '0'); break; // Are we tracking exits? case 'exits_active': $propbag->add('type', 'boolean'); $propbag->add('name', SHOWS_TOP_EXIT); $propbag->add('description', ''); $propbag->add('default', 'false'); break; // Show karma voting on article summary, or only extended page? case 'extended_only': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_EXTENDEDONLY); $propbag->add('description', PLUGIN_KARMA_EXTENDEDONLY_BLAHBLAH); $propbag->add('default', 'false'); break; // Log karma votes? case 'logging': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_LOGGING); $propbag->add('description', PLUGIN_KARMA_LOGGING_BLAHBLAH); $propbag->add('default', 'false'); break; // Log karma votes? case 'ajax': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_AJAX); $propbag->add('description', PLUGIN_KARMA_AJAX_BLAHBLAH); $propbag->add('default', 'false'); break; // Alignment to use for rating bars case 'alignment': $select = array( 'left' => LEFT, 'center' => CENTER, 'right' => RIGHT); $propbag->add('type', 'select'); $propbag->add('name', PLUGIN_KARMA_ALIGNMENT); $propbag->add('description', PLUGIN_KARMA_ALIGNMENT_BLAHBLAH); $propbag->add('select_values', $select); $propbag->add('default', 'right'); break; // Use words for ratings? case 'rate_with_words': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_WORDRATING); $propbag->add('description', PLUGIN_KARMA_WORDRATING_BLAHBLAH); $propbag->add('default', 'false'); break; case 'textual_msg': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_IMAGE_WITH_MESSAGE); $propbag->add('description', PLUGIN_KARMA_IMAGE_WITH_MESSAGE_BLAHBLAH); $propbag->add('default', 'true'); break; case 'textual_current': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_IMAGE_WITH_CURR); $propbag->add('description', PLUGIN_KARMA_IMAGE_WITH_CURR_BLAHBLAH); $propbag->add('default', 'true'); break; case 'textual_visits': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_KARMA_IMAGE_WITH_VISITS); $propbag->add('description', PLUGIN_KARMA_IMAGE_WITH_VISITS_BLAHBLAH); $propbag->add('default', 'true'); break; // Background of admin preview table case 'preview_bg': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_PREVIEW_BG); $propbag->add('description', PLUGIN_KARMA_PREVIEW_BG_BLAHBLAH); $propbag->add('default', ''); break; // Image to use for graphical rating bar case 'base_image': $propbag->add('type', 'content'); $propbag->add('default', $this->createRatingSelector()); break; case 'rate_msg': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_NAME_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT); break; case 'curr_msg': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_CURRENT_NAME); $propbag->add('description', PLUGIN_KARMA_CURRENT_NAME_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_CURRENT); break; case 'rate_best': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTEPOINT_5_NAME); $propbag->add('description', PLUGIN_KARMA_VOTEPOINT_5_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTEPOINT_5); break; case 'rate_good': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTEPOINT_4_NAME); $propbag->add('description', PLUGIN_KARMA_VOTEPOINT_4_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTEPOINT_4); break; case 'rate_okay': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTEPOINT_3_NAME); $propbag->add('description', PLUGIN_KARMA_VOTEPOINT_3_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTEPOINT_3); break; case 'rate_poor': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTEPOINT_2_NAME); $propbag->add('description', PLUGIN_KARMA_VOTEPOINT_2_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTEPOINT_2); break; case 'rate_vile': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTEPOINT_1_NAME); $propbag->add('description', PLUGIN_KARMA_VOTEPOINT_1_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTEPOINT_1); break; case 'text_best': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_5_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_5_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT_5); break; case 'text_good': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_4_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_4_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT_4); break; case 'text_okay': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_3_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_3_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT_3); break; case 'text_poor': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_2_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_2_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT_2); break; case 'text_vile': $propbag->add('type', 'string'); $propbag->add('name', PLUGIN_KARMA_VOTETEXT_1_NAME); $propbag->add('description', PLUGIN_KARMA_VOTETEXT_1_BLAHBLAH); $propbag->add('default', PLUGIN_KARMA_VOTETEXT_1); break; default: return false; } return true; } /** * Updates database schema when upgrading from older versions. */ function checkScheme() { global $serendipity; $version = $this->get_config('dbversion', '0'); if ($version == "2.1") { $q = "ALTER TABLE {$serendipity['dbPrefix']}karmalog ADD votetime INT(11) default null"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', PLUGIN_KARMA_DB_VERSION); } elseif ($version == '2.0') { if (preg_match('@(postgres|pgsql)@i', $serendipity['dbType'])) { $q = "ALTER TABLE {$serendipity['dbPrefix']}karmalog ALTER COLUMN ip TYPE VARCHAR(45)"; } else { $q = "ALTER TABLE {$serendipity['dbPrefix']}karmalog CHANGE COLUMN ip ip VARCHAR(45)"; } $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', PLUGIN_KARMA_DB_VERSION); } elseif ($version == '1.1') { $q = "ALTER TABLE {$serendipity['dbPrefix']}karma ADD visits INT(11) default 0"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', PLUGIN_KARMA_DB_VERSION); } elseif ($version == '1.0') { $q = "ALTER TABLE {$serendipity['dbPrefix']}karma ADD visits INT(11) default 0"; $sql = serendipity_db_schema_import($q); $q = "CREATE TABLE {$serendipity['dbPrefix']}karmalog ( entryid int(11) default null, points int(4) default null, ip varchar(45), user_agent varchar(255), votetime int(11) default null )"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', PLUGIN_KARMA_DB_VERSION); } elseif ($version != PLUGIN_KARMA_DB_VERSION) { $q = "CREATE TABLE {$serendipity['dbPrefix']}karma ( entryid int(11) default null, points int(4) default null, votes int(4) default null, lastvote int(10) {UNSIGNED} NULL, visits int(11) default null )"; $sql = serendipity_db_schema_import($q); $q = "CREATE TABLE {$serendipity['dbPrefix']}karmalog ( entryid int(11) default null, points int(4) default null, ip varchar(45), user_agent varchar(255), votetime int(11) default null )"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kfetch ON {$serendipity['dbPrefix']}karma (entryid, lastvote);"; $sql = serendipity_db_schema_import($q); $q = "CREATE INDEX kentryid ON {$serendipity['dbPrefix']}karma (entryid);"; $sql = serendipity_db_schema_import($q); $this->set_config('dbversion', PLUGIN_KARMA_DB_VERSION); } return true; } /** * Unused; would create sidebar content. */ function generate_content(&$title) { $title = $this->title; } /** * Creates an array of entries and their exit counts. * * @param array entries The entries to get exit counts for * @param boolean get optional Whether to return the array (otherwise * does all the calculations, but returns only true) default:false * * @return Boolean true when $get is false; otherwise the array of * entries with their exit counts. */ function prepareExits($entries, $get = false) { static $exits = null; global $serendipity; if ($exits === null) { $q = 'SELECT entry_id, SUM(count) AS exits FROM ' . $serendipity['dbPrefix'] . 'exits WHERE entry_id IN (' . implode(', ', $entries) . ') GROUP BY entry_id'; $sql = serendipity_db_query($q); $exits = array(); if (is_array($sql)) { foreach($sql AS $idx => $row) { $exits[$row['entry_id']] = (int)$row['exits']; } } } if ($get) { return $exits[$entries]; } return true; } function getExits($entryid, $get_prepared = false) { global $serendipity; static $karma_exits = null; if ($karma_exits === null) { $karma_exits = ' | ' . TOP_EXITS . ' (%d)'; } if ($get_prepared) { $points = $this->prepareExits($entryid, true); } else { $res = serendipity_db_query("SELECT sum(count) AS exits FROM {$serendipity['dbPrefix']}exits WHERE entry_id = " . (int)$entryid . " GROUP BY entry_id", true, 'assoc'); if (is_array($res) && isset($res['exits'])) { $points = $res['exits']; } else { $points = 0; } } return sprintf($karma_exits, $points); } function performkarmaVote() { global $serendipity; // Make sure the karmaVote cookie is set, even if empty if (!isset($serendipity['COOKIE']['karmaVote'])) { serendipity_setCookie('karmaVote', serialize(array())); } // If user didn't vote, we're done. // Do we realy need this? if (!isset($_POST['karmaId'])) { if (!isset($serendipity['GET']['karmaId']) || !isset($serendipity['GET']['karmaVote'])) { return; } } // Get URL vote data if (isset($_POST['karmaId'])) { $this->karmaId = (int)$_POST['karmaId']; $this->karmaVoting = (int)$_POST['karmaVote']; } else { $this->karmaId = (int)$serendipity['GET']['karmaId']; $this->karmaVoting = (int)$serendipity['GET']['karmaVote']; } // karmaVote cookie was just set (see name="#1"); this boils down to // "if check cookie isn't 1, there's no real cookie". // The check cookie gets set when a rater is displayed, // so you've got no business voting if you haven't even // seen the rater yet. if (!isset($serendipity['COOKIE']['karmaVote']) OR $serendipity['COOKIE']['check'] != '1') { $this->karmaVote = 'nocookie'; return; } // Everything is ready. Get the cookie vote data. $karma = unserialize($serendipity['COOKIE']['karmaVote']); // Stop on invalid votes (cookie invalid, or URL data incorrect) if (!is_array($karma) || !is_numeric($this->karmaVoting) || !is_numeric($this->karmaId) || $this->karmaVoting > 2 || $this->karmaVoting < -2) { $this->karmaVote = 'invalid1'; return; } // Stop if the cookie says we already voted if (!empty($karma[$this->karmaId])) { $this->karmaVote = 'alreadyvoted'; return ; } // We don't want bots hitting the karma-voting $agent = $_SERVER['HTTP_USER_AGENT']; if (stristr($agent, 'google') || stristr($agent, 'LinkWalker') || stristr($agent, 'zermelo') || stristr($agent, 'NimbleCrawler') || stristr($agent, 'archive.org_bot') || stristr($agent, 'waybackarchive.org')) { $this->karmaVote = 'invalid1'; return ; } // Voting takes place here. // // Get voting data from the database (keeps all entries, // even if no karma match) $q = 'SELECT * FROM ' . $serendipity['dbPrefix'] . 'entries AS e LEFT OUTER JOIN ' . $serendipity['dbPrefix'] . 'karma AS k ON e.id = k.entryid WHERE e.id = ' . serendipity_db_escape_string($this->karmaId) . ' LIMIT 1'; $row = serendipity_db_query($q, true); // If there's no entry with this ID, we're done // // --TODO: Modify the plugin to allow arbitrary voting with generated IDs if (!isset($row) || !is_array($row)) { $this->karmaVote = 'invalid2'; return; } $now = time(); if ($row['votes'] === '0' || $row['votes'] > 0) { // Votes for this entry already exist. Do some checking. $max_entrytime = $this->get_config('max_entrytime', 1440) * 60; $max_votetime = $this->get_config('max_votetime', 5) * 60; $max_karmatime = $this->get_config('max_karmatime', 7) * 24 * 60 * 60; // Allow infinite voting when 0 or negative if ($max_karmatime <= 0) { $max_karmatime = $now; } // If the entry's timestamp is too old for voting, // we're done. if ($row['timestamp'] < ($now - $max_karmatime)) { $this->karmaVote = 'timeout2'; return; } // If the entry is in the grace period, or votes // aren't too close together, record the vote. if (($row['timestamp'] > ($now - $max_entrytime)) || ($row['lastvote'] + $max_votetime < $now) || $row['lastvote'] == 0) { // Update votes $q = sprintf( "UPDATE {$serendipity['dbPrefix']}karma SET points = %s, votes = %s, lastvote = %s WHERE entryid = %s", $row['points'] + $this->karmaVoting, $row['votes'] + 1, $now, $this->karmaId ); serendipity_db_query($q); } else { // Entry was too recently voted upon. Figure out // how long until voting will be allowed (in minutes). $this->karmaVote = 'timeout'; $this->karmaTimeOut = abs(ceil(($now - ($row['lastvote'] + $max_votetime)) / 60)); return; } } else { // No row. Use INSERT instead of UPDATE. $q = sprintf( "INSERT INTO {$serendipity['dbPrefix']}karma (entryid, points, votes, lastvote, visits) VALUES (%s, %s, %s, %s, %s)", $this->karmaId, $this->karmaVoting, 1, $now, 0 ); $sql = serendipity_db_query($q); } // Log the vote if (serendipity_db_bool($this->get_config('logging', false))) { $q = sprintf( "INSERT INTO {$serendipity['dbPrefix']}karmalog (entryid, points, ip, user_agent, votetime) VALUES (%s, %s, '%s', '%s', %s)", $this->karmaId, $this->karmaVoting, serendipity_db_escape_string($_SERVER['REMOTE_ADDR']), substr(serendipity_db_escape_string($_SERVER['HTTP_USER_AGENT']), 0, 255), $now ); $sql = serendipity_db_query($q); if (is_string($sql)) { mail($serendipity['serendipityEmail'] , 'KARMA ERROR', $q . '
' . $sql . '
'); } } // Set the cookie that we already voted for this entry $karma[$this->karmaId] = $this->karmaVoting; $this->karmaVote = 'voted'; serendipity_setCookie('karmaVote', serialize($karma)); } function karmaVoted($myvote,$points,$votes) { $msg = '
' . PLUGIN_KARMA_VOTED . '
'; if ($this->get_config('rate_with_words', false)) { $myvote = $this->wordRating($myvote, 1); } elseif ($this->image_name != '0') { $myvote = $this->imageRating($myvote, 1); } // Just a current rating bar, if any $bar = $this->createRatingBar(null, $points, $votes); return array("myvote" => $myvote, "msg" => $msg, "bar" => $bar); } function createkarmaBlock($entryid, $textual_msg, $msg, $bar, $enough_votes, $textual_current, $enough_visits, $textual_visits, $points, $votes) { $image_class = ''; if ($this->image_name != '0') { $image_class = ' serendipity_karmaVoting_images'; } $karma_block = "
"; if ($textual_msg) { $karma_block .= $msg; } $karma_block .= $bar; if ($enough_votes && $textual_current) { $curr_msg = $this->get_config('curr_msg', PLUGIN_KARMA_CURRENT); $karma_block .= '' . $curr_msg . ''; } if ($enough_visits && $textual_visits) { $karma_block .= '' . PLUGIN_KARMA_VISITSCOUNT . ''; } $karma_block .= "\n
\n"; // Adjust rating points if ($this->get_config('rate_with_words', false)) { $points = $this->wordRating($points, $votes); } elseif ($this->image_name != '0') { $points = $this->imageRating($points, $votes); } return array("karma_block" => $karma_block, "points" => $points); } function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get('event_hooks'); if (isset($hooks[$event])) { // Moved from above: only get image data if we're actually going to do something $this->set_valid_image_data(); // Get dimensions of image, only if not text-only if ($this->image_name) { // Is this a single-image bar, or a single segment? $ratio = $this->image_width / $this->image_height; if ($ratio < $this->max_segment_ratio) { // This is probably a single segment. Square segments // will have a ratio of 0.3; long, flat segments won't // get up to 1.0 unless they're 3 times as wide as they // are tall; full-bar images with square segments will // be 1.666; and full-bar images with tall, narrow // segments will be greater than 1.0 unless they're // nearly twice as high as they are wide. $this->image_width = $this->image_width * 5; } } switch($event) { //Javascript for ajax functionality case 'frontend_footer': if ($this->get_config('ajax') == true) { ?> $req) { $val = explode('=', $req); if (empty($_REQUEST[$val[0]])) { $_REQUEST[$val[0]] = $val[1]; } } } $parts = explode('_', $uri_parts[0]); switch($parts[0]) { case 'karma-ajaxquery': $this->performkarmaVote(); $q = "SELECT SUM(votes) AS votes, SUM(points) AS points, SUM(visits) AS visits FROM " . $serendipity['dbPrefix'] . "karma WHERE entryid = '" . (int)$_POST['karmaId'] . "';"; $sql = serendipity_db_query($q); $track_clicks = serendipity_db_bool($this->get_config('visits_active', true)); $track_karma = serendipity_db_bool($this->get_config('karma_active', true)); $enough_votes = $track_karma && ($sql[0]['votes'] >= $this->get_config('min_disp_votes', 0)); $enough_visits = $track_clicks && ($sql[0]['visits'] >= $this->get_config('min_disp_visits', 0)); $textual_msg = true; $textual_current = true; $textual_visits = true; if ($this->image_name != '0') { $textual_msg = $this->get_config('textual_msg', 'true'); $textual_current = $this->get_config('textual_current', 'true'); $textual_visits = $this->get_config('textual_visits', 'true'); } $temp = $this->karmaVoted((int)$_POST['karmaVote'],$sql[0]['points'],$sql[0]['votes']); $myvote = $temp['myvote']; $msg = $temp['msg']; $bar = $temp['bar']; $temp = $this->createkarmaBlock((int)$_POST['karmaId'], $textual_msg, $msg, $bar, $enough_votes, $textual_current, $enough_visits, $textual_visits, $sql[0]['points'], $sql[0]['votes']); $karma_block = $temp['karma_block']; $points = $temp['points']; echo sprintf($karma_block, $myvote, $points, $sql[0]['votes'], $sql[0]['visits'], ''); break; } return true; break; // Early hook, before any page is displayed case 'frontend_configure': $this->performkarmaVote(); return true; break; // CSS generation hooks case 'backend_header': if (($serendipity['GET']['adminModule'] == 'event_display' && $serendipity['GET']['adminAction'] == 'karmalog') || $serendipity['GET']['adminModule'] == 'plugins') { // Generate the CSS for the graphical rating bar selector // // The CSS appears to be generated in a completely // different instance of Serendipity, as if index.php gets // called separately for the CSS. // // Note that the css_backend hook adds properties to the // serendipity_admin.css, but that file is *always* // cached. We use backend_header and add the CSS to the // HEAD styles to make it dynamic. (Edit: with 2.0 this has changed) // Get the CSS, set $this->image_name so we'll output the // standard graphical CSS prologue if any images are found. $this->createRatingSelector(); print ("\n"); } } if ($serendipity['version'][0] > 1) { break; return true; } case 'css_backend': case 'css': // Some CSS notes: // // .serendipity_karmaVoting is the class for the karma wrapper/container, // including the text explanations, messages, and rating bar. // (currently a div) // .serendipity_karmaVoting a specifies the links for the text-mode // rating bar // .serendipity_karmaError is the class for any text indicating an // error condition // .serendipity_karmaSuccess is the class for any text indicating // successful operation // .serendipity_karmaVoting_links is the container for the graphical // rating bar (currently an ol) // .serendipity_karmaVoting_links a indicates the various voting links in // the graphical rating bar // .serendipity_karmaVoting_current-rating is the class for the current // rating in the graphical rating bar // a.serendipity_karmaVoting_link1, _link2, etc are the classes applied // to the individual voting links // Note that there are two possible template types: early // templates that only handle the text rating bars, and // newer templates that understand the graphical raters. // We check for both types and act appropriately. /*--JAM: Let's just skip this whole hassle if (!$align) { $align = $this->get_config('alignment', 'detect'); } if ($align == 'detect') { */ $align = $this->get_config('alignment'); // Try to let the template take care of it if ($this->image_name == '0') { // Text-only rating bar is used if (strpos($eventData, '.serendipity_karmaVoting')) { // Template is handling all our CSS return true; } }/* --JAM: else { // Graphical rating bar is used if (strpos($eventData, '.serendipity_karmaVoting_images')) { // Template is handling all our CSS return true; } // Check for old text-only templates $pos = strpos($eventData, '.serendipity_karmaVoting'); while ($pos && ($align == 'detect')) { // Find text-align: in the current block $endpos = strpos($eventData, '}', $pos); if (!$endpos) { // Broken CSS break; } $alignpos = strpos($eventData, 'text-align:', $pos); // Can't check for comments, or I would. Hope // the first is the correct one. if ($alignpos && $alignpos < $endpos) { $start = $alignpos + 11; $alignend = strpos($eventData, ';', $alignpos); if ($alignend) { // All valid. Pull out the alignment. $len = $alignend - $start; $align = trim(substr($eventData, $start, $len)); } } $pos = strpos($eventData, '.serendipity_karmaVoting', $endpos); } // I should have a valid alignment or 'detect' in $align now. } } // If we couldn't detect the alignment, guess 'right' if ($align == 'detect') { $align = 'right'; } --JAM: END COMMENT BLOCK */ if ($serendipity['version'][0] < 2 && $event == 'backend_header') { print ("\n"); } return true; break; //--TODO: Comment the functionality of this event hook. case 'event_additional_statistics': $sql = array(); $sql['visits_top'] = array('visits', 'DESC'); $sql['visits_bottom'] = array('visits', 'ASC'); $sql['votes_top'] = array('votes', 'DESC'); $sql['votes_bottom'] = array('votes', 'ASC'); $sql['points_top'] = array('points', 'DESC'); $sql['points_bottom'] = array('points', 'ASC'); foreach($sql AS $key => $rows) { $q = "SELECT e.id, e.title, e.timestamp, SUM(k.{$rows[0]}) AS no FROM {$serendipity['dbPrefix']}karma AS k JOIN {$serendipity['dbPrefix']}entries AS e ON k.entryid = e.id WHERE k.{$rows[0]} IS NOT NULL AND k.{$rows[0]} != 0 GROUP BY e.id, e.title, e.timestamp ORDER BY no {$rows[1]} LIMIT {$addData['maxitems']}"; $sql_rows = serendipity_db_query($q); ?>

$row) { ?>
get_config('dbversion', 0) != PLUGIN_KARMA_DB_VERSION) { $this->checkScheme(); } // Find the ID of this entry if (isset($serendipity['GET']['id'])) { $entryid = (int)serendipity_db_escape_string($serendipity['GET']['id']); } elseif (preg_match(PAT_COMMENTSUB, $_SERVER['REQUEST_URI'], $matches)) { $entryid = (int)$matches[1]; } else { $entryid = false; } // If we're actually reading the entry, not voting or editing it... if ($entryid && empty($serendipity['GET']['adminAction']) && !$serendipity['GET']['karmaVote']) { // Update the number of visits // Are we supposed to track visits? $track_clicks = serendipity_db_bool($this->get_config('visits_active', true)) && $this->track_clicks_allowed_by_user(); if ($track_clicks && $_SERVER['REQUEST_METHOD'] == 'GET') { $sql = serendipity_db_query( "UPDATE {$serendipity['dbPrefix']}karma SET visits = visits + 1 WHERE entryid = $entryid", true); if (serendipity_db_affected_rows() < 1) { serendipity_db_query( "INSERT INTO {$serendipity['dbPrefix']}karma (entryid, points, votes, lastvote, visits) VALUES ('$entryid', 0, 0, 0, 1)" ); } } } // Set a cookie to look for later, verifying that cookies are enabled serendipity_setCookie('check', '1'); switch($this->karmaVote) { case 'nocookie': // Users with no cookies won't be able to vote. $msg = '
' . PLUGIN_KARMA_NOCOOKIE . '
'; // Continue until output case 'timeout2': if (!isset($msg)) { $msg = '
' . PLUGIN_KARMA_CLOSED . '
'; } // Continue until output case 'timeout': if (!isset($msg)) { $msg = '
' . sprintf(PLUGIN_KARMA_TIMEOUT, $this->karmaTimeOut) . '
'; } // Continue until output case 'alreadyvoted': if (!isset($msg)) { $msg = '
' . PLUGIN_KARMA_ALREADYVOTED . '
'; } // Continue until output case 'invalid1': case 'invalid2': case 'invalid': // Set message if (!isset($msg)) { $msg = '
' . PLUGIN_KARMA_INVALID . '
'; } // Continue until output /* OUTPUT MESSAGE */ //--TODO: Shouldn't this work with the cache plugin, too? if ($addData['extended']) { $eventData[0]['exflag'] = 1; $eventData[0]['add_footer'] .= $msg; } else { $elements = count($eventData); // Find the right container to store our message in. for ($i = 0; $i < $elements; $i++) { if ($eventData[$i]['id'] == $this->karmaId) { $eventData[$i]['add_footer'] .= $msg; } } } break; case 'voted': default: // If there's no data, there's no need to go on if (!is_array($eventData)) return; // Find out what the admin wants $track_clicks = serendipity_db_bool($this->get_config('visits_active', true)); $track_karma = serendipity_db_bool($this->get_config('karma_active', true)); if (serendipity_db_bool($this->get_config('karma_active_registered', false))) { if (!serendipity_userLoggedIn()) $track_karma = false; } $track_exits = serendipity_db_bool($this->get_config('exits_active', true)); // Get the limits $now = time(); $karmatime = $this->get_config('max_karmatime', 7); $max_karmatime = $karmatime * 24 * 60 * 60; // Accept infinite voting if ($max_karmatime <= 0) { $max_karmatime = $now; } //--TODO: Ensure that this works with the Custom Permalinks plugin // (We've seen trouble; it votes correctly, but redirects to the front page) $url = serendipity_currentURL(true); // Voting is only allowed on entries. Therefore voting URLs must be // either single-entry URLs or summary URLs. serendipity_currentURL // converts them to an "ErrorDocument-URI", so we can focus on the // query portion of the URI. // // Single-entry URLs should be well-defined. They can be permalinks, // of course; otherwise they're of the configured pattern. // // Summary URLs could be a little harder. The summary pages that // include entries are: frontpage, category, author, and archives. // It's possible a plugin would show entries, but if that's the case // we don't need to allow the user to vote on them. Still, that's // a lot of URLs to check for. // // Then there's the problem of the rest of the query. It could // include stuff we really want to keep around, like template // overrides or something. One can even add serendipity variables // to the URL in extreme cases. // // It seems that canonicalizing the URL will be quite difficult. // The only thing we can say for certain is that whatever the // current URL is, it got us to this page, and we'd like to return // to this page after we cast our vote. // Remove any clutter from our previous voting activity $url_parts = parse_url(serendipity_currentURL(true)); if (!empty($url_parts['query'])) { $exclude = array('serendipity[karmaVote]', 'serendipity[karmaId]'); // I tried using parse_str, but it gave me very weird results // with embedded arrays //parse_str($url_parts['query'], $q_parts); $q_parts = array(); // I don't know why this URL has been HTML encoded. Oh well. $pairs = explode('&', $url_parts['query']); foreach($pairs as $pair) { $parts = explode('=', $pair); $q_parts[$parts[0]] = $parts[1]; } foreach($q_parts as $key => $value) { if (in_array($key, $exclude)) { $rm = preg_quote("$key=$value"); $url = preg_replace("@(&|&)?$rm@", '', $url); } } } if (substr($url, -1) != '?') { $url .= '&'; } // Get the cookie data (past votes, etc) $karma = (isset($serendipity['COOKIE']['karmaVote']) ? unserialize($serendipity['COOKIE']['karmaVote']) : array()); // Get all required entry IDs, making keys match keys in eventData $entries = array(); if ($addData['extended'] || $addData['preview']) { // We're in extended or preview mode, we only need the current ID $eventData[0]['exflag'] = 1; $entries[0] = (int)($eventData[0]['id']); } elseif (!serendipity_db_bool($this->get_config('extended_only', false))) { // We're in overview mode, and we want rating bars for all the entry IDs foreach(array_keys($eventData) as $key) { if (isset($eventData[$key]['id'])) { $entries[$key] = (int)$eventData[$key]['id']; } } } // Fetch votes for all entry IDs. Store them in an array for later usage. $q = 'SELECT k.entryid, SUM(votes) AS votes, SUM(points) AS points, SUM(visits) AS visits FROM ' . $serendipity['dbPrefix'] . 'karma AS k WHERE k.entryid IN (' . implode(', ', $entries) . ') GROUP BY k.entryid'; $sql = serendipity_db_query($q); $rows = array(); if ($sql && is_array($sql)) { foreach($sql AS $row) { $rows[$row['entryid']] = array( 'votes' => $row['votes'], 'points' => $row['points'], 'visits' => $row['visits'] ); } } $this->prepareExits($entries); // Add karma block to the footer of each entry // // The entries array was populated, above, so its keys match the eventData array, // and overview entries are skipped if "extended only" is enabled foreach (array_keys($entries) as $i) { // Get the statistics $entryid = $eventData[$i]['id']; $votes = (!empty($rows[$entryid]['votes']) ? $rows[$entryid]['votes'] : 0); $points = (!empty($rows[$entryid]['points']) ? $rows[$entryid]['points'] : 0); $visits = (!empty($rows[$entryid]['visits']) ? $rows[$entryid]['visits'] : 0); $enough_votes = $track_karma && ($votes >= $this->get_config('min_disp_votes', 0)); $enough_visits = $track_clicks && ($visits >= $this->get_config('min_disp_visits', 0)); $textual_msg = true; $textual_current = true; $textual_visits = true; if ($this->image_name != '0') { $textual_msg = $this->get_config('textual_msg', 'true'); $textual_current = $this->get_config('textual_current', 'true'); $textual_visits = $this->get_config('textual_visits', 'true'); } // Where's the footer? Normally it would be // in eventData[n]['add_footer'] but if the // cache plugin is used, it's in // eventData[n]['properties']['ep_cache_add_footer']. // This method retrieves it either way. $footer = &$this->getFieldReference('add_footer', $eventData[$i]); // Depending on what existed, $footer could // be referencing the cached version, the // uncached version, or even a new empty // string. In particular, if $eventData[$i] // has no properties, and no 'add_footer' key, // $footer is referencing a new empty string, // so adding a karma bar to $footer would do // nothing. // // We could be referencing an empty uncached // 'add_footer', but empty cache entries are // never returned. // // Reference a footer that will be printed if (empty($footer) && !isset($eventData[$i]['add_footer']) && is_array($eventData[$i])) { $eventData[$i]['add_footer'] = ''; $footer = &$eventData[$i]['add_footer']; // It's still empty, but it's referencing // the right place. } if ($track_exits) { $footer .= $this->getExits($entryid, true); } // Pick the appropriate intro msg and rating bar // No msg or bar if karma is disabled if ($track_karma) { if (isset($karma[$entryid])) { // We already voted for this one $temp = $this->karmaVoted($karma[$entryid],$points,$votes); $myvote = $temp['myvote']; $msg = $temp['msg']; $bar = $temp['bar']; } elseif ($eventData[$i]['timestamp'] < ($now - $max_karmatime)) { // Too late to vote for this one $msg = '
' . sprintf(PLUGIN_KARMA_CLOSED, $karmatime) . '
'; // Just a current rating bar, if any $bar = $this->createRatingBar(null, $points, $votes); } else { // We can vote for this; make the whole voting block $rate_msg = $this->get_config('rate_msg', PLUGIN_KARMA_VOTETEXT); $msg = '
' . $rate_msg . '
'; // Full voting bar $bar = $this->createRatingBar($entryid, $points, $votes); } } // Create the karma block $temp = $this->createkarmaBlock($entryid, $textual_msg, $msg, $bar, $enough_votes, $textual_current, $enough_visits, $textual_visits, $points, $votes); $karma_block = $temp['karma_block']; $points = $temp['points']; /* print("

--DEBUG: Karma block code:

\n
\n");
                                   print_r(htmlspecialchars($karma_block));
                                   print("\n
\n"); */ // Substitute the % stuff and add it to the footer $eventData[$i]['properties']['myvote'] = $myvote; $eventData[$i]['properties']['points'] = $points; $eventData[$i]['properties']['votes'] = $votes; $eventData[$i]['properties']['visits'] = $visits; $footer .= sprintf($karma_block, $myvote, $points, $votes, $visits, $url); } // foreach key in entries }// End switch on karma voting status return true; break; // Display the Karma Log link on the sidebar case 'backend_sidebar_admin_appearance': ?>
  • $msg\n"); //} // Was I asked to process any votes? if (($serendipity['POST']['delete_button'] || $serendipity['POST']['approve_button']) && sizeof($serendipity['POST']['delete']) != 0 && serendipity_checkFormToken()) { foreach($serendipity['POST']['delete'] as $d => $i) { $kdata = $serendipity['POST']['karmalog'.$i]; // validate posted variables // posted points $ppoints = $kdata['points']; if (!is_numeric($ppoints) || ((int)$ppoints < -2) || ((int)$ppoints > 2)) { print(" ".PLUGIN_KARMA_INVALID_INPUT."\n"); return false; } // posted id $pid = $kdata['entryid']; if (!is_numeric($pid)) { print(" ".PLUGIN_KARMA_INVALID_INPUT."\n"); return false; } // posted IP $pip = long2ip(ip2long($kdata['ip'])); if ($pip == -1 || $pip === FALSE) { print(" ".PLUGIN_KARMA_INVALID_INPUT."\n"); return false; } // posted user agent (need a better validator, I think) $puser_agent = $kdata['user_agent']; if (serendipity_db_escape_string($puser_agent) != $puser_agent) { print(" ".PLUGIN_KARMA_INVALID_INPUT."\n"); return false; } // posted vote time $pvotetime = $kdata['votetime']; $unixsecs = date('U', $kdata['votetime']); if ($pvotetime != $unixsecs) { print(" ".PLUGIN_KARMA_INVALID_INPUT."\n"); return false; } // Remove karma from entry? if ($serendipity['POST']['delete_button']) { // Fetch vote total for the entry IDs $q = 'SELECT k.* FROM ' . $serendipity['dbPrefix'] . 'karma AS k WHERE k.entryid IN (' . $pid . ') GROUP BY k.entryid'; $sql = serendipity_db_query($q); if (is_array($sql)) { $karma = $sql[0]; $update = sprintf( "UPDATE {$serendipity['dbPrefix']}karma SET points = %s, votes = %s WHERE entryid = %s", serendipity_db_escape_string($karma['points'] - $ppoints), serendipity_db_escape_string($karma['votes'] - 1), serendipity_db_escape_string($pid) ); $updated = serendipity_db_query($update); if ($updated != 1) { printf(" ".PLUGIN_KARMA_REMOVE_ERROR."\n", $pid); // Don't delete from karma log if we couldn't take away the points continue; } } else { // This will only happen if someone is messing with the karma table or submit data printf(" ".PLUGIN_KARMA_UPDATE_ERROR."", $pid); continue; } } // Remove vote from log (approved or deleted, doesn't matter) $del = sprintf( "DELETE FROM {$serendipity['dbPrefix']}karmalog WHERE entryid = %s AND ip = '%s' AND user_agent LIKE '%%%s%%' AND votetime = %s LIMIT 1", serendipity_db_escape_string($pid), serendipity_db_escape_string($pip), serendipity_db_escape_string($puser_agent), serendipity_db_escape_string($pvotetime) ); $deleted = serendipity_db_query($del); // User feedback if ($deleted == 1) { if ($serendipity['POST']['delete_button']) { printf(" ".PLUGIN_KARMA_REMOVED_POINTS."\n", $ppoints, $pid); } else { printf(" ".PLUGIN_KARMA_APPROVED_POINTS."\n", $ppoints, $pid); } } else { printf(" ".PLUGIN_KARMA_REMOVE_ERROR."\n", $pid); } } } // URL; expected to be event_display and karmalog, respectively $url = '?serendipity[adminModule]='.(function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['adminModule']) : htmlspecialchars($serendipity['GET']['adminModule'], ENT_COMPAT, LANG_CHARSET)).'&serendipity[adminAction]='.(function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['GET']['adminAction']) : htmlspecialchars($serendipity['GET']['adminAction'], ENT_COMPAT, LANG_CHARSET)); // Filters print("

    ".PLUGIN_KARMA_DISPLAY_LOG."

    ".FILTERS."
    "); // Set all filters into $and and $searchString if (!empty($serendipity['GET']['filter']['entryid'])) { $val = $serendipity['GET']['filter']['entryid']; $and .= "AND l.entryid = '" . serendipity_db_escape_string($val) . "'"; $searchString .= "&serendipity['filter']['entryid']=".(function_exists('serendipity_specialchars') ? serendipity_specialchars($val) : htmlspecialchars($val, ENT_COMPAT, LANG_CHARSET)); } if (!empty($serendipity['GET']['filter']['ip'])) { $val = $serendipity['GET']['filter']['ip']; $and .= "AND l.ip = '" . serendipity_db_escape_string($val) . "'"; $searchString .= "&serendipity['filter']['ip']=".(function_exists('serendipity_specialchars') ? serendipity_specialchars($val) : htmlspecialchars($val, ENT_COMPAT, LANG_CHARSET)); } if (!empty($serendipity['GET']['filter']['user_agent'])) { $val = $serendipity['GET']['filter']['user_agent']; $and .= "AND l.user_agent LIKE '%" . serendipity_db_escape_string($val) . "%'"; $searchString .= "&serendipity['filter']['user_agent']=".(function_exists('serendipity_specialchars') ? serendipity_specialchars($val) : htmlspecialchars($val, ENT_COMPAT, LANG_CHARSET)); } if (!empty($serendipity['GET']['filter']['title'])) { $val = $serendipity['GET']['filter']['title']; $and .= "AND e.title LIKE '%" . serendipity_db_escape_string($val) . "%'"; $searchString .= "&serendipity['filter']['title']=".(function_exists('serendipity_specialchars') ? serendipity_specialchars($val) : htmlspecialchars($val, ENT_COMPAT, LANG_CHARSET)); } // Sorting (controls go after filtering controls in form above) $sort_order = array( 'votetime' => DATE, 'user_agent' => USER_AGENT, 'title' => TITLE, 'entryid' => 'ID'); if (empty($serendipity['GET']['sort']['ordermode']) || $serendipity['GET']['sort']['ordermode'] != 'ASC') { $desc = true; $serendipity['GET']['sort']['ordermode'] = 'DESC'; } if (!empty($serendipity['GET']['sort']['order']) && !empty($sort_order[$serendipity['GET']['sort']['order']])) { $curr_order = $serendipity['GET']['sort']['order']; $orderby = serendipity_db_escape_string($curr_order . ' ' . $serendipity['GET']['sort']['ordermode']); } else { $curr_order = 'votetime'; $orderby = 'votetime ' . serendipity_db_escape_string($serendipity['GET']['sort']['ordermode']); } print("
    ".SORT_ORDER."
    "); // Paging (partly ripped from include/admin/comments.inc.php) $commentsPerPage = (int)(!empty($serendipity['GET']['filter']['perpage']) ? $serendipity['GET']['filter']['perpage'] : 25); $sql = serendipity_db_query("SELECT COUNT(*) AS total FROM {$serendipity['dbPrefix']}karmalog l WHERE 1 = 1 " . $and, true); if (is_string($sql)) print(" ".$sql."\n"); $totalVotes = (is_array($sql) && is_int($sql['total'])) ? $sql['total'] : 0; $pages = ($commentsPerPage == COMMENTS_FILTER_ALL ? 1 : ceil($totalVotes/(int)$commentsPerPage)); $page = (int)$serendipity['GET']['page']; if ($page == 0 || ($page > $pages)) { $page = 1; } if ($page > 1) { $linkPrevious = $url . '&serendipity[page]='. ($page-1) . $searchString; } if ($pages > $page) { $linkNext = $url . '&serendipity[page]='. ($page+1) . $searchString; } if ($commentsPerPage == COMMENTS_FILTER_ALL) { $limit = ''; } else { $limit = serendipity_db_limit_sql(serendipity_db_limit(($page-1)*(int)$commentsPerPage, (int)$commentsPerPage)); } // Variables for display if ($linkPrevious) { $linkPrevious = ' '. PREVIOUS .''; } else { $linkPrevious = ''. NO_ENTRIES_TO_PRINT .''; } if ($linkNext) { $linkNext = ''. NEXT .' '; } else { $linkNext = ''. NO_ENTRIES_TO_PRINT .''; } $paging = sprintf(PAGE_BROWSE_COMMENTS, $page, $pages, $totalVotes); // Retrieve the next batch of karma votes // [entryid, points, ip, user_agent, votetime] $sql = serendipity_db_query("SELECT l.entryid AS entryid, l.points AS points, l.ip AS ip, l.user_agent AS user_agent, l.votetime AS votetime, e.title AS title FROM {$serendipity['dbPrefix']}karmalog l LEFT JOIN {$serendipity['dbPrefix']}entries e ON (e.id = l.entryid) WHERE 1 = 1 " . $and . " ORDER BY $orderby $limit"); // Start the form for display and deleting if (is_array($sql)) { print("
    \n".serendipity_setFormToken()); // Start the vote table print("
    "); // Print the footer paging table print("
    "); if (is_array($sql)) { print("
    "); } } else { print(" No logs to display. You need to enable karma logging, if you want to see single votes displayed here. "); } return true; break; default: return false; }// End switch on event hooks } else { return false; } } /** * Check, if visit counting for the actual visitor should be done. */ function track_clicks_allowed_by_user(){ if (!$this->get_config('track_visits_of_loggedin_users',true) && serendipity_userLoggedIn()){ return false; } return true; } /** * Creates an HTML snippet with images of all the available rating bars. * * @return string an HTML string including working images of all the rating bars in the img/ directory. */ function createRatingSelector() { global $serendipity; // Since the inputs are set up with the proper names, the config item // gets saved automatically, with no need for magic // Get the filename to be automatically selected $this->set_valid_image_data(); $cursel = $this->image_name; $this->select_css = ''; $this->select_html = ''; // We will be wrapped in a $this->select_html .= " " . PLUGIN_KARMA_IMAGE . "
     ".PLUGIN_KARMA_IMAGE_DESC.""; if ($serendipity['version'][0] < 2) { $this->select_html .= " \n"; } $this->select_html .= " "; // Add the 'text-only' selection and its CSS if ($cursel == '0') { $checked = 'checked="checked" '; } else { $checked = ''; } $this->image_name = '0'; $bar = $this->createRatingBar('', 0, 0, 'textbar'); $this->select_html .= " \n"; $this->select_css .= " .textbar, .textbar a, .textbar a:hover { font-size: 100%; position: relative; background: none; } .serendipityAdminContent span.textbar { color: black !important; } "; // Retrieve all the *valid* images from the image directory $files = $this->getImageFiles(); // Add an
      for each rating bar, and add its CSS overrides $n = 0; foreach ($files as $fdata) { // Columnize if (($n % 3) == 0) { // Time to start a new row $this->select_html .= "
    \n\n"; } // Set the image data $fname = $fdata['fname']; $height = $fdata['height']; $width = $fdata['width']; $ratio = $width / $height; // If this is a single segment, adjust width if ($ratio < $this->max_segment_ratio) { $width = $width * 5; } $height = $height / 3; // Set up class variables correctly $this->image_name = $fname; $this->image_width = $width; $this->image_height = $height; // Create a rating bar of this image // // What would be a good CSS class for this image? $css_class = str_replace(array('.',' '), array('_','_'), $fname); $checked = ''; if ($fname == $cursel) { $checked = 'checked="checked" '; } $bar_html = "\n"; $bar_html = sprintf($bar_html, '', '2.5 of 5', '1'); $this->select_html .= $bar_html; // Add the necessary CSS to the stylesheet (will be added when css hooks are called) // Sorry to interrupt your regularly scheduled HTML; I need to // use the $css_class while it's still here. $this->select_css .= " /* Overrides for $css_class */ .$css_class { width: ${width}px; height: ${height}px; } .$css_class, .$css_class a:hover, .$css_class .serendipity_karmaVoting_current-rating { background-image: url({$serendipity['baseURL']}plugins/serendipity_event_karma/img/${fname}); } .$css_class, .$css_class a, .$css_class .serendipity_karmaVoting_current-rating { line-height: ${height}px; height: ${height}px; } "; $n++; } // Go back up for another image // Check for nothing displayed if ($n == 0) { // There were no images! $this->select_html .= "\n"; } // End the table, with a config-item bottom-border separator $this->select_html .= "\n
    " . PLUGIN_KARMA_STATISTICS_POINTS_NO . "
    $bar
    $fname
    \n" . $this->createRatingBar('', -1, 2, $css_class) . "
    " . PLUGIN_KARMA_NO_IMAGES . "
    \n"; if ($serendipity['version'][0] < 2) { $this->select_html .= " \n"; } // The config item and row are closed by the core code return $this->select_html; } /** * Retrieves all the images from the img/ directory * * @return array an array with 'fname', 'width', and 'height' entries for * each valid image file in the img/ directory */ function getImageFiles() { $path = dirname(__FILE__) . "/img"; $images = array(); $folder = opendir($path); while (false !== ($filename = readdir($folder))) { if ($filename != "ajax-loader.gif") { $parts = serendipity_parseFileName($filename); $img_data = serendipity_getimagesize($path . '/' . $filename); if (!isset($img_data['noimage'])) { // Curly braces are just a different syntax of associative array assignment $images{$filename} = array('fname'=>$filename, 'width'=>$img_data[0], 'height'=>$img_data[1]); } } } closedir($folder); ksort($images); return $images; } /** * Creates the HTML snippet for the currently defined rating bar, with * appropriate links (and current rating indication for graphical bars). * Automatically detects from $this->image_name whether to create text * links or a graphical bar. * * @param string id optional The ID of the bar we're creating; an empty * id ('' or 0) creates a bar with dummy links; null creates a bar with * no voting links at all (shows only current configuration). * @param int karma optional The current total karma (default: 0 points) * @param int votes optional The total number of votes (default: 0 votes)) * @param string extra_class optional Any additional CSS classes to be added to the bar container */ function createRatingBar($id = null, $karma = 0, $votes = 0, $extra_class = '') { // Are there enough votes to display the current rating? $enough_votes = ($votes >= $this->get_config('min_disp_votes', 0)); // Do I need to create links? if ($id !== null) { // Create the rating tooltips and texts $vilestr = $this->get_config('rate_vile', PLUGIN_KARMA_VOTEPOINT_1); $poorstr = $this->get_config('rate_poor', PLUGIN_KARMA_VOTEPOINT_2); $okaystr = $this->get_config('rate_okay', PLUGIN_KARMA_VOTEPOINT_3); $goodstr = $this->get_config('rate_good', PLUGIN_KARMA_VOTEPOINT_4); $beststr = $this->get_config('rate_best', PLUGIN_KARMA_VOTEPOINT_5); $vile = sprintf(PLUGIN_KARMA_RATE, $vilestr); $poor = sprintf(PLUGIN_KARMA_RATE, $poorstr); $okay = sprintf(PLUGIN_KARMA_RATE, $okaystr); $good = sprintf(PLUGIN_KARMA_RATE, $goodstr); $best = sprintf(PLUGIN_KARMA_RATE, $beststr); $vilestr = $this->get_config('text_vile', PLUGIN_KARMA_VOTETEXT_1); $poorstr = $this->get_config('text_poor', PLUGIN_KARMA_VOTETEXT_2); $okaystr = $this->get_config('text_okay', PLUGIN_KARMA_VOTETEXT_3); $goodstr = $this->get_config('text_good', PLUGIN_KARMA_VOTETEXT_4); $beststr = $this->get_config('text_best', PLUGIN_KARMA_VOTETEXT_5); if (empty($id)) { // Create dummy links $link_1 = '' . $vilestr . ''; $link_2 = '' . $poorstr . ''; $link_3 = '' . $okaystr . ''; $link_4 = '' . $goodstr . ''; $link_5 = '' . $beststr . ''; // There *are* enough votes for a dummy rating bar $enough_votes = true; } else { // Create ordinary links $link_1 = "$vilestr"; $link_2 = "$poorstr"; $link_3 = " $okaystr"; $link_4 = "$goodstr"; $link_5 = "$beststr"; } } // Normalize the CSS class if (!empty($extra_class)) { $extra_class = " $extra_class"; } // Create the rating bar if ($this->image_name == '0') { // Text bar // if ($id === null) { // No need for a "current rating" bar, like with graphics $karma_display = ''; } else { // Show the links $karma_display = " $link_1 | $link_2 | $link_3 | $link_4 | $link_5
    "; } } else { // Graphic bar // // No need to create a bar if there's nothing to display if (($id !== null) || $enough_votes) { // Start the bar $karma_display = "
      "; // Only display the current rating if there are enough votes if ($enough_votes) { // Figure out the image-based rating and width $rating = $this->imageRating($karma, $votes); $cr_width = ($rating * $this->image_width) / 5; // Get current karma text $curr_msg = $this->get_config('curr_msg', PLUGIN_KARMA_CURRENT); $karma_display .= "
    1. "; } // Only create voting links if required if ($id !== null) { $karma_display .= "
    2. $link_1
    3. $link_2
    4. $link_3
    5. $link_4
    6. $link_5
    7. "; } // Close the
        $karma_display .= "
      "; } } if (empty($karma_display)) { $karma_display = "[" . $karma . "]"; } return $karma_display; } /** * Sets $this->image_name to the name of a valid image, or to '0' to * indicate text-only rating bars should be used. */ function set_valid_image_data() { $base_image = $this->get_config('base_image', false); if ($base_image !== false) { // Definitely configured if ($base_image == '0') { // Configured to text-only $this->image_name = $base_image; } else { $imagesize = serendipity_getimagesize(dirname(__FILE__) . "/img/" . $base_image); if ($imagesize['noimage']) { // Leave as default } else { // Set to valid image name $this->image_name = $base_image; } } } // Is the (possibly default) image valid? if ($this->image_name) { $imagesize = serendipity_getimagesize(dirname(__FILE__) . "/img/" . $this->image_name); if ($imagesize['noimage']) { // No valid image; use text-only $this->image_name = '0'; } else { // Valid graphical image; set the dimensions, too $this->image_width = $imagesize[0]; $this->image_height = $imagesize[1]; } } } /** * Compute the equivalent graphical points rating for a karma rating. * * @param int points The total karma points * @param int votes The total number of votes * * @return string A string indicating the number of points "of 5", * or PLUGIN_KARMA_IMAGE_NONE_RATING if no votes have been recorded. */ function imageRating($points, $votes) { if ($votes == 0) { $rating = PLUGIN_KARMA_IMAGE_NONE_RATING; } else { // Find average karma (-2 to +2) $rating = ((float)$points) / ((float)$votes); // Remap into [1,5] $rating = $rating + 3; // Put it into the language-specific string format if ($rating == (int)$rating) { $rating = sprintf(PLUGIN_KARMA_IMAGE_INT_RATING, $rating); } else { $rating = sprintf(PLUGIN_KARMA_IMAGE_RATING, $rating); } } return $rating; } /* Compute the equivalent word rating for a karma rating. * * @param mixed points The total karma points * @param mixed votes The total number of votes * * @return string A word corresponding to the article rating, * or PLUGIN_KARMA_IMAGE_NONE_RATING if no votes have been recorded. */ function wordRating($points, $votes) { if ($votes == 0) { $rating = PLUGIN_KARMA_IMAGE_NONE_RATING; } else { // Find average karma (-2 to +2) $rating = ((float)$points) / ((float)$votes); // Put it into the language-specific string format, rounding up // I'm mapping invalid cases (rating > 5 and rating < -2) to the extremes if ($rating < -1.5) { $rating = $this->get_config('rate_vile', PLUGIN_KARMA_VOTEPOINT_1); } elseif ($rating < -0.5) { $rating = $this->get_config('rate_poor', PLUGIN_KARMA_VOTEPOINT_2); } elseif ($rating < 0.5) { $rating = $this->get_config('rate_okay', PLUGIN_KARMA_VOTEPOINT_3); } elseif ($rating < 1.5) { $rating = $this->get_config('rate_good', PLUGIN_KARMA_VOTEPOINT_4); } else { $rating = $this->get_config('rate_best', PLUGIN_KARMA_VOTEPOINT_5); } } return $rating; } } /* vim: set sts=4 ts=4 sw=4 expandtab : */