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