so-css
Last commit date
css
10 years ago
inc
11 years ago
js
10 years ago
lib
10 years ago
tpl
10 years ago
LICENSE
11 years ago
readme.txt
10 years ago
so-css.php
10 years ago
so-css.php
438 lines
| 1 | <?php |
| 2 | /* |
| 3 | Plugin Name: SiteOrigin CSS |
| 4 | Description: An advanced CSS editor from SiteOrigin. |
| 5 | Version: 1.0.6 |
| 6 | Author: SiteOrigin |
| 7 | Author URI: https://siteorigin.com |
| 8 | Plugin URI: https://siteorigin.com/css/ |
| 9 | License: GPL3 |
| 10 | License URI: https://www.gnu.org/licenses/gpl-3.0.txt |
| 11 | */ |
| 12 | |
| 13 | // Handle the legacy CSS editor that came with SiteOrigin themes |
| 14 | include plugin_dir_path(__FILE__) . '/inc/legacy.php'; |
| 15 | |
| 16 | define('SOCSS_VERSION', '1.0.6'); |
| 17 | define('SOCSS_JS_SUFFIX', '.min'); |
| 18 | |
| 19 | /** |
| 20 | * Class SiteOrigin_CSS The main class for the SiteOrigin CSS Editor |
| 21 | */ |
| 22 | class SiteOrigin_CSS { |
| 23 | private $theme; |
| 24 | private $snippet_paths; |
| 25 | |
| 26 | function __construct(){ |
| 27 | $this->theme = basename( get_template_directory() ); |
| 28 | $this->snippet_paths = array(); |
| 29 | |
| 30 | // Main header actions |
| 31 | add_action( 'wp_head', array($this, 'action_wp_head'), 20 ); |
| 32 | |
| 33 | // All the admin actions |
| 34 | add_action( 'admin_menu', array($this, 'action_admin_menu') ); |
| 35 | add_action( 'admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'), 20 ); |
| 36 | add_action( 'admin_enqueue_scripts', array($this, 'dequeue_admin_scripts'), 19 ); |
| 37 | add_action( 'load-appearance_page_so_custom_css', array($this, 'add_help_tab') ); |
| 38 | add_action( 'admin_footer', array($this, 'action_admin_footer') ); |
| 39 | |
| 40 | // The request to hide the getting started video |
| 41 | add_action( 'wp_ajax_socss_hide_getting_started', array( $this, 'admin_action_hide_getting_started' ) ); |
| 42 | |
| 43 | if( isset($_GET['so_css_preview']) && !is_admin() ) { |
| 44 | |
| 45 | add_action('plugins_loaded', array($this, 'disable_ngg_resource_manager')); |
| 46 | add_filter( 'show_admin_bar', '__return_false' ); |
| 47 | add_filter( 'wp_enqueue_scripts', array($this, 'enqueue_inspector_scripts') ); |
| 48 | add_filter( 'wp_footer', array($this, 'inspector_templates') ); |
| 49 | |
| 50 | // We'll be grabbing all the enqueued scripts and outputting them |
| 51 | add_action( 'wp_enqueue_scripts', array($this, 'inline_inspector_scripts'), 100 ); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | function disable_ngg_resource_manager() { |
| 56 | if( !current_user_can('edit_theme_options') ) return; |
| 57 | |
| 58 | //The NextGen Gallery plugin does some weird interfering with the output buffer. |
| 59 | define('NGG_DISABLE_RESOURCE_MANAGER', true); |
| 60 | } |
| 61 | |
| 62 | /** |
| 63 | * Get a singleton of the SiteOrigin CSS. |
| 64 | * |
| 65 | * @return SiteOrigin_CSS |
| 66 | */ |
| 67 | static function single(){ |
| 68 | static $single; |
| 69 | |
| 70 | if( empty($single) ) { |
| 71 | $single = new SiteOrigin_CSS(); |
| 72 | } |
| 73 | |
| 74 | return $single; |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * Display the custom CSS in the header. |
| 79 | */ |
| 80 | function action_wp_head(){ |
| 81 | $custom_css = get_option( 'siteorigin_custom_css[' . $this->theme . ']', '' ); |
| 82 | if ( empty( $custom_css ) ) return; |
| 83 | |
| 84 | // We just need to enqueue a dummy style |
| 85 | echo "<style id='" . sanitize_html_class($this->theme) . "-custom-css' class='siteorigin-custom-css' type='text/css'>\n"; |
| 86 | echo self::sanitize_css( $custom_css ) . "\n"; |
| 87 | echo "</style>\n"; |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Action to run on the admin action. |
| 92 | */ |
| 93 | function action_admin_menu(){ |
| 94 | add_theme_page( __( 'Custom CSS', 'so-css' ), __( 'Custom CSS', 'so-css' ), 'edit_theme_options', 'so_custom_css', array( $this, 'display_admin_page' ) ); |
| 95 | |
| 96 | if ( current_user_can('edit_theme_options') && isset( $_POST['siteorigin_custom_css_save'] ) ) { |
| 97 | check_admin_referer( 'custom_css', '_sononce' ); |
| 98 | $theme = basename( get_template_directory() ); |
| 99 | |
| 100 | // Sanitize CSS input. Should keep most tags, apart from script and style tags. |
| 101 | $custom_css = self::sanitize_css( filter_input(INPUT_POST, 'custom_css' ) ); |
| 102 | |
| 103 | $current = get_option('siteorigin_custom_css[' . $theme . ']'); |
| 104 | if( $current === false ) { |
| 105 | add_option( 'siteorigin_custom_css[' . $theme . ']', $custom_css , '', 'no' ); |
| 106 | } |
| 107 | else { |
| 108 | update_option( 'siteorigin_custom_css[' . $theme . ']', $custom_css ); |
| 109 | } |
| 110 | |
| 111 | // If this has changed, then add a revision. |
| 112 | if ( $current != $custom_css ) { |
| 113 | $revisions = get_option( 'siteorigin_custom_css_revisions[' . $theme . ']' ); |
| 114 | if ( empty( $revisions ) ) { |
| 115 | add_option( 'siteorigin_custom_css_revisions[' . $theme . ']', array(), '', 'no' ); |
| 116 | $revisions = array(); |
| 117 | } |
| 118 | $revisions[ time() ] = $custom_css; |
| 119 | |
| 120 | // Sort the revisions and cut off any old ones. |
| 121 | krsort($revisions); |
| 122 | $revisions = array_slice($revisions, 0, 15, true); |
| 123 | |
| 124 | update_option( 'siteorigin_custom_css_revisions[' . $theme . ']', $revisions ); |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Display the help tab |
| 131 | */ |
| 132 | function add_help_tab(){ |
| 133 | $screen = get_current_screen(); |
| 134 | $screen->add_help_tab( array( |
| 135 | 'id' => 'custom-css', |
| 136 | 'title' => __( 'Custom CSS', 'so-css' ), |
| 137 | 'content' => '<p>' |
| 138 | . sprintf( __( "SiteOrigin CSS adds any custom CSS you enter here into your site's header. ", 'so-css' ) ) |
| 139 | . __( "These changes will persist across updates so it's best to make all your changes here. ", 'so-css' ) |
| 140 | . '</p>' |
| 141 | ) ); |
| 142 | } |
| 143 | |
| 144 | function enqueue_admin_scripts( $page ){ |
| 145 | if( $page != 'appearance_page_so_custom_css' ) return; |
| 146 | |
| 147 | // Core WordPress stuff that we use |
| 148 | wp_enqueue_media(); |
| 149 | |
| 150 | // Enqueue the codemirror scripts. Call Underscore and Backbone dependencies so they're enqueued first to prevent conflicts. |
| 151 | wp_enqueue_script( 'codemirror', plugin_dir_url(__FILE__) . 'lib/codemirror/lib/codemirror' . SOCSS_JS_SUFFIX . '.js', array( 'underscore', 'backbone' ), '5.2.0' ); |
| 152 | wp_enqueue_script( 'codemirror-mode-css', plugin_dir_url(__FILE__) . 'lib/codemirror/mode/css/css' . SOCSS_JS_SUFFIX . '.js', array(), '5.2.0' ); |
| 153 | |
| 154 | if( !wp_script_is( 'wp-color-picker' ) ) { |
| 155 | // Add in all the linting libs |
| 156 | wp_enqueue_script( 'codemirror-lint', plugin_dir_url(__FILE__) . 'lib/codemirror/addon/lint/lint' . SOCSS_JS_SUFFIX . '.js', array( 'codemirror' ), '5.2.0' ); |
| 157 | wp_enqueue_script( 'codemirror-lint-css', plugin_dir_url(__FILE__) . 'lib/codemirror/addon/lint/css-lint' . SOCSS_JS_SUFFIX . '.js', array( 'codemirror', 'codemirror-lint-css-lib' ), '5.2.0' ); |
| 158 | wp_enqueue_script( 'codemirror-lint-css-lib', plugin_dir_url(__FILE__) . 'js/csslint' . SOCSS_JS_SUFFIX . '.js', array(), '0.10.0' ); |
| 159 | } |
| 160 | |
| 161 | // The CodeMirror autocomplete library |
| 162 | wp_enqueue_script( 'codemirror-show-hint', plugin_dir_url(__FILE__) . 'lib/codemirror/addon/hint/show-hint' . SOCSS_JS_SUFFIX . '.js', array( 'codemirror' ), '5.2.0' ); |
| 163 | |
| 164 | // All the CodeMirror styles |
| 165 | wp_enqueue_style( 'codemirror', plugin_dir_url(__FILE__) . 'lib/codemirror/lib/codemirror.css', array(), '5.2.0' ); |
| 166 | wp_enqueue_style( 'codemirror-theme-neat', plugin_dir_url(__FILE__) . 'lib/codemirror/theme/neat.css', array(), '5.2.0' ); |
| 167 | wp_enqueue_style( 'codemirror-lint-css', plugin_dir_url(__FILE__) . 'lib/codemirror/addon/lint/lint.css', array(), '5.2.0' ); |
| 168 | wp_enqueue_style( 'codemirror-show-hint', plugin_dir_url(__FILE__) . 'lib/codemirror/addon/hint/show-hint.css', array( ), '5.2.0' ); |
| 169 | |
| 170 | // Enqueue the scripts for theme CSS processing |
| 171 | wp_enqueue_script( 'siteorigin-custom-css-parser', plugin_dir_url(__FILE__) . 'js/css' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), SOCSS_VERSION ); |
| 172 | |
| 173 | // There are conflicts between CSS linting and the built in WordPress color picker, so use something else |
| 174 | wp_enqueue_style('siteorigin-custom-css-minicolors', plugin_dir_url(__FILE__) . 'lib/minicolors/jquery.minicolors.css', array(), '2.1.7' ); |
| 175 | wp_enqueue_script('siteorigin-custom-css-minicolors', plugin_dir_url(__FILE__) . 'lib/minicolors/jquery.minicolors' . SOCSS_JS_SUFFIX . '.js', array('jquery'), '2.1.7' ); |
| 176 | |
| 177 | // We need Font Awesome |
| 178 | wp_enqueue_style( 'siteorigin-custom-css-font-awesome', plugin_dir_url(__FILE__) . 'lib/fontawesome/css/font-awesome.min.css', array( ), SOCSS_VERSION ); |
| 179 | |
| 180 | // All the custom SiteOrigin CSS stuff |
| 181 | wp_enqueue_script( 'siteorigin-custom-css', plugin_dir_url(__FILE__) . 'js/editor' . SOCSS_JS_SUFFIX . '.js', array( 'jquery', 'underscore', 'backbone', 'siteorigin-custom-css-parser', 'codemirror' ), SOCSS_VERSION, true ); |
| 182 | wp_enqueue_style( 'siteorigin-custom-css', plugin_dir_url(__FILE__) . 'css/admin.css', array( ), SOCSS_VERSION ); |
| 183 | |
| 184 | wp_localize_script( 'siteorigin-custom-css', 'socssOptions', array( |
| 185 | 'themeCSS' => SiteOrigin_CSS::single()->get_theme_css(), |
| 186 | 'homeURL' => add_query_arg( 'so_css_preview', '1', site_url() ), |
| 187 | 'snippets' => $this->get_snippets(), |
| 188 | |
| 189 | 'propertyControllers' => apply_filters( 'siteorigin_css_property_controllers', $this->get_property_controllers() ), |
| 190 | |
| 191 | 'loc' => array( |
| 192 | 'unchanged' => __('Unchanged', 'so-css'), |
| 193 | 'select' => __('Select', 'so-css'), |
| 194 | 'select_image' => __('Select Image', 'so-css'), |
| 195 | 'leave' => __('Are you sure you want to leave without saving?', 'so-css'), |
| 196 | ) |
| 197 | ) ); |
| 198 | |
| 199 | // This is for the templates required by the CSS editor |
| 200 | add_action( 'admin_footer', array($this, 'action_admin_footer') ); |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * @param $page |
| 205 | */ |
| 206 | function dequeue_admin_scripts( $page ) { |
| 207 | if( $page != 'appearance_page_so_custom_css' ) return; |
| 208 | |
| 209 | // Dequeue the core WordPress color picker on the custom CSS page. |
| 210 | // This script causes conflicts and other plugins seem to be enqueueing it on the SO CSS admin page. |
| 211 | wp_dequeue_script('wp-color-picker'); |
| 212 | wp_dequeue_style('wp-color-picker'); |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Get all the available property controllers |
| 217 | */ |
| 218 | function get_property_controllers() { |
| 219 | return include plugin_dir_path(__FILE__) . 'inc/controller-config.php'; |
| 220 | } |
| 221 | |
| 222 | /** |
| 223 | * Display the templates for the CSS Editor |
| 224 | */ |
| 225 | function action_admin_footer(){ |
| 226 | include plugin_dir_path( __FILE__ ) . 'tpl/js-templates.php'; |
| 227 | } |
| 228 | |
| 229 | function display_admin_page(){ |
| 230 | $theme = basename( get_template_directory() ); |
| 231 | |
| 232 | $custom_css = get_option( 'siteorigin_custom_css[' . $theme . ']', '' ); |
| 233 | $custom_css_revisions = get_option('siteorigin_custom_css_revisions[' . $theme . ']'); |
| 234 | |
| 235 | if(!empty($_GET['theme']) && $_GET['theme'] == $theme && !empty($_GET['time']) && !empty($custom_css_revisions[$_GET['time']])) { |
| 236 | $custom_css = $custom_css_revisions[$_GET['time']]; |
| 237 | $revision = true; |
| 238 | } |
| 239 | |
| 240 | include plugin_dir_path(__FILE__).'/tpl/page.php'; |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * |
| 245 | */ |
| 246 | function admin_action_hide_getting_started(){ |
| 247 | if( !isset($_GET['_wpnonce']) || !wp_verify_nonce( $_GET['_wpnonce'], 'hide' ) ) return; |
| 248 | |
| 249 | $user = wp_get_current_user(); |
| 250 | if( !empty($user) ) { |
| 251 | update_user_meta( $user->ID, 'socss_hide_gs', true ); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /** |
| 256 | * Add one or more paths to the registered snippet paths |
| 257 | * |
| 258 | * @param string|array $path |
| 259 | */ |
| 260 | function register_snippet_path( $path ){ |
| 261 | $this->snippet_paths = array_merge( $this->snippet_paths, (array) $path ); |
| 262 | } |
| 263 | |
| 264 | /** |
| 265 | * Get all the available snippets |
| 266 | * |
| 267 | * @return array|bool |
| 268 | */ |
| 269 | function get_snippets(){ |
| 270 | // Get the snippet paths |
| 271 | $snippet_paths = apply_filters( 'siteorigin_css_snippet_paths', $this->snippet_paths ); |
| 272 | if( empty($snippet_paths) ) return array(); |
| 273 | |
| 274 | static $snippets = array(); |
| 275 | if( !empty($snippets) ) return $snippets; |
| 276 | |
| 277 | if( !WP_Filesystem() ) return false; |
| 278 | global $wp_filesystem; |
| 279 | foreach( $snippet_paths as $path ) { |
| 280 | foreach( glob($path . '/*.css') as $css_file ) { |
| 281 | $data = get_file_data( $css_file, array( |
| 282 | 'Name' => 'Name', |
| 283 | 'Description' => 'Description', |
| 284 | ) ); |
| 285 | |
| 286 | // Get the CSS and strip out the first header |
| 287 | $css = $wp_filesystem->get_contents( $css_file ); |
| 288 | $css = preg_replace('!/\*.*?\*/!s', '', $css, 1); |
| 289 | |
| 290 | $snippets[] = wp_parse_args( $data, array( |
| 291 | 'css' => str_replace( "\t", ' ', trim($css) ), |
| 292 | ) ); |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | usort($snippets, array( $this, 'sort_snippet_callback' ) ); |
| 297 | return $snippets; |
| 298 | } |
| 299 | |
| 300 | /** |
| 301 | * Sort snippets by name. |
| 302 | * |
| 303 | * @param $a |
| 304 | * @param $b |
| 305 | * |
| 306 | * @return int |
| 307 | */ |
| 308 | static function sort_snippet_callback( $a, $b ){ |
| 309 | return $a['Name'] > $b['Name'] ? 1 : -1; |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * A very simple CSS sanitization function. |
| 314 | * |
| 315 | * @param $css |
| 316 | * |
| 317 | * @return string |
| 318 | */ |
| 319 | static function sanitize_css( $css ){ |
| 320 | return trim( strip_tags( $css ) ); |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * Get all the available theme CSS |
| 325 | */ |
| 326 | function get_theme_css(){ |
| 327 | $css = ''; |
| 328 | if( file_exists( get_template_directory() . '/style.css' ) ) { |
| 329 | $css .= file_get_contents( get_template_directory() . '/style.css' ); |
| 330 | } |
| 331 | |
| 332 | if( is_child_theme() ) { |
| 333 | $css .= file_get_contents( get_stylesheet_directory() . '/style.css' ); |
| 334 | } |
| 335 | |
| 336 | // Remove all CSS comments |
| 337 | $regex = array( |
| 338 | "`^([\t\s]+)`ism"=>'', |
| 339 | "`^\/\*(.+?)\*\/`ism"=>"", |
| 340 | "`([\n\A;]+)\/\*(.+?)\*\/`ism"=>"$1", |
| 341 | "`([\n\A;\s]+)//(.+?)[\n\r]`ism"=>"$1\n", |
| 342 | "`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism"=>"\n" |
| 343 | ); |
| 344 | $css = preg_replace( array_keys($regex), $regex, $css ); |
| 345 | $css = preg_replace('/\s+/', ' ', $css); |
| 346 | |
| 347 | return $css; |
| 348 | } |
| 349 | |
| 350 | /** |
| 351 | * Get the editor description |
| 352 | * |
| 353 | * @return string |
| 354 | */ |
| 355 | static function editor_description(){ |
| 356 | $theme = wp_get_theme(); |
| 357 | return sprintf( __( 'Changes apply to %s and its child themes', 'so-css' ), $theme->get('Name') ); |
| 358 | } |
| 359 | |
| 360 | function enqueue_inspector_scripts(){ |
| 361 | if( !current_user_can('edit_theme_options') ) return; |
| 362 | |
| 363 | wp_enqueue_style( 'dashicons' ); |
| 364 | |
| 365 | wp_enqueue_script( 'siteorigin-custom-css-parser', plugin_dir_url(__FILE__) . 'js/css' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), SOCSS_VERSION ); |
| 366 | |
| 367 | wp_enqueue_script('siteorigin-css-sizes', plugin_dir_url(__FILE__) . 'js/jquery.sizes' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), '0.33' ); |
| 368 | wp_enqueue_script('siteorigin-css-specificity', plugin_dir_url(__FILE__) . 'js/specificity' . SOCSS_JS_SUFFIX . '.js', array( ) ); |
| 369 | wp_enqueue_script('siteorigin-css-inspector', plugin_dir_url(__FILE__) . 'js/inspector' . SOCSS_JS_SUFFIX . '.js', array( 'jquery', 'underscore', 'backbone' ), SOCSS_VERSION, true ); |
| 370 | wp_enqueue_style('siteorigin-css-inspector', plugin_dir_url(__FILE__) . 'css/inspector.css', array( ), SOCSS_VERSION ); |
| 371 | |
| 372 | wp_localize_script('siteorigin-css-inspector', 'socssOptions', array( |
| 373 | |
| 374 | ) ); |
| 375 | } |
| 376 | |
| 377 | function inspector_templates(){ |
| 378 | if( !current_user_can('edit_theme_options') ) return; |
| 379 | |
| 380 | include plugin_dir_path( __FILE__ ) . 'tpl/inspector-templates.php'; |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Change the stylesheets to all be inline |
| 385 | */ |
| 386 | function inline_inspector_scripts(){ |
| 387 | if( !current_user_can('edit_theme_options') ) return; |
| 388 | |
| 389 | $regex = array( |
| 390 | "`^([\t\s]+)`ism"=>'', |
| 391 | "`^\/\*(.+?)\*\/`ism"=>"", |
| 392 | "`([\n\A;]+)\/\*(.+?)\*\/`ism"=>"$1", |
| 393 | "`([\n\A;\s]+)//(.+?)[\n\r]`ism"=>"$1\n", |
| 394 | "`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism"=>"\n" |
| 395 | ); |
| 396 | |
| 397 | global $wp_styles; |
| 398 | if( empty($wp_styles->queue) ) return; |
| 399 | |
| 400 | // Make each of the scripts inline |
| 401 | foreach( $wp_styles->queue as $handle ) { |
| 402 | if( $handle === 'siteorigin-css-inspector' || $handle === 'dashicons' ) continue; |
| 403 | $style = $wp_styles->registered[$handle]; |
| 404 | if( empty($style->src) || substr($style->src, 0, 4) !== 'http' ) continue; |
| 405 | $response = wp_remote_get( $style->src ); |
| 406 | if( is_wp_error($response) || $response['response']['code'] !== 200 || empty($response['body']) ) continue; |
| 407 | |
| 408 | $css = $response['body']; |
| 409 | $css = preg_replace( array_keys($regex), $regex, $css ); |
| 410 | |
| 411 | ?> |
| 412 | <script type="text/css" class="socss-theme-styles" id="socss-inlined-style-<?php echo sanitize_html_class( $handle ) ?>"> |
| 413 | <?php echo strip_tags( $css ) ?> |
| 414 | </script> |
| 415 | <?php |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | /** |
| 420 | * Get a URL to tweet out the changes |
| 421 | */ |
| 422 | function get_tweet_url(){ |
| 423 | $tweet = __('I changed my site design using @SiteOrigin CSS (http://siteorigin.com/css/). What do you think?', 'so-css'); |
| 424 | $tweet .= ' '; |
| 425 | $tweet .= get_site_url(); |
| 426 | |
| 427 | return add_query_arg( |
| 428 | 'text', |
| 429 | urlencode($tweet), |
| 430 | 'https://twitter.com/intent/tweet' |
| 431 | ); |
| 432 | |
| 433 | |
| 434 | } |
| 435 | } |
| 436 | |
| 437 | // Initialize the single |
| 438 | SiteOrigin_CSS::single(); |