diff --git a/data/css/tablesorter.css b/data/css/tablesorter.css
index 6c262d5679276ff81adfe4ac35f740a244903d3d..50ff913c13942e6a02761f6d4dd6bec2267151dc 100644
--- a/data/css/tablesorter.css
+++ b/data/css/tablesorter.css
@@ -1,15 +1,36 @@
 /* tables */
-table.tablesorter thead tr .header {
-/*  background-image: url(../images/tablesorter/bg.gif); */
-    cursor: pointer; 
-    background-repeat: no-repeat; 
-    background-position: center right; 
-    padding-right: 18px; 
+table.tablesorter {
+    width: 100%;
+    margin-left:auto;
+    margin-right:auto;
+    text-align:left;
 }
-table.tablesorter thead tr th, table.tablesorter tfoot tr th {
+table.tablesorter th {
     border: 1px solid #241109;
     padding: 3px;
-    font-weight:700;
+    border-collapse: collapse;
+    background-color: #333;
+    color: #fff;
+    text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
+    text-align:center;
+}
+table.tablesorter .header {
+/*  background-image: url(../images/tablesorter/bg.gif); */
+    background-repeat: no-repeat;
+    background-position: center right;
+    cursor: pointer;
+    padding-right: 18px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+table.tablesorter td {
+    color: #000;
+    padding: 4px;
+}
+table.sickbeardTable tfoot a {
+    color:#fff;
+    text-decoration: none;
 }
 /*
 table.tablesorter thead tr .headerSortUp {
@@ -22,11 +43,29 @@ table.tablesorter thead tr .headerSortDown {
 table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
     background-color: #57442B;
 }
-table.tablesorter tbody tr.even td {
-    background-color:#f5f1e4;
-    color: #000000;
+/* stickyheader widget */
+tr.tablesorter-stickyheader {
+    background-color: #fff;
+    padding: 2px 0;
 }
-table.tablesorter tbody tr.odd td {
+/* Zebra Widget - row alternating colors */
+tr.even {
     background-color:#dfdacf;
-    color: #000000;
-}
\ No newline at end of file
+}
+tr.odd {
+    background-color:#f5f1e4;
+}
+/* filter widget */
+tr.tablesorter-filter {
+    background-color: #eee;
+    text-align: center;
+}
+input.tablesorter-filter {
+    width: 100%; 
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+} 
+.tablesorter-filter .disabled {
+    display: none;
+}
diff --git a/data/interfaces/default/comingEpisodes.tmpl b/data/interfaces/default/comingEpisodes.tmpl
index 7c0818075ab94cb59f9baee8c11df83091248243..0f3c68169532f43d06aef077856d3ee3c5f7f7f6 100644
--- a/data/interfaces/default/comingEpisodes.tmpl
+++ b/data/interfaces/default/comingEpisodes.tmpl
@@ -25,26 +25,13 @@
 <script type="text/javascript" src="$sbRoot/js/plotTooltip.js"></script>
 <script type="text/javascript" charset="utf-8">
 <!--
-\$.tablesorter.addParser({ 
-    id: 'nextAirDate',
-    is: function(s) {
-        return false;
-    },
-    format: function(s) {
-        if (s == '')
-            return '9999-99-99';
-        else
-            return s; 
-    },
-    type: 'text'
-});
 
 \$.tablesorter.addParser({
     id: 'loadingNames',
     is: function(s) {
         return false;
     }, 
-    format: function(s) { 
+    format: function(s) {
         if (s.indexOf('Loading...') == 0)
             return s.replace('Loading...','!!!');
         else if (s.indexOf('The ') == 0)
@@ -62,48 +49,36 @@
     is: function(s) {
         return false;
     },
-    format: function(s) { 
-        return s.toLowerCase().replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); 
+    format: function(s) {
+        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); 
     },
     type: 'numeric'
 });
 
-//http://web.archive.org/web/20080801073104/http://www.jdempster.com/category/code/jquery/tablesortercookiewidget/
-\$.tablesorter.addWidget({
-  id: 'cookie',
-  format: function(table) {
-    var sortList = table.config.sortList;
-    var tablesorterCookieJar = \$.cookieJar('tablesorter');
-    if ( sortList.length > 0) {
-      tablesorterCookieJar.set(\$(table).attr('id'), sortList);
-    } else {
-      var sortList = tablesorterCookieJar.get(\$(table).attr('id'));
-      if (sortList && sortList.length > 0) {
-        jQuery(table).trigger('sorton', [sortList]);
-      }
-    }
-  }
-});
 
-\$(document).ready(function(){ 
+\$(document).ready(function(){
 
 #set $sort_codes = {'date': 0, 'show': 1, 'network': 4}
 #if $sort not in $sort_codes:
     $sort = 'date'
 #end if
-        sortList = [[$sort_codes[$sort],0]]; 
+        sortList = [[$sort_codes[$sort],0]];
 
     \$("#showListTable:has(tbody tr)").tablesorter({
+        widgets: ['stickyHeaders'],
         sortList: sortList,
+        textExtraction: {
+            5: function(node) { return \$(node).find("span").text().toLowerCase(); }
+        },
         headers: {
-            0: { sorter: 'nextAirDate' },
+            0: { sorter: 'isoDate' },
             1: { sorter: 'loadingNames' },
-            2: { sorter: false},
-            3: { sorter: false},
+            2: { sorter: false },
+            3: { sorter: false },
             4: { sorter: 'loadingNames' },
             5: { sorter: 'quality' },
-            6: { sorter: false},
-            7: { sorter: false}
+            6: { sorter: false },
+            7: { sorter: false }
         }
     });
 
diff --git a/data/interfaces/default/config_providers.tmpl b/data/interfaces/default/config_providers.tmpl
index 7ec47f65c61476cc0a837c2e33749ec620ddb17c..50b810a661bbf2f6cf71a2f077968bc3b28043c3 100755
--- a/data/interfaces/default/config_providers.tmpl
+++ b/data/interfaces/default/config_providers.tmpl
@@ -77,7 +77,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                                 <span class="component-title jumbo">Configure Provider:</span>
                                 <span class="component-desc">
                                     #set $provider_config_list = []
-                                    #for $cur_provider in ("nzbs_org", "nzbs_r_us", "newzbin", "nzbmatrix", "tvtorrents", "btn"):
+                                    #for $cur_provider in ("nzbs_r_us", "newzbin", "nzbmatrix", "tvtorrents", "btn"):
                                         #set $cur_provider_obj = $sickbeard.providers.getProviderClass($cur_provider)
                                         #if $cur_provider_obj.providerType == $GenericProvider.NZB and not $sickbeard.USE_NZBS:
                                             #continue
@@ -119,7 +119,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
 </div>
 #end for
 
-
+<!--
 <div class="providerDiv" id="nzbs_orgDiv">
                         <div class="field-pair">
                             <label class="clearfix">
@@ -134,7 +134,7 @@ var show_nzb_providers = #if $sickbeard.USE_NZBS then "true" else "false"#;
                             </label>
                         </div>
 </div>
-
+-->
 <div class="providerDiv" id="nzbmatrixDiv">
                         <div class="field-pair">
                             <label class="clearfix">
diff --git a/data/interfaces/default/history.tmpl b/data/interfaces/default/history.tmpl
index e063f6555a1a6c1b79fc3be16358ab52ed74bd23..ccf7d6ea0e89339054ed430aa5191df05fb6406f 100644
--- a/data/interfaces/default/history.tmpl
+++ b/data/interfaces/default/history.tmpl
@@ -18,8 +18,11 @@
 \$(document).ready(function() 
 { 
     \$("#historyTable:has(tbody tr)").tablesorter({
-        widgets: ['zebra'],
-        sortList: [[0,1]]
+        widgets: ['zebra', 'stickyHeaders', 'filter'],
+        sortList: [[0,1]],
+        textExtraction: {
+            4: function(node) { return \$(node).find("span").text().toLowerCase(); }
+        },
     });
     \$('#limit').change(function(){
         url = '$sbRoot/history/?limit='+\$(this).val()
@@ -44,7 +47,7 @@
 #for $hItem in $historyResults:
 #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($hItem["action"]))
   <tr>
-    <td>$datetime.datetime.strptime(str($hItem["date"]), $history.dateFormat)</td>
+    <td class="nowrap">$datetime.datetime.strptime(str($hItem["date"]), $history.dateFormat)</td>
     <td><a href="$sbRoot/home/displayShow?show=$hItem["showid"]#season-$hItem["season"]">$hItem["show_name"] - <%=str(hItem["season"]) +"x"+ "%02i" % int(hItem["episode"]) %></a></td>
     <td align="center">$statusStrings[$curStatus]</td>
     <td align="center">
diff --git a/data/interfaces/default/home.tmpl b/data/interfaces/default/home.tmpl
index 71db654688fc30112dfc3f3c2eb30e09801fa2c7..fef32c92dd6bc6d0ca0db6d44d5bafb8cfbe82bd 100644
--- a/data/interfaces/default/home.tmpl
+++ b/data/interfaces/default/home.tmpl
@@ -19,19 +19,6 @@
 
 <script type="text/javascript" charset="utf-8">
 <!--
-\$.tablesorter.addParser({ 
-    id: 'nextAirDate',
-    is: function(s) {
-        return false;
-    },
-    format: function(s) {
-        if (s == '')
-            return '9999-99-99';
-        else
-            return s;
-    },
-    type: 'text'
-});
 
 \$.tablesorter.addParser({
     id: 'loadingNames',
@@ -57,7 +44,7 @@
         return false;
     },
     format: function(s) { 
-        return s.toLowerCase().replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4);
+        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4);
     },
     type: 'numeric'
 });
@@ -68,7 +55,7 @@
         return false; 
     },
     format: function(s) {
-        match = s.match(/^\<\!\-\-(.*)\-\-\>/)
+        match = s.match(/^(.*)/);
 
         if (match == null || match[1] == "?")
             return -10;
@@ -84,54 +71,21 @@
     type: 'numeric'
 });
 
-\$.tablesorter.addParser({
-    id: 'active',
-    is: function(s) {
-        return false;
-    }, 
-    format: function(s) {
-        if (s == '')
-            return '~~';
-        else
-            return s;
-    },
-    type: 'text'
-});
-
-//http://web.archive.org/web/20080801073104/http://www.jdempster.com/category/code/jquery/tablesortercookiewidget/
-\$.tablesorter.addWidget({
-  id: 'cookie',
-  format: function(table) {
-    var sortList = table.config.sortList;
-    var tablesorterCookieJar = \$.cookieJar('tablesorter');
-    if ( sortList.length > 0) {
-      tablesorterCookieJar.set(\$(table).attr('id'), sortList);
-    } else {
-      var sortList = tablesorterCookieJar.get(\$(table).attr('id'));
-      if (sortList && sortList.length > 0) {
-        jQuery(table).trigger('sorton', [sortList]);
-      }
-    }
-  }
-});
- 
 \$(document).ready(function(){ 
 
-    // workaround: the tablesorter cookie widget does not support a defaulted sortList set within the $.tablesorter() instantiation
-    var tablesorterCookieJar = \$.cookieJar('tablesorter');
-    var sortList = tablesorterCookieJar.get('showListTable');
-    if (!sortList || !sortList.length)
-        sortList = [[5,1],[1,0]];   
-
     \$("#showListTable").tablesorter({
-        sortList: sortList,
-        widgets: ['cookie','zebra'],
+        sortList: [[5,1],[1,0]],
+        textExtraction: {
+            3: function(node) { return \$(node).find("span").text().toLowerCase(); },
+            4: function(node) { return \$(node).find("span").text(); },
+            5: function(node) { return \$(node).find("img").attr("alt"); }
+        },
+        widgets: ['saveSort', 'zebra', 'stickyHeaders'],
         headers: {
-            0: { sorter: 'nextAirDate' },
+            0: { sorter: 'isoDate' },
             1: { sorter: 'loadingNames' },
             3: { sorter: 'quality' },
-            4: { sorter: 'eps' },
-            5: { sorter: 'active' }
+            4: { sorter: 'eps' }
         }
     });
 
@@ -196,6 +150,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
   #set $den = 1
 #end if
 
+
   <tr>
     <td align="center" class="nowrap">#if len($curEp) != 0 then $curEp[0].airdate else ""#</td>
     <td class="tvShow"><a href="$sbRoot/home/displayShow?show=$curShow.tvdbid">$curShow.name</a></td>
@@ -205,7 +160,7 @@ $myShowList.sort(lambda x, y: cmp(x.name, y.name))
 #else:
     <td align="center"><span class="quality Custom">Custom</span></td>
 #end if
-    <td align="center"><!--$dlStat--><div id="progressbar$curShow.tvdbid" style="position:relative;"></div>
+    <td align="center"><span style="display: none;">$dlStat</span><div id="progressbar$curShow.tvdbid" style="position:relative;"></div>
         <script type="text/javascript">
         <!--
             \$(function() {
diff --git a/data/interfaces/default/inc_qualityChooser.tmpl b/data/interfaces/default/inc_qualityChooser.tmpl
index a1ef1589be73119c868424fce9613d5f2845fcf0..a4be61632ed322128445b56df2756acae9cdfd32 100644
--- a/data/interfaces/default/inc_qualityChooser.tmpl
+++ b/data/interfaces/default/inc_qualityChooser.tmpl
@@ -25,11 +25,11 @@
 <div id="customQuality">
 
 <div class="component-group-desc">
-    <p>One of the <b>Inital</b> quality selections must be obtained before Sick Beard will attempt to process the <b>Archive</b> selections.</p>
+    <p>One of the <b>Initial</b> quality selections must be obtained before Sick Beard will attempt to process the <b>Archive</b> selections.</p>
 </div>
 
 <div class="float-left" style="margin-left: 50px;">
-<h3 style="text-align: center;">Inital</h3>
+<h3 style="text-align: center;">Initial</h3>
 #set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings)
 <select id="anyQualities" name="anyQualities" multiple="multiple" size="$len($anyQualityList)">
 #for $curQuality in sorted($anyQualityList):
diff --git a/data/interfaces/default/inc_top.tmpl b/data/interfaces/default/inc_top.tmpl
index 239a55057df91fc7674b17076f7e1ede3d000fc1..a5232706e4595bb52fd6a5ee8af405eeefdee184 100644
--- a/data/interfaces/default/inc_top.tmpl
+++ b/data/interfaces/default/inc_top.tmpl
@@ -72,7 +72,8 @@ table.tablesorter thead tr .headerSortDown { background-image: url("$sbRoot/imag
     <script type="text/javascript" src="$sbRoot/js/jquery.cookiejar.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.json-2.2.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.selectboxes.min.js"></script>
-    <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter-2.0.3.min.js"></script>
+    <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter-2.1.6.min.js"></script>
+    <script type="text/javascript" src="$sbRoot/js/jquery.tablesorter.widgets.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.qtip-2011-11-14.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.pnotify-1.0.2.min.js"></script>
     <script type="text/javascript" src="$sbRoot/js/jquery.expand-1.3.8.js"></script>
diff --git a/data/interfaces/default/manage.tmpl b/data/interfaces/default/manage.tmpl
index 7bd6f6336c46893ecc2cd32bb952db6d066c2cd4..2685fec16d64b8be373bfee41e4fd572c6d47411 100644
--- a/data/interfaces/default/manage.tmpl
+++ b/data/interfaces/default/manage.tmpl
@@ -27,30 +27,31 @@
 });
 
 \$.tablesorter.addParser({
-    id: 'images',
+    id: 'quality',
     is: function(s) {
         return false;
-    }, 
-    format: function(s) {
-        if (s == '')
-            return '~~';
-        else
-            return s;
     },
-    type: 'text'
+    format: function(s) { 
+        return s.replace('hd',3).replace('sd',1).replace('any',0).replace('best',2).replace('custom',4); 
+    },
+    type: 'numeric'
 });
 
 \$(document).ready(function() 
 { 
     \$("#massUpdateTable:has(tbody tr)").tablesorter({
         sortList: [[2,0]],
-        widgets: ['zebra'],
+        textExtraction: {
+            1: function(node) { return \$(node).find("img").attr("alt"); },
+            3: function(node) { return \$(node).find("span").text().toLowerCase(); },
+            4: function(node) { return \$(node).find("img").attr("alt"); },
+            5: function(node) { return \$(node).find("img").attr("alt"); }
+        },
+        widgets: ['zebra', 'stickyHeaders'],
         headers: {
             0: { sorter: false},
-            1: { sorter: 'images'},
             2: { sorter: 'showNames'},
-            4: { sorter: 'images'},
-            5: { sorter: 'images'},
+            3: { sorter: 'quality'},
             7: { sorter: false},
             8: { sorter: false},
             9: { sorter: false},
@@ -83,8 +84,8 @@
   </thead>
 <tfoot>
   <tr>
-    <td rowspan="1" colspan="1" align="center"><input type="button" value="Edit Selected" id="submitMassEdit" /></td>
-    <td rowspan="1" colspan="10" align="right"><input type="button" value="Submit" id="submitMassUpdate" /></td>
+    <td rowspan="1" colspan="1" class="align-center alt"><input type="button" value="Edit Selected" id="submitMassEdit" /></td>
+    <td rowspan="1" colspan="10" class="align-right alt"><input type="button" value="Submit" id="submitMassUpdate" /></td>
   </tr>
 </tfoot>
   <tbody>
diff --git a/data/js/browser.js b/data/js/browser.js
index 47e26c27a78a3fa9c2f93b69f61bfc2930300ddb..d4b15f813800f344858e4ad78d67342e83b35f73 100644
--- a/data/js/browser.js
+++ b/data/js/browser.js
@@ -137,9 +137,13 @@
                 };
         }
 
-        var initialDir, path, callback = null;
-        // if the text field is empty and we're given a key then populate it with the last browsed value from a cookie
-        if (options.key && options.field.val().length === 0 && (path = $.cookie('fileBrowser-' + options.key))) {
+        var initialDir, path, callback, ls = false;
+        // if the text field is empty and we're given a key then populate it with the last browsed value from localStorage
+        try { ls = !!(localStorage.getItem); } catch (e) {}
+        if (ls && options.key) {
+            path = localStorage['fileBrowser-' + options.key];
+        }
+        if (options.key && options.field.val().length === 0 && (path)) {
             options.field.val(path);
         }
 
@@ -147,13 +151,14 @@
             // store the browsed path to the associated text field
             options.field.val(path);
 
-            // use a cookie to remember for next time
-            if (options.key) {
-                $.cookie('fileBrowser-' + options.key, path);
+            // use a localStorage to remember for next time -- no ie6/7
+            if (ls && options.key) {
+                localStorage['fileBrowser-' + options.key] = path;
             }
+
         };
 
-        initialDir = options.field.val() || (options.key && $.cookie('fileBrowser-' + options.key)) || '';
+        initialDir = options.field.val() || (options.key && path) || '';
 
         options = $.extend(options, {initialDir: initialDir});
 
diff --git a/data/js/displayShow.js b/data/js/displayShow.js
index c53c14bab94f76712b71e329456c16fbc5221b0c..af26f4772ecd5fce1642424406fd7e202fcd8cec 100644
--- a/data/js/displayShow.js
+++ b/data/js/displayShow.js
@@ -54,6 +54,30 @@ $(document).ready(function(){
         });
     });
 
+    var lastCheck = null;
+    $('.epCheck').click(function(event) {
+
+      if(!lastCheck || !event.shiftKey) {
+        lastCheck = this;
+        return;
+      }
+
+      var check = this;
+      var found = 0;
+
+      $('.epCheck').each(function() {
+        switch (found) {
+          case 2: return false;
+          case 1: this.checked = lastCheck.checked;
+        }
+
+        if (this == check || this == lastCheck)
+          found++;
+      });
+
+      lastClick = this;
+    });
+
     // selects all visible episode checkboxes.
     $('.seriesCheck').click(function(){
         $('.epCheck:visible').each(function(){
diff --git a/data/js/jquery.cookie.js b/data/js/jquery.cookie.js
index 79317f743963cac9d4d82dd3574d32e6d994995c..dc031611f2c0c3c9b70b7cfa9e317f5f17ccacd7 100644
--- a/data/js/jquery.cookie.js
+++ b/data/js/jquery.cookie.js
@@ -1,96 +1,47 @@
 /**
- * Cookie plugin
+ * jQuery Cookie plugin
  *
- * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
  * Dual licensed under the MIT and GPL licenses:
  * http://www.opensource.org/licenses/mit-license.php
  * http://www.gnu.org/licenses/gpl.html
  *
  */
+(function($) {
+    $.cookie = function(key, value, options) {
 
-/**
- * Create a cookie with the given name and value and other optional parameters.
- *
- * @example $.cookie('the_cookie', 'the_value');
- * @desc Set the value of a cookie.
- * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
- * @desc Create a cookie with all available options.
- * @example $.cookie('the_cookie', 'the_value');
- * @desc Create a session cookie.
- * @example $.cookie('the_cookie', null);
- * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
- *       used when the cookie was set.
- *
- * @param String name The name of the cookie.
- * @param String value The value of the cookie.
- * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
- * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
- *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
- *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
- *                             when the the browser exits.
- * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
- * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
- * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
- *                        require a secure protocol (like HTTPS).
- * @type undefined
- *
- * @name $.cookie
- * @cat Plugins/Cookie
- * @author Klaus Hartl/klaus.hartl@stilbuero.de
- */
+        // key and at least value given, set cookie...
+        if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
+            options = $.extend({}, options);
 
-/**
- * Get the value of a cookie with the given name.
- *
- * @example $.cookie('the_cookie');
- * @desc Get the value of a cookie.
- *
- * @param String name The name of the cookie.
- * @return The value of the cookie.
- * @type String
- *
- * @name $.cookie
- * @cat Plugins/Cookie
- * @author Klaus Hartl/klaus.hartl@stilbuero.de
- */
-jQuery.cookie = function(name, value, options) {
-    if (typeof value != 'undefined') { // name and value given, set cookie
-        options = options || {};
-        if (value === null) {
-            value = '';
-            options.expires = -1;
-        }
-        var expires = '';
-        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
-            var date;
-            if (typeof options.expires == 'number') {
-                date = new Date();
-                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
-            } else {
-                date = options.expires;
+            if (value === null || value === undefined) {
+                options.expires = -1;
             }
-            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
-        }
-        // CAUTION: Needed to parenthesize options.path and options.domain
-        // in the following expressions, otherwise they evaluate to undefined
-        // in the packed version for some reason...
-        var path = options.path ? '; path=' + (options.path) : '';
-        var domain = options.domain ? '; domain=' + (options.domain) : '';
-        var secure = options.secure ? '; secure' : '';
-        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
-    } else { // only name given, get cookie
-        var cookieValue = null;
-        if (document.cookie && document.cookie != '') {
-            var cookies = document.cookie.split(';');
-            for (var i = 0; i < cookies.length; i++) {
-                var cookie = jQuery.trim(cookies[i]);
-                // Does this cookie string begin with the name we want?
-                if (cookie.substring(0, name.length + 1) == (name + '=')) {
-                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                    break;
-                }
+
+            if (typeof options.expires === 'number') {
+                var days = options.expires, t = options.expires = new Date();
+                t.setDate(t.getDate() + days);
             }
+
+            value = String(value);
+
+            return (document.cookie = [
+                encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
+                options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+                options.path    ? '; path=' + options.path : '',
+                options.domain  ? '; domain=' + options.domain : '',
+                options.secure  ? '; secure' : ''
+            ].join(''));
+        }
+
+        // key and possibly options given, get cookie...
+        options = value || {};
+        var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
+
+        var pairs = document.cookie.split('; ');
+        for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
+            if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
         }
-        return cookieValue;
-    }
-};
+        return null;
+    };
+})(jQuery);
diff --git a/data/js/jquery.tablesorter-2.0.3.min.js b/data/js/jquery.tablesorter-2.0.3.min.js
deleted file mode 100644
index 88ae32e77804b90ab93ce9dd9ffa12aeb8b3a44e..0000000000000000000000000000000000000000
--- a/data/js/jquery.tablesorter-2.0.3.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-
-(function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,cells[i]);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,node){var l=parsers.length;for(var i=1;i<l;i++){if(parsers[i].is($.trim(getElementText(table.config,node)),table,node)){return parsers[i];}}return parsers[0];}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=table.tBodies[0].rows[i],cols=[];cache.row.push($(c));for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c.cells[j]),table,c.cells[j]));}cols.push(i);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){if(!node)return"";var t="";if(config.textExtraction=="simple"){if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){t=node.childNodes[0].innerHTML;}else{t=node.innerHTML;}}else{if(typeof(config.textExtraction)=="function"){t=config.textExtraction(node);}else{t=$(node).text();}}return t;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){rows.push(r[n[i][checkCell]]);if(!table.config.appender){var o=r[n[i][checkCell]];var l=o.length;for(var j=0;j<l;j++){tableBody[0].appendChild(o[j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false,tableHeadersRows=[];for(var i=0;i<table.tHead.rows.length;i++){tableHeadersRows[i]=0;};$tableHeaders=$("thead th",table);$tableHeaders.each(function(index){this.count=0;this.column=index;this.order=formatSortingOrder(table.config.sortInitialOrder);if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(!this.sortDisabled){$(this).addClass(table.config.cssHeader);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){i=(v.toLowerCase()=="desc")?1:0;}else{i=(v==(0||1))?v:0;}return i;}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(getCachedSortType(table.config.parsers,c)=="text")?((order==0)?"sortText":"sortTextDesc"):((order==0)?"sortNumeric":"sortNumericDesc");var e="e"+i;dynamicExp+="var "+e+" = "+s+"(a["+c+"],b["+c+"]); ";dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function sortText(a,b){return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){var DECIMAL='\\'+config.decimal;var exp='/(^[+]?0('+DECIMAL+'0+)?$)|(^([-+]?[1-9][0-9]*)$)|(^([-+]?((0?|[1-9][0-9]*)'+DECIMAL+'(0*[1-9][0-9]*)))$)|(^[-+]?[1-9]+[0-9]*'+DECIMAL+'0+$)/';return RegExp(exp).test($.trim(s));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[A?$a��?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9.]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}$("tr:visible",table.tBodies[0]).filter(':even').removeClass(table.config.widgetZebra.css[1]).addClass(table.config.widgetZebra.css[0]).end().filter(':odd').removeClass(table.config.widgetZebra.css[0]).addClass(table.config.widgetZebra.css[1]);if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file
diff --git a/data/js/jquery.tablesorter-2.1.6.min.js b/data/js/jquery.tablesorter-2.1.6.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d031709c5e4230bb05fd6da45abe237380581cb
--- /dev/null
+++ b/data/js/jquery.tablesorter-2.1.6.min.js
@@ -0,0 +1,6 @@
+/*!
+* TableSorter 2.1.6 - Client-side table sorting with ease!
+* Minified using http://dean.edwards.name/packer/
+* Copyright (c) 2007 Christian Bach
+*/
+!(function($){$.extend({tablesorter:new function(){this.version="2.1.6";var g=[],widgets=[],tbl;this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:false,sortReset:false,sortRestart:false,textExtraction:"simple",parsers:{},widgets:[],headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"mmddyyyy",usNumberFormat:true,onRenderHeader:null,selectorHeaders:'thead th',selectorRemove:"tr.remove-me",tableClass:'tablesorter',debug:false,widgetOptions:{zebra:["even","odd"]}};function log(s){if(typeof console!=="undefined"&&typeof console.log!=="undefined"){console.log(s)}else{alert(s)}}function benchmark(s,d){log(s+" ("+(new Date().getTime()-d.getTime())+"ms)")}this.benchmark=benchmark;this.hasInitialized=false;function getElementText(a,b,c){var d="",te=a.textExtraction;if(!b){return""}if(!a.supportsTextContent){a.supportsTextContent=b.textContent||false}if(te==="simple"){if(a.supportsTextContent){d=b.textContent}else{if(b.childNodes[0]&&b.childNodes[0].hasChildNodes()){d=b.childNodes[0].innerHTML}else{d=b.innerHTML}}}else{if(typeof(te)==="function"){d=te(b,tbl,c)}else if(typeof(te)==="object"&&te.hasOwnProperty(c)){d=te[c](b,tbl,c)}else{d=$(b).text()}}return d}function getParserById(a){var i,l=g.length;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===(a.toString()).toLowerCase()){return g[i]}}return false}function trimAndGetNodeText(a,b,c){return $.trim(getElementText(a,b,c))}function detectParserForColumn(a,b,c,d){var i,l=g.length,node=false,nodeValue='',keepLooking=true;while(nodeValue===''&&keepLooking){c++;if(b[c]){node=b[c].cells[d];nodeValue=trimAndGetNodeText(a.config,node,d);if(a.config.debug){log('Checking if value was empty on row '+c+', column:'+d+": "+nodeValue)}}else{keepLooking=false}}for(i=1;i<l;i++){if(g[i].is(nodeValue,a,node)){return g[i]}}return g[0]}function buildParserCache(a,b){if(a.tBodies.length===0){return}var c=a.tBodies[0].rows,list,cells,l,h,i,p,parsersDebug="";if(c[0]){list=[];cells=c[0].cells;l=cells.length;for(i=0;i<l;i++){p=false;h=$(b[i]);if($.metadata&&(h.metadata()&&h.metadata().sorter)){p=getParserById(h.metadata().sorter)}else if((a.config.headers[i]&&a.config.headers[i].sorter)){p=getParserById(a.config.headers[i].sorter)}else if(h.attr('class')&&h.attr('class').match('sorter-')){p=getParserById(h.attr('class').match(/sorter-(\w+)/)[1]||'')}if(!p){p=detectParserForColumn(a,c,-1,i)}if(a.config.debug){parsersDebug+="column:"+i+"; parser:"+p.id+"\n"}list.push(p)}}if(a.config.debug){log(parsersDebug)}return list}function buildCache(a){var b=a.tBodies[0],totalRows=(b&&b.rows.length)||0,totalCells=(b.rows[0]&&b.rows[0].cells.length)||0,g=a.config.parsers,cache={row:[],normalized:[]},t,i,j,c,cols,cacheTime;if(a.config.debug){cacheTime=new Date()}for(i=0;i<totalRows;++i){c=$(b.rows[i]);cols=[];if(c.hasClass(a.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue}cache.row.push(c);for(j=0;j<totalCells;++j){t=trimAndGetNodeText(a.config,c[0].cells[j],j);cols.push(g[j].format(t,a,c[0].cells[j],j))}cols.push(cache.normalized.length);cache.normalized.push(cols)}if(a.config.debug){benchmark("Building cache for "+totalRows+" rows",cacheTime)}a.config.cache=cache;return cache}function getWidgetById(a){var i,w,l=widgets.length;for(i=0;i<l;i++){w=widgets[i];if(w&&w.hasOwnProperty('id')&&w.id.toLowerCase()===a.toLowerCase()){return w}}}function applyWidget(a,b){var c=a.config.widgets,i,w,l=c.length;for(i=0;i<l;i++){w=getWidgetById(c[i]);if(w){if(b&&w.hasOwnProperty('init')){w.init(a,widgets,w)}else if(!b&&w.hasOwnProperty('format')){w.format(a)}}}}function appendToTable(a,b){var c=a.config,r=b.row,n=b.normalized,totalRows=n.length,checkCell=totalRows?(n[0].length-1):0,rows=[],i,j,l,pos,appendTime;if(c.debug){appendTime=new Date()}for(i=0;i<totalRows;i++){pos=n[i][checkCell];rows.push(r[pos]);if(!c.appender||!c.removeRows){l=r[pos].length;for(j=0;j<l;j++){a.tBodies[0].appendChild(r[pos][j])}}}if(c.appender){c.appender(a,rows)}if(c.debug){benchmark("Rebuilt table",appendTime)}applyWidget(a);$(a).trigger("sortEnd",a)}function computeTableHeaderCellIndexes(t){var a=[],lookup={},thead=t.getElementsByTagName('THEAD')[0],trs=thead.getElementsByTagName('TR'),i,j,k,l,c,cells,rowIndex,cellId,rowSpan,colSpan,firstAvailCol,matrixrow;for(i=0;i<trs.length;i++){cells=trs[i].cells;for(j=0;j<cells.length;j++){c=cells[j];rowIndex=c.parentNode.rowIndex;cellId=rowIndex+"-"+c.cellIndex;rowSpan=c.rowSpan||1;colSpan=c.colSpan||1;if(typeof(a[rowIndex])==="undefined"){a[rowIndex]=[]}for(k=0;k<a[rowIndex].length+1;k++){if(typeof(a[rowIndex][k])==="undefined"){firstAvailCol=k;break}}lookup[cellId]=firstAvailCol;for(k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(a[k])==="undefined"){a[k]=[]}matrixrow=a[k];for(l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x"}}}}return lookup}function formatSortingOrder(v){return(/^d/i.test(v)||v===1)}function checkHeaderMetadata(a){return(($.metadata)&&($(a).metadata().sorter===false))}function checkHeaderOptions(a,i){return((a.config.headers[i])&&(a.config.headers[i].sorter===false))}function checkHeaderLocked(a,i){if((a.config.headers[i])&&(a.config.headers[i].lockedOrder!==null)){return a.config.headers[i].lockedOrder}return false}function checkHeaderOrder(a,i){if((a.config.headers[i])&&(a.config.headers[i].sortInitialOrder)){return a.config.headers[i].sortInitialOrder}return a.config.sortInitialOrder}function buildHeaders(b){var d=($.metadata)?true:false,header_index=computeTableHeaderCellIndexes(b),$th,lock,time,$tableHeaders,c=b.config;c.headerList=[];if(c.debug){time=new Date()}$tableHeaders=$(c.selectorHeaders,b).wrapInner("<span/>").each(function(a){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(checkHeaderOrder(b,a))?[1,0,2]:[0,1,2];this.count=-1;if(checkHeaderMetadata(this)||checkHeaderOptions(b,a)||$(this).is('.sorter-false')){this.sortDisabled=true}this.lockedOrder=false;lock=checkHeaderLocked(b,a);if(typeof(lock)!=='undefined'&&lock!==false){this.order=this.lockedOrder=formatSortingOrder(lock)?[1,1,1]:[0,0,0]}if(!this.sortDisabled){$th=$(this).addClass(c.cssHeader);if(c.onRenderHeader){c.onRenderHeader.apply($th,[a])}}c.headerList[a]=this;$(this).parent().addClass('tablesorter-header')});if(c.debug){benchmark("Built headers",time);log($tableHeaders)}return $tableHeaders}function checkCellColSpan(a,b,d){var i,cell,arr=[],r=a.tHead.rows,c=r[d].cells;for(i=0;i<c.length;i++){cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(a,b,d++))}else{if(a.tHead.length===1||(cell.rowSpan>1||!r[d+1])){arr.push(cell)}}}return arr}function isValueInArray(v,a){var i,l=a.length;for(i=0;i<l;i++){if(a[i][0]===v){return true}}return false}function setHeadersCss(b,c,d){var h=[],i,l,css=[b.config.cssDesc,b.config.cssAsc];c.removeClass(css[0]).removeClass(css[1]);c.each(function(a){if(!this.sortDisabled){h[this.column]=$(this)}});l=d.length;for(i=0;i<l;i++){if(d[i][1]===2){continue}h[d[i][0]].addClass(css[d[i][1]])}}function fixColumnWidth(a,b){if(a.config.widthFixed){var c=$('<colgroup>');$("tr:first td",a.tBodies[0]).each(function(){c.append($('<col>').css('width',$(this).width()))});$(a).prepend(c)}}function updateHeaderSortCount(a,b){var i,s,o,c=a.config,l=b.length;for(i=0;i<l;i++){s=b[i];o=c.headerList[s[0]];o.count=(s[1]+1)%(c.sortReset?3:2)}}function getCachedSortType(a,i){return(a)?a[i].type:''}function multisort(a,b,d){var f="var sortWrapper = function(a,b) {",col,mx=0,dir=0,tc=a.config,lc=d.normalized.length,l=b.length,sortTime,i,j,c,s,e,order,orgOrderCol;if(tc.debug){sortTime=new Date()}for(i=0;i<l;i++){c=b[i][0];order=b[i][1];s=(getCachedSortType(tc.parsers,c)==="text")?((order===0)?"sortText":"sortTextDesc"):((order===0)?"sortNumeric":"sortNumericDesc");e="e"+i;if(/Numeric/.test(s)&&tc.headers[c]&&tc.headers[c].string){for(j=0;j<lc;j++){col=Math.abs(parseFloat(d.normalized[j][c]));mx=Math.max(mx,isNaN(col)?0:col)}dir=(tc.headers[c])?tc.string[tc.headers[c].string]||0:0}f+="var "+e+" = "+s+"(a["+c+"],b["+c+"],"+mx+","+dir+"); ";f+="if ("+e+") { return "+e+"; } ";f+="else { "}orgOrderCol=(d.normalized&&d.normalized[0])?d.normalized[0].length-1:0;f+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(i=0;i<l;i++){f+="}; "}f+="return 0; ";f+="}; ";eval(f);d.normalized.sort(sortWrapper);if(tc.debug){benchmark("Sorting on "+b.toString()+" and dir "+order+" time",sortTime)}return d}function sortText(a,b){if(a===''){return 1}if(b===''){return-1}if(a===b){return 0}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return a.localeCompare(b)}try{var c=0,ax,t,x=/^(\.)?\d/,L=Math.min(a.length,b.length)+1;while(c<L&&a.charAt(c)===b.charAt(c)&&x.test(b.substring(c))===false&&x.test(a.substring(c))===false){c++}a=a.substring(c);b=b.substring(c);if(x.test(a)||x.test(b)){if(x.test(a)===false){return(a)?1:-1}else if(x.test(b)===false){return(b)?-1:1}else{t=parseFloat(a)-parseFloat(b);if(t!==0){return t}else{t=a.search(/[^\.\d]/)}if(t===-1){t=b.search(/[^\.\d]/)}a=a.substring(t);b=b.substring(t)}}return(a>b)?1:-1}catch(er){return 0}}function sortTextDesc(a,b){if(a===''){return 1}if(b===''){return-1}if(a===b){return 0}if($.data(tbl[0],"tablesorter").sortLocaleCompare){return b.localeCompare(a)}return-sortText(a,b)}function getTextValue(a,b,d){if(b){var i,l=a.length,n=b+d;for(i=0;i<l;i++){n+=a.charCodeAt(i)}return d*n}return 0}function sortNumeric(a,b,c,d){if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return a-b}function sortNumericDesc(a,b,c,d){if(a===''){return 1}if(b===''){return-1}if(isNaN(a)){a=getTextValue(a,c,d)}if(isNaN(b)){b=getTextValue(b,c,d)}return b-a}this.construct=function(f){return this.each(function(){if(!this.tHead||this.tBodies.length===0){return}var d,$document,$headers,cache,config,shiftDown=0,sortOrder,totalRows,$cell,c,i,j,k,a,s,o;this.config={};c=config=$.extend(true,this.config,$.tablesorter.defaults,f);tbl=d=$(this).addClass(this.config.tableClass);$.data(this,"tablesorter",c);$headers=buildHeaders(this);c.parsers=buildParserCache(this,$headers);c.string={max:1,'max+':1,'max-':-1,none:0};cache=buildCache(this);fixColumnWidth(this);$headers.click(function(e){totalRows=(d[0].tBodies[0]&&d[0].tBodies[0].rows.length)||0;if(!this.sortDisabled){d.trigger("sortStart",tbl[0]);$cell=$(this);k=!e[c.sortMultiSortKey];this.count=(this.count+1)%(c.sortReset?3:2);if(c.sortRestart){i=this;$headers.each(function(){if(this!==i&&(k||!$(this).is('.'+c.cssDesc+',.'+c.cssAsc))){this.count=-1}})}i=this.column;if(k){c.sortList=[];if(c.sortForce!==null){a=c.sortForce;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}else{if(isValueInArray(i,c.sortList)){for(j=0;j<c.sortList.length;j++){s=c.sortList[j];o=c.headerList[s[0]];if(s[0]===i){s[1]=o.order[o.count];if(s[1]===2){c.sortList.splice(j,1);o.count=-1}}}}else{if(this.order[this.count]<2){c.sortList.push([i,this.order[this.count]])}}}if(c.sortAppend!==null){a=c.sortAppend;for(j=0;j<a.length;j++){if(a[j][0]!==i){c.sortList.push(a[j])}}}d.trigger("sortBegin",tbl[0]);setHeadersCss(d[0],$headers,c.sortList);appendToTable(d[0],multisort(d[0],c.sortList,cache));return false}}).mousedown(function(){if(c.cancelSelection){this.onselectstart=function(){return false};return false}});d.bind("update",function(){var t=this,c=t.config;$(c.selectorRemove,t.tBodies[0]).remove();t.config.parsers=buildParserCache(t,$headers);cache=buildCache(t);d.trigger("sorton",[t.config.sortList])}).bind("updateCell",function(e,a){var b=[(a.parentNode.rowIndex-1),a.cellIndex];cache.normalized[b[0]][b[1]]=c.parsers[b[1]].format(getElementText(c,a,b[1]),d,a,b[1]);c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("addRows",function(e,a){var i,rows=a.filter('tr').length,dat=[],l=a[0].cells.length;for(i=0;i<rows;i++){for(j=0;j<l;j++){dat[j]=c.parsers[j].format(getElementText(c,a[i].cells[j],j),d,a[i].cells[j],j)}dat.push(cache.row.length);cache.row.push([a[i]]);cache.normalized.push(dat);dat=[]}c.cache=cache;d.trigger("sorton",[c.sortList])}).bind("sorton",function(e,a){$(this).trigger("sortStart",tbl[0]);c.sortList=a;var b=c.sortList;updateHeaderSortCount(this,b);setHeadersCss(this,$headers,b);appendToTable(this,multisort(this,b,cache))}).bind("appendCache",function(){appendToTable(this,cache)}).bind("applyWidgetId",function(e,a){getWidgetById(a).format(this)}).bind("applyWidgets",function(){applyWidget(this)});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){c.sortList=$(this).metadata().sortlist}applyWidget(this,true);if(c.sortList.length>0){d.trigger("sorton",[c.sortList])}else{applyWidget(this)}this.hasInitialized=true})};this.addParser=function(b){var i,l=g.length,a=true;for(i=0;i<l;i++){if(g[i].id.toLowerCase()===b.id.toLowerCase()){a=false}}if(a){g.push(b)}};this.addWidget=function(a){widgets.push(a)};this.formatFloat=function(s){if(typeof(s)!=='string'){return s}if(tbl[0].config.usNumberFormat){s=s.replace(/,/g,'')}else{s=s.replace(/[\s|\.]/g,'').replace(/,/g,'.')}var i=parseFloat(s);return isNaN(i)?$.trim(s):i};this.isDigit=function(s){return(/^[\-+]?\d*$/).test($.trim(s.replace(/[,.'\s]/g,'')))};this.clearTableBody=function(a){$(a.tBodies[0]).empty()}}})();$.fn.extend({tablesorter:$.tablesorter.construct});var m=$.tablesorter;m.addParser({id:"text",is:function(s){return true},format:function(s){return $.trim(s.toLocaleLowerCase())},type:"text"});m.addParser({id:"digit",is:function(s){return $.tablesorter.isDigit(s)},format:function(s){return $.tablesorter.formatFloat(s)},type:"numeric"});m.addParser({id:"currency",is:function(s){return(/^[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]/).test(s)},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[^0-9,. \-]/g),""))},type:"numeric"});m.addParser({id:"ipAddress",is:function(s){return(/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/).test(s)},format:function(s){var i,item,a=s.split("."),r="",l=a.length;for(i=0;i<l;i++){item=a[i];if(item.length===2){r+="0"+item}else{r+=item}}return $.tablesorter.formatFloat(r)},type:"numeric"});m.addParser({id:"url",is:function(s){return(/^(https?|ftp|file):\/\/$/).test(s)},format:function(s){return $.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''))},type:"text"});m.addParser({id:"isoDate",is:function(s){return(/^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/).test(s)},format:function(s){return $.tablesorter.formatFloat((s!=="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"")},type:"numeric"});m.addParser({id:"percent",is:function(s){return(/\%$/).test($.trim(s))},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""))},type:"numeric"});m.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/))},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"shortDate",is:function(s){return(/\d{1,4}[\/\-\,\.\s+]\d{1,4}[\/\-\.\,\s+]\d{1,4}/).test(s)},format:function(s,a,b,d){var c=a.config,format=(c.headers&&c.headers[d])?c.headers[d].dateFormat||c.dateFormat:c.dateFormat;s=s.replace(/\s+/g," ").replace(/[\-|\.|\,|\s]/g,"/");if(format==="mmddyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$1/$2")}else if(format==="ddmmyyyy"){s=s.replace(/(\d{1,2})\/(\d{1,2})\/(\d{4})/,"$3/$2/$1")}else if(format==="yyyymmdd"){s=s.replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/,"$1/$2/$3")}return $.tablesorter.formatFloat(new Date(s).getTime())},type:"numeric"});m.addParser({id:"time",is:function(s){return(/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/).test(s)},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime())},type:"numeric"});m.addParser({id:"metadata",is:function(s){return false},format:function(s,a,b){var c=a.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(b).metadata()[p]},type:"numeric"});m.addWidget({id:"zebra",format:function(a){var b,row=0,even,time,c=a.config,child=c.cssChildRow,css=["even","odd"];css=c.widgetZebra&&c.hasOwnProperty('css')?c.widgetZebra.css:(c.widgetOptions&&c.widgetOptions.hasOwnProperty('zebra'))?c.widgetOptions.zebra:css;if(a.config.debug){time=new Date()}$("tr:visible",a.tBodies[0]).each(function(i){b=$(this);if(!b.hasClass(child)){row++}even=(row%2===0);b.removeClass(css[even?1:0]).addClass(css[even?0:1])});if(a.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time)}}})})(jQuery);
diff --git a/data/js/jquery.tablesorter.widgets.min.js b/data/js/jquery.tablesorter.widgets.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..01868eb0bfa54a9704f5227fc8f3ca7eaef5a907
--- /dev/null
+++ b/data/js/jquery.tablesorter.widgets.min.js
@@ -0,0 +1,10 @@
+/*! TableSorter 2.1 Widgets - updated 3/18/2012 */
+(function(b){
+b.tablesorter.storage=function(a,e,c){var d,g=!1;d={};var a=a.id||b(".tablesorter").index(b(a)),f=window.location.pathname;try{g=!!localStorage.getItem}catch(j){}c&&JSON&&JSON.hasOwnProperty("stringify")?(d[f]={},d[f][a]={},d[f][a]=c,g?localStorage[e]=JSON.stringify(d):(c=new Date,c.setTime(c.getTime()+31536E6),document.cookie=e+"="+JSON.stringify(d).replace(/\"/g,'"')+"; expires="+c.toGMTString()+"; path=/")):b.parseJSON&&(g?d=b.parseJSON(localStorage[e])||{}:(d=document.cookie.split(/[;\s|=]/), c=b.inArray(e,d)+1,d=0!==c?b.parseJSON(d[c])||{}:{}));return d&&d.hasOwnProperty(f)&&d[f].hasOwnProperty(a)?d[f][a]:{}};
+b.tablesorter.addWidget({id:"uitheme",format:function(a){var e,c,d,g,f,j=b(a),i=a.config,h=i.widgetOptions,k=["ui-icon-arrowthick-2-n-s","ui-icon-arrowthick-1-s","ui-icon-arrowthick-1-n"],k=i.widgetUitheme&&i.widgetUitheme.hasOwnProperty("css")?i.widgetUitheme.css||k:h&&h.hasOwnProperty("uitheme")?h.uitheme:k;d=k.join(" ");i.debug&&(e=new Date);j.hasClass("ui-theme")||(j.addClass("ui-widget ui-widget-content ui-corner-all ui-theme"), b.each(i.headerList,function(){b(this).addClass("ui-widget-header ui-corner-all ui-state-default").append('<span class="ui-icon"/>').wrapInner('<div class="tablesorter-inner"/>').hover(function(){b(this).addClass("ui-state-hover")},function(){b(this).removeClass("ui-state-hover")})}));b.each(i.headerList,function(a){g=b(this);if(this.sortDisabled)g.find("span.ui-icon").removeClass(d+" ui-icon");else{c=g.hasClass(i.cssAsc)?k[1]:g.hasClass(i.cssDesc)?k[2]:g.hasClass(i.cssHeader)?k[0]:"";f=j.hasClass("hasStickyHeaders")? j.find("tr."+h.stickyHeaders||"tablesorter-stickyheader").find("th").eq(a).add(g):g;f[c===k[0]?"removeClass":"addClass"]("ui-state-active").find("span.ui-icon").removeClass(d).addClass(c)}});i.debug&&b.tablesorter.benchmark("Applying uitheme widget",e)}});
+b.tablesorter.addWidget({id:"columns",format:function(a){var e,c,d,g,f=a.config,j=f.sortList,i=j.length,h=["primary","secondary","tertiary"],h=f.widgetColumns&&f.widgetColumns.hasOwnProperty("css")?f.widgetColumns.css||h:f.widgetOptions&&f.widgetOptions.hasOwnProperty("columns")? f.widgetOptions.columns||h:h;d=h.length-1;g=h.join(" ");f.debug&&(c=new Date);j&&j[0]?b("tr:visible",a.tBodies[0]).each(function(a){e=b(this).children().removeClass(g);e.eq(j[0][0]).addClass(h[0]);if(1<i)for(a=1;a<i;a++)e.eq(j[a][0]).addClass(h[a]||h[d])}):b("td",a.tBodies[0]).removeClass(g);f.debug&&b.tablesorter.benchmark("Applying Columns widget",c)}});
+b.tablesorter.addWidget({id:"filter",format:function(a){if(!b(a).hasClass("hasFilters")){var e,c,d,g,f,j,i,h=a.config,k=h.widgetOptions,l=k.filter_cssFilter|| "tablesorter-filter",m=h.headerList.length,n=b(a).addClass("hasFilters"),a='<tr class="'+l+'">',o;h.debug&&(o=new Date);for(e=0;e<m;e++)a+='<td><input type="search" data-col="'+e+'" class="'+l,a+=h.headers[e]&&h.headers[e].hasOwnProperty("filter")&&!1===h.headers[e].filter||b(h.headerList[e]).is(".filter-false")?' disabled" disabled':'"',a+="></td>";n.find("thead").append(a+="</tr>").find("input."+l).bind("keyup search",function(){c=n.find("thead").find("input."+l).map(function(){return(b(this).val()|| "").toLowerCase()}).get();""===c.join("")?n.find("tr").show():n.find("tbody").find("tr:not(."+h.cssChildRow+")").each(function(){d=!0;j=b(this).nextUntil("tr:not(."+h.cssChildRow+")");g=j.length&&(k&&k.hasOwnProperty("filter_childRows")&&"undefined"!==typeof k.filter_childRows?k.filter_childRows:1)?j.text():"";i=b(this).find("td");for(e=0;e<m;e++)f=b.inArray(c[e],(i.eq(e).text()+g).toLowerCase()),""!==c[e]&&(!k.filter_startsWith&&0<=f||k.filter_startsWith&&0===f)?d=d?!0:!1:""!==c[e]&&(d=!1);b(this)[d? "show":"hide"]();if(j.length)j[d?"show":"hide"]()});n.trigger("applyWidgets")});h.debug&&b.tablesorter.benchmark("Applying Filter widget",o)}}});
+b.tablesorter.addWidget({id:"stickyHeaders",format:function(a){if(!b(a).hasClass("hasStickyHeaders")){var e=b(a).addClass("hasStickyHeaders"),c=a.config.widgetOptions,d=b(window),g=b(a).find("thead"),f=g.find("tr").children(),j=c.stickyHeaders||"tablesorter-stickyheader",i=f.eq(0),h=2*parseInt(f.eq(0).css("border-left-width"),10),k=g.find("tr.tablesorter-header").clone().removeClass("tablesorter-header").addClass(j).css({width:g.outerWidth()+ h,position:"fixed",left:i.offset().left,marginLeft:-h,top:0,visibility:"hidden",zIndex:10}),l=k.children(),m;e.bind("sortEnd",function(a,c){var d=b(c).find("thead tr"),e=d.filter("."+j).children();d.filter(":not(."+j+")").children().each(function(a){e.eq(a).attr("class",b(this).attr("class"))})}).bind("pagerComplete",function(){d.resize()});f.each(function(a){var c=b(this);l.eq(a).width(c.width()).bind("click",function(b){c.trigger(b)}).bind("mousedown",function(){this.onselectstart=function(){return!1}; return!1})});g.prepend(k);d.scroll(function(){var b=i.offset(),a=d.scrollTop(),a=a>b.top&&a<b.top+e.find("tbody").height()?"visible":"hidden";k.css({left:b.left-d.scrollLeft(),visibility:a});a!==m&&(d.resize(),m=a)}).resize(function(){k.css({left:i.offset().left-d.scrollLeft(),width:g.outerWidth()+2*h});l.each(function(a){b(this).width(f.eq(a).width())})})}}});
+b.tablesorter.addWidget({id:"resizable",format:function(a){if(!b(a).hasClass("hasResizable")){b(a).addClass("hasResizable");var e,c,d=a.config, g=b(d.headerList).filter(":gt(0)"),f=0,j=null,i=null,h=function(){f=0;j=i=null;b(window).trigger("resize")};if(c=b.tablesorter.storage?b.tablesorter.storage(a,"tablesorter-resizable"):"")for(e in c)!isNaN(e)&&e<d.headerList.length&&b(d.headerList[e]).width(c[e]);g.each(function(){b(this).append('<div class="tablesorter-resizer" style="cursor:w-resize;position:absolute;height:100%;width:20px;left:-20px;top:0;z-index:1;"></div>').wrapInner('<div style="position:relative;height:100%;width:100%"></div>')}).bind("mousemove", function(a){if(0!==f&&j){var b=a.pageX-f;j.width()<-b||i&&i.width()<=b||(i.width(i.width()+b),f=a.pageX)}}).bind("mouseup",function(){c&&b.tablesorter.storage&&j&&(c[i.index()]=i.width(),b.tablesorter.storage(a,"tablesorter-resizable",c));h();return!1}).find(".tablesorter-resizer").bind("mousedown",function(a){j=b(a.target).closest("th");i=j.prev();f=a.pageX});b(a).find("thead").bind("mouseup mouseleave",function(){h()})}}});
+b.tablesorter.addWidget({id:"saveSort",init:function(a,b,c){c.format(a,!0)}, format:function(a,e){var c,d,g=a.config;c={sortList:g.sortList};g.debug&&(d=new Date);b(a).hasClass("hasSaveSort")?a.hasInitialized&&b.tablesorter.storage&&(b.tablesorter.storage(a,"tablesorter-savesort",c),g.debug&&b.tablesorter.benchmark("saveSort widget: Saving last sort: "+g.sortList,d)):(b(a).addClass("hasSaveSort"),c="",b.tablesorter.storage&&(c=(c=b.tablesorter.storage(a,"tablesorter-savesort"))&&c.hasOwnProperty("sortList")&&b.isArray(c.sortList)?c.sortList:"",g.debug&&b.tablesorter.benchmark("saveSort: Last sort loaded: "+ c,d)),e&&c&&0<c.length?g.sortList=c:a.hasInitialized&&c&&0<c.length&&b(a).trigger("sorton",[c]))}})
+})(jQuery);
\ No newline at end of file
diff --git a/data/js/massUpdate.js b/data/js/massUpdate.js
index 4fa241d00587311fdbba9b8e7fc6a4612b515896..e7524d3f954b2cca5f144925f0ecf1cd784e0276 100644
--- a/data/js/massUpdate.js
+++ b/data/js/massUpdate.js
@@ -75,5 +75,35 @@ $(document).ready(function(){
             this.checked = !this.checked 
     });
   });
+
+  ['.editCheck', '.updateCheck', '.refreshCheck', '.renameCheck', '.deleteCheck'].forEach(function(name) {
+    var lastCheck = null;
+
+    $(name).click(function(event) {
+
+      if(!lastCheck || !event.shiftKey) {
+        lastCheck = this;
+        return;
+      }
+
+      var check = this;
+      var found = 0;
+
+      $(name).each(function() {
+        switch (found) {
+          case 2: return false;
+          case 1: 
+            if (!this.disabled)
+              this.checked = lastCheck.checked;
+        }
+
+        if (this == check || this == lastCheck)
+          found++;
+      });
+
+      lastClick = this;
+    });
+
+  });
   
 });
diff --git a/lib/pygithub/github.py b/lib/pygithub/github.py
index bd9f40776dad1db40696c16fff73849d9452f784..c0bfad7780f8bf8a0a5b6cb75397c2dcc9efb59b 100644
--- a/lib/pygithub/github.py
+++ b/lib/pygithub/github.py
@@ -244,7 +244,7 @@ for __t in (t for t in globals().values() if hasattr(t, 'parses')):
 
 class BaseEndpoint(object):
 
-    BASE_URL = 'http://github.com/api/v2/xml/'
+    BASE_URL = 'https://github.com/api/v2/xml/'
 
     def __init__(self, user, token, fetcher):
         self.user = user
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index ce061acc28c63f6b0314fe2ea23444bea08e5149..cda6094e4d37cf2773bd8be7299b95c966bd744d 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -30,7 +30,7 @@ from threading import Lock
 
 # apparently py2exe won't build these unless they're imported somewhere
 from sickbeard import providers, metadata
-from providers import ezrss, tvtorrents, btn, nzbs_org, nzbmatrix, nzbsrus, newznab, womble, newzbin
+from providers import ezrss, tvtorrents, btn, nzbmatrix, nzbsrus, newznab, womble, newzbin, nzbs_org_old
 
 from sickbeard import searchCurrent, searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser
 from sickbeard import helpers, db, exceptions, show_queue, search_queue, scheduler
@@ -115,6 +115,7 @@ METADATA_MEDIABROWSER = None
 METADATA_PS3 = None
 METADATA_WDTV = None
 METADATA_TIVO = None
+METADATA_SYNOLOGY = None
 
 QUALITY_DEFAULT = None
 STATUS_DEFAULT = None
@@ -405,7 +406,7 @@ def initialize(consoleLogging=True):
                 USE_NOTIFO, NOTIFO_USERNAME, NOTIFO_APISECRET, NOTIFO_NOTIFY_ONDOWNLOAD, NOTIFO_NOTIFY_ONSNATCH, \
                 USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \
                 USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_SYNOINDEX, \
-                USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, metadata_provider_dict, \
+                USE_BANNER, USE_LISTVIEW, METADATA_XBMC, METADATA_MEDIABROWSER, METADATA_PS3, METADATA_SYNOLOGY, metadata_provider_dict, \
                 NEWZBIN, NEWZBIN_USERNAME, NEWZBIN_PASSWORD, GIT_PATH, MOVE_ASSOCIATED_FILES, \
                 COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS
 
@@ -553,7 +554,7 @@ def initialize(consoleLogging=True):
         NZBS = bool(check_setting_int(CFG, 'NZBs', 'nzbs', 0))
         NZBS_UID = check_setting_str(CFG, 'NZBs', 'nzbs_uid', '')
         NZBS_HASH = check_setting_str(CFG, 'NZBs', 'nzbs_hash', '')
-
+        
         NZBSRUS = bool(check_setting_int(CFG, 'NZBsRUS', 'nzbsrus', 0))
         NZBSRUS_UID = check_setting_str(CFG, 'NZBsRUS', 'nzbsrus_uid', '')
         NZBSRUS_HASH = check_setting_str(CFG, 'NZBsRUS', 'nzbsrus_hash', '')
@@ -706,12 +707,14 @@ def initialize(consoleLogging=True):
             METADATA_PS3 = check_setting_str(CFG, 'General', 'metadata_ps3', '0|0|0|0|0|0')
             METADATA_WDTV = check_setting_str(CFG, 'General', 'metadata_wdtv', '0|0|0|0|0|0')
             METADATA_TIVO = check_setting_str(CFG, 'General', 'metadata_tivo', '0|0|0|0|0|0')
-            
+            METADATA_SYNOLOGY = check_setting_str(CFG, 'General', 'metadata_synology', '0|0|0|0|0|0')
+
             for cur_metadata_tuple in [(METADATA_XBMC, metadata.xbmc),
                                        (METADATA_MEDIABROWSER, metadata.mediabrowser),
                                        (METADATA_PS3, metadata.ps3),
                                        (METADATA_WDTV, metadata.wdtv),
                                        (METADATA_TIVO, metadata.tivo),
+                                       (METADATA_SYNOLOGY, metadata.synology),
                                        ]:
 
                 (cur_metadata_config, cur_metadata_class) = cur_metadata_tuple
@@ -1036,6 +1039,7 @@ def save_config():
     new_config['General']['metadata_ps3'] = metadata_provider_dict['Sony PS3'].get_config()
     new_config['General']['metadata_wdtv'] = metadata_provider_dict['WDTV'].get_config()
     new_config['General']['metadata_tivo'] = metadata_provider_dict['TIVO'].get_config()
+    new_config['General']['metadata_synology'] = metadata_provider_dict['Synology'].get_config()
 
     new_config['General']['cache_dir'] = ACTUAL_CACHE_DIR if ACTUAL_CACHE_DIR else 'cache'
     new_config['General']['root_dirs'] = ROOT_DIRS if ROOT_DIRS else ''
diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py
index f44f8656223d4da6d8a1add99736a37af0edd5aa..e6c6cd89ea1a604c0910e1e8f0bc8644eb751f27 100644
--- a/sickbeard/name_parser/parser.py
+++ b/sickbeard/name_parser/parser.py
@@ -61,7 +61,7 @@ class NameParser(object):
             try:
                 cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE)
             except re.error, errormsg:
-                logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_regex.pattern))
+                logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern))
             else:
                 self.compiled_regexes.append((cur_pattern_name, cur_regex))
 
@@ -311,4 +311,4 @@ class ParseResult(object):
     air_by_date = property(_is_air_by_date)
 
 class InvalidNameException(Exception):
-    "The given name is not valid"
\ No newline at end of file
+    "The given name is not valid"
diff --git a/sickbeard/notifiers/boxcar.py b/sickbeard/notifiers/boxcar.py
index 3e1ac37e59ee0c5b5689e0869e80693d4a74377c..35a86942de89b5786e9e574138ce8b8f35a2d14b 100644
--- a/sickbeard/notifiers/boxcar.py
+++ b/sickbeard/notifiers/boxcar.py
@@ -34,38 +34,66 @@ class BoxcarNotifier:
         return self._sendBoxcar("This is a test notification from SickBeard", title, email)
 
     def _sendBoxcar(self, msg, title, email, subscribe=False):
+        """
+        Sends a boxcar notification to the address provided
+        
+        msg: The message to send (unicode)
+        title: The title of the message
+        email: The email address to send the message to (or to subscribe with)
+        subscribe: If true then instead of sending a message this function will send a subscription notification (optional, default is False)
+        
+        returns: True if the message succeeded, False otherwise
+        """
+        
+        # build up the URL and parameters
         msg = msg.strip()
         curUrl = API_URL
-        data = urllib.urlencode({
+
+        # if this is a subscription notification then act accordingly
+        if subscribe:
+            data = urllib.urlencode({'email': email})
+            curUrl = curUrl + "/subscribe"
+        
+        # for normal requests we need all these parameters
+        else:
+            data = urllib.urlencode({
                 'email': email,
                 'notification[from_screen_name]': title,
                 'notification[message]': msg.encode('utf-8'),
                 'notification[from_remote_service_id]': int(time.time())
                 })
-        if subscribe: # subscription notification
-            data = urllib.urlencode({'email': email})
-            curUrl = curUrl + "/subscribe"
 
-        req = urllib2.Request(curUrl)
+
+        # send the request to boxcar
         try:
+            req = urllib2.Request(curUrl)
             handle = urllib2.urlopen(req, data)
             handle.close()
+            
         except urllib2.URLError, e:
+            # if we get an error back that doesn't have an error code then who knows what's really happening
             if not hasattr(e, 'code'):
                 logger.log("Boxcar notification failed." + ex(e), logger.ERROR)
                 return False
             else:
                 logger.log("Boxcar notification failed. Error code: " + str(e.code), logger.WARNING)
 
-            if e.code == 404: #HTTP status 404 if the provided email address isn't a Boxcar user.
+            # HTTP status 404 if the provided email address isn't a Boxcar user.
+            if e.code == 404:
                 logger.log("Username is wrong/not a boxcar email. Boxcar will send an email to it", logger.WARNING)
                 return False
-            elif e.code == 401: #For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service.
-                if subscribe: #If the user has already added your service, we'll return an HTTP status code of 401.
+            
+            # For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service.
+            elif e.code == 401:
+                
+                # If the user has already added your service, we'll return an HTTP status code of 401.
+                if subscribe:
                     logger.log("Already subscribed to service", logger.ERROR)
                     # i dont know if this is true or false ... its neither but i also dont know how we got here in the first place
                     return False
-                else: #HTTP status 401 if the user doesn't have the service added
+                
+                #HTTP status 401 if the user doesn't have the service added
+                else:
                     subscribeNote = self._sendBoxcar(msg, title, email, True)
                     if subscribeNote:
                         logger.log("Subscription send", logger.DEBUG)
@@ -73,12 +101,14 @@ class BoxcarNotifier:
                     else:
                         logger.log("Subscription could not be send", logger.ERROR)
                         return False
-            elif e.code == 400: #If you receive an HTTP status code of 400, it is because you failed to send the proper parameters
+            
+            # If you receive an HTTP status code of 400, it is because you failed to send the proper parameters
+            elif e.code == 400:
                 logger.log("Wrong data send to boxcar", logger.ERROR)
                 return False
-        else:# 200
-            logger.log("Boxcar notification successful.", logger.DEBUG)
-            return True
+
+        logger.log("Boxcar notification successful.", logger.DEBUG)
+        return True
 
     def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]):
         if sickbeard.BOXCAR_NOTIFY_ONSNATCH:
@@ -89,11 +119,21 @@ class BoxcarNotifier:
         if sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD:
             self._notifyBoxcar(title, ep_name)
 
-    def _notifyBoxcar(self, title, message=None, username=None, force=False):
+    def _notifyBoxcar(self, title, message, username=None, force=False):
+        """
+        Sends a boxcar notification based on the provided info or SB config
+
+        title: The title of the notification to send
+        message: The message string to send
+        username: The username to send the notification to (optional, defaults to the username in the config)
+        force: If True then the notification will be sent even if Boxcar is disabled in the config
+        """
+
         if not sickbeard.USE_BOXCAR and not force:
             logger.log("Notification for Boxcar not enabled, skipping this notification", logger.DEBUG)
             return False
 
+        # if no username was given then use the one from the config
         if not username:
             username = sickbeard.BOXCAR_USERNAME
 
diff --git a/sickbeard/notifiers/nmj.py b/sickbeard/notifiers/nmj.py
index de074f048547feb3fe9dda939fe130852abb37ba..b84ae816cf55391ebffea04e0e91f0444a66c3a1 100644
--- a/sickbeard/notifiers/nmj.py
+++ b/sickbeard/notifiers/nmj.py
@@ -31,6 +31,15 @@ except ImportError:
 
 class NMJNotifier:
     def notify_settings(self, host):
+        """
+        Retrieves the settings from a NMJ/Popcorn hour
+        
+        host: The hostname/IP of the Popcorn Hour server
+        
+        Returns: True if the settings were retrieved successfully, False otherwise
+        """
+        
+        # establish a terminal session to the PC
         terminal = False
         try:
             terminal = telnetlib.Telnet(host)
@@ -38,6 +47,7 @@ class NMJNotifier:
             logger.log(u"Warning: unable to get a telnet session to %s" % (host), logger.ERROR)
             return False
 
+        # tell the terminal to output the necessary info to the screen so we can search it later
         logger.log(u"Connected to %s via telnet" % (host), logger.DEBUG)
         terminal.read_until("sh-3.00# ")
         terminal.write("cat /tmp/source\n")
@@ -49,6 +59,7 @@ class NMJNotifier:
         device = ""
         match = re.search(r"(.+\.db)\r\n?(.+)(?=sh-3.00# cat /tmp/netshare)", tnoutput)
 
+        # if we found the database in the terminal output then save that database to the config
         if match:
             database = match.group(1)
             device = match.group(2)
@@ -57,7 +68,8 @@ class NMJNotifier:
         else:
             logger.log(u"Could not get current NMJ database on %s, NMJ is probably not running!" % (host), logger.ERROR)
             return False
-            
+        
+        # if the device is a remote host then try to parse the mounting URL and save it to the config
         if device.startswith("NETWORK_SHARE/"):
             match = re.search(".*(?=\r\n?%s)" % (re.escape(device[14:])), tnoutput)
 
@@ -82,6 +94,17 @@ class NMJNotifier:
         return self._sendNMJ(host, database, mount)
 
     def _sendNMJ(self, host, database, mount=None):
+        """
+        Sends a NMJ update command to the specified machine
+        
+        host: The hostname/IP to send the request to (no port)
+        database: The database to send the requst to
+        mount: The mount URL to use (optional)
+        
+        Returns: True if the request succeeded, False otherwise
+        """
+        
+        # if a mount URL is provided then attempt to open a handle to that URL
         if mount:
             try:
                 req = urllib2.Request(mount)
@@ -91,6 +114,7 @@ class NMJNotifier:
                 logger.log(u"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e))
                 return False
 
+        # build up the request URL and parameters
         UPDATE_URL = "http://%(host)s:8008/metadata_database?%(params)s"
         params = {
             "arg0": "scanner_start",
@@ -100,6 +124,7 @@ class NMJNotifier:
         params = urllib.urlencode(params)
         updateUrl = UPDATE_URL % {"host": host, "params": params}
 
+        # send the request to the server
         try:
             req = urllib2.Request(updateUrl)
             logger.log(u"Sending NMJ scan update command via url: %s" % (updateUrl), logger.DEBUG)
@@ -109,6 +134,7 @@ class NMJNotifier:
             logger.log(u"Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e))
             return False
 
+        # try to parse the resulting XML
         try:
             et = etree.fromstring(response)
             result = et.findtext("returnValue")
@@ -116,6 +142,7 @@ class NMJNotifier:
             logger.log(u"Unable to parse XML returned from the Popcorn Hour: %s" % (e), logger.ERROR)
             return False
         
+        # if the result was a number then consider that an error
         if int(result) > 0:
             logger.log(u"Popcorn Hour returned an errorcode: %s" % (result))
             return False
@@ -124,10 +151,19 @@ class NMJNotifier:
             return True
 
     def _notifyNMJ(self, host=None, database=None, mount=None, force=False):
+        """
+        Sends a NMJ update command based on the SB config settings
+        
+        host: The host to send the command to (optional, defaults to the host in the config)
+        database: The database to use (optional, defaults to the database in the config)
+        mount: The mount URL (optional, defaults to the mount URL in the config)
+        force: If True then the notification will be sent even if NMJ is disabled in the config
+        """
         if not sickbeard.USE_NMJ and not force:
             logger.log("Notification for NMJ scan update not enabled, skipping this notification", logger.DEBUG)
             return False
 
+        # fill in omitted parameters
         if not host:
             host = sickbeard.NMJ_HOST
         if not database:
diff --git a/sickbeard/notifiers/notifo.py b/sickbeard/notifiers/notifo.py
index 59c0d1ee2032f615e011f78ee7641be69cc213ed..11337e0701be9e742c310258030177c0f019c5a7 100644
--- a/sickbeard/notifiers/notifo.py
+++ b/sickbeard/notifiers/notifo.py
@@ -37,7 +37,22 @@ class NotifoNotifier:
         return self._sendNotifo("This is a test notification from SickBeard", title, username, apisecret)
 
     def _sendNotifo(self, msg, title, username, apisecret, label="SickBeard"):
+        """
+        Sends a message to notify using the given authentication information
+        
+        msg: The string to send to notifo
+        title: The title of the message
+        username: The username to send it to
+        apisecret: The API key for the username
+        label: The label to use for the message (optional)
+        
+        Returns: True if the message was delivered, False otherwise
+        """
+
+        # tidy up the message
         msg = msg.strip()
+        
+        # build up the URL and parameters
         apiurl = API_URL % {"username": username, "secret": apisecret}
         data = urllib.urlencode({
             "title": title,
@@ -45,18 +60,22 @@ class NotifoNotifier:
             "msg": msg.encode(sickbeard.SYS_ENCODING)
         })
 
+        # send the request to notifo
         try:
             data = urllib.urlopen(apiurl, data)    
             result = json.load(data)
+
         except ValueError, e:
             logger.log(u"Unable to decode JSON: "+data, logger.ERROR)
             return False
+        
         except IOError, e:
             logger.log(u"Error trying to communicate with notifo: "+ex(e), logger.ERROR)
             return False
         
         data.close()
 
+        # see if it worked
         if result["status"] != "success" or result["response_message"] != "OK":
             return False
         else:
@@ -64,14 +83,37 @@ class NotifoNotifier:
 
 
     def notify_snatch(self, ep_name, title="Snatched:"):
+        """
+        Send a notification that an episode was snatched
+        
+        ep_name: The name of the episode that was snatched
+        title: The title of the notification (optional)
+        """
         if sickbeard.NOTIFO_NOTIFY_ONSNATCH:
             self._notifyNotifo(title, ep_name)
 
     def notify_download(self, ep_name, title="Completed:"):
+        """
+        Send a notification that an episode was downloaded
+        
+        ep_name: The name of the episode that was downloaded
+        title: The title of the notification (optional)
+        """
         if sickbeard.NOTIFO_NOTIFY_ONDOWNLOAD:
             self._notifyNotifo(title, ep_name)       
 
-    def _notifyNotifo(self, title, message=None, username=None, apisecret=None, force=False):
+    def _notifyNotifo(self, title, message, username=None, apisecret=None, force=False):
+        """
+        Send a notifo notification based on the SB settings.
+        
+        title: The title to send
+        message: The message to send
+        username: The username to send it to (optional, default to the username in the config)
+        apisecret: The API key to use (optional, defaults to the api key in the config)
+        force: If true then the notification will be sent even if it is disabled in the config (optional)
+        
+        Returns: True if the message succeeded, false otherwise
+        """
         if not sickbeard.USE_NOTIFO and not force:
             logger.log("Notification for Notifo not enabled, skipping this notification", logger.DEBUG)
             return False
diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py
index aaea6116c27bdcfeed7977e88f6eaf7ea653dd1a..d5bcb6515071b12bc900690bea5748b66d22b53f 100644
--- a/sickbeard/notifiers/trakt.py
+++ b/sickbeard/notifiers/trakt.py
@@ -30,12 +30,10 @@ import sickbeard
 
 from sickbeard import logger
 
-try:
-    import xml.etree.cElementTree as etree
-except ImportError:
-    import xml.etree.ElementTree as etree #@UnusedImport
-
 class TraktNotifier:
+    """
+    A "notifier" for trakt.tv which keeps track of what has and hasn't been added to your library.
+    """
 
     def notify_snatch(self, ep_name):
         pass
@@ -44,10 +42,17 @@ class TraktNotifier:
         pass
 
     def update_library(self, ep_obj):
+        """
+        Sends a request to trakt indicating that the given episode is part of our library.
+        
+        ep_obj: The TVEpisode object to add to trakt
+        """
+        
         if sickbeard.USE_TRAKT:
             method = "show/episode/library/"
             method += "%API%"
             
+            # URL parameters
             data = {
                 'tvdb_id': ep_obj.show.tvdbid,
                 'title': ep_obj.show.name,
@@ -62,6 +67,17 @@ class TraktNotifier:
                 self._notifyTrakt(method, None, None, None, data)
 
     def test_notify(self, api, username, password):
+        """
+        Sends a test notification to trakt with the given authentication info and returns a boolean
+        representing success.
+        
+        api: The api string to use
+        username: The username to use
+        password: The password to use
+        
+        Returns: True if the request succeeded, False otherwise
+        """
+        
         method = "account/test/"
         method += "%API%"
         return self._notifyTrakt(method, api, username, password, {})
@@ -79,23 +95,42 @@ class TraktNotifier:
         return sickbeard.USE_TRAKT
 
     def _notifyTrakt(self, method, api, username, password, data = {}):
+        """
+        A generic method for communicating with trakt. Uses the method and data provided along
+        with the auth info to send the command.
+        
+        method: The URL to use at trakt, relative, no leading slash.
+        api: The API string to provide to trakt
+        username: The username to use when logging in
+        password: The unencrypted password to use when logging in
+        
+        Returns: A boolean representing success
+        """
         logger.log("trakt_notifier: Call method " + method, logger.DEBUG)
 
+        # if the API isn't given then use the config API
         if not api:
             api = self._api()
+
+        # if the username isn't given then use the config username
         if not username:
             username = self._username()
+        
+        # if the password isn't given then use the config password
         if not password:
             password = self._password()
         password = sha1(password).hexdigest()
 
+        # replace the API string with what we found
         method = method.replace("%API%", api)
 
         data["username"] = username
         data["password"] = password
 
+        # take the URL params and make a json object out of them
         encoded_data = json.dumps(data);
 
+        # request the URL from trakt and parse the result as json
         try:
             logger.log("trakt_notifier: Calling method http://api.trakt.tv/" + method + ", with data" + encoded_data, logger.DEBUG)
             stream = urllib2.urlopen("http://api.trakt.tv/" + method, encoded_data)
@@ -105,6 +140,7 @@ class TraktNotifier:
             
             if ("error" in resp):
                 raise Exception(resp["error"])
+
         except (IOError):
             logger.log("trakt_notifier: Failed calling method", logger.ERROR)
             return False
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 0d7c8c1f34e5ad8e76c1d1765466b722a133392c..7539779df6faa901867ef4106663f6374067a91b 100755
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -45,6 +45,9 @@ from sickbeard.name_parser.parser import NameParser, InvalidNameException
 from lib.tvdb_api import tvdb_api, tvdb_exceptions
 
 class PostProcessor(object):
+    """
+    A class which will process a media file according to the post processing settings in the config.
+    """
 
     EXISTS_LARGER = 1
     EXISTS_SAME = 2
@@ -52,6 +55,12 @@ class PostProcessor(object):
     DOESNT_EXIST = 4
 
     def __init__(self, file_path, nzb_name = None):
+        """
+        Creates a new post processor with the given file path and optionally an NZB name.
+        
+        file_path: The path to the file to be processed
+        nzb_name: The name of the NZB which resulted in this file being downloaded (optional)
+        """
         # absolute path to the folder that is being processed
         self.folder_path = ek.ek(os.path.dirname, ek.ek(os.path.abspath, file_path))
         
@@ -74,10 +83,28 @@ class PostProcessor(object):
         self.log = ''
     
     def _log(self, message, level=logger.MESSAGE):
+        """
+        A wrapper for the internal logger which also keeps track of messages and saves them to a string for later.
+        
+        message: The string to log (unicode)
+        level: The log level to use (optional)
+        """
         logger.log(message, level)
         self.log += message + '\n'
     
     def _checkForExistingFile(self, existing_file):
+        """
+        Checks if a file exists already and if it does whether it's bigger or smaller than
+        the file we are post processing
+        
+        existing_file: The file to compare to
+        
+        Returns:
+            DOESNT_EXIST if the file doesn't exist
+            EXISTS_LARGER if the file exists and is larger than the file we are post processing
+            EXISTS_SMALLER if the file exists and is smaller than the file we are post processing
+            EXISTS_SAME if the file exists and is the same size as the file we are post processing
+        """
     
         if not existing_file:
             self._log(u"There is no existing file so there's no worries about replacing it", logger.DEBUG)
@@ -104,6 +131,13 @@ class PostProcessor(object):
             return PostProcessor.DOESNT_EXIST
 
     def _list_associated_files(self, file_path):
+        """
+        For a given file path searches for files with the same name but different extension and returns them.
+        
+        file_path: The file to check for associated files
+        
+        Returns: A list containing all files which are associated to the given file
+        """
     
         if not file_path:
             return []
@@ -125,10 +159,17 @@ class PostProcessor(object):
         return file_path_list
 
     def _delete(self, file_path, associated_files=False):
+        """
+        Deletes the file and optionall all associated files.
+        
+        file_path: The file to delete
+        associated_files: True to delete all files which differ only by extension, False to leave them
+        """
         
         if not file_path:
             return
         
+        # figure out which files we want to delete
         if associated_files:
             file_list = self._list_associated_files(file_path)
         else:
@@ -138,6 +179,7 @@ class PostProcessor(object):
             self._log(u"There were no files associated with "+file_path+", not deleting anything", logger.DEBUG)
             return
         
+        # delete the file and any other files which we want to delete
         for cur_file in file_list:
             self._log(u"Deleting file "+cur_file, logger.DEBUG)
             if ek.ek(os.path.isfile, cur_file):
@@ -145,8 +187,11 @@ class PostProcessor(object):
                 
     def _combined_file_operation (self, file_path, new_path, new_base_name, associated_files=False, action=None):
         """
-        file_path: The full path of the media file to copy
-        new_path: Destination path where we want to copy the file to 
+        Performs a generic operation (move or copy) on a file. Can rename the file as well as change its location,
+        and optionally move associated files too.
+        
+        file_path: The full path of the media file to act on
+        new_path: Destination path where we want to move/copy the file to 
         new_base_name: The base filename (no extension) to use during the copy. Use None to keep the same name.
         associated_files: Boolean, whether we should copy similarly-named files too
         action: function that takes an old path and new path and does an operation with them (move/copy)
@@ -228,6 +273,14 @@ class PostProcessor(object):
         self._combined_file_operation(file_path, new_path, new_base_name, associated_files, action=_int_copy)
 
     def _find_ep_destination_folder(self, ep_obj):
+        """
+        Finds the final folder where the episode should go. If season folders are enabled
+        and an existing season folder can be found then it is used, otherwise a new one
+        is created in accordance with the config settings. If season folders aren't enabled
+        then this function should simply return the show dir.
+        
+        ep_obj: The TVEpisode object to figure out the location for 
+        """
         
         # if we're supposed to put it in a season folder then figure out what folder to use
         season_folder = ''
@@ -271,10 +324,12 @@ class PostProcessor(object):
         
         to_return = (None, None, [])
         
+        # if we don't have either of these then there's nothing to use to search the history for anyway
         if not self.nzb_name and not self.folder_name:
             self.in_history = False
             return to_return
 
+        # make a list of possible names to use in the search
         names = []
         if self.nzb_name:
             names.append(self.nzb_name)
@@ -285,6 +340,7 @@ class PostProcessor(object):
 
         myDB = db.DBConnection()
     
+        # search the database for a possible match and return immediately if we find one
         for curName in names:
             sql_results = myDB.select("SELECT * FROM history WHERE resource LIKE ?", [re.sub("[\.\-\ ]", "_", curName)])
     
@@ -306,6 +362,8 @@ class PostProcessor(object):
         """
         Takes a name and tries to figure out a show, season, and episode from it.
         
+        name: A string which we want to analyze to determine show info from (unicode)
+        
         Returns a (tvdb_id, season, [episodes]) tuple. The first two may be None and episodes may be []
         if none were found.
         """
@@ -486,6 +544,16 @@ class PostProcessor(object):
         return (tvdb_id, season, episodes)
     
     def _get_ep_obj(self, tvdb_id, season, episodes):
+        """
+        Retrieve the TVEpisode object requested.
+        
+        tvdb_id: The TVDBID of the show (int)
+        season: The season of the episode (int)
+        episodes: A list of episodes to find (list of ints)
+        
+        If the episode(s) can be found then a TVEpisode object with the correct related eps will
+        be instantiated and returned. If the episode can't be found then None will be returned. 
+        """
 
         show_obj = None
 
@@ -496,6 +564,7 @@ class PostProcessor(object):
         except exceptions.MultipleShowObjectsException:
             raise #TODO: later I'll just log this, for now I want to know about it ASAP
 
+        # if we can't find the show then there's nothing we can really do
         if not show_obj:
             self._log(u"This show isn't in your list, you need to add it to SB before post-processing an episode", logger.ERROR)
             raise exceptions.PostProcessingFailed()
@@ -513,6 +582,7 @@ class PostProcessor(object):
                 self._log(u"Unable to create episode: "+ex(e), logger.DEBUG)
                 raise exceptions.PostProcessingFailed()
     
+            # associate all the episodes together under a single root episode
             if root_ep == None:
                 root_ep = curEp
                 root_ep.relatedEps = []
@@ -522,22 +592,34 @@ class PostProcessor(object):
         return root_ep
     
     def _get_quality(self, ep_obj):
+        """
+        Determines the quality of the file that is being post processed, first by checking if it is directly
+        available in the TVEpisode's status or otherwise by parsing through the data available.
+        
+        ep_obj: The TVEpisode object related to the file we are post processing
+        
+        Returns: A quality value found in common.Quality
+        """
         
         ep_quality = common.Quality.UNKNOWN
 
-        # make sure the quality is set right before we continue
+        # if there is a quality available in the status then we don't need to bother guessing from the filename
         if ep_obj.status in common.Quality.SNATCHED + common.Quality.SNATCHED_PROPER:
             oldStatus, ep_quality = common.Quality.splitCompositeStatus(ep_obj.status) #@UnusedVariable
             if ep_quality != common.Quality.UNKNOWN:
                 self._log(u"The old status had a quality in it, using that: "+common.Quality.qualityStrings[ep_quality], logger.DEBUG)
                 return ep_quality
 
+        # nzb name is the most reliable if it exists, followed by folder name and lastly file name
         name_list = [self.nzb_name, self.folder_name, self.file_name]
     
         # search all possible names for our new quality, in case the file or dir doesn't have it
         for cur_name in name_list:
+            
+            # some stuff might be None at this point still
             if not cur_name:
                 continue
+
             ep_quality = common.Quality.nameQuality(cur_name)
             self._log(u"Looking up quality for name "+cur_name+u", got "+common.Quality.qualityStrings[ep_quality], logger.DEBUG)
             
@@ -556,8 +638,17 @@ class PostProcessor(object):
         return ep_quality
     
     def _run_extra_scripts(self, ep_obj):
+        """
+        Executes any extra scripts defined in the config.
+        
+        ep_obj: The object to use when calling the extra script
+        """
         for curScriptName in sickbeard.EXTRA_SCRIPTS:
+            
+            # generate a safe command line string to execute the script and provide all the parameters
             script_cmd = shlex.split(curScriptName) + [ep_obj.location, self.file_path, str(ep_obj.show.tvdbid), str(ep_obj.season), str(ep_obj.episode), str(ep_obj.airdate)]
+            
+            # use subprocess to run the command and capture output
             self._log(u"Executing command "+str(script_cmd))
             self._log(u"Absolute path to script: "+ek.ek(os.path.abspath, script_cmd[0]), logger.DEBUG)
             try:
@@ -568,6 +659,15 @@ class PostProcessor(object):
                 self._log(u"Unable to run extra_script: "+ex(e))
     
     def _is_priority(self, ep_obj, new_ep_quality):
+        """
+        Determines if the episode is a priority download or not (if it is expected). Episodes which are expected
+        (snatched) or larger than the existing episode are priority, others are not.
+        
+        ep_obj: The TVEpisode object in question
+        new_ep_quality: The quality of the episode that is being processed
+        
+        Returns: True if the episode is priority, False otherwise.
+        """
         
         # if SB downloaded this on purpose then this is a priority download
         if self.in_history or ep_obj.status in common.Quality.SNATCHED + common.Quality.SNATCHED_PROPER:
@@ -726,5 +826,3 @@ class PostProcessor(object):
         self._run_extra_scripts(ep_obj)
 
         return True
-        
-        # e
diff --git a/sickbeard/processTV.py b/sickbeard/processTV.py
index 95425f2c6a6d2607974fdfcf7f7380ac8d01e29c..57c41569d90da55df1320343479633feaaa47267 100644
--- a/sickbeard/processTV.py
+++ b/sickbeard/processTV.py
@@ -35,6 +35,13 @@ def logHelper (logMessage, logLevel=logger.MESSAGE):
     return logMessage + u"\n"
 
 def processDir (dirName, nzbName=None, recurse=False):
+    """
+    Scans through the files in dirName and processes whatever media files it finds
+    
+    dirName: The folder name to look in
+    nzbName: The NZB name which resulted in this folder being downloaded
+    recurse: Boolean for whether we should descend into subfolders or not
+    """
 
     returnStr = ''
 
diff --git a/sickbeard/providers/__init__.py b/sickbeard/providers/__init__.py
index 9538ab5c4ccc1c5ae59db60af2aac81255240e3c..71991a7932d32711a72404f06592c512345a216a 100755
--- a/sickbeard/providers/__init__.py
+++ b/sickbeard/providers/__init__.py
@@ -19,7 +19,7 @@
 __all__ = ['ezrss',
            'tvtorrents',
            'nzbmatrix',
-           'nzbs_org',
+           'nzbs_org_old',
            'nzbsrus',
            'womble',
            'newzbin',
@@ -97,7 +97,7 @@ def makeNewznabProvider(configString):
     return newProvider
 
 def getDefaultNewznabProviders():
-    return 'Sick Beard Index|http://momo.sickbeard.com/|0|0'
+    return 'Sick Beard Index|http://momo.sickbeard.com/|0|0!!!NZBs.org|http://beta.nzbs.org/||0'
 
 
 def getProviderModule(name):
@@ -106,7 +106,7 @@ def getProviderModule(name):
     if name in __all__ and prefix+name in sys.modules:
         return sys.modules[prefix+name]
     else:
-        return None
+        raise Exception("Can't find "+prefix+name+" in "+repr(sys.modules))
 
 def getProviderClass(id):
 
diff --git a/sickbeard/providers/btn.py b/sickbeard/providers/btn.py
index 3e580f0bf43b9bb574438d96755c7d2a72c1a2fb..c332506536c1526b3b19521686e8d07448559674 100644
--- a/sickbeard/providers/btn.py
+++ b/sickbeard/providers/btn.py
@@ -35,7 +35,7 @@ class BTNProvider(generic.TorrentProvider):
         self.url = 'http://broadcasthe.net/'
 
     def isEnabled(self):
-        return True
+        return sickbeard.BTN
         
     def imageName(self):
         return 'btn.gif'
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index 87c330950870b254fb3535f11544706813c7a619..6bd7be0520cf0f115d03754f8fd73f312bbd1349 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -209,9 +209,12 @@ class GenericProvider:
         Returns: A tuple containing two strings representing title and URL respectively
         """
         title = helpers.get_xml_text(item.getElementsByTagName('title')[0])
-        url = helpers.get_xml_text(item.getElementsByTagName('link')[0])
-        if url:
-            url = url.replace('&amp;','&')
+        try:
+            url = helpers.get_xml_text(item.getElementsByTagName('link')[0])
+            if url:
+                url = url.replace('&amp;','&')
+        except IndexError:
+            url = None
         
         return (title, url)
     
diff --git a/sickbeard/providers/nzbs_org.py b/sickbeard/providers/nzbs_org_old.py
similarity index 95%
rename from sickbeard/providers/nzbs_org.py
rename to sickbeard/providers/nzbs_org_old.py
index fecea63c4db028fa50d9b92e327b240606ff2a44..d7f5eb10a9919287e2138de6f4dc59b34b2c83c3 100644
--- a/sickbeard/providers/nzbs_org.py
+++ b/sickbeard/providers/nzbs_org_old.py
@@ -38,7 +38,7 @@ class NZBsProvider(generic.NZBProvider):
 
 	def __init__(self):
 
-		generic.NZBProvider.__init__(self, "NZBs.org")
+		generic.NZBProvider.__init__(self, "NZBs.org Old")
 
 		self.supportsBacklog = True
 
diff --git a/sickbeard/sab.py b/sickbeard/sab.py
index 252d739cd0deb5aba2228c7f440fb7c62658bf4c..880e222d30c5018ceb1b816e04d87e1feffebde1 100644
--- a/sickbeard/sab.py
+++ b/sickbeard/sab.py
@@ -35,9 +35,14 @@ from sickbeard import logger
 from sickbeard.exceptions import ex
 
 def sendNZB(nzb):
+    """
+    Sends an NZB to SABnzbd via the API.
+    
+    nzb: The NZBSearchResult object to send to SAB
+    """
 
+    # set up a dict with the URL params in it
     params = {}
-
     if sickbeard.SAB_USERNAME != None:
         params['ma_username'] = sickbeard.SAB_USERNAME
     if sickbeard.SAB_PASSWORD != None:
@@ -58,7 +63,7 @@ def sendNZB(nzb):
         if nzb.provider.getID() == 'newzbin':
             id = nzb.provider.getIDFromURL(nzb.url)
             if not id:
-                logger.log("Unable to send NZB to sab, can't find ID in URL "+str(nzb.url), logger.ERROR)
+                logger.log("Unable to send NZB to sab, can't find ID in URL " + str(nzb.url), logger.ERROR)
                 return False
             params['mode'] = 'addid'
             params['name'] = id
@@ -69,23 +74,23 @@ def sendNZB(nzb):
     # if we get a raw data result we want to upload it to SAB
     elif nzb.resultType == "nzbdata":
         params['mode'] = 'addfile'
-        multiPartParams = {"nzbfile": (nzb.name+".nzb", nzb.extraInfo[0])}
+        multiPartParams = {"nzbfile": (nzb.name + ".nzb", nzb.extraInfo[0])}
 
     url = sickbeard.SAB_HOST + "api?" + urllib.urlencode(params)
 
     logger.log(u"Sending NZB to SABnzbd")
-
     logger.log(u"URL: " + url, logger.DEBUG)
 
     try:
-
+        # if we have the URL to an NZB then we've built up the SAB API URL already so just call it 
         if nzb.resultType == "nzb":
-                f = urllib.urlopen(url)
+            f = urllib.urlopen(url)
+        
+        # if we are uploading the NZB data to SAB then we need to build a little POST form and send it
         elif nzb.resultType == "nzbdata":
             cookies = cookielib.CookieJar()
             opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
                                           MultipartPostHandler.MultipartPostHandler)
-
             req = urllib2.Request(url,
                                   multiPartParams,
                                   headers={'User-Agent': USER_AGENT})
@@ -93,31 +98,36 @@ def sendNZB(nzb):
             f = opener.open(req)
 
     except (EOFError, IOError), e:
-        logger.log(u"Unable to connect to SAB: "+ex(e), logger.ERROR)
+        logger.log(u"Unable to connect to SAB: " + ex(e), logger.ERROR)
         return False
 
     except httplib.InvalidURL, e:
-        logger.log(u"Invalid SAB host, check your config: "+ex(e), logger.ERROR)
+        logger.log(u"Invalid SAB host, check your config: " + ex(e), logger.ERROR)
         return False
 
+    # this means we couldn't open the connection or something just as bad
     if f == None:
         logger.log(u"No data returned from SABnzbd, NZB not sent", logger.ERROR)
         return False
 
+    # if we opened the URL connection then read the result from SAB
     try:
         result = f.readlines()
     except Exception, e:
         logger.log(u"Error trying to get result from SAB, NZB not sent: " + ex(e), logger.ERROR)
         return False
 
+    # SAB shouldn't return a blank result, this most likely (but not always) means that it timed out and didn't recieve the NZB
     if len(result) == 0:
         logger.log(u"No data returned from SABnzbd, NZB not sent", logger.ERROR)
         return False
 
+    # massage the result a little bit
     sabText = result[0].strip()
 
     logger.log(u"Result text from SAB: " + sabText, logger.DEBUG)
 
+    # do some crude parsing of the result text to determine what SAB said
     if sabText == "ok":
         logger.log(u"NZB sent to SAB successfully", logger.DEBUG)
         return True
@@ -133,11 +143,11 @@ def _checkSabResponse(f):
         result = f.readlines()
     except Exception, e:
         logger.log(u"Error trying to get result from SAB" + ex(e), logger.ERROR)
-        return False,"Error from SAB"
+        return False, "Error from SAB"
 
     if len(result) == 0:
         logger.log(u"No data returned from SABnzbd, NZB not sent", logger.ERROR)
-        return False,"No data from SAB"
+        return False, "No data from SAB"
 
     sabText = result[0].strip()
     sabJson = {}
@@ -148,22 +158,22 @@ def _checkSabResponse(f):
 
     if sabText == "Missing authentication":
         logger.log(u"Incorrect username/password sent to SAB", logger.ERROR)
-        return False,"Incorrect username/password sent to SAB"
+        return False, "Incorrect username/password sent to SAB"
     elif 'error' in sabJson:
         logger.log(sabJson['error'], logger.ERROR)
-        return False,sabJson['error']
+        return False, sabJson['error']
     else:
-        return True,sabText
+        return True, sabText
 
 def _sabURLOpenSimple(url):
     try:
         f = urllib.urlopen(url)
     except (EOFError, IOError), e:
-        logger.log(u"Unable to connect to SAB: "+ex(e), logger.ERROR)
-        return False,"Unable to connect"
+        logger.log(u"Unable to connect to SAB: " + ex(e), logger.ERROR)
+        return False, "Unable to connect"
     except httplib.InvalidURL, e:
-        logger.log(u"Invalid SAB host, check your config: "+ex(e), logger.ERROR)
-        return False,"Invalid SAB host"
+        logger.log(u"Invalid SAB host, check your config: " + ex(e), logger.ERROR)
+        return False, "Invalid SAB host"
     if f == None:
         logger.log(u"No data returned from SABnzbd", logger.ERROR)
         return False, "No data returned from SABnzbd"
@@ -175,31 +185,45 @@ def getSabAccesMethod(host=None, username=None, password=None, apikey=None):
     
     result, f = _sabURLOpenSimple(url)
     if not result:
-        return False,f
+        return False, f
 
     result, sabText = _checkSabResponse(f)
     if not result:
-        return False,sabText
+        return False, sabText
 
-    return True,sabText
+    return True, sabText
 
 def testAuthentication(host=None, username=None, password=None, apikey=None):
+    """
+    Sends a simple API request to SAB to determine if the given connection information is connect
+    
+    host: The host where SAB is running (incl port)
+    username: The username to use for the HTTP request
+    password: The password to use for the HTTP request
+    apikey: The API key to provide to SAB
+    
+    Returns: A tuple containing the success boolean and a message
+    """
+    
+    # build up the URL parameters
     params = {}
     params['mode'] = 'queue'
-    params['output'] ='json'
+    params['output'] = 'json'
     params['ma_username'] = username
     params['ma_password'] = password
     params['apikey'] = apikey
     url = host + "api?" + urllib.urlencode(params)
     
+    # send the test request
     logger.log(u"SABnzbd test URL: " + url, logger.DEBUG)
     result, f = _sabURLOpenSimple(url)
     if not result:
-        return False,f
+        return False, f
 
+    # check the result and determine if it's good or not
     result, sabText = _checkSabResponse(f)
     if not result:
-        return False,sabText
+        return False, sabText
     
-    return True,"Success"
+    return True, "Success"
     
diff --git a/sickbeard/show_name_helpers.py b/sickbeard/show_name_helpers.py
index dc244dad15f98ce5b31b9fcc0bc895c56beeca03..ed2c0a35c8306d4ca7845246f1aafdf7706a9b76 100644
--- a/sickbeard/show_name_helpers.py
+++ b/sickbeard/show_name_helpers.py
@@ -206,6 +206,8 @@ def isGoodResult(name, show, log=True):
 
     for curName in set(showNames):
         escaped_name = re.sub('\\\\[\\s.-]', '\W+', re.escape(curName))
+        if show.startyear:
+            escaped_name += "(?:\W+"+str(show.startyear)+")?"
         curRegex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+)'
         if log:
             logger.log(u"Checking if show "+name+" matches " + curRegex, logger.DEBUG)
diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py
index 76355298812d3ad51ce39914a4fe58b1caffa393..f4f040f8041382ec7a517095bc9196ce71a4dbdd 100644
--- a/sickbeard/versionChecker.py
+++ b/sickbeard/versionChecker.py
@@ -431,7 +431,7 @@ class SourceUpdateManager(GitUpdateManager):
         Downloads the latest source tarball from github and installs it over the existing version.
         """
 
-        tar_download_url = 'http://github.com/midgetspy/Sick-Beard/tarball/'+version.SICKBEARD_VERSION
+        tar_download_url = 'https://github.com/midgetspy/Sick-Beard/tarball/'+version.SICKBEARD_VERSION
         sb_update_dir = os.path.join(sickbeard.PROG_DIR, 'sb-update')
         version_path = os.path.join(sickbeard.PROG_DIR, 'version.txt')
 
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 6e79d7604ad19b6739b153e3c57dfd0366d55f6d..7190a4704eba985f053234c7b88032e3d14661a6 100755
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -40,7 +40,7 @@ from sickbeard import encodingKludge as ek
 from sickbeard import search_queue
 from sickbeard import image_cache
 
-from sickbeard.providers import newznab
+from sickbeard.providers import newznab, getProviderClass
 from sickbeard.common import Quality, Overview, statusStrings
 from sickbeard.common import SNATCHED, DOWNLOADED, SKIPPED, UNAIRED, IGNORED, ARCHIVED, WANTED
 from sickbeard.exceptions import ex
@@ -69,6 +69,18 @@ class PageTemplate (Template):
         self.sbHost = re.match("[^:]+", cherrypy.request.headers['Host'], re.X|re.M|re.S).group(0)
         self.projectHomePage = "http://code.google.com/p/sickbeard/"
 
+        if sickbeard.NZBS and sickbeard.NZBS_UID and sickbeard.NZBS_HASH:
+            logger.log(u"NZBs.org has been replaced, please check the config to configure the new provider!", logger.ERROR)
+            ui.notifications.error("NZBs.org Config Update", "NZBs.org has a new site. Please <a href=\""+sickbeard.WEB_ROOT+"/config/providers\">update your config</a> with the api key from <a href=\"http://beta.nzbs.org/login\">http://beta.nzbs.org</a> and then disable the old NZBs.org provider.")
+
+        if "X-Forwarded-Host" in cherrypy.request.headers:
+            self.sbHost = cherrypy.request.headers['X-Forwarded-Host']
+        if "X-Forwarded-Port" in cherrypy.request.headers:
+            self.sbHttpPort = cherrypy.request.headers['X-Forwarded-Port']
+            self.sbHttpsPort = self.sbHttpPort
+        if "X-Forwarded-Proto" in cherrypy.request.headers:
+            self.sbHttpsEnabled = True if cherrypy.request.headers['X-Forwarded-Proto'] == 'https' else False
+
         logPageTitle = 'Logs &amp; Errors'
         if len(classes.ErrorViewer.errors):
             logPageTitle += ' ('+str(len(classes.ErrorViewer.errors))+')'
@@ -1074,8 +1086,7 @@ class ConfigProviders:
 
 
     @cherrypy.expose
-    def saveProviders(self, nzbs_org_uid=None, nzbs_org_hash=None,
-                      nzbmatrix_username=None, nzbmatrix_apikey=None,
+    def saveProviders(self, nzbmatrix_username=None, nzbmatrix_apikey=None,
                       nzbs_r_us_uid=None, nzbs_r_us_hash=None, newznab_string=None,
                       tvtorrents_digest=None, tvtorrents_hash=None,
  					  btn_user_id=None, btn_auth_token=None, btn_passkey=None, btn_authkey=None,
@@ -1113,7 +1124,6 @@ class ConfigProviders:
 
             finishedNames.append(curID)
 
-
         # delete anything that is missing
         for curProvider in sickbeard.newznabProviderList:
             if curProvider.getID() not in finishedNames:
@@ -1126,10 +1136,10 @@ class ConfigProviders:
 
             provider_list.append(curProvider)
 
-            if curProvider == 'nzbs_org':
-                sickbeard.NZBS = curEnabled
-            elif curProvider == 'nzbs_r_us':
+            if curProvider == 'nzbs_r_us':
                 sickbeard.NZBSRUS = curEnabled
+            elif curProvider == 'nzbs_org_old':
+                sickbeard.NZBS = curEnabled
             elif curProvider == 'nzbmatrix':
                 sickbeard.NZBMATRIX = curEnabled
             elif curProvider == 'newzbin':
@@ -1157,9 +1167,6 @@ class ConfigProviders:
         sickbeard.BTN_PASSKEY = btn_passkey.strip()
         sickbeard.BTN_AUTHKEY = btn_authkey.strip()
 
-        sickbeard.NZBS_UID = nzbs_org_uid.strip()
-        sickbeard.NZBS_HASH = nzbs_org_hash.strip()
-
         sickbeard.NZBSRUS_UID = nzbs_r_us_uid.strip()
         sickbeard.NZBSRUS_HASH = nzbs_r_us_hash.strip()
 
@@ -1553,26 +1560,42 @@ class NewHomeAddShows:
                 lang = "en"
 
         baseURL = "http://thetvdb.com/api/GetSeries.php?"
+        nameUTF8 = name.encode('utf-8')
 
-        params = {'seriesname': name.encode('utf-8'),
-                  'language': lang}
+        # Use each word in the show's name as a possible search term
+        keywords = nameUTF8.split(' ')
+
+        # Insert the whole show's name as the first search term so best results are first
+        # ex: keywords = ['Some Show Name', 'Some', 'Show', 'Name']
+        keywords.insert(0, nameUTF8)
 
-        finalURL = baseURL + urllib.urlencode(params)
+        # Query the TVDB for each search term and build the list of results
+        results = []
+        for searchTerm in keywords:
+            params = {'seriesname': searchTerm,
+                  'language': lang}
 
-        urlData = helpers.getURL(finalURL)
+            finalURL = baseURL + urllib.urlencode(params)
 
-        try:
-            seriesXML = etree.ElementTree(etree.XML(urlData))
-        except Exception, e:
-            logger.log(u"Unable to parse XML for some reason: "+ex(e)+" from XML: "+urlData, logger.ERROR)
-            return ''
+            urlData = helpers.getURL(finalURL)
 
-        series = seriesXML.getiterator('Series')
+            try:
+                seriesXML = etree.ElementTree(etree.XML(urlData))
+            except Exception, e:
+                logger.log(u"Unable to parse XML for some reason: "+ex(e)+" from XML: "+urlData, logger.ERROR)
+                return ''
 
-        results = []
+            series = seriesXML.getiterator('Series')
 
-        for curSeries in series:
-            results.append((int(curSeries.findtext('seriesid')), curSeries.findtext('SeriesName'), curSeries.findtext('FirstAired')))
+            # add each result to our list
+            for curSeries in series:
+                tvdb_id = int(curSeries.findtext('seriesid'))
+                
+                # don't add duplicates
+                if tvdb_id in [x[0] for x in results]:
+                    continue
+                
+                results.append((tvdb_id, curSeries.findtext('SeriesName'), curSeries.findtext('FirstAired')))
 
         lang_id = tvdb_api.Tvdb().config['langabbv_to_id'][lang]