Version Description
- Added revisions
- Added drafts/previewing
- Added minification
Download this release
Release Info
Developer | hearken |
Plugin | Custom CSS and Javascript |
Version | 2.0 |
Comparing to | |
See all releases |
Code changes from version 1.0.6 to 2.0
- css/custom-css-and-javascript.css +14 -0
- custom-css-and-javascript.php +235 -65
- images/potent-logo.png +0 -0
- js/custom-css-and-javascript.js +175 -0
- minify/LICENSE +18 -0
- minify/data/js/keywords_after.txt +7 -0
- minify/data/js/keywords_before.txt +27 -0
- minify/data/js/keywords_reserved.txt +47 -0
- minify/data/js/operators_after.txt +45 -0
- minify/data/js/operators_before.txt +43 -0
- minify/src/CSS.php +573 -0
- minify/src/Converter.php +195 -0
- minify/src/Exception.php +10 -0
- minify/src/JS.php +480 -0
- minify/src/Minify.php +382 -0
- plugin-credit.php +30 -0
- readme.txt +16 -4
css/custom-css-and-javascript.css
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Author: Hearken Media
|
3 |
+
* License: GNU General Public License version 2 or later
|
4 |
+
* License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
5 |
+
*/
|
6 |
+
#hm_custom_css_js_revisions {
|
7 |
+
margin-top: 5px;
|
8 |
+
}
|
9 |
+
#hm_custom_css_js_revisions li {
|
10 |
+
white-space: nowrap;
|
11 |
+
}
|
12 |
+
#hm_custom_css_js_revisions .active a.view-rev {
|
13 |
+
font-weight: bold;
|
14 |
+
}
|
custom-css-and-javascript.php
CHANGED
@@ -2,9 +2,9 @@
|
|
2 |
/**
|
3 |
* Plugin Name: Custom CSS and Javascript
|
4 |
* Description: Easily add custom CSS and Javascript code to your WordPress site.
|
5 |
-
* Version:
|
6 |
-
* Author:
|
7 |
-
* Author URI: http://
|
8 |
* License: GNU General Public License version 2 or later
|
9 |
* License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
10 |
*/
|
@@ -12,10 +12,15 @@
|
|
12 |
add_action('wp_enqueue_scripts', 'hm_custom_css_js_scripts', 999999);
|
13 |
function hm_custom_css_js_scripts() {
|
14 |
$uploadDir = wp_upload_dir();
|
15 |
-
if (
|
16 |
-
wp_enqueue_script('hm_custom_js',
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
19 |
}
|
20 |
add_action('admin_menu', 'hm_custom_css_admin_menu');
|
21 |
function hm_custom_css_admin_menu() {
|
@@ -35,7 +40,10 @@ function hm_custom_css_js_admin_scripts($hook) {
|
|
35 |
else
|
36 |
wp_enqueue_script('hm_custom_css_js_codemirror_mode_js', plugins_url('codemirror/mode/javascript.js', __FILE__));
|
37 |
wp_enqueue_style('hm_custom_css_js_codemirror', plugins_url('codemirror/codemirror.css', __FILE__));
|
|
|
|
|
38 |
}
|
|
|
39 |
add_action('wp_ajax_hm_custom_css_js_save', 'hm_custom_css_js_save');
|
40 |
function hm_custom_css_js_save() {
|
41 |
if (!current_user_can('edit_theme_options') || empty($_POST['mode']) || !isset($_POST['code']))
|
@@ -43,15 +51,216 @@ function hm_custom_css_js_save() {
|
|
43 |
$_POST['mode'] = strtolower($_POST['mode']);
|
44 |
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
45 |
wp_send_json_error();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
$uploadDir = wp_upload_dir();
|
47 |
if (!is_dir($uploadDir['basedir'].'/hm_custom_css_js'))
|
48 |
mkdir($uploadDir['basedir'].'/hm_custom_css_js') or wp_send_json_error();
|
49 |
-
|
|
|
50 |
wp_send_json_error();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
update_option('hm_custom_'.$_POST['mode'].'_ver', time());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
wp_send_json_success();
|
53 |
}
|
54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
function hm_custom_css_page() {
|
56 |
hm_custom_css_js_page('CSS');
|
57 |
}
|
@@ -68,69 +277,30 @@ function hm_custom_css_js_page($mode) {
|
|
68 |
echo('
|
69 |
<div class="wrap">
|
70 |
<h2>Custom '.$mode.'</h2>
|
|
|
71 |
<div>
|
72 |
-
<div id="hm_custom_code_editor" style="margin-top: 15px;"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
<div style="float: right; padding-left: 10px; margin-top: 3px; white-space: nowrap; font-style: italic;">
|
74 |
<a href="https://codemirror.net/" target="_blank">CodeMirror</a> code editor
|
75 |
</div>
|
76 |
<button type="button" class="button-primary hm-custom-css-js-save-btn" style="margin-top: 15px;" disabled="disabled">Saved</button>
|
|
|
|
|
|
|
|
|
77 |
</div>
|
78 |
-
<
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
mode: "'.strtolower($mode).'",
|
84 |
-
value: '.$code.'
|
85 |
-
});
|
86 |
-
hm_custom_code_editor.on("change", function() {
|
87 |
-
if (hm_custom_code_editor_has_changes)
|
88 |
-
return;
|
89 |
-
hm_custom_code_editor_has_changes = true;
|
90 |
-
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
91 |
-
});
|
92 |
-
$(".hm-custom-css-js-save-btn").click(function() {
|
93 |
-
$(".hm-custom-css-js-save-btn").prop("disabled", true).html("Saving...");
|
94 |
-
$.post(ajaxurl, {action: "hm_custom_css_js_save", mode: "'.$mode.'", code: hm_custom_code_editor.getValue()})
|
95 |
-
.done(function(data) {
|
96 |
-
if (data.success) {
|
97 |
-
$(".hm-custom-css-js-save-btn").html("Saved");
|
98 |
-
hm_custom_code_editor_has_changes = false;
|
99 |
-
} else {
|
100 |
-
alert("Error while saving. Please try again.");
|
101 |
-
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
102 |
-
}
|
103 |
-
})
|
104 |
-
.fail(function() {
|
105 |
-
alert("Error while saving. Please try again.");
|
106 |
-
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
107 |
-
});
|
108 |
-
});
|
109 |
-
$(window).resize(function() {
|
110 |
-
$("#hm_custom_code_editor .CodeMirror").height(Math.max(150,
|
111 |
-
$(window).height()
|
112 |
-
- $("#hm_custom_code_editor").offset().top
|
113 |
-
- $(".hm-custom-css-js-save-btn").height()
|
114 |
-
- 30));
|
115 |
-
hm_custom_code_editor.refresh();
|
116 |
-
});
|
117 |
-
$(window).resize();
|
118 |
-
$(window).on("beforeunload", function(ev) {
|
119 |
-
if (hm_custom_code_editor_has_changes) {
|
120 |
-
ev.returnValue = "You have unsaved changes that will be lost if you leave this page!";
|
121 |
-
return ev.returnValue;
|
122 |
-
}
|
123 |
-
});
|
124 |
-
});
|
125 |
-
</script>
|
126 |
-
|
127 |
-
<div style="background-color: #fff; border: 1px solid #ccc; padding: 20px; display: inline-block; margin-top: 30px;">
|
128 |
-
<h3 style="margin: 0;">Plugin by:</h3>
|
129 |
-
<a href="http://hearkenmedia.com/landing-wp-plugin.php?utm_source=custom-css-and-javascript&utm_medium=link&utm_campaign=wp-widget-link" target="_blank"><img src="'.plugins_url('images/hm-logo.png', __FILE__).'" alt="Hearken Media" style="width: 250px;" /></a><br />
|
130 |
-
<a href="https://wordpress.org/support/view/plugin-reviews/custom-css-and-javascript" target="_blank"><strong>
|
131 |
-
If you find this plugin useful, please write a brief review!
|
132 |
-
</strong></a>
|
133 |
-
</div>
|
134 |
</div>
|
135 |
');
|
136 |
}
|
2 |
/**
|
3 |
* Plugin Name: Custom CSS and Javascript
|
4 |
* Description: Easily add custom CSS and Javascript code to your WordPress site.
|
5 |
+
* Version: 2.0
|
6 |
+
* Author: Potent Plugins
|
7 |
+
* Author URI: http://potentplugins.com/?utm_source=custom-css-and-javascript&utm_medium=link&utm_campaign=wp-plugin-credit-link
|
8 |
* License: GNU General Public License version 2 or later
|
9 |
* License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
10 |
*/
|
12 |
add_action('wp_enqueue_scripts', 'hm_custom_css_js_scripts', 999999);
|
13 |
function hm_custom_css_js_scripts() {
|
14 |
$uploadDir = wp_upload_dir();
|
15 |
+
if (current_user_can('edit_theme_options')) {
|
16 |
+
wp_enqueue_script('hm_custom_js', get_site_url(null, '/index.php').'?hm_custom_js_draft=1', array(), time());
|
17 |
+
wp_enqueue_style('hm_custom_css', get_site_url(null, '/index.php').'?hm_custom_css_draft=1', array(), time());
|
18 |
+
} else {
|
19 |
+
if (file_exists($uploadDir['basedir'].'/hm_custom_css_js/custom.js'))
|
20 |
+
wp_enqueue_script('hm_custom_js', $uploadDir['baseurl'].'/hm_custom_css_js/custom.js', array(), get_option('hm_custom_javascript_ver', 1));
|
21 |
+
if (file_exists($uploadDir['basedir'].'/hm_custom_css_js/custom.css'))
|
22 |
+
wp_enqueue_style('hm_custom_css', $uploadDir['baseurl'].'/hm_custom_css_js/custom.css', array(), get_option('hm_custom_css_ver', 1));
|
23 |
+
}
|
24 |
}
|
25 |
add_action('admin_menu', 'hm_custom_css_admin_menu');
|
26 |
function hm_custom_css_admin_menu() {
|
40 |
else
|
41 |
wp_enqueue_script('hm_custom_css_js_codemirror_mode_js', plugins_url('codemirror/mode/javascript.js', __FILE__));
|
42 |
wp_enqueue_style('hm_custom_css_js_codemirror', plugins_url('codemirror/codemirror.css', __FILE__));
|
43 |
+
wp_enqueue_script('hm_custom_css_js', plugins_url('js/custom-css-and-javascript.js', __FILE__));
|
44 |
+
wp_enqueue_style('hm_custom_css_js', plugins_url('css/custom-css-and-javascript.css', __FILE__));
|
45 |
}
|
46 |
+
|
47 |
add_action('wp_ajax_hm_custom_css_js_save', 'hm_custom_css_js_save');
|
48 |
function hm_custom_css_js_save() {
|
49 |
if (!current_user_can('edit_theme_options') || empty($_POST['mode']) || !isset($_POST['code']))
|
51 |
$_POST['mode'] = strtolower($_POST['mode']);
|
52 |
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
53 |
wp_send_json_error();
|
54 |
+
|
55 |
+
$_POST['code'] = wp_unslash($_POST['code']);
|
56 |
+
|
57 |
+
$rev_id = wp_insert_post(array(
|
58 |
+
'post_content' => $_POST['code'],
|
59 |
+
'post_status' => 'draft',
|
60 |
+
'post_type' => 'hm_custom_'.$_POST['mode'],
|
61 |
+
));
|
62 |
+
if ($rev_id === false)
|
63 |
+
wp_send_json_error();
|
64 |
+
|
65 |
+
wp_send_json_success($rev_id);
|
66 |
+
}
|
67 |
+
|
68 |
+
add_action('wp_ajax_hm_custom_css_js_publish', 'hm_custom_css_js_publish');
|
69 |
+
function hm_custom_css_js_publish() {
|
70 |
+
if (!current_user_can('edit_theme_options') || empty($_POST['mode']) || !isset($_POST['rev']) || !is_numeric($_POST['rev']))
|
71 |
+
wp_send_json_error();
|
72 |
+
$_POST['mode'] = strtolower($_POST['mode']);
|
73 |
+
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
74 |
+
wp_send_json_error();
|
75 |
+
|
76 |
+
$post = get_post($_POST['rev']);
|
77 |
+
if ($post->post_type != 'hm_custom_'.$_POST['mode'])
|
78 |
+
wp_send_json_error();
|
79 |
+
|
80 |
$uploadDir = wp_upload_dir();
|
81 |
if (!is_dir($uploadDir['basedir'].'/hm_custom_css_js'))
|
82 |
mkdir($uploadDir['basedir'].'/hm_custom_css_js') or wp_send_json_error();
|
83 |
+
$outputFile = $uploadDir['basedir'].'/hm_custom_css_js/custom.'.($_POST['mode'] == 'css' ? 'css' : 'js');
|
84 |
+
if (file_put_contents($outputFile, $post->post_content) === false)
|
85 |
wp_send_json_error();
|
86 |
+
if (empty($_POST['minify'])) {
|
87 |
+
update_option('hm_custom_'.$_POST['mode'].'_minify', false);
|
88 |
+
} else {
|
89 |
+
update_option('hm_custom_'.$_POST['mode'].'_minify', true);
|
90 |
+
require_once(__DIR__.'/minify/src/Minify.php');
|
91 |
+
require_once(__DIR__.'/minify/src/Exception.php');
|
92 |
+
if ($_POST['mode'] == 'css') {
|
93 |
+
require_once(__DIR__.'/minify/src/CSS.php');
|
94 |
+
require_once(__DIR__.'/minify/src/Converter.php');
|
95 |
+
$minifier = new MatthiasMullie\Minify\CSS;
|
96 |
+
} else {
|
97 |
+
require_once(__DIR__.'/minify/src/JS.php');
|
98 |
+
$minifier = new MatthiasMullie\Minify\JS;
|
99 |
+
}
|
100 |
+
$minifier->add($outputFile);
|
101 |
+
$minifier->minify($outputFile);
|
102 |
+
}
|
103 |
+
|
104 |
update_option('hm_custom_'.$_POST['mode'].'_ver', time());
|
105 |
+
|
106 |
+
// Unpublish previous revisions
|
107 |
+
$wp_query = new WP_Query(array(
|
108 |
+
'post_type' => 'hm_custom_'.$_POST['mode'],
|
109 |
+
'post_status' => 'publish',
|
110 |
+
'fields' => 'ids',
|
111 |
+
'nopaging' => true
|
112 |
+
));
|
113 |
+
$posts = $wp_query->get_posts();
|
114 |
+
foreach ($posts as $postId) {
|
115 |
+
if (!wp_update_post(array(
|
116 |
+
'ID' => $postId,
|
117 |
+
'post_status' => 'draft',
|
118 |
+
)))
|
119 |
+
wp_send_json_error();
|
120 |
+
}
|
121 |
+
|
122 |
+
if (!wp_update_post(array(
|
123 |
+
'ID' => $_POST['rev'],
|
124 |
+
'post_status' => 'publish',
|
125 |
+
'post_date' => current_time('Y-m-d H:i:s'),
|
126 |
+
)))
|
127 |
+
wp_send_json_error();
|
128 |
+
|
129 |
wp_send_json_success();
|
130 |
}
|
131 |
|
132 |
+
add_action('wp_ajax_hm_custom_css_js_delete_revision', 'hm_custom_css_js_delete_revision');
|
133 |
+
function hm_custom_css_js_delete_revision() {
|
134 |
+
if (!current_user_can('edit_theme_options') || empty($_POST['mode']) || !isset($_POST['rev']) || !is_numeric($_POST['rev']))
|
135 |
+
wp_send_json_error();
|
136 |
+
$_POST['mode'] = strtolower($_POST['mode']);
|
137 |
+
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
138 |
+
wp_send_json_error();
|
139 |
+
|
140 |
+
$post = get_post($_POST['rev']);
|
141 |
+
if ($post->post_type != 'hm_custom_'.$_POST['mode'] || $post->post_status == 'publish')
|
142 |
+
wp_send_json_error();
|
143 |
+
|
144 |
+
|
145 |
+
if (!wp_delete_post($post->ID, true))
|
146 |
+
wp_send_json_error();
|
147 |
+
|
148 |
+
wp_send_json_success();
|
149 |
+
}
|
150 |
+
|
151 |
+
add_action('wp_ajax_hm_custom_css_js_delete_revisions', 'hm_custom_css_js_delete_revisions');
|
152 |
+
function hm_custom_css_js_delete_revisions() {
|
153 |
+
if (!current_user_can('edit_theme_options') || empty($_POST['mode']))
|
154 |
+
wp_send_json_error();
|
155 |
+
$_POST['mode'] = strtolower($_POST['mode']);
|
156 |
+
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
157 |
+
wp_send_json_error();
|
158 |
+
|
159 |
+
$wp_query = new WP_Query(array(
|
160 |
+
'post_type' => 'hm_custom_'.$_POST['mode'],
|
161 |
+
'post_status' => 'draft',
|
162 |
+
'fields' => 'ids',
|
163 |
+
'nopaging' => true
|
164 |
+
));
|
165 |
+
$posts = $wp_query->get_posts();
|
166 |
+
foreach ($posts as $postId) {
|
167 |
+
if (!wp_delete_post($postId, true))
|
168 |
+
wp_send_json_error();
|
169 |
+
}
|
170 |
+
|
171 |
+
wp_send_json_success();
|
172 |
+
}
|
173 |
+
|
174 |
+
|
175 |
+
add_action('wp_ajax_hm_custom_css_js_get_revisions', 'hm_custom_css_js_get_revisions');
|
176 |
+
function hm_custom_css_js_get_revisions() {
|
177 |
+
if (!current_user_can('edit_theme_options') || empty($_POST['mode']))
|
178 |
+
wp_send_json_error();
|
179 |
+
$_POST['mode'] = strtolower($_POST['mode']);
|
180 |
+
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
181 |
+
wp_send_json_error();
|
182 |
+
|
183 |
+
$wp_query = new WP_Query();
|
184 |
+
$posts = $wp_query->query(array(
|
185 |
+
'post_type' => 'hm_custom_'.$_POST['mode'],
|
186 |
+
'post_status' => 'any',
|
187 |
+
'nopaging' => true
|
188 |
+
));
|
189 |
+
|
190 |
+
|
191 |
+
$revisions = array();
|
192 |
+
if (empty($posts)) {
|
193 |
+
$uploadDir = wp_upload_dir();
|
194 |
+
$customFile = $uploadDir['basedir'].'/hm_custom_css_js/custom.'.($_POST['mode'] == 'css' ? 'css' : 'js');
|
195 |
+
if (file_exists($customFile)) {
|
196 |
+
$contents = file_get_contents($customFile);
|
197 |
+
if ($contents === false)
|
198 |
+
wp_send_json_error();
|
199 |
+
$rev_id = wp_insert_post(array(
|
200 |
+
'post_content' => $contents,
|
201 |
+
'post_status' => 'publish',
|
202 |
+
'post_type' => 'hm_custom_'.$_POST['mode'],
|
203 |
+
));
|
204 |
+
$revisions[] = array('id' => $rev_id, 'rev_date' => current_time('Y-m-d H:i:s'), 'published' => true);
|
205 |
+
}
|
206 |
+
} else {
|
207 |
+
foreach ($posts as $post) {
|
208 |
+
$revisions[] = array('id' => $post->ID, 'rev_date' => $post->post_date, 'published' => ($post->post_status == 'publish'));
|
209 |
+
}
|
210 |
+
}
|
211 |
+
|
212 |
+
wp_send_json_success($revisions);
|
213 |
+
}
|
214 |
+
|
215 |
+
add_action('wp_ajax_hm_custom_css_js_get_revision', 'hm_custom_css_js_get_revision');
|
216 |
+
function hm_custom_css_js_get_revision() {
|
217 |
+
if (!current_user_can('edit_theme_options') || empty($_POST['mode']) || !isset($_POST['rev']) || !is_numeric($_POST['rev']))
|
218 |
+
wp_send_json_error();
|
219 |
+
$_POST['mode'] = strtolower($_POST['mode']);
|
220 |
+
if ($_POST['mode'] != 'css' && $_POST['mode'] != 'javascript')
|
221 |
+
wp_send_json_error();
|
222 |
+
|
223 |
+
$post = get_post($_POST['rev']);
|
224 |
+
if ($post->post_type != 'hm_custom_'.$_POST['mode'])
|
225 |
+
wp_send_json_error();
|
226 |
+
|
227 |
+
wp_send_json_success(array(
|
228 |
+
'id' => $post->ID,
|
229 |
+
'content' => $post->post_content
|
230 |
+
));
|
231 |
+
}
|
232 |
+
|
233 |
+
add_action('init', 'hm_custom_css_js_init');
|
234 |
+
function hm_custom_css_js_init() {
|
235 |
+
register_post_type('hm_custom_css');
|
236 |
+
register_post_type('hm_custom_javascript');
|
237 |
+
|
238 |
+
if (!empty($_GET['hm_custom_css_draft'])) {
|
239 |
+
$wp_query = new WP_Query(array(
|
240 |
+
'post_type' => 'hm_custom_css',
|
241 |
+
'post_status' => 'any',
|
242 |
+
'posts_per_page' => 1
|
243 |
+
));
|
244 |
+
$posts = $wp_query->get_posts();
|
245 |
+
header('Content-Type: text/css');
|
246 |
+
if (isset($posts[0]))
|
247 |
+
echo($posts[0]->post_content);
|
248 |
+
exit;
|
249 |
+
}
|
250 |
+
if (!empty($_GET['hm_custom_js_draft'])) {
|
251 |
+
$wp_query = new WP_Query(array(
|
252 |
+
'post_type' => 'hm_custom_javascript',
|
253 |
+
'post_status' => 'any',
|
254 |
+
'posts_per_page' => 1
|
255 |
+
));
|
256 |
+
$posts = $wp_query->get_posts();
|
257 |
+
header('Content-Type: text/javascript');
|
258 |
+
if (isset($posts[0]))
|
259 |
+
echo($posts[0]->post_content);
|
260 |
+
exit;
|
261 |
+
}
|
262 |
+
}
|
263 |
+
|
264 |
function hm_custom_css_page() {
|
265 |
hm_custom_css_js_page('CSS');
|
266 |
}
|
277 |
echo('
|
278 |
<div class="wrap">
|
279 |
<h2>Custom '.$mode.'</h2>
|
280 |
+
<script>var hm_custom_css_js_mode = "'.$mode.'";</script>
|
281 |
<div>
|
282 |
+
<div id="hm_custom_code_editor" style="margin-top: 15px;">
|
283 |
+
<div style="width: 200px; height: 100%; overflow: auto; float: right; padding: 0 20px;">
|
284 |
+
<h4 style="margin: 0; margin-bottom: 5px;">Revisions:</h4>
|
285 |
+
<button class="button-secondary hm-custom-css-js-delete-revisions-btn">Delete All</button>
|
286 |
+
<ul id="hm_custom_css_js_revisions">
|
287 |
+
</ul>
|
288 |
+
</div>
|
289 |
+
</div>
|
290 |
<div style="float: right; padding-left: 10px; margin-top: 3px; white-space: nowrap; font-style: italic;">
|
291 |
<a href="https://codemirror.net/" target="_blank">CodeMirror</a> code editor
|
292 |
</div>
|
293 |
<button type="button" class="button-primary hm-custom-css-js-save-btn" style="margin-top: 15px;" disabled="disabled">Saved</button>
|
294 |
+
<button type="button" class="button-primary hm-custom-css-js-publish-btn" style="margin-top: 15px; margin-right: 10px;" disabled="disabled">Save & Publish</button>
|
295 |
+
<label style="margin-top: 15px; white-space: nowrap;">
|
296 |
+
<input type="checkbox" class="hm-custom-css-js-minify-cb"'.(get_option('hm_custom_'.strtolower($mode).'_minify', true) ? ' checked="checked"' : '').' /> Minify output
|
297 |
+
</label>
|
298 |
</div>
|
299 |
+
<div style="clear: both; margin-bottom: 20px;"></div>
|
300 |
+
');
|
301 |
+
$potent_slug = 'custom-css-and-javascript';
|
302 |
+
include(__DIR__.'/plugin-credit.php');
|
303 |
+
echo('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
</div>
|
305 |
');
|
306 |
}
|
images/potent-logo.png
ADDED
Binary file
|
js/custom-css-and-javascript.js
ADDED
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Author: Hearken Media
|
3 |
+
* License: GNU General Public License version 2 or later
|
4 |
+
* License URI: http://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html
|
5 |
+
*/
|
6 |
+
var hm_custom_code_editor, hm_custom_code_editor_has_changes = false, hm_custom_css_js_save_publish = false, hm_custom_css_js_rev = 0, hm_custom_css_js_published_rev = 0;
|
7 |
+
jQuery(document).ready(function($) {
|
8 |
+
hm_custom_code_editor = CodeMirror(document.getElementById("hm_custom_code_editor"), {
|
9 |
+
lineNumbers: true,
|
10 |
+
mode: hm_custom_css_js_mode.toLowerCase()
|
11 |
+
});
|
12 |
+
hm_custom_code_editor.on("change", function() {
|
13 |
+
if (hm_custom_code_editor_has_changes)
|
14 |
+
return;
|
15 |
+
hm_custom_code_editor_has_changes = true;
|
16 |
+
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
17 |
+
$(".hm-custom-css-js-publish-btn").html("Save & Publish").prop("disabled", false);
|
18 |
+
});
|
19 |
+
$(".hm-custom-css-js-save-btn").click(function() {
|
20 |
+
$(".hm-custom-css-js-save-btn").prop("disabled", true).html("Saving...");
|
21 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_save", mode: hm_custom_css_js_mode, code: hm_custom_code_editor.getValue()})
|
22 |
+
.done(function(data) {
|
23 |
+
if (data.success) {
|
24 |
+
$(".hm-custom-css-js-save-btn").html("Saved");
|
25 |
+
hm_custom_css_js_rev = data.data;
|
26 |
+
hm_custom_code_editor_has_changes = false;
|
27 |
+
if (hm_custom_css_js_save_publish)
|
28 |
+
$(".hm-custom-css-js-publish-btn").click()
|
29 |
+
else
|
30 |
+
hm_custom_css_js_get_revisions();
|
31 |
+
} else {
|
32 |
+
alert("Error while saving. Please try again.");
|
33 |
+
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
34 |
+
if (hm_custom_css_js_save_publish)
|
35 |
+
$(".hm-custom-css-js-publish-btn").html("Save & Publish").prop("disabled", true);
|
36 |
+
}
|
37 |
+
})
|
38 |
+
.fail(function() {
|
39 |
+
alert("Error while saving. Please try again.");
|
40 |
+
$(".hm-custom-css-js-save-btn").html("Save").prop("disabled", false);
|
41 |
+
if (hm_custom_css_js_save_publish)
|
42 |
+
$(".hm-custom-css-js-publish-btn").html("Save & Publish").prop("disabled", true);
|
43 |
+
});
|
44 |
+
});
|
45 |
+
$(".hm-custom-css-js-publish-btn").click(function() {
|
46 |
+
$(".hm-custom-css-js-publish-btn").prop("disabled", true).html("Publishing...");
|
47 |
+
if (!$(".hm-custom-css-js-save-btn").prop("disabled")) {
|
48 |
+
hm_custom_css_js_save_publish = true;
|
49 |
+
$(".hm-custom-css-js-save-btn").click();
|
50 |
+
return;
|
51 |
+
}
|
52 |
+
hm_custom_css_js_save_publish = false;
|
53 |
+
|
54 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_publish", mode: hm_custom_css_js_mode, minify: ($('.hm-custom-css-js-minify-cb').prop('checked') ? 1 : 0), rev: hm_custom_css_js_rev})
|
55 |
+
.done(function(data) {
|
56 |
+
if (data.success) {
|
57 |
+
$(".hm-custom-css-js-publish-btn").html("Published");
|
58 |
+
hm_custom_css_js_get_revisions();
|
59 |
+
} else {
|
60 |
+
alert("Error while publishing. Please try again.");
|
61 |
+
$(".hm-custom-css-js-publish-btn").html("Save & Publish").prop("disabled", false);
|
62 |
+
}
|
63 |
+
})
|
64 |
+
.fail(function() {
|
65 |
+
alert("Error while publishing. Please try again.");
|
66 |
+
$(".hm-custom-css-js-publish-btn").html("Save & Publish").prop("disabled", false);
|
67 |
+
});
|
68 |
+
});
|
69 |
+
|
70 |
+
$(".hm-custom-css-js-delete-revisions-btn").click(function() {
|
71 |
+
$(this).prop('disabled', true).html('Deleting...');
|
72 |
+
|
73 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_delete_revisions", mode: hm_custom_css_js_mode})
|
74 |
+
.done(function(data) {
|
75 |
+
if (data.success) {
|
76 |
+
hm_custom_css_js_get_revisions();
|
77 |
+
$(".hm-custom-css-js-delete-revisions-btn").html('Delete All').prop('disabled', false);
|
78 |
+
} else {
|
79 |
+
alert("Error while deleting. Please try again.");
|
80 |
+
$(".hm-custom-css-js-delete-revisions-btn").html('Delete All').prop('disabled', false);
|
81 |
+
}
|
82 |
+
})
|
83 |
+
.fail(function() {
|
84 |
+
alert("Error while deleting. Please try again.");
|
85 |
+
$(".hm-custom-css-js-delete-revisions-btn").html('Delete All').prop('disabled', false);
|
86 |
+
});
|
87 |
+
});
|
88 |
+
|
89 |
+
|
90 |
+
$(window).resize(function() {
|
91 |
+
$("#hm_custom_code_editor, #hm_custom_code_editor .CodeMirror").height(Math.max(150,
|
92 |
+
$(window).height()
|
93 |
+
- $("#hm_custom_code_editor").offset().top
|
94 |
+
- $(".hm-custom-css-js-save-btn").height()
|
95 |
+
- 30));
|
96 |
+
hm_custom_code_editor.refresh();
|
97 |
+
});
|
98 |
+
$(window).resize();
|
99 |
+
$(window).on("beforeunload", function(ev) {
|
100 |
+
if (hm_custom_code_editor_has_changes) {
|
101 |
+
ev.returnValue = "You have unsaved changes that will be lost if you leave this page!";
|
102 |
+
return ev.returnValue;
|
103 |
+
}
|
104 |
+
});
|
105 |
+
|
106 |
+
$("#hm_custom_css_js_revisions").on("click", "li > a.view-rev", function(ev) {
|
107 |
+
|
108 |
+
if (hm_custom_code_editor_has_changes &&
|
109 |
+
!confirm("You have unsaved changes that will be lost if you view this revision!"))
|
110 |
+
return;
|
111 |
+
|
112 |
+
var revId = $(this).parent().attr("id").substring(20);
|
113 |
+
|
114 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_get_revision", mode: hm_custom_css_js_mode, rev: revId})
|
115 |
+
.done(function(data) {
|
116 |
+
if (data.success) {
|
117 |
+
hm_custom_code_editor.doc.setValue(data.data.content);
|
118 |
+
hm_custom_css_js_rev = data.data.id;
|
119 |
+
$('#hm_custom_css_js_revisions .active').removeClass('active');
|
120 |
+
$('#hm_custom_css_js_rev' + hm_custom_css_js_rev).addClass('active');
|
121 |
+
$(".hm-custom-css-js-save-btn").html("Saved").prop("disabled", true);
|
122 |
+
if (hm_custom_css_js_rev == hm_custom_css_js_published_rev)
|
123 |
+
$(".hm-custom-css-js-publish-btn").html("Published").prop("disabled", true);
|
124 |
+
hm_custom_code_editor_has_changes = false;
|
125 |
+
} else {
|
126 |
+
alert("Error while loading. Please try again.");
|
127 |
+
}
|
128 |
+
})
|
129 |
+
.fail(function() {
|
130 |
+
alert("Error while loading. Please try again.");
|
131 |
+
});
|
132 |
+
});
|
133 |
+
|
134 |
+
$("#hm_custom_css_js_revisions").on("click", "li > a.del-rev", function(ev) {
|
135 |
+
|
136 |
+
var revId = $(this).parent().attr("id").substring(20);
|
137 |
+
|
138 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_delete_revision", mode: hm_custom_css_js_mode, rev: revId})
|
139 |
+
.done(function(data) {
|
140 |
+
if (data.success) {
|
141 |
+
hm_custom_css_js_get_revisions();
|
142 |
+
} else {
|
143 |
+
alert("Error while deleting. Please try again.");
|
144 |
+
}
|
145 |
+
})
|
146 |
+
.fail(function() {
|
147 |
+
alert("Error while deleting. Please try again.");
|
148 |
+
});
|
149 |
+
});
|
150 |
+
|
151 |
+
function hm_custom_css_js_get_revisions() {
|
152 |
+
$.post(ajaxurl, {action: "hm_custom_css_js_get_revisions", mode: hm_custom_css_js_mode, })
|
153 |
+
.done(function(data) {
|
154 |
+
if (data.success) {
|
155 |
+
$("#hm_custom_css_js_revisions").empty();
|
156 |
+
if (data.data.length == 0) {
|
157 |
+
$("#hm_custom_css_js_revisions").append("<li>None</li>");
|
158 |
+
} else {
|
159 |
+
for (var i = 0; i < data.data.length; ++i) {
|
160 |
+
$("#hm_custom_css_js_revisions").append("<li id=\"hm_custom_css_js_rev" + data.data[i].id + "\"><a class=\"view-rev\" href=\"javascript:void(0);\">" + data.data[i].rev_date + "</a>" + (data.data[i].published ? " [published]" : " <a class=\"del-rev\" href=\"javascript:void(0);\">[delete]</a>") + "</li>");
|
161 |
+
if (data.data[i].published)
|
162 |
+
hm_custom_css_js_published_rev = data.data[i].id;
|
163 |
+
}
|
164 |
+
if (hm_custom_css_js_rev == 0) {
|
165 |
+
$("#hm_custom_css_js_revisions > li:first-child > a.view-rev").click();
|
166 |
+
} else {
|
167 |
+
$('#hm_custom_css_js_rev' + hm_custom_css_js_rev).addClass('active');
|
168 |
+
}
|
169 |
+
}
|
170 |
+
}
|
171 |
+
});
|
172 |
+
}
|
173 |
+
hm_custom_css_js_get_revisions();
|
174 |
+
|
175 |
+
});
|
minify/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright (c) 2012 Matthias Mullie
|
2 |
+
|
3 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4 |
+
this software and associated documentation files (the "Software"), to deal in
|
5 |
+
the Software without restriction, including without limitation the rights to
|
6 |
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7 |
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8 |
+
subject to the following conditions:
|
9 |
+
|
10 |
+
The above copyright notice and this permission notice shall be included in all
|
11 |
+
copies or substantial portions of the Software.
|
12 |
+
|
13 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15 |
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16 |
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17 |
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18 |
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
minify/data/js/keywords_after.txt
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
in
|
2 |
+
public
|
3 |
+
extends
|
4 |
+
private
|
5 |
+
protected
|
6 |
+
implements
|
7 |
+
instanceof
|
minify/data/js/keywords_before.txt
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
do
|
2 |
+
in
|
3 |
+
let
|
4 |
+
new
|
5 |
+
var
|
6 |
+
case
|
7 |
+
else
|
8 |
+
enum
|
9 |
+
void
|
10 |
+
with
|
11 |
+
class
|
12 |
+
const
|
13 |
+
yield
|
14 |
+
delete
|
15 |
+
export
|
16 |
+
import
|
17 |
+
public
|
18 |
+
static
|
19 |
+
typeof
|
20 |
+
extends
|
21 |
+
package
|
22 |
+
private
|
23 |
+
continue
|
24 |
+
function
|
25 |
+
protected
|
26 |
+
implements
|
27 |
+
instanceof
|
minify/data/js/keywords_reserved.txt
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
do
|
2 |
+
if
|
3 |
+
in
|
4 |
+
for
|
5 |
+
let
|
6 |
+
new
|
7 |
+
try
|
8 |
+
var
|
9 |
+
case
|
10 |
+
else
|
11 |
+
enum
|
12 |
+
eval
|
13 |
+
null
|
14 |
+
this
|
15 |
+
true
|
16 |
+
void
|
17 |
+
with
|
18 |
+
break
|
19 |
+
catch
|
20 |
+
class
|
21 |
+
const
|
22 |
+
false
|
23 |
+
super
|
24 |
+
throw
|
25 |
+
while
|
26 |
+
yield
|
27 |
+
delete
|
28 |
+
export
|
29 |
+
import
|
30 |
+
public
|
31 |
+
return
|
32 |
+
static
|
33 |
+
switch
|
34 |
+
typeof
|
35 |
+
default
|
36 |
+
extends
|
37 |
+
finally
|
38 |
+
package
|
39 |
+
private
|
40 |
+
continue
|
41 |
+
debugger
|
42 |
+
function
|
43 |
+
arguments
|
44 |
+
interface
|
45 |
+
protected
|
46 |
+
implements
|
47 |
+
instanceof
|
minify/data/js/operators_after.txt
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
+
|
2 |
+
-
|
3 |
+
*
|
4 |
+
/
|
5 |
+
%
|
6 |
+
=
|
7 |
+
+=
|
8 |
+
-=
|
9 |
+
*=
|
10 |
+
/=
|
11 |
+
%=
|
12 |
+
<<=
|
13 |
+
>>=
|
14 |
+
>>>=
|
15 |
+
&=
|
16 |
+
^=
|
17 |
+
|=
|
18 |
+
&
|
19 |
+
|
|
20 |
+
^
|
21 |
+
~
|
22 |
+
<<
|
23 |
+
>>
|
24 |
+
>>>
|
25 |
+
==
|
26 |
+
===
|
27 |
+
!=
|
28 |
+
!==
|
29 |
+
>
|
30 |
+
<
|
31 |
+
>=
|
32 |
+
<=
|
33 |
+
&&
|
34 |
+
||
|
35 |
+
.
|
36 |
+
[
|
37 |
+
]
|
38 |
+
?
|
39 |
+
:
|
40 |
+
,
|
41 |
+
;
|
42 |
+
(
|
43 |
+
)
|
44 |
+
{
|
45 |
+
}
|
minify/data/js/operators_before.txt
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
+
|
2 |
+
-
|
3 |
+
*
|
4 |
+
/
|
5 |
+
%
|
6 |
+
=
|
7 |
+
+=
|
8 |
+
-=
|
9 |
+
*=
|
10 |
+
/=
|
11 |
+
%=
|
12 |
+
<<=
|
13 |
+
>>=
|
14 |
+
>>>=
|
15 |
+
&=
|
16 |
+
^=
|
17 |
+
|=
|
18 |
+
&
|
19 |
+
|
|
20 |
+
^
|
21 |
+
~
|
22 |
+
<<
|
23 |
+
>>
|
24 |
+
>>>
|
25 |
+
==
|
26 |
+
===
|
27 |
+
!=
|
28 |
+
!==
|
29 |
+
>
|
30 |
+
<
|
31 |
+
>=
|
32 |
+
<=
|
33 |
+
&&
|
34 |
+
||
|
35 |
+
!
|
36 |
+
.
|
37 |
+
[
|
38 |
+
?
|
39 |
+
:
|
40 |
+
,
|
41 |
+
;
|
42 |
+
(
|
43 |
+
{
|
minify/src/CSS.php
ADDED
@@ -0,0 +1,573 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MatthiasMullie\Minify;
|
4 |
+
|
5 |
+
use MatthiasMullie\PathConverter\Converter;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* CSS minifier.
|
9 |
+
*
|
10 |
+
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
11 |
+
*
|
12 |
+
* @author Matthias Mullie <minify@mullie.eu>
|
13 |
+
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
14 |
+
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
15 |
+
* @license MIT License
|
16 |
+
*/
|
17 |
+
class CSS extends Minify
|
18 |
+
{
|
19 |
+
/**
|
20 |
+
* @var int
|
21 |
+
*/
|
22 |
+
protected $maxImportSize = 5;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @var string[]
|
26 |
+
*/
|
27 |
+
protected $importExtensions = array(
|
28 |
+
'gif' => 'data:image/gif',
|
29 |
+
'png' => 'data:image/png',
|
30 |
+
'jpe' => 'data:image/jpeg',
|
31 |
+
'jpg' => 'data:image/jpeg',
|
32 |
+
'jpeg' => 'data:image/jpeg',
|
33 |
+
'svg' => 'data:image/svg+xml',
|
34 |
+
'woff' => 'data:application/x-font-woff',
|
35 |
+
'tif' => 'image/tiff',
|
36 |
+
'tiff' => 'image/tiff',
|
37 |
+
'xbm' => 'image/x-xbitmap',
|
38 |
+
);
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Set the maximum size if files to be imported.
|
42 |
+
*
|
43 |
+
* Files larger than this size (in kB) will not be imported into the CSS.
|
44 |
+
* Importing files into the CSS as data-uri will save you some connections,
|
45 |
+
* but we should only import relatively small decorative images so that our
|
46 |
+
* CSS file doesn't get too bulky.
|
47 |
+
*
|
48 |
+
* @param int $size Size in kB
|
49 |
+
*/
|
50 |
+
public function setMaxImportSize($size)
|
51 |
+
{
|
52 |
+
$this->maxImportSize = $size;
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Set the type of extensions to be imported into the CSS (to save network
|
57 |
+
* connections).
|
58 |
+
* Keys of the array should be the file extensions & respective values
|
59 |
+
* should be the data type.
|
60 |
+
*
|
61 |
+
* @param string[] $extensions Array of file extensions
|
62 |
+
*/
|
63 |
+
public function setImportExtensions(array $extensions)
|
64 |
+
{
|
65 |
+
$this->importExtensions = $extensions;
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Move any import statements to the top.
|
70 |
+
*
|
71 |
+
* @param $content string Nearly finished CSS content
|
72 |
+
*
|
73 |
+
* @return string
|
74 |
+
*/
|
75 |
+
protected function moveImportsToTop($content)
|
76 |
+
{
|
77 |
+
if (preg_match_all('/@import[^;]+;/', $content, $matches)) {
|
78 |
+
|
79 |
+
// remove from content
|
80 |
+
foreach ($matches[0] as $import) {
|
81 |
+
$content = str_replace($import, '', $content);
|
82 |
+
}
|
83 |
+
|
84 |
+
// add to top
|
85 |
+
$content = implode('', $matches[0]).$content;
|
86 |
+
};
|
87 |
+
|
88 |
+
return $content;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Combine CSS from import statements.
|
93 |
+
*
|
94 |
+
* @import's will be loaded and their content merged into the original file,
|
95 |
+
* to save HTTP requests.
|
96 |
+
*
|
97 |
+
* @param string $source The file to combine imports for.
|
98 |
+
* @param string $content The CSS content to combine imports for.
|
99 |
+
*
|
100 |
+
* @return string
|
101 |
+
*/
|
102 |
+
protected function combineImports($source, $content)
|
103 |
+
{
|
104 |
+
$importRegexes = array(
|
105 |
+
// @import url(xxx)
|
106 |
+
'/
|
107 |
+
# import statement
|
108 |
+
@import
|
109 |
+
|
110 |
+
# whitespace
|
111 |
+
\s+
|
112 |
+
|
113 |
+
# open url()
|
114 |
+
url\(
|
115 |
+
|
116 |
+
# (optional) open path enclosure
|
117 |
+
(?P<quotes>["\']?)
|
118 |
+
|
119 |
+
# fetch path
|
120 |
+
(?P<path>
|
121 |
+
|
122 |
+
# do not fetch data uris or external sources
|
123 |
+
(?!(
|
124 |
+
["\']?
|
125 |
+
(data|https?):
|
126 |
+
))
|
127 |
+
|
128 |
+
.+?
|
129 |
+
)
|
130 |
+
|
131 |
+
# (optional) close path enclosure
|
132 |
+
(?P=quotes)
|
133 |
+
|
134 |
+
# close url()
|
135 |
+
\)
|
136 |
+
|
137 |
+
# (optional) trailing whitespace
|
138 |
+
\s*
|
139 |
+
|
140 |
+
# (optional) media statement(s)
|
141 |
+
(?P<media>[^;]*)
|
142 |
+
|
143 |
+
# (optional) trailing whitespace
|
144 |
+
\s*
|
145 |
+
|
146 |
+
# (optional) closing semi-colon
|
147 |
+
;?
|
148 |
+
|
149 |
+
/ix',
|
150 |
+
|
151 |
+
// @import 'xxx'
|
152 |
+
'/
|
153 |
+
|
154 |
+
# import statement
|
155 |
+
@import
|
156 |
+
|
157 |
+
# whitespace
|
158 |
+
\s+
|
159 |
+
|
160 |
+
# open path enclosure
|
161 |
+
(?P<quotes>["\'])
|
162 |
+
|
163 |
+
# fetch path
|
164 |
+
(?P<path>
|
165 |
+
|
166 |
+
# do not fetch data uris or external sources
|
167 |
+
(?!(
|
168 |
+
["\']?
|
169 |
+
(data|https?):
|
170 |
+
))
|
171 |
+
|
172 |
+
.+?
|
173 |
+
)
|
174 |
+
|
175 |
+
# close path enclosure
|
176 |
+
(?P=quotes)
|
177 |
+
|
178 |
+
# (optional) trailing whitespace
|
179 |
+
\s*
|
180 |
+
|
181 |
+
# (optional) media statement(s)
|
182 |
+
(?P<media>[^;]*)
|
183 |
+
|
184 |
+
# (optional) trailing whitespace
|
185 |
+
\s*
|
186 |
+
|
187 |
+
# (optional) closing semi-colon
|
188 |
+
;?
|
189 |
+
|
190 |
+
/ix',
|
191 |
+
);
|
192 |
+
|
193 |
+
// find all relative imports in css
|
194 |
+
$matches = array();
|
195 |
+
foreach ($importRegexes as $importRegex) {
|
196 |
+
if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
197 |
+
$matches = array_merge($matches, $regexMatches);
|
198 |
+
}
|
199 |
+
}
|
200 |
+
|
201 |
+
$search = array();
|
202 |
+
$replace = array();
|
203 |
+
|
204 |
+
// loop the matches
|
205 |
+
foreach ($matches as $match) {
|
206 |
+
// get the path for the file that will be imported
|
207 |
+
$importPath = dirname($source).'/'.$match['path'];
|
208 |
+
|
209 |
+
// only replace the import with the content if we can grab the
|
210 |
+
// content of the file
|
211 |
+
if (file_exists($importPath) && is_file($importPath)) {
|
212 |
+
// grab referenced file & minify it (which may include importing
|
213 |
+
// yet other @import statements recursively)
|
214 |
+
$minifier = new static($importPath);
|
215 |
+
$importContent = $minifier->execute($source);
|
216 |
+
|
217 |
+
// check if this is only valid for certain media
|
218 |
+
if ($match['media']) {
|
219 |
+
$importContent = '@media '.$match['media'].'{'.$importContent.'}';
|
220 |
+
}
|
221 |
+
|
222 |
+
// add to replacement array
|
223 |
+
$search[] = $match[0];
|
224 |
+
$replace[] = $importContent;
|
225 |
+
}
|
226 |
+
}
|
227 |
+
|
228 |
+
// replace the import statements
|
229 |
+
$content = str_replace($search, $replace, $content);
|
230 |
+
|
231 |
+
return $content;
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Import files into the CSS, base64-ized.
|
236 |
+
*
|
237 |
+
* @url(image.jpg) images will be loaded and their content merged into the
|
238 |
+
* original file, to save HTTP requests.
|
239 |
+
*
|
240 |
+
* @param string $source The file to import files for.
|
241 |
+
* @param string $content The CSS content to import files for.
|
242 |
+
*
|
243 |
+
* @return string
|
244 |
+
*/
|
245 |
+
protected function importFiles($source, $content)
|
246 |
+
{
|
247 |
+
$extensions = array_keys($this->importExtensions);
|
248 |
+
$regex = '/url\((["\']?)((?!["\']?data:).*?\.('.implode('|', $extensions).'))\\1\)/i';
|
249 |
+
if ($extensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
|
250 |
+
$search = array();
|
251 |
+
$replace = array();
|
252 |
+
|
253 |
+
// loop the matches
|
254 |
+
foreach ($matches as $match) {
|
255 |
+
// get the path for the file that will be imported
|
256 |
+
$path = $match[2];
|
257 |
+
$path = dirname($source).'/'.$path;
|
258 |
+
$extension = $match[3];
|
259 |
+
|
260 |
+
// only replace the import with the content if we're able to get
|
261 |
+
// the content of the file, and it's relatively small
|
262 |
+
$import = file_exists($path);
|
263 |
+
$import = $import && is_file($path);
|
264 |
+
$import = $import && filesize($path) <= $this->maxImportSize * 1024;
|
265 |
+
if (!$import) {
|
266 |
+
continue;
|
267 |
+
}
|
268 |
+
|
269 |
+
// grab content && base64-ize
|
270 |
+
$importContent = $this->load($path);
|
271 |
+
$importContent = base64_encode($importContent);
|
272 |
+
|
273 |
+
// build replacement
|
274 |
+
$search[] = $match[0];
|
275 |
+
$replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
|
276 |
+
}
|
277 |
+
|
278 |
+
// replace the import statements
|
279 |
+
$content = str_replace($search, $replace, $content);
|
280 |
+
}
|
281 |
+
|
282 |
+
return $content;
|
283 |
+
}
|
284 |
+
|
285 |
+
/**
|
286 |
+
* Minify the data.
|
287 |
+
* Perform CSS optimizations.
|
288 |
+
*
|
289 |
+
* @param string[optional] $path Path to write the data to.
|
290 |
+
*
|
291 |
+
* @return string The minified data.
|
292 |
+
*/
|
293 |
+
public function execute($path = null)
|
294 |
+
{
|
295 |
+
$content = '';
|
296 |
+
|
297 |
+
// loop files
|
298 |
+
foreach ($this->data as $source => $css) {
|
299 |
+
/*
|
300 |
+
* Let's first take out strings & comments, since we can't just remove
|
301 |
+
* whitespace anywhere. If whitespace occurs inside a string, we should
|
302 |
+
* leave it alone. E.g.:
|
303 |
+
* p { content: "a test" }
|
304 |
+
*/
|
305 |
+
$this->extractStrings();
|
306 |
+
$this->stripComments();
|
307 |
+
$css = $this->replace($css);
|
308 |
+
|
309 |
+
$css = $this->stripWhitespace($css);
|
310 |
+
$css = $this->shortenHex($css);
|
311 |
+
$css = $this->shortenZeroes($css);
|
312 |
+
$css = $this->stripEmptyTags($css);
|
313 |
+
|
314 |
+
// restore the string we've extracted earlier
|
315 |
+
$css = $this->restoreExtractedData($css);
|
316 |
+
|
317 |
+
/*
|
318 |
+
* If we'll save to a new path, we'll have to fix the relative paths
|
319 |
+
* to be relative no longer to the source file, but to the new path.
|
320 |
+
* If we don't write to a file, fall back to same path so no
|
321 |
+
* conversion happens (because we still want it to go through most
|
322 |
+
* of the move code...)
|
323 |
+
*/
|
324 |
+
$source = $source ?: '';
|
325 |
+
$converter = new Converter($source, $path ?: $source);
|
326 |
+
$css = $this->move($converter, $css);
|
327 |
+
|
328 |
+
// if no target path is given, relative paths were not converted, so
|
329 |
+
// they'll still be relative to the source file then
|
330 |
+
$css = $this->importFiles($path ?: $source, $css);
|
331 |
+
$css = $this->combineImports($path ?: $source, $css);
|
332 |
+
|
333 |
+
// combine css
|
334 |
+
$content .= $css;
|
335 |
+
}
|
336 |
+
|
337 |
+
$content = $this->moveImportsToTop($content);
|
338 |
+
|
339 |
+
return $content;
|
340 |
+
}
|
341 |
+
|
342 |
+
/**
|
343 |
+
* Moving a css file should update all relative urls.
|
344 |
+
* Relative references (e.g. ../images/image.gif) in a certain css file,
|
345 |
+
* will have to be updated when a file is being saved at another location
|
346 |
+
* (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
|
347 |
+
*
|
348 |
+
* @param Converter $converter Relative path converter
|
349 |
+
* @param string $content The CSS content to update relative urls for.
|
350 |
+
*
|
351 |
+
* @return string
|
352 |
+
*/
|
353 |
+
protected function move(Converter $converter, $content)
|
354 |
+
{
|
355 |
+
/*
|
356 |
+
* Relative path references will usually be enclosed by url(). @import
|
357 |
+
* is an exception, where url() is not necessary around the path (but is
|
358 |
+
* allowed).
|
359 |
+
* This *could* be 1 regular expression, where both regular expressions
|
360 |
+
* in this array are on different sides of a |. But we're using named
|
361 |
+
* patterns in both regexes, the same name on both regexes. This is only
|
362 |
+
* possible with a (?J) modifier, but that only works after a fairly
|
363 |
+
* recent PCRE version. That's why I'm doing 2 separate regular
|
364 |
+
* expressions & combining the matches after executing of both.
|
365 |
+
*/
|
366 |
+
$relativeRegexes = array(
|
367 |
+
// url(xxx)
|
368 |
+
'/
|
369 |
+
# open url()
|
370 |
+
url\(
|
371 |
+
|
372 |
+
\s*
|
373 |
+
|
374 |
+
# open path enclosure
|
375 |
+
(?P<quotes>["\'])?
|
376 |
+
|
377 |
+
# fetch path
|
378 |
+
(?P<path>
|
379 |
+
|
380 |
+
# do not fetch data uris or external sources
|
381 |
+
(?!(
|
382 |
+
\s?
|
383 |
+
["\']?
|
384 |
+
(data|https?):
|
385 |
+
))
|
386 |
+
|
387 |
+
.+?
|
388 |
+
)
|
389 |
+
|
390 |
+
# close path enclosure
|
391 |
+
(?(quotes)(?P=quotes))
|
392 |
+
|
393 |
+
\s*
|
394 |
+
|
395 |
+
# close url()
|
396 |
+
\)
|
397 |
+
|
398 |
+
/ix',
|
399 |
+
|
400 |
+
// @import "xxx"
|
401 |
+
'/
|
402 |
+
# import statement
|
403 |
+
@import
|
404 |
+
|
405 |
+
# whitespace
|
406 |
+
\s+
|
407 |
+
|
408 |
+
# we don\'t have to check for @import url(), because the
|
409 |
+
# condition above will already catch these
|
410 |
+
|
411 |
+
# open path enclosure
|
412 |
+
(?P<quotes>["\'])
|
413 |
+
|
414 |
+
# fetch path
|
415 |
+
(?P<path>
|
416 |
+
|
417 |
+
# do not fetch data uris or external sources
|
418 |
+
(?!(
|
419 |
+
["\']?
|
420 |
+
(data|https?):
|
421 |
+
))
|
422 |
+
|
423 |
+
.+?
|
424 |
+
)
|
425 |
+
|
426 |
+
# close path enclosure
|
427 |
+
(?P=quotes)
|
428 |
+
|
429 |
+
/ix',
|
430 |
+
);
|
431 |
+
|
432 |
+
// find all relative urls in css
|
433 |
+
$matches = array();
|
434 |
+
foreach ($relativeRegexes as $relativeRegex) {
|
435 |
+
if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
|
436 |
+
$matches = array_merge($matches, $regexMatches);
|
437 |
+
}
|
438 |
+
}
|
439 |
+
|
440 |
+
$search = array();
|
441 |
+
$replace = array();
|
442 |
+
|
443 |
+
// loop all urls
|
444 |
+
foreach ($matches as $match) {
|
445 |
+
// determine if it's a url() or an @import match
|
446 |
+
$type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
|
447 |
+
|
448 |
+
// fix relative url
|
449 |
+
$url = $converter->convert($match['path']);
|
450 |
+
|
451 |
+
// build replacement
|
452 |
+
$search[] = $match[0];
|
453 |
+
if ($type == 'url') {
|
454 |
+
$replace[] = 'url('.$url.')';
|
455 |
+
} elseif ($type == 'import') {
|
456 |
+
$replace[] = '@import "'.$url.'"';
|
457 |
+
}
|
458 |
+
}
|
459 |
+
|
460 |
+
// replace urls
|
461 |
+
$content = str_replace($search, $replace, $content);
|
462 |
+
|
463 |
+
return $content;
|
464 |
+
}
|
465 |
+
|
466 |
+
/**
|
467 |
+
* Shorthand hex color codes.
|
468 |
+
* #FF0000 -> #F00.
|
469 |
+
*
|
470 |
+
* @param string $content The CSS content to shorten the hex color codes for.
|
471 |
+
*
|
472 |
+
* @return string
|
473 |
+
*/
|
474 |
+
protected function shortenHex($content)
|
475 |
+
{
|
476 |
+
$content = preg_replace('/(?<![\'"])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?![\'"])/i', '#$1$2$3', $content);
|
477 |
+
|
478 |
+
return $content;
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* Shorthand 0 values to plain 0, instead of e.g. -0em.
|
483 |
+
*
|
484 |
+
* @param string $content The CSS content to shorten the zero values for.
|
485 |
+
*
|
486 |
+
* @return string
|
487 |
+
*/
|
488 |
+
protected function shortenZeroes($content)
|
489 |
+
{
|
490 |
+
// reusable bits of code throughout these regexes:
|
491 |
+
// before & after are used to make sure we don't match lose unintended
|
492 |
+
// 0-like values (e.g. in #000, or in http://url/1.0)
|
493 |
+
// units can be stripped from 0 values, or used to recognize non 0
|
494 |
+
// values (where wa may be able to strip a .0 suffix)
|
495 |
+
$before = '(?<=[:(, ])';
|
496 |
+
$after = '(?=[ ,);}])';
|
497 |
+
$units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
|
498 |
+
|
499 |
+
// strip units after zeroes (0px -> 0)
|
500 |
+
// NOTE: it should be safe to remove all units for a 0 value, but in
|
501 |
+
// practice, Webkit (especially Safari) seems to stumble over at least
|
502 |
+
// 0%, potentially other units as well. Only stripping 'px' for now.
|
503 |
+
// @see https://github.com/matthiasmullie/minify/issues/60
|
504 |
+
$content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
|
505 |
+
|
506 |
+
// strip 0-digits (.0 -> 0)
|
507 |
+
$content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
|
508 |
+
// strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
|
509 |
+
$content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
|
510 |
+
// strip trailing 0: 50.00 -> 50, 50.00px -> 50px
|
511 |
+
$content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
|
512 |
+
// strip leading 0: 0.1 -> .1, 01.1 -> 1.1
|
513 |
+
$content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
|
514 |
+
|
515 |
+
// strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
|
516 |
+
$content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
|
517 |
+
|
518 |
+
return $content;
|
519 |
+
}
|
520 |
+
|
521 |
+
/**
|
522 |
+
* Strip comments from source code.
|
523 |
+
*
|
524 |
+
* @param string $content
|
525 |
+
* @return string
|
526 |
+
*/
|
527 |
+
protected function stripEmptyTags($content)
|
528 |
+
{
|
529 |
+
return preg_replace('/(^|\})[^\{]+\{\s*\}/', '\\1', $content);
|
530 |
+
}
|
531 |
+
|
532 |
+
/**
|
533 |
+
* Strip comments from source code.
|
534 |
+
*/
|
535 |
+
protected function stripComments()
|
536 |
+
{
|
537 |
+
$this->registerPattern('/\/\*.*?\*\//s', '');
|
538 |
+
}
|
539 |
+
|
540 |
+
/**
|
541 |
+
* Strip whitespace.
|
542 |
+
*
|
543 |
+
* @param string $content The CSS content to strip the whitespace for.
|
544 |
+
*
|
545 |
+
* @return string
|
546 |
+
*/
|
547 |
+
protected function stripWhitespace($content)
|
548 |
+
{
|
549 |
+
// remove leading & trailing whitespace
|
550 |
+
$content = preg_replace('/^\s*/m', '', $content);
|
551 |
+
$content = preg_replace('/\s*$/m', '', $content);
|
552 |
+
|
553 |
+
// replace newlines with a single space
|
554 |
+
$content = preg_replace('/\s+/', ' ', $content);
|
555 |
+
|
556 |
+
// remove whitespace around meta characters
|
557 |
+
// inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
|
558 |
+
$content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
|
559 |
+
$content = preg_replace('/([\[(:])\s+/', '$1', $content);
|
560 |
+
$content = preg_replace('/\s+([\]\)])/', '$1', $content);
|
561 |
+
$content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
|
562 |
+
|
563 |
+
// whitespace around + and - can only be stripped in selectors, like
|
564 |
+
// :nth-child(3+2n), not in things like calc(3px + 2px) or shorthands
|
565 |
+
// like 3px -2px
|
566 |
+
$content = preg_replace('/\s*([+-])\s*(?=[^}]*{)/', '$1', $content);
|
567 |
+
|
568 |
+
// remove semicolon/whitespace followed by closing bracket
|
569 |
+
$content = str_replace(';}', '}', $content);
|
570 |
+
|
571 |
+
return trim($content);
|
572 |
+
}
|
573 |
+
}
|
minify/src/Converter.php
ADDED
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MatthiasMullie\PathConverter;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Convert paths relative from 1 file to another.
|
7 |
+
*
|
8 |
+
* E.g.
|
9 |
+
* ../../images/icon.jpg relative to /css/imports/icons.css
|
10 |
+
* becomes
|
11 |
+
* ../images/icon.jpg relative to /css/minified.css
|
12 |
+
*
|
13 |
+
* Please report bugs on https://github.com/matthiasmullie/path-converter/issues
|
14 |
+
*
|
15 |
+
* @author Matthias Mullie <pathconverter@mullie.eu>
|
16 |
+
* @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved.
|
17 |
+
* @license MIT License
|
18 |
+
*/
|
19 |
+
class Converter
|
20 |
+
{
|
21 |
+
/**
|
22 |
+
* @var string
|
23 |
+
*/
|
24 |
+
protected $from;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
protected $to;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @param string $from The original base path (directory, not file!)
|
33 |
+
* @param string $to The new base path (directory, not file!)
|
34 |
+
*/
|
35 |
+
public function __construct($from, $to)
|
36 |
+
{
|
37 |
+
$shared = $this->shared($from, $to);
|
38 |
+
if ($shared === '') {
|
39 |
+
// when both paths have nothing in common, one of them is probably
|
40 |
+
// absolute while the other is relative
|
41 |
+
$cwd = getcwd();
|
42 |
+
$from = strpos($from, $cwd) === 0 ? $from : $cwd.'/'.$from;
|
43 |
+
$to = strpos($to, $cwd) === 0 ? $to : $cwd.'/'.$to;
|
44 |
+
|
45 |
+
// or traveling the tree via `..`
|
46 |
+
// attempt to resolve path, or assume it's fine if it doesn't exist
|
47 |
+
$from = realpath($from) ?: $from;
|
48 |
+
$to = realpath($to) ?: $to;
|
49 |
+
}
|
50 |
+
|
51 |
+
$from = $this->normalize($from);
|
52 |
+
$to = $this->normalize($to);
|
53 |
+
|
54 |
+
$from = $this->dirname($from);
|
55 |
+
$to = $this->dirname($to);
|
56 |
+
|
57 |
+
$this->from = $from;
|
58 |
+
$this->to = $to;
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Normalize path.
|
63 |
+
*
|
64 |
+
* @param string $path
|
65 |
+
*
|
66 |
+
* @return string
|
67 |
+
*/
|
68 |
+
protected function normalize($path)
|
69 |
+
{
|
70 |
+
// deal with different operating systems' directory structure
|
71 |
+
$path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
|
72 |
+
|
73 |
+
/*
|
74 |
+
* Example:
|
75 |
+
* /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
|
76 |
+
* to
|
77 |
+
* /home/forkcms/frontend/core/layout/images/img.gif
|
78 |
+
*/
|
79 |
+
do {
|
80 |
+
$path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
|
81 |
+
} while ($count);
|
82 |
+
|
83 |
+
return $path;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Figure out the shared path of 2 locations.
|
88 |
+
*
|
89 |
+
* Example:
|
90 |
+
* /home/forkcms/frontend/core/layout/images/img.gif
|
91 |
+
* and
|
92 |
+
* /home/forkcms/frontend/cache/minified_css
|
93 |
+
* share
|
94 |
+
* /home/forkcms/frontend
|
95 |
+
*
|
96 |
+
* @param string $path1
|
97 |
+
* @param string $path2
|
98 |
+
*
|
99 |
+
* @return string
|
100 |
+
*/
|
101 |
+
protected function shared($path1, $path2)
|
102 |
+
{
|
103 |
+
// $path could theoretically be empty (e.g. no path is given), in which
|
104 |
+
// case it shouldn't expand to array(''), which would compare to one's
|
105 |
+
// root /
|
106 |
+
$path1 = $path1 ? explode('/', $path1) : array();
|
107 |
+
$path2 = $path2 ? explode('/', $path2) : array();
|
108 |
+
|
109 |
+
$shared = array();
|
110 |
+
|
111 |
+
// compare paths & strip identical ancestors
|
112 |
+
foreach ($path1 as $i => $chunk) {
|
113 |
+
if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
|
114 |
+
$shared[] = $chunk;
|
115 |
+
} else {
|
116 |
+
break;
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
return implode('/', $shared);
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Convert paths relative from 1 file to another.
|
125 |
+
*
|
126 |
+
* E.g.
|
127 |
+
* ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
|
128 |
+
* should become:
|
129 |
+
* ../../core/layout/images/img.gif relative to
|
130 |
+
* /home/forkcms/frontend/cache/minified_css
|
131 |
+
*
|
132 |
+
* @param string $path The relative path that needs to be converted.
|
133 |
+
*
|
134 |
+
* @return string The new relative path.
|
135 |
+
*/
|
136 |
+
public function convert($path)
|
137 |
+
{
|
138 |
+
// quit early if conversion makes no sense
|
139 |
+
if ($this->from === $this->to) {
|
140 |
+
return $path;
|
141 |
+
}
|
142 |
+
|
143 |
+
$path = $this->normalize($path);
|
144 |
+
// if we're not dealing with a relative path, just return absolute
|
145 |
+
if (strpos($path, '/') === 0) {
|
146 |
+
return $path;
|
147 |
+
}
|
148 |
+
|
149 |
+
// normalize paths
|
150 |
+
$path = $this->normalize($this->from.'/'.$path);
|
151 |
+
|
152 |
+
// strip shared ancestor paths
|
153 |
+
$shared = $this->shared($path, $this->to);
|
154 |
+
$path = mb_substr($path, mb_strlen($shared));
|
155 |
+
$to = mb_substr($this->to, mb_strlen($shared));
|
156 |
+
|
157 |
+
// add .. for every directory that needs to be traversed to new path
|
158 |
+
$to = str_repeat('../', mb_substr_count($to, '/'));
|
159 |
+
|
160 |
+
return $to.ltrim($path, '/');
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Attempt to get the directory name from a path.
|
165 |
+
*
|
166 |
+
* @param string $path
|
167 |
+
*
|
168 |
+
* @return string
|
169 |
+
*/
|
170 |
+
public function dirname($path)
|
171 |
+
{
|
172 |
+
if (is_file($path)) {
|
173 |
+
return dirname($path);
|
174 |
+
}
|
175 |
+
|
176 |
+
if (is_dir($path)) {
|
177 |
+
return rtrim($path, '/');
|
178 |
+
}
|
179 |
+
|
180 |
+
// no known file/dir, start making assumptions
|
181 |
+
|
182 |
+
// ends in / = dir
|
183 |
+
if (mb_substr($path, -1) === '/') {
|
184 |
+
return rtrim($path, '/');
|
185 |
+
}
|
186 |
+
|
187 |
+
// has a dot in the name, likely a file
|
188 |
+
if (preg_match('/.*\..*$/', basename($path)) !== 0) {
|
189 |
+
return dirname($path);
|
190 |
+
}
|
191 |
+
|
192 |
+
// you're on your own here!
|
193 |
+
return $path;
|
194 |
+
}
|
195 |
+
}
|
minify/src/Exception.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MatthiasMullie\Minify;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @author Matthias Mullie <minify@mullie.eu>
|
7 |
+
*/
|
8 |
+
class Exception extends \Exception
|
9 |
+
{
|
10 |
+
}
|
minify/src/JS.php
ADDED
@@ -0,0 +1,480 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MatthiasMullie\Minify;
|
4 |
+
|
5 |
+
/**
|
6 |
+
* JavaScript minifier.
|
7 |
+
*
|
8 |
+
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
9 |
+
*
|
10 |
+
* @author Matthias Mullie <minify@mullie.eu>
|
11 |
+
* @author Tijs Verkoyen <minify@verkoyen.eu>
|
12 |
+
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
13 |
+
* @license MIT License
|
14 |
+
*/
|
15 |
+
class JS extends Minify
|
16 |
+
{
|
17 |
+
/**
|
18 |
+
* Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
|
19 |
+
*
|
20 |
+
* Note that regular expressions using that bit must have the PCRE_UTF8
|
21 |
+
* pattern modifier (/u) set.
|
22 |
+
*
|
23 |
+
* @var string
|
24 |
+
*/
|
25 |
+
const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Full list of JavaScript reserved words.
|
29 |
+
* Will be loaded from /data/js/keywords_reserved.txt.
|
30 |
+
*
|
31 |
+
* @see https://mathiasbynens.be/notes/reserved-keywords
|
32 |
+
*
|
33 |
+
* @var string[]
|
34 |
+
*/
|
35 |
+
protected $keywordsReserved = array();
|
36 |
+
|
37 |
+
/**
|
38 |
+
* List of JavaScript reserved words that accept a <variable, value, ...>
|
39 |
+
* after them. Some end of lines are not the end of a statement, like with
|
40 |
+
* these keywords.
|
41 |
+
*
|
42 |
+
* E.g.: we shouldn't insert a ; after this else
|
43 |
+
* else
|
44 |
+
* console.log('this is quite fine')
|
45 |
+
*
|
46 |
+
* Will be loaded from /data/js/keywords_before.txt
|
47 |
+
*
|
48 |
+
* @var string[]
|
49 |
+
*/
|
50 |
+
protected $keywordsBefore = array();
|
51 |
+
|
52 |
+
/**
|
53 |
+
* List of JavaScript reserved words that accept a <variable, value, ...>
|
54 |
+
* before them. Some end of lines are not the end of a statement, like when
|
55 |
+
* continued by one of these keywords on the newline.
|
56 |
+
*
|
57 |
+
* E.g.: we shouldn't insert a ; before this instanceof
|
58 |
+
* variable
|
59 |
+
* instanceof String
|
60 |
+
*
|
61 |
+
* Will be loaded from /data/js/keywords_after.txt
|
62 |
+
*
|
63 |
+
* @var string[]
|
64 |
+
*/
|
65 |
+
protected $keywordsAfter = array();
|
66 |
+
|
67 |
+
/**
|
68 |
+
* List of JavaScript operators that accept a <variable, value, ...> after
|
69 |
+
* them. Some end of lines are not the end of a statement, like with these
|
70 |
+
* operators.
|
71 |
+
*
|
72 |
+
* Note: Most operators are fine, we've only removed !, ++ and --.
|
73 |
+
* There can't be a newline separating ! and whatever it is negating.
|
74 |
+
* ++ & -- have to be joined with the value they're in-/decrementing.
|
75 |
+
*
|
76 |
+
* Will be loaded from /data/js/operators_before.txt
|
77 |
+
*
|
78 |
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
|
79 |
+
*
|
80 |
+
* @var string[]
|
81 |
+
*/
|
82 |
+
protected $operatorsBefore = array();
|
83 |
+
|
84 |
+
/**
|
85 |
+
* List of JavaScript operators that accept a <variable, value, ...> before
|
86 |
+
* them. Some end of lines are not the end of a statement, like when
|
87 |
+
* continued by one of these operators on the newline.
|
88 |
+
*
|
89 |
+
* Note: Most operators are fine, we've only removed ), ], ++ and --.
|
90 |
+
* ++ & -- have to be joined with the value they're in-/decrementing.
|
91 |
+
* ) & ] are "special" in that they have lots or usecases. () for example
|
92 |
+
* is used for function calls, for grouping, in if () and for (), ...
|
93 |
+
*
|
94 |
+
* Will be loaded from /data/js/operators_after.txt
|
95 |
+
*
|
96 |
+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
|
97 |
+
*
|
98 |
+
* @var string[]
|
99 |
+
*/
|
100 |
+
protected $operatorsAfter = array();
|
101 |
+
|
102 |
+
/**
|
103 |
+
* {@inheritdoc}
|
104 |
+
*/
|
105 |
+
public function __construct()
|
106 |
+
{
|
107 |
+
call_user_func_array(array('parent', '__construct'), func_get_args());
|
108 |
+
|
109 |
+
$dataDir = __DIR__.'/../data/js/';
|
110 |
+
$options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
|
111 |
+
$this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
|
112 |
+
$this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
|
113 |
+
$this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
|
114 |
+
$this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
|
115 |
+
$this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Minify the data.
|
120 |
+
* Perform JS optimizations.
|
121 |
+
*
|
122 |
+
* @param string[optional] $path Path to write the data to.
|
123 |
+
*
|
124 |
+
* @return string The minified data.
|
125 |
+
*/
|
126 |
+
public function execute($path = null)
|
127 |
+
{
|
128 |
+
$content = '';
|
129 |
+
|
130 |
+
// loop files
|
131 |
+
foreach ($this->data as $source => $js) {
|
132 |
+
/*
|
133 |
+
* Combine js: separating the scripts by a ;
|
134 |
+
* I'm also adding a newline: it will be eaten when whitespace is
|
135 |
+
* stripped, but we need to make sure we're not just appending
|
136 |
+
* a new script right after a previous script that ended with a
|
137 |
+
* singe-line comment on the last line (in which case it would also
|
138 |
+
* be seen as part of that comment)
|
139 |
+
*/
|
140 |
+
$content .= $js."\n;";
|
141 |
+
}
|
142 |
+
|
143 |
+
/*
|
144 |
+
* Let's first take out strings, comments and regular expressions.
|
145 |
+
* All of these can contain JS code-like characters, and we should make
|
146 |
+
* sure any further magic ignores anything inside of these.
|
147 |
+
*
|
148 |
+
* Consider this example, where we should not strip any whitespace:
|
149 |
+
* var str = "a test";
|
150 |
+
*
|
151 |
+
* Comments will be removed altogether, strings and regular expressions
|
152 |
+
* will be replaced by placeholder text, which we'll restore later.
|
153 |
+
*/
|
154 |
+
$this->extractStrings('\'"`');
|
155 |
+
$this->stripComments();
|
156 |
+
$this->extractRegex();
|
157 |
+
$content = $this->replace($content);
|
158 |
+
|
159 |
+
$content = $this->stripWhitespace($content);
|
160 |
+
$content = $this->propertyNotation($content);
|
161 |
+
$content = $this->shortenBools($content);
|
162 |
+
|
163 |
+
/*
|
164 |
+
* Earlier, we extracted strings & regular expressions and replaced them
|
165 |
+
* with placeholder text. This will restore them.
|
166 |
+
*/
|
167 |
+
$content = $this->restoreExtractedData($content);
|
168 |
+
|
169 |
+
return $content;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Strip comments from source code.
|
174 |
+
*/
|
175 |
+
protected function stripComments()
|
176 |
+
{
|
177 |
+
// single-line comments
|
178 |
+
$this->registerPattern('/\/\/.*$/m', '');
|
179 |
+
|
180 |
+
// multi-line comments
|
181 |
+
$this->registerPattern('/\/\*.*?\*\//s', '');
|
182 |
+
}
|
183 |
+
|
184 |
+
/**
|
185 |
+
* JS can have /-delimited regular expressions, like: /ab+c/.match(string).
|
186 |
+
*
|
187 |
+
* The content inside the regex can contain characters that may be confused
|
188 |
+
* for JS code: e.g. it could contain whitespace it needs to match & we
|
189 |
+
* don't want to strip whitespace in there.
|
190 |
+
*
|
191 |
+
* The regex can be pretty simple: we don't have to care about comments,
|
192 |
+
* (which also use slashes) because stripComments() will have stripped those
|
193 |
+
* already.
|
194 |
+
*
|
195 |
+
* This method will replace all string content with simple REGEX#
|
196 |
+
* placeholder text, so we've rid all regular expressions from characters
|
197 |
+
* that may be misinterpreted. Original regex content will be saved in
|
198 |
+
* $this->extracted and after doing all other minifying, we can restore the
|
199 |
+
* original content via restoreRegex()
|
200 |
+
*/
|
201 |
+
protected function extractRegex()
|
202 |
+
{
|
203 |
+
// PHP only supports $this inside anonymous functions since 5.4
|
204 |
+
$minifier = $this;
|
205 |
+
$callback = function ($match) use ($minifier) {
|
206 |
+
$count = count($minifier->extracted);
|
207 |
+
$placeholder = '/'.$count.'/';
|
208 |
+
$minifier->extracted[$placeholder] = $match[1];
|
209 |
+
|
210 |
+
return $placeholder;
|
211 |
+
};
|
212 |
+
|
213 |
+
// it's a regex if we can find an opening and (not escaped) closing /,
|
214 |
+
// include \n because it may be there for a reason
|
215 |
+
// (https://github.com/matthiasmullie/minify/issues/56)
|
216 |
+
$pattern = '(\/.*?(?<!\\\\)(\\\\\\\\)*+\/\n?)';
|
217 |
+
|
218 |
+
// / can't be preceded by variable, value, or similar because then
|
219 |
+
// it's going to be division
|
220 |
+
// checking for that is complex, so we'll do inverse:
|
221 |
+
// * at the beginning of the file, it's not division, but regex
|
222 |
+
$this->registerPattern('/^\s*\K'.$pattern.'/', $callback);
|
223 |
+
// * following another operator, it's not division, but regex
|
224 |
+
$operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
|
225 |
+
$operators += $this->getKeywordsForRegex($this->keywordsReserved, '/');
|
226 |
+
$this->registerPattern('/(?:'.implode('|', $operators).')\s*\K'.$pattern.'/', $callback);
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Strip whitespace.
|
231 |
+
*
|
232 |
+
* We won't strip *all* whitespace, but as much as possible. The thing that
|
233 |
+
* we'll preserve are newlines we're unsure about.
|
234 |
+
* JavaScript doesn't require statements to be terminated with a semicolon.
|
235 |
+
* It will automatically fix missing semicolons with ASI (automatic semi-
|
236 |
+
* colon insertion) at the end of line causing errors (without semicolon.)
|
237 |
+
*
|
238 |
+
* Because it's sometimes hard to tell if a newline is part of a statement
|
239 |
+
* that should be terminated or not, we'll just leave some of them alone.
|
240 |
+
*
|
241 |
+
* @param string $content The content to strip the whitespace for.
|
242 |
+
*
|
243 |
+
* @return string
|
244 |
+
*/
|
245 |
+
protected function stripWhitespace($content)
|
246 |
+
{
|
247 |
+
// uniform line endings, make them all line feed
|
248 |
+
$content = str_replace(array("\r\n", "\r"), "\n", $content);
|
249 |
+
|
250 |
+
// collapse all non-line feed whitespace into a single space
|
251 |
+
$content = preg_replace('/[^\S\n]+/', ' ', $content);
|
252 |
+
|
253 |
+
// strip leading & trailing whitespace
|
254 |
+
$content = str_replace(array(" \n", "\n "), "\n", $content);
|
255 |
+
|
256 |
+
// collapse consecutive line feeds into just 1
|
257 |
+
$content = preg_replace('/\n+/', "\n", $content);
|
258 |
+
|
259 |
+
$before = $this->getOperatorsForRegex($this->operatorsBefore, '/');
|
260 |
+
$after = $this->getOperatorsForRegex($this->operatorsAfter, '/');
|
261 |
+
$operators = $before + $after;
|
262 |
+
|
263 |
+
// strip whitespace that ends in (or next line begin with) an operator
|
264 |
+
// that allows statements to be broken up over multiple lines
|
265 |
+
unset($before['+'], $before['-'], $after['+'], $after['-']);
|
266 |
+
$content = preg_replace('/('.implode('|', $before).')\s+/', '\\1', $content);
|
267 |
+
$content = preg_replace('/\s+('.implode('|', $after).')/', '\\1', $content);
|
268 |
+
|
269 |
+
// make sure + and - can't be mistaken for, or joined into ++ and --
|
270 |
+
$content = preg_replace('/(?<![\+\-])\s*([\+\-])(?![\+\-])/', '\\1', $content);
|
271 |
+
$content = preg_replace('/(?<![\+\-])([\+\-])\s*(?![\+\-])/', '\\1', $content);
|
272 |
+
|
273 |
+
/*
|
274 |
+
* We didn't strip whitespace after a couple of operators because they
|
275 |
+
* could be used in different contexts and we can't be sure it's ok to
|
276 |
+
* strip the newlines. However, we can safely strip any non-line feed
|
277 |
+
* whitespace that follows them.
|
278 |
+
*/
|
279 |
+
$content = preg_replace('/([\}\)\]])[^\S\n]+(?!'.implode('|', $operators).')/', '\\1', $content);
|
280 |
+
|
281 |
+
// collapse whitespace around reserved words into single space
|
282 |
+
$before = $this->getKeywordsForRegex($this->keywordsBefore, '/');
|
283 |
+
$after = $this->getKeywordsForRegex($this->keywordsAfter, '/');
|
284 |
+
$content = preg_replace('/(^|[;\}\s])\K('.implode('|', $before).')\s+/', '\\2 ', $content);
|
285 |
+
$content = preg_replace('/\s+('.implode('|', $after).')(?=([;\{\s]|$))/', ' \\1', $content);
|
286 |
+
|
287 |
+
/*
|
288 |
+
* Get rid of double semicolons, except where they can be used like:
|
289 |
+
* "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
|
290 |
+
* I'll safeguard these double semicolons inside for-loops by
|
291 |
+
* temporarily replacing them with an invalid condition: they won't have
|
292 |
+
* a double semicolon and will be easy to spot to restore afterwards.
|
293 |
+
*/
|
294 |
+
$content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
|
295 |
+
$content = preg_replace('/;+/', ';', $content);
|
296 |
+
$content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
|
297 |
+
|
298 |
+
/*
|
299 |
+
* Next, we'll be removing all semicolons where ASI kicks in.
|
300 |
+
* for-loops however, can have an empty body (ending in only a
|
301 |
+
* semicolon), like: `for(i=1;i<3;i++);`
|
302 |
+
* Here, nothing happens during the loop; it's just used to keep
|
303 |
+
* increasing `i`. With that ; omitted, the next line would be expected
|
304 |
+
* to be the for-loop's body...
|
305 |
+
* I'm going to double that semicolon (if any) so after the next line,
|
306 |
+
* which strips semicolons here & there, we're still left with this one.
|
307 |
+
*/
|
308 |
+
$content = preg_replace('/(for\([^;]*;[^;]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
|
309 |
+
|
310 |
+
/*
|
311 |
+
* We also don't really want to terminate statements followed by closing
|
312 |
+
* curly braces (which we've ignored completely up until now) or end-of-
|
313 |
+
* script: ASI will kick in here & we're all about minifying.
|
314 |
+
* Semicolons at beginning of the file don't make any sense either.
|
315 |
+
*/
|
316 |
+
$content = preg_replace('/;(\}|$)/s', '\\1', $content);
|
317 |
+
$content = ltrim($content, ';');
|
318 |
+
|
319 |
+
// get rid of remaining whitespace af beginning/end
|
320 |
+
return trim($content);
|
321 |
+
}
|
322 |
+
|
323 |
+
/**
|
324 |
+
* We'll strip whitespace around certain operators with regular expressions.
|
325 |
+
* This will prepare the given array by escaping all characters.
|
326 |
+
*
|
327 |
+
* @param string[] $operators
|
328 |
+
* @param string $delimiter
|
329 |
+
*
|
330 |
+
* @return string[]
|
331 |
+
*/
|
332 |
+
protected function getOperatorsForRegex(array $operators, $delimiter = '/')
|
333 |
+
{
|
334 |
+
// escape operators for use in regex
|
335 |
+
$delimiter = array_fill(0, count($operators), $delimiter);
|
336 |
+
$escaped = array_map('preg_quote', $operators, $delimiter);
|
337 |
+
|
338 |
+
$operators = array_combine($operators, $escaped);
|
339 |
+
|
340 |
+
// ignore + & - for now, they'll get special treatment
|
341 |
+
unset($operators['+'], $operators['-']);
|
342 |
+
|
343 |
+
// dot can not just immediately follow a number; it can be confused for
|
344 |
+
// decimal point, or calling a method on it, e.g. 42 .toString()
|
345 |
+
$operators['.'] = '(?<![0-9]\s)\.';
|
346 |
+
|
347 |
+
// don't confuse = with other assignment shortcuts (e.g. +=)
|
348 |
+
$chars = preg_quote('+-*\=<>%&|');
|
349 |
+
$operators['='] = '(?<!['.$chars.'])\=';
|
350 |
+
|
351 |
+
return $operators;
|
352 |
+
}
|
353 |
+
|
354 |
+
/**
|
355 |
+
* We'll strip whitespace around certain keywords with regular expressions.
|
356 |
+
* This will prepare the given array by escaping all characters.
|
357 |
+
*
|
358 |
+
* @param string[] $keywords
|
359 |
+
* @param string $delimiter
|
360 |
+
*
|
361 |
+
* @return string[]
|
362 |
+
*/
|
363 |
+
protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
|
364 |
+
{
|
365 |
+
// escape keywords for use in regex
|
366 |
+
$delimiter = array_fill(0, count($keywords), $delimiter);
|
367 |
+
$escaped = array_map('preg_quote', $keywords, $delimiter);
|
368 |
+
|
369 |
+
// add word boundaries
|
370 |
+
array_walk($keywords, function ($value) {
|
371 |
+
return '\b'.$value.'\b';
|
372 |
+
});
|
373 |
+
|
374 |
+
$keywords = array_combine($keywords, $escaped);
|
375 |
+
|
376 |
+
return $keywords;
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* Replaces all occurrences of array['key'] by array.key.
|
381 |
+
*
|
382 |
+
* @param string $content
|
383 |
+
*
|
384 |
+
* @return string
|
385 |
+
*/
|
386 |
+
protected function propertyNotation($content)
|
387 |
+
{
|
388 |
+
// PHP only supports $this inside anonymous functions since 5.4
|
389 |
+
$minifier = $this;
|
390 |
+
$keywords = $this->keywordsReserved;
|
391 |
+
$callback = function ($match) use ($minifier, $keywords) {
|
392 |
+
$property = trim($minifier->extracted[$match[1]], '\'"');
|
393 |
+
|
394 |
+
/*
|
395 |
+
* Check if the property is a reserved keyword. In this context (as
|
396 |
+
* property of an object literal/array) it shouldn't matter, but IE8
|
397 |
+
* freaks out with "Expected identifier".
|
398 |
+
*/
|
399 |
+
if (in_array($property, $keywords)) {
|
400 |
+
return $match[0];
|
401 |
+
}
|
402 |
+
|
403 |
+
/*
|
404 |
+
* See if the property is in a variable-like format (e.g.
|
405 |
+
* array['key-here'] can't be replaced by array.key-here since '-'
|
406 |
+
* is not a valid character there.
|
407 |
+
*/
|
408 |
+
if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
|
409 |
+
return $match[0];
|
410 |
+
}
|
411 |
+
|
412 |
+
return '.'.$property;
|
413 |
+
};
|
414 |
+
|
415 |
+
/*
|
416 |
+
* Figure out if previous character is a variable name (of the array
|
417 |
+
* we want to use property notation on) - this is to make sure
|
418 |
+
* standalone ['value'] arrays aren't confused for keys-of-an-array.
|
419 |
+
* We can (and only have to) check the last character, because PHP's
|
420 |
+
* regex implementation doesn't allow un-fixed-length lookbehind
|
421 |
+
* assertions.
|
422 |
+
*/
|
423 |
+
preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
|
424 |
+
$previousChar = $previousChar[1];
|
425 |
+
|
426 |
+
/*
|
427 |
+
* Make sure word preceding the ['value'] is not a keyword, e.g.
|
428 |
+
* return['x']. Because -again- PHP's regex implementation doesn't allow
|
429 |
+
* un-fixed-length lookbehind assertions, I'm just going to do a lot of
|
430 |
+
* separate lookbehind assertions, one for each keyword.
|
431 |
+
*/
|
432 |
+
$keywords = $this->getKeywordsForRegex($keywords);
|
433 |
+
$keywords = '(?<!'.implode(')(?<!', $keywords).')';
|
434 |
+
|
435 |
+
return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[(([\'"])[0-9]+\\2)\]/u', $callback, $content);
|
436 |
+
}
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Replaces true & false by !0 and !1.
|
440 |
+
*
|
441 |
+
* @param string $content
|
442 |
+
*
|
443 |
+
* @return string
|
444 |
+
*/
|
445 |
+
protected function shortenBools($content)
|
446 |
+
{
|
447 |
+
$content = preg_replace('/\btrue\b/', '!0', $content);
|
448 |
+
$content = preg_replace('/\bfalse\b/', '!1', $content);
|
449 |
+
|
450 |
+
// for(;;) is exactly the same as while(true)
|
451 |
+
$content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
|
452 |
+
|
453 |
+
// now make sure we didn't turn any do ... while(true) into do ... for(;;)
|
454 |
+
preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
455 |
+
|
456 |
+
// go backward to make sure positional offsets aren't altered when $content changes
|
457 |
+
$dos = array_reverse($dos);
|
458 |
+
foreach ($dos as $do) {
|
459 |
+
$offsetDo = $do[0][1];
|
460 |
+
|
461 |
+
// find all `while` (now `for`) following `do`: one of those must be
|
462 |
+
// associated with the `do` and be turned back into `while`
|
463 |
+
preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
|
464 |
+
foreach ($whiles as $while) {
|
465 |
+
$offsetWhile = $while[0][1];
|
466 |
+
|
467 |
+
$open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
|
468 |
+
$close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
|
469 |
+
if ($open === $close) {
|
470 |
+
// only restore `while` if amount of `{` and `}` are the same;
|
471 |
+
// otherwise, that `for` isn't associated with this `do`
|
472 |
+
$content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
|
473 |
+
break;
|
474 |
+
}
|
475 |
+
}
|
476 |
+
}
|
477 |
+
|
478 |
+
return $content;
|
479 |
+
}
|
480 |
+
}
|
minify/src/Minify.php
ADDED
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace MatthiasMullie\Minify;
|
4 |
+
|
5 |
+
use Psr\Cache\CacheItemInterface;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Abstract minifier class.
|
9 |
+
*
|
10 |
+
* Please report bugs on https://github.com/matthiasmullie/minify/issues
|
11 |
+
*
|
12 |
+
* @author Matthias Mullie <minify@mullie.eu>
|
13 |
+
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
|
14 |
+
* @license MIT License
|
15 |
+
*/
|
16 |
+
abstract class Minify
|
17 |
+
{
|
18 |
+
/**
|
19 |
+
* The data to be minified.
|
20 |
+
*
|
21 |
+
* @var string[]
|
22 |
+
*/
|
23 |
+
protected $data = array();
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Array of patterns to match.
|
27 |
+
*
|
28 |
+
* @var string[]
|
29 |
+
*/
|
30 |
+
protected $patterns = array();
|
31 |
+
|
32 |
+
/**
|
33 |
+
* This array will hold content of strings and regular expressions that have
|
34 |
+
* been extracted from the JS source code, so we can reliably match "code",
|
35 |
+
* without having to worry about potential "code-like" characters inside.
|
36 |
+
*
|
37 |
+
* @var string[]
|
38 |
+
*/
|
39 |
+
public $extracted = array();
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Init the minify class - optionally, code may be passed along already.
|
43 |
+
*/
|
44 |
+
public function __construct(/* $data = null, ... */)
|
45 |
+
{
|
46 |
+
// it's possible to add the source through the constructor as well ;)
|
47 |
+
if (func_num_args()) {
|
48 |
+
call_user_func_array(array($this, 'add'), func_get_args());
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Add a file or straight-up code to be minified.
|
54 |
+
*
|
55 |
+
* @param string $data
|
56 |
+
*/
|
57 |
+
public function add($data /* $data = null, ... */)
|
58 |
+
{
|
59 |
+
// bogus "usage" of parameter $data: scrutinizer warns this variable is
|
60 |
+
// not used (we're using func_get_args instead to support overloading),
|
61 |
+
// but it still needs to be defined because it makes no sense to have
|
62 |
+
// this function without argument :)
|
63 |
+
$args = array($data) + func_get_args();
|
64 |
+
|
65 |
+
// this method can be overloaded
|
66 |
+
foreach ($args as $data) {
|
67 |
+
// redefine var
|
68 |
+
$data = (string) $data;
|
69 |
+
|
70 |
+
// load data
|
71 |
+
$value = $this->load($data);
|
72 |
+
$key = ($data != $value) ? $data : count($this->data);
|
73 |
+
|
74 |
+
// store data
|
75 |
+
$this->data[$key] = $value;
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Load data.
|
81 |
+
*
|
82 |
+
* @param string $data Either a path to a file or the content itself.
|
83 |
+
*
|
84 |
+
* @return string
|
85 |
+
*/
|
86 |
+
protected function load($data)
|
87 |
+
{
|
88 |
+
// check if the data is a file
|
89 |
+
if (file_exists($data) && is_file($data)) {
|
90 |
+
$data = file_get_contents($data);
|
91 |
+
|
92 |
+
// strip BOM, if any
|
93 |
+
if (substr($data, 0, 3) == "\xef\xbb\xbf") {
|
94 |
+
$data = substr($data, 3);
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
return $data;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Save to file.
|
103 |
+
*
|
104 |
+
* @param string $content The minified data.
|
105 |
+
* @param string $path The path to save the minified data to.
|
106 |
+
*
|
107 |
+
* @throws Exception
|
108 |
+
*/
|
109 |
+
protected function save($content, $path)
|
110 |
+
{
|
111 |
+
// create file & open for writing
|
112 |
+
if (($handler = @fopen($path, 'w')) === false) {
|
113 |
+
throw new Exception('The file "'.$path.'" could not be opened. Check if PHP has enough permissions.');
|
114 |
+
}
|
115 |
+
|
116 |
+
// write to file
|
117 |
+
if (@fwrite($handler, $content) === false) {
|
118 |
+
throw new Exception('The file "'.$path.'" could not be written to. Check if PHP has enough permissions.');
|
119 |
+
}
|
120 |
+
|
121 |
+
// close the file
|
122 |
+
@fclose($handler);
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Minify the data & (optionally) saves it to a file.
|
127 |
+
*
|
128 |
+
* @param string[optional] $path Path to write the data to.
|
129 |
+
*
|
130 |
+
* @return string The minified data.
|
131 |
+
*/
|
132 |
+
public function minify($path = null)
|
133 |
+
{
|
134 |
+
$content = $this->execute($path);
|
135 |
+
|
136 |
+
// save to path
|
137 |
+
if ($path !== null) {
|
138 |
+
$this->save($content, $path);
|
139 |
+
}
|
140 |
+
|
141 |
+
return $content;
|
142 |
+
}
|
143 |
+
|
144 |
+
/**
|
145 |
+
* Minify & gzip the data & (optionally) saves it to a file.
|
146 |
+
*
|
147 |
+
* @param string[optional] $path Path to write the data to.
|
148 |
+
* @param int[optional] $level Compression level, from 0 to 9.
|
149 |
+
*
|
150 |
+
* @return string The minified & gzipped data.
|
151 |
+
*/
|
152 |
+
public function gzip($path = null, $level = 9)
|
153 |
+
{
|
154 |
+
$content = $this->execute($path);
|
155 |
+
$content = gzencode($content, $level, FORCE_GZIP);
|
156 |
+
|
157 |
+
// save to path
|
158 |
+
if ($path !== null) {
|
159 |
+
$this->save($content, $path);
|
160 |
+
}
|
161 |
+
|
162 |
+
return $content;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Minify the data & write it to a CacheItemInterface object.
|
167 |
+
*
|
168 |
+
* @param CacheItemInterface $item Cache item to write the data to.
|
169 |
+
*
|
170 |
+
* @return CacheItemInterface Cache item with the minifier data.
|
171 |
+
*/
|
172 |
+
public function cache(CacheItemInterface $item)
|
173 |
+
{
|
174 |
+
$content = $this->execute();
|
175 |
+
$item->set($content);
|
176 |
+
|
177 |
+
return $item;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Minify the data.
|
182 |
+
*
|
183 |
+
* @param string[optional] $path Path to write the data to.
|
184 |
+
*
|
185 |
+
* @return string The minified data.
|
186 |
+
*/
|
187 |
+
abstract public function execute($path = null);
|
188 |
+
|
189 |
+
/**
|
190 |
+
* Register a pattern to execute against the source content.
|
191 |
+
*
|
192 |
+
* @param string $pattern PCRE pattern.
|
193 |
+
* @param string|callable $replacement Replacement value for matched pattern.
|
194 |
+
*
|
195 |
+
* @throws Exception
|
196 |
+
*/
|
197 |
+
protected function registerPattern($pattern, $replacement = '')
|
198 |
+
{
|
199 |
+
// study the pattern, we'll execute it more than once
|
200 |
+
$pattern .= 'S';
|
201 |
+
|
202 |
+
$this->patterns[] = array($pattern, $replacement);
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* We can't "just" run some regular expressions against JavaScript: it's a
|
207 |
+
* complex language. E.g. having an occurrence of // xyz would be a comment,
|
208 |
+
* unless it's used within a string. Of you could have something that looks
|
209 |
+
* like a 'string', but inside a comment.
|
210 |
+
* The only way to accurately replace these pieces is to traverse the JS one
|
211 |
+
* character at a time and try to find whatever starts first.
|
212 |
+
*
|
213 |
+
* @param string $content The content to replace patterns in.
|
214 |
+
*
|
215 |
+
* @return string The (manipulated) content.
|
216 |
+
*/
|
217 |
+
protected function replace($content)
|
218 |
+
{
|
219 |
+
$processed = '';
|
220 |
+
$positions = array_fill(0, count($this->patterns), -1);
|
221 |
+
$matches = array();
|
222 |
+
|
223 |
+
while ($content) {
|
224 |
+
// find first match for all patterns
|
225 |
+
foreach ($this->patterns as $i => $pattern) {
|
226 |
+
list($pattern, $replacement) = $pattern;
|
227 |
+
|
228 |
+
// no need to re-run matches that are still in the part of the
|
229 |
+
// content that hasn't been processed
|
230 |
+
if ($positions[$i] >= 0) {
|
231 |
+
continue;
|
232 |
+
}
|
233 |
+
|
234 |
+
$match = null;
|
235 |
+
if (preg_match($pattern, $content, $match)) {
|
236 |
+
$matches[$i] = $match;
|
237 |
+
|
238 |
+
// we'll store the match position as well; that way, we
|
239 |
+
// don't have to redo all preg_matches after changing only
|
240 |
+
// the first (we'll still know where those others are)
|
241 |
+
$positions[$i] = strpos($content, $match[0]);
|
242 |
+
} else {
|
243 |
+
// if the pattern couldn't be matched, there's no point in
|
244 |
+
// executing it again in later runs on this same content;
|
245 |
+
// ignore this one until we reach end of content
|
246 |
+
unset($matches[$i]);
|
247 |
+
$positions[$i] = strlen($content);
|
248 |
+
}
|
249 |
+
}
|
250 |
+
|
251 |
+
// no more matches to find: everything's been processed, break out
|
252 |
+
if (!$matches) {
|
253 |
+
$processed .= $content;
|
254 |
+
break;
|
255 |
+
}
|
256 |
+
|
257 |
+
// see which of the patterns actually found the first thing (we'll
|
258 |
+
// only want to execute that one, since we're unsure if what the
|
259 |
+
// other found was not inside what the first found)
|
260 |
+
$discardLength = min($positions);
|
261 |
+
$firstPattern = array_search($discardLength, $positions);
|
262 |
+
$match = $matches[$firstPattern][0];
|
263 |
+
|
264 |
+
// execute the pattern that matches earliest in the content string
|
265 |
+
list($pattern, $replacement) = $this->patterns[$firstPattern];
|
266 |
+
$replacement = $this->replacePattern($pattern, $replacement, $content);
|
267 |
+
|
268 |
+
// figure out which part of the string was unmatched; that's the
|
269 |
+
// part we'll execute the patterns on again next
|
270 |
+
$content = substr($content, $discardLength);
|
271 |
+
$unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
|
272 |
+
|
273 |
+
// move the replaced part to $processed and prepare $content to
|
274 |
+
// again match batch of patterns against
|
275 |
+
$processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
|
276 |
+
$content = $unmatched;
|
277 |
+
|
278 |
+
// first match has been replaced & that content is to be left alone,
|
279 |
+
// the next matches will start after this replacement, so we should
|
280 |
+
// fix their offsets
|
281 |
+
foreach ($positions as $i => $position) {
|
282 |
+
$positions[$i] -= $discardLength + strlen($match);
|
283 |
+
}
|
284 |
+
}
|
285 |
+
|
286 |
+
return $processed;
|
287 |
+
}
|
288 |
+
|
289 |
+
/**
|
290 |
+
* This is where a pattern is matched against $content and the matches
|
291 |
+
* are replaced by their respective value.
|
292 |
+
* This function will be called plenty of times, where $content will always
|
293 |
+
* move up 1 character.
|
294 |
+
*
|
295 |
+
* @param string $pattern Pattern to match.
|
296 |
+
* @param string|callable $replacement Replacement value.
|
297 |
+
* @param string $content Content to match pattern against.
|
298 |
+
*
|
299 |
+
* @return string
|
300 |
+
*/
|
301 |
+
protected function replacePattern($pattern, $replacement, $content)
|
302 |
+
{
|
303 |
+
if (is_callable($replacement)) {
|
304 |
+
return preg_replace_callback($pattern, $replacement, $content, 1, $count);
|
305 |
+
} else {
|
306 |
+
return preg_replace($pattern, $replacement, $content, 1, $count);
|
307 |
+
}
|
308 |
+
}
|
309 |
+
|
310 |
+
/**
|
311 |
+
* Strings are a pattern we need to match, in order to ignore potential
|
312 |
+
* code-like content inside them, but we just want all of the string
|
313 |
+
* content to remain untouched.
|
314 |
+
*
|
315 |
+
* This method will replace all string content with simple STRING#
|
316 |
+
* placeholder text, so we've rid all strings from characters that may be
|
317 |
+
* misinterpreted. Original string content will be saved in $this->extracted
|
318 |
+
* and after doing all other minifying, we can restore the original content
|
319 |
+
* via restoreStrings()
|
320 |
+
*
|
321 |
+
* @param string[optional] $chars
|
322 |
+
*/
|
323 |
+
protected function extractStrings($chars = '\'"')
|
324 |
+
{
|
325 |
+
// PHP only supports $this inside anonymous functions since 5.4
|
326 |
+
$minifier = $this;
|
327 |
+
$callback = function ($match) use ($minifier) {
|
328 |
+
if (!$match[1]) {
|
329 |
+
/*
|
330 |
+
* Empty strings need no placeholder; they can't be confused for
|
331 |
+
* anything else anyway.
|
332 |
+
* But we still needed to match them, for the extraction routine
|
333 |
+
* to skip over this particular string.
|
334 |
+
*/
|
335 |
+
return $match[0];
|
336 |
+
}
|
337 |
+
|
338 |
+
$count = count($minifier->extracted);
|
339 |
+
$placeholder = $match[1].$count.$match[1];
|
340 |
+
$minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
|
341 |
+
|
342 |
+
return $placeholder;
|
343 |
+
};
|
344 |
+
|
345 |
+
/*
|
346 |
+
* The \\ messiness explained:
|
347 |
+
* * Don't count ' or " as end-of-string if it's escaped (has backslash
|
348 |
+
* in front of it)
|
349 |
+
* * Unless... that backslash itself is escaped (another leading slash),
|
350 |
+
* in which case it's no longer escaping the ' or "
|
351 |
+
* * So there can be either no backslash, or an even number
|
352 |
+
* * multiply all of that times 4, to account for the escaping that has
|
353 |
+
* to be done to pass the backslash into the PHP string without it being
|
354 |
+
* considered as escape-char (times 2) and to get it in the regex,
|
355 |
+
* escaped (times 2)
|
356 |
+
*/
|
357 |
+
$this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* This method will restore all extracted data (strings, regexes) that were
|
362 |
+
* replaced with placeholder text in extract*(). The original content was
|
363 |
+
* saved in $this->extracted.
|
364 |
+
*
|
365 |
+
* @param string $content
|
366 |
+
*
|
367 |
+
* @return string
|
368 |
+
*/
|
369 |
+
protected function restoreExtractedData($content)
|
370 |
+
{
|
371 |
+
if (!$this->extracted) {
|
372 |
+
// nothing was extracted, nothing to restore
|
373 |
+
return $content;
|
374 |
+
}
|
375 |
+
|
376 |
+
$content = strtr($content, $this->extracted);
|
377 |
+
|
378 |
+
$this->extracted = array();
|
379 |
+
|
380 |
+
return $content;
|
381 |
+
}
|
382 |
+
}
|
plugin-credit.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div style="background-color: #ff6600; max-width: 500px; text-align: center; padding: 20px; color: #fff;">
|
2 |
+
<a href="http://potentplugins.com/?utm_source=<?php echo($potent_slug); ?>&utm_medium=link&utm_campaign=wp-plugin-credit-link" target="_blank">
|
3 |
+
<img src="<?php echo(plugins_url('images/potent-logo.png', __FILE__)); ?>" alt="Potent Plugins" style="max-width: 100%;" />
|
4 |
+
</a>
|
5 |
+
<div style="margin-bottom: 10px; font-size: 1.2em;">
|
6 |
+
<strong>If you like our free plugin, please:</strong>
|
7 |
+
</div>
|
8 |
+
<div style="margin-bottom: 10px;">
|
9 |
+
<a href="https://wordpress.org/support/view/plugin-reviews/<?php echo($potent_slug); ?>" target="_blank" class="button-secondary">Write a Review</a>
|
10 |
+
</div>
|
11 |
+
<div style="margin-bottom: 10px;">
|
12 |
+
<div class="fb-page" data-href="http://facebook.com/potentplugins" data-width="250" data-height="70" data-small-header="true" data-adapt-container-width="true" data-hide-cover="true" data-show-facepile="false"><div class="fb-xfbml-parse-ignore"><blockquote cite="http://facebook.com/potentplugins"><a href="http://facebook.com/potentplugins">Potent Plugins</a></blockquote></div></div>
|
13 |
+
</div>
|
14 |
+
<div>
|
15 |
+
<a href="https://twitter.com/potentplugins" class="twitter-follow-button" data-show-count="false" data-size="large" data-dnt="true">Follow @potentplugins</a>
|
16 |
+
</div>
|
17 |
+
</div>
|
18 |
+
|
19 |
+
<!-- FB -->
|
20 |
+
<div id="fb-root"></div>
|
21 |
+
<script>(function(d, s, id) {
|
22 |
+
var js, fjs = d.getElementsByTagName(s)[0];
|
23 |
+
if (d.getElementById(id)) return;
|
24 |
+
js = d.createElement(s); js.id = id;
|
25 |
+
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.5";
|
26 |
+
fjs.parentNode.insertBefore(js, fjs);
|
27 |
+
}(document, 'script', 'facebook-jssdk'));</script>
|
28 |
+
|
29 |
+
<!-- Twitter -->
|
30 |
+
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
readme.txt
CHANGED
@@ -2,16 +2,23 @@
|
|
2 |
Contributors: hearken
|
3 |
Tags: css, custom css, styles, custom styles, stylesheet, custom stylesheet, javascript, custom javascript, js, custom js
|
4 |
Requires at least: 3.5
|
5 |
-
Tested up to: 4.
|
6 |
-
Stable tag:
|
7 |
License: GPLv2 or later
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
|
10 |
-
Easily add custom CSS and Javascript code to your WordPress site
|
11 |
|
12 |
== Description ==
|
13 |
|
14 |
-
This plugin allows you to add custom site-wide CSS styles and Javascript code to your WordPress site. Useful for overriding your theme's styles and adding client-side functionality.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
== Installation ==
|
17 |
|
@@ -31,6 +38,11 @@ Alternatively, you can manually upload the plugin to your wp-content/plugins dir
|
|
31 |
|
32 |
== Changelog ==
|
33 |
|
|
|
|
|
|
|
|
|
|
|
34 |
= 1.0.5 =
|
35 |
* Changed file storage location to prevent deletion on plugin update. IMPORTANT: BACK UP YOUR CUSTOM CSS AND JAVASCRIPT CODE BEFORE INSTALLING THIS UPDATE.
|
36 |
|
2 |
Contributors: hearken
|
3 |
Tags: css, custom css, styles, custom styles, stylesheet, custom stylesheet, javascript, custom javascript, js, custom js
|
4 |
Requires at least: 3.5
|
5 |
+
Tested up to: 4.5
|
6 |
+
Stable tag: 2.0
|
7 |
License: GPLv2 or later
|
8 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
9 |
|
10 |
+
Easily add custom CSS and Javascript code to your WordPress site, with draft previewing, revisions, and minification!
|
11 |
|
12 |
== Description ==
|
13 |
|
14 |
+
This plugin allows you to add custom site-wide CSS styles and Javascript code to your WordPress site. Useful for overriding your theme's styles and adding client-side functionality.
|
15 |
+
|
16 |
+
Features:
|
17 |
+
* Code editor with syntax highlighting and AJAX saving to avoid reloading the editor at each save.
|
18 |
+
* Save and preview your CSS and Javascript as a draft that is only applied to logged-in users with the necessary permissions until you are ready to publish your changes to the public.
|
19 |
+
* View and restore past revisions of your CSS and Javascript.
|
20 |
+
* Automatically minify your custom CSS and Jasascript code to reduce file size.
|
21 |
+
* For the public, custom CSS and Javascript code is served from the filesystem instead of the database for optimal performance.
|
22 |
|
23 |
== Installation ==
|
24 |
|
38 |
|
39 |
== Changelog ==
|
40 |
|
41 |
+
= 2.0 =
|
42 |
+
* Added revisions
|
43 |
+
* Added drafts/previewing
|
44 |
+
* Added minification
|
45 |
+
|
46 |
= 1.0.5 =
|
47 |
* Changed file storage location to prevent deletion on plugin update. IMPORTANT: BACK UP YOUR CUSTOM CSS AND JAVASCRIPT CODE BEFORE INSTALLING THIS UPDATE.
|
48 |
|