PluginProbe ʕ •ᴥ•ʔ
SiteOrigin CSS / 1.5.6
SiteOrigin CSS v1.5.6
1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 trunk 1.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.2.0
so-css / so-css.php
so-css Last commit date
css 4 years ago inc 3 years ago js 4 years ago lang 3 years ago lib 4 years ago tpl 3 years ago LICENSE 11 years ago readme.txt 3 years ago so-css.php 3 years ago
so-css.php
938 lines
1 <?php
2 /*
3 Plugin Name: SiteOrigin CSS
4 Description: An advanced CSS editor from SiteOrigin.
5 Version: 1.5.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 Text Domain: so-css
12 */
13
14 // Handle the legacy CSS editor that came with SiteOrigin themes
15 include plugin_dir_path( __FILE__ ) . 'inc/legacy.php';
16
17 define( 'SOCSS_VERSION', '1.5.6' );
18 define( 'SOCSS_JS_SUFFIX', '.min' );
19
20 /**
21 * Class SiteOrigin_CSS The main class for the SiteOrigin CSS Editor
22 */
23 class SiteOrigin_CSS {
24
25 private $theme;
26 private $snippet_paths;
27 private $css_file;
28
29 public function __construct() {
30 $this->theme = basename( get_template_directory() );
31 $this->snippet_paths = array();
32
33 // Main header actions
34 add_action( 'plugins_loaded', array( $this, 'set_plugin_textdomain' ) );
35
36 global $wp_filesystem;
37
38 if ( ! class_exists( 'wp_filesystem' ) ) {
39 require_once ABSPATH . '/wp-admin/includes/file.php';
40 WP_Filesystem();
41 }
42
43 // Priority 20 is necessary to ensure our CSS takes precedence.
44 add_action( 'wp_head', array( $this, 'enqueue_css' ), 20 );
45
46 // All the admin actions
47 add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
48 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ), 20 );
49 add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_admin_scripts' ), 19 );
50 add_action( 'load-appearance_page_so_custom_css', array( $this, 'add_help_tab' ) );
51
52 // Add the action links.
53 add_action( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'plugin_action_links' ) );
54
55 // The request to hide the getting started video
56 add_action( 'wp_ajax_socss_hide_getting_started', array( $this, 'admin_action_hide_getting_started' ) );
57
58 add_action( 'wp_ajax_socss_get_post_css', array( $this, 'admin_action_get_post_css' ) );
59 add_action( 'wp_ajax_socss_get_revisions_list', array( $this, 'admin_action_get_revisions_list' ) );
60 add_action( 'wp_ajax_socss_save_css', array( $this, 'admin_action_save_css' ) );
61
62 if ( ! is_admin() ) {
63 if ( isset( $_GET['so_css_preview'] ) ) {
64 add_action( 'plugins_loaded', array( $this, 'disable_ngg_resource_manager' ) );
65 add_filter( 'show_admin_bar', '__return_false' );
66 add_filter( 'wp_enqueue_scripts', array( $this, 'enqueue_inspector_scripts' ) );
67 add_filter( 'wp_footer', array( $this, 'inspector_templates' ) );
68
69 // We'll be grabbing all the enqueued scripts and outputting them
70 add_action( 'wp_enqueue_scripts', array( $this, 'inline_inspector_scripts' ), 100 );
71 }
72 }
73 }
74
75 /**
76 * Get a singleton of the SiteOrigin CSS.
77 *
78 * @return SiteOrigin_CSS
79 */
80 public static function single() {
81 static $single;
82
83 if ( empty( $single ) ) {
84 $single = new SiteOrigin_CSS();
85 }
86
87 return $single;
88 }
89
90 /**
91 * Retrieve the current custom CSS for a given theme and post id combination.
92 *
93 * @param $theme string The name of the theme for which to retrieve custom CSS.
94 * @param $post_id int The ID of the specific post for which to retrieve custom CSS.
95 *
96 * @return string The custom CSS for the theme and post ID combination.
97 */
98 public function get_custom_css( $theme, $post_id = null ) {
99 $css_key = 'siteorigin_custom_css[' . $theme . ']';
100
101 if ( empty( $post_id ) && WP_Filesystem() ) {
102 $custom_css_file = apply_filters( 'siteorigin_custom_css_file', false );
103
104 if (
105 ! empty( $custom_css_file ) &&
106 ! empty( $custom_css_file['file'] )
107 ) {
108 // Did we previously load the CSS file? If not, load it.
109 if ( empty( $this->css_file ) || isset( $_POST['siteorigin_custom_css'] ) ) {
110 global $wp_filesystem;
111
112 // If custom file doesn't exist, create it.
113 if ( ! $wp_filesystem->exists( $custom_css_file['file'] ) ) {
114 $wp_filesystem->touch( $custom_css_file['file'] );
115 }
116
117 if ( empty( get_option( 'siteorigin_custom_file' ) ) ) {
118 update_option( 'siteorigin_custom_file', true, true );
119 }
120
121 if ( $wp_filesystem->is_writable( $custom_css_file['file'] ) ) {
122 $this->css_file = $wp_filesystem->get_contents( $custom_css_file['file'] );
123 }
124 }
125
126 return $this->css_file;
127 } elseif ( ! empty( get_option( 'siteorigin_custom_file' ) ) ) {
128 // If the custom file filter was previously active we need to
129 // generate the global CSS file to avoid no CSS outputting
130 // without modification.
131 delete_option( 'siteorigin_custom_file', true );
132 $css_file_path = $this->get_css_file_name( $theme );
133
134 global $wp_filesystem;
135 $wp_filesystem->put_contents(
136 $css_file_path,
137 get_option( $css_key, '' )
138 );
139 }
140 }
141
142 if ( ! empty( $post_id ) ) {
143 return get_post_meta( $post_id, $css_key, true );
144 }
145
146 return get_option( $css_key, '' );
147 }
148
149 /**
150 * Save custom CSS for a given theme and post id combination.
151 *
152 * @param $custom_css string The custom CSS to save.
153 * @param $theme string The name of the theme for which to save custom CSS.
154 * @param $post_id int The ID of the specific post for which to save custom CSS.
155 *
156 * @return bool Whether or not saving the custom CSS was successful.
157 */
158 public function save_custom_css( $custom_css, $theme, $post_id = null ) {
159 $css_key = 'siteorigin_custom_css[' . $theme . ']';
160
161 if ( empty( $post_id ) ) {
162 $current = get_option( $css_key );
163
164 if ( $current === false ) {
165 return add_option( $css_key, $custom_css, '', 'no' );
166 } else {
167 return update_option( $css_key, $custom_css );
168 }
169 }
170
171 if ( metadata_exists( 'post', $post_id, $css_key ) ) {
172 return update_post_meta( $post_id, $css_key, $custom_css );
173 }
174
175 return add_post_meta( $post_id, $css_key, $custom_css );
176 }
177
178 /**
179 * Returns the file name of the CSS file we're editing.
180 *
181 * @param null $post_id
182 */
183 public function get_css_file_name( $theme, $post_id = null ) {
184 global $wp_filesystem;
185 $upload_dir = wp_upload_dir();
186 $upload_dir_path = $upload_dir['basedir'] . '/so-css/';
187
188 if ( ! $wp_filesystem->is_dir( $upload_dir_path ) ) {
189 $wp_filesystem->mkdir( $upload_dir_path );
190 }
191
192 $css_file_name = 'so-css-' . $theme . ( ! empty( $post_id ) ? '_' . $post_id : '' );
193
194 return $upload_dir_path . $css_file_name . '.css';
195 }
196
197 /**
198 * Save custom CSS for a given theme and post id combination to a file in the uploads directory to allow for caching.
199 *
200 * @param null $post_id
201 */
202 public function save_custom_css_file( $custom_css, $theme, $post_id = null ) {
203 if ( WP_Filesystem() ) {
204 global $wp_filesystem;
205 $css_file_path = apply_filters( 'siteorigin_custom_css_file', false );
206
207 if (
208 empty( $css_file_path ) ||
209 empty( $css_file_path['file'] ) ||
210 ! $wp_filesystem->is_writable( $css_file_path['file'] )
211 ) {
212 $css_file_path = $this->get_css_file_name( $theme, $post_id );
213
214 if ( file_exists( $css_file_path ) ) {
215 $wp_filesystem->delete( $css_file_path );
216 }
217 } else {
218 $css_file_path = $css_file_path['file'];
219 $this->css_file = $custom_css;
220 }
221
222 $wp_filesystem->put_contents(
223 $css_file_path,
224 $custom_css
225 );
226 }
227 }
228
229 /**
230 * Retrieve the previous revisions of custom CSS for a given theme and post id combination.
231 *
232 * @param $theme string The name of the theme for which to retrieve custom CSS revisions.
233 * @param $post_id int The ID of the specific post for which to retrieve custom CSS revisions.
234 *
235 * @return array The custom CSS revisions for the theme and post ID combination.
236 */
237 public function get_custom_css_revisions( $theme, $post_id = null ) {
238 $css_key = 'siteorigin_custom_css_revisions[' . $theme . ']';
239
240 if ( empty( $post_id ) ) {
241 return get_option( $css_key, '' );
242 }
243
244 return get_post_meta( $post_id, $css_key, true );
245 }
246
247 /**
248 * Adds a custom CSS revision for a given theme and post id combination.
249 *
250 * @param $custom_css string The custom CSS to add as a revision.
251 * @param $theme string The name of the theme for which to save custom CSS.
252 * @param $post_id int The ID of the specific post for which to save custom CSS.
253 *
254 * @return bool Whether or not adding the custom CSS revision was successful.
255 */
256 public function add_custom_css_revision( $custom_css, $theme, $post_id = null ) {
257 $revisions = $this->get_custom_css_revisions( $this->theme, $post_id );
258
259 $css_key = 'siteorigin_custom_css_revisions[' . $theme . ']';
260
261 if ( empty( $revisions ) ) {
262 $revisions = array();
263
264 if ( empty( $post_id ) ) {
265 add_option( $css_key, $revisions, '', 'no' );
266 } else {
267 add_post_meta( $post_id, $css_key, $revisions );
268 }
269 }
270 $revisions[ time() ] = $custom_css;
271
272 // Sort the revisions and cut off any old ones.
273 krsort( $revisions );
274 $revisions = array_slice( $revisions, 0, 15, true );
275
276 if ( empty( $post_id ) ) {
277 return update_option( $css_key, $revisions );
278 }
279
280 return update_post_meta( $post_id, $css_key, $revisions );
281 }
282
283 /**
284 * Enqueue or print inline CSS.
285 */
286 public function enqueue_css() {
287 $this->enqueue_custom_css( $this->theme );
288
289 if ( is_singular() ) {
290 $this->enqueue_custom_css( $this->theme, get_the_ID() );
291 }
292 }
293
294 /**
295 * Enqueue the custom CSS for the given theme and post id combination.
296 *
297 * @param $theme string The name of the theme for which to enqueue custom CSS.
298 * @param $post_id int The ID of the specific post for which to enqueue custom CSS.
299 */
300 public function enqueue_custom_css( $theme, $post_id = null ) {
301 $css_id = $theme . ( ! empty( $post_id ) ? '_' . $post_id : '' );
302
303 if (
304 empty( $_GET['so_css_preview'] ) &&
305 ! is_admin() &&
306 apply_filters( 'siteorigin_css_enqueue_css', true )
307 ) {
308 $custom_css_file = apply_filters( 'siteorigin_custom_css_file', array() );
309
310 if ( ! empty( $post_id ) || empty( $custom_css_file ) ) {
311 $upload_dir = wp_upload_dir();
312 $upload_dir_path = $upload_dir['basedir'] . '/so-css/';
313 $css_file_name = 'so-css-' . $css_id;
314 $css_file_path = $upload_dir_path . $css_file_name . '.css';
315 $css_file_url = $upload_dir['baseurl'] . '/so-css/' . $css_file_name . '.css';
316 } elseif ( isset( $custom_css_file['url'] ) ) {
317 $css_file_path = $custom_css_file['file'];
318 $css_file_url = $custom_css_file['url'];
319 }
320
321 if ( ! empty( $css_file_path ) && file_exists( $css_file_path ) ) {
322 wp_enqueue_style(
323 'so-css-' . $css_id,
324 set_url_scheme( $css_file_url ),
325 array(),
326 $this->get_latest_revision_timestamp()
327 );
328 }
329 } else {
330 $custom_css = $this->get_custom_css( $theme, $post_id );
331 // We just need to enqueue a dummy style
332 if ( ! empty( $custom_css ) ) {
333 echo "<style id='" . sanitize_html_class( $css_id ) . "-custom-css' class='siteorigin-custom-css' type='text/css'>\n";
334 echo self::sanitize_css( $custom_css ) . "\n";
335 echo "</style>\n";
336 }
337 }
338 }
339
340 public function set_plugin_textdomain() {
341 load_plugin_textdomain( 'so-css', false, plugin_dir_path( __FILE__ ) . 'lang/' );
342 }
343
344 /**
345 * Action to run on the admin action.
346 */
347 public function action_admin_menu() {
348 add_theme_page( esc_html__( 'Custom CSS', 'so-css' ), esc_html__( 'Custom CSS', 'so-css' ), 'edit_theme_options', 'so_custom_css', array(
349 $this,
350 'display_admin_page',
351 ) );
352
353 if ( current_user_can( 'edit_theme_options' ) && isset( $_POST['siteorigin_custom_css'] ) ) {
354 check_admin_referer( 'custom_css', '_sononce' );
355
356 // Sanitize CSS input. Should keep most tags, apart from script and style tags.
357 $custom_css = self::sanitize_css( filter_input( INPUT_POST, 'siteorigin_custom_css' ) );
358 $socss_post_id = filter_input( INPUT_GET, 'socss_post_id', FILTER_VALIDATE_INT );
359
360 if ( empty( $this->css_file ) ) {
361 $current = $this->get_custom_css( $this->theme, $socss_post_id );
362 $this->save_custom_css( $custom_css, $this->theme, $socss_post_id );
363 } else {
364 $current = $this->css_file;
365 }
366
367 // If this has changed, then add a revision.
368 if ( $current != $custom_css ) {
369 $this->add_custom_css_revision( $custom_css, $this->theme, $socss_post_id );
370
371 $this->save_custom_css_file( $custom_css, $this->theme, $socss_post_id );
372 }
373
374 // Update Editor Theme.
375 if (
376 $_POST['so_css_editor_theme'] == 'neat' ||
377 $_POST['so_css_editor_theme'] == 'ambiance'
378 ) {
379 update_option( 'so_css_editor_theme', $_POST['so_css_editor_theme'] );
380 }
381 }
382 }
383
384 /**
385 * Display the help tab
386 */
387 public function add_help_tab() {
388 $screen = get_current_screen();
389 $screen->add_help_tab( array(
390 'id' => 'custom-css',
391 'title' => esc_html__( 'Custom CSS', 'so-css' ),
392 'content' => '<p>'
393 . sprintf( esc_html__( "SiteOrigin CSS adds any custom CSS you enter here into your site's header. ", 'so-css' ) )
394 . esc_html__( "These changes will persist across updates so it's best to make all your changes here. ", 'so-css' )
395 . '</p>',
396 ) );
397 }
398
399 public function enqueue_admin_scripts( $page ) {
400 if ( $page != 'appearance_page_so_custom_css' ) {
401 return;
402 }
403
404 // Core WordPress stuff that we use
405 wp_enqueue_media();
406
407 global $wp_version;
408
409 if ( version_compare( $wp_version, '4.9', '>=' ) && wp_get_current_user()->syntax_highlighting ) {
410 wp_enqueue_code_editor(
411 array(
412 'type' => 'css',
413 'codemirror' => array(
414 'lint' => true,
415 ),
416 )
417 );
418 } else {
419 $this->enqueue_fallback_codemirror();
420 }
421 wp_enqueue_style( 'socss-codemirror-theme-neat', plugin_dir_url( __FILE__ ) . 'lib/codemirror/theme/neat.css', array(), SOCSS_VERSION );
422 wp_enqueue_style( 'socss-codemirror-theme-ambiance', plugin_dir_url( __FILE__ ) . 'lib/codemirror/theme/ambiance.css', array(), SOCSS_VERSION );
423
424 // Enqueue the scripts for theme CSS processing
425 wp_enqueue_script( 'siteorigin-css-parser-lib', plugin_dir_url( __FILE__ ) . 'js/css' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), SOCSS_VERSION );
426
427 // There are conflicts between CSS linting and the built in WordPress color picker, so use something else
428 wp_enqueue_style( 'siteorigin-custom-css-minicolors', plugin_dir_url( __FILE__ ) . 'lib/minicolors/jquery.minicolors.css', array(), '2.1.7' );
429 wp_enqueue_script( 'siteorigin-custom-css-minicolors', plugin_dir_url( __FILE__ ) . 'lib/minicolors/jquery.minicolors' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), '2.1.7' );
430
431 // URI parsing for preview navigation
432 wp_enqueue_script( 'siteorigin-uri', plugin_dir_url( __FILE__ ) . 'js/URI' . SOCSS_JS_SUFFIX . '.js', array(), SOCSS_VERSION, true );
433
434 // All the custom SiteOrigin CSS stuff
435 wp_enqueue_script( 'siteorigin-custom-css', plugin_dir_url( __FILE__ ) . 'js/editor' . SOCSS_JS_SUFFIX . '.js', array( 'jquery', 'underscore', 'backbone' ), SOCSS_VERSION, true );
436 wp_enqueue_style( 'siteorigin-custom-css', plugin_dir_url( __FILE__ ) . 'css/admin.css', array( ), SOCSS_VERSION );
437
438 // Pretty confusing, but it seems we should be using `home_url` and NOT `site_url`
439 // as described here => https://wordpress.stackexchange.com/a/50605
440 $init_url = home_url();
441
442 if ( ! empty( $socss_post_id ) && is_int( $socss_post_id ) ) {
443 $init_url = set_url_scheme( get_permalink( $socss_post_id ) );
444 }
445
446 $open_visual_editor = ! empty( $_REQUEST['open_visual_editor'] );
447
448 $home_url = add_query_arg( 'so_css_preview', '1', $init_url );
449
450 wp_localize_script( 'siteorigin-custom-css', 'socssOptions', array(
451 'homeURL' => $home_url,
452 'postCssUrlRoot' => wp_nonce_url( admin_url( 'admin-ajax.php?action=socss_get_post_css' ), 'get_post_css' ),
453 'getRevisionsListAjaxUrl' => wp_nonce_url( admin_url( 'admin-ajax.php?action=socss_get_revisions_list' ), 'get_revisions_list' ),
454 'ajaxurl' => wp_nonce_url( admin_url( 'admin-ajax.php' ), 'so-css-ajax' ),
455 'openVisualEditor' => $open_visual_editor,
456
457 'propertyControllers' => apply_filters( 'siteorigin_css_property_controllers', $this->get_property_controllers() ),
458
459 'loc' => array(
460 'unchanged' => esc_html__( 'Unchanged', 'so-css' ),
461 'select' => esc_html__( 'Select', 'so-css' ),
462 'select_image' => esc_html__( 'Select Image', 'so-css' ),
463 'leave' => esc_html__( 'Are you sure you want to leave without saving?', 'so-css' ),
464 ),
465 ) );
466
467 // This is for the templates required by the CSS editor. Ideally this would be out in the footer, but we need
468 // it earlier for dependent scripts.
469 include plugin_dir_path( __FILE__ ) . 'tpl/js-templates.php';
470 }
471
472 // Handles loading the fallback version of CodeMirror.
473 public function enqueue_fallback_codemirror() {
474 // Enqueue the codemirror scripts. Call Underscore and Backbone dependencies so they're enqueued first to prevent conflicts.
475 wp_enqueue_script( 'socss-codemirror', plugin_dir_url( __FILE__ ) . 'lib/codemirror/lib/codemirror' . SOCSS_JS_SUFFIX . '.js', array( 'underscore', 'backbone' ), '5.2.0' );
476 wp_enqueue_script( 'socss-codemirror-mode-css', plugin_dir_url( __FILE__ ) . 'lib/codemirror/mode/css/css' . SOCSS_JS_SUFFIX . '.js', array(), '5.2.0' );
477
478 // Add in all the linting libs
479 wp_enqueue_script( 'socss-codemirror-lint', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/lint/lint' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror' ), '5.2.0' );
480 wp_enqueue_script( 'socss-codemirror-lint-css', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/lint/css-lint' . SOCSS_JS_SUFFIX . '.js', array(
481 'socss-codemirror',
482 'socss-codemirror-lint-css-lib',
483 ), '5.2.0' );
484 wp_enqueue_script( 'socss-codemirror-lint-css-lib', plugin_dir_url( __FILE__ ) . 'js/csslint' . SOCSS_JS_SUFFIX . '.js', array(), '0.10.0' );
485
486 // The CodeMirror autocomplete library
487 wp_enqueue_script( 'socss-codemirror-show-hint', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/hint/show-hint' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror' ), '5.2.0' );
488
489 // CodeMirror search and dialog addons
490 wp_enqueue_script( 'socss-codemirror-dialog', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/dialog/dialog' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror' ), '5.2.0' );
491
492 wp_enqueue_script( 'socss-codemirror-search', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/search' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror' ), '5.37.0' );
493 wp_enqueue_script( 'socss-codemirror-search-searchcursor', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/searchcursor' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror', 'socss-codemirror-search' ), '5.37.0' );
494 wp_enqueue_script( 'socss-codemirror-search-match-cursor', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/match-highlighter' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror', 'socss-codemirror-search' ), '5.37.0' );
495 wp_enqueue_script( 'socss-codemirror-search-matchesonscrollbar', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/matchesonscrollbar' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror', 'socss-codemirror-search' ), '5.37.0' );
496 wp_enqueue_script( 'socss-codemirror-scroll-annotatescrollbar', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/scroll/annotatescrollbar' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror', 'socss-codemirror-search', 'socss-codemirror-search-matchesonscrollbar' ), '5.37.0' );
497 wp_enqueue_script( 'socss-codemirror-jump-to-line', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/jump-to-line' . SOCSS_JS_SUFFIX . '.js', array( 'socss-codemirror', 'socss-codemirror-search' ), '5.37.0' );
498
499 // All the CodeMirror styles
500 wp_enqueue_style( 'socss-codemirror', plugin_dir_url( __FILE__ ) . 'lib/codemirror/lib/codemirror.css', array(), '5.2.0' );
501 wp_enqueue_style( 'socss-codemirror-lint-css', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/lint/lint.css', array(), '5.2.0' );
502 wp_enqueue_style( 'socss-codemirror-show-hint', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/hint/show-hint.css', array(), '5.2.0' );
503 wp_enqueue_style( 'socss-codemirror-dialog', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/dialog/dialog.css', '5.2.0' );
504 wp_enqueue_style( 'socss-codemirror-search-matchesonscrollbar', plugin_dir_url( __FILE__ ) . 'lib/codemirror/addon/search/matchesonscrollbar.css', array(), '5.37.0' );
505 }
506
507 public function dequeue_admin_scripts( $page ) {
508 if ( $page != 'appearance_page_so_custom_css' ) {
509 return;
510 }
511
512 // Dequeue the core WordPress color picker on the custom CSS page.
513 // This script causes conflicts and other plugins seem to be enqueueing it on the SO CSS admin page.
514 wp_dequeue_script( 'wp-color-picker' );
515 wp_dequeue_style( 'wp-color-picker' );
516 }
517
518 /**
519 * Get all the available property controllers
520 */
521 public function get_property_controllers() {
522 return include plugin_dir_path( __FILE__ ) . 'inc/controller-config.php';
523 }
524
525 public function plugin_action_links( $links ) {
526 if ( isset( $links['edit'] ) ) {
527 unset( $links['edit'] );
528 }
529 $links['css_editor'] = '<a href="' . admin_url( 'themes.php?page=so_custom_css' ) . '">' . esc_html__( 'CSS Editor', 'so-css' ) . '</a>';
530 $links['support'] = '<a href="https://siteorigin.com/thread/" target="_blank">' . esc_html__( 'Support', 'so-css' ) . '</a>';
531
532 if ( apply_filters( 'siteorigin_premium_upgrade_teaser', true ) && ! defined( 'SITEORIGIN_PREMIUM_VERSION' ) ) {
533 $links['addons'] = '<a href="https://siteorigin.com/downloads/premium/?featured_addon=plugin/web-font-selector" style="color: #3db634" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Addons', 'so-css' ) . '</a>';
534 }
535
536 return $links;
537 }
538
539 public function display_admin_page() {
540 $socss_post_id = filter_input( INPUT_GET, 'socss_post_id', FILTER_VALIDATE_INT );
541 $theme = filter_input( INPUT_GET, 'theme' );
542 $time = filter_input( INPUT_GET, 'time', FILTER_VALIDATE_INT );
543
544 $page_title = esc_html__( 'SiteOrigin CSS', 'so-css' );
545 $theme_obj = wp_get_theme();
546 $theme_name = $theme_obj->get( 'Name' );
547 $editor_description = sprintf( esc_html__( 'Changes apply to %s and its child themes', 'so-css' ), $theme_name );
548 $save_button_label = esc_html__( 'Save CSS', 'so-css' );
549 $form_save_url = admin_url( 'themes.php?page=so_custom_css' );
550
551 if ( ! empty( $socss_post_id ) ) {
552 $selected_post = get_post( $socss_post_id );
553
554 $page_title = sprintf(
555 esc_html__( 'Editing CSS for: %s', 'so-css' ),
556 $selected_post->post_title
557 );
558
559 $editor_description = sprintf(
560 esc_html__( 'Changes apply to the %s %s when the current theme is %s or its child themes', 'so-css' ),
561 $selected_post->post_type,
562 $selected_post->post_title,
563 $theme_name
564 );
565 $post_type_obj = get_post_type_object( $selected_post->post_type );
566 $post_type_labels = $post_type_obj->labels;
567 $save_button_label = sprintf( esc_html__( 'Save %s CSS', 'so-css' ), $post_type_labels->singular_name );
568 $form_save_url = add_query_arg( 'socss_post_id', urlencode( $socss_post_id ), $form_save_url );
569 }
570 $custom_css = $this->get_custom_css( $this->theme, $socss_post_id );
571 $custom_css_revisions = $this->get_custom_css_revisions( $this->theme, $socss_post_id );
572 $current_revision = 0;
573
574 if ( ! empty( $theme ) && $theme == $this->theme && ! empty( $time ) && ! empty( $custom_css_revisions[ $time ] ) ) {
575 $current_revision = $time;
576 $custom_css = $custom_css_revisions[ $time ];
577 }
578
579 if ( ! empty( $current_revision ) ) {
580 $save_button_label = esc_html__( 'Revert to this revision', 'so-css' );
581 }
582
583 if ( ! empty( $custom_css_revisions ) ) {
584 krsort( $custom_css_revisions );
585 }
586
587 $theme = basename( get_template_directory() );
588
589 $editor_theme = get_option( 'so_css_editor_theme', 'neat' );
590
591 include plugin_dir_path( __FILE__ ) . 'tpl/page.php';
592 }
593
594 public function display_teaser() {
595 return apply_filters( 'siteorigin_premium_upgrade_teaser', true ) &&
596 ! defined( 'SITEORIGIN_PREMIUM_VERSION' );
597 }
598
599 /**
600 * Generates the url to edit the custom CSS for a post.
601 */
602 public function get_edit_css_link( $post ) {
603 $url = admin_url( 'themes.php?page=so_custom_css' );
604
605 if ( ! is_int( $post ) ) {
606 $post = get_post( $post );
607 $post = $post->ID;
608 }
609
610 return empty( $post ) ? $url : add_query_arg( array(
611 'socss_post_id' => urlencode( $post ),
612 'open_visual_editor' => 1,
613 ), $url );
614 }
615
616 public function admin_action_hide_getting_started() {
617 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'hide' ) ) {
618 return;
619 }
620
621 $user = wp_get_current_user();
622
623 if ( ! empty( $user ) ) {
624 update_user_meta( $user->ID, 'socss_hide_gs', true );
625 }
626 }
627
628 /**
629 * Retrieves the post specific CSS for the supplied postId.
630 */
631 public function admin_action_get_post_css() {
632 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'get_post_css' ) ) {
633 wp_die(
634 esc_html__( 'The supplied nonce is invalid.', 'so-css' ),
635 esc_html__( 'Invalid nonce.', 'so-css' ),
636 403
637 );
638 }
639
640 $post_id = filter_input( INPUT_GET, 'postId', FILTER_VALIDATE_INT );
641
642 $current = $this->get_custom_css( $this->theme, $post_id );
643
644 $url = empty( $post_id ) ? home_url() : set_url_scheme( get_permalink( $post_id ) );
645
646 wp_send_json( array( 'css' => empty( $current ) ? '' : $current, 'postUrl' => $url ) );
647 }
648
649 /**
650 * Retrieves the past revisions of post specific CSS for the supplied postId.
651 */
652 public function admin_action_get_revisions_list() {
653 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'get_revisions_list' ) ) {
654 wp_die(
655 esc_html__( 'The supplied nonce is invalid.', 'so-css' ),
656 esc_html__( 'Invalid nonce.', 'so-css' ),
657 403
658 );
659 }
660
661 $post_id = filter_input( INPUT_GET, 'postId', FILTER_VALIDATE_INT );
662
663 $this->custom_css_revisions_list( $this->theme, $post_id );
664
665 wp_die();
666 }
667
668 /**
669 * Retrieves the past revisions of post specific CSS for the supplied postId.
670 */
671 public function admin_action_save_css() {
672 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'so-css-ajax' ) ) {
673 wp_die(
674 esc_html__( 'The supplied nonce is invalid.', 'so-css' ),
675 esc_html__( 'Invalid nonce.', 'so-css' ),
676 403
677 );
678 }
679
680 if ( current_user_can( 'edit_theme_options' ) && isset( $_POST['css'] ) ) {
681 // Sanitize CSS input. Should keep most tags, apart from script and style tags.
682 $custom_css = self::sanitize_css( stripslashes( $_POST['css'] ) );
683
684 if ( empty( $this->css_file ) ) {
685 $current = $this->get_custom_css( $this->theme );
686 $this->save_custom_css( $custom_css, $this->theme );
687 } else {
688 $current = $this->css_file;
689 }
690
691 // If this has changed, then add a revision.
692 if ( $current != $custom_css ) {
693 $this->add_custom_css_revision( $custom_css, $this->theme );
694 $this->save_custom_css_file( $custom_css, $this->theme );
695
696 // Output the full revisions list.
697 $this->custom_css_revisions_list( $this->theme );
698 }
699 }
700 wp_die();
701 }
702
703 public function custom_css_revisions_list( $theme, $post_id = null, $current_revision = null ) {
704 $revisions = $this->get_custom_css_revisions( $theme, $post_id );
705
706 if ( is_array( $revisions ) && ! empty( $revisions ) ) {
707 $i = 0;
708
709 foreach ( $revisions as $time => $css ) {
710 $is_current = ( empty( $current_revision ) && $i == 0 ) || ( ! empty( $current_revision ) && $time == $current_revision );
711 $query_args = array( 'theme' => $theme, 'time' => $time, 'open_visual_editor' => false );
712
713 if ( ! empty( $post_id ) ) {
714 $query_args['socss_post_id'] = $post_id;
715 }
716 ?>
717 <li>
718 <?php if ( ! $is_current ) { ?>
719 <a href="<?php echo esc_url( add_query_arg( $query_args, admin_url( 'themes.php?page=so_custom_css' ) ) ); ?>"
720 class="load-css-revision">
721 <?php } ?>
722 <?php echo date( 'j F Y @ H:i:s', $time + get_option( 'gmt_offset' ) * 60 * 60 ); ?>
723 <?php if ( ! $is_current ) { ?>
724 </a>
725 <?php } ?>
726 (<?php printf( esc_html__( '%d chars', 'so-css' ), strlen( $css ) ); ?>)<?php if ( $i == 0 ) { ?> (<?php esc_html_e( 'Latest', 'so-css' ); ?>)<?php } ?>
727 </li>
728 <?php
729 $i++;
730 }
731 } else {
732 printf( '<em>%s</em>', esc_html__( 'No revisions yet.', 'so-css' ) );
733 }
734 }
735
736 /**
737 * Add one or more paths to the registered snippet paths
738 *
739 * @param string|array $path
740 */
741 public function register_snippet_path( $path ) {
742 $this->snippet_paths = array_merge( $this->snippet_paths, (array) $path );
743 }
744
745 /**
746 * Get all the available snippets
747 *
748 * @return array|bool
749 */
750 public function get_snippets() {
751 // Get the snippet paths
752 $snippet_paths = apply_filters( 'siteorigin_css_snippet_paths', $this->snippet_paths );
753
754 if ( empty( $snippet_paths ) ) {
755 return array();
756 }
757
758 static $snippets = array();
759
760 if ( ! empty( $snippets ) ) {
761 return $snippets;
762 }
763
764 if ( ! WP_Filesystem() ) {
765 return false;
766 }
767 global $wp_filesystem;
768
769 foreach ( $snippet_paths as $path ) {
770 foreach ( glob( $path . '/*.css' ) as $css_file ) {
771 $data = get_file_data( $css_file, array(
772 'Name' => 'Name',
773 'Description' => 'Description',
774 ) );
775
776 // Get the CSS and strip out the first header
777 $css = $wp_filesystem->get_contents( $css_file );
778 $css = preg_replace( '!/\*.*?\*/!s', '', $css, 1 );
779
780 $snippets[] = wp_parse_args( $data, array(
781 'css' => str_replace( "\t", ' ', trim( $css ) ),
782 ) );
783 }
784 }
785
786 usort( $snippets, array( $this, 'sort_snippet_callback' ) );
787
788 return $snippets;
789 }
790
791 /**
792 * Sort snippets by name.
793 *
794 * @return int
795 */
796 public static function sort_snippet_callback( $a, $b ) {
797 return $a['Name'] > $b['Name'] ? 1 : - 1;
798 }
799
800 /**
801 * A very simple CSS sanitization function.
802 *
803 * @return string
804 */
805 public static function sanitize_css( $css ) {
806 return trim( strip_tags( $css ) );
807 }
808
809 /**
810 * Get all the available theme CSS
811 */
812 public function get_theme_css() {
813 $css = '';
814
815 if ( file_exists( get_template_directory() . '/style.css' ) ) {
816 $css .= file_get_contents( get_template_directory() . '/style.css' );
817 }
818
819 if ( is_child_theme() ) {
820 $css .= file_get_contents( get_stylesheet_directory() . '/style.css' );
821 }
822
823 // Remove all CSS comments
824 $regex = array(
825 "`^([\t\s]+)`ism" => '',
826 "`^\/\*(.+?)\*\/`ism" => '',
827 "`(\A|[\n;]+)/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`" => '$1',
828 "`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism" => "\n",
829 );
830 $css = preg_replace( array_keys( $regex ), $regex, $css );
831 $css = preg_replace( '/\s+/', ' ', $css );
832
833 return $css;
834 }
835
836 public function enqueue_inspector_scripts() {
837 if ( ! current_user_can( 'edit_theme_options' ) ) {
838 return;
839 }
840
841 wp_enqueue_style( 'dashicons' );
842
843 wp_enqueue_script( 'siteorigin-css-parser-lib', plugin_dir_url( __FILE__ ) . 'js/css' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), SOCSS_VERSION );
844
845 wp_enqueue_script( 'siteorigin-css-sizes', plugin_dir_url( __FILE__ ) . 'js/jquery.sizes' . SOCSS_JS_SUFFIX . '.js', array( 'jquery' ), '0.33' );
846 wp_enqueue_script( 'siteorigin-css-specificity', plugin_dir_url( __FILE__ ) . 'js/specificity' . SOCSS_JS_SUFFIX . '.js', array() );
847 wp_enqueue_script( 'siteorigin-css-inspector', plugin_dir_url( __FILE__ ) . 'js/inspector' . SOCSS_JS_SUFFIX . '.js', array(
848 'jquery',
849 'underscore',
850 'backbone',
851 ), SOCSS_VERSION, true );
852 wp_enqueue_style( 'siteorigin-css-inspector', plugin_dir_url( __FILE__ ) . 'css/inspector.css', array(), SOCSS_VERSION );
853
854 wp_localize_script( 'siteorigin-css-inspector', 'socssOptions', array() );
855 }
856
857 public function inspector_templates() {
858 if ( ! current_user_can( 'edit_theme_options' ) ) {
859 return;
860 }
861
862 include plugin_dir_path( __FILE__ ) . 'tpl/inspector-templates.php';
863 }
864
865 /**
866 * Change the stylesheets to all be inline
867 */
868 public function inline_inspector_scripts() {
869 if ( ! current_user_can( 'edit_theme_options' ) ) {
870 return;
871 }
872
873 $regex = array(
874 "`^([\t\s]+)`ism" => '',
875 "`^\/\*(.+?)\*\/`ism" => '',
876 "`(\A|[\n;]+)/\*[^*]*\*+(?:[^/*][^*]*\*+)*/`" => '$1',
877 "`(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+`ism" => "\n",
878 );
879
880 global $wp_styles;
881
882 if ( empty( $wp_styles->queue ) ) {
883 return;
884 }
885
886 // Make each of the scripts inline
887 foreach ( $wp_styles->queue as $handle ) {
888 if ( $handle === 'siteorigin-css-inspector' || $handle === 'dashicons' ) {
889 continue;
890 }
891 $style = $wp_styles->registered[ $handle ];
892
893 if ( empty( $style->src ) || substr( $style->src, 0, 4 ) !== 'http' ) {
894 continue;
895 }
896 $response = wp_remote_get( $style->src );
897
898 if ( is_wp_error( $response ) || $response['response']['code'] !== 200 || empty( $response['body'] ) ) {
899 continue;
900 }
901
902 $css = $response['body'];
903 $css = preg_replace( array_keys( $regex ), $regex, $css );
904
905 ?>
906 <script type="text/css" class="socss-theme-styles"
907 id="socss-inlined-style-<?php echo sanitize_html_class( $handle ); ?>">
908 <?php echo strip_tags( $css ); ?>
909 </script>
910 <?php
911 }
912 }
913
914 public function disable_ngg_resource_manager() {
915 if ( ! current_user_can( 'edit_theme_options' ) ) {
916 return;
917 }
918
919 //The NextGen Gallery plugin does some weird interfering with the output buffer.
920 define( 'NGG_DISABLE_RESOURCE_MANAGER', true );
921 }
922
923 private function get_latest_revision_timestamp() {
924 $revisions = $this->get_custom_css_revisions( $this->theme );
925
926 if ( empty( $revisions ) ) {
927 return false;
928 }
929 krsort( $revisions );
930 $revision_times = array_keys( $revisions );
931
932 return $revision_times[0];
933 }
934 }
935
936 // Initialize the single
937 SiteOrigin_CSS::single();
938