<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[2124] sites/trunk/wordcamp.org/public_html/wp-content/plugins: WordCamp Remote CSS: Initial commit.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/2124">2124</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/2124","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2015-11-24 18:57:05 +0000 (Tue, 24 Nov 2015)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>WordCamp Remote CSS: Initial commit.</pre>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/</li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappcommonphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/common.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappoutputcachedcssphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/output-cached-css.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappsynchronizeremotecssphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/synchronize-remote-css.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappuserinterfacephp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/user-interface.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappwebhookhandlerphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/webhook-handler.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssbootstrapphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/bootstrap.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/platforms/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssplatformsgithubphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/platforms/github.php</a></li>
<li>sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/</li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpautomatedsynchronizationphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-automated-synchronization.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpbasicsetupphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-basic-setup.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpoverviewphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-overview.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelptipsphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-tips.php</a></li>
<li><a href="#sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewspageremotecssphp">sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/page-remote-css.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappcommonphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/common.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/common.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/common.php  2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,57 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+const SAFE_CSS_POST_SLUG    = 'wcrcss_safe_cached_version';
+const OPTION_LAST_UPDATE    = 'wcrcss_last_update';
+const AJAX_ACTION           = 'wcrcss_webhook';
+const SYNCHRONIZE_ACTION    = 'wcrcss_synchronize';
+const WEBHOOK_RATE_LIMIT    = 30; // seconds
+const OPTION_REMOTE_CSS_URL = 'wcrcss_remote_css_url';
+const CSS_HANDLE            = 'wordcamp_remote_css';
+const GITHUB_API_HOSTNAME   = 'api.github.com';
+
+/**
+ * Find the ID of the post we use to store the safe CSS
+ *
+ * @return int|\WP_Error
+ */
+function get_safe_css_post_id() {
+       $post    = get_safe_css_post();
+       $post_id = is_a( $post, 'WP_Post' ) ? $post->ID : $post;
+
+       return $post_id;
+}
+
+/**
+ * Find the post we use to store the safe CSS
+ *
+ * @return \WP_Post|\WP_Error
+ */
+function get_safe_css_post() {
+       $safe_css_post = get_posts( array(
+               'posts_per_page' => 1,
+               'post_type'      => 'safecss',
+               'post_status'    => 'private',
+               'post_name'      => SAFE_CSS_POST_SLUG,
+       ) );
+
+       if ( $safe_css_post ) {
+               $post = $safe_css_post[0];
+       } else {
+               $post = wp_insert_post( array(
+                       'post_type'   => 'safecss',
+                       'post_status' => 'private', // Jetpack_Custom_CSS::post_id() only searches for `public` posts, so this prevents Jetpack from fetching our post
+                       'post_title'  => SAFE_CSS_POST_SLUG,
+                       'post_name'   => SAFE_CSS_POST_SLUG,
+               ), true );
+
+               if ( ! is_wp_error( $post ) ) {
+                       $post = get_post( $post );
+               }
+       }
+
+       return $post;
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappoutputcachedcssphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/output-cached-css.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/output-cached-css.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/output-cached-css.php       2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,106 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+add_action( 'wp_enqueue_scripts',           __NAMESPACE__ . '\enqueue_cached_css', 11 );  // after the theme's stylesheet, but before before Jetpack Custom CSS's stylesheet
+add_action( 'wp_ajax_'        . CSS_HANDLE, __NAMESPACE__ . '\output_cached_css'      );
+add_action( 'wp_ajax_nopriv_' . CSS_HANDLE, __NAMESPACE__ . '\output_cached_css'      );
+add_filter( 'nocache_headers',              __NAMESPACE__ . '\set_cache_headers'      );
+
+/**
+ * Enqueue the cached CSS
+ *
+ * An AJAX endpoint is used because the CSS is stored in the database, rather than on the file system.
+ */
+function enqueue_cached_css() {
+       if ( false === get_option( OPTION_REMOTE_CSS_URL ) ) {
+               return;
+       }
+
+       $cachebuster = get_latest_revision_id();
+
+       if ( ! $cachebuster ) {
+               $cachebuster = date( 'Y-m-d' ); // We should always have a revision ID, but this will work as a fallback if we don't for some reason
+       }
+
+       wp_enqueue_style(
+               CSS_HANDLE,
+               add_query_arg( 'action', CSS_HANDLE, admin_url( 'admin-ajax.php' ) ),
+               array(),
+               $cachebuster,
+               'all'
+       );
+}
+
+/**
+ * Get the ID of the latest revision of the safe CSS post
+ *
+ * @return int|bool
+ */
+function get_latest_revision_id() {
+       $safe_css = get_safe_css_post();
+
+       if ( ! is_a( $safe_css, 'WP_Post' ) || empty( $safe_css->post_content_filtered ) ) {
+               return false;
+       }
+       $latest_revision = wp_get_post_revisions( $safe_css->ID, array( 'posts_per_page' => 1 ) );
+
+       if ( empty( $latest_revision ) ) {
+               return false;
+       }
+
+       $latest_revision = array_shift( $latest_revision );
+
+       return $latest_revision->ID;
+}
+
+/**
+ * Adjust the HTTP response headers so that browsers will cache the CSS we send
+ *
+ * Normally Core prevents caching of all AJAX requests, but we want to make sure the CSS is cached because it's
+ * loaded on every front-end request.
+ *
+ * @param array $cache_headers
+ *
+ * @return array
+ */
+function set_cache_headers( $cache_headers ) {
+       if ( ! defined( 'DOING_AJAX' ) || empty( $_GET['action'] ) || CSS_HANDLE !== $_GET['action'] ) {
+               return $cache_headers;
+       }
+
+       $safe_css = get_safe_css_post();
+
+       if ( ! is_a( $safe_css, 'WP_Post' ) ) {
+               return $cache_headers;
+       }
+
+       $last_modified     = date( 'D, d M Y H:i:s', strtotime( $safe_css->post_date_gmt ) ) . ' GMT';
+       $expiration_period = YEAR_IN_SECONDS;
+
+       $cache_headers = array(
+               'Cache-Control' => 'maxage=' . $expiration_period,
+               'ETag'          => '"' . md5( $last_modified ) . '"',
+               'Last-Modified' => $last_modified, // Currently Core always strips this out, but we want to send it, and maybe Core will allow that in the future
+               'Expires'       => gmdate( 'D, d M Y H:i:s', time() + $expiration_period ) . ' GMT',
+       );
+
+       return $cache_headers;
+}
+
+/**
+ * Handles the AJAX endpoint to output the local copy of the CSS
+ */
+function output_cached_css() {
+       header( 'Content-Type: text/css; charset=' . get_option( 'blog_charset' ) );
+
+       $safe_css = get_safe_css_post();
+
+       if ( is_a( $safe_css, 'WP_Post' ) ) {
+               echo $safe_css->post_content_filtered;
+       }
+
+       wp_die();
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappsynchronizeremotecssphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/synchronize-remote-css.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/synchronize-remote-css.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/synchronize-remote-css.php  2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,93 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+/**
+ * Synchronizes the local safe/cached copy of the CSS with the canonical, remote source.
+ *
+ * @param string $remote_css_url
+ */
+function synchronize_remote_css( $remote_css_url ) {
+       sanitize_and_save_unsafe_css( fetch_unsafe_remote_css( $remote_css_url ) );
+       update_option( OPTION_LAST_UPDATE, time() );
+}
+
+/**
+ * Fetch the unsafe CSS from the remote server
+ *
+ * @param string $remote_css_url
+ *
+ * @throws \Exception if the response body could not be retrieved for any reason
+ *
+ * @return string
+ */
+function fetch_unsafe_remote_css( $remote_css_url ) {
+       $response = wp_remote_get(
+               $remote_css_url,
+               array(
+                       'user-agent'         => 'WordCamp.org Remote CSS',  // GitHub's API explicitly requests this, and it could be beneficial for other platforms too
+                       'reject_unsafe_urls' => true,
+               )
+       );
+
+       if ( is_wp_error( $response ) ) {
+               throw new \Exception( $response->get_error_message() );
+       }
+
+       $response_code = (int) wp_remote_retrieve_response_code( $response );
+
+       if ( ! in_array( $response_code, array( 200, 301, 302, 303, 307, 308 ), true ) ) {
+               throw new \Exception( sprintf(
+                       __( 'The remote server responded with status code <code>%d</code>, which is not valid.', 'wordcamporg' ),
+                       $response_code
+               ) );
+       }
+
+       return apply_filters( 'wcrcss_unsafe_remote_css', wp_remote_retrieve_body( $response ), $remote_css_url );
+}
+
+/**
+ * Sanitize unsafe CSS and save the safe version
+ *
+ * Note: If we ever need to decouple from Jetpack Custom CSS, then https://github.com/google/caja might be
+ * a viable alternative. It'd be nice to have a modular solution, but we'd also have to keep it up to date,
+ * and we'd still need to mirror the Output Mode setting.
+ *
+ * @param string $unsafe_css
+ *
+ * @throws \Exception if Jetpack's Custom CSS module isn't available
+ */
+function sanitize_and_save_unsafe_css( $unsafe_css ) {
+       if ( ! is_callable( array( '\Jetpack_Custom_CSS', 'save' ) ) ) {
+               throw new \Exception(
+                       __( "<code>Jetpack_Custom_CSS::save()</code> is not available.
+                       Please make sure Jetpack's Custom CSS module has been activated.", 'wordcamporg' )
+               );
+       }
+
+       /*
+        * Note: In addition to the sanitization that Jetpack_Custom_CSS::save() does, there's additional sanitization
+        * done by the callbacks in mu-plugins/jetpack-tweaks.php.
+        */
+
+       add_filter( 'jetpack_custom_css_pre_post_id', __NAMESPACE__ . '\get_safe_css_post_id' );
+
+       \Jetpack_Custom_CSS::save( array(
+               'css'             => $unsafe_css,
+               'is_preview'      => false,
+               'preprocessor'    => '',     // This should never be changed to allow pre-processing. See note in validate_remote_css_url()
+               'add_to_existing' => false,  // This isn't actually used, see get_output_mode()
+               'content_width'   => false,
+       ) );
+
+       remove_filter( 'jetpack_custom_css_pre_post_id', __NAMESPACE__ . '\get_safe_css_post_id' );
+
+       /*
+        * Jetpack_Custom_CSS::save_revision() caches our post ID because it retrieves the post ID from
+        * Jetpack_Custom_CSS::post_id() while the get_safe_css_post_id() callback is active. We need to clear that
+        * to avoid unintended side-effects.
+        */
+       wp_cache_delete( 'custom_css_post_id' );
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappuserinterfacephp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/user-interface.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/user-interface.php                                (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/user-interface.php  2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,308 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+add_action( 'admin_menu', __NAMESPACE__ . '\add_admin_pages' );
+
+/**
+ * Register new admin pages
+ */
+function add_admin_pages() {
+       $page_hook = \add_submenu_page(
+               'themes.php',
+               __( 'Remote CSS', 'wordcamporg' ),
+               __( 'Remote CSS', 'wordcamporg' ),
+               'switch_themes',
+               'remote-css',
+               __NAMESPACE__ . '\render_options_page'
+       );
+
+       add_action( 'admin_print_styles-' . $page_hook, __NAMESPACE__ . '\print_css' );
+       add_action( 'load-'               . $page_hook, __NAMESPACE__ . '\add_contextual_help_tabs' );
+}
+
+/**
+ * Render the view for the options page
+ */
+function render_options_page() {
+       $notice = null;
+
+       if ( isset( $_POST['submit'] ) ) {
+               try {
+                       $notice       = process_options_page();
+                       $notice_class = 'notice-success';
+               } catch( \Exception $exception ) {
+                       $notice         = $exception->getMessage();
+                       $notice_class   = 'notice-error';
+               }
+       }
+
+       $output_mode               = get_output_mode();
+       $remote_css_url            = get_option( OPTION_REMOTE_CSS_URL , '' );
+       $fonts_tool_url            = admin_url( 'themes.php?page=wc-fonts-options' );
+       $jetpack_modules_url       = admin_url( 'admin.php?page=jetpack_modules' );
+       $jetpack_custom_css_active = method_exists( '\Jetpack_Custom_CSS', 'init' );
+
+       require_once( dirname( __DIR__ ) . '/views/page-remote-css.php' );
+}
+
+/**
+ * Get the mode for outputting the custom CSS.
+ *
+ * This just uses the same mode as Jetpack's CSS post, because it wouldn't make any sense to have them configured
+ * with opposite values.
+ *
+ * @return string
+ */
+function get_output_mode() {
+       $mode = 'add-on';
+
+       try {
+               $jetpack_css_post_id = get_jetpack_css_post_id();
+
+               if ( 'no' === get_post_meta( $jetpack_css_post_id, 'custom_css_add', true ) ) {
+                       $mode = 'replace';
+               }
+       } catch( \Exception $exception ) {
+               // Just fall back to the default $mode
+       }
+
+       return $mode;
+}
+
+/**
+ * Get or set the mode for outputting the custom CSS.
+ *
+ * See get_output_mode() for notes.
+ *
+ * @param string $mode
+ */
+function set_output_mode( $mode ) {
+       $mode        = 'replace' === $mode ? 'no' : 'yes';
+       $revision_id = get_jetpack_css_latest_revision_id();
+
+       update_post_meta( get_jetpack_css_post_id(), 'custom_css_add', $mode );
+
+       if ( $revision_id ) {
+               update_metadata( 'post', $revision_id, 'custom_css_add', $mode );   // update_post_meta doesn't allow modifying revisions
+       }
+}
+
+/**
+ * Get the ID of Jetpack's safecss post
+ *
+ * If it doesn't exist yet, create it, because we'll need it for the Output Mode setting.
+ *
+ * @throws \Exception if Jetpack's Custom CSS module isn't available
+ *
+ * @return int
+ */
+function get_jetpack_css_post_id() {
+       if ( ! is_callable( array( '\Jetpack_Custom_CSS', 'post_id' ) ) ) {
+               throw new \Exception(
+                       __( "<code>Jetpack_Custom_CSS::post_id()</code> is not available.
+                       Please make sure Jetpack's Custom CSS module has been activated.",
+                       'wordcamporg' )
+               );
+       }
+
+       $post_id = \Jetpack_Custom_CSS::post_id();
+
+       if ( ! is_int( $post_id ) || 0 === $post_id ) {
+               $post_values = array(
+                       'post_content'          => '',
+                       'post_title'            => 'safecss',
+                       'post_status'           => 'publish',
+                       'post_type'             => 'safecss',
+                       'post_content_filtered' => '',
+               );
+
+               $post_id = wp_insert_post( $post_values, true );
+
+               if ( is_wp_error( $post_id ) ) {
+                       throw new \Exception( $post_id->get_error_message() );
+               }
+       }
+
+       return $post_id;
+}
+
+/**
+ * Get the ID of the current Jetpack's safecss revision post
+ *
+ * @throws \Exception if Jetpack's Custom CSS module isn't available
+ *
+ * @return int|bool
+ */
+function get_jetpack_css_latest_revision_id() {
+       if ( ! is_callable( array( '\Jetpack_Custom_CSS', 'get_current_revision' ) ) ) {
+               throw new \Exception(
+                       __( "<code>Jetpack_Custom_CSS::get_current_revision()</code> is not available.
+                       Please make sure Jetpack's Custom CSS module has been activated.",
+                       'wordcamporg' )
+               );
+       }
+
+       $revision = \Jetpack_Custom_CSS::get_current_revision();
+
+       return empty( $revision['ID'] ) ? false : $revision['ID'];
+}
+
+/**
+ * Process submissions of the form on the options page
+ *
+ * @throws \Exception if the user isn't authorized
+ *
+ * @return string
+ */
+function process_options_page() {
+       check_admin_referer( 'wcrcss-options-submit', 'wcrcss-options-nonce' );
+
+       if ( ! current_user_can( 'switch_themes' ) ) {
+               throw new \Exception( __( 'Access denied.', 'wordcamporg' ) );
+       }
+
+       $remote_css_url = trim( $_POST['wcrcss-remote-css-url'] );
+
+       if ( '' === $remote_css_url ) {
+               $notice = '';
+               wp_delete_post( get_safe_css_post_id() );
+       } else {
+               $notice         = __( 'The remote CSS file was successfully synchronized.', 'wordcamporg' );
+               $remote_css_url = validate_remote_css_url( $remote_css_url );
+
+               synchronize_remote_css( $remote_css_url );
+               set_output_mode( $_POST['wcrcss-output-mode'] );
+       }
+
+       update_option( OPTION_REMOTE_CSS_URL, $remote_css_url );
+
+       return $notice;
+}
+
+/**
+ * Validate the remote CSS URL provided by the user
+ *
+ * @param string $remote_css_url
+ *
+ * @throws \Exception if the URL cannot be validated
+ *
+ * @return string
+ */
+function validate_remote_css_url( $remote_css_url ) {
+       // Syntactically-valid URLs only
+       $remote_css_url = filter_var( $remote_css_url, FILTER_VALIDATE_URL );
+
+       if ( false === $remote_css_url ) {
+               throw new \Exception( __( 'The URL was invalid.', 'wordcamporg' ) );
+       }
+
+       $remote_css_url = esc_url_raw( $remote_css_url, array( 'http', 'https' ) );
+       $parsed_url     = parse_url( $remote_css_url );
+
+       /*
+        * Only allow whitelisted hostnames, to prevent SSRF attacks
+        *
+        * WARNING: These must be trusted in the sense that they're not malicious, but also in the sense that they
+        * have strong internal security. We can't allow sites hosted by local WordPress communities, for instance,
+        * because an attacker could gain control over their DNS zone and then change the A record to 127.0.0.1,
+        * or an IP on our internal network.
+        *
+        * Therefore, only reputable platforms like GitHub, Beanstalk, CloudForge, BitBucket, etc should be added.
+        */
+       $trusted_hostnames = apply_filters( 'wcrcss_trusted_remote_hostnames', array() );
+
+       if ( ! in_array( $parsed_url['host'], $trusted_hostnames, true ) ) {
+               throw new \Exception( sprintf(
+                       __( 'Due to security constraints, only certain third-party platforms can be used,
+                       and the URL you provided is not hosted by one of our currently-supported platforms.
+                       To request that it be added, please <a href="%s">create a ticket</a> on Meta Trac.',
+                       'wordcamporg' ),
+                       'https://meta.trac.wordpress.org/newticket'
+               ) );
+       }
+
+       /*
+        * Vanilla CSS only
+        *
+        * We need to force the user to do their own pre-processing, because Jetpack_Custom_CSS::save() doesn't
+        * sanitize the unsafe CSS when a preprocessor is present. We'd have to add more logic to make sure it gets
+        * sanitized, which would further couple the plugin to Jetpack.
+        */
+       if ( '.css' !== substr( $parsed_url['path'], strlen( $parsed_url['path'] ) - 4, 4 ) ) {
+               throw new \Exception(
+                       __( 'The URL must be a vanilla CSS file ending in <code>.css</code>.
+                       If you\'d like to use SASS/LESS, please compile it into vanilla CSS on your server,
+                       and then enter the URL for that file.',
+                       'wordcamporg' )
+               );
+       }
+
+       /*
+        * Note: We also want to restrict the URL to ports 80, 443, and 8080. The 'reject_unsafe_urls' in
+        * fetch_unsafe_remote_css() takes care of that for us.
+        */
+
+       return apply_filters( 'wcrcss_validate_remote_css_url', $remote_css_url );
+}
+
+/**
+ * Print CSS for the options page
+ */
+function print_css() {
+       ?>
+
+       <style type="text/css">
+               body.appearance_page_remote-css button.button-link {
+                       color: #0073aa;
+                       padding: 0;
+               }
+       </style>
+
+       <?php
+}
+
+/**
+ * Register contextual help tabs
+ */
+function add_contextual_help_tabs() {
+       $screen = get_current_screen();
+       $tabs   = array( 'Overview', 'Basic Setup', 'Automated Synchronization', 'Tips' );
+
+       foreach ( $tabs as $tab ) {
+               $screen->add_help_tab( array(
+                       'id'       => 'wcrcss-' . sanitize_title( $tab ),
+                       'title'    => $tab,
+                       'callback' => __NAMESPACE__ . '\render_contextual_help_tabs',
+               ) );
+       }
+}
+
+/**
+ * Render contextual help tabs
+ *
+ * @param \WP_Screen $screen
+ * @param array      $tab
+ */
+function render_contextual_help_tabs( $screen, $tab ) {
+       $view_slug = str_replace( 'wcrcss-', '', $tab['id'] );
+
+       switch ( $view_slug ) {
+               case 'overview':
+                       $jetpack_editor_url = admin_url( 'themes.php?page=editcss' );
+                       break;
+
+               case 'automated-synchronization':
+                       $webhook_payload_url = sprintf( '%s?action=%s', admin_url( 'admin-ajax.php' ), AJAX_ACTION );
+                       break;
+
+               case 'tips':
+                       $fonts_tool_url    = admin_url( 'themes.php?page=wc-fonts-options' );
+                       $media_library_url = admin_url( 'upload.php' );
+                       break;
+       }
+
+       require_once( sprintf( '%s/views/help-%s.php', dirname( __DIR__ ), $view_slug ) );
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssappwebhookhandlerphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/webhook-handler.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/webhook-handler.php                               (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/app/webhook-handler.php 2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,50 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+add_action( 'wp_ajax_'        . AJAX_ACTION, __NAMESPACE__ . '\webhook_handler'        ); // This is useless in production, but useful for manual testing
+add_action( 'wp_ajax_nopriv_' . AJAX_ACTION, __NAMESPACE__ . '\webhook_handler'        );
+add_action( SYNCHRONIZE_ACTION,              __NAMESPACE__ . '\synchronize_remote_css' );
+
+/**
+ * Trigger a synchronization when a push notification is received
+ *
+ * Because the client can't modify the remote URL that's being used, it's safe to allow anonymous access to this,
+ * provided that requests are rate-limited to prevent a malicious party from making us flood remote servers, which
+ * would result in them blocking us. The worst they could do would be to force us to unnecessarily refresh the cache.
+ *
+ * Avoiding authentication makes the process simpler because there's less to do, and also more flexible, because
+ * we don't have to handle notification formats from various platforms.
+ */
+function webhook_handler() {
+       $time_since_last_sync = time() - get_option( OPTION_LAST_UPDATE, 0 );
+
+       if ( $time_since_last_sync < WEBHOOK_RATE_LIMIT ) {
+               $time_limit_remaining = WEBHOOK_RATE_LIMIT - $time_since_last_sync;
+
+               /*
+                * We only want one event scheduled to prevent abuse and unnecessary requests, but
+                * wp_schedule_single_event() does that for us if the period is under 10 minutes.
+                */
+               wp_schedule_single_event(
+                       time() + $time_limit_remaining,
+                       SYNCHRONIZE_ACTION,
+                       array( get_option( OPTION_REMOTE_CSS_URL ) )
+               );
+
+               wp_send_json_error( sprintf(
+                       __( 'The request could not be executed immediately because of the rate limit. Instead, it has been queued and will run in %d seconds.',
+                       'wordcamporg' ),
+                       $time_limit_remaining
+               ) );
+       } else {
+               try {
+                       do_action( SYNCHRONIZE_ACTION, get_option( OPTION_REMOTE_CSS_URL ) );
+                       wp_send_json_success( __( 'The remote CSS file was successfully synchronized.', 'wordcamporg' ) );
+               } catch ( \Exception $exception ) {
+                       wp_send_json_error( strip_tags( $exception->getMessage() ) );   // strip_tags() instead of wp_strip_tags() because we want to preserve the inner content
+               }
+       }
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssbootstrapphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/bootstrap.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/bootstrap.php                         (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/bootstrap.php   2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,27 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+/*
+Plugin Name: WordCamp Remote CSS
+Description: Allows organizers to develop their Custom CSS with whatever tools and environment they prefer.
+Version:     0.1
+Author:      WordCamp.org
+Author URI:  http://wordcamp.org
+License:     GPLv2 or later
+*/
+
+require_once( __DIR__ . '/app/common.php' );
+
+if ( is_admin() ) {
+       require_once( __DIR__ . '/app/synchronize-remote-css.php' );
+       require_once( __DIR__ . '/app/user-interface.php'         );
+       require_once( __DIR__ . '/app/webhook-handler.php'        );
+       require_once( __DIR__ . '/platforms/github.php'           );
+}
+
+if ( ! is_admin() || defined( 'DOING_AJAX' ) ) {
+       require_once( __DIR__ . '/app/output-cached-css.php' );
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssplatformsgithubphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/platforms/github.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/platforms/github.php                          (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/platforms/github.php    2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,81 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+
+defined( 'WPINC' ) or die();
+
+/*
+ * @todo -- Once another platform has been added and you can see the similarities, this should probably be
+ * refactored to extend an abstract class or implement an interface.
+ */
+
+add_filter( 'wcrcss_trusted_remote_hostnames', __NAMESPACE__ . '\whitelist_trusted_hostnames' );
+add_filter( 'wcrcss_validate_remote_css_url',  __NAMESPACE__ . '\convert_to_api_urls'         );
+add_filter( 'wcrcss_unsafe_remote_css',        __NAMESPACE__ . '\decode_api_response',  10, 2 );
+
+/**
+ * Add GitHub's hostnames to the whitelist of trusted hostnames
+ *
+ * @param array $hostnames
+ *
+ * @return array
+ */
+function whitelist_trusted_hostnames( $hostnames ) {
+       return array_merge( $hostnames, array( 'github.com', 'raw.githubusercontent.com', GITHUB_API_HOSTNAME ) );
+}
+
+/**
+ * Convert various GitHub URLs to their API equivalents
+ *
+ * We need to use the API to request the the file contents, because github.com shows the file embedded in an HTML
+ * page, and raw.githubusercontent.come is cached and will often respond with stale content.
+ *
+ * @param string $remote_css_url
+ *
+ * @return string
+ */
+function convert_to_api_urls( $remote_css_url ) {
+       $owner = $repository = $file_path = null;
+
+       $parsed_url = parse_url( $remote_css_url );
+       $path       = explode( '/', $parsed_url['path'] );
+
+       if ( 'github.com' == $parsed_url['host'] ) {
+               $owner      = $path[1];
+               $repository = $path[2];
+               $file_path  = implode( '/', array_slice( $path, 5 ) );
+       } elseif ( 'raw.githubusercontent.com' == $parsed_url['host'] ) {
+               $owner      = $path[1];
+               $repository = $path[2];
+               $file_path  = implode( '/', array_slice( $path, 4 ) );
+       }
+
+       if ( $owner && $repository && $file_path ) {
+               $remote_css_url = sprintf(
+                       'https://%s/repos/%s/%s/contents/%s',
+                       GITHUB_API_HOSTNAME,
+                       $owner,
+                       $repository,
+                       $file_path
+               );
+       }
+
+       return $remote_css_url;
+}
+
+/**
+ * Decode the file contents from GitHub's API response
+ *
+ * @param string $response_body
+ * @param string $remote_css_url
+ *
+ * @return string
+ */
+function decode_api_response( $response_body, $remote_css_url ) {
+       if ( false !== strpos( $remote_css_url, GITHUB_API_HOSTNAME ) ) {
+               $response_body = json_decode( $response_body );
+               $response_body = base64_decode( $response_body->content );
+       }
+
+       return $response_body;
+}
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpautomatedsynchronizationphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-automated-synchronization.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-automated-synchronization.php                              (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-automated-synchronization.php        2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,59 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+defined( 'WPINC' ) or die();
+
+?>
+
+<p>
+       <?php _e( "You don't have to manually synchronize the local file every time you make a change to the remote file;
+       instead, you can setup a webhook to trigger synchronization automatically.", 'wordcamporg' ); ?>
+</p>
+
+<h2><?php _e( 'Setup', 'wordcamporg' ); ?></h2>
+
+<p>
+       <?php _e( "The details will vary depending on your server, but let's use GitHub as an example.", 'wordcamporg' ); ?>
+</p>
+
+<ol>
+       <li>
+               <?php printf(
+                       __( 'Follow <a href="%s">GitHub\'s instructions for creating a webhook</a>.', 'wordcamporg' ),
+                       'https://developer.github.com/webhooks/creating/'
+               ); ?>
+       </li>
+
+       <li>
+               <?php printf(
+                       __( 'For the <code>Payload URL</code>, enter <code>%s</code>.', 'wordcamporg' ),
+                       esc_url( $webhook_payload_url )
+               ); ?>
+       </li>
+
+       <li><?php _e( 'For the rest of the options, you can accept the default values.', 'wordcamporg' ); ?></li>
+</ol>
+
+<p>
+       <?php _e( "If you're not using GitHub, your process will be different, but at the end of the day all you need to do
+       is setup something to open an HTTP request to the payload URL above whenever your file changes.", 'wordcamporg' ); ?>
+</p>
+
+<h2><?php _e( 'Testing &amp; Troubleshooting', 'wordcamporg' ); ?></h2>
+
+<p>
+       <?php _e( 'To test if the synchronization is working, make a change to the file, commit it, push it to GitHub,
+       and then check the site to see if that change is active.', 'wordcamporg' ); ?>
+</p>
+
+<p>
+       <?php _e( "If your change isn't active on WordCamp.org, edit the webhook and scroll down to the <strong>Recent Deliveries</strong> section,
+       then open the latest delivery and look at the <strong>Response</strong> tab for any errors.", 'wordcamporg' ); ?>
+</p>
+
+<p>
+       <?php printf(
+               __( 'If that doesn\'t help solve the problem, you can ask for help in the <code>#meta-wordcamp</code> channel on <a href="%s">Slack</a>.', 'wordcamporg' ),
+               'https://chat.wordpress.org'
+       ); ?>
+</p>
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpbasicsetupphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-basic-setup.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-basic-setup.php                            (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-basic-setup.php      2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,77 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+defined( 'WPINC' ) or die();
+
+?>
+
+<ol>
+       <li>
+               <p>
+                       <?php _e( '<strong>Publish your CSS file</strong> to anywhere on the public Internet.', 'wordcamporg' ); ?>
+               </p>
+
+               <p>
+                       <?php _e( "If you're using SASS or LESS, you'll need to compile it into vanilla CSS and publish that file.", 'wordcamporg' ); ?>
+               </p>
+       </li>
+
+       <li>
+               <p>
+                       <?php _e( '<strong>Enter the URL</strong> for the CSS file into the input box below.', 'wordcamporg' ); ?>
+               </p>
+
+               <p>
+                       <?php printf(
+                               __( 'Due to security constraints, only certain third-party platforms can be used.
+                               We currently only support GitHub, but more platforms can be added if there\'s interest from organizers.
+                               To request an additional platform, please <a href="%s">create a ticket</a> on Meta Trac.', 'wordcamporg' ),
+                               'https://meta.trac.wordpress.org/newticket'
+                       ); ?>
+               </p>
+
+               <p>
+                       <?php _e( "If you're using GitHub, you can enter the URL in any of the following formats,
+                       but we'll convert them to use the GitHub API.", 'wordcamporg' ); ?>
+               </p>
+
+               <ul>
+                       <li>
+                               <?php _e( 'Web-based file browser:', 'wordcamporg' ); ?>
+                               <code>https://github.com/WordPressSeattle/seattle.wordcamp.org-<?php echo esc_html( date( 'Y' ) ); ?>/blob/master/style.css</code>
+                       </li>
+
+                       <li>
+                               <?php _e( 'Raw file:', 'wordcamporg' ); ?>
+                               <code>https://raw.githubusercontent.com/WordPressSeattle/seattle.wordcamp.org-<?php echo esc_html( date( 'Y' ) ); ?>/master/style.css</code>
+                       </li>
+
+                       <li>
+                               <?php _e( 'API:', 'wordcamporg' ); ?>
+                               <code>https://api.github.com/repos/WordPressSeattle/seattle.wordcamp.org-<?php echo esc_html( date( 'Y' ) ); ?>/contents/style.css</code>
+                       </li>
+               </ul>
+       </li>
+
+       <li>
+               <p><?php _e( 'Click the <strong>Update</strong> button.', 'wordcamporg' ); ?></p>
+
+               <p>
+                       <?php _e( "WordCamp.org will download the file, sanitize it, and store a local copy,
+                       then enqueue the local copy as a stylesheet alongside your theme's default stylesheet.", 'wordcamporg' ); ?>
+               </p>
+       </li>
+
+       <li>
+               <?php _e( 'The local copy will need to be <strong>synchronized</strong> whenever you make a change to the file.
+               You can either update manually by pushing the <strong>Update</strong> button again, or update automatically by setting up a webhook.
+               For instructions on setting up a webhook, open the <strong>Automated Synchronization</strong> tab.', 'wordcamporg' ); ?>
+       </li>
+</ol>
+
+<p>
+       <?php printf(
+               __( 'If you run into any problems, you can ask for help in the <code>#meta-wordcamp</code> channel on <a href="%s">Slack</a>.', 'wordcamporg' ),
+               'https://chat.wordpress.org'
+       ); ?>
+</p>
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelpoverviewphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-overview.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-overview.php                               (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-overview.php 2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,39 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+defined( 'WPINC' ) or die();
+
+?>
+
+<p>
+       <?php _e( 'Remote CSS gives you a lot more flexibility in how you develop your site than the Jetpack Custom CSS module.
+       For instance, you can:', 'wordcamporg' ); ?>
+</p>
+
+<ul>
+       <li><?php _e( 'Work in a local development environment, like Varying Vagrant Vagrants.', 'wordcamporg' ); ?></li>
+       <li><?php _e( 'Use your favorite IDE or text-editor, like PhpStorm or Sublime Text.',    'wordcamporg' ); ?></li>
+       <li><?php _e( 'Use SASS or LESS instead of vanilla CSS.',                                'wordcamporg' ); ?></li>
+       <li><?php _e( 'Use tools like Grunt to automate your workflow.',                         'wordcamporg' ); ?></li>
+       <li><?php _e( 'Manage your CSS in a source control system like Git.',                    'wordcamporg' ); ?></li>
+       <li><?php _e( 'Collaborate with others on a social coding platform like GitHub.',        'wordcamporg' ); ?></li>
+</ul>
+
+<p>
+       <?php _e( "You can use all of those tools, only some of them, or completely different ones.
+       It's up to you how you choose to work.", 'wordcamporg' ); ?>
+</p>
+
+<p>
+       <?php _e( "This tool works by fetching your CSS file from a remote server (like GitHub.com), sanitizing the CSS,
+       and then storing a local copy on WordCamp.org. The local copy is then enqueued as a stylesheet, either in addition to your theme's stylesheet,
+       or as a replacement for it. The local copy of the CSS is synchronized with the remote file whenever you press the <strong>Update</strong> button,
+       and you can also setup webhook notifications for automatic synchronization when the remote file changes.", 'wordcamporg' ); ?>
+</p>
+
+<p>
+       <?php printf(
+               __( 'If you\'re looking for something simpler, <a href="%s">Jetpack\'s CSS Editor</a> is a great option.', 'wordcamporg' ),
+               esc_url( $jetpack_editor_url )
+       ); ?>
+</p>
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewshelptipsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-tips.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-tips.php                           (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/help-tips.php     2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,42 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+defined( 'WPINC' ) or die();
+
+?>
+
+<ul>
+       <li>
+               <?php printf(
+                       __( 'We recommend <a href="%s">setting up a local development environment that mirrors WordCamp.org</a>.', 'wordcamporg' ),
+                       'https://make.wordpress.org/community/handbook/wordcamp-organizer-handbook/first-steps/web-presence/contributing-to-wordcamp-org/setting-up-a-local-wordcamp-org-sandbox/'
+               ); ?>
+       </li>
+
+       <li>
+               <?php _e( "Don't use post IDs as selectors, because they can change between your development environment and production.
+               Instead, use the slug; e.g. <code>body.post-slug-call-for-volunteers</code>, or <code>body.wcb_speaker-slug-sergey-biryukov</code>.
+               Just make sure that you update your CSS if you rename a post.", 'wordcamporg' ); ?>
+       </li>
+
+       <li>
+               <?php printf(
+                       __( 'Use <a href="%s">the Fonts tool</a> to embed your web fonts.', 'wordcamporg' ),
+                       esc_url( $fonts_tool_url )
+               ); ?>
+       </li>
+
+       <li>
+               <?php printf(
+                       __( 'Upload your images to <a href="%s">the Media Library</a> rather than hosting them on 3rd party servers.
+                       That way, visitors will avoid an extra DNS request,
+                       and you won\'t have to worry about them going offline if there\'s a problem with the external server.', 'wordcamporg' ),
+                       esc_url( $media_library_url )
+               ); ?>
+       </li>
+
+       <li>
+               <?php _e( "This tool plays nicely with Jetpack's CSS editor, and it's possible to use both.
+               If you do, the rules in the Jetpack editor will take precedence.", 'wordcamporg' ); ?>
+       </li>
+</ul>
</ins></span></pre></div>
<a id="sitestrunkwordcamporgpublic_htmlwpcontentpluginswordcampremotecssviewspageremotecssphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/page-remote-css.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/page-remote-css.php                             (rev 0)
+++ sites/trunk/wordcamp.org/public_html/wp-content/plugins/wordcamp-remote-css/views/page-remote-css.php       2015-11-24 18:57:05 UTC (rev 2124)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,101 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+namespace WordCamp\RemoteCSS;
+defined( 'WPINC' ) or die();
+
+?>
+
+<div class="wrap">
+       <h1><?php _e( 'Remote CSS', 'wordcamporg' ); ?></h1>
+
+       <?php if ( ! $jetpack_custom_css_active ) : ?>
+               <div id="message" class="notice notice-error inline">
+                       <?php
+                               /*
+                                * Jetpack_Custom_CSS is used to sanitize the unsafe CSS, and for removing the theme's stylesheet
+                                * in `replace` mode. Methods from Jetpack_Custom_CSS are called throughout this plugin, so we
+                                * need it to be active.
+                                */
+                       ?>
+
+                       <p>
+                               <?php printf(
+                                       __( 'This tool uses some functionality from Jetpack\'s Custom CSS module,
+                                       but it doesn\'t look like it\'s available.
+                                       Please <a href="%s">activate it</a>.',
+                                       'wordcamporg' ),
+                                       esc_url( $jetpack_modules_url )
+                               ); ?>
+                       </p>
+               </div>
+       <?php endif; ?>
+
+       <?php
+               if ( is_callable( '\WordCamp\Jetpack_Tweaks\notify_import_rules_stripped' ) ) {
+                       // This has to be called manually because process_options_page() is called after `admin_notices` fires
+                       \WordCamp\Jetpack_Tweaks\notify_import_rules_stripped();
+               }
+       ?>
+
+       <?php if ( $notice ) : ?>
+               <div id="message" class="notice <?php echo esc_attr( $notice_class ); ?> is-dismissible">
+                       <?php
+                           /*
+                            * Typically KSES is discouraged when displaying text because it's expensive, but in this case
+                            * it's appropriate because the underlying layers need to pass HTML-formatted error messages, and
+                            * this only only runs when the options are updated.
+                            */
+                       ?>
+
+                       <p><?php echo wp_kses( $notice, wp_kses_allowed_html( 'data' ) ); ?></p>
+               </div>
+       <?php endif; ?>
+
+       <p>
+               <?php _e( 'This tool allows you to develop your CSS in any environment that you choose, and with the tools that you prefer,
+               rather than with Jetpack\'s CSS Editor.
+               <button type="button" id="wcrcss-open-help-tab" class="button-link">Open the Help tab</button> for detailed instructions.',
+               'wordcamporg' ); ?>
+       </p>
+
+       <form action="" method="POST">
+               <?php wp_nonce_field( 'wcrcss-options-submit', 'wcrcss-options-nonce' ); ?>
+
+               <fieldset <?php disabled( $jetpack_custom_css_active, false ); ?>>
+                       <p>
+                               <label>
+                                       <?php _e( 'Remote CSS File:', 'wordcamporg' ); ?><br />
+                                       <input type="text" name="wcrcss-remote-css-url" class="large-text" value="<?php echo esc_url( $remote_css_url ); ?>" />
+                               </label>
+                       </p>
+
+                       <div>
+                               <?php _e( 'Output Mode:', 'wordcamporg' ); ?>
+
+                               <ul>
+                                       <li>
+                                               <label>
+                                                       <input type="radio" name="wcrcss-output-mode" value="add-on" <?php checked( $output_mode, 'add-on' ); ?> />
+                                                       <?php _e( "Add-on: The theme's stylesheet will remain, and your custom CSS will be added after it.", 'wordcamporg' ); ?>
+                                               </label>
+                                       </li>
+
+                                       <li>
+                                               <label>
+                                                       <input type="radio" name="wcrcss-output-mode" value="replace" <?php checked( $output_mode, 'replace' ); ?> />
+                                                       <?php _e( "Replace: The theme's stylesheet will be removed, so that only your custom CSS is present.", 'wordcamporg' ); ?>
+                                               </label>
+                                       </li>
+                               </ul>
+                       </div>
+
+                       <?php submit_button( __( 'Update', 'wordcamporg' ) ); ?>
+               </fieldset>
+       </form>
+</div>
+
+<script>
+       jQuery( '#wcrcss-open-help-tab' ).click( function() {
+               jQuery( '#contextual-help-link' ).click();
+       } );
+</script>
</ins></span></pre>
</div>
</div>

</body>
</html>