WordPress.org

Making WordPress.org

Ticket #1112: 1112.diff

File 1112.diff, 22.9 KB (added by prettyboymp, 4 years ago)

Cleaned up the whitespace changes in the original patch. This is still an early rough patch just to illustrate the client side filtering.

  • wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php

    diff --git wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php
    index 8628d2c..69a517d 100644
    namespace WordCamp\Site_Cloner; 
    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';
     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        }
    1418
    1519        /**
    1620         * Enqueue scripts and styles
    class Site_Control extends \WP_Customize_Control { 
    1822        public function enqueue() {
    1923                wp_enqueue_style(  'wordcamp-site-cloner' );
    2024                wp_enqueue_script( 'wordcamp-site-cloner' );
     25                add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_view_templates' ) );
    2126        }
    2227
    23         /**
    24          * Render the control's content
    25          */
    2628        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 
    3529                require( dirname( __DIR__ ) . '/templates/site-control.php' );
    3630        }
     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                <?php
     38        }
    3739}
  • wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php

    diff --git wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php
    index 952a134..9b60dba 100644
     
    1 <?php defined( 'WPINC' ) or die(); ?>
     1<?php
     2defined( 'WPINC' ) or die();
    23
    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>
    1012        </h3>
     13        <div class="filters">
     14        </div>
    1115
    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-option.php

    diff --git wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php 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
     6defined( '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 wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php 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 wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css
    index 0a33876..dbc3581 100644
     
    66                overflow: auto;
    77        }
    88
    9                 .wcscSite {
     9                .wcsc-site {
    1010                        position: relative;
    1111                        cursor: pointer;
    1212                        border: 1px solid #DEDEDE;
     
    1717                                transition: opacity 0.2s ease-in-out 0s;
    1818                        }
    1919
    20                                 .wcscSite:hover .wcsc-site-screenshot {
     20                                .wcsc-site:hover .wcsc-site-screenshot {
    2121                                        opacity: 0.4;
    2222                                }
    2323
     
    3838                                transition: opacity 0.1s ease-in-out 0s;
    3939                        }
    4040
    41                                 .wcscSite:hover .wcsc-live-preview-label {
     41                                .wcsc-site:hover .wcsc-live-preview-label {
    4242                                        opacity: 1;
    4343                                }
     44
     45#wcsc-cloner div.filters {
     46        padding: 8px;
     47}
     48
     49#wcsc-cloner div.filters label {
     50        position: relative;
     51}
  • wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js

    diff --git wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js
    index 5c05f82..2279105 100644
     
    1 ( function( wp, $ ) {
     1( function ( wp, $, Backbone, win ) {
    22        'use strict';
    33
    44        if ( ! wp || ! wp.customize ) {
    55                return;
    66        }
    77
    8         var api = wp.customize;
     8        var api = wp.customize,
     9            wcsc;
    910
    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 = wp.wcSiteCloner = { models: {}, views: {}, collections: {}, settings: {} };
    2312
    24                         if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
    25                                 this.expand();
    26                                 api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id );
     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';
     15
     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 );
    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 ) {
     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' );
     126                },
     127
     128                // resets this collection to the filtered search results
     129                search: function ( term ) {
     130                        var match, results, haystack, name;
     131
     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;
     332                        } else {
     333                                this.searching = true;
     334                        }
     335
     336                }, 500 ),
     337
     338                pushState: function ( event ) {
     339                        this.searching = false;
     340                }
     341        } );
     342
     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                                api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id );
     368                        }
     369
     370                        $( '#wcsc-cloner' ).on( 'wcsc:previewSite', '.wcsc-site', function ( event, site ) {
     371                                control.previewSite( site );
     372                        } );
     373                },
     374
     375                previewSite: function ( site ) {
     376
     377                        if ( api( 'wcsc_source_site_id' ).get() == site.get( 'site_id' ) ) {
     378                                //we're already previewing this site
     379                                return;
     380                        }
     381
     382                        if ( api.settings.theme.stylesheet === site.get( 'theme_slug' ) ) {
     383                                api( 'wcsc_source_site_id' ).set( site.get( 'site_id' ) );
    61384                        } else {
    62                                 window.parent.location = previewUrl;
     385                                //@todo properly set this
     386                                win.parent.location = win.location.protocol + '//' + win.location.hostname +
     387                                        '/wp-admin/customize.php?theme=' + site.get( 'theme_slug' ) + '&wcsc_source_site_id=' + site.get( 'site_id' );
    63388                        }
     389                },
     390
     391                renderSearch: function () {
     392                        this.view.render();
    64393                }
     394
    65395        } );
    66396
    67397        /**
     
    96426
    97427                return urlParams;
    98428        }
    99 } )( window.wp, jQuery );
     429} )( window.wp, jQuery, Backbone, window );
     430 No newline at end of file
  • wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php

    diff --git wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php
    index c398534..7d40aed 100755
    defined( 'WPINC' ) or die(); 
    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
    function register_scripts() { 
    3434        wp_register_script(
    3535                'wordcamp-site-cloner',
    3636                plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
    37                 array( 'jquery', 'customize-controls' ),
     37                array( 'jquery', 'customize-controls', 'wp-backbone' ),
    3838                1,
    3939                true
    4040        );
    function add_submenu_page() { 
    6363 */
    6464function register_customizer_components( $wp_customize ) {
    6565        require_once( __DIR__ . '/includes/source-site-id-setting.php' );
    66         require_once( __DIR__ . '/includes/sites-section.php' );
    6766        require_once( __DIR__ . '/includes/site-control.php' );
    6867
    69         $wp_customize->register_control_type( __NAMESPACE__ . '\Site_Control' );
    70 
    7168        $wp_customize->add_setting( new Source_Site_ID_Setting(
    7269                $wp_customize,
    7370                'wcsc_source_site_id',
    7471                array()
    7572        ) );
    7673
    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' ),
     74        $wp_customize->add_section( 'wcsc_sites', array(
     75                        'title' => __( 'Clone Another WordCamp', 'wordcamporg' ),
    8376                )
    8477        );
    8578
    86         $wp_customize->add_section( new Sites_Section(
     79        $wp_customize->add_control( new Site_Control(
    8780                $wp_customize,
    88                 'wcsc_sites',
     81                'wcsc_site_search',
    8982                array(
    90                         'panel' => 'wordcamp_site_cloner',
    91                         'title' => __( 'WordCamp Sites', 'wordcamporg' ),
     83                        'type'     => 'wcscSearch',
     84                        'label'    => __( 'Search', 'wordcamporg' ),
     85                        'settings' => 'wcsc_source_site_id',
     86                        'section'  => 'wcsc_sites'
    9287                )
    9388        ) );
    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(
    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         }
    11289}
    11390
    11491/**
    function get_screenshot_url( $site_url ) { 
    195172
    196173        return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url );
    197174}
     175
     176/**
     177 * Quick and ugly handling to provide a JSON endpoint for the WordCamp Sites data
     178 *
     179 * @todo Make a real API
     180 */
     181add_action( 'wp_loaded', function() {
     182        if ( ! empty( $_GET[ 'get_wordcamp_sites' ] ) ) {
     183                $sites = \WordCamp\Site_Cloner\get_wordcamp_sites();
     184
     185                $fakeSites = array_merge( $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                        $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites );
     196
     197                //hack to expand with fake data for now so the front-end can be worked on without having to create 100s of sites
     198                if ( defined( 'WCSC_MOCK_FILL_SITES' ) && WCSC_MOCK_FILL_SITES ) {
     199                        $fake_site_id = 10000;
     200                        foreach ( $fakeSites as &$site ) {
     201                                $site[ 'site_id' ] = $fake_site_id;
     202                                $site[ 'name' ] = $site[ 'name' ] . " Mock Only (Don't Click) - " . $fake_site_id;
     203                                $fake_site_id++;
     204                        }
     205
     206                        $sites = array_merge( $sites, $fakeSites );
     207                }
     208
     209                wp_send_json( $sites );
     210        }
     211} );
     212 No newline at end of file