├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── README.md ├── build └── js │ ├── wp-cache.js │ └── wp-cache.min.js ├── composer.json ├── gulpfile.js ├── js ├── app.js ├── local-storage.js └── variable-storage.js ├── package.json ├── readme.txt └── wp-cache-js.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # WordPress Coding Standards 2 | # http://make.wordpress.org/core/handbook/coding-standards/ 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | tab_width = 4 11 | indent_style = tab 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.txt] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | insert_final_newline = false 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [.*rc] 29 | insert_final_newline = false 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.yml] 34 | insert_final_newline = false 35 | indent_style = space 36 | indent_size = 2 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folders and files to be ignored by git 2 | 3 | ############ 4 | ## IDEs 5 | ############ 6 | 7 | *.pydevproject 8 | .project 9 | .metadata 10 | *.swp 11 | *~.nib 12 | local.properties 13 | .classpath 14 | .settings/ 15 | .loadpath 16 | .externalToolBuilders/ 17 | *.launch 18 | .cproject 19 | .buildpath 20 | nbproject/ 21 | composer.lock 22 | 23 | ############ 24 | ## Vendor 25 | ############ 26 | 27 | node_modules/ 28 | vendor/ 29 | 30 | ############ 31 | ## OSes 32 | ############ 33 | 34 | [Tt]humbs.db 35 | [Dd]esktop.ini 36 | *.DS_store 37 | .DS_store? 38 | 39 | ############ 40 | ## Misc 41 | ############ 42 | 43 | bin/ 44 | tmp/ 45 | *.tmp 46 | *.bak 47 | *.log 48 | *.lock 49 | *.[Cc]ache 50 | *.cpr 51 | *.orig 52 | *.php.in 53 | .idea/ 54 | .sass-cache/* 55 | temp/ 56 | ._* 57 | .Trashes 58 | 59 | .svn 60 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "wordpress", 3 | "excludeFiles": [ 4 | "**/*.min.js", 5 | "**/node_modules/**", 6 | "**/vendor/**" 7 | ] 8 | } -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | gulpfile.js 2 | **/*.min.js 3 | **/node_modules/** 4 | **/vendor/** 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "es3": true, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "onevar": true, 11 | "quotmark": "single", 12 | "trailing": true, 13 | "undef": true, 14 | "unused": true, 15 | "browser": true, 16 | 17 | "globals": { 18 | "_": false, 19 | "JSON" : false, 20 | "wp": false, 21 | "wpCacheSettings": false, 22 | "console": true 23 | } 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wp-cache.js 2 | 3 | A JavaScript-based cache solution for WordPress using the client's local storage or, as fallback, a simple variable storage. 4 | 5 | ## Description 6 | 7 | The script exposes a `wp.cache` object with the following methods: 8 | 9 | * `add( key, data, group, expire )` 10 | * `replace( key, data, group, expire )` 11 | * `set( key, data, group, expire )` 12 | * `get( key, group, force )` 13 | * `remove( key, group )` 14 | * `flush()` 15 | * `incr( key, offset, group )` 16 | * `decr( key, offset, group )` 17 | * `switchToSite( siteId )` 18 | * `switchToNetwork( networkId )` 19 | * `addNetworkGroups( groups )` 20 | * `addGlobalGroups( groups )` 21 | * `addNonPersistentGroups( groups )` 22 | * `init()` 23 | * `close()` 24 | 25 | ## Installation 26 | 27 | 1. Upload the entire `wp-cache-js` folder to the `/wp-content/plugins/` directory. 28 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 29 | 3. Enqueue the script by using `wp_enqueue_script( 'wp-cache' )` or by declaring it as a dependency to one of your scripts, such as `wp_enqueue_script( 'my-script', '/path/to/my/script.js', array( 'wp-cache' ) )`. 30 | 31 | ## FAQ 32 | 33 | ### How can I register my own cache implementation? 34 | 35 | You can register your own cache implementation if you would like to add support for another type of cache to use in JavaScript, just like you can place a cache drop-in for WordPress server-side. 36 | 37 | In order to do that, you need to create your own object which implements all of the methods mentioned in the Description section (except for `init()` and `close()` which can be optionally implemented), plus an additional `checkRequirements()` method that should return `true|false` depending on whether the client fulfills the requirements for this cache type. 38 | 39 | You can then register your object using `wp.cache.registerImplementation( identifier, implementationObject, priority )` with `identifier` being a unique identifier for your implementation and `priority` being a numeric value to determine the priority in which to check for the implementation. The default `localStorage` implementation has a priority of 10 and the `variableStorage` implementation a priority of 100. 40 | -------------------------------------------------------------------------------- /build/js/wp-cache.js: -------------------------------------------------------------------------------- 1 | ( function( window, _ ) { 2 | 'use strict'; 3 | 4 | var implementations = [], 5 | currentImplementation, 6 | loggedError = false; 7 | 8 | window.wpCacheSettings = window.wpCacheSettings || { 9 | siteId: 1, 10 | networkId: 1, 11 | isMultisite: false, 12 | i18n: { 13 | noImplementationSet: 'No cache implementation set.' 14 | } 15 | }; 16 | 17 | /** 18 | * Sets the current implementation based on priority and whether requirements are met. 19 | * 20 | * @since 0.1.0 21 | */ 22 | function setCurrentImplementation() { 23 | var i, j; 24 | 25 | implementationLoop : for ( i in implementations ) { 26 | for ( j in implementations[ i ] ) { 27 | if ( implementations[ i ][ j ].checkRequirements() ) { 28 | if ( currentImplementation && _.isFunction( currentImplementation.close ) ) { 29 | currentImplementation.close(); 30 | } 31 | 32 | currentImplementation = implementations[ i ][ j ]; 33 | if ( _.isFunction( currentImplementation.init ) ) { 34 | currentImplementation.init(); 35 | } 36 | 37 | loggedError = false; 38 | break implementationLoop; 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Logs an error message that no cache implementation is set, if it has not been logged before. 46 | * 47 | * @since 0.1.0 48 | */ 49 | function maybeLogError() { 50 | if ( loggedError ) { 51 | return; 52 | } 53 | 54 | console.error( window.wpCacheSettings.i18n.noImplementationSet ); 55 | loggedError = true; 56 | } 57 | 58 | window.wp = window.wp || {}; 59 | 60 | window.wp.cache = window.wp.cache || { 61 | 62 | MINUTE_IN_SECONDS: 60, 63 | 64 | HOUR_IN_SECONDS: 3600, 65 | 66 | DAY_IN_SECONDS: 86400, 67 | 68 | WEEK_IN_SECONDS: 604800, 69 | 70 | YEAR_IN_SECONDS: 31536000, 71 | 72 | /** 73 | * Adds data to the cache if the cache key doesn't already exist. 74 | * 75 | * @since 0.1.0 76 | * @access public 77 | * 78 | * @param {number|string} key The cache key to use for retrieval later. 79 | * @param {mixed} data The data to add to the cache. 80 | * @param {string} [group='default'] The group to add the cache to. Enables the same key 81 | * to be used across groups. 82 | * @param {number} [expire=0] When the cache data should expire, in seconds. 83 | * 0 means no expiration. 84 | * @returns {boolean} True on success, false if cache key and group already exist. 85 | */ 86 | add: function( key, data, group, expire ) { 87 | group = group || 'default'; 88 | expire = expire || 0; 89 | 90 | if ( ! currentImplementation ) { 91 | maybeLogError(); 92 | return false; 93 | } 94 | 95 | return currentImplementation.add( key, data, group, parseInt( expire, 10 ) ); 96 | }, 97 | 98 | /** 99 | * Replaces the contents of the cache with new data. 100 | * 101 | * @since 0.1.0 102 | * @access public 103 | * 104 | * @param {number|string} key The key for the cache data that should be replaced. 105 | * @param {mixed} data The new data to store in the cache. 106 | * @param {string} [group='default'] The group for the cache data that should be replaced. 107 | * @param {number} [expire=0] When to expire the cache contents, in seconds. 108 | * 0 means no expiration. 109 | * @returns {boolean} True if contents were replaced, false if original value does not exist. 110 | */ 111 | replace: function( key, data, group, expire ) { 112 | group = group || 'default'; 113 | expire = expire || 0; 114 | 115 | if ( ! currentImplementation ) { 116 | maybeLogError(); 117 | return false; 118 | } 119 | 120 | return currentImplementation.replace( key, data, group, parseInt( expire, 10 ) ); 121 | }, 122 | 123 | /** 124 | * Saves data to the cache. 125 | * 126 | * Differs from wp.cache.add() and wp.cache.replace() in that it will always write data. 127 | * 128 | * @since 0.1.0 129 | * @access public 130 | * 131 | * @param {number|string} key The cache key to use for retrieval later. 132 | * @param {mixed} data The contents to store in the cache. 133 | * @param {string} [group='default'] Where to group the cache contents. Enables the same key 134 | * to be used across groups. 135 | * @param {number} [expire=0] When to expire the cache contents, in seconds. 136 | * 0 means no expiration. 137 | * @returns {boolean} True on success, false on failure. 138 | */ 139 | set: function( key, data, group, expire ) { 140 | group = group || 'default'; 141 | expire = expire || 0; 142 | 143 | if ( ! currentImplementation ) { 144 | maybeLogError(); 145 | return false; 146 | } 147 | 148 | return currentImplementation.set( key, data, group, parseInt( expire, 10 ) ); 149 | }, 150 | 151 | /** 152 | * Retrieves the cache contents from the cache by key and group. 153 | * 154 | * @since 0.1.0 155 | * @access public 156 | * 157 | * @param {number|string} key The key under which the cache contents are stored. 158 | * @param {string} [group='default'] Where the cache contents are grouped. 159 | * @param {boolean} [force=false] Whether to force an update of the local cache from 160 | * the persistent cache. 161 | * @returns {mixed|undefined} Cache contents on success or undefined on failure. 162 | */ 163 | get: function( key, group, force ) { 164 | group = group || 'default'; 165 | force = force || false; 166 | 167 | if ( ! currentImplementation ) { 168 | maybeLogError(); 169 | return undefined; 170 | } 171 | 172 | return currentImplementation.get( key, group, force ); 173 | }, 174 | 175 | /** 176 | * Removes the cache contents matching key and group. 177 | * 178 | * @since 0.1.0 179 | * @access public 180 | * 181 | * @param {number|string} key What the contents in the cache are called. 182 | * @param {string} [group='default'] Where the cache contents are grouped. 183 | * @returns {boolean} True on successful removal, false on failure. 184 | */ 185 | remove: function( key, group ) { 186 | group = group || 'default'; 187 | 188 | if ( ! currentImplementation ) { 189 | maybeLogError(); 190 | return false; 191 | } 192 | 193 | return currentImplementation.remove( key, group ); 194 | }, 195 | 196 | /** 197 | * Removes all cache items. 198 | * 199 | * @since 0.1.0 200 | * @access public 201 | * 202 | * @returns {boolean} True on success, false on failure. 203 | */ 204 | flush: function() { 205 | if ( ! currentImplementation ) { 206 | maybeLogError(); 207 | return false; 208 | } 209 | 210 | return currentImplementation.flush(); 211 | }, 212 | 213 | /** 214 | * Increments a numeric cache item's value. 215 | * 216 | * @since 0.1.0 217 | * @access public 218 | * 219 | * @param {number|string} key The key for the cache contents that should be incremented. 220 | * @param {number} [offset=1] The amount by which to increment the item's value. 221 | * @param {string} [group='default'] The group the key is in. 222 | * @returns {number|boolean} The item's new value on success, false on failure. 223 | */ 224 | incr: function( key, offset, group ) { 225 | offset = offset || 1; 226 | group = group || 'default'; 227 | 228 | if ( ! currentImplementation ) { 229 | maybeLogError(); 230 | return false; 231 | } 232 | 233 | return currentImplementation.incr( key, parseInt( offset, 10 ), group ); 234 | }, 235 | 236 | /** 237 | * Decrements a numeric cache item's value. 238 | * 239 | * @since 0.1.0 240 | * @access public 241 | * 242 | * @param {number|string} key The key for the cache contents that should be decremented. 243 | * @param {number} [offset=1] The amount by which to decrement the item's value. 244 | * @param {string} [group='default'] The group the key is in. 245 | * @returns {number|boolean} The item's new value on success, false on failure. 246 | */ 247 | decr: function( key, offset, group ) { 248 | offset = offset || 1; 249 | group = group || 'default'; 250 | 251 | if ( ! currentImplementation ) { 252 | maybeLogError(); 253 | return false; 254 | } 255 | 256 | return currentImplementation.decr( key, parseInt( offset, 10 ), group ); 257 | }, 258 | 259 | /** 260 | * Switches the internal site ID. 261 | * 262 | * @since 0.1.0 263 | * @access public 264 | * 265 | * @param {number} siteId Site ID. 266 | * @returns {boolean} True if the site was switched, false otherwise. 267 | */ 268 | switchToSite: function( siteId ) { 269 | if ( ! window.wpCacheSettings.isMultisite ) { 270 | return false; 271 | } 272 | 273 | siteId = parseInt( siteId, 10 ); 274 | 275 | if ( siteId === window.wpCacheSettings.siteId ) { 276 | return true; 277 | } 278 | 279 | if ( ! currentImplementation ) { 280 | maybeLogError(); 281 | return false; 282 | } 283 | 284 | return currentImplementation.switchToSite( siteId ); 285 | }, 286 | 287 | /** 288 | * Switches the internal network ID. 289 | * 290 | * @since 0.1.0 291 | * @access public 292 | * 293 | * @param {number} networkId Network ID. 294 | * @returns {boolean} True if the network was switched, false otherwise. 295 | */ 296 | switchToNetwork: function( networkId ) { 297 | if ( ! window.wpCacheSettings.isMultisite ) { 298 | return false; 299 | } 300 | 301 | networkId = parseInt( networkId, 10 ); 302 | 303 | if ( networkId === window.wpCacheSettings.networkId ) { 304 | return true; 305 | } 306 | 307 | if ( ! currentImplementation ) { 308 | maybeLogError(); 309 | return false; 310 | } 311 | 312 | currentImplementation.switchToNetwork( networkId ); 313 | }, 314 | 315 | /** 316 | * Adds a group or set of groups to the list of network groups. 317 | * 318 | * @since 0.1.0 319 | * @access public 320 | * 321 | * @param {string|string[]} groups A group or an array of groups to add. 322 | */ 323 | addNetworkGroups: function( groups ) { 324 | if ( ! currentImplementation ) { 325 | maybeLogError(); 326 | return; 327 | } 328 | 329 | currentImplementation.addNetworkGroups( groups ); 330 | }, 331 | 332 | /** 333 | * Adds a group or set of groups to the list of global groups. 334 | * 335 | * @since 0.1.0 336 | * @access public 337 | * 338 | * @param {string|string[]} groups A group or an array of groups to add. 339 | */ 340 | addGlobalGroups: function( groups ) { 341 | if ( ! currentImplementation ) { 342 | maybeLogError(); 343 | return; 344 | } 345 | 346 | currentImplementation.addGlobalGroups( groups ); 347 | }, 348 | 349 | /** 350 | * Adds a group or set of groups to the list of non-persistent groups. 351 | * 352 | * @since 0.1.0 353 | * @access public 354 | * 355 | * @param {string|string[]} groups A group or an array of groups to add. 356 | */ 357 | addNonPersistentGroups: function( groups ) { 358 | if ( ! currentImplementation ) { 359 | maybeLogError(); 360 | return; 361 | } 362 | 363 | currentImplementation.addNonPersistentGroups( groups ); 364 | }, 365 | 366 | /** 367 | * Initializes the cache. 368 | * 369 | * This method is optional and does not need to be implemented by the cache 370 | * implementation unless necessary. 371 | * 372 | * @since 0.1.0 373 | * @access public 374 | */ 375 | init: function() { 376 | if ( ! currentImplementation ) { 377 | maybeLogError(); 378 | return; 379 | } 380 | 381 | if ( ! _.isFunction( currentImplementation.init ) ) { 382 | return; 383 | } 384 | 385 | currentImplementation.init(); 386 | }, 387 | 388 | /** 389 | * Closes the cache. 390 | * 391 | * This method is optional and does not need to be implemented by the cache 392 | * implementation unless necessary. 393 | * 394 | * @since 0.1.0 395 | * @access public 396 | */ 397 | close: function() { 398 | if ( ! currentImplementation ) { 399 | maybeLogError(); 400 | return; 401 | } 402 | 403 | if ( ! _.isFunction( currentImplementation.close ) ) { 404 | return; 405 | } 406 | 407 | currentImplementation.close(); 408 | }, 409 | 410 | /** 411 | * Registers a cache implementation. 412 | * 413 | * @since 0.1.0 414 | * @access public 415 | * 416 | * @param {string} identifier Unique identifier for the implementation. 417 | * @param {object} implementation The actual implementation. 418 | * @param {number} [priority=10] Priority for the implementation. 419 | * @returns {boolean} True on success, false on failure. 420 | */ 421 | registerImplementation: function( identifier, implementation, priority ) { 422 | var requiredMethods = [ 423 | 'add', 424 | 'replace', 425 | 'set', 426 | 'get', 427 | 'remove', 428 | 'flush', 429 | 'incr', 430 | 'decr', 431 | 'switchToSite', 432 | 'switchToNetwork', 433 | 'addNetworkGroups', 434 | 'addGlobalGroups', 435 | 'addNonPersistentGroups', 436 | 'checkRequirements' 437 | ], i; 438 | 439 | for ( i in requiredMethods ) { 440 | if ( ! _.isFunction( implementation[ requiredMethods[ i ] ] ) ) { 441 | return false; 442 | } 443 | } 444 | 445 | priority = priority || 10; 446 | 447 | implementation.identifier = identifier; 448 | implementation.priority = priority; 449 | 450 | if ( _.isUndefined( implementations[ priority ] ) ) { 451 | implementations[ priority ] = []; 452 | } 453 | 454 | implementations[ priority ].push( implementation ); 455 | 456 | if ( ! currentImplementation || currentImplementation.priority > priority ) { 457 | setCurrentImplementation(); 458 | } 459 | 460 | return true; 461 | } 462 | }; 463 | })( window, window._ ); 464 | 465 | ( function( cache, _, settings ) { 466 | 'use strict'; 467 | 468 | var nonPersistentData = {}, 469 | nonPersistentGroups = {}, 470 | networkGroups = {}, 471 | globalGroups = {}, 472 | currentSiteId = settings.siteId, 473 | currentNetworkId = settings.networkId, 474 | implementation; 475 | 476 | function getCurrentTime() { 477 | return Math.floor( Date.now() / 1000 ); 478 | } 479 | 480 | function getFullKey( key, group ) { 481 | if ( globalGroups[ group ] ) { 482 | return key; 483 | } 484 | 485 | if ( networkGroups[ group ] ) { 486 | return 'network' + currentNetworkId + '_' + key; 487 | } 488 | 489 | return 'site' + currentSiteId + '_' + key; 490 | } 491 | 492 | function getLocalStorageIdentifier( key, group, isFullKey ) { 493 | if ( ! isFullKey ) { 494 | key = getFullKey( key, group ); 495 | } 496 | 497 | return 'wpCache:' + group + ':' + key; 498 | } 499 | 500 | function getFullDataObject( data, expire ) { 501 | if ( _.isObject( data ) && ! _.isNull( data ) && ! _.isArray( data ) ) { 502 | data = _.extend({}, data ); 503 | } 504 | 505 | if ( expire ) { 506 | expire = getCurrentTime() + expire; 507 | } 508 | 509 | return { 510 | data: data, 511 | expire: expire 512 | }; 513 | } 514 | 515 | function getFromFullDataObject( dataObject ) { 516 | if ( _.isObject( dataObject.data ) && ! _.isNull( dataObject.data ) && ! _.isArray( dataObject.data ) ) { 517 | return _.extend({}, dataObject.data ); 518 | } 519 | 520 | return dataObject.data; 521 | } 522 | 523 | function exists( key, group, isFullKey ) { 524 | var item; 525 | 526 | key = getLocalStorageIdentifier( key, group, isFullKey ); 527 | 528 | item = localStorage.getItem( key ); 529 | if ( ! item ) { 530 | return false; 531 | } 532 | 533 | item = JSON.parse( item ); 534 | if ( ! _.isObject( item ) ) { 535 | localStorage.removeItem( key ); 536 | return false; 537 | } 538 | 539 | if ( item.expire && item.expire < getCurrentTime() ) { 540 | localStorage.removeItem( key ); 541 | return false; 542 | } 543 | 544 | return true; 545 | } 546 | 547 | function existsNonPersistent( key, group, isFullKey ) { 548 | var item; 549 | 550 | if ( _.isUndefined( nonPersistentData[ group ] ) ) { 551 | return false; 552 | } 553 | 554 | if ( ! isFullKey ) { 555 | key = getFullKey( key, group ); 556 | } 557 | 558 | if ( _.isUndefined( nonPersistentData[ group ][ key ] ) ) { 559 | return false; 560 | } 561 | 562 | item = nonPersistentData[ group ][ key ]; 563 | if ( item.expire && item.expire < getCurrentTime() ) { 564 | delete nonPersistentData[ group ][ key ]; 565 | return false; 566 | } 567 | 568 | return true; 569 | } 570 | 571 | implementation = { 572 | 573 | add: function( key, data, group, expire ) { 574 | if ( nonPersistentGroups[ group ] ) { 575 | if ( existsNonPersistent( key, group ) ) { 576 | return false; 577 | } 578 | 579 | return implementation.set( key, data, group, expire ); 580 | } 581 | 582 | if ( exists( key, group ) ) { 583 | return false; 584 | } 585 | 586 | return implementation.set( key, data, group, expire ); 587 | }, 588 | 589 | replace: function( key, data, group, expire ) { 590 | if ( nonPersistentGroups[ group ] ) { 591 | if ( ! existsNonPersistent( key, group ) ) { 592 | return false; 593 | } 594 | 595 | return implementation.set( key, data, group, expire ); 596 | } 597 | 598 | if ( ! exists( key, group ) ) { 599 | return false; 600 | } 601 | 602 | return implementation.set( key, data, group, expire ); 603 | }, 604 | 605 | set: function( key, data, group, expire ) { 606 | key = getFullKey( key, group ); 607 | data = getFullDataObject( data, expire ); 608 | 609 | if ( nonPersistentGroups[ group ] ) { 610 | if ( _.isUndefined( nonPersistentData[ group ] ) ) { 611 | nonPersistentData[ group ] = {}; 612 | } 613 | 614 | nonPersistentData[ group ][ key ] = data; 615 | 616 | return true; 617 | } 618 | 619 | key = getLocalStorageIdentifier( key, group, true ); 620 | localStorage.setItem( key, JSON.stringify( data ) ); 621 | 622 | return true; 623 | }, 624 | 625 | get: function( key, group ) { 626 | key = getFullKey( key, group ); 627 | 628 | if ( nonPersistentGroups[ group ] ) { 629 | if ( ! existsNonPersistent( key, group, true ) ) { 630 | return undefined; 631 | } 632 | 633 | return getFromFullDataObject( nonPersistentData[ group ][ key ] ); 634 | } 635 | 636 | if ( ! exists( key, group, true ) ) { 637 | return undefined; 638 | } 639 | 640 | key = getLocalStorageIdentifier( key, group, true ); 641 | 642 | return getFromFullDataObject( JSON.parse( localStorage.getItem( key ) ) ); 643 | }, 644 | 645 | remove: function( key, group ) { 646 | key = getFullKey( key, group ); 647 | 648 | if ( nonPersistentGroups[ group ] ) { 649 | if ( ! existsNonPersistent( key, group, true ) ) { 650 | return false; 651 | } 652 | 653 | delete nonPersistentData[ group ][ key ]; 654 | 655 | return true; 656 | } 657 | 658 | if ( ! exists( key, group, true ) ) { 659 | return false; 660 | } 661 | 662 | key = getLocalStorageIdentifier( key, group, true ); 663 | 664 | localStorage.removeItem( key ); 665 | 666 | return true; 667 | }, 668 | 669 | flush: function() { 670 | var keys, i; 671 | 672 | nonPersistentData = {}; 673 | 674 | keys = Object.keys( localStorage ); 675 | for ( i in keys ) { 676 | if ( 'wpCache:' === keys[ i ].substring( 0, 8 ) ) { 677 | localStorage.removeItem( keys[ i ] ); 678 | } 679 | } 680 | 681 | return true; 682 | }, 683 | 684 | incr: function( key, offset, group ) { 685 | var dataObject; 686 | 687 | key = getFullKey( key, group ); 688 | 689 | if ( nonPersistentGroups[ group ] ) { 690 | if ( ! existsNonPersistent( key, group, true ) ) { 691 | return false; 692 | } 693 | 694 | if ( ! _.isNumber( nonPersistentData[ group ][ key ].data ) ) { 695 | nonPersistentData[ group ][ key ].data = 0; 696 | } 697 | 698 | nonPersistentData[ group ][ key ].data += offset; 699 | 700 | if ( nonPersistentData[ group ][ key ].data < 0 ) { 701 | nonPersistentData[ group ][ key ].data = 0; 702 | } 703 | 704 | return nonPersistentData[ group ][ key ].data; 705 | } 706 | 707 | if ( ! exists( key, group, true ) ) { 708 | return false; 709 | } 710 | 711 | key = getLocalStorageIdentifier( key, group, true ); 712 | 713 | dataObject = JSON.parse( localStorage.getItem( key ) ); 714 | 715 | if ( ! _.isNumber( dataObject.data ) ) { 716 | dataObject.data = 0; 717 | } 718 | 719 | dataObject.data += offset; 720 | 721 | if ( dataObject.data < 0 ) { 722 | dataObject.data = 0; 723 | } 724 | 725 | localStorage.setItem( key, JSON.stringify( dataObject ) ); 726 | 727 | return dataObject.data; 728 | }, 729 | 730 | decr: function( key, offset, group ) { 731 | var dataObject; 732 | 733 | key = getFullKey( key, group ); 734 | 735 | if ( nonPersistentGroups[ group ] ) { 736 | if ( ! existsNonPersistent( key, group, true ) ) { 737 | return false; 738 | } 739 | 740 | if ( ! _.isNumber( nonPersistentData[ group ][ key ].data ) ) { 741 | nonPersistentData[ group ][ key ].data = 0; 742 | } 743 | 744 | nonPersistentData[ group ][ key ].data -= offset; 745 | 746 | if ( nonPersistentData[ group ][ key ].data < 0 ) { 747 | nonPersistentData[ group ][ key ].data = 0; 748 | } 749 | 750 | return nonPersistentData[ group ][ key ].data; 751 | } 752 | 753 | if ( ! exists( key, group, true ) ) { 754 | return false; 755 | } 756 | 757 | key = getLocalStorageIdentifier( key, group, true ); 758 | 759 | dataObject = JSON.parse( localStorage.getItem( key ) ); 760 | 761 | if ( ! _.isNumber( dataObject.data ) ) { 762 | dataObject.data = 0; 763 | } 764 | 765 | dataObject.data -= offset; 766 | 767 | if ( dataObject.data < 0 ) { 768 | dataObject.data = 0; 769 | } 770 | 771 | localStorage.setItem( key, JSON.stringify( dataObject ) ); 772 | 773 | return dataObject.data; 774 | }, 775 | 776 | switchToSite: function( siteId ) { 777 | currentSiteId = siteId; 778 | 779 | return true; 780 | }, 781 | 782 | switchToNetwork: function( networkId ) { 783 | currentNetworkId = networkId; 784 | 785 | return true; 786 | }, 787 | 788 | addNetworkGroups: function( groups ) { 789 | var i; 790 | 791 | if ( ! _.isArray( groups ) ) { 792 | groups = [ groups ]; 793 | } 794 | 795 | for ( i in groups ) { 796 | if ( networkGroups[ groups[ i ] ] ) { 797 | continue; 798 | } 799 | 800 | networkGroups[ groups[ i ] ] = true; 801 | } 802 | }, 803 | 804 | addGlobalGroups: function( groups ) { 805 | var i; 806 | 807 | if ( ! _.isArray( groups ) ) { 808 | groups = [ groups ]; 809 | } 810 | 811 | for ( i in groups ) { 812 | if ( globalGroups[ groups[ i ] ] ) { 813 | continue; 814 | } 815 | 816 | globalGroups[ groups[ i ] ] = true; 817 | } 818 | }, 819 | 820 | addNonPersistentGroups: function( groups ) { 821 | var i; 822 | 823 | if ( ! _.isArray( groups ) ) { 824 | groups = [ groups ]; 825 | } 826 | 827 | for ( i in groups ) { 828 | if ( nonPersistentGroups[ groups[ i ] ] ) { 829 | continue; 830 | } 831 | 832 | nonPersistentGroups[ groups[ i ] ] = true; 833 | } 834 | }, 835 | 836 | checkRequirements: function() { 837 | return ! _.isUndefined( localStorage ) && ! _.isUndefined( JSON ); 838 | } 839 | }; 840 | 841 | cache.registerImplementation( 'localStorage', implementation, 10 ); 842 | 843 | })( window.wp.cache, window._, window.wpCacheSettings ); 844 | 845 | ( function( cache, _, settings ) { 846 | 'use strict'; 847 | 848 | var cachedData = {}, 849 | networkGroups = {}, 850 | globalGroups = {}, 851 | currentSiteId = settings.siteId, 852 | currentNetworkId = settings.networkId, 853 | implementation; 854 | 855 | function getCurrentTime() { 856 | return Math.floor( Date.now() / 1000 ); 857 | } 858 | 859 | function getFullKey( key, group ) { 860 | if ( globalGroups[ group ] ) { 861 | return key; 862 | } 863 | 864 | if ( networkGroups[ group ] ) { 865 | return 'network' + currentNetworkId + '_' + key; 866 | } 867 | 868 | return 'site' + currentSiteId + '_' + key; 869 | } 870 | 871 | function getFullDataObject( data, expire ) { 872 | if ( _.isObject( data ) && ! _.isNull( data ) && ! _.isArray( data ) ) { 873 | data = _.extend({}, data ); 874 | } 875 | 876 | if ( expire ) { 877 | expire = getCurrentTime() + expire; 878 | } 879 | 880 | return { 881 | data: data, 882 | expire: expire 883 | }; 884 | } 885 | 886 | function getFromFullDataObject( dataObject ) { 887 | if ( _.isObject( dataObject.data ) && ! _.isNull( dataObject.data ) && ! _.isArray( dataObject.data ) ) { 888 | return _.extend({}, dataObject.data ); 889 | } 890 | 891 | return dataObject.data; 892 | } 893 | 894 | function exists( key, group, isFullKey ) { 895 | var item; 896 | 897 | if ( _.isUndefined( cachedData[ group ] ) ) { 898 | return false; 899 | } 900 | 901 | if ( ! isFullKey ) { 902 | key = getFullKey( key, group ); 903 | } 904 | 905 | if ( _.isUndefined( cachedData[ group ][ key ] ) ) { 906 | return false; 907 | } 908 | 909 | item = cachedData[ group ][ key ]; 910 | if ( item.expire && item.expire < getCurrentTime() ) { 911 | delete cachedData[ group ][ key ]; 912 | return false; 913 | } 914 | 915 | return true; 916 | } 917 | 918 | implementation = { 919 | 920 | add: function( key, data, group, expire ) { 921 | if ( exists( key, group ) ) { 922 | return false; 923 | } 924 | 925 | return implementation.set( key, data, group, expire ); 926 | }, 927 | 928 | replace: function( key, data, group, expire ) { 929 | if ( ! exists( key, group ) ) { 930 | return false; 931 | } 932 | 933 | return implementation.set( key, data, group, expire ); 934 | }, 935 | 936 | set: function( key, data, group, expire ) { 937 | key = getFullKey( key, group ); 938 | data = getFullDataObject( data, expire ); 939 | 940 | if ( _.isUndefined( cachedData[ group ] ) ) { 941 | cachedData[ group ] = {}; 942 | } 943 | 944 | cachedData[ group ][ key ] = data; 945 | 946 | return true; 947 | }, 948 | 949 | get: function( key, group ) { 950 | key = getFullKey( key, group ); 951 | 952 | if ( ! exists( key, group, true ) ) { 953 | return undefined; 954 | } 955 | 956 | return getFromFullDataObject( cachedData[ group ][ key ] ); 957 | }, 958 | 959 | remove: function( key, group ) { 960 | key = getFullKey( key, group ); 961 | 962 | if ( ! exists( key, group, true ) ) { 963 | return false; 964 | } 965 | 966 | delete cachedData[ group ][ key ]; 967 | 968 | return true; 969 | }, 970 | 971 | flush: function() { 972 | cachedData = {}; 973 | 974 | return true; 975 | }, 976 | 977 | incr: function( key, offset, group ) { 978 | key = getFullKey( key, group ); 979 | 980 | if ( ! exists( key, group, true ) ) { 981 | return false; 982 | } 983 | 984 | if ( ! _.isNumber( cachedData[ group ][ key ].data ) ) { 985 | cachedData[ group ][ key ].data = 0; 986 | } 987 | 988 | cachedData[ group ][ key ].data += offset; 989 | 990 | if ( cachedData[ group ][ key ].data < 0 ) { 991 | cachedData[ group ][ key ].data = 0; 992 | } 993 | 994 | return cachedData[ group ][ key ].data; 995 | }, 996 | 997 | decr: function( key, offset, group ) { 998 | key = getFullKey( key, group ); 999 | 1000 | if ( ! exists( key, group, true ) ) { 1001 | return false; 1002 | } 1003 | 1004 | if ( ! _.isNumber( cachedData[ group ][ key ].data ) ) { 1005 | cachedData[ group ][ key ].data = 0; 1006 | } 1007 | 1008 | cachedData[ group ][ key ].data -= offset; 1009 | 1010 | if ( cachedData[ group ][ key ].data < 0 ) { 1011 | cachedData[ group ][ key ].data = 0; 1012 | } 1013 | 1014 | return cachedData[ group ][ key ].data; 1015 | }, 1016 | 1017 | switchToSite: function( siteId ) { 1018 | currentSiteId = siteId; 1019 | 1020 | return true; 1021 | }, 1022 | 1023 | switchToNetwork: function( networkId ) { 1024 | currentNetworkId = networkId; 1025 | 1026 | return true; 1027 | }, 1028 | 1029 | addNetworkGroups: function( groups ) { 1030 | var i; 1031 | 1032 | if ( ! _.isArray( groups ) ) { 1033 | groups = [ groups ]; 1034 | } 1035 | 1036 | for ( i in groups ) { 1037 | if ( networkGroups[ groups[ i ] ] ) { 1038 | continue; 1039 | } 1040 | 1041 | networkGroups[ groups[ i ] ] = true; 1042 | } 1043 | }, 1044 | 1045 | addGlobalGroups: function( groups ) { 1046 | var i; 1047 | 1048 | if ( ! _.isArray( groups ) ) { 1049 | groups = [ groups ]; 1050 | } 1051 | 1052 | for ( i in groups ) { 1053 | if ( globalGroups[ groups[ i ] ] ) { 1054 | continue; 1055 | } 1056 | 1057 | globalGroups[ groups[ i ] ] = true; 1058 | } 1059 | }, 1060 | 1061 | addNonPersistentGroups: function() { 1062 | /* Default cache doesn't persist so nothing to do here. */ 1063 | }, 1064 | 1065 | checkRequirements: function() { 1066 | return true; 1067 | } 1068 | }; 1069 | 1070 | cache.registerImplementation( 'variableStorage', implementation, 100 ); 1071 | 1072 | })( window.wp.cache, window._, window.wpCacheSettings ); 1073 | -------------------------------------------------------------------------------- /build/js/wp-cache.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"use strict";function n(){var t,n;t:for(t in a)for(n in a[t])if(a[t][n].checkRequirements()){i&&e.isFunction(i.close)&&i.close(),i=a[t][n],e.isFunction(i.init)&&i.init(),o=!1;break t}}function r(){o||(console.error(t.wpCacheSettings.i18n.noImplementationSet),o=!0)}var i,a=[],o=!1;t.wpCacheSettings=t.wpCacheSettings||{siteId:1,networkId:1,isMultisite:!1,i18n:{noImplementationSet:"No cache implementation set."}},t.wp=t.wp||{},t.wp.cache=t.wp.cache||{MINUTE_IN_SECONDS:60,HOUR_IN_SECONDS:3600,DAY_IN_SECONDS:86400,WEEK_IN_SECONDS:604800,YEAR_IN_SECONDS:31536e3,add:function(t,e,n,a){return n=n||"default",a=a||0,i?i.add(t,e,n,parseInt(a,10)):(r(),!1)},replace:function(t,e,n,a){return n=n||"default",a=a||0,i?i.replace(t,e,n,parseInt(a,10)):(r(),!1)},set:function(t,e,n,a){return n=n||"default",a=a||0,i?i.set(t,e,n,parseInt(a,10)):(r(),!1)},get:function(t,e,n){return e=e||"default",n=n||!1,i?i.get(t,e,n):void r()},remove:function(t,e){return e=e||"default",i?i.remove(t,e):(r(),!1)},flush:function(){return i?i.flush():(r(),!1)},incr:function(t,e,n){return e=e||1,n=n||"default",i?i.incr(t,parseInt(e,10),n):(r(),!1)},decr:function(t,e,n){return e=e||1,n=n||"default",i?i.decr(t,parseInt(e,10),n):(r(),!1)},switchToSite:function(e){return!!t.wpCacheSettings.isMultisite&&(e=parseInt(e,10),e===t.wpCacheSettings.siteId||(i?i.switchToSite(e):(r(),!1)))},switchToNetwork:function(e){return!!t.wpCacheSettings.isMultisite&&(e=parseInt(e,10),e===t.wpCacheSettings.networkId||(i?void i.switchToNetwork(e):(r(),!1)))},addNetworkGroups:function(t){return i?void i.addNetworkGroups(t):void r()},addGlobalGroups:function(t){return i?void i.addGlobalGroups(t):void r()},addNonPersistentGroups:function(t){return i?void i.addNonPersistentGroups(t):void r()},init:function(){return i?void(e.isFunction(i.init)&&i.init()):void r()},close:function(){return i?void(e.isFunction(i.close)&&i.close()):void r()},registerImplementation:function(t,r,o){var u,d=["add","replace","set","get","remove","flush","incr","decr","switchToSite","switchToNetwork","addNetworkGroups","addGlobalGroups","addNonPersistentGroups","checkRequirements"];for(u in d)if(!e.isFunction(r[d[u]]))return!1;return o=o||10,r.identifier=t,r.priority=o,e.isUndefined(a[o])&&(a[o]=[]),a[o].push(r),(!i||i.priority>o)&&n(),!0}}}(window,window._),function(t,e,n){"use strict";function r(){return Math.floor(Date.now()/1e3)}function i(t,e){return w[e]?t:p[e]?"network"+N+"_"+t:"site"+S+"_"+t}function a(t,e,n){return n||(t=i(t,e)),"wpCache:"+e+":"+t}function o(t,n){return!e.isObject(t)||e.isNull(t)||e.isArray(t)||(t=e.extend({},t)),n&&(n=r()+n),{data:t,expire:n}}function u(t){return!e.isObject(t.data)||e.isNull(t.data)||e.isArray(t.data)?t.data:e.extend({},t.data)}function d(t,n,i){var o;return t=a(t,n,i),!!(o=localStorage.getItem(t))&&(o=JSON.parse(o),e.isObject(o)?!(o.expire&&o.expire=5.6.0", 28 | "composer/installers": "~1.0" 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* ---- THE FOLLOWING CONFIG SHOULD BE EDITED ---- */ 2 | 3 | var pkg = require( './package.json' ); 4 | 5 | function parseKeywords( keywords ) { 6 | // These keywords are useful for Packagist/NPM/Bower, but not for the WordPress plugin repository. 7 | var disallowed = [ 'wordpress', 'plugin' ]; 8 | 9 | var k = keywords; 10 | for ( var i in disallowed ) { 11 | var index = k.indexOf( disallowed[ i ] ); 12 | if ( -1 < index ) { 13 | k.splice( index, 1 ); 14 | } 15 | } 16 | 17 | return k; 18 | } 19 | 20 | var keywords = parseKeywords( pkg.keywords ); 21 | 22 | var config = { 23 | pluginSlug: 'wp-cache-js', 24 | pluginName: 'wp-cache.js', 25 | pluginURI: pkg.homepage, 26 | author: pkg.author.name, 27 | authorURI: pkg.author.url, 28 | description: pkg.description, 29 | version: pkg.version, 30 | license: 'GNU General Public License v3', 31 | licenseURI: 'http://www.gnu.org/licenses/gpl-3.0.html', 32 | tags: keywords.join( ', ' ), 33 | contributors: [ 'flixos90' ].join( ', ' ), 34 | donateLink: false, 35 | minRequired: '4.6', 36 | testedUpTo: '4.6', 37 | translateURI: 'https://translate.wordpress.org/projects/wp-plugins/wp-cache-js', 38 | network: false 39 | }; 40 | 41 | /* ---- DO NOT EDIT BELOW THIS LINE ---- */ 42 | 43 | // WP plugin header for main plugin file 44 | var pluginheader = 'Plugin Name: ' + config.pluginName + '\n' + 45 | 'Plugin URI: ' + config.pluginURI + '\n' + 46 | 'Description: ' + config.description + '\n' + 47 | 'Version: ' + config.version + '\n' + 48 | 'Author: ' + config.author + '\n' + 49 | 'Author URI: ' + config.authorURI + '\n' + 50 | 'License: ' + config.license + '\n' + 51 | 'License URI: ' + config.licenseURI + '\n' + 52 | 'Text Domain: ' + config.pluginSlug + '\n' + 53 | ( config.network ? 'Network: true' + '\n' : '' ) + 54 | 'Tags: ' + config.tags; 55 | 56 | // WP plugin header for readme.txt 57 | var readmeheader = 'Plugin Name: ' + config.pluginName + '\n' + 58 | 'Plugin URI: ' + config.pluginURI + '\n' + 59 | 'Author: ' + config.author + '\n' + 60 | 'Author URI: ' + config.authorURI + '\n' + 61 | 'Contributors: ' + config.contributors + '\n' + 62 | ( config.donateLink ? 'Donate link: ' + config.donateLink + '\n' : '' ) + 63 | 'Requires at least: ' + config.minRequired + '\n' + 64 | 'Tested up to: ' + config.testedUpTo + '\n' + 65 | 'Stable tag: ' + config.version + '\n' + 66 | 'Version: ' + config.version + '\n' + 67 | 'License: ' + config.license + '\n' + 68 | 'License URI: ' + config.licenseURI + '\n' + 69 | 'Tags: ' + config.tags; 70 | 71 | 72 | /* ---- REQUIRED DEPENDENCIES ---- */ 73 | 74 | var gulp = require( 'gulp' ); 75 | 76 | var jscs = require( 'gulp-jscs' ); 77 | var jshint = require( 'gulp-jshint' ); 78 | var concat = require( 'gulp-concat' ); 79 | var uglify = require( 'gulp-uglify' ); 80 | var rename = require( 'gulp-rename' ); 81 | var replace = require( 'gulp-replace' ); 82 | 83 | var paths = { 84 | php: { 85 | files: [ './*.php', './src/**/*.php', './templates/**/*.php' ] 86 | }, 87 | js: { 88 | files: [ './js/**/*.js' ], 89 | src: './js/', 90 | dst: './build/js/' 91 | } 92 | }; 93 | 94 | /* ---- MAIN TASKS ---- */ 95 | 96 | // general task (compile JavaScript) 97 | gulp.task( 'default', [ 'js' ]); 98 | 99 | // watch JavaScript files 100 | gulp.task( 'watch', function() { 101 | gulp.watch( paths.js.files, [ 'js' ]); 102 | }); 103 | 104 | // build the plugin 105 | gulp.task( 'build', [ 'readme-replace' ], function() { 106 | gulp.start( 'header-replace' ); 107 | gulp.start( 'default' ); 108 | }); 109 | 110 | /* ---- SUB TASKS ---- */ 111 | 112 | // compile JavaScript 113 | gulp.task( 'js', function( done ) { 114 | gulp.src( paths.js.files ) 115 | .pipe( jscs() ) 116 | .pipe( jscs.reporter() ) 117 | .pipe( jshint({ 118 | lookup: true 119 | }) ) 120 | .pipe( concat( 'wp-cache.js' ) ) 121 | .pipe( gulp.dest( paths.js.dst ) ) 122 | .pipe( uglify() ) 123 | .pipe( rename({ 124 | extname: '.min.js' 125 | }) ) 126 | .pipe( gulp.dest( paths.js.dst ) ) 127 | .on( 'end', done ); 128 | }); 129 | 130 | // replace the plugin header in the main plugin file 131 | gulp.task( 'header-replace', function( done ) { 132 | gulp.src( './' + config.pluginSlug + '.php' ) 133 | .pipe( replace( /((?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)|(?:\/\/.*))/, '/*\n' + pluginheader + '\n*/' ) ) 134 | .pipe( gulp.dest( './' ) ) 135 | .on( 'end', done ); 136 | }); 137 | 138 | // replace the plugin header in readme.txt 139 | gulp.task( 'readme-replace', function( done ) { 140 | gulp.src( './readme.txt' ) 141 | .pipe( replace( /\=\=\= (.+) \=\=\=([\s\S]+)\=\= Description \=\=/m, '=== ' + config.pluginName + ' ===\n\n' + readmeheader + '\n\n' + config.description + '\n\n== Description ==' ) ) 142 | .pipe( gulp.dest( './' ) ) 143 | .on( 'end', done ); 144 | }); 145 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | ( function( window, _ ) { 2 | 'use strict'; 3 | 4 | var implementations = [], 5 | currentImplementation, 6 | loggedError = false; 7 | 8 | window.wpCacheSettings = window.wpCacheSettings || { 9 | siteId: 1, 10 | networkId: 1, 11 | isMultisite: false, 12 | i18n: { 13 | noImplementationSet: 'No cache implementation set.' 14 | } 15 | }; 16 | 17 | /** 18 | * Sets the current implementation based on priority and whether requirements are met. 19 | * 20 | * @since 0.1.0 21 | */ 22 | function setCurrentImplementation() { 23 | var i, j; 24 | 25 | implementationLoop : for ( i in implementations ) { 26 | for ( j in implementations[ i ] ) { 27 | if ( implementations[ i ][ j ].checkRequirements() ) { 28 | if ( currentImplementation && _.isFunction( currentImplementation.close ) ) { 29 | currentImplementation.close(); 30 | } 31 | 32 | currentImplementation = implementations[ i ][ j ]; 33 | if ( _.isFunction( currentImplementation.init ) ) { 34 | currentImplementation.init(); 35 | } 36 | 37 | loggedError = false; 38 | break implementationLoop; 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Logs an error message that no cache implementation is set, if it has not been logged before. 46 | * 47 | * @since 0.1.0 48 | */ 49 | function maybeLogError() { 50 | if ( loggedError ) { 51 | return; 52 | } 53 | 54 | console.error( window.wpCacheSettings.i18n.noImplementationSet ); 55 | loggedError = true; 56 | } 57 | 58 | window.wp = window.wp || {}; 59 | 60 | window.wp.cache = window.wp.cache || { 61 | 62 | MINUTE_IN_SECONDS: 60, 63 | 64 | HOUR_IN_SECONDS: 3600, 65 | 66 | DAY_IN_SECONDS: 86400, 67 | 68 | WEEK_IN_SECONDS: 604800, 69 | 70 | YEAR_IN_SECONDS: 31536000, 71 | 72 | /** 73 | * Adds data to the cache if the cache key doesn't already exist. 74 | * 75 | * @since 0.1.0 76 | * @access public 77 | * 78 | * @param {number|string} key The cache key to use for retrieval later. 79 | * @param {mixed} data The data to add to the cache. 80 | * @param {string} [group='default'] The group to add the cache to. Enables the same key 81 | * to be used across groups. 82 | * @param {number} [expire=0] When the cache data should expire, in seconds. 83 | * 0 means no expiration. 84 | * @returns {boolean} True on success, false if cache key and group already exist. 85 | */ 86 | add: function( key, data, group, expire ) { 87 | group = group || 'default'; 88 | expire = expire || 0; 89 | 90 | if ( ! currentImplementation ) { 91 | maybeLogError(); 92 | return false; 93 | } 94 | 95 | return currentImplementation.add( key, data, group, parseInt( expire, 10 ) ); 96 | }, 97 | 98 | /** 99 | * Replaces the contents of the cache with new data. 100 | * 101 | * @since 0.1.0 102 | * @access public 103 | * 104 | * @param {number|string} key The key for the cache data that should be replaced. 105 | * @param {mixed} data The new data to store in the cache. 106 | * @param {string} [group='default'] The group for the cache data that should be replaced. 107 | * @param {number} [expire=0] When to expire the cache contents, in seconds. 108 | * 0 means no expiration. 109 | * @returns {boolean} True if contents were replaced, false if original value does not exist. 110 | */ 111 | replace: function( key, data, group, expire ) { 112 | group = group || 'default'; 113 | expire = expire || 0; 114 | 115 | if ( ! currentImplementation ) { 116 | maybeLogError(); 117 | return false; 118 | } 119 | 120 | return currentImplementation.replace( key, data, group, parseInt( expire, 10 ) ); 121 | }, 122 | 123 | /** 124 | * Saves data to the cache. 125 | * 126 | * Differs from wp.cache.add() and wp.cache.replace() in that it will always write data. 127 | * 128 | * @since 0.1.0 129 | * @access public 130 | * 131 | * @param {number|string} key The cache key to use for retrieval later. 132 | * @param {mixed} data The contents to store in the cache. 133 | * @param {string} [group='default'] Where to group the cache contents. Enables the same key 134 | * to be used across groups. 135 | * @param {number} [expire=0] When to expire the cache contents, in seconds. 136 | * 0 means no expiration. 137 | * @returns {boolean} True on success, false on failure. 138 | */ 139 | set: function( key, data, group, expire ) { 140 | group = group || 'default'; 141 | expire = expire || 0; 142 | 143 | if ( ! currentImplementation ) { 144 | maybeLogError(); 145 | return false; 146 | } 147 | 148 | return currentImplementation.set( key, data, group, parseInt( expire, 10 ) ); 149 | }, 150 | 151 | /** 152 | * Retrieves the cache contents from the cache by key and group. 153 | * 154 | * @since 0.1.0 155 | * @access public 156 | * 157 | * @param {number|string} key The key under which the cache contents are stored. 158 | * @param {string} [group='default'] Where the cache contents are grouped. 159 | * @param {boolean} [force=false] Whether to force an update of the local cache from 160 | * the persistent cache. 161 | * @returns {mixed|undefined} Cache contents on success or undefined on failure. 162 | */ 163 | get: function( key, group, force ) { 164 | group = group || 'default'; 165 | force = force || false; 166 | 167 | if ( ! currentImplementation ) { 168 | maybeLogError(); 169 | return undefined; 170 | } 171 | 172 | return currentImplementation.get( key, group, force ); 173 | }, 174 | 175 | /** 176 | * Removes the cache contents matching key and group. 177 | * 178 | * @since 0.1.0 179 | * @access public 180 | * 181 | * @param {number|string} key What the contents in the cache are called. 182 | * @param {string} [group='default'] Where the cache contents are grouped. 183 | * @returns {boolean} True on successful removal, false on failure. 184 | */ 185 | remove: function( key, group ) { 186 | group = group || 'default'; 187 | 188 | if ( ! currentImplementation ) { 189 | maybeLogError(); 190 | return false; 191 | } 192 | 193 | return currentImplementation.remove( key, group ); 194 | }, 195 | 196 | /** 197 | * Removes all cache items. 198 | * 199 | * @since 0.1.0 200 | * @access public 201 | * 202 | * @returns {boolean} True on success, false on failure. 203 | */ 204 | flush: function() { 205 | if ( ! currentImplementation ) { 206 | maybeLogError(); 207 | return false; 208 | } 209 | 210 | return currentImplementation.flush(); 211 | }, 212 | 213 | /** 214 | * Increments a numeric cache item's value. 215 | * 216 | * @since 0.1.0 217 | * @access public 218 | * 219 | * @param {number|string} key The key for the cache contents that should be incremented. 220 | * @param {number} [offset=1] The amount by which to increment the item's value. 221 | * @param {string} [group='default'] The group the key is in. 222 | * @returns {number|boolean} The item's new value on success, false on failure. 223 | */ 224 | incr: function( key, offset, group ) { 225 | offset = offset || 1; 226 | group = group || 'default'; 227 | 228 | if ( ! currentImplementation ) { 229 | maybeLogError(); 230 | return false; 231 | } 232 | 233 | return currentImplementation.incr( key, parseInt( offset, 10 ), group ); 234 | }, 235 | 236 | /** 237 | * Decrements a numeric cache item's value. 238 | * 239 | * @since 0.1.0 240 | * @access public 241 | * 242 | * @param {number|string} key The key for the cache contents that should be decremented. 243 | * @param {number} [offset=1] The amount by which to decrement the item's value. 244 | * @param {string} [group='default'] The group the key is in. 245 | * @returns {number|boolean} The item's new value on success, false on failure. 246 | */ 247 | decr: function( key, offset, group ) { 248 | offset = offset || 1; 249 | group = group || 'default'; 250 | 251 | if ( ! currentImplementation ) { 252 | maybeLogError(); 253 | return false; 254 | } 255 | 256 | return currentImplementation.decr( key, parseInt( offset, 10 ), group ); 257 | }, 258 | 259 | /** 260 | * Switches the internal site ID. 261 | * 262 | * @since 0.1.0 263 | * @access public 264 | * 265 | * @param {number} siteId Site ID. 266 | * @returns {boolean} True if the site was switched, false otherwise. 267 | */ 268 | switchToSite: function( siteId ) { 269 | if ( ! window.wpCacheSettings.isMultisite ) { 270 | return false; 271 | } 272 | 273 | siteId = parseInt( siteId, 10 ); 274 | 275 | if ( siteId === window.wpCacheSettings.siteId ) { 276 | return true; 277 | } 278 | 279 | if ( ! currentImplementation ) { 280 | maybeLogError(); 281 | return false; 282 | } 283 | 284 | return currentImplementation.switchToSite( siteId ); 285 | }, 286 | 287 | /** 288 | * Switches the internal network ID. 289 | * 290 | * @since 0.1.0 291 | * @access public 292 | * 293 | * @param {number} networkId Network ID. 294 | * @returns {boolean} True if the network was switched, false otherwise. 295 | */ 296 | switchToNetwork: function( networkId ) { 297 | if ( ! window.wpCacheSettings.isMultisite ) { 298 | return false; 299 | } 300 | 301 | networkId = parseInt( networkId, 10 ); 302 | 303 | if ( networkId === window.wpCacheSettings.networkId ) { 304 | return true; 305 | } 306 | 307 | if ( ! currentImplementation ) { 308 | maybeLogError(); 309 | return false; 310 | } 311 | 312 | currentImplementation.switchToNetwork( networkId ); 313 | }, 314 | 315 | /** 316 | * Adds a group or set of groups to the list of network groups. 317 | * 318 | * @since 0.1.0 319 | * @access public 320 | * 321 | * @param {string|string[]} groups A group or an array of groups to add. 322 | */ 323 | addNetworkGroups: function( groups ) { 324 | if ( ! currentImplementation ) { 325 | maybeLogError(); 326 | return; 327 | } 328 | 329 | currentImplementation.addNetworkGroups( groups ); 330 | }, 331 | 332 | /** 333 | * Adds a group or set of groups to the list of global groups. 334 | * 335 | * @since 0.1.0 336 | * @access public 337 | * 338 | * @param {string|string[]} groups A group or an array of groups to add. 339 | */ 340 | addGlobalGroups: function( groups ) { 341 | if ( ! currentImplementation ) { 342 | maybeLogError(); 343 | return; 344 | } 345 | 346 | currentImplementation.addGlobalGroups( groups ); 347 | }, 348 | 349 | /** 350 | * Adds a group or set of groups to the list of non-persistent groups. 351 | * 352 | * @since 0.1.0 353 | * @access public 354 | * 355 | * @param {string|string[]} groups A group or an array of groups to add. 356 | */ 357 | addNonPersistentGroups: function( groups ) { 358 | if ( ! currentImplementation ) { 359 | maybeLogError(); 360 | return; 361 | } 362 | 363 | currentImplementation.addNonPersistentGroups( groups ); 364 | }, 365 | 366 | /** 367 | * Initializes the cache. 368 | * 369 | * This method is optional and does not need to be implemented by the cache 370 | * implementation unless necessary. 371 | * 372 | * @since 0.1.0 373 | * @access public 374 | */ 375 | init: function() { 376 | if ( ! currentImplementation ) { 377 | maybeLogError(); 378 | return; 379 | } 380 | 381 | if ( ! _.isFunction( currentImplementation.init ) ) { 382 | return; 383 | } 384 | 385 | currentImplementation.init(); 386 | }, 387 | 388 | /** 389 | * Closes the cache. 390 | * 391 | * This method is optional and does not need to be implemented by the cache 392 | * implementation unless necessary. 393 | * 394 | * @since 0.1.0 395 | * @access public 396 | */ 397 | close: function() { 398 | if ( ! currentImplementation ) { 399 | maybeLogError(); 400 | return; 401 | } 402 | 403 | if ( ! _.isFunction( currentImplementation.close ) ) { 404 | return; 405 | } 406 | 407 | currentImplementation.close(); 408 | }, 409 | 410 | /** 411 | * Registers a cache implementation. 412 | * 413 | * @since 0.1.0 414 | * @access public 415 | * 416 | * @param {string} identifier Unique identifier for the implementation. 417 | * @param {object} implementation The actual implementation. 418 | * @param {number} [priority=10] Priority for the implementation. 419 | * @returns {boolean} True on success, false on failure. 420 | */ 421 | registerImplementation: function( identifier, implementation, priority ) { 422 | var requiredMethods = [ 423 | 'add', 424 | 'replace', 425 | 'set', 426 | 'get', 427 | 'remove', 428 | 'flush', 429 | 'incr', 430 | 'decr', 431 | 'switchToSite', 432 | 'switchToNetwork', 433 | 'addNetworkGroups', 434 | 'addGlobalGroups', 435 | 'addNonPersistentGroups', 436 | 'checkRequirements' 437 | ], i; 438 | 439 | for ( i in requiredMethods ) { 440 | if ( ! _.isFunction( implementation[ requiredMethods[ i ] ] ) ) { 441 | return false; 442 | } 443 | } 444 | 445 | priority = priority || 10; 446 | 447 | implementation.identifier = identifier; 448 | implementation.priority = priority; 449 | 450 | if ( _.isUndefined( implementations[ priority ] ) ) { 451 | implementations[ priority ] = []; 452 | } 453 | 454 | implementations[ priority ].push( implementation ); 455 | 456 | if ( ! currentImplementation || currentImplementation.priority > priority ) { 457 | setCurrentImplementation(); 458 | } 459 | 460 | return true; 461 | } 462 | }; 463 | })( window, window._ ); 464 | -------------------------------------------------------------------------------- /js/local-storage.js: -------------------------------------------------------------------------------- 1 | ( function( cache, _, settings ) { 2 | 'use strict'; 3 | 4 | var nonPersistentData = {}, 5 | nonPersistentGroups = {}, 6 | networkGroups = {}, 7 | globalGroups = {}, 8 | currentSiteId = settings.siteId, 9 | currentNetworkId = settings.networkId, 10 | implementation; 11 | 12 | function getCurrentTime() { 13 | return Math.floor( Date.now() / 1000 ); 14 | } 15 | 16 | function getFullKey( key, group ) { 17 | if ( globalGroups[ group ] ) { 18 | return key; 19 | } 20 | 21 | if ( networkGroups[ group ] ) { 22 | return 'network' + currentNetworkId + '_' + key; 23 | } 24 | 25 | return 'site' + currentSiteId + '_' + key; 26 | } 27 | 28 | function getLocalStorageIdentifier( key, group, isFullKey ) { 29 | if ( ! isFullKey ) { 30 | key = getFullKey( key, group ); 31 | } 32 | 33 | return 'wpCache:' + group + ':' + key; 34 | } 35 | 36 | function getFullDataObject( data, expire ) { 37 | if ( _.isObject( data ) && ! _.isNull( data ) && ! _.isArray( data ) ) { 38 | data = _.extend({}, data ); 39 | } 40 | 41 | if ( expire ) { 42 | expire = getCurrentTime() + expire; 43 | } 44 | 45 | return { 46 | data: data, 47 | expire: expire 48 | }; 49 | } 50 | 51 | function getFromFullDataObject( dataObject ) { 52 | if ( _.isObject( dataObject.data ) && ! _.isNull( dataObject.data ) && ! _.isArray( dataObject.data ) ) { 53 | return _.extend({}, dataObject.data ); 54 | } 55 | 56 | return dataObject.data; 57 | } 58 | 59 | function exists( key, group, isFullKey ) { 60 | var item; 61 | 62 | key = getLocalStorageIdentifier( key, group, isFullKey ); 63 | 64 | item = localStorage.getItem( key ); 65 | if ( ! item ) { 66 | return false; 67 | } 68 | 69 | item = JSON.parse( item ); 70 | if ( ! _.isObject( item ) ) { 71 | localStorage.removeItem( key ); 72 | return false; 73 | } 74 | 75 | if ( item.expire && item.expire < getCurrentTime() ) { 76 | localStorage.removeItem( key ); 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | function existsNonPersistent( key, group, isFullKey ) { 84 | var item; 85 | 86 | if ( _.isUndefined( nonPersistentData[ group ] ) ) { 87 | return false; 88 | } 89 | 90 | if ( ! isFullKey ) { 91 | key = getFullKey( key, group ); 92 | } 93 | 94 | if ( _.isUndefined( nonPersistentData[ group ][ key ] ) ) { 95 | return false; 96 | } 97 | 98 | item = nonPersistentData[ group ][ key ]; 99 | if ( item.expire && item.expire < getCurrentTime() ) { 100 | delete nonPersistentData[ group ][ key ]; 101 | return false; 102 | } 103 | 104 | return true; 105 | } 106 | 107 | implementation = { 108 | 109 | add: function( key, data, group, expire ) { 110 | if ( nonPersistentGroups[ group ] ) { 111 | if ( existsNonPersistent( key, group ) ) { 112 | return false; 113 | } 114 | 115 | return implementation.set( key, data, group, expire ); 116 | } 117 | 118 | if ( exists( key, group ) ) { 119 | return false; 120 | } 121 | 122 | return implementation.set( key, data, group, expire ); 123 | }, 124 | 125 | replace: function( key, data, group, expire ) { 126 | if ( nonPersistentGroups[ group ] ) { 127 | if ( ! existsNonPersistent( key, group ) ) { 128 | return false; 129 | } 130 | 131 | return implementation.set( key, data, group, expire ); 132 | } 133 | 134 | if ( ! exists( key, group ) ) { 135 | return false; 136 | } 137 | 138 | return implementation.set( key, data, group, expire ); 139 | }, 140 | 141 | set: function( key, data, group, expire ) { 142 | key = getFullKey( key, group ); 143 | data = getFullDataObject( data, expire ); 144 | 145 | if ( nonPersistentGroups[ group ] ) { 146 | if ( _.isUndefined( nonPersistentData[ group ] ) ) { 147 | nonPersistentData[ group ] = {}; 148 | } 149 | 150 | nonPersistentData[ group ][ key ] = data; 151 | 152 | return true; 153 | } 154 | 155 | key = getLocalStorageIdentifier( key, group, true ); 156 | localStorage.setItem( key, JSON.stringify( data ) ); 157 | 158 | return true; 159 | }, 160 | 161 | get: function( key, group ) { 162 | key = getFullKey( key, group ); 163 | 164 | if ( nonPersistentGroups[ group ] ) { 165 | if ( ! existsNonPersistent( key, group, true ) ) { 166 | return undefined; 167 | } 168 | 169 | return getFromFullDataObject( nonPersistentData[ group ][ key ] ); 170 | } 171 | 172 | if ( ! exists( key, group, true ) ) { 173 | return undefined; 174 | } 175 | 176 | key = getLocalStorageIdentifier( key, group, true ); 177 | 178 | return getFromFullDataObject( JSON.parse( localStorage.getItem( key ) ) ); 179 | }, 180 | 181 | remove: function( key, group ) { 182 | key = getFullKey( key, group ); 183 | 184 | if ( nonPersistentGroups[ group ] ) { 185 | if ( ! existsNonPersistent( key, group, true ) ) { 186 | return false; 187 | } 188 | 189 | delete nonPersistentData[ group ][ key ]; 190 | 191 | return true; 192 | } 193 | 194 | if ( ! exists( key, group, true ) ) { 195 | return false; 196 | } 197 | 198 | key = getLocalStorageIdentifier( key, group, true ); 199 | 200 | localStorage.removeItem( key ); 201 | 202 | return true; 203 | }, 204 | 205 | flush: function() { 206 | var keys, i; 207 | 208 | nonPersistentData = {}; 209 | 210 | keys = Object.keys( localStorage ); 211 | for ( i in keys ) { 212 | if ( 'wpCache:' === keys[ i ].substring( 0, 8 ) ) { 213 | localStorage.removeItem( keys[ i ] ); 214 | } 215 | } 216 | 217 | return true; 218 | }, 219 | 220 | incr: function( key, offset, group ) { 221 | var dataObject; 222 | 223 | key = getFullKey( key, group ); 224 | 225 | if ( nonPersistentGroups[ group ] ) { 226 | if ( ! existsNonPersistent( key, group, true ) ) { 227 | return false; 228 | } 229 | 230 | if ( ! _.isNumber( nonPersistentData[ group ][ key ].data ) ) { 231 | nonPersistentData[ group ][ key ].data = 0; 232 | } 233 | 234 | nonPersistentData[ group ][ key ].data += offset; 235 | 236 | if ( nonPersistentData[ group ][ key ].data < 0 ) { 237 | nonPersistentData[ group ][ key ].data = 0; 238 | } 239 | 240 | return nonPersistentData[ group ][ key ].data; 241 | } 242 | 243 | if ( ! exists( key, group, true ) ) { 244 | return false; 245 | } 246 | 247 | key = getLocalStorageIdentifier( key, group, true ); 248 | 249 | dataObject = JSON.parse( localStorage.getItem( key ) ); 250 | 251 | if ( ! _.isNumber( dataObject.data ) ) { 252 | dataObject.data = 0; 253 | } 254 | 255 | dataObject.data += offset; 256 | 257 | if ( dataObject.data < 0 ) { 258 | dataObject.data = 0; 259 | } 260 | 261 | localStorage.setItem( key, JSON.stringify( dataObject ) ); 262 | 263 | return dataObject.data; 264 | }, 265 | 266 | decr: function( key, offset, group ) { 267 | var dataObject; 268 | 269 | key = getFullKey( key, group ); 270 | 271 | if ( nonPersistentGroups[ group ] ) { 272 | if ( ! existsNonPersistent( key, group, true ) ) { 273 | return false; 274 | } 275 | 276 | if ( ! _.isNumber( nonPersistentData[ group ][ key ].data ) ) { 277 | nonPersistentData[ group ][ key ].data = 0; 278 | } 279 | 280 | nonPersistentData[ group ][ key ].data -= offset; 281 | 282 | if ( nonPersistentData[ group ][ key ].data < 0 ) { 283 | nonPersistentData[ group ][ key ].data = 0; 284 | } 285 | 286 | return nonPersistentData[ group ][ key ].data; 287 | } 288 | 289 | if ( ! exists( key, group, true ) ) { 290 | return false; 291 | } 292 | 293 | key = getLocalStorageIdentifier( key, group, true ); 294 | 295 | dataObject = JSON.parse( localStorage.getItem( key ) ); 296 | 297 | if ( ! _.isNumber( dataObject.data ) ) { 298 | dataObject.data = 0; 299 | } 300 | 301 | dataObject.data -= offset; 302 | 303 | if ( dataObject.data < 0 ) { 304 | dataObject.data = 0; 305 | } 306 | 307 | localStorage.setItem( key, JSON.stringify( dataObject ) ); 308 | 309 | return dataObject.data; 310 | }, 311 | 312 | switchToSite: function( siteId ) { 313 | currentSiteId = siteId; 314 | 315 | return true; 316 | }, 317 | 318 | switchToNetwork: function( networkId ) { 319 | currentNetworkId = networkId; 320 | 321 | return true; 322 | }, 323 | 324 | addNetworkGroups: function( groups ) { 325 | var i; 326 | 327 | if ( ! _.isArray( groups ) ) { 328 | groups = [ groups ]; 329 | } 330 | 331 | for ( i in groups ) { 332 | if ( networkGroups[ groups[ i ] ] ) { 333 | continue; 334 | } 335 | 336 | networkGroups[ groups[ i ] ] = true; 337 | } 338 | }, 339 | 340 | addGlobalGroups: function( groups ) { 341 | var i; 342 | 343 | if ( ! _.isArray( groups ) ) { 344 | groups = [ groups ]; 345 | } 346 | 347 | for ( i in groups ) { 348 | if ( globalGroups[ groups[ i ] ] ) { 349 | continue; 350 | } 351 | 352 | globalGroups[ groups[ i ] ] = true; 353 | } 354 | }, 355 | 356 | addNonPersistentGroups: function( groups ) { 357 | var i; 358 | 359 | if ( ! _.isArray( groups ) ) { 360 | groups = [ groups ]; 361 | } 362 | 363 | for ( i in groups ) { 364 | if ( nonPersistentGroups[ groups[ i ] ] ) { 365 | continue; 366 | } 367 | 368 | nonPersistentGroups[ groups[ i ] ] = true; 369 | } 370 | }, 371 | 372 | checkRequirements: function() { 373 | return ! _.isUndefined( localStorage ) && ! _.isUndefined( JSON ); 374 | } 375 | }; 376 | 377 | cache.registerImplementation( 'localStorage', implementation, 10 ); 378 | 379 | })( window.wp.cache, window._, window.wpCacheSettings ); 380 | -------------------------------------------------------------------------------- /js/variable-storage.js: -------------------------------------------------------------------------------- 1 | ( function( cache, _, settings ) { 2 | 'use strict'; 3 | 4 | var cachedData = {}, 5 | networkGroups = {}, 6 | globalGroups = {}, 7 | currentSiteId = settings.siteId, 8 | currentNetworkId = settings.networkId, 9 | implementation; 10 | 11 | function getCurrentTime() { 12 | return Math.floor( Date.now() / 1000 ); 13 | } 14 | 15 | function getFullKey( key, group ) { 16 | if ( globalGroups[ group ] ) { 17 | return key; 18 | } 19 | 20 | if ( networkGroups[ group ] ) { 21 | return 'network' + currentNetworkId + '_' + key; 22 | } 23 | 24 | return 'site' + currentSiteId + '_' + key; 25 | } 26 | 27 | function getFullDataObject( data, expire ) { 28 | if ( _.isObject( data ) && ! _.isNull( data ) && ! _.isArray( data ) ) { 29 | data = _.extend({}, data ); 30 | } 31 | 32 | if ( expire ) { 33 | expire = getCurrentTime() + expire; 34 | } 35 | 36 | return { 37 | data: data, 38 | expire: expire 39 | }; 40 | } 41 | 42 | function getFromFullDataObject( dataObject ) { 43 | if ( _.isObject( dataObject.data ) && ! _.isNull( dataObject.data ) && ! _.isArray( dataObject.data ) ) { 44 | return _.extend({}, dataObject.data ); 45 | } 46 | 47 | return dataObject.data; 48 | } 49 | 50 | function exists( key, group, isFullKey ) { 51 | var item; 52 | 53 | if ( _.isUndefined( cachedData[ group ] ) ) { 54 | return false; 55 | } 56 | 57 | if ( ! isFullKey ) { 58 | key = getFullKey( key, group ); 59 | } 60 | 61 | if ( _.isUndefined( cachedData[ group ][ key ] ) ) { 62 | return false; 63 | } 64 | 65 | item = cachedData[ group ][ key ]; 66 | if ( item.expire && item.expire < getCurrentTime() ) { 67 | delete cachedData[ group ][ key ]; 68 | return false; 69 | } 70 | 71 | return true; 72 | } 73 | 74 | implementation = { 75 | 76 | add: function( key, data, group, expire ) { 77 | if ( exists( key, group ) ) { 78 | return false; 79 | } 80 | 81 | return implementation.set( key, data, group, expire ); 82 | }, 83 | 84 | replace: function( key, data, group, expire ) { 85 | if ( ! exists( key, group ) ) { 86 | return false; 87 | } 88 | 89 | return implementation.set( key, data, group, expire ); 90 | }, 91 | 92 | set: function( key, data, group, expire ) { 93 | key = getFullKey( key, group ); 94 | data = getFullDataObject( data, expire ); 95 | 96 | if ( _.isUndefined( cachedData[ group ] ) ) { 97 | cachedData[ group ] = {}; 98 | } 99 | 100 | cachedData[ group ][ key ] = data; 101 | 102 | return true; 103 | }, 104 | 105 | get: function( key, group ) { 106 | key = getFullKey( key, group ); 107 | 108 | if ( ! exists( key, group, true ) ) { 109 | return undefined; 110 | } 111 | 112 | return getFromFullDataObject( cachedData[ group ][ key ] ); 113 | }, 114 | 115 | remove: function( key, group ) { 116 | key = getFullKey( key, group ); 117 | 118 | if ( ! exists( key, group, true ) ) { 119 | return false; 120 | } 121 | 122 | delete cachedData[ group ][ key ]; 123 | 124 | return true; 125 | }, 126 | 127 | flush: function() { 128 | cachedData = {}; 129 | 130 | return true; 131 | }, 132 | 133 | incr: function( key, offset, group ) { 134 | key = getFullKey( key, group ); 135 | 136 | if ( ! exists( key, group, true ) ) { 137 | return false; 138 | } 139 | 140 | if ( ! _.isNumber( cachedData[ group ][ key ].data ) ) { 141 | cachedData[ group ][ key ].data = 0; 142 | } 143 | 144 | cachedData[ group ][ key ].data += offset; 145 | 146 | if ( cachedData[ group ][ key ].data < 0 ) { 147 | cachedData[ group ][ key ].data = 0; 148 | } 149 | 150 | return cachedData[ group ][ key ].data; 151 | }, 152 | 153 | decr: function( key, offset, group ) { 154 | key = getFullKey( key, group ); 155 | 156 | if ( ! exists( key, group, true ) ) { 157 | return false; 158 | } 159 | 160 | if ( ! _.isNumber( cachedData[ group ][ key ].data ) ) { 161 | cachedData[ group ][ key ].data = 0; 162 | } 163 | 164 | cachedData[ group ][ key ].data -= offset; 165 | 166 | if ( cachedData[ group ][ key ].data < 0 ) { 167 | cachedData[ group ][ key ].data = 0; 168 | } 169 | 170 | return cachedData[ group ][ key ].data; 171 | }, 172 | 173 | switchToSite: function( siteId ) { 174 | currentSiteId = siteId; 175 | 176 | return true; 177 | }, 178 | 179 | switchToNetwork: function( networkId ) { 180 | currentNetworkId = networkId; 181 | 182 | return true; 183 | }, 184 | 185 | addNetworkGroups: function( groups ) { 186 | var i; 187 | 188 | if ( ! _.isArray( groups ) ) { 189 | groups = [ groups ]; 190 | } 191 | 192 | for ( i in groups ) { 193 | if ( networkGroups[ groups[ i ] ] ) { 194 | continue; 195 | } 196 | 197 | networkGroups[ groups[ i ] ] = true; 198 | } 199 | }, 200 | 201 | addGlobalGroups: function( groups ) { 202 | var i; 203 | 204 | if ( ! _.isArray( groups ) ) { 205 | groups = [ groups ]; 206 | } 207 | 208 | for ( i in groups ) { 209 | if ( globalGroups[ groups[ i ] ] ) { 210 | continue; 211 | } 212 | 213 | globalGroups[ groups[ i ] ] = true; 214 | } 215 | }, 216 | 217 | addNonPersistentGroups: function() { 218 | /* Default cache doesn't persist so nothing to do here. */ 219 | }, 220 | 221 | checkRequirements: function() { 222 | return true; 223 | } 224 | }; 225 | 226 | cache.registerImplementation( 'variableStorage', implementation, 100 ); 227 | 228 | })( window.wp.cache, window._, window.wpCacheSettings ); 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-cache-js", 3 | "version": "0.1.0", 4 | "description": "A JavaScript-based cache solution for WordPress using the client's local storage or, as fallback, a simple variable storage.", 5 | "keywords": [ 6 | "wordpress", 7 | "plugin", 8 | "javascript", 9 | "library", 10 | "cache" 11 | ], 12 | "author": { 13 | "name": "Felix Arntz", 14 | "email": "felix-arntz@leaves-and-love.net", 15 | "url": "https://leaves-and-love.net" 16 | }, 17 | "homepage": "https://github.com/felixarntz/wp-cache-js", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/felixarntz/wp-cache-js.git" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/felixarntz/wp-cache-js/issues" 24 | }, 25 | "license": "GPL-3.0", 26 | "engines": { 27 | "node": ">= 0.10.0" 28 | }, 29 | "devDependencies": { 30 | "gulp": "~3.9.1", 31 | "gulp-jscs": "~4.0.0", 32 | "gulp-jshint": "~2.0.4", 33 | "gulp-concat": "~2.6.1", 34 | "gulp-uglify": "~2.0.1", 35 | "gulp-rename": "~1.2.2", 36 | "gulp-replace": "~0.5.4", 37 | "jshint": "~2.9.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === wp-cache.js === 2 | 3 | Plugin Name: wp-cache.js 4 | Plugin URI: https://github.com/felixarntz/wp-cache-js 5 | Author: Felix Arntz 6 | Author URI: https://leaves-and-love.net 7 | Contributors: flixos90 8 | Requires at least: 4.6 9 | Tested up to: 4.6 10 | Stable tag: 0.1.0 11 | Version: 0.1.0 12 | License: GNU General Public License v3 13 | License URI: http://www.gnu.org/licenses/gpl-3.0.html 14 | Tags: javascript, library, cache 15 | 16 | A JavaScript-based cache solution for WordPress using the client's local storage or, as fallback, a simple variable storage. 17 | 18 | == Description == 19 | 20 | The script exposes a `wp.cache` object with the following methods: 21 | 22 | * `add( key, data, group, expire )` 23 | * `replace( key, data, group, expire )` 24 | * `set( key, data, group, expire )` 25 | * `get( key, group, force )` 26 | * `remove( key, group )` 27 | * `flush()` 28 | * `incr( key, offset, group )` 29 | * `decr( key, offset, group )` 30 | * `switchToSite( siteId )` 31 | * `switchToNetwork( networkId )` 32 | * `addNetworkGroups( groups )` 33 | * `addGlobalGroups( groups )` 34 | * `addNonPersistentGroups( groups )` 35 | * `init()` 36 | * `close()` 37 | 38 | == Installation == 39 | 40 | 1. Upload the entire `wp-cache-js` folder to the `/wp-content/plugins/` directory or download it through the WordPress backend. 41 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 42 | 3. Enqueue the script by using `wp_enqueue_script( 'wp-cache' )` or by declaring it as a dependency to one of your scripts, such as `wp_enqueue_script( 'my-script', '/path/to/my/script.js', array( 'wp-cache' ) )`. 43 | 44 | == Frequently Asked Questions == 45 | 46 | = How can I register my own cache implementation? = 47 | 48 | You can register your own cache implementation if you would like to add support for another type of cache to use in JavaScript, just like you can place a cache drop-in for WordPress server-side. 49 | 50 | In order to do that, you need to create your own object which implements all of the methods mentioned in the Description section (except for `init()` and `close()` which can be optionally implemented), plus an additional `checkRequirements()` method that should return `true|false` depending on whether the client fulfills the requirements for this cache type. 51 | 52 | You can then register your object using `wp.cache.registerImplementation( identifier, implementationObject, priority )` with `identifier` being a unique identifier for your implementation and `priority` being a numeric value to determine the priority in which to check for the implementation. The default `localStorage` implementation has a priority of 10 and the `variableStorage` implementation a priority of 100. 53 | 54 | == Changelog == 55 | 56 | = 0.1.0 = 57 | 58 | * Initial release. 59 | -------------------------------------------------------------------------------- /wp-cache-js.php: -------------------------------------------------------------------------------- 1 | add( 'wp-cache', plugin_dir_url( __FILE__ ) . "build/js/wp-cache$suffix.js", array( 'underscore' ), $version, true ); 22 | did_action( 'init' ) && $scripts->localize( 'wp-cache', 'wpCacheSettings', array( 23 | 'siteId' => get_current_blog_id(), 24 | 'networkId' => get_current_network_id(), 25 | 'isMultisite' => is_multisite(), 26 | 'i18n' => array( 27 | 'noImplementationSet' => __( 'No cache implementation set.', 'wp-cache-js' ), 28 | ), 29 | ) ); 30 | } 31 | add_action( 'wp_default_scripts', 'wp_cache_js_register_script', 11, 1 ); 32 | --------------------------------------------------------------------------------