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