diff --git a/modules/cms/classes/ThemeManager.php b/modules/cms/classes/ThemeManager.php
new file mode 100644
index 000000000..d3042e940
--- /dev/null
+++ b/modules/cms/classes/ThemeManager.php
@@ -0,0 +1,126 @@
+getInstalled();
+ foreach ($installed as $code => $name) {
+ if ($dirName == $name) {
+ return $code;
+ }
+ }
+
+ return null;
+ }
+
+ //
+ // Management
+ //
+
+ /**
+ * Completely delete a theme from the system.
+ * @param string $id Theme code/namespace
+ * @return void
+ */
+ public function deleteTheme($theme)
+ {
+ if (!$theme) {
+ return false;
+ }
+
+ if (is_string($theme)) {
+ $theme = CmsTheme::load($theme);
+ }
+
+ if ($theme->isActiveTheme()) {
+ throw new ApplicationException(trans('cms::lang.theme.delete_active_theme_failed'));
+ }
+
+ /*
+ * Delete from file system
+ */
+ $themePath = $theme->getPath();
+ if (File::isDirectory($themePath)) {
+ File::deleteDirectory($themePath);
+ }
+
+ /*
+ * Set uninstalled
+ */
+ if ($themeCode = $this->findByDirName($theme->getDirName())) {
+ $this->setUninstalled($themeCode);
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/modules/cms/controllers/Themes.php b/modules/cms/controllers/Themes.php
index 4815dc37d..08ba468cd 100644
--- a/modules/cms/controllers/Themes.php
+++ b/modules/cms/controllers/Themes.php
@@ -13,6 +13,7 @@ use Cms\Models\ThemeData;
use Cms\Models\ThemeExport;
use Cms\Models\ThemeImport;
use Cms\Classes\Theme as CmsTheme;
+use Cms\Classes\ThemeManager;
use System\Classes\SettingsManager;
use Backend\Classes\Controller;
use Exception;
@@ -71,16 +72,7 @@ class Themes extends Controller
public function index_onDelete()
{
- $theme = $this->findThemeObject();
-
- if ($theme->isActiveTheme()) {
- throw new ApplicationException(trans('cms::lang.theme.delete_active_theme_failed'));
- }
-
- $themePath = $theme->getPath();
- if (File::isDirectory($themePath)) {
- File::deleteDirectory($themePath);
- }
+ ThemeManager::instance()->deleteTheme(post('theme'));
Flash::success(trans('cms::lang.theme.delete_theme_success'));
return Redirect::refresh();
diff --git a/modules/cms/controllers/themes/_theme_list.htm b/modules/cms/controllers/themes/_theme_list.htm
index f28a3f298..f2a887292 100644
--- a/modules/cms/controllers/themes/_theme_list.htm
+++ b/modules/cms/controllers/themes/_theme_list.htm
@@ -19,14 +19,12 @@
data-control="popup"
data-handler="onLoadCreateForm"
data-size="huge"
- href="javascript:;"
- target="_blank">
+ href="javascript:;">
= e(trans('cms::lang.theme.create_new_blank_theme')) ?>
+ href="= Backend::url('system/updates/install/themes') ?>">
= e(trans('cms::lang.theme.find_more_themes')) ?>
diff --git a/modules/cms/lang/en/lang.php b/modules/cms/lang/en/lang.php
index c62ce19dc..6524f842e 100644
--- a/modules/cms/lang/en/lang.php
+++ b/modules/cms/lang/en/lang.php
@@ -74,7 +74,7 @@ return [
'new_directory_name_comment' => 'Provide a new directory name for the duplicated theme.',
'dir_name_invalid' => 'Name can contain only digits, Latin letters and the following symbols: _-',
'dir_name_taken' => 'Desired theme directory already exists.',
- 'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace',
+ 'find_more_themes' => 'Find more themes',
'return' => 'Return to themes list',
],
'maintenance' => [
diff --git a/modules/system/assets/css/settings.css b/modules/system/assets/css/settings/settings.css
similarity index 100%
rename from modules/system/assets/css/settings.css
rename to modules/system/assets/css/settings/settings.css
diff --git a/modules/system/assets/css/updates/install.css b/modules/system/assets/css/updates/install.css
new file mode 100644
index 000000000..ddf0db7df
--- /dev/null
+++ b/modules/system/assets/css/updates/install.css
@@ -0,0 +1,276 @@
+.product-list-empty {
+ padding: 5px 0;
+ font-size: 16px;
+ color: #999;
+}
+.product-list {
+ margin: 0;
+ padding: 10px 0;
+ overflow: hidden;
+ /* clearfix */
+}
+.product-list li button,
+.product-list li .image,
+.product-list li .details {
+ -webkit-transition: opacity .2s linear;
+ -moz-transition: opacity .2s linear;
+ transition: opacity .2s linear;
+}
+.product-list li button {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 20px;
+ height: 20px;
+ opacity: 0;
+ outline: none;
+}
+.product-list li:hover button {
+ opacity: .3;
+}
+.product-list li:hover button:hover {
+ opacity: .8;
+}
+.plugin-list li {
+ list-style: none;
+ position: relative;
+ border-bottom: 1px solid #E6E9E9;
+ margin-bottom: 10px;
+ padding-bottom: 10px;
+ overflow: hidden;
+}
+.plugin-list li:last-child {
+ border-bottom: none;
+}
+.plugin-list li .image {
+ float: left;
+ margin-right: 15px;
+ margin-left: 5px;
+}
+.plugin-list li .image img {
+ width: 50px;
+ height: 50px;
+}
+.plugin-list li .details p {
+ padding: 0;
+ margin: 3px 0 0 0;
+ color: #808C8D;
+}
+.plugin-list li h4 {
+ padding: 5px 0 0;
+ margin: 0;
+ color: #C03F31;
+ font-weight: 400;
+}
+.theme-list li {
+ float: left;
+ padding: 0;
+ margin: 0 10px 10px 0;
+ list-style: none;
+ border: 1px solid #E6E9E9;
+ background: #fff;
+ position: relative;
+ border-radius: 3px;
+}
+.theme-list li:hover {
+ border-color: transparent;
+}
+.theme-list li {
+ -webkit-transition: border .2s linear;
+ -moz-transition: border .2s linear;
+ transition: border .2s linear;
+}
+.theme-list li .image {
+ padding: 5px;
+}
+.theme-list li .image img {
+ width: 210px;
+ height: 140px;
+}
+.theme-list li:hover .image {
+ opacity: 0;
+}
+.theme-list li .details {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ opacity: 0;
+ padding: 10px;
+ overflow: hidden;
+}
+.theme-list li:hover .details {
+ opacity: 1;
+}
+.theme-list li h4 {
+ padding: 15px 0 0;
+ margin: 0;
+}
+.theme-list li p {
+ padding: 0;
+ margin: 0;
+ color: #999;
+ text-transform: uppercase;
+ font-size: 12px;
+}
+.suggested-products {
+ padding: 0;
+}
+.suggested-products .product {
+ padding: 0;
+}
+.suggested-products .image img {
+ width: 40px;
+ height: 40px;
+ margin-top: 10px;
+}
+.suggested-themes .image img {
+ width: 60px;
+ height: 40px;
+}
+.suggested-products .image {
+ float: left;
+ position: relative;
+}
+.suggested-products .details {
+ margin-left: 50px;
+ padding: 10px 0;
+}
+.suggested-themes .details {
+ margin-left: 70px;
+}
+.suggested-products .details h5 {
+ margin: 0 0 3px;
+ font-size: 14px;
+ color: #C03F31;
+ font-weight: 400;
+}
+.suggested-products .details p {
+ font-size: 12px;
+}
+.suggested-products a {
+ color: #777;
+ background: #fff;
+ padding: 5px;
+ text-decoration: none;
+ display: block;
+ overflow: hidden;
+ border-bottom: 1px solid #E6E9E9;
+}
+.suggested-products a:hover {
+ color: #333;
+ background: #f9f9f9;
+}
+.suggested-products a:hover .image:after {
+ content: "+";
+ color: #999;
+ font-size: 32px;
+ display: block;
+ width: 40px;
+ height: 40px;
+ text-align: center;
+ line-height: 40px;
+ position: absolute;
+ top: 7px;
+ left: 0;
+}
+.suggested-products a:hover .image img {
+ opacity: .5;
+}
+/*!
+ * Typeahead
+ */
+.product-search {
+ position: relative;
+ width: 100%;
+ margin: 0 auto 0 auto;
+ text-align: left;
+ padding-bottom: 15px;
+}
+.twitter-typeahead {
+ width: 100%;
+}
+.typeahead,
+.tt-hint {
+ width: 100%;
+ height: 46px;
+ padding: 8px 12px;
+ font-size: 24px;
+ line-height: 30px;
+ border: 1px solid #024e6a;
+ border-radius: 3px;
+ outline: none;
+}
+.typeahead {
+ background-color: #fff;
+ border-color: #e0e0e0;
+}
+.tt-input {
+ font-weight: 200;
+}
+.tt-input:focus {
+ border-color: #E6E9E9;
+}
+.tt-hint {
+ color: #999;
+ font-weight: 200;
+}
+.tt-dropdown-menu {
+ width: 100%;
+ margin-top: 0;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 3px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+}
+.tt-suggestion {
+ font-size: 14px;
+ line-height: 18px;
+}
+.tt-suggestion + .tt-suggestion {
+ font-size: 14px;
+ border-top: 1px solid #ccc;
+}
+.tt-suggestions .product-details {
+ padding: 5px;
+ overflow: hidden;
+ position: relative;
+}
+.tt-suggestions .product-image {
+ float: left;
+ margin-right: 10px;
+}
+.tt-suggestions .product-image img {
+ height: 45px;
+ width: 45px;
+}
+.tt-suggestions .product-name {
+ font-size: 20px;
+ padding-top: 5px;
+}
+.tt-suggestion.tt-cursor {
+ cursor: pointer;
+}
+.tt-suggestion.tt-cursor .product-details {
+ color: #333;
+ background: #f9f9f9;
+ border-color: #f0f0f0;
+}
+.tt-suggestion.tt-cursor .product-details .product-image:after {
+ content: "+";
+ color: #999;
+ font-size: 38px;
+ display: block;
+ width: 45px;
+ height: 45px;
+ text-align: center;
+ line-height: 45px;
+ position: absolute;
+ top: 5px;
+ left: 5px;
+}
+.tt-suggestion.tt-cursor .product-details .product-image img {
+ opacity: .5;
+}
diff --git a/modules/system/assets/css/updates.css b/modules/system/assets/css/updates/updates.css
similarity index 100%
rename from modules/system/assets/css/updates.css
rename to modules/system/assets/css/updates/updates.css
diff --git a/modules/system/assets/js/updates/install.js b/modules/system/assets/js/updates/install.js
new file mode 100644
index 000000000..1f4c0f462
--- /dev/null
+++ b/modules/system/assets/js/updates/install.js
@@ -0,0 +1,164 @@
+/*
+ * Installs class
+ *
+ * Dependences:
+ * - Waterfall plugin (waterfall.js)
+ */
+
++function ($) { "use strict";
+
+ var InstallProcess = function () {
+
+ // Init
+ this.init()
+
+ }
+
+ InstallProcess.prototype.init = function() {
+ var self = this
+
+ this.dataSet = {}
+
+ $(document).ready(function(){
+
+ self.bindSearch('#pluginSearchInput')
+ self.bindSearch('#themeSearchInput')
+
+ self.bindSuggested('#suggestedPlugins')
+ self.bindSuggested('#suggestedThemes')
+ })
+ }
+
+ //
+ // Searching
+ //
+
+ InstallProcess.prototype.bindSearch = function(el) {
+ var $el = $(el),
+ $form = $el.closest('form'),
+ searchType = $el.data('searchType')
+
+ if ($el.length == 0) return
+
+ // Template for search results
+ var template = Mustache.compile([
+ '
',
+ '
',
+ '
{{name}}
',
+ '
{{description}}
',
+ '
'
+ ].join(''))
+
+ // Source for product search
+ var engine = new Bloodhound({
+ name: 'products',
+ method: 'POST',
+ remote: window.location.pathname + '?search=' + searchType + '&query=%QUERY',
+ datumTokenizer: function(d) {
+ return Bloodhound.tokenizers.whitespace(d.val)
+ },
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
+ limit: 5
+ })
+
+ engine.initialize()
+
+ /*
+ * Bind autocomplete to search field
+ */
+ $(el)
+ .typeahead(null, {
+ displayKey: 'code',
+ source: engine.ttAdapter(),
+ minLength: 3,
+ templates: {
+ suggestion: template
+ }
+ })
+ .on('typeahead:opened', function(){
+ $(el + ' .tt-dropdown-menu').css('width', $(el).width() + 'px')
+ })
+ .on('typeahead:selected', function(object, datum){
+ $form.submit()
+ })
+ .on('keypress', function(e) {
+ if (e.keyCode == 13) // ENTER key
+ $form.submit()
+ })
+ }
+
+ InstallProcess.prototype.searchSubmit = function(el) {
+ var
+ $el = $(el),
+ $input = $el.find('.product-search-input.tt-input:first')
+
+ $el.popup()
+
+ $input.typeahead('val', '')
+ }
+
+ //
+ // Suggested products
+ //
+
+ InstallProcess.prototype.bindSuggested = function(el) {
+ var
+ self = this,
+ handler = $(el).data('handler')
+
+ $.request(handler).done(function(data){
+ var result = data.result
+ if (!$.isArray(result)) return
+ self.renderSuggested(el, result)
+ })
+ }
+
+ InstallProcess.prototype.renderSuggested = function(el, suggestedProducts) {
+ var self = this,
+ $el = $(el),
+ $container = $el.closest('.suggested-products-container'),
+ partialView = $(el).data('view')
+
+ if (suggestedProducts.length == 0) {
+ $container.hide()
+ }
+ else {
+ $container.show()
+ $.each(suggestedProducts, function(index, product) {
+ self.renderPartial($el, partialView, product, { append:true })
+ })
+ }
+ }
+
+ //
+ // Helpers
+ //
+
+ InstallProcess.prototype.renderPartial = function($el, name, data, options) {
+ var container = $el,
+ template = $('[data-partial="' + name + '"]'),
+ contents = Mustache.to_html(template.html(), data)
+
+ options = $.extend(true, {
+ append: false
+ }, options)
+
+ if (options.append) container.append(contents)
+ else container.html(contents)
+ }
+
+ if ($.oc === undefined)
+ $.oc = {}
+
+ $.oc.installProcess = new InstallProcess;
+
+}(window.jQuery);
+
+
+/*!
+ * typeahead.js 0.10.1
+ * https://github.com/twitter/typeahead.js
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+!function(a){var b={isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}},c="0.10.1",d=function(){function a(a){this.maxSize=a||100,this.size=0,this.hash={},this.list=new c}function c(){this.head=this.tail=null}function d(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(a.prototype,{set:function(a,b){var c,e=this.list.tail;this.size>=this.maxSize&&(this.list.remove(e),delete this.hash[e.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new d(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0}}),b.mixin(c.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),a}(this),e=function(){function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),f=function(){function c(b){b=b||{},this._send=b.transport?e(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get}function e(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new d(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i=new d(10)},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(b),i.set(a,b)}function e(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;(j=g[a])?j.done(d):h>f?(f++,g[a]=this._send(a,b).done(d).always(e)):this.onDeckRequestArgs=[].slice.call(arguments,0)},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),(e=i.get(a))?b.defer(function(){d&&d(e)}):this._get(a,c,d),!!e}}),c}(),g=function(){function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.datums=[],this.trie=e()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0;db[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),h=function(){function d(a){var c=a.local||null;return b.isFunction(c)&&(c=c.call(null)),c}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}(),i=(window.Bloodhound=function(){function c(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=d(b.sorter),this.dupDetector=b.dupDetector||i,this.local=h.local(b),this.prefetch=h.prefetch(b),this.remote=h.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new g({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new e(this.cacheKey):null}function d(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function i(){return!1}var j;return j={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.tokenizers={whitespace:function(a){return a.split(/\s+/)},nonword:function(a){return a.split(/\W+/)}},b.mixin(c.prototype,{_loadPrefetch:function(b){function c(a){var c;c=b.filter?b.filter(a):a,f.add(c),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a){var c=f.remote.filter?f.remote.filter(a):a;b(c)}var d,e,f=this;return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(j.data,a,c),this.storage.set(j.protocol,location.protocol,c),this.storage.set(j.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(j.data),c.protocol=this.storage.get(j.protocol),c.thumbprint=this.storage.get(j.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},initialize:function(){function b(){d.add(d.local)}var c,d=this;return c=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),this.local&&c.done(b),this.transport=this.remote?new f(this.remote):null,this.initialize=function(){return c.promise()},c.promise()},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=e.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return f.dupDetector(a,b)}),!c&&d.push(a),d.length