Making WordPress.org


Ignore:
Timestamp:
10/21/2016 04:10:02 PM (10 years ago)
Author:
iandunn
Message:

WordCamp Site Cloner: Convert to REST/Backbone to improve scalability

See #1112
Props prettyboymp

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php

    r2933 r4280  
    11<?php
    2 
    3 namespace WordCamp\Site_Cloner;
    4 
    5 defined( 'WPINC' ) or die();
    62
    73/*
    84Plugin Name: WordCamp Site Cloner
    95Description: Allows organizers to clone another WordCamp's theme and custom CSS as a starting point for their site.
    10 Version:     0.1
     6Version:     0.2
    117Author:      WordCamp.org
    128Author URI:  http://wordcamp.org
     
    1410*/
    1511
    16 // todo if Jetpack_Custom_CSS:get_css is callable, register these, otherwise fatal errors
    17 
    18 add_action( 'plugins_loaded',        __NAMESPACE__ . '\get_wordcamp_sites' );
    19 add_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' );
     12namespace WordCamp\Site_Cloner;
     13defined( 'WPINC' ) or die();
     14
     15const PRIME_SITES_CRON_ACTION      = 'wcsc_prime_sites';
     16const WORDCAMP_SITES_TRANSIENT_KEY = 'wcsc_sites';
     17
     18/**
     19 * Initialization
     20 */
     21function initialize() {
     22        // We rely on the Custom CSS module being available
     23        if ( ! class_exists( '\Jetpack' ) ) {
     24                return;
     25        }
     26
     27        add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\register_scripts'               );
     28        add_action( 'admin_menu',            __NAMESPACE__ . '\add_submenu_page'               );
     29        add_action( 'customize_register',    __NAMESPACE__ . '\register_customizer_components' );
     30        add_action( 'rest_api_init',         __NAMESPACE__ . '\register_api_endpoints'         );
     31        add_action( PRIME_SITES_CRON_ACTION, __NAMESPACE__ . '\prime_wordcamp_sites'           );
     32
     33        if ( ! wp_next_scheduled( PRIME_SITES_CRON_ACTION ) ) {
     34                wp_schedule_event( time(), 'daily', PRIME_SITES_CRON_ACTION );
     35        }
     36}
     37add_action( 'plugins_loaded', __NAMESPACE__ . '\initialize' ); // After Jetpack has loaded
    2238
    2339/**
     
    2945                plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
    3046                array(),
    31                 1
     47                2
    3248        );
    3349
     
    3551                'wordcamp-site-cloner',
    3652                plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
    37                 array( 'jquery', 'customize-controls' ),
    38                 1,
     53                array( 'jquery', 'customize-controls', 'wp-backbone' ),
     54                2,
    3955                true
    4056        );
     57
     58        wp_localize_script(
     59                'wordcamp-site-cloner',
     60                '_wcscSettings',
     61                array(
     62                        'apiUrl'        => get_rest_url( null, '/wordcamp-site-cloner/v1/sites/' ),
     63                        'customizerUrl' => admin_url( 'customize.php' ),
     64                        'themes'        => get_available_themes(),
     65                )
     66        );
     67}
     68
     69/**
     70 * Get all of the available themes
     71 *
     72 * @return array
     73 */
     74function get_available_themes() {
     75        /** @var \WP_Theme $theme */
     76        $available_themes = array();
     77        $raw_themes       = wp_get_themes( array( 'allowed' => true ) );
     78
     79        foreach ( $raw_themes as $theme ) {
     80                $theme_name         = $theme->display( 'Name' );
     81                $available_themes[] = array(
     82                        'slug' => $theme->get_stylesheet(),
     83                        'name' => $theme_name ?: $theme->get_stylesheet()
     84                );
     85        }
     86
     87        return $available_themes;
    4188}
    4289
     
    53100                __( 'Clone Another WordCamp', 'wordcamporg' ),
    54101                'switch_themes',
    55                 'customize.php?autofocus[panel]=wordcamp_site_cloner'
     102                'customize.php?autofocus[section]=wcsc_sites'
    56103        );
    57104}
     
    64111function register_customizer_components( $wp_customize ) {
    65112        require_once( __DIR__ . '/includes/source-site-id-setting.php' );
    66         require_once( __DIR__ . '/includes/sites-section.php' );
    67113        require_once( __DIR__ . '/includes/site-control.php' );
    68 
    69         $wp_customize->register_control_type( __NAMESPACE__ . '\Site_Control' );
    70114
    71115        $wp_customize->add_setting( new Source_Site_ID_Setting(
    72116                $wp_customize,
    73117                'wcsc_source_site_id',
    74                 array()
     118                array( 'capability' => 'switch_themes' )
    75119        ) );
    76120
    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                 )
    84         );
    85 
    86         $wp_customize->add_section( new Sites_Section(
    87                 $wp_customize,
     121        $wp_customize->add_section(
    88122                'wcsc_sites',
    89123                array(
    90                         'panel' => 'wordcamp_site_cloner',
    91                         'title' => __( 'WordCamp Sites', 'wordcamporg' ),
     124                        'title'      => __( 'Clone Another WordCamp', 'wordcamporg' ),
     125                        'capability' => 'switch_themes'
     126                )
     127        );
     128
     129        $wp_customize->add_control( new Site_Control(
     130                $wp_customize,
     131                'wcsc_site_search',
     132                array(
     133                        'type'     => 'wcscSearch',
     134                        'label'    => __( 'Search', 'wordcamporg' ),
     135                        'settings' => 'wcsc_source_site_id',
     136                        'section'  => 'wcsc_sites'
    92137                )
    93138        ) );
    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         }
    112 }
    113 
    114 /**
    115  * Get required data for relevant WordCamp sites
    116  *
    117  * This isn't actually used until register_customizer_components(), but it's called during `plugins_loaded` in
    118  * order to prime the cache. That has to be done before `setup_theme`, because the Theme Switcher will override
    119  * the current theme when `?theme=` is present in the URL parameters, and it's safer to just avoid that than to
    120  * muck with the internals and try to reverse it on the fly.
     139}
     140
     141/**
     142 * Register the REST API endpoint for the Customizer to use to retriever the site list
     143 */
     144function register_api_endpoints() {
     145        if ( ! current_user_can( 'switch_themes' ) ) {
     146                return;
     147
     148                // todo - use `permission_callback` instead
     149        }
     150
     151        register_rest_route(
     152                'wordcamp-site-cloner/v1',
     153                '/sites',
     154                array(
     155                        'methods'  => 'GET',
     156                        'callback' => __NAMESPACE__ . '\sites_endpoint',
     157                )
     158        );
     159}
     160
     161/**
     162 * Handle the response for the Sites endpoint
     163 *
     164 * This always pulls cached data, because Central needs to be the site generating it. See get_wordcamp_sites().
    121165 *
    122166 * @return array
    123167 */
     168function sites_endpoint() {
     169        $sites        = array();
     170        $cached_sites = get_site_transient( WORDCAMP_SITES_TRANSIENT_KEY );
     171
     172        if ( $cached_sites ) {
     173                unset( $cached_sites[ get_current_blog_id() ] );
     174
     175            $sites = array_values( $cached_sites );
     176        }
     177
     178        return $sites;
     179}
     180
     181/**
     182 * Prime the cache of cloneable WordCamp sites
     183 *
     184 * This is called via WP Cron.
     185 *
     186 * @todo - Reintroduce batching from `1112.3.diff` to get more than 500 sites
     187 */
     188function prime_wordcamp_sites() {
     189        // This only needs to run on a single site, then the whole network can use the cached result
     190        if ( ! is_main_site() ) {
     191                return;
     192        }
     193
     194        // Keep the cache longer than needed, just to be sure that it doesn't expire before the cron job runs again
     195        set_site_transient( WORDCAMP_SITES_TRANSIENT_KEY, get_wordcamp_sites(), DAY_IN_SECONDS * 2 );
     196}
     197
     198/**
     199 * Get WordCamp sites that are suitable for cloning
     200 *
     201 * @return array
     202 */
    124203function get_wordcamp_sites() {
    125         require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' );
    126 
    127         // plugins_loaded is runs on every screen, but we only need this when loading the Customizer and Previewer
    128         if ( 'customize.php' != basename( $_SERVER['SCRIPT_NAME'] ) && empty( $_REQUEST['wp_customize'] ) ) {
     204        /*
     205         * The post statuses that \WordCamp_Loader::get_public_post_statuses() returns are only created on Central,
     206         * because the plugin isn't active on any other sites.
     207         */
     208        if ( ! is_main_site() ) {
    129209                return array();
    130210        }
    131211
    132         $transient_key = 'wcsc_sites';
    133 
    134         if ( $sites = get_site_transient( $transient_key ) ) {
    135                 return $sites;
     212        if ( ! \Jetpack::is_module_active( 'custom-css' ) ) {
     213                \Jetpack::activate_module( 'custom-css', false, false );
    136214        }
    137215
    138216        switch_to_blog( BLOG_ID_CURRENT_SITE ); // central.wordcamp.org
    139217
    140         $sites = array();
    141         $wordcamps = get_posts( array(
    142                 'post_type'      => 'wordcamp',
     218        $wordcamp_query = new \WP_Query( array(
     219                'post_type'      => WCPT_POST_TYPE_ID,
    143220                '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
     221                'posts_per_page' => 500,
    145222                'meta_key'       => 'Start Date (YYYY-mm-dd)',
    146223                'orderby'        => 'meta_value_num',
     224                'order'          => 'DESC',
    147225
    148226                'meta_query' => array(
    149227                        array(
     228                                // New sites won't have finished designs, so ignore them
    150229                                'key'     => 'Start Date (YYYY-mm-dd)',
    151230                                'value'   => strtotime( 'now - 1 month' ),
    152231                                'compare' => '<'
    153                         ),
     232                        )
    154233                ),
    155234        ) );
    156235
    157         foreach( $wordcamps as $wordcamp ) {
    158                 $site_id  = get_wordcamp_site_id( $wordcamp );
    159                 $site_url = get_post_meta( $wordcamp->ID, 'URL', true );
    160 
    161                 if ( ! $site_id || ! $site_url ) {
     236        $sites = get_filtered_wordcamp_sites( $wordcamp_query->get_posts() );
     237
     238        uasort( $sites, __NAMESPACE__ . '\sort_sites_by_year' );
     239
     240        restore_current_blog();
     241
     242        return $sites;
     243}
     244
     245/**
     246 * Filter out sites that aren't relevant to the Cloner
     247 *
     248 * @param array $wordcamps
     249 *
     250 * @return array
     251 */
     252function get_filtered_wordcamp_sites( $wordcamps ) {
     253        $sites = array();
     254
     255        foreach ( $wordcamps as $wordcamp ) {
     256                $site_id    = get_wordcamp_site_id( $wordcamp );
     257                $site_url   = get_post_meta( $wordcamp->ID, 'URL',                     true );
     258                $start_date = get_post_meta( $wordcamp->ID, 'Start Date (YYYY-mm-dd)', true );
     259
     260                if ( ! $site_id || ! $site_url || ! $start_date ) {
    162261                        continue;
    163262                }
     
    165264                switch_to_blog( $site_id );
    166265
    167                 $sites[] = array(
    168                         'site_id'        => $site_id,
    169                         'name'           => get_wordcamp_name(),
    170                         'theme_slug'     => get_stylesheet(),
    171                         'screenshot_url' => get_screenshot_url( $site_url ),
    172                 );
     266                /*
     267                 * Sites with Coming Soon enabled probably don't have a finished design yet, so there's no point in
     268                 * cloning it.
     269                 */
     270                if ( ! coming_soon_plugin_enabled() ) {
     271                        $preprocessor = \Jetpack_Custom_CSS::get_preprocessor();
     272                        $preprocessor = isset( $preprocessor[ 'name' ] ) ? $preprocessor[ 'name' ] : 'none';
     273
     274                        $sites[ $site_id ] = array(
     275                                'site_id'          => $site_id,
     276                                'name'             => get_wordcamp_name(),
     277                                'theme_slug'       => get_stylesheet(),
     278                                'screenshot_url'   => get_screenshot_url( $site_url ),
     279                                'year'             => date( 'Y', $start_date ),
     280                                'css_preprocessor' => $preprocessor,
     281                        );
     282                }
    173283
    174284                restore_current_blog();
    175285        }
    176286
    177         restore_current_blog();
    178 
    179         set_site_transient( $transient_key, $sites, DAY_IN_SECONDS );
    180 
    181287        return $sites;
     288}
     289
     290/**
     291 * Determine if the Coming Soon plugin is enabled for the current site
     292 *
     293 * @return bool
     294 */
     295function coming_soon_plugin_enabled() {
     296        global $WCCSP_Settings;
     297        $enabled = false;
     298
     299        if ( ! is_callable( 'WCCSP_Settings::get_settings' ) ) {
     300                return $enabled;
     301        }
     302
     303        // We may need to instantiate the class if this is the first time calling this function
     304        if ( ! is_a( $WCCSP_Settings, 'WCCSP_Settings' ) ) {
     305                $WCCSP_Settings = new \WCCSP_Settings();
     306        }
     307
     308        $settings = $WCCSP_Settings->get_settings();
     309
     310        if ( isset( $settings[ 'enabled' ] ) && 'on' === $settings[ 'enabled' ] ) {
     311                $enabled = true;
     312        }
     313
     314        return $enabled;
    182315}
    183316
     
    196329        return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url );
    197330}
     331
     332/**
     333 * Sort arrays by the year
     334 *
     335 * @param array $site_a
     336 * @param array $site_b
     337 *
     338 * @return int
     339 */
     340function sort_sites_by_year( $site_a, $site_b ) {
     341        if ( $site_a[ 'year' ] === $site_b[ 'year' ] ) {
     342                return 0;
     343        }
     344
     345        return ( $site_a[ 'year' ] < $site_b[ 'year' ] ? 1 : -1 );
     346}
Note: See TracChangeset for help on using the changeset viewer.