Making WordPress.org

Ticket #1112: 1112.diff

File 1112.diff, 22.9 KB (added by prettyboymp, 10 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