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