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