Ticket #1112: 1112.2.diff
File 1112.2.diff, 35.8 KB (added by , 9 years ago) |
---|
-
wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php index 8628d2c..88bc3d1 100644
a b namespace WordCamp\Site_Cloner; 5 5 defined( 'WPINC' ) or die(); 6 6 7 7 /** 8 * Custom Customizer Control for a WordCamp site8 * Custom Customizer Control for Search WordCamp sites to clone 9 9 */ 10 10 class Site_Control extends \WP_Customize_Control { 11 public $site_id, $site_name, $screenshot_url, $theme_slug; 12 public $settings = 'wcsc_source_site_id'; 13 public $section = 'wcsc_sites'; 11 12 public function __construct( $manager, $id, $args = [ ] ) { 13 parent::__construct( $manager, $id, $args ); 14 15 $this->capability = 'edit_theme_options'; 16 $this->section = 'wcsc_sites'; 17 } 14 18 15 19 /** 16 20 * Enqueue scripts and styles … … class Site_Control extends \WP_Customize_Control { 18 22 public function enqueue() { 19 23 wp_enqueue_style( 'wordcamp-site-cloner' ); 20 24 wp_enqueue_script( 'wordcamp-site-cloner' ); 25 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_view_templates' ) ); 21 26 } 22 27 23 /**24 * Render the control's content25 */26 28 public function render_content() { 27 $preview_url = add_query_arg(28 array(29 'theme' => rawurlencode( $this->theme_slug ),30 'wcsc_source_site_id' => rawurlencode( $this->site_id ),31 ),32 admin_url( 'customize.php' )33 );34 35 29 require( dirname( __DIR__ ) . '/templates/site-control.php' ); 36 30 } 31 32 public function print_view_templates() { 33 ?> 34 <script id="tmpl-wcsc-site-option" type="text/html"> 35 <?php include dirname( __DIR__ ) . '/templates/site-option.php'; ?> 36 </script> 37 <script id="tmpl-wcsc-site-filters" type="text/html"> 38 <?php include dirname( __DIR__ ) . '/templates/site-filters.php'; ?> 39 </script> 40 <?php 41 } 37 42 } -
deleted file wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php deleted file mode 100644 index 69b85e4..0000000
+ - 1 <?php2 3 namespace WordCamp\Site_Cloner;4 5 defined( 'WPINC' ) or die();6 7 /**8 * Custom Customizer Section for WordCamp sites9 */10 class Sites_Section extends \WP_Customize_Section {11 public $type = 'wcsc-sites';12 13 /**14 * Render the section's content15 */16 protected function render() {17 require_once( dirname( __DIR__ ) . '/templates/sites-section.php' );18 }19 } -
wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php index 952a134..9b60dba 100644
a b 1 <?php defined( 'WPINC' ) or die(); ?> 1 <?php 2 defined( 'WPINC' ) or die(); 2 3 3 <div id="wcsc-site-<?php echo esc_attr( $this->site_id ); ?>" class="wcscSite" data-preview-url="<?php echo esc_url( $preview_url ); ?>"> 4 <div class="wcsc-site-screenshot"> 5 <img src="<?php echo esc_url( $this->screenshot_url ); ?>" alt="<?php echo esc_attr( $this->site_name ); ?>" /> 6 </div> 7 8 <h3 class="wcsc-site-name"> 9 <?php echo esc_html( $this->site_name ); ?> 4 /** 5 * Top level template for the output of the Site Cloner Customizer Control 6 */ 7 ?> 8 <div id="wcsc-cloner"> 9 <h3> 10 <?php esc_html_e( 'WordCamp Sites', 'wordcamporg' ); ?> 11 <span id="wcsc-sites-count" class="title-count wcsc-sites-count"></span> 10 12 </h3> 13 <div class="filters"> 14 </div> 11 15 12 <span id="live-preview-label-<?php echo esc_attr( $this->site_id ); ?>" class="wcsc-live-preview-label"> 13 <?php _e( 'Live Preview', 'wordcamporg' ); ?> 14 </span> 15 </div> 16 <div class="wcsc-search"> 17 <ul id="wcsc-results"> 18 </ul> 19 </div> 20 </div> 21 No newline at end of file -
new file wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php new file mode 100644 index 0000000..d68edc3
- + 1 <?php 2 /** 3 * Template for the search and drop down filters 4 */ 5 6 defined( 'WPINC' ) or die(); 7 8 ?> 9 <div class="wcsc-filter"> 10 <label for="wcsc-filter-search-input"> 11 <span class="customize-control-title"><?php _e( 'Search', 'wordcamporg' ); ?></span> 12 <div class="customize-control-content"> 13 <input type="search" id="wcsc-filter-search-input" class="wcsc-filter-search"/> 14 </div> 15 </label> 16 </div> 17 18 <div class="wcsc-filter"> 19 <label for="wcsc-filter-theme_slug"> 20 <span class="customize-control-title"><?php _e( 'Theme', 'wordcamporg' ); ?></span> 21 <div class="customize-control-content"> 22 <select id="wcsc-filter-theme_slug" data-filter="theme_slug"> 23 <option value="">Any</option> 24 <# _.each(data.themeOptions, function(themeOption) { #> 25 <option value="{{themeOption}}">{{themeOption}}</option> 26 <# }); #> 27 </select> 28 </div> 29 </label> 30 </div> 31 32 <div class="wcsc-filter"> 33 <label for="wcsc-filter-year"> 34 <span class="customize-control-title"><?php _e( 'WordCamp Year', 'wordcamporg' ); ?></span> 35 <div class="customize-control-content"> 36 <select id="wcsc-filter-year" data-filter="year"> 37 <option value="">Any</option> 38 <# _.each(data.yearOptions, function(yearOption) { #> 39 <option value="{{yearOption}}">{{yearOption}}</option> 40 <# }); #> 41 </select> 42 </div> 43 </label> 44 </div> 45 46 <div class="wcsc-filter"> 47 <label for="wcsc-filter-css_preprocessor"> 48 <span class="customize-control-title"><?php _e( 'CSS Preprocessor', 'wordcamporg' ); ?></span> 49 <div class="customize-control-content"> 50 <select id="wcsc-filter-css_preprocessor" data-filter="css_preprocessor"> 51 <option value="">Any</option> 52 <# _.each(data.preprocessorOptions, function(preprocessorOption) { #> 53 <option value="{{preprocessorOption}}">{{preprocessorOption}}</option> 54 <# }); #> 55 </select> 56 </div> 57 </label> 58 </div> 59 <?php 60 No newline at end of file -
new file wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php new file mode 100644 index 0000000..453a407
- + 1 <?php 2 /** 3 * Template for a single Site representation within the Site Cloner Control 4 */ 5 6 defined( 'WPINC' ) or die(); 7 8 ?> 9 <div class="wcsc-site-screenshot"> 10 <img src="{{ data.screenshot_url }}" alt="{{ data.name }}"/> 11 </div> 12 13 <h3 class="wcsc-site-name"> 14 {{ data.name }} 15 </h3> 16 17 <span id="live-preview-label-{{ data.site_id }}" class="wcsc-live-preview-label"> 18 <?php _e( 'Live Preview', 'wordcamporg' ); ?> 19 </span> 20 <?php 21 No newline at end of file -
deleted file wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php deleted file mode 100644 index a05272c..0000000
+ - 1 <?php defined( 'WPINC' ) or die(); ?>2 3 <li id="section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section control-section control-section-<?php echo esc_attr( $this->type ); ?>">4 <h3>5 <?php esc_html_e( 'WordCamp Sites' ); ?>6 7 <span class="title-count wcsc-sites-count">8 <?php echo count( $this->controls ); ?>9 </span>10 </h3>11 12 <div class="wcsc-sites-section-content">13 <ul id="wcsc-sites"></ul>14 </div>15 </li> -
wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css index 0a33876..afde3c1 100644
a b 6 6 overflow: auto; 7 7 } 8 8 9 .wcsc Site {9 .wcsc-site { 10 10 position: relative; 11 11 cursor: pointer; 12 12 border: 1px solid #DEDEDE; 13 13 box-shadow: 0 1px 1px -1px rgba( 0, 0, 0, 0.1 ); 14 margin-top: 5px; 14 15 } 15 16 16 17 .wcsc-site-screenshot { 17 18 transition: opacity 0.2s ease-in-out 0s; 18 19 } 19 20 20 .wcsc Site:hover .wcsc-site-screenshot {21 .wcsc-site:hover .wcsc-site-screenshot { 21 22 opacity: 0.4; 22 23 } 23 24 … … 38 39 transition: opacity 0.1s ease-in-out 0s; 39 40 } 40 41 41 .wcsc Site:hover .wcsc-live-preview-label {42 .wcsc-site:hover .wcsc-live-preview-label { 42 43 opacity: 1; 43 44 } 45 46 div.wcsc-filter { 47 margin-bottom: 10px; 48 } 49 No newline at end of file -
wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js index 5c05f82..0dcec81 100644
a b 1 ( function( wp, $) {1 (function ( wp, $, Backbone, win, settings ) { 2 2 'use strict'; 3 3 4 if ( ! wp || !wp.customize ) {4 if ( !wp || !wp.customize ) { 5 5 return; 6 6 } 7 7 8 var api = wp.customize; 8 var api = wp.customize, 9 wcsc = wp.wcSiteCloner = { models: {}, views: {}, collections: {}, routers: {}, settings: {} }; 9 10 10 /** 11 * The Clone Another WordCamp panel 12 */ 13 api.panelConstructor.wcscPanel = api.Panel.extend( { 14 /** 15 * Initialize the panel after it's loaded 16 * 17 * Ideally, the Previewer would be set to the requested site ID during the initial PHP request, rather than 18 * loading the host site in the Previewer, and then refreshing it to use the requested site. That became a 19 * rabbit hole, though, so it's done this way instead. 20 */ 21 ready : function() { 22 var urlParams = getUrlParams( window.location.href ); 11 wcsc.settings = settings || {}; 23 12 24 if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) { 25 this.expand(); 26 api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id ); 13 // Model for a single site 14 wcsc.models.Site = Backbone.Model.extend( { 15 idAttribute: 'site_id' 16 } ); 17 18 wcsc.models.SearchFilter = Backbone.Model.extend( { 19 's': '', 20 'theme_slug': '', 21 'year': '', 22 'css_preprocessor': '' 23 } ); 24 25 // Top level view for the Site Cloner Control 26 wcsc.views.SiteSearch = Backbone.View.extend( { 27 el: '#wcsc-cloner .wcsc-search', 28 29 // index of the currently viewed page of results 30 page: 0, 31 32 initialize: function ( options ) { 33 34 // update scroller position 35 _.bindAll( this, 'scroller' ); 36 37 // container that will be scrolled within 38 this.$container = $( '#wcsc-cloner' ).parents( 'ul.accordion-section-content' ); 39 // bind scrolling within the container to check for infinite scroll 40 this.$container.bind( 'scroll', _.throttle( this.scroller, 300 ) ); 41 42 // the model and view for filtering the site results 43 this.filter = options.filter; 44 this.filterView = new wcsc.views.SearchFilters( { 45 model: this.filter, 46 parent: this 47 } ); 48 49 // View for listing the matching sites 50 this.resultsView = new wcsc.views.SearchResults( { 51 collection: this.collection, 52 parent: this 53 } ); 54 }, 55 56 render: function () { 57 this.filterView.render(); 58 59 this.resultsView.render(); 60 61 this.$el.empty().append( this.resultsView.el ); 62 }, 63 64 // Checks if a user has reached the bottom of the list 65 // and triggers a scroll event to show more sites if needed 66 scroller: function () { 67 var visibleBottom, threshold, elementHeight, containerHeight, scrollTop; 68 69 scrollTop = this.$container.scrollTop(); 70 containerHeight = this.$container.innerHeight(); 71 elementHeight = this.$container.get( 0 ).scrollHeight; 72 73 visibleBottom = scrollTop + containerHeight; 74 threshold = Math.round( elementHeight * 0.9 ); 75 76 if ( visibleBottom > threshold ) { 77 this.trigger( 'wcsc:scroll' ); 27 78 } 28 79 } 80 29 81 } ); 30 82 31 /** 32 * Custom control representing a site that can be previewed/imported 33 */ 34 api.controlConstructor.wcscSite = api.Control.extend( { 35 /** 36 * Initialize the control after it's loaded 37 */ 38 ready : function() { 39 this.container.on( 'click', '.wcscSite', this.previewSite ); 40 }, 41 42 /** 43 * Preview the selected site 44 * 45 * If the site is using a different theme, then reload the entire Customizer with the theme URL parameter 46 * set, so that the Theme Switcher will handle previewing the new theme for us. Otherwise just set the ID 47 * to refresh the Previewer with the current theme and the new site's CSS, etc. 48 * 49 * @param {object} event 50 */ 51 previewSite : function( event ) { 52 var previewUrl = $( this ).data( 'preview-url' ), 53 previewUrlParams = getUrlParams( previewUrl ); 54 55 if ( api( 'wcsc_source_site_id' ).get() == previewUrlParams.wcsc_source_site_id ) { 83 // Collection representing the list of cloneable sites 84 wcsc.collections.Sites = Backbone.Collection.extend( { 85 model: wcsc.models.Site, 86 url: wcsc.settings.apiUrl, 87 88 initialize: function ( options ) { 89 this.searchFilter = options.searchFilter || {}; 90 91 this.listenTo( this.searchFilter, 'change', this.applyFilter ); 92 }, 93 94 applyFilter: function () { 95 var filters = this.searchFilter.toJSON(), 96 activeFilters = _.pick( filters, _.identity ), 97 term = '', 98 sites; 99 100 // nothing actually changed, so don't update the collection 101 if ( _.isEmpty( this.searchFilter.changedAttributes() ) ) { 56 102 return; 57 103 } 58 104 59 if ( api.settings.theme.stylesheet === previewUrlParams.theme ) { 60 api( 'wcsc_source_site_id' ).set( previewUrlParams.wcsc_source_site_id ); 105 //no active filters. Reset to the full list and bail 106 if ( _.isEmpty( activeFilters ) ) { 107 this.resetCanonical(); 108 return; 109 } 110 111 this.resetCanonical( { silent: true } ); 112 113 if ( activeFilters.s ) { 114 term = activeFilters.s; 115 delete activeFilters.s; 116 } 117 118 sites = this.where( activeFilters ); 119 120 if ( term ) { 121 sites = this.filterBySearch( sites, term ); 122 } 123 124 this.reset( sites ); 125 126 }, 127 128 filterBySearch: function ( sites, term ) { 129 var match, name; 130 131 // Escape the term string for RegExp meta characters 132 term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); 133 134 // Consider spaces as word delimiters and match the whole string 135 // so matching terms can be combined 136 term = term.replace( / /g, ')(?=.*' ); 137 match = new RegExp( '^(?=.*' + term + ').+', 'i' ); 138 139 return _.filter( sites, function ( site ) { 140 name = site.get( 'name' ).replace( /(<([^>]+)>)/ig, '' ); 141 142 return match.test( name ); 143 } ); 144 }, 145 146 paginate: function ( pageIndex ) { 147 var collection = this; 148 pageIndex = pageIndex || 0; 149 150 collection = _( collection.rest( 20 * pageIndex ) ); 151 collection = _( collection.first( 20 ) ); 152 return collection; 153 }, 154 155 // Resets the site collection dataset to the canonical list originally pulled from the api 156 resetCanonical: function ( options ) { 157 options = options || {}; 158 this.reset( wcsc.settings.siteData, options ); 159 } 160 } ); 161 162 // View for a single site 163 wcsc.views.Site = Backbone.View.extend( { 164 className: 'wcsc-site', 165 166 attributes: function () { 167 return { 168 'id': 'wcsc-site-' + this.model.get( 'site_id' ), 169 'data-site-id': this.model.get( 'site_id' ) 170 } 171 }, 172 173 html: wp.template( 'wcsc-site-option' ), 174 175 touchDrag: false, 176 177 events: { 178 'click': 'preview', 179 'keydown': 'preview', 180 'touchend': 'preview', 181 'touchmove': 'preventPreview' 182 }, 183 184 initialize: function ( options ) { 185 this.parent = options.parent; 186 187 this.render(); 188 }, 189 190 render: function () { 191 var data = this.model.toJSON(); 192 193 this.$el.html( this.html( data ) ); 194 }, 195 196 preventPreview: function () { 197 this.touchDrag = true; 198 }, 199 200 preview: function ( event ) { 201 var current, preview; 202 203 event = event || window.event; 204 205 //ignore touches caused by scrolling 206 if ( this.touchDrag === true ) { 207 this.touchDrag = false; 208 } 209 210 event.preventDefault(); 211 212 this.$el.trigger( 'wcsc:previewSite', this.model ); 213 } 214 215 } ); 216 217 wcsc.views.SearchResults = Backbone.View.extend( { 218 className: 'wcsc-results', 219 220 liveSiteCount: 0, 221 222 initialize: function ( options ) { 223 var self = this; 224 225 this.parent = options.parent; 226 227 this.$siteCount = $( '#wcsc-sites-count' ); 228 229 // Rerender the view whenever a collection change is complete 230 this.listenTo( this.collection, 'reset', function () { 231 //reset pagination 232 self.parent.page = 0; 233 self.render( this ); 234 } ); 235 236 this.listenTo( this.parent, 'wcsc:scroll', function () { 237 self.renderSites( self.parent.page ); 238 } ); 239 }, 240 241 render: function () { 242 this.$el.empty(); 243 244 this.renderSites( this.parent.page ); 245 246 this.$siteCount.text( this.collection.length ); 247 }, 248 249 renderSites: function ( page ) { 250 var self = this; 251 252 // get a collection of just the requested page 253 this.instance = this.collection.paginate( page ); 254 255 if ( this.instance.size() === 0 ) { 256 this.parent.trigger( 'wcsc:end' ); 257 return; 258 } 259 260 this.instance.each( function ( site ) { 261 var siteView = new wcsc.views.Site( { 262 model: site, 263 parent: self 264 } ); 265 266 siteView.render(); 267 268 self.$el.append( siteView.el ); 269 } ); 270 271 this.parent.page++; 272 } 273 274 } ); 275 276 wcsc.views.SearchFilters = Backbone.View.extend( { 277 el: '#wcsc-cloner .filters', 278 className: 'wscs-filters', 279 html: wp.template( 'wcsc-site-filters' ), 280 events: { 281 "input #wcsc-filter-search-input": "search", 282 "keyup #wcsc-filter-search-input": "search", 283 "change select": "applyFilter" 284 }, 285 initialize: function ( options ) { 286 this.parent = options.parent; 287 288 this.$searchInput = $( '#wcsc-filter-search-input' ); 289 //this.listenTo( this.$searchInput, 'input keyup', this.search ); 290 291 this.$themeFilter = $( '#wcsc-filter-theme' ); 292 //this.listenTo( this.$themeFilter, 'change', this.applyFilter ); 293 294 this.$yearFilter = $( '#wcsc-filter-year' ); 295 //this.listenTo( this.$yearFilter, 'change', this.applyFilter ); 296 297 this.$preprocessorFilter = $( '#wcsc-filter-preprocessor' ); 298 //this.listenTo( this.$preprocessorFilter, 'change', this.applyFilter ); 299 300 }, 301 302 render: function () { 303 var data = {}; 304 305 data.themeOptions = _.uniq( this.parent.collection.pluck( 'theme_slug' ) ).sort(); 306 data.yearOptions = _.uniq( this.parent.collection.pluck( 'year' ) ).sort(); 307 data.preprocessorOptions = _.uniq( this.parent.collection.pluck( 'css_preprocessor' ) ).sort(); 308 309 this.$el.html( this.html( data ) ); 310 }, 311 312 search: function ( event ) { 313 // Clear on escape. 314 if ( event.type === 'keyup' && event.which === 27 ) { 315 event.target.value = ''; 316 } 317 318 /** 319 * Since doSearch is debounced, it will only run when user input comes to a rest 320 */ 321 this.doSearch( event ); 322 }, 323 324 doSearch: _.debounce( function ( event ) { 325 var options = {}; 326 327 this.model.set( 's', event.target.value ); 328 329 }, 500 ), 330 331 applyFilter: function ( event ) { 332 var $target = $( event.target ), 333 value = $target.val(), 334 filter = $target.data( 'filter' ); 335 this.model.set( filter, value ); 336 } 337 338 } ); 339 340 // Sets up listener to store the user's selected filters and search so a user's position can be 341 // restored as well as possible after a theme changes causes a full refresh 342 wcsc.routers.FilterState = Backbone.Router.extend( { 343 routes: { 344 'wcsc?*filters': 'applyFilters' 345 }, 346 intialize: function ( options ) { 347 this.parent = options.parent; 348 }, 349 applyFilters: function ( queryString ) { 350 var filters = deserializeQueryString( queryString ); 351 if ( filters.s ) { 352 this.parent.filterView.$searchInput.val( filters.s ); 353 } 354 355 this.parent.view.trigger( 'change:filters' ) 356 } 357 } ); 358 359 api.controlConstructor.wcscSearch = api.Control.extend( { 360 ready: function () { 361 var control = this, 362 urlParams = getUrlParams( win.location.href ), 363 filter = new wcsc.models.SearchFilter(), 364 siteCollection = new wcsc.collections.Sites( { searchFilter: filter } ); 365 366 //setup the backbone view for the control 367 //@todo would be nice to delay the fetch until this section is loaded 368 siteCollection.fetch( { 369 success: function ( collection ) { 370 wcsc.settings.siteData = collection.toJSON(); 371 372 control.view = new wcsc.views.SiteSearch( { 373 parent: this, 374 collection: collection, 375 filter: filter 376 } ); 377 378 control.renderSearch(); 379 wcsc.router = new wcsc.routers.FilterState( { parent: control } ); 380 Backbone.history.start(); 381 } 382 } ); 383 384 385 // if the wcsc_source_site_id is set, its most likely from a user previewing a site, so bring them back 386 if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) { 387 api.section( this.section() ).expand(); 388 api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id ); 389 } 390 391 $( '#wcsc-cloner' ).on( 'wcsc:previewSite', '.wcsc-site', function ( event, site ) { 392 control.previewSite( site ); 393 } ); 394 }, 395 396 previewSite: function ( site ) { 397 398 if ( api( 'wcsc_source_site_id' ).get() == site.get( 'site_id' ) ) { 399 //we're already previewing this site 400 return; 401 } 402 403 if ( api.settings.theme.stylesheet === site.get( 'theme_slug' ) ) { 404 api( 'wcsc_source_site_id' ).set( site.get( 'site_id' ) ); 61 405 } else { 62 window.parent.location = previewUrl; 406 win.parent.location = wcsc.settings.customizerUrl + '?' + $.param( { 407 'theme': site.get( 'theme_slug' ), 408 'wcsc_source_site_id': site.get( 'site_id' ) 409 } ); 63 410 } 411 }, 412 413 renderSearch: function () { 414 this.view.render(); 64 415 } 416 65 417 } ); 66 418 419 67 420 /** 68 421 * Parse the URL parameters 69 422 * … … 74 427 * @returns {object} 75 428 */ 76 429 function getUrlParams( url ) { 77 var match, questionMarkIndex, query, 78 urlParams = {}, 79 pl = /\+/g, // Regex for replacing addition symbol with a space 80 search = /([^&=]+)=?([^&]*)/g, 81 decode = function ( s ) { 82 return decodeURIComponent( s.replace( pl, " " ) ); 83 }; 430 var questionMarkIndex, query, 431 pl = /\+/g, // Regex for replacing addition symbol with a space 432 search = /([^&=]+)=?([^&]*)/g, 433 decode = function ( s ) { 434 return decodeURIComponent( s.replace( pl, " " ) ); 435 }; 84 436 85 437 questionMarkIndex = url.indexOf( '?' ); 86 438 87 439 if ( -1 === questionMarkIndex ) { 88 return urlParams;440 return {}; 89 441 } else { 90 442 query = url.substring( questionMarkIndex + 1 ); 91 443 } 92 444 93 while ( match = search.exec( query ) ) { 445 return deserializeQueryString( query ); 446 } 447 448 /** 449 * Deserialize a query string into an object 450 * 451 * @param queryString 452 * @returns {{}} 453 */ 454 function deserializeQueryString( queryString ) { 455 var match, 456 urlParams = {}, 457 pl = /\+/g, // Regex for replacing addition symbol with a space 458 search = /([^&=]+)=?([^&]*)/g, 459 decode = function ( s ) { 460 return decodeURIComponent( s.replace( pl, " " ) ); 461 }; 462 463 while ( match = search.exec( queryString ) ) { 94 464 urlParams[ decode( match[ 1 ] ) ] = decode( match[ 2 ] ); 95 465 } 96 466 97 467 return urlParams; 98 468 } 99 } )( window.wp, jQuery ); 469 })( wp, jQuery, Backbone, window, _wcscSettings ); 470 No newline at end of file -
wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php index c398534..51e46c5 100755
a b defined( 'WPINC' ) or die(); 7 7 /* 8 8 Plugin Name: WordCamp Site Cloner 9 9 Description: Allows organizers to clone another WordCamp's theme and custom CSS as a starting point for their site. 10 Version: 0. 110 Version: 0.2.0-alpha 11 11 Author: WordCamp.org 12 12 Author URI: http://wordcamp.org 13 13 License: GPLv2 or later … … License: GPLv2 or later 15 15 16 16 // todo if Jetpack_Custom_CSS:get_css is callable, register these, otherwise fatal errors 17 17 18 add_action( 'plugins_loaded', __NAMESPACE__ . '\get_wordcamp_sites' );19 18 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\register_scripts' ); 20 19 add_action( 'admin_menu', __NAMESPACE__ . '\add_submenu_page' ); 21 20 add_action( 'customize_register', __NAMESPACE__ . '\register_customizer_components' ); 21 add_action( 'rest_api_init', __NAMESPACE__ . '\initialize_site_list_API' ); 22 23 // Setup a daily cron to run on the main central.wordcamp.org site to keep the site list up to date. 24 if ( is_main_site() ) { 25 $hook = 'wcsc_prime_sites'; 26 if ( ! wp_next_scheduled( $hook ) ) { 27 wp_schedule_event( time(), 'daily', $hook ); 28 } 29 30 add_action( $hook, __NAMESPACE__ . '\prime_wordcamp_sites' ); 31 } 22 32 23 33 /** 24 34 * Register scripts and styles … … function register_scripts() { 28 38 'wordcamp-site-cloner', 29 39 plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css', 30 40 array(), 31 141 '0.2.0-alpha' 32 42 ); 33 43 34 44 wp_register_script( 35 45 'wordcamp-site-cloner', 36 46 plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js', 37 array( 'jquery', 'customize-controls' ),38 1,47 array( 'jquery', 'customize-controls', 'wp-backbone' ), 48 '0.2.0-alpha', 39 49 true 40 50 ); 51 52 wp_localize_script( 'wordcamp-site-cloner', '_wcscSettings', array( 53 'apiUrl' => get_rest_url( null, '/wordcamp-site-cloner/v1/sites/' ), 54 'customizerUrl' => admin_url( 'customize.php' ), 55 'l10n' => array( 56 'search' => __( 'Search', 'wordcamporg' ) 57 ) 58 ) ); 59 41 60 } 42 61 43 62 /** … … function add_submenu_page() { 52 71 __( 'Clone Another WordCamp', 'wordcamporg' ), 53 72 __( 'Clone Another WordCamp', 'wordcamporg' ), 54 73 'switch_themes', 55 'customize.php?autofocus[ panel]=wordcamp_site_cloner'74 'customize.php?autofocus[section]=wcsc_sites' 56 75 ); 57 76 } 58 77 … … function add_submenu_page() { 63 82 */ 64 83 function register_customizer_components( $wp_customize ) { 65 84 require_once( __DIR__ . '/includes/source-site-id-setting.php' ); 66 require_once( __DIR__ . '/includes/sites-section.php' );67 85 require_once( __DIR__ . '/includes/site-control.php' ); 68 86 69 $wp_customize->register_control_type( __NAMESPACE__ . '\Site_Control' );70 71 87 $wp_customize->add_setting( new Source_Site_ID_Setting( 72 88 $wp_customize, 73 89 'wcsc_source_site_id', 74 array() 90 array( 91 'capability' => 'switch_themes' 92 ) 75 93 ) ); 76 94 77 $wp_customize->add_panel( 78 'wordcamp_site_cloner', 79 array( 80 'type' => 'wcscPanel', 81 'title' => __( 'Clone Another WordCamp', 'wordcamporg' ), 82 'description' => __( "Clone another WordCamp's theme and custom CSS as a starting point for your site.", 'wordcamporg' ), 95 $wp_customize->add_section( 'wcsc_sites', array( 96 'title' => __( 'Clone Another WordCamp', 'wordcamporg' ), 97 'capability' => 'switch_themes' 83 98 ) 84 99 ); 85 100 86 $wp_customize->add_ section( new Sites_Section(101 $wp_customize->add_control( new Site_Control( 87 102 $wp_customize, 88 'wcsc_site s',103 'wcsc_site_search', 89 104 array( 90 'panel' => 'wordcamp_site_cloner', 91 'title' => __( 'WordCamp Sites', 'wordcamporg' ), 105 'type' => 'wcscSearch', 106 'label' => __( 'Search', 'wordcamporg' ), 107 'settings' => 'wcsc_source_site_id', 108 'section' => 'wcsc_sites' 92 109 ) 93 110 ) ); 111 } 94 112 95 foreach( get_wordcamp_sites() as $wordcamp ) { 96 if ( get_current_blog_id() == $wordcamp['site_id'] ) { 97 continue; 98 } 99 100 $wp_customize->add_control( new Site_Control( 101 $wp_customize, 102 'wcsc_site_id_' . $wordcamp['site_id'], 103 array( 104 'type' => 'wcscSite', // todo should be able to set this in control instead of here, but if do that then control contents aren't rendered 105 'site_id' => $wordcamp['site_id'], 106 'site_name' => $wordcamp['name'], 107 'theme_slug' => $wordcamp['theme_slug'], 108 'screenshot_url' => $wordcamp['screenshot_url'], 109 ) 110 ) ); 111 } 113 function initialize_site_list_API() { 114 register_rest_route( 'wordcamp-site-cloner/v1', '/sites', array( 115 'methods' => 'GET', 116 'callback' => __NAMESPACE__ . '\get_wordcamp_sites' 117 ) ); 112 118 } 113 119 114 120 /** … … function register_customizer_components( $wp_customize ) { 122 128 * @return array 123 129 */ 124 130 function get_wordcamp_sites() { 125 require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' ); 131 $transient_key = 'wcsc_sites'; 132 133 if ( false == ( $sites = get_site_transient( $transient_key ) ) ) { 134 $sites = _get_wordcamp_sites(); 126 135 127 // plugins_loaded is runs on every screen, but we only need this when loading the Customizer and Previewer 128 if ( 'customize.php' != basename( $_SERVER['SCRIPT_NAME'] ) && empty( $_REQUEST['wp_customize'] ) ) { 129 return array(); 136 //set the transient longer than needed to be sure it doesn't expire before the cron runs again. 137 set_site_transient( $transient_key, $sites, DAY_IN_SECONDS * 2 ); 130 138 } 131 139 132 $transient_key = 'wcsc_sites'; 140 // remove the current site from the result set 141 unset( $sites[ get_current_blog_id() ] ); 133 142 134 if ( $sites = get_site_transient( $transient_key ) ) { 135 return $sites; 136 } 143 return array_values( $sites ); 144 } 137 145 138 switch_to_blog( BLOG_ID_CURRENT_SITE ); // central.wordcamp.org 146 /** 147 * Queries and filters the available sites to clone by a single page. If more than one page of WordCamp posts exists, 148 * a cron will be scheduled in 10 minutes to update the next page's worth of sites in the cache. * 149 * 150 * @internal 151 * 152 * @param array $args { 153 * An array of elements that make up a post to update or insert. 154 * 155 * @type int $page The page of results to select. Default 1. 156 * @type int $posts_per_page The number of results to return in the initial query. Default 500 157 * @type bool $schedule_cron Whether to schedule a cron job to prime the next page of results. Default true 158 * } 159 * 160 * @return array WordCamp Sites array. 161 */ 162 function _get_wordcamp_sites($args = array()) { 139 163 164 $options = wp_parse_args( $args, array( 165 'page' => 1, 166 'posts_per_page' => 500, 167 'schedule_cron' => true 168 ) ); 169 170 $page = absint($options['page']); 171 172 require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' ); 173 if ( ! \Jetpack::is_module_active( 'custom-css' ) ) { 174 \Jetpack::activate_module( 'custom-css', false, false ); 175 } 176 switch_to_blog( get_current_site()->blog_id ); 140 177 $sites = array(); 141 $wordcamp s = get_posts( array(178 $wordcamp_query = new \WP_Query( array( 142 179 'post_type' => 'wordcamp', 143 180 'post_status' => \WordCamp_Loader::get_public_post_statuses(), 144 'posts_per_page' => 125, // todo temporary workaround until able to add filters to make hundreds of sites manageable181 'posts_per_page' => $options[ 'posts_per_page' ], 145 182 'meta_key' => 'Start Date (YYYY-mm-dd)', 146 183 'orderby' => 'meta_value_num', 147 148 'meta_query' => array( 184 'order' => 'DESC', 185 'paged' => $page, 186 'meta_query' => array( 149 187 array( 150 188 'key' => 'Start Date (YYYY-mm-dd)', 151 189 'value' => strtotime( 'now - 1 month' ), 152 190 'compare' => '<' 153 191 ), 192 array( 193 'key' => '_site_id', 194 'compare' => 'EXISTS' 195 ) 154 196 ), 155 197 ) ); 156 198 157 foreach( $wordcamps as $wordcamp ) { 158 $site_id = get_wordcamp_site_id( $wordcamp ); 199 //If there are more pages to process, reschedule the cron to run again with the next page 200 if ( $options[ 'schedule_cron' ] && $wordcamp_query->max_num_pages > $page && ! wp_next_scheduled( 'wcsc_prime_sites', [ $page + 1 ] ) ) { 201 wp_schedule_single_event( time() + 10 * MINUTE_IN_SECONDS, 'wcsc_prime_sites', [ $page + 1 ] ); 202 } 203 204 $wordcamps = $wordcamp_query->get_posts(); 205 206 $coming_soon_settings_handler = false; 207 if(class_exists('\WCCSP_Settings')) { 208 $coming_soon_settings_handler = isset( $GLOBALS[ 'WCCSP_Settings' ] ) ? $GLOBALS[ 'WCCSP_Settings' ] : new \WCCSP_Settings(); 209 } 210 211 foreach ( $wordcamps as $wordcamp ) { 212 $site_id = get_wordcamp_site_id( $wordcamp ); 159 213 $site_url = get_post_meta( $wordcamp->ID, 'URL', true ); 214 $start_date = get_post_meta( $wordcamp->ID, 'Start Date (YYYY-mm-dd)', true ); 160 215 161 if ( ! $site_id || ! $site_url ) {216 if ( ! $site_id || ! $site_url || ! $start_date ) { 162 217 continue; 163 218 } 164 219 165 220 switch_to_blog( $site_id ); 166 221 167 $sites[] = array( 168 'site_id' => $site_id, 169 'name' => get_wordcamp_name(), 170 'theme_slug' => get_stylesheet(), 171 'screenshot_url' => get_screenshot_url( $site_url ), 222 //skip any sites with the coming soon enabled. 223 if ( $coming_soon_settings_handler ) { 224 $coming_soon_settings = $coming_soon_settings_handler->get_settings(); 225 if ( isset( $coming_soon_settings[ 'enabled' ] ) && $coming_soon_settings[ 'enabled' ] === 'on' ) { 226 restore_current_blog(); 227 continue; 228 } 229 } 230 231 232 $preprocessor = \Jetpack_Custom_CSS::get_preprocessor(); 233 234 $sites[ $site_id ] = array( 235 'site_id' => $site_id, 236 'name' => get_wordcamp_name(), 237 'theme_slug' => get_stylesheet(), 238 'screenshot_url' => get_screenshot_url( $site_url ), 239 'year' => date( 'Y', $start_date ), 240 'css_preprocessor' => is_array( $preprocessor ) && isset( $preprocessor[ 'name' ] ) ? $preprocessor[ 'name' ] : 'none' 172 241 ); 173 242 174 243 restore_current_blog(); … … function get_wordcamp_sites() { 176 245 177 246 restore_current_blog(); 178 247 179 set_site_transient( $transient_key, $sites, DAY_IN_SECONDS );180 181 248 return $sites; 182 249 } 183 250 184 251 /** 252 * Daily cron job callback to keep the site list transient valid. This is only run on the main site. 253 * 254 * Since it can be expensive to loop through 100's of sites with switch_to_blog, we're breaking the cron up 255 * into smaller gorups and updating a set at a time. This means that users getting the initial load may not 256 * see EVERY site,but since they are ordered by date descending, most applicable sites should be available 257 * immediately if the transient gets flushed. 258 * 259 * The current logic assumes that no sites that have previously made the list of sites will end up getting 260 * removed. 261 * 262 */ 263 function prime_wordcamp_sites($page = 1) { 264 $transient_key = 'wcsc_sites'; 265 266 $sites = _get_wordcamp_sites( compact( 'page' ) ); 267 268 $stale_sites = get_site_transient( $transient_key ); 269 270 if ( ! empty( $stale_sites ) ) { 271 //merge the fresh sites with the previously stored sites, overriding old copies with the new 272 $sites = array_merge( $stale_sites, $sites ); 273 274 //sort the sites in descending order by year. More exact dates can be added if the rough order causes confusion. 275 uasort( $sites, function( $site_a, $site_b ) { 276 if ( $site_a[ 'year' ] === $site_b[ 'year' ] ) { 277 return 0; 278 } 279 return ( $site_a[ 'year' ] < $site_b[ 'year' ] ? 1 : -1 ); 280 } ); 281 } 282 283 //set the transient longer than needed to be sure it doesn't expire before the cron runs again. 284 set_site_transient( 'wcsc_sites', $sites, DAY_IN_SECONDS * 2 ); 285 } 286 287 /** 185 288 * Get the mShot URL for the given site URL 186 289 * 187 290 * Allow it to be filtered so that production URLs can be changed to match development URLs in local environments. … … function get_screenshot_url( $site_url ) { 194 297 $screenshot_url = add_query_arg( 'w', 275, 'https://www.wordpress.com/mshots/v1/' . rawurlencode( $site_url ) ); 195 298 196 299 return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url ); 197 } 300 } 301 No newline at end of file