Changeset 1989517
- Timestamp:
- 12/08/2018 08:54:34 PM (7 years ago)
- Location:
- plugin-stats/trunk
- Files:
-
- 4 deleted
- 2 edited
-
COPYING (deleted)
-
plugin-stats.php (modified) (1 diff)
-
readme.txt (modified) (1 diff)
-
screenshot-1.jpg (deleted)
-
screenshot-2.jpg (deleted)
-
screenshot-3.jpg (deleted)
Legend:
- Unmodified
- Added
- Removed
-
plugin-stats/trunk/plugin-stats.php
r1988624 r1989517 1 1 <?php 2 /* 2 3 /** 4 * ----------------------------------------------------------------------------- 3 5 * Plugin Name: Plugin Stats 4 * Plugin URI: http://scompt.com/projects/plugin-stats 5 * Description: Plugin Stats provides a shortcode, template function, and dashboard widget which graphs the downloads completed for plugins hosted at WordPress.org. 6 * Author: Edward Dale 7 * Version: 1.1 8 * Author URI: http://scompt.com 6 * Description: Provides plugin developers a simple hub from which they can keep track of the stats on their own (or other developers') plugins. 7 * Version: 2.0 8 * Author: John Alarcon 9 * Author URI: https://johnalarcon.com 10 * Text Domain: plugin-stats 11 * Domain Path: /languages 12 * ----------------------------------------------------------------------------- 13 * This is free software released under the terms of the General Public License, 14 * version 2, or later. It is distributed WITHOUT ANY WARRANTY; without even the 15 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Full 16 * text of the license is available at https://www.gnu.org/licenses/gpl-2.0.txt. 17 * ----------------------------------------------------------------------------- 18 * A special thanks to Edward Dale (scompt.com) who graciously changed ownership 19 * of this plugin at the WordPress plugin repository, so it could be redeveloped 20 * into a new plugin. 21 * ----------------------------------------------------------------------------- 22 * Copyright © 2018 - John Alarcon 23 * ----------------------------------------------------------------------------- 9 24 */ 10 25 11 26 /** 12 * Plugin Stats provides a shortcode, template function, and dashboard widget 13 * which graphs the downloads completed for plugins hosted at WordPress.org. 27 * The primary plugin class. 14 28 * 15 * LICENSE 16 * This file is part of Plugin Stats. 29 * @author John Alarcon 17 30 * 18 * Plugin Stats is free software; you can redistribute it and/or19 * modify it under the terms of the GNU General Public License20 * as published by the Free Software Foundation; either version 221 * of the License, or (at your option) any later version.22 *23 * This program is distributed in the hope that it will be useful,24 * but WITHOUT ANY WARRANTY; without even the implied warranty of25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the26 * GNU General Public License for more details.27 *28 * You should have received a copy of the GNU General Public License29 * along with this program; if not, write to the Free Software30 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.31 *32 * @package Plugin Stats33 * @author Edward Dale <[email protected]>34 * @copyright Copyright 2008 Edward Dale35 * @license http://www.gnu.org/licenses/gpl.txt GPL 2.036 * @version $Id: immerstat.php 45292 2008-05-11 19:20:29Z scompt $37 * @link http://www.scompt.com/projects/plugin-stats38 * @since 1.039 31 */ 40 41 // Adding a comment, so this will refresh the listing.42 43 32 class PluginStats { 44 var $snoop; // For grabbing the xml from WordPress.org45 46 /**47 * Adds hooks for the shortcode, template function, and initialization48 */49 function PluginStats() {50 add_action('admin_init', array(&$this, 'init'));51 add_shortcode('plugin-stats', array(&$this, 'shortcode'));52 add_action('plugin-stats', array(&$this, 'template_function'));53 }54 55 /**56 * Adds dashboard filters and loads translations.57 */58 function init() {59 add_action('wp_dashboard_setup', array(&$this, 'wp_dashboard_setup'));60 add_filter( 'wp_dashboard_widgets', array(&$this, 'add_widget') );61 add_filter('dashwidman_safewidgets', array(&$this, 'add_safe_widget'));62 33 63 load_plugin_textdomain('plugin-stats', str_replace(ABSPATH, '', dirname(__FILE__))); 64 } 65 66 /** 67 * Action hook for the plugin-stats template function. 68 */ 69 function template_function($slugname='default', $size='360x100', $addlink='0') { 70 echo $this->shortcode(array('slugname'=>$slugname, 'size'=>$size, 'addlink'=>$addlink)); 71 } 72 73 /** 74 * Handler for the plugin-stats shortcode. 75 */ 76 function shortcode($atts) { 77 extract(shortcode_atts(array( 78 'slugname' => 'default', 79 'size' => '360x100', 80 'addlink' => '0', 81 ), $atts)); 34 /** 35 * Object instance. 36 * 37 * @var null|object Current or new instance of the object. 38 * 39 * @since 2.0.0 40 */ 41 private static $instance = null; 82 42 83 $slugname = preg_replace('/[^a-zA-Z0-9_-]/', '', $slugname); 84 if( $addlink ) add_filter('plugin-stats_img-link', array(&$this, 'add_link'), 10, 2); 85 $imgLink = $this->getImageLink($slugname, $size); 86 remove_filter('plugin-stats_img-link', array(&$this, 'add_link'), 10, 2); 87 88 return $imgLink; 89 } 43 /** 44 * Simple constructor. 45 * 46 * @author John Alarcon 47 * 48 * @since 2.0.0 49 */ 50 private function __construct() { 90 51 91 ///////////////////////// 92 // Dashboard functions // 93 ///////////////////////// 94 95 /** 96 * Register the widget and control function. 97 */ 98 function wp_dashboard_setup() { 99 wp_register_sidebar_widget( 'plugin-stats', __( 'Plugin Stats', 'plugin-stats'), 'wp_dashboard_empty', array(), array(&$this, 'widget_output') ); 100 wp_register_widget_control( 'plugin-stats', __( 'Plugin Stats', 'plugin-stats' ), array(&$this, 'widget_control'), array(), array( 'widget_id' => 'plugin-stats' ) ); 101 } 102 103 /** 104 * Handles the 'edit' side of the widget. 105 */ 106 function widget_control() { 107 global $user_ID; 52 $this->init(); 108 53 109 if ( 'POST' == $_SERVER['REQUEST_METHOD'] && isset($_POST['plugin-stats_slugs']) ) { 110 // sanitize the plugin names and save them 111 $slugs = $_POST['plugin-stats_slugs']; 112 $slugs = preg_replace('/[^a-zA-Z0-9_,-]/', '', $slugs); 113 update_usermeta($user_ID, 'plugin-stats_slugs', $slugs); 114 } else { 115 // display the plugin names 116 $slugs = get_usermeta($user_ID, 'plugin-stats_slugs'); 117 ?> 118 <p> 119 <label for="plugin-stats_slugs"><?php _e('Enter a comma-separated list of plugin slugs.', 'plugin-stats' ); ?> 120 <input id="plugin-stats_slugs" name="plugin-stats_slugs" type="text" value="<?php echo $slugs ?>" /> 121 </label> 122 </p> 123 <?php 124 } 125 } 126 127 /** 128 * Handles the output side of the widget 129 */ 130 function widget_output() { 131 global $user_ID; 54 } 132 55 133 // grab the plugins and split 'em, should already be sanitized 134 $slugs = get_usermeta($user_ID, 'plugin-stats_slugs'); 135 if( empty($slugs) ) { 136 _e('Thanks for installing the <a href="http://scompt.com/projects/plugin-stats">Plugin Stats</a> plugin! <a href="index.php?edit=plugin-stats#plugin-stats">Add some plugins</a> to start tracking your downloads in your dashboard.', 'plugin-stats'); 137 return; 138 } 139 $slugs = explode(',', $slugs); 140 141 // We want the dashboard output linked 142 add_filter('plugin-stats_img-link', array(&$this, 'add_link'), 10, 2); 56 /** 57 * Setup filters and actions. 58 * 59 * @author John Alarcon 60 * 61 * @since 2.0.0 62 */ 63 public function init() { 143 64 144 foreach( $slugs as $slug ) { 145 echo $this->getImageLink($slug, '360x100'); 146 } 147 } 148 149 /** 150 * Add widget to the current widgets. 151 */ 152 function add_widget( $widgets ) { 153 global $wp_registered_widgets; 154 array_splice( $widgets, 0, 0, 'plugin-stats' ); 155 return $widgets; 65 // Add a notice to the dashboard. 66 add_action('admin_notices', [$this, 'print_admin_notice']); 67 156 68 } 157 158 /** 159 * Mark the plugin as safe for http://wordpress.org/extend/plugins/dashboard-widget-manager/ 160 */ 161 function add_safe_widget($safe) { 162 $safe[]='plugin-stats'; 163 return $safe; 164 } 165 166 /** 167 * Makes a link to the stats page on WP.org out of the provided image link. 168 */ 169 function add_link($imgLink, $slugname) { 170 return "<a href='http://wordpress.org/extend/plugins/$slugname/stats/'>$imgLink</a>"; 69 70 /** 71 * Print admin notice. 72 * 73 * 74 * @author John Alarcon 75 * 76 * @since 2.0.0 77 */ 78 public function print_admin_notice() { 79 80 // Check if user has already been notified. 81 $notified = get_option('plugin_stats_notified', false); 82 83 // Already notified? Don't be a nag. 84 if ($notified) { 85 return; 86 } 87 88 // Contain and print the message. 89 echo '<div class="notice notice-error is-dismissible"><p>The <strong>Plugin Stats</strong> plugin is under redevelopment and will be re-released soon!</p><p>If you want to use the previous version, you can download it <a href="https://plugins.trac.wordpress.org/browser/plugin-stats/tags/1.1">here</a> – be sure not to update it afterward or you will be right back here!</p><p>This message will not be shown again.</p></div>'; 90 91 // Indicate user has been notified. 92 update_option('plugin_stats_notified', 1); 93 171 94 } 172 95 173 96 /** 174 * Builds an image link out of the stats that have already been retrieved 97 * Get current instance of this object, or create and return a new instance. 98 * 99 * @author John Alarcon 100 * 101 * @since 0.1.0 102 * 103 * @return object The current instance of the object. 175 104 */ 176 function getImageLink($slugname, $imgSize) { 177 global $wp_locale; 105 public final static function get_instance() { 178 106 179 $new_stats = apply_filters('plugin-stats_build-link', $this->fetchStats($slugname)); 180 $new_stats = true; 181 // Put out the processed data 182 $all_stats = get_option('plugin-stats'); 183 $plugin = $all_stats[$slugname]; 184 $processed = $plugin['processed']; 107 // Get current instance or create a new one. 108 if (self::$instance===null) { 109 self::$instance = new PluginStats; 110 } 185 111 186 if( !$new_stats && !empty($plugin["link_$imgSize"])) { 187 $chart = $plugin["link_$imgSize"]; 188 } else { 189 // Trim down to what we need 190 $size = min(count($processed['dates']), apply_filters('plugin-stats_num-days', 180)); 191 $dates = array_slice($processed['dates'], -$size); 192 $downloads = array_slice($processed['downloads'], -$size); 193 194 // Figure out where the month labels should go based on the first day of the month 195 $monthBegins = array(); 196 $labels = array(); 197 for ($i=0; $i < $size; $i++) { 198 $day = $dates[$i]; 199 if( preg_match('/\d{4}-\d{2}-01/', $day)) { 200 $monthBegins []= round((float)$i*100/$size,2); 201 $pieces = explode('-', $day); 202 $labels []= $wp_locale->get_month($pieces[1]); 203 } 204 } 205 // If no months have begun in the interval, stick the current month name in the middle 206 if( empty($labels) ) { 207 $labels[]= $wp_locale->get_month(date('m')); 208 $monthBegins[]= 50; 209 } 210 211 // Build all of the parameters for the Google Charts API 212 $data = $this->simpleEncode($downloads, $processed['max'], $processed['min']); 213 $chartArgs = array( 'chxr'=>"1,{$processed['min']},{$processed['max']}", 214 'chxp'=>'0,'.implode(',',$monthBegins), 'chxt'=>'x,y', 215 'chxl'=>'0:|'.implode('|', $labels), 'chs'=>$imgSize, 216 'cht'=>'lc', 'chd'=>$data, 'chtt'=>$slugname ); 217 218 if( $processed['max']!=$processed['min'] ) { 219 $current_ratio = round((float)((end($downloads)-$processed['min'])/($processed['max']-$processed['min'])),2); 220 $chartArgs['chm'] = "h,FF0000,0,$current_ratio,0.5"; 112 // Return the instance. 113 return self::$instance; 221 114 222 $avgDev = $this->averageDeviation($downloads); 223 $begin = round(max(0,($avgDev[0]-$avgDev[1]-$processed['min'])/($processed['max']-$processed['min'])),2); 224 $end = round(min(1,($avgDev[0]+$avgDev[1]-$processed['min'])/($processed['max']-$processed['min'])),2); 225 $chartArgs['chm'] .= "|r,99FF99,0,$begin,$end"; 226 } 115 } 227 116 228 // Build the image link, pass it through some filters, and return it229 $chartArgs = apply_filters('plugin-stats_img-link-args', $chartArgs, $slugname);230 $chart = add_query_arg($chartArgs, 'http://chart.apis.google.com/chart?');231 232 $plugin["link_$imgSize"] = $chart;233 $all_stats[$slugname] = $plugin;234 update_option('plugin-stats', $all_stats);235 }236 return apply_filters('plugin-stats_img-link', "<img src='$chart' />", $slugname);237 }238 239 /**240 * Fetches and processes the stats for $slugname241 */242 function fetchStats($slugname) {243 $allStats = get_option('plugin-stats');244 245 $cacheTime = apply_filters('plugin-stats_cache-time', 600); // 10 Minute cache246 if( !isset($allStats[$slugname]) || time() - $allStats[$slugname]['retrieved'] > $cacheTime ) {247 248 $oldStats = $allStats[$slugname];249 if( empty($oldStats) ) $oldStats = array();250 251 // 1. Get the stats252 $stats = $this->downloadStats($slugname);253 if( !$stats ) return false;254 255 // 2. Parse the stats256 $parser = new PluginStatsParser();257 $processed = $parser->parse($stats);258 259 // 3. Process the stats (store a max of 180 days)260 $processed['dates'] = array_slice($processed['dates'], -180);261 $processed['downloads'] = array_slice($processed['downloads'], -180);262 $processed['min'] = min($processed['downloads']);263 $processed['max'] = max($processed['downloads']);264 265 // 4. Store the stats266 $newStats = array('retrieved'=>time(), 'raw'=>$stats, 'processed'=>$processed);267 $allStats[$slugname] = array_merge($oldStats, $newStats);268 update_option('plugin-stats', $allStats);269 return true;270 }271 return false;272 }273 274 /**275 * Downloads and returns the stats for $slugname from WP.org276 */277 function downloadStats($slugname) {278 require_once(ABSPATH.WPINC.'/class-snoopy.php');279 if( is_null($this->snoop) ) $this->snoop = new Snoopy;280 $this->snoop->fetch("http://wordpress.org/extend/stats/plugin-xml.php?slug=$slugname");281 if( $this->snoop->status == '200' ) {282 return $this->snoop->results;283 }284 return false;285 }286 287 /**288 * Encodes an array of values using Google Charts API Simple Encoding289 * From: http://groups.google.com/group/google-chart-api/browse_thread/thread/7552496ccef00d96290 */291 function simpleEncode($values, $max = 61, $min = 0){292 $simple_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';293 $chardata = 's:';294 $delta = $max - $min;295 $size = strlen($simple_table)-1;296 297 // prevent a divide-by-zero if $max==$min298 if( $delta == 0 ) return $chardata.str_repeat('A', count($values));299 foreach($values as $k => $v){300 if($v >= $min && $v <= $max){301 $chardata .= $simple_table[round($size * ($v - $min) / $delta)];302 }else{303 $chardata .= '_';304 }305 }306 return $chardata;307 }308 309 /**310 * Calculates the average of all the elements in the array.311 */312 function average($array){313 if( empty($array) ) return 0;314 $sum = array_sum($array);315 $count = count($array);316 return $sum/$count;317 }318 319 /**320 * Calculates the average and standard deviation of all the elements in the array.321 */322 function averageDeviation($array){323 $avg = $this->average($array);324 foreach ($array as $value) {325 $variance[] = pow($value-$avg, 2);326 }327 $deviation = sqrt($this->average($variance));328 return array($avg, $deviation);329 }330 117 } 331 118 332 /** 333 * Parser for WordPress.org plugin stats 334 */ 335 class PluginStatsParser { 336 var $parserState; 337 var $parsed; 338 339 function parse($xml) { 340 $parser = xml_parser_create(); 341 xml_set_object($parser, $this); 342 xml_set_element_handler($parser, "tag_open", "tag_close"); 343 xml_set_character_data_handler($parser, "cdata"); 344 345 $this->parserState = 0; 346 $this->parsed = array('dates'=>array(), 'downloads'=>array()); 347 xml_parse($parser, trim($xml), true); 348 xml_parser_free($parser); 349 350 return $this->parsed; 351 } 352 353 /** 354 * Tag started, modify parser state 355 */ 356 function tag_open($parser, $tag, $attributes) { 357 if( $tag == 'ROW' ) { 358 $this->parserState+=3; 359 } else if( $tag == 'STRING' ) { 360 $this->parserState+=1; 361 } else if( $tag == 'NUMBER' ) { 362 $this->parserState+=2; 363 } 364 } 365 366 /** 367 * Got some data, save it if we're in the right state 368 */ 369 function cdata($parser, $cdata) { 370 if( $this->parserState == 4 ) { 371 $this->parsed['dates'] []= $cdata; 372 } else if( $this->parserState == 8) { 373 $this->parsed['downloads'] []= $cdata; 374 } 375 } 376 377 /** 378 * Tag ended, modify parser state 379 */ 380 function tag_close($parser, $tag) { 381 if( $tag == 'STRING' ) { 382 $this->parserState-=1; 383 } else if( $tag == 'NUMBER' ) { 384 $this->parserState-=2; 385 } 386 } 387 388 } 389 390 // Let's get this show on the road! 391 new PluginStats(); 392 ?> 119 PluginStats::get_instance(); -
plugin-stats/trunk/readme.txt
r1988621 r1989517 3 3 Donate link: https://johnalarcon.com/ 4 4 Tags: wordpress, admin, stats, plugin, developer, shortcode 5 Requires at least: 2.56 Tested up to: 2.5.17 Stable tag: 1.15 Requires at least: 4.5 6 Tested up to: 5.0 7 Stable tag: trunk 8 8 9 9 *After nearly a decade of inactivity, this plugin has been adopted! It is now being redeveloped for release soon!* 10 10 11 *Many thanks to Edward Dale who graciously granted new ownership to make this possible! *11 *Many thanks to Edward Dale who graciously granted new ownership to make this possible!*
Note: See TracChangeset
for help on using the changeset viewer.