{"id":615,"date":"2010-08-07T11:40:47","date_gmt":"2010-08-07T19:40:47","guid":{"rendered":"http:\/\/www.zackgrossbart.com\/hackito\/?p=615"},"modified":"2013-03-26T03:29:56","modified_gmt":"2013-03-26T11:29:56","slug":"edcal_stats","status":"publish","type":"post","link":"http:\/\/www.zackgrossbart.com\/hackito\/edcal_stats\/","title":{"rendered":"Collecting Statistics for The WordPress Editorial Calendar Plugin"},"content":{"rendered":"<p><a href=\"https:\/\/github.com\/zgrossbart\/edcalpepper\"><img decoding=\"async\" style=\"position: absolute; top: 0; right: 0; border: 0;\" src=\"http:\/\/s3.amazonaws.com\/github\/ribbons\/forkme_right_darkblue_121621.png\" alt=\"Fork me on GitHub\"><\/a><\/p>\n<p>Writing a WordPress plugin is inspiration, perspiration, testing, guess work, and frustration.  You spend hours making your plugin just right, but you never know if it is just right.  You hear from some of the happy users and none of the unhappy ones.  This guess work leads to assumptions we can&#8217;t back up.<\/p>\n<p><a href=\"http:\/\/www.zackgrossbart.com\/edcal\/mint\/\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_stats.png\" alt=\"\" title=\"Editorial Calendar Statistics\" width=\"256\" height=\"339\" class=\"alignright size-full wp-image-662\" srcset=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_stats.png 256w, http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_stats-226x300.png 226w\" sizes=\"(max-width: 256px) 100vw, 256px\" \/><\/a><\/p>\n<p>The team from <a href=\"http:\/\/stresslimitdesign.com\/\">Stresslimit Design<\/a> and I have been working on the <a href=\"\/hackito\/edcal\/\">WordPress Editorial Calendar<\/a> for months, but we have no real idea how people use it.  How many weeks are people looking at in the calendar?  How big are there screens?  How many posts do they have per day?  We need the answers to design the plugin, but we&#8217;re just fumbling through the dark.<\/p>\n<p>With version 0.9 of the calendar we&#8217;re trying to turn on the light.  <\/p>\n<h2>Why we&#8217;re collecting data<\/h2>\n<p>WordPress plugins go out into the wild and don&#8217;t phone home.  You get a simple <a href=\"http:\/\/wordpress.org\/extend\/plugins\/editorial-calendar\/stats\/\">plugin statistics page<\/a>, but never find out who&#8217;s using your plugin, how they&#8217;re using it, or even if they installed it after downloading.  As a privacy nut I like that, but it makes my life as a designer difficult.  <\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_9w.png\" alt=\"\" title=\"9 Week Editorial Calendar\" width=\"350\" height=\"373\" class=\"alignright size-full wp-image-637\" srcset=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_9w.png 350w, http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/edcal_9w-281x300.png 281w\" sizes=\"(max-width: 350px) 100vw, 350px\" \/><\/p>\n<p>The editorial calendar is getting close to version 1.0 and we&#8217;re trying to refine our interface.  Make those little tweaks that take it from good to great.  We started with a simple question, how many weeks should we show on the calendar by default?  I rarely post more than once a day and my 24-inch monitor stretches on like the Montana prairie so I want as many weeks as possible.  I could even go up to eight or nine.  <\/p>\n<p>Nine weeks is the perfect answer for me, but it isn&#8217;t the right answer.  Most users are stuck in Manhattan apartment sized screens and can&#8217;t see nine weeks with a magnifying glass.  Well&hellip; that&#8217;s what I think, but I don&#8217;t really know.  If I had real data on people&#8217;s window sizes I could make the right choice and make the calendar that much better.  <\/p>\n<p>That&#8217;s why we&#8217;re tracking data with this plugin.  To make it work better.  That&#8217;s most of the reason.  We also want to know how many people are really using it.  <\/p>\n<h2>Always make it opt-in<\/h2>\n<p>WordPress plugins have a gigantic amount of power.  They can delete your data, steal your passwords, and hack your blog.  When you install a plugin you&#8217;re saying <i>I trust this plugin to not be evil<\/i>.  That&#8217;s a lot of trust and the Editorial Calendar has to earn it.  <\/p>\n<p>We could collect data automatically, but that&#8217;s evil.  Instead we make all data collection opt-in.  Users can very clearly see everything we collect and have to click the button before we collect anything.  <\/p>\n<p><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/feedback.png\" alt=\"\" title=\"Editorial calendar feedback screen\" width=\"534\" height=\"161\" class=\"aligncenter size-full wp-image-627\" srcset=\"http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/feedback.png 534w, http:\/\/www.zackgrossbart.com\/hackito\/wp-content\/uploads\/2010\/08\/feedback-300x90.png 300w\" sizes=\"(max-width: 534px) 100vw, 534px\" \/><\/p>\n<h2>How it works<\/h2>\n<p>The data collection is based on <a href=\"http:\/\/www.haveamint.com\/\">Mint<\/a>.  I already used Mint to track statistics for <a href=\"http:\/\/www.zackgrossbart.com\">my own site<\/a>.  Mint is a more configurable and better looking competitor to <a href=\"http:\/\/www.google.com\/analytics\/\">Google Analytics<\/a>.  It also lets me hold onto my data instead of sending it to Google.  <\/p>\n<p>Normally Mint is part of your website with a simple declaration like <code>&lt;script src=\"\/mint\/?js\" type=\"text\/javascript\"&gt;&lt;\/script&gt;<\/code>.  This causes the Mint JavaScript to get downloaded, collect information about the browser, and call back to the server without visitors ever knowing.  <\/p>\n<div class=\"sourcebox\">\nGet the<br \/><a href=\"https:\/\/github.com\/zgrossbart\/edcalpepper\">Source Code<\/a>\n<\/div>\n<p>This type of website data collection is ubiquitous.  Every major site does it and the total volume of personal information they collect is a little scary.  My privacy rant is coming up in a little while.<\/p>\n<p>Mint made collecting data from the Editorial Calendar pretty easy.  It just takes a few lines of JavaScript to get Mint onto the Calendar page:<\/p>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">jQuery.getScript(<span class=\"code_string\">'http:\/\/www.zackgrossbart.com\/edcal\/mint\/?js'<\/span>, function() {\r\n    edcal.saveFeedbackPref();\r\n});\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>Now I can dynamically load the mint JavaScript files and save the preference of that when it&#8217;s done.  The dynamic loading is important because I don&#8217;t want to collect data every time and automatic data collection is way too scary.  Right now we prompt the user to let us collect data after they&#8217;ve loaded the calendar three times, and we never collect any data until we get permission.<\/p>\n<h2>Peppers<\/h2>\n<p>Mint collects data about how people came to your site like who they were referred by and what terms they searched for to get there.  It also collects information about the environment like what browser they&#8217;re running and if they have Flash.  I needed data more specific to the calendar like how many weeks are people seeing and how many posts do they have per day.  For that I needed to write a <a href=\"http:\/\/www.haveamint.com\/peppermill\/\">Pepper<\/a>.<\/p>\n<p>Peppers are the way you extend the capabilities of Mint.  I love puns.  They&#8217;re written in PHP and JavaScript and collect or display extra data.  There are Peppers to tell you how big someone&#8217;s browser window is or who has a secret crush on your website.  My Pepper collects data about the calendar.<\/p>\n<p>This Pepper is open source and like the rest of the code on this blog it is released under the <a href=\"http:\/\/www.apache.org\/licenses\/LICENSE-2.0\">Apache License, Version 2.0<\/a>.  That means you can copy it, change it, or use it for free.<\/p>\n<h3>Setting up our Pepper<\/h3>\n<p>The Editorial Calendar Stats Pepper starts with a PHP file called class.php and a big comment.<\/p>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">&lt;?php\r\n<span class=\"code_comment\">\/**************************************************\r\n Pepper\r\n \r\n Developer      : Zack Grossbart\r\n Plug-in Name   : Edcal Stats\r\n \r\n **************************************************\/\r\n<\/span>\r\n \r\n$installPepper = <span class=\"code_string\">\"SI_EdcalStats\"<\/span>;\r\n    \r\nclass SI_EdcalStats extends Pepper\r\n{\r\n    var $version    = 1; <span class=\"code_comment\">\/\/ Displays as 0.01<\/span>\r\n    var $info       = array\r\n    (\r\n        <span class=\"code_string\">'pepperName'<\/span>    =&gt; <span class=\"code_string\">'Editorial Calendar Stats'<\/span>,\r\n        <span class=\"code_string\">'pepperUrl'<\/span>     =&gt; <span class=\"code_string\">'http:\/\/www.zackgrossbart.com'<\/span>,\r\n        <span class=\"code_string\">'pepperDesc'<\/span>    =&gt; <span class=\"code_string\">'Editorial Calendar Pepper'<\/span>,\r\n        <span class=\"code_string\">'developerName'<\/span> =&gt; <span class=\"code_string\">'Zack Grossbart'<\/span>,\r\n        <span class=\"code_string\">'developerUrl'<\/span>  =&gt; <span class=\"code_string\">'http:\/\/www.zackgrossbart.com'<\/span>\r\n    );\r\n\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>The <code>$info<\/code> array sets up our Pepper and gives us the basics; a title and description.  With this class we define the functionality and feel of the Pepper using a special set of variables.  We start by defining the panes we&#8217;ll show on the <a href=\"http:\/\/www.zackgrossbart.com\/edcal\/mint\/\">statistics page<\/a>.<\/p>\n<h3>Defining our panes<\/h3>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">var $panes = array\r\n(\r\n    <span class=\"code_string\">'Editorial Calendar Stats'<\/span> =&gt; array\r\n    (\r\n        <span class=\"code_string\">'Stats'<\/span>\r\n    )\r\n);\r\n\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>The Editorial Calendar Stats Pepper just has one pane where it shows the <i>Stats<\/i> for the calendar plugin.  This makes our <code>$panes<\/code> array very simple.  <\/p>\n<h3>Collecting data<\/h3>\n<p>The next step is to define the data we want to collect and where we&#8217;ll store it in the database.<\/p>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">var $manifest = array<span class=\"footnote\"><a href=\"#edcalnote5\">1<\/a><\/span>\r\n(\r\n    <span class=\"code_string\">'visit'<\/span>    =&gt; array\r\n    (\r\n        <span class=\"code_string\">'edcal_weeks'<\/span> =&gt; <span class=\"code_string\">\"SMALLINT(5) NOT NULL DEFAULT '-1'\"<\/span>,\r\n        <span class=\"code_string\">'edcal_posts'<\/span> =&gt; <span class=\"code_string\">\"SMALLINT(5) NOT NULL DEFAULT '-1'\"<\/span>,\r\n        <span class=\"code_string\">'edcal_author'<\/span> =&gt; <span class=\"code_string\">\"TINYINT\"<\/span>\r\n    )\r\n);\r\n    \r\nfunction onRecord() \r\n{\r\n    $edcalWeeks =  $this->Mint->escapeSQL($_GET[<span class=\"code_string\">'edcal_weeks'<\/span>]);<span class=\"footnote\"><a href=\"#edcalnote6\">2<\/a><\/span>\r\n    $edcalPosts =  $this->Mint->escapeSQL($_GET[<span class=\"code_string\">'edcal_posts'<\/span>]);\r\n    $edcalAuthor =  $this->Mint->escapeSQL($_GET[<span class=\"code_string\">'edcal_author'<\/span>]);\r\n    \r\n    return array\r\n    (\r\n        <span class=\"code_string\">'edcal_weeks'<\/span> =&gt; (float) $edcalWeeks,\r\n        <span class=\"code_string\">'edcal_posts'<\/span> =&gt; (float) $edcalPosts,\r\n        <span class=\"code_string\">'edcal_author'<\/span> =&gt; (boolean) $edcalAuthor\r\n    );\r\n}\r\n\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>The <code>$manifest<\/code> array<span class=\"footnote\"><a name=\"edcalnote5\">1<\/a><\/span> defines the database columns we need.  Mint will create these database columns when this Pepper is installed.  We need columns for the number of visible weeks, the number of posts, and if they are showing authors.  <\/p>\n<p>We populate these fields with the <code>onRecord<\/code> function.  The values come on the URL so we access the data using the PHP <code>$_GET<\/code> function<span class=\"footnote\"><a name=\"edcalnote6\">2<\/a><\/span>.  <\/p>\n<h3>Querying the calendar with JavaScript<\/h3>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">function onJavaScript() \r\n{\r\n    $js = <span class=\"code_string\">\"pepper\/zackgrossbart\/edcalstats\/script.js\"<\/span><span class=\"footnote\"><a href=\"#edcalnote7\">1<\/a><\/span>;\r\n    if (file_exists($js))\r\n    {\r\n        include_once($js);\r\n    }\r\n}\r\n\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>We access configuration values in the calendar using JavaScript.  We tell Mint about our JavaScript file with the <code>onJavaScript<\/code> function<span class=\"footnote\"><a name=\"edcalnote7\">1<\/a><\/span>.  This adds the <code>script.js<\/code> file to our Mint request.  <\/p>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">Mint.SI.EdcalStats = \r\n{\r\n    onsave  : function() \r\n    {\r\n        if (edcal) {\r\n            \r\n            <span class=\"code_comment\">\/*\r\n             * Collect the number of weeks they are showing\r\n             *\/<\/span>\r\n            var val = '&edcal_weeks=' + edcal.weeksPref;<span class=\"footnote\"><a href=\"#edcalnote1\">1<\/a><\/span>\r\n            \r\n            \r\n            <span class=\"code_comment\">\/*\r\n             * If they are showing authors\r\n             *\/<\/span>\r\n            if (edcal.authorPref) {<span class=\"footnote\"><a href=\"#edcalnote2\">2<\/a><\/span>\r\n                val += <span class=\"code_string\">'&edcal_author=1'<\/span>;\r\n            } else {\r\n                val += <span class=\"code_string\">'&edcal_author=0'<\/span>;\r\n            }\r\n            \r\n            <span class=\"code_comment\">\/*\r\n             * Get the average number of posts they have\r\n             * per day\r\n             *\/<\/span>\r\n            var dayCounts = [];\r\n            \r\n            jQuery(\"#cal_cont .dayobj &gt; .postlist\").each(function() {\r\n                <span class=\"code_comment\">\/*\r\n                 * Don't consider days with zero posts\r\n                 *\/<\/span>\r\n                if (jQuery(this).children().length &gt; 0) {\r\n                    dayCounts.push(jQuery(this).children().length);<span class=\"footnote\"><a href=\"#edcalnote3\">3<\/a><\/span>\r\n                }\r\n            });\r\n            \r\n            var total = 0;\r\n            for (var i = 0; i &lt; dayCounts.length; i++) {\r\n                total += dayCounts[i];\r\n            }\r\n            \r\n            if (dayCounts.length &gt; 0) {\r\n                val += <span class=\"code_string\">'&edcal_posts='<\/span> + Math.round(total \/ dayCounts.length);\r\n            } else {\r\n                val += <span class=\"code_string\">'&edcal_posts=0'<\/span>;\r\n            }\r\n            \r\n            return val;\r\n                \r\n        }\r\n    }\r\n};\r\n\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>The Editorial Calendar is already running in our JavaScript memory space so calling it from <code>script.js<\/code> is ideal.  We can call the existing functions to collect the data we need.  The current number of weeks<span class=\"footnote\"><a name=\"edcalnote1\">1<\/a><\/span>, the authors preference<span class=\"footnote\"><a name=\"edcalnote2\">2<\/a><\/span>, and the average number of posts per day<span class=\"footnote\"><a name=\"edcalnote3\">3<\/a><\/span>.  JQuery makes it easy to iterate over the DOM.<\/p>\n<h3>Showing the data<\/h3>\n<p>Now that we&#8217;ve collected all of this data we need to show it on the <a href=\"http:\/\/www.zackgrossbart.com\/edcal\/mint\/\">Editorial Calendar Statistics<\/a> page.  For that we&#8217;ll implement the <code>onDisplay<\/code> function<\/p>\n<table width=\"750px\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n<tr>\n<td class=\"code-outline\">\n<pre class=\"displaycode\">function onDisplay($pane, $tab, $column = '', $sort = '')\r\n{\r\n$html = '';\r\n    switch($pane) \r\n    {\r\n        case <span class=\"code_string\">'Editorial Calendar Stats'<\/span>:\r\n        $html  = <span class=\"code_string\">'&lt;table cellspacing=\"0\" class=\"two-edcal-columns\"&gt;'<\/span>;\r\n        $html .= <span class=\"code_string\">\"\\r\\t&lt;tr&gt;\\r\"<\/span>;\r\n        $html .= <span class=\"code_string\">\"\\t\\t&lt;td style=\\\"padding-right: 4px;\\\" class=\\\"left\\\"&gt;\\r\"<\/span>;\r\n        $html .= $this-&gt;getHTML_EdcalWeeks();\r\n        $html .= <span class=\"code_string\">\"\\t\\t&lt;\/td&gt;\"<\/span>;\r\n        $html .= <span class=\"code_string\">\"\\t\\t&lt;td class=\\\"right\\\"&gt;\\r\"<\/span>;\r\n        $html .= $this-&gt;getHTML_EdcalPosts();\r\n        $html .= <span class=\"code_string\">\"&lt;br \/&gt;\"<\/span>;\r\n        $html .= $this-&gt;getHTML_EdcalAuthor();\r\n        $html .= <span class=\"code_string\">\"\\t\\t&lt;\/td&gt;<\/span>\";\r\n        $html .= <span class=\"code_string\">\"\\r\\t&lt;\/tr&gt;\\r<\/span>\";\r\n        $html .= <span class=\"code_string\">\"&lt;\/table&gt;\\r<\/span>\";\r\n        break;\r\n    }\r\n    \r\n    return $html;\r\n}\r\n<\/pre>\n<\/td>\n<\/tr>\n<\/table>\n<p>We show a little table for each piece of data we&#8217;ve collected and define the contents of that table in separate functions.  <\/p>\n<h2>What about privacy?<\/h2>\n<div class=\"sourcebox\">\nView the<br \/><a href=\"http:\/\/www.zackgrossbart.com\/edcal\/mint\">Stats Page<\/a>\n<\/div>\n<p>There is a constant tension between collecting data and preserving privacy.  I know we&#8217;re only using this data to improve the plugin, but would I trust a group of strangers to use the data?  Maybe.  <\/p>\n<p>This article is a how to, but it&#8217;s also about transparency.  I want to make it clear what data we&#8217;re collecting and how we&#8217;re using it.  <\/p>\n<p>We went out of our way to make sure the data collected by the calendar is innocuous and non-commercial.  We don&#8217;t sell the data.  Honestly, nobody would want to buy it.  <\/p>\n<p>The whole point is to make the calendar better.  The more we understand how it&#8217;s being used the better we can make the design.  The calendar should be simple and easy to use, but simple isn&#8217;t so simple.  Making something that feels good means we have to know how people use it and what feels good to them.  These statistics help us do just that.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Writing a WordPress plugin is inspiration, perspiration, testing, guess work, and frustration.  You spend hours making your plugin just right, but you never know if it is just right.  This post shows you the details of the new process we added to collect remote data about the <a href=\"\/hackito\/edcal\/\">WordPress Editorial Calendar Plugin<\/a>.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,20,23,15],"tags":[],"_links":{"self":[{"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/posts\/615"}],"collection":[{"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/comments?post=615"}],"version-history":[{"count":50,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/posts\/615\/revisions"}],"predecessor-version":[{"id":666,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/posts\/615\/revisions\/666"}],"wp:attachment":[{"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/media?parent=615"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/categories?post=615"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.zackgrossbart.com\/hackito\/wp-json\/wp\/v2\/tags?post=615"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}