Making WordPress.org

Ticket #1112: 1112-site-cloner-scaling.diff

File 1112-site-cloner-scaling.diff, 25.7 KB (added by prettyboymp, 9 years ago)

Initial rough patch to add filtering to the UI of the Site Cloner Customizer control

  • includes/site-control.php

     
    55defined( 'WPINC' ) or die();
    66
    77/**
    8  * Custom Customizer Control for a WordCamp site
     8 * Custom Customizer Control for Search WordCamp sites to clone
    99 */
    1010class 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';
    1411
    15         /**
    16          * Enqueue scripts and styles
    17          */
     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        }
     18
    1819        public function enqueue() {
    19                 wp_enqueue_style(  'wordcamp-site-cloner' );
     20                wp_enqueue_style( 'wordcamp-site-cloner' );
    2021                wp_enqueue_script( 'wordcamp-site-cloner' );
     22                add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_view_templates' ) );
    2123        }
    2224
    23         /**
    24          * Render the control's content
    25          */
    2625        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                 );
     26                include dirname( __DIR__ ) . '/templates/site-control.php';
     27        }
    3428
    35                 require( dirname( __DIR__ ) . '/templates/site-control.php' );
     29        public function print_view_templates() {
     30                ?>
     31                <script id="tmpl-wcsc-site-option" type="text/html">
     32                        <?php include dirname( __DIR__ ) . '/templates/site-option.php'; ?>
     33                </script>
     34                <?php
    3635        }
    3736}
  • templates/site-control.php

     
    1 <?php defined( 'WPINC' ) or die(); ?>
     1<?php
     2/**
     3 *  Top level template for the output of the Site Cloner Customizer Control
     4 */
    25
    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 ); ?>" />
     6defined( 'WPINC' ) or die();
     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>
     12        </h3>
     13        <div class="filters">
    614        </div>
    715
    8         <h3 class="wcsc-site-name">
    9                 <?php echo esc_html( $this->site_name ); ?>
    10         </h3>
    11 
    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
  • wordcamp-site-cloner.css

     
     1#wcsc-cloner div.filters{
     2    padding: 8px;
     3}
     4
     5#wcsc-cloner div.filters label{
     6    position: relative;
     7}
     8
    19.control-section-wcsc-sites {
    2         padding: 0 8px;
     10    padding: 0 8px;
    311}
    412
    5         #wcsc-sites {
    6                 overflow: auto;
    7         }
     13#wcsc-sites {
     14    overflow: auto;
     15}
    816
    9                 .wcscSite {
    10                         position: relative;
    11                         cursor: pointer;
    12                         border: 1px solid #DEDEDE;
    13                         box-shadow: 0 1px 1px -1px rgba( 0, 0, 0, 0.1 );
    14                 }
     17.wcsc-site {
     18    position: relative;
     19    cursor: pointer;
     20    border: 1px solid #DEDEDE;
     21    box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.1);
     22}
    1523
    16                         .wcsc-site-screenshot {
    17                                 transition: opacity 0.2s ease-in-out 0s;
    18                         }
     24.wcsc-site-screenshot {
     25    transition: opacity 0.2s ease-in-out 0s;
     26}
    1927
    20                                 .wcscSite:hover .wcsc-site-screenshot {
    21                                         opacity: 0.4;
    22                                 }
     28.wcsc-site:hover .wcsc-site-screenshot {
     29    opacity: 0.4;
     30}
    2331
    24                         .wcsc-live-preview-label {
    25                                 opacity: 0;
    26                                 position: absolute;
    27                                 top: 35%;
    28                                 right: 25%;
    29                                 left: 25%;
    30                                 background: rgba( 0, 0, 0, 0.7 ) none repeat scroll 0 0;
    31                                 color: #FFF;
    32                                 font-size: 15px;
    33                                 text-shadow: 0 1px 0 rgba( 0, 0, 0, 0.6 );
    34                                 font-weight: 600;
    35                                 padding: 15px 12px;
    36                                 text-align: center;
    37                                 border-radius: 3px;
    38                                 transition: opacity 0.1s ease-in-out 0s;
    39                         }
     32.wcsc-live-preview-label {
     33    opacity: 0;
     34    position: absolute;
     35    top: 35%;
     36    right: 25%;
     37    left: 25%;
     38    background: rgba(0, 0, 0, 0.7) none repeat scroll 0 0;
     39    color: #FFF;
     40    font-size: 15px;
     41    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
     42    font-weight: 600;
     43    padding: 15px 12px;
     44    text-align: center;
     45    border-radius: 3px;
     46    transition: opacity 0.1s ease-in-out 0s;
     47}
    4048
    41                                 .wcscSite:hover .wcsc-live-preview-label {
    42                                         opacity: 1;
    43                                 }
     49.wcsc-site:hover .wcsc-live-preview-label {
     50    opacity: 1;
     51}
  • wordcamp-site-cloner.js

     
    1 ( function( wp, $ ) {
     1(function ( $, Backbone, win, wp ) {
    22        'use strict';
    33
    4         if ( ! wp || ! wp.customize ) {
     4        var api = wp.customize,
     5            wcsc;
     6
     7        if ( !wp || !wp.customize ) {
    58                return;
    69        }
    710
    8         var api = wp.customize;
     11        wcsc = wp.wcSiteCloner = { models: {}, views: {}, collections: {}, settings: {} };
    912
    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 );
     13        // @todo dynamically set the real URL
     14        wcsc.settings.apiUrl = win.location.protocol + '//' + win.location.hostname + '/wp-admin/customize.php?get_wordcamp_sites=1';
    2315
    24                         if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
    25                                 this.expand();
    26                                 api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id );
     16        // @todo implement real l10n
     17        var l10n = wcsc.settings.l10n = {
     18                search: 'Search'
     19        };
     20
     21
     22        // Model for a single site
     23        wcsc.models.Site = Backbone.Model.extend( {
     24                idAttribute: 'site_id'
     25        } );
     26
     27        // Top level view for the Site Cloner Control
     28        wcsc.views.SiteSearch = Backbone.View.extend( {
     29                el: '#wcsc-cloner .wcsc-search',
     30
     31                // index of the currently viewed page of results
     32                page: 0,
     33
     34                initialize: function ( options ) {
     35
     36                        // update scroller position
     37                        _.bindAll( this, 'scroller' );
     38
     39                        this.$searchContainer = $( '#wcsc-cloner div.filters' );
     40
     41                        // container that will be scrolled within
     42                        this.$container = $( '#wcsc-cloner' ).parents( 'ul.accordion-section-content' );
     43
     44                        // bind scrolling within the container to check for infinite scroll
     45                        this.$container.bind( 'scroll', _.throttle( this.scroller, 300 ) );
     46                },
     47
     48                render: function () {
     49
     50                        // View for listing the matching sites
     51                        this.resultsView = new wcsc.views.SearchResults( {
     52                                collection: this.collection,
     53                                parent: this
     54                        } );
     55
     56                        // render search form
     57                        this.renderSearch();
     58
     59                        this.resultsView.render();
     60                        this.$el.empty().append( this.resultsView.el );
     61                },
     62
     63                // Render the search input view
     64                renderSearch: function () {
     65                        var self = this,
     66                            view;
     67
     68                        view = new wcsc.views.SearchInput( {
     69                                collection: this.collection,
     70                                parent: this
     71                        } );
     72
     73                        view.render();
     74
     75                        this.$searchContainer
     76                                .append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) );
     77                        this.$searchContainer
     78                                .append( view.el );
     79                },
     80
     81                // Checks if a user has reached the bottom of the list
     82                // and triggers a scroll event to show more sites if needed
     83                scroller: function () {
     84                        var visibleBottom, threshold, elementHeight, containerHeight, scrollTop;
     85
     86                        scrollTop = this.$container.scrollTop();
     87                        containerHeight = this.$container.innerHeight();
     88                        elementHeight = this.$container.get( 0 ).scrollHeight;
     89
     90                        visibleBottom = scrollTop + containerHeight;
     91                        threshold = Math.round( elementHeight * 0.9 );
     92
     93                        if ( visibleBottom > threshold ) {
     94                                this.trigger( 'wcsc:scroll' );
    2795                        }
    2896                }
     97
    2998        } );
    3099
    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 );
     100        // Collection representing the list of cloneable sites
     101        wcsc.collections.Sites = Backbone.Collection.extend( {
     102                model: wcsc.models.Site,
     103                terms: '',
     104                url: wcsc.settings.apiUrl,
     105
     106                // Runs the search against the current collection and triggers the update event
     107                doSearch: function ( value ) {
     108
     109                        //duplicate search
     110                        if ( this.terms === value ) {
     111                                return;
     112                        }
     113
     114                        this.terms = value;
     115
     116                        //if there are terms, run the serach filter
     117                        if ( this.terms.length > 0 ) {
     118                                this.search( this.terms );
     119                        }
     120
     121                        if ( this.terms === '' ) {
     122                                this.resetCanonical();
     123                        }
     124
     125                        this.trigger( 'wcsc:updated' );
    40126                },
    41127
    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 );
     128                // resets this collection to the filtered search results
     129                search: function ( term ) {
     130                        var match, results, haystack, name;
    54131
    55                         if ( api( 'wcsc_source_site_id' ).get() == previewUrlParams.wcsc_source_site_id ) {
     132                        this.resetCanonical( { silent: true } );
     133
     134                        // Escape the term string for RegExp meta characters
     135                        term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
     136
     137                        // Consider spaces as word delimiters and match the whole string
     138                        // so matching terms can be combined
     139                        term = term.replace( / /g, ')(?=.*' );
     140                        match = new RegExp( '^(?=.*' + term + ').+', 'i' );
     141
     142                        // Find results
     143                        // _.filter and .test
     144                        results = this.filter( function ( data ) {
     145                                name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
     146
     147                                return match.test( name );
     148                        } );
     149
     150                        if ( results.length === 0 ) {
     151                                this.trigger( 'query:empty' );
     152                        }
     153
     154                        this.reset( results );
     155                },
     156
     157                paginate: function ( pageIndex ) {
     158                        var collection = this;
     159                        pageIndex = pageIndex || 0;
     160
     161                        collection = _( collection.rest( 20 * pageIndex ) );
     162                        collection = _( collection.first( 20 ) );
     163                        return collection;
     164                },
     165
     166                // Resets the site collection dataset to the canonical list originally pulled from the api
     167                resetCanonical: function ( options ) {
     168                        options = options || {};
     169                        this.reset( wcsc.settings.siteData, options );
     170                }
     171        } );
     172
     173
     174        // View for a single site
     175        wcsc.views.Site = Backbone.View.extend( {
     176                className: 'wcsc-site',
     177
     178                attributes: function () {
     179                        return {
     180                                'id': 'wcsc-site-' + this.model.get( 'site_id' ),
     181                                'data-site-id': this.model.get( 'site_id' )
     182                        }
     183                },
     184
     185                html: wp.template( 'wcsc-site-option' ),
     186
     187                touchDrag: false,
     188
     189                events: {
     190                        'click': 'preview',
     191                        'keydown': 'preview',
     192                        'touchend': 'preview',
     193                        'touchmove': 'preventPreview'
     194                },
     195
     196                initialize: function ( options ) {
     197                        this.parent = options.parent;
     198
     199                        this.render();
     200                },
     201
     202                render: function () {
     203                        var data = this.model.toJSON();
     204
     205                        this.$el.html( this.html( data ) );
     206                },
     207
     208                preventPreview: function () {
     209                        this.touchDrag = true;
     210                },
     211
     212                preview: function ( event ) {
     213                        var current, preview;
     214
     215                        event = event || window.event;
     216
     217                        //ignore touches caused by scrolling
     218                        if ( this.touchDrag === true ) {
     219                                this.touchDrag = false;
     220                        }
     221
     222                        event.preventDefault();
     223
     224                        this.$el.trigger( 'wcsc:previewSite', this.model );
     225                }
     226
     227        } );
     228
     229        wcsc.views.SearchResults = Backbone.View.extend( {
     230                className: 'wcsc-results',
     231
     232                liveSiteCount: 0,
     233
     234                initialize: function ( options ) {
     235                        var self = this;
     236
     237                        this.parent = options.parent;
     238
     239                        this.$siteCount = $( '#wcsc-sites-count' );
     240
     241                        // Rerender the view whenever a collection change is complete
     242                        this.listenTo( self.collection, 'wcsc:updated', function () {
     243                                //reset pagination
     244                                self.parent.page = 0;
     245                                self.render( this );
     246                        } );
     247
     248                        this.listenTo( this.parent, 'wcsc:scroll', function () {
     249                                self.renderSites( self.parent.page );
     250                        } );
     251                },
     252
     253                render: function () {
     254                        this.$el.empty();
     255
     256                        //if ( this.options.collection.size() > 0 ) {
     257                        this.renderSites( this.parent.page );
     258                        //}
     259
     260                        this.$siteCount.text( this.collection.length );
     261                },
     262
     263                renderSites: function ( page ) {
     264                        var self = this;
     265
     266                        // get a collection of just the requested page
     267                        this.instance = this.collection.paginate( page );
     268
     269                        if ( this.instance.size() === 0 ) {
     270                                this.parent.trigger( 'wcsc:end' );
    56271                                return;
    57272                        }
    58273
    59                         if ( api.settings.theme.stylesheet === previewUrlParams.theme ) {
    60                                 api( 'wcsc_source_site_id' ).set( previewUrlParams.wcsc_source_site_id );
     274                        this.instance.each( function ( site ) {
     275                                var siteView = new wcsc.views.Site( {
     276                                        model: site,
     277                                        parent: self
     278                                } );
     279
     280                                siteView.render();
     281
     282                                self.$el.append( siteView.el );
     283                        } );
     284
     285                        this.parent.page++;
     286                }
     287
     288        } );
     289
     290        // View for the search input field
     291        wcsc.views.SearchInput = Backbone.View.extend( {
     292                tagName: 'input',
     293                className: 'wcsc-filter-search',
     294                id: 'wcsc-filter-search-input',
     295                searching: false,
     296
     297                attributes: {
     298                        type: 'search',
     299
     300                },
     301
     302                events: {
     303                        'input': 'search',
     304                        'keyup': 'search',
     305                        'blur': 'pushState'
     306                },
     307
     308                initialize: function ( options ) {
     309                        this.parent = options.parent;
     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.collection.doSearch( event.target.value );
     328
     329                        // if search is initiated and key is not return
     330                        if ( this.searching && event.which !== 13 ) {
     331                                options.replace = true;
    61332                        } else {
    62                                 window.parent.location = previewUrl;
     333                                this.searching = true;
    63334                        }
     335
     336                }, 500 ),
     337
     338                pushState: function ( event ) {
     339                        this.searching = false;
    64340                }
    65341        } );
    66342
     343        api.controlConstructor.wcscSearch = api.Control.extend( {
     344                ready: function () {
     345                        var control        = this,
     346                            urlParams      = getUrlParams( win.location.href ),
     347                            siteCollection = new wcsc.collections.Sites();
     348
     349                        //setup the backbone view for the control
     350                        //@todo would be nice to delay the fetch until this section is loaded
     351                        siteCollection.fetch( {
     352                                success: function ( collection ) {
     353                                        wcsc.settings.siteData = collection.toJSON();
     354
     355                                        control.view = new wcsc.views.SiteSearch( {
     356                                                parent: this,
     357                                                collection: collection
     358                                        } );
     359
     360                                        control.renderSearch();
     361                                }
     362                        } );
     363
     364                        // if the wcsc_source_site_id is set, its most likely from a user previewing a site, so bring them back
     365                        if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
     366                                api.section( this.section() ).expand();
     367                        }
     368
     369                        $( '#wcsc-cloner' ).on( 'wcsc:previewSite', '.wcsc-site', function ( event, site ) {
     370                                control.previewSite( site );
     371                        } );
     372                },
     373
     374                previewSite: function ( site ) {
     375
     376                        if ( api( 'wcsc_source_site_id' ).get() == site.get( 'site_id' ) ) {
     377                                //we're already previewing this site
     378                                return;
     379                        }
     380
     381                        if ( api.settings.theme.stylesheet === site.get( 'theme_slug' ) ) {
     382                                api( 'wcsc_source_site_id' ).set( site.get( 'site_id' ) );
     383                        } else {
     384                                //@todo properly set this
     385                                win.parent.location = win.location.protocol + '//' + win.location.hostname +
     386                                        '/wp-admin/customize.php?theme=' + site.get( 'theme_slug' ) + '&wcsc_source_site_id=' + site.get( 'site_id' );
     387                        }
     388                },
     389
     390                renderSearch: function () {
     391                        this.view.render();
     392                }
     393
     394        } );
     395
    67396        /**
    68397         * Parse the URL parameters
    69398         *
     
    75404         */
    76405        function getUrlParams( url ) {
    77406                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                         };
     407                    urlParams = {},
     408                    pl        = /\+/g,  // Regex for replacing addition symbol with a space
     409                    search    = /([^&=]+)=?([^&]*)/g,
     410                    decode    = function ( s ) {
     411                            return decodeURIComponent( s.replace( pl, " " ) );
     412                    };
    84413
    85414                questionMarkIndex = url.indexOf( '?' );
    86415
     
    96425
    97426                return urlParams;
    98427        }
    99 } )( window.wp, jQuery );
     428})( jQuery, Backbone, window, wp )
     429 No newline at end of file
  • wordcamp-site-cloner.php

     
    77/*
    88Plugin Name: WordCamp Site Cloner
    99Description: Allows organizers to clone another WordCamp's theme and custom CSS as a starting point for their site.
    10 Version:     0.1
     10Version:     0.2.0-alpha
    1111Author:      WordCamp.org
    1212Author URI:  http://wordcamp.org
    1313License:     GPLv2 or later
    1414*/
    1515
    1616// todo if Jetpack_Custom_CSS:get_css is callable, register these, otherwise fatal errors
    17 
    18 add_action( 'plugins_loaded',        __NAMESPACE__ . '\get_wordcamp_sites' );
     17add_action( 'plugins_loaded', __NAMESPACE__ . '\get_wordcamp_sites' );
    1918add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\register_scripts' );
    20 add_action( 'admin_menu',            __NAMESPACE__ . '\add_submenu_page' );
    21 add_action( 'customize_register',    __NAMESPACE__ . '\register_customizer_components' );
     19add_action( 'admin_menu', __NAMESPACE__ . '\add_submenu_page' );
     20add_action( 'customize_register', __NAMESPACE__ . '\register_customizer_components' );
    2221
    2322/**
    2423 * Register scripts and styles
    2524 */
    2625function register_scripts() {
    2726        wp_register_style(
    28                 'wordcamp-site-cloner',
    29                 plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
    30                 array(),
    31                 1
     27                        'wordcamp-site-cloner',
     28                        plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
     29                        array(),
     30                        1
    3231        );
    3332
    3433        wp_register_script(
    35                 'wordcamp-site-cloner',
    36                 plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
    37                 array( 'jquery', 'customize-controls' ),
    38                 1,
    39                 true
     34                        'wordcamp-site-cloner',
     35                        plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
     36                        array( 'jquery', 'customize-controls', 'wp-backbone' ),
     37                        1,
     38                        true
    4039        );
    4140}
    4241
     
    4847 */
    4948function add_submenu_page() {
    5049        \add_submenu_page(
    51                 'themes.php',
    52                 __( 'Clone Another WordCamp', 'wordcamporg' ),
    53                 __( 'Clone Another WordCamp', 'wordcamporg' ),
    54                 'switch_themes',
    55                 'customize.php?autofocus[panel]=wordcamp_site_cloner'
     50                        'themes.php',
     51                        __( 'Clone Another WordCamp', 'wordcamporg' ),
     52                        __( 'Clone Another WordCamp', 'wordcamporg' ),
     53                        'switch_themes',
     54                        'customize.php?autofocus[panel]=wordcamp_site_cloner'
    5655        );
    5756}
    5857
     
    6362 */
    6463function register_customizer_components( $wp_customize ) {
    6564        require_once( __DIR__ . '/includes/source-site-id-setting.php' );
    66         require_once( __DIR__ . '/includes/sites-section.php' );
    6765        require_once( __DIR__ . '/includes/site-control.php' );
    6866
    69         $wp_customize->register_control_type( __NAMESPACE__ . '\Site_Control' );
    70 
    7167        $wp_customize->add_setting( new Source_Site_ID_Setting(
    72                 $wp_customize,
    73                 'wcsc_source_site_id',
    74                 array()
     68                        $wp_customize,
     69                        'wcsc_source_site_id',
     70                        array()
    7571        ) );
    7672
    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' ),
    83                 )
     73        $wp_customize->add_section( 'wcsc_sites', array(
     74                                        'title' => __( 'Clone Another WordCamp', 'wordcamporg' ),
     75                        )
    8476        );
    8577
    86         $wp_customize->add_section( new Sites_Section(
    87                 $wp_customize,
    88                 'wcsc_sites',
    89                 array(
    90                         'panel' => 'wordcamp_site_cloner',
    91                         'title' => __( 'WordCamp Sites', 'wordcamporg' ),
    92                 )
    93         ) );
    94 
    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(
     78        $wp_customize->add_control( new Site_Control(
    10179                        $wp_customize,
    102                         'wcsc_site_id_' . $wordcamp['site_id'],
     80                        'wcsc_site_search',
    10381                        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'],
     82                                        'type'     => 'wcscSearch',
     83                                        'label'    => __( 'Search', 'wordcamporg' ),
     84                                        'settings' => 'wcsc_source_site_id',
     85                                        'section'  => 'wcsc_sites'
    10986                        )
    110                 ) );
    111         }
     87        ) );
    11288}
    11389
    11490/**
     
    125101        require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' );
    126102
    127103        // 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'] ) ) {
     104        if ( 'customize.php' != basename( $_SERVER[ 'SCRIPT_NAME' ] ) && empty( $_REQUEST[ 'wp_customize' ] ) ) {
    129105                return array();
    130106        }
    131107
     
    139115
    140116        $sites = array();
    141117        $wordcamps = get_posts( array(
    142                 'post_type'      => 'wordcamp',
    143                 '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 manageable
    145                 'meta_key'       => 'Start Date (YYYY-mm-dd)',
    146                 'orderby'        => 'meta_value_num',
     118                        'post_type'      => 'wordcamp',
     119                        'post_status'    => \WordCamp_Loader::get_public_post_statuses(),
     120                        'posts_per_page' => 125, // todo temporary workaround until able to add filters to make hundreds of sites manageable
     121                        'meta_key'       => 'Start Date (YYYY-mm-dd)',
     122                        'orderby'        => 'meta_value_num',
    147123
    148                 'meta_query' => array(
    149                         array(
    150                                 'key'     => 'Start Date (YYYY-mm-dd)',
    151                                 'value'   => strtotime( 'now - 1 month' ),
    152                                 'compare' => '<'
     124                        'meta_query' => array(
     125                                        array(
     126                                                        'key'     => 'Start Date (YYYY-mm-dd)',
     127                                                        'value'   => strtotime( 'now - 1 month' ),
     128                                                        'compare' => '<'
     129                                        ),
    153130                        ),
    154                 ),
    155131        ) );
    156132
    157         foreach( $wordcamps as $wordcamp ) {
    158                 $site_id  = get_wordcamp_site_id( $wordcamp );
     133        foreach ( $wordcamps as $wordcamp ) {
     134                $site_id = get_wordcamp_site_id( $wordcamp );
    159135                $site_url = get_post_meta( $wordcamp->ID, 'URL', true );
    160136
    161137                if ( ! $site_id || ! $site_url ) {
     
    165141                switch_to_blog( $site_id );
    166142
    167143                $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 ),
     144                                'site_id'        => $site_id,
     145                                'name'           => get_wordcamp_name(),
     146                                'theme_slug'     => get_stylesheet(),
     147                                'screenshot_url' => get_screenshot_url( $site_url ),
    172148                );
    173149
    174150                restore_current_blog();
     
    195171
    196172        return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url );
    197173}
     174
     175/**
     176 * Quick and ugly handling to provide a JSON endpoint for the WordCamp Sites data
     177 *
     178 * @todo Make a real API
     179 */
     180add_action( 'wp_loaded', function() {
     181        if ( ! empty( $_GET[ 'get_wordcamp_sites' ] ) ) {
     182                $sites = \WordCamp\Site_Cloner\get_wordcamp_sites();
     183
     184                $fakeSites = array_merge( $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     185                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     186                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     187                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     188                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     189                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     190                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     191                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     192                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     193                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
     194                                $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites );
     195
     196                //hack to expand with fake data for now so the front-end can be worked on without having to create 100s of sites
     197                if ( defined( 'WCSC_MOCK_FILL_SITES' ) && WCSC_MOCK_FILL_SITES ) {
     198                        $fake_site_id = 10000;
     199                        foreach ( $fakeSites as &$site ) {
     200                                $site[ 'site_id' ] = $fake_site_id;
     201                                $site[ 'name' ] = $site[ 'name' ] . " Mock Only (Don't Click) - " . $fake_site_id;
     202                                $fake_site_id++;
     203                        }
     204
     205                        $sites = array_merge( $sites, $fakeSites );
     206                }
     207
     208                wp_send_json( $sites );
     209        }
     210} );
     211 No newline at end of file