Custom CSS and Javascript - Version 2.0

Version Description

  • Added revisions
  • Added drafts/previewing
  • Added minification
Download this release

Release Info

Developer hearken
Plugin Icon 128x128 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 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: 1.0.6
6
- * Author: Hearken Media
7
- * Author URI: http://hearkenmedia.com/landing-wp-plugin.php?utm_source=custom-css-and-javascript&utm_medium=link&utm_campaign=wp-widget-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,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 (file_exists($uploadDir['basedir'].'/hm_custom_css_js/custom.js'))
16
- wp_enqueue_script('hm_custom_js', $uploadDir['baseurl'].'/hm_custom_css_js/custom.js', array(), get_option('hm_custom_javascript_ver', 1));
17
- if (file_exists($uploadDir['basedir'].'/hm_custom_css_js/custom.css'))
18
- wp_enqueue_style('hm_custom_css', $uploadDir['baseurl'].'/hm_custom_css_js/custom.css', array(), get_option('hm_custom_css_ver', 1));
 
 
 
 
 
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
- if (file_put_contents($uploadDir['basedir'].'/hm_custom_css_js/custom.'.($_POST['mode'] == 'css' ? 'css' : 'js'), wp_unslash($_POST['code'])) === false)
 
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;"></div>
 
 
 
 
 
 
 
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
- <script>
79
- var hm_custom_code_editor, hm_custom_code_editor_has_changes = false;
80
- jQuery(document).ready(function($) {
81
- hm_custom_code_editor = CodeMirror(document.getElementById("hm_custom_code_editor"), {
82
- lineNumbers: true,
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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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); ?>&amp;utm_medium=link&amp;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.4
6
- Stable tag: 1.0.6
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. Features a code editor with syntax highlighting and AJAX saving to avoid reloading the editor at each save.
 
 
 
 
 
 
 
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