Admin Menu Editor - Version 1.3.2

Version Description

  • Added a large number of menu icons based on the Dashicons icon font.
  • Fixed default menu icons not showing up in WP 3.9.
  • Fixed a rare "$link.attr(...) is undefined" JavaScript error.
  • Fixed a bug where a hidden submenu page with a URL like "options-general.php?page=something" would still be accessible via "admin.php?page=something".
  • Fixed several other minor bugs.
  • Tested up to WordPress 3.9-RC1. Minimum requirements increased to WP 3.5.
Download this release

Release Info

Developer whiteshadow
Plugin Icon 128x128 Admin Menu Editor
Version 1.3.2
Comparing to
See all releases

Code changes from version 1.3.1 to 1.3.2

Files changed (46) hide show
  1. css/admin.css +1 -9
  2. css/menu-editor.css +94 -5
  3. includes/.htaccess +2 -0
  4. includes/PHP-CSS-Parser/CHANGELOG.md +121 -0
  5. includes/PHP-CSS-Parser/README.md +519 -0
  6. includes/PHP-CSS-Parser/autoloader.php +10 -0
  7. includes/PHP-CSS-Parser/composer.json +17 -0
  8. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/AtRuleBlockList.php +36 -0
  9. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSBlockList.php +78 -0
  10. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSList.php +75 -0
  11. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/Document.php +87 -0
  12. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/KeyFrame.php +48 -0
  13. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parser.php +629 -0
  14. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parsing/UnexpectedTokenException.php +28 -0
  15. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/AtRule.php +13 -0
  16. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/CSSNamespace.php +48 -0
  17. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Charset.php +39 -0
  18. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Import.php +42 -0
  19. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Selector.php +75 -0
  20. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Rule/Rule.php +142 -0
  21. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/AtRuleSet.php +36 -0
  22. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php +585 -0
  23. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/RuleSet.php +95 -0
  24. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Settings.php +54 -0
  25. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/CSSFunction.php +35 -0
  26. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Color.php +38 -0
  27. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/PrimitiveValue.php +7 -0
  28. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/RuleValueList.php +11 -0
  29. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Size.php +68 -0
  30. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/String.php +27 -0
  31. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/URL.php +26 -0
  32. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Value.php +8 -0
  33. includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/ValueList.php +42 -0
  34. includes/admin-menu-editor-mu.php +12 -2
  35. includes/editor-page.php +103 -18
  36. includes/generate-menu-dashicons.php +107 -0
  37. includes/menu-editor-core.php +62 -5
  38. includes/menu-item.php +5 -0
  39. includes/menu.php +12 -3
  40. includes/shadow_plugin_framework.php +18 -5
  41. js/admin-helpers.js +51 -9
  42. js/jquery.cookie.js +117 -0
  43. js/menu-editor.js +244 -45
  44. js/menu-highlight-fix.js +2 -2
  45. menu-editor.php +1 -1
  46. readme.txt +12 -4
css/admin.css CHANGED
@@ -41,12 +41,4 @@ hr.ws-submenu-separator {
41
  /* No pointer/hand on separators. */
42
  #adminmenu li.ws-submenu-separator-wrap a {
43
  cursor: default;
44
- }
45
-
46
- /* Override the bluish highlight used by WP */
47
- #adminmenu li.ws-submenu-separator-wrap a:hover,
48
- #adminmenu li.ws-submenu-separator-wrap a:focus
49
- {
50
- background-color: white;
51
- }
52
-
41
  /* No pointer/hand on separators. */
42
  #adminmenu li.ws-submenu-separator-wrap a {
43
  cursor: default;
44
+ }
 
 
 
 
 
 
 
 
css/menu-editor.css CHANGED
@@ -225,8 +225,8 @@
225
  }
226
 
227
  .ws_edit_field {
228
- margin-bottom: 8px;
229
- height: 42.2px;
230
  }
231
 
232
  .ws_edit_field-custom {
@@ -254,7 +254,9 @@
254
  background-image: url("../images/pencil_delete.png");
255
  }
256
 
257
- .ws_input_default input, .ws_input_default select {
 
 
258
  color: gray;
259
  }
260
 
@@ -496,11 +498,24 @@ select.ws_dropdown optgroup option {
496
  border: 1px solid silver;
497
  border-radius: 3px;
498
  background-color: white;
499
- width: 144px;
500
  padding: 2px;
501
  position: absolute;
502
  }
503
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  #ws_icon_selector .ws_icon_option {
505
  float: left;
506
  height: 30px;
@@ -604,6 +619,80 @@ select.ws_dropdown optgroup option {
604
  margin: 2px;
605
  }
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  /************************************
608
  Export and import
609
  *************************************/
@@ -696,7 +785,7 @@ select.ws_dropdown optgroup option {
696
  }
697
 
698
  .ws_dialog_buttons {
699
- height: 23px;
700
  text-align: right;
701
  margin-top: 20px;
702
  margin-bottom: 1px;
225
  }
226
 
227
  .ws_edit_field {
228
+ margin-bottom: 6px;
229
+ height: 45px;
230
  }
231
 
232
  .ws_edit_field-custom {
254
  background-image: url("../images/pencil_delete.png");
255
  }
256
 
257
+ .ws_input_default input,
258
+ .ws_input_default select,
259
+ .ws_input_default .ws_color_scheme_display {
260
  color: gray;
261
  }
262
 
498
  border: 1px solid silver;
499
  border-radius: 3px;
500
  background-color: white;
501
+ width: 216px;
502
  padding: 2px;
503
  position: absolute;
504
  }
505
 
506
+ #ws_icon_selector.ws_with_more_icons {
507
+ width: 504px;
508
+ }
509
+
510
+ #ws_icon_selector .ws_icon_extra {
511
+ display: none;
512
+ }
513
+
514
+ #ws_icon_selector.ws_with_more_icons .ws_icon_extra {
515
+ display: inline-block;
516
+ }
517
+
518
+
519
  #ws_icon_selector .ws_icon_option {
520
  float: left;
521
  height: 30px;
619
  margin: 2px;
620
  }
621
 
622
+ #ws_show_more_icons {
623
+ margin: 2px;
624
+ height: 30px;
625
+ width: 68px;
626
+ }
627
+
628
+
629
+ /************************************
630
+ Menu color picker
631
+ *************************************/
632
+
633
+ #ws-ame-menu-color-settings {
634
+ background: white;
635
+ display: none;
636
+ }
637
+
638
+ #ame-menu-color-list {
639
+ height: 500px;
640
+ overflow-y: auto;
641
+ }
642
+
643
+ .ame-menu-color-column {
644
+ min-width: 460px;
645
+ }
646
+
647
+ .ame-menu-color-name {
648
+ display: inline-block;
649
+ vertical-align: top;
650
+ padding-top: 2px;
651
+
652
+ line-height: 1.3;
653
+ font-size: 14px;
654
+ font-weight: 600;
655
+
656
+ min-width: 180px;
657
+ }
658
+
659
+ .ame-color-option {
660
+ padding: 10px 0;
661
+ }
662
+
663
+ .ame-advanced-menu-color {
664
+ display: none;
665
+ }
666
+
667
+ /* Color scheme display in the editor widget. */
668
+
669
+ .ws_color_scheme_display {
670
+ display: inline-block;
671
+ height: 20px;
672
+ width: 190px;
673
+
674
+ margin-right: 5px;
675
+ padding: 2px 2px 2px 3px;
676
+
677
+ border: 1px solid #ddd;
678
+ background: white;
679
+ cursor: pointer;
680
+ }
681
+
682
+ .ws_color_display_item {
683
+ display: inline-block;
684
+ width: 18px;
685
+ height: 18px;
686
+
687
+ margin-right: 4px;
688
+ border: 1px solid #ccc;
689
+ border-radius: 3px;
690
+ }
691
+
692
+ .ws_color_display_item:last-child {
693
+ margin-right: 0;
694
+ }
695
+
696
  /************************************
697
  Export and import
698
  *************************************/
785
  }
786
 
787
  .ws_dialog_buttons {
788
+ /*height: 30px;*/
789
  text-align: right;
790
  margin-top: 20px;
791
  margin-bottom: 1px;
includes/.htaccess ADDED
@@ -0,0 +1,2 @@
 
 
1
+ Order deny,allow
2
+ Deny from all
includes/PHP-CSS-Parser/CHANGELOG.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Revision History
2
+
3
+ ## 5.0
4
+
5
+ ### 5.0.0 (2013-03-20)
6
+
7
+ * Correctly parse all known CSS 3 units (including Hz and kHz).
8
+ * Output RGB colors in short (#aaa or #ababab) notation
9
+ * Be case-insensitive when parsing identifiers.
10
+ * *No deprecations*
11
+
12
+ #### Backwards-incompatible changes
13
+
14
+ * `Sabberworm\CSS\Value\Color`’s `__toString` method overrides `CSSList`’s to maybe return something other than `type(value, …)` (see above).
15
+
16
+ ### 5.0.1 (2013-03-20)
17
+
18
+ * Internal cleanup
19
+ * *No backwards-incompatible changes*
20
+ * *No deprecations*
21
+
22
+ ### 5.0.2 (2013-03-21)
23
+
24
+ * CHANGELOG.md file added to distribution
25
+ * *No backwards-incompatible changes*
26
+ * *No deprecations*
27
+
28
+ ### 5.0.3 (2013-03-21)
29
+
30
+ * More size units recognized
31
+ * *No backwards-incompatible changes*
32
+ * *No deprecations*
33
+
34
+ ### 5.0.4 (2013-03-21)
35
+
36
+ * Don’t output floats with locale-aware separator chars
37
+ * *No backwards-incompatible changes*
38
+ * *No deprecations*
39
+
40
+ ### 5.0.5 (2013-04-17)
41
+
42
+ * Initial support for lenient parsing (setting this parser option will catch some exceptions internally and recover the parser’s state as neatly as possible).
43
+ * *No backwards-incompatible changes*
44
+ * *No deprecations*
45
+
46
+ ### 5.0.6 (2013-05-31)
47
+
48
+ * Fix broken unit test
49
+ * *No backwards-incompatible changes*
50
+ * *No deprecations*
51
+
52
+ ### 5.0.7 (2013-08-04)
53
+
54
+ * Fix broken decimal point output optimization
55
+ * *No backwards-incompatible changes*
56
+ * *No deprecations*
57
+
58
+ ### 5.0.8 (2013-08-15)
59
+
60
+ * Make default settings’ multibyte parsing option dependent on whether or not the mbstring extension is actually installed.
61
+ * *No backwards-incompatible changes*
62
+ * *No deprecations*
63
+
64
+ ### 5.1.0 (2013-10-24)
65
+
66
+ * Performance enhancements by Michael M Slusarz
67
+ * More rescue entry points for lenient parsing (unexpected tokens between declaration blocks and unclosed comments)
68
+ * *No backwards-incompatible changes*
69
+ * *No deprecations*
70
+
71
+ ### 5.1.1 (2013-10-28)
72
+
73
+ * Updated CHANGELOG.md to reflect changes since 5.0.4
74
+ * *No backwards-incompatible changes*
75
+ * *No deprecations*
76
+
77
+ ## 4.0
78
+
79
+ ### 4.0.0 (2013-03-19)
80
+
81
+ * Support for more @-rules
82
+ * Generic interface `Sabberworm\CSS\Property\AtRule`, implemented by all @-rule classes
83
+ * *No deprecations*
84
+
85
+ #### Backwards-incompatible changes
86
+
87
+ * `Sabberworm\CSS\RuleSet\AtRule` renamed to `Sabberworm\CSS\RuleSet\AtRuleSet`
88
+ * `Sabberworm\CSS\CSSList\MediaQuery` renamed to `Sabberworm\CSS\RuleSet\CSSList\AtRuleBlockList` with differing semantics and API (which also works for other block-list-based @-rules like `@supports`).
89
+
90
+ ## 3.0
91
+
92
+ ### 3.0.0 (2013-03-06)
93
+
94
+ * Support for lenient parsing (on by default)
95
+ * *No deprecations*
96
+
97
+ #### Backwards-incompatible changes
98
+
99
+ * All properties (like whether or not to use `mb_`-functions, which default charset to use and – new – whether or not to be forgiving when parsing) are now encapsulated in an instance of `Sabberworm\CSS\Settings` which can be passed as the second argument to `Sabberworm\CSS\Parser->__construct()`.
100
+ * Specifying a charset as the second argument to `Sabberworm\CSS\Parser->__construct()` is no longer supported. Use `Sabberworm\CSS\Settings::create()->withDefaultCharset('some-charset')` instead.
101
+ * Setting `Sabberworm\CSS\Parser->bUseMbFunctions` has no effect. Use `Sabberworm\CSS\Settings::create()->withMultibyteSupport(true/false)` instead.
102
+ * `Sabberworm\CSS\Parser->parse()` may throw a `Sabberworm\CSS\Parsing\UnexpectedTokenException` when in strict parsing mode.
103
+
104
+ ## 2.0
105
+
106
+ ### 2.0.0 (2013-01-29)
107
+
108
+ * Allow multiple rules of the same type per rule set
109
+
110
+ #### Backwards-incompatible changes
111
+
112
+ * `Sabberworm\CSS\RuleSet->getRules()` returns an index-based array instead of an associative array. Use `Sabberworm\CSS\RuleSet->getRulesAssoc()` (which eliminates duplicate rules and lets the later rule of the same name win).
113
+ * `Sabberworm\CSS\RuleSet->removeRule()` works as it did before except when passed an instance of `Sabberworm\CSS\Rule\Rule`, in which case it would only remove the exact rule given instead of all the rules of the same type. To get the old behaviour, use `Sabberworm\CSS\RuleSet->removeRule($oRule->getRule()`;
114
+
115
+ ## 1.0
116
+
117
+ Initial release of a stable public API.
118
+
119
+ ## 0.9
120
+
121
+ Last version not to use PSR-0 project organization semantics.
includes/PHP-CSS-Parser/README.md ADDED
@@ -0,0 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ PHP CSS Parser
2
+ --------------
3
+
4
+ A Parser for CSS Files written in PHP. Allows extraction of CSS files into a data structure, manipulation of said structure and output as (optimized) CSS.
5
+
6
+ ## Usage
7
+
8
+ ### Installation using composer
9
+
10
+ Add php-css-parser to your composer.json
11
+
12
+ {
13
+ "require": {
14
+ "sabberworm/php-css-parser": "*"
15
+ }
16
+ }
17
+
18
+ ### Extraction
19
+
20
+ To use the CSS Parser, create a new instance. The constructor takes the following form:
21
+
22
+ new Sabberworm\CSS\Parser($sText);
23
+
24
+ To read a file, for example, you’d do the following:
25
+
26
+ $oCssParser = new Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
27
+ $oCssDocument = $oCssParser->parse();
28
+
29
+ The resulting CSS document structure can be manipulated prior to being output.
30
+
31
+ ### Options
32
+
33
+ #### Charset
34
+
35
+ The charset option is used only if no @charset declaration is found in the CSS file. UTF-8 is the default, so you won’t have to create a settings object at all if you don’t intend to change that.
36
+
37
+ $oSettings = Sabberworm\CSS\Settings::create()->withDefaultCharset('windows-1252');
38
+ new Sabberworm\CSS\Parser($sText, $oSettings);
39
+
40
+ #### Strict parsing
41
+
42
+ To have the parser choke on invalid rules, supply a thusly configured Sabberworm\CSS\Settings object:
43
+
44
+ $oCssParser = new Sabberworm\CSS\Parser(file_get_contents('somefile.css'), Sabberworm\CSS\Settings::create()->beStrict());
45
+
46
+ #### Disable multibyte functions
47
+
48
+ To achieve faster parsing, you can choose to have PHP-CSS-Parser use regular string functions instead of `mb_*` functions. This should work fine in most cases, even for UTF-8 files, as all the multibyte characters are in string literals. Still it’s not recommended to use this with input you have no control over as it’s not thoroughly covered by test cases.
49
+
50
+ $oSettings = Sabberworm\CSS\Settings::create()->withMultibyteSupport(false);
51
+ new Sabberworm\CSS\Parser($sText, $oSettings);
52
+
53
+ ### Manipulation
54
+
55
+ The resulting data structure consists mainly of five basic types: `CSSList`, `RuleSet`, `Rule`, `Selector` and `Value`. There are two additional types used: `Import` and `Charset` which you won’t use often.
56
+
57
+ #### CSSList
58
+
59
+ `CSSList` represents a generic CSS container, most likely containing declaration blocks (rule sets with a selector) but it may also contain at-rules, charset declarations, etc. `CSSList` has the following concrete subtypes:
60
+
61
+ * `Document` – representing the root of a CSS file.
62
+ * `MediaQuery` – represents a subsection of a CSSList that only applies to a output device matching the contained media query.
63
+
64
+ #### RuleSet
65
+
66
+ `RuleSet` is a container for individual rules. The most common form of a rule set is one constrained by a selector. The following concrete subtypes exist:
67
+
68
+ * `AtRuleSet` – for generic at-rules which do not match the ones specifically mentioned like @import, @charset or @media. A common example for this is @font-face.
69
+ * `DeclarationBlock` – a RuleSet constrained by a `Selector`; contains an array of selector objects (comma-separated in the CSS) as well as the rules to be applied to the matching elements.
70
+
71
+ Note: A `CSSList` can contain other `CSSList`s (and `Import`s as well as a `Charset`) while a `RuleSet` can only contain `Rule`s.
72
+
73
+ #### Rule
74
+
75
+ `Rule`s just have a key (the rule) and a value. These values are all instances of a `Value`.
76
+
77
+ #### Value
78
+
79
+ `Value` is an abstract class that only defines the `__toString` method. The concrete subclasses for atomic value types are:
80
+
81
+ * `Size` – consists of a numeric `size` value and a unit.
82
+ * `Color` – colors can be input in the form #rrggbb, #rgb or schema(val1, val2, …) but are always stored as an array of ('s' => val1, 'c' => val2, 'h' => val3, …) and output in the second form.
83
+ * `String` – this is just a wrapper for quoted strings to distinguish them from keywords; always output with double quotes.
84
+ * `URL` – URLs in CSS; always output in URL("") notation.
85
+
86
+ There is another abstract subclass of `Value`, `ValueList`. A `ValueList` represents a lists of `Value`s, separated by some separation character (mostly `,`, whitespace, or `/`). There are two types of `ValueList`s:
87
+
88
+ * `RuleValueList` – The default type, used to represent all multi-valued rules like `font: bold 12px/3 Helvetica, Verdana, sans-serif;` (where the value would be a whitespace-separated list of the primitive value `bold`, a slash-separated list and a comma-separated list).
89
+ * `CSSFunction` – A special kind of value that also contains a function name and where the values are the function’s arguments. Also handles equals-sign-separated argument lists like `filter: alpha(opacity=90);`.
90
+
91
+ To access the items stored in a `CSSList` – like the document you got back when calling `$oCssParser->parse()` –, use `getContents()`, then iterate over that collection and use instanceof to check whether you’re dealing with another `CSSList`, a `RuleSet`, a `Import` or a `Charset`.
92
+
93
+ To append a new item (selector, media query, etc.) to an existing `CSSList`, construct it using the constructor for this class and use the `append($oItem)` method.
94
+
95
+ If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $oRule)`, `getRules()` and `removeRule($mRule)` (which accepts either a Rule instance or a rule name; optionally suffixed by a dash to remove all related rules).
96
+
97
+ #### Convenience methods
98
+
99
+ There are a few convenience methods on Document to ease finding, manipulating and deleting rules:
100
+
101
+ * `getAllDeclarationBlocks()` – does what it says; no matter how deeply nested your selectors are. Aliased as `getAllSelectors()`.
102
+ * `getAllRuleSets()` – does what it says; no matter how deeply nested your rule sets are.
103
+ * `getAllValues()` – finds all `Value` objects inside `Rule`s.
104
+
105
+ ## To-Do
106
+
107
+ * More convenience methods [like `selectorsWithElement($sId/Class/TagName)`, `removeSelector($oSelector)`, `attributesOfType($sType)`, `removeAttributesOfType($sType)`]
108
+ * Options for output (compact, verbose, etc.)
109
+ * Real multibyte support. Currently only multibyte charsets whose first 255 code points take up only one byte and are identical with ASCII are supported (yes, UTF-8 fits this description).
110
+ * Named color support (using `Color` instead of an anonymous string literal)
111
+
112
+ ## Use cases
113
+
114
+ ### Use `Parser` to prepend an id to all selectors
115
+
116
+ $sMyId = "#my_id";
117
+ $oParser = new Sabberworm\CSS\Parser($sText);
118
+ $oCss = $oParser->parse();
119
+ foreach($oCss->getAllDeclarationBlocks() as $oBlock) {
120
+ foreach($oBlock->getSelectors() as $oSelector) {
121
+ //Loop over all selector parts (the comma-separated strings in a selector) and prepend the id
122
+ $oSelector->setSelector($sMyId.' '.$oSelector->getSelector());
123
+ }
124
+ }
125
+
126
+ ### Shrink all absolute sizes to half
127
+
128
+ $oParser = new Sabberworm\CSS\Parser($sText);
129
+ $oCss = $oParser->parse();
130
+ foreach($oCss->getAllValues() as $mValue) {
131
+ if($mValue instanceof CSSSize && !$mValue->isRelative()) {
132
+ $mValue->setSize($mValue->getSize()/2);
133
+ }
134
+ }
135
+
136
+ ### Remove unwanted rules
137
+
138
+ $oParser = new Sabberworm\CSS\Parser($sText);
139
+ $oCss = $oParser->parse();
140
+ foreach($oCss->getAllRuleSets() as $oRuleSet) {
141
+ $oRuleSet->removeRule('font-'); //Note that the added dash will make this remove all rules starting with font- (like font-size, font-weight, etc.) as well as a potential font-rule
142
+ $oRuleSet->removeRule('cursor');
143
+ }
144
+
145
+ ### Output
146
+
147
+ To output the entire CSS document into a variable, just use `->__toString()`:
148
+
149
+ $oCssParser = new Sabberworm\CSS\Parser(file_get_contents('somefile.css'));
150
+ $oCssDocument = $oCssParser->parse();
151
+ print $oCssDocument->__toString();
152
+
153
+ ## Examples
154
+
155
+ ### Example 1 (At-Rules)
156
+
157
+ #### Input
158
+
159
+ @charset "utf-8";
160
+
161
+ @font-face {
162
+ font-family: "CrassRoots";
163
+ src: url("../media/cr.ttf")
164
+ }
165
+
166
+ html, body {
167
+ font-size: 1.6em
168
+ }
169
+
170
+ @keyframes mymove {
171
+ from { top: 0px; }
172
+ to { top: 200px; }
173
+ }
174
+
175
+ #### Structure (`var_dump()`)
176
+
177
+ object(Sabberworm\CSS\CSSList\Document)#4 (1) {
178
+ ["aContents":protected]=>
179
+ array(4) {
180
+ [0]=>
181
+ object(Sabberworm\CSS\Property\Charset)#6 (1) {
182
+ ["sCharset":"Sabberworm\CSS\Property\Charset":private]=>
183
+ object(Sabberworm\CSS\Value\String)#5 (1) {
184
+ ["sString":"Sabberworm\CSS\Value\String":private]=>
185
+ string(5) "utf-8"
186
+ }
187
+ }
188
+ [1]=>
189
+ object(Sabberworm\CSS\RuleSet\AtRuleSet)#7 (2) {
190
+ ["sType":"Sabberworm\CSS\RuleSet\AtRuleSet":private]=>
191
+ string(9) "font-face"
192
+ ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=>
193
+ array(2) {
194
+ ["font-family"]=>
195
+ array(1) {
196
+ [0]=>
197
+ object(Sabberworm\CSS\Rule\Rule)#8 (3) {
198
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
199
+ string(11) "font-family"
200
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
201
+ object(Sabberworm\CSS\Value\String)#9 (1) {
202
+ ["sString":"Sabberworm\CSS\Value\String":private]=>
203
+ string(10) "CrassRoots"
204
+ }
205
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
206
+ bool(false)
207
+ }
208
+ }
209
+ ["src"]=>
210
+ array(1) {
211
+ [0]=>
212
+ object(Sabberworm\CSS\Rule\Rule)#10 (3) {
213
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
214
+ string(3) "src"
215
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
216
+ object(Sabberworm\CSS\Value\URL)#11 (1) {
217
+ ["oURL":"Sabberworm\CSS\Value\URL":private]=>
218
+ object(Sabberworm\CSS\Value\String)#12 (1) {
219
+ ["sString":"Sabberworm\CSS\Value\String":private]=>
220
+ string(15) "../media/cr.ttf"
221
+ }
222
+ }
223
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
224
+ bool(false)
225
+ }
226
+ }
227
+ }
228
+ }
229
+ [2]=>
230
+ object(Sabberworm\CSS\RuleSet\DeclarationBlock)#13 (2) {
231
+ ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=>
232
+ array(2) {
233
+ [0]=>
234
+ object(Sabberworm\CSS\Property\Selector)#14 (2) {
235
+ ["sSelector":"Sabberworm\CSS\Property\Selector":private]=>
236
+ string(4) "html"
237
+ ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=>
238
+ NULL
239
+ }
240
+ [1]=>
241
+ object(Sabberworm\CSS\Property\Selector)#15 (2) {
242
+ ["sSelector":"Sabberworm\CSS\Property\Selector":private]=>
243
+ string(4) "body"
244
+ ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=>
245
+ NULL
246
+ }
247
+ }
248
+ ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=>
249
+ array(1) {
250
+ ["font-size"]=>
251
+ array(1) {
252
+ [0]=>
253
+ object(Sabberworm\CSS\Rule\Rule)#16 (3) {
254
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
255
+ string(9) "font-size"
256
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
257
+ object(Sabberworm\CSS\Value\Size)#17 (3) {
258
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
259
+ float(1.6)
260
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
261
+ string(2) "em"
262
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
263
+ bool(false)
264
+ }
265
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
266
+ bool(false)
267
+ }
268
+ }
269
+ }
270
+ }
271
+ [3]=>
272
+ object(Sabberworm\CSS\CSSList\KeyFrame)#18 (3) {
273
+ ["vendorKeyFrame":"Sabberworm\CSS\CSSList\KeyFrame":private]=>
274
+ string(9) "keyframes"
275
+ ["animationName":"Sabberworm\CSS\CSSList\KeyFrame":private]=>
276
+ string(6) "mymove"
277
+ ["aContents":protected]=>
278
+ array(2) {
279
+ [0]=>
280
+ object(Sabberworm\CSS\RuleSet\DeclarationBlock)#19 (2) {
281
+ ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=>
282
+ array(1) {
283
+ [0]=>
284
+ object(Sabberworm\CSS\Property\Selector)#20 (2) {
285
+ ["sSelector":"Sabberworm\CSS\Property\Selector":private]=>
286
+ string(4) "from"
287
+ ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=>
288
+ NULL
289
+ }
290
+ }
291
+ ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=>
292
+ array(1) {
293
+ ["top"]=>
294
+ array(1) {
295
+ [0]=>
296
+ object(Sabberworm\CSS\Rule\Rule)#21 (3) {
297
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
298
+ string(3) "top"
299
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
300
+ object(Sabberworm\CSS\Value\Size)#22 (3) {
301
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
302
+ float(0)
303
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
304
+ string(2) "px"
305
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
306
+ bool(false)
307
+ }
308
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
309
+ bool(false)
310
+ }
311
+ }
312
+ }
313
+ }
314
+ [1]=>
315
+ object(Sabberworm\CSS\RuleSet\DeclarationBlock)#23 (2) {
316
+ ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=>
317
+ array(1) {
318
+ [0]=>
319
+ object(Sabberworm\CSS\Property\Selector)#24 (2) {
320
+ ["sSelector":"Sabberworm\CSS\Property\Selector":private]=>
321
+ string(2) "to"
322
+ ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=>
323
+ NULL
324
+ }
325
+ }
326
+ ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=>
327
+ array(1) {
328
+ ["top"]=>
329
+ array(1) {
330
+ [0]=>
331
+ object(Sabberworm\CSS\Rule\Rule)#25 (3) {
332
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
333
+ string(3) "top"
334
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
335
+ object(Sabberworm\CSS\Value\Size)#26 (3) {
336
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
337
+ float(200)
338
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
339
+ string(2) "px"
340
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
341
+ bool(false)
342
+ }
343
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
344
+ bool(false)
345
+ }
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ #### Output (`__toString()`)
355
+
356
+ @charset "utf-8";@font-face {font-family: "CrassRoots";src: url("../media/cr.ttf");}html, body {font-size: 1.6em;}
357
+ @keyframes mymove {from {top: 0px;}
358
+ to {top: 200px;}
359
+ }
360
+
361
+ ### Example 2 (Values)
362
+
363
+ #### Input
364
+
365
+ #header {
366
+ margin: 10px 2em 1cm 2%;
367
+ font-family: Verdana, Helvetica, "Gill Sans", sans-serif;
368
+ color: red !important;
369
+ }
370
+
371
+ #### Structure (`var_dump()`)
372
+
373
+ object(Sabberworm\CSS\CSSList\Document)#4 (1) {
374
+ ["aContents":protected]=>
375
+ array(1) {
376
+ [0]=>
377
+ object(Sabberworm\CSS\RuleSet\DeclarationBlock)#5 (2) {
378
+ ["aSelectors":"Sabberworm\CSS\RuleSet\DeclarationBlock":private]=>
379
+ array(1) {
380
+ [0]=>
381
+ object(Sabberworm\CSS\Property\Selector)#6 (2) {
382
+ ["sSelector":"Sabberworm\CSS\Property\Selector":private]=>
383
+ string(7) "#header"
384
+ ["iSpecificity":"Sabberworm\CSS\Property\Selector":private]=>
385
+ NULL
386
+ }
387
+ }
388
+ ["aRules":"Sabberworm\CSS\RuleSet\RuleSet":private]=>
389
+ array(3) {
390
+ ["margin"]=>
391
+ array(1) {
392
+ [0]=>
393
+ object(Sabberworm\CSS\Rule\Rule)#7 (3) {
394
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
395
+ string(6) "margin"
396
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
397
+ object(Sabberworm\CSS\Value\RuleValueList)#12 (2) {
398
+ ["aComponents":protected]=>
399
+ array(4) {
400
+ [0]=>
401
+ object(Sabberworm\CSS\Value\Size)#8 (3) {
402
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
403
+ float(10)
404
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
405
+ string(2) "px"
406
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
407
+ bool(false)
408
+ }
409
+ [1]=>
410
+ object(Sabberworm\CSS\Value\Size)#9 (3) {
411
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
412
+ float(2)
413
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
414
+ string(2) "em"
415
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
416
+ bool(false)
417
+ }
418
+ [2]=>
419
+ object(Sabberworm\CSS\Value\Size)#10 (3) {
420
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
421
+ float(1)
422
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
423
+ string(2) "cm"
424
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
425
+ bool(false)
426
+ }
427
+ [3]=>
428
+ object(Sabberworm\CSS\Value\Size)#11 (3) {
429
+ ["fSize":"Sabberworm\CSS\Value\Size":private]=>
430
+ float(2)
431
+ ["sUnit":"Sabberworm\CSS\Value\Size":private]=>
432
+ string(1) "%"
433
+ ["bIsColorComponent":"Sabberworm\CSS\Value\Size":private]=>
434
+ bool(false)
435
+ }
436
+ }
437
+ ["sSeparator":protected]=>
438
+ string(1) " "
439
+ }
440
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
441
+ bool(false)
442
+ }
443
+ }
444
+ ["font-family"]=>
445
+ array(1) {
446
+ [0]=>
447
+ object(Sabberworm\CSS\Rule\Rule)#13 (3) {
448
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
449
+ string(11) "font-family"
450
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
451
+ object(Sabberworm\CSS\Value\RuleValueList)#15 (2) {
452
+ ["aComponents":protected]=>
453
+ array(4) {
454
+ [0]=>
455
+ string(7) "Verdana"
456
+ [1]=>
457
+ string(9) "Helvetica"
458
+ [2]=>
459
+ object(Sabberworm\CSS\Value\String)#14 (1) {
460
+ ["sString":"Sabberworm\CSS\Value\String":private]=>
461
+ string(9) "Gill Sans"
462
+ }
463
+ [3]=>
464
+ string(10) "sans-serif"
465
+ }
466
+ ["sSeparator":protected]=>
467
+ string(1) ","
468
+ }
469
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
470
+ bool(false)
471
+ }
472
+ }
473
+ ["color"]=>
474
+ array(1) {
475
+ [0]=>
476
+ object(Sabberworm\CSS\Rule\Rule)#16 (3) {
477
+ ["sRule":"Sabberworm\CSS\Rule\Rule":private]=>
478
+ string(5) "color"
479
+ ["mValue":"Sabberworm\CSS\Rule\Rule":private]=>
480
+ string(3) "red"
481
+ ["bIsImportant":"Sabberworm\CSS\Rule\Rule":private]=>
482
+ bool(true)
483
+ }
484
+ }
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ #### Output (`__toString()`)
491
+
492
+ #header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;color: red !important;}
493
+
494
+ ## Contributors/Thanks to
495
+
496
+ * [ju1ius](https://github.com/ju1ius) for the specificity parsing code and the ability to expand/compact shorthand properties.
497
+ * [GaryJones](https://github.com/GaryJones) for lots of input and [http://css-specificity.info/](http://css-specificity.info/).
498
+ * [docteurklein](https://github.com/docteurklein) for output formatting and `CSSList->remove()` inspiration.
499
+ * [nicolopignatelli](https://github.com/nicolopignatelli) for PSR-0 compatibility.
500
+ * [diegoembarcadero](https://github.com/diegoembarcadero) for keyframe at-rule parsing.
501
+ * [goetas](https://github.com/goetas) for @namespace at-rule support.
502
+ * [View full list](https://github.com/sabberworm/PHP-CSS-Parser/contributors)
503
+
504
+ ## Misc
505
+
506
+ * Legacy Support: The latest pre-PSR-0 version of this project can be checked with the `0.9.0` tag.
507
+ * Running Tests: To run all unit tests for this project, have `phpunit` installed, `cd` to the `tests` dir and run `phpunit .`.
508
+
509
+ ## License
510
+
511
+ PHP-CSS-Parser is freely distributable under the terms of an MIT-style license.
512
+
513
+ Copyright (c) 2011 Raphael Schweikert, http://sabberworm.com/
514
+
515
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
516
+
517
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
518
+
519
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
includes/PHP-CSS-Parser/autoloader.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ spl_autoload_register(function($class)
4
+ {
5
+ $file = __DIR__.'/lib/'.strtr($class, '\\', '/').'.php';
6
+ if (file_exists($file)) {
7
+ require $file;
8
+ return true;
9
+ }
10
+ });
includes/PHP-CSS-Parser/composer.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "sabberworm/php-css-parser",
3
+ "type": "library",
4
+ "description": "Parser for CSS Files written in PHP",
5
+ "keywords": ["parser", "css", "stylesheet"],
6
+ "homepage": "http://www.sabberworm.com/blog/2010/6/10/php-css-parser",
7
+ "license": "MIT",
8
+ "authors": [
9
+ {"name": "Raphael Schweikert"}
10
+ ],
11
+ "require": {
12
+ "php": ">=5.3.2"
13
+ },
14
+ "autoload": {
15
+ "psr-0": { "Sabberworm\\CSS": "lib/" }
16
+ }
17
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/AtRuleBlockList.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\CSSList;
4
+
5
+ use Sabberworm\CSS\Property\AtRule;
6
+
7
+ /**
8
+ * A RuleSet constructed by an unknown @-rule. @font-face rules are rendered into AtRule objects.
9
+ */
10
+ class AtRuleBlockList extends CSSBlockList implements AtRule {
11
+
12
+ private $sType;
13
+ private $sArgs;
14
+
15
+ public function __construct($sType, $sArgs = '') {
16
+ parent::__construct();
17
+ $this->sType = $sType;
18
+ $this->sArgs = $sArgs;
19
+ }
20
+
21
+ public function atRuleName() {
22
+ return $this->sType;
23
+ }
24
+
25
+ public function atRuleArgs() {
26
+ return $this->sArgs;
27
+ }
28
+
29
+ public function __toString() {
30
+ $sResult = "@{$this->sType} {$this->sArgs}{";
31
+ $sResult .= parent::__toString();
32
+ $sResult .= '}';
33
+ return $sResult;
34
+ }
35
+
36
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSBlockList.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\CSSList;
4
+
5
+ use Sabberworm\CSS\RuleSet\DeclarationBlock;
6
+ use Sabberworm\CSS\RuleSet\RuleSet;
7
+ use Sabberworm\CSS\Property\Selector;
8
+ use Sabberworm\CSS\Rule\Rule;
9
+ use Sabberworm\CSS\Value\ValueList;
10
+ use Sabberworm\CSS\Value\CSSFunction;
11
+
12
+ /**
13
+ * A CSSBlockList is a CSSList whose DeclarationBlocks are guaranteed to contain valid declaration blocks or at-rules.
14
+ * Most CSSLists conform to this category but some at-rules (such as @keyframes) do not.
15
+ */
16
+ abstract class CSSBlockList extends CSSList {
17
+ protected function allDeclarationBlocks(&$aResult) {
18
+ foreach ($this->aContents as $mContent) {
19
+ if ($mContent instanceof DeclarationBlock) {
20
+ $aResult[] = $mContent;
21
+ } else if ($mContent instanceof CSSBlockList) {
22
+ $mContent->allDeclarationBlocks($aResult);
23
+ }
24
+ }
25
+ }
26
+
27
+ protected function allRuleSets(&$aResult) {
28
+ foreach ($this->aContents as $mContent) {
29
+ if ($mContent instanceof RuleSet) {
30
+ $aResult[] = $mContent;
31
+ } else if ($mContent instanceof CSSBlockList) {
32
+ $mContent->allRuleSets($aResult);
33
+ }
34
+ }
35
+ }
36
+
37
+ protected function allValues($oElement, &$aResult, $sSearchString = null, $bSearchInFunctionArguments = false) {
38
+ if ($oElement instanceof CSSBlockList) {
39
+ foreach ($oElement->getContents() as $oContent) {
40
+ $this->allValues($oContent, $aResult, $sSearchString, $bSearchInFunctionArguments);
41
+ }
42
+ } else if ($oElement instanceof RuleSet) {
43
+ foreach ($oElement->getRules($sSearchString) as $oRule) {
44
+ $this->allValues($oRule, $aResult, $sSearchString, $bSearchInFunctionArguments);
45
+ }
46
+ } else if ($oElement instanceof Rule) {
47
+ $this->allValues($oElement->getValue(), $aResult, $sSearchString, $bSearchInFunctionArguments);
48
+ } else if ($oElement instanceof ValueList) {
49
+ if ($bSearchInFunctionArguments || !($oElement instanceof CSSFunction)) {
50
+ foreach ($oElement->getListComponents() as $mComponent) {
51
+ $this->allValues($mComponent, $aResult, $sSearchString, $bSearchInFunctionArguments);
52
+ }
53
+ }
54
+ } else {
55
+ //Non-List Value or String (CSS identifier)
56
+ $aResult[] = $oElement;
57
+ }
58
+ }
59
+
60
+ protected function allSelectors(&$aResult, $sSpecificitySearch = null) {
61
+ $aDeclarationBlocks = array();
62
+ $this->allDeclarationBlocks($aDeclarationBlocks);
63
+ foreach ($aDeclarationBlocks as $oBlock) {
64
+ foreach ($oBlock->getSelectors() as $oSelector) {
65
+ if ($sSpecificitySearch === null) {
66
+ $aResult[] = $oSelector;
67
+ } else {
68
+ $sComparison = "\$bRes = {$oSelector->getSpecificity()} $sSpecificitySearch;";
69
+ eval($sComparison);
70
+ if ($bRes) {
71
+ $aResult[] = $oSelector;
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSList.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\CSSList;
4
+
5
+ use Sabberworm\CSS\RuleSet\DeclarationBlock;
6
+ use Sabberworm\CSS\RuleSet\RuleSet;
7
+ use Sabberworm\CSS\Property\Selector;
8
+ use Sabberworm\CSS\Rule\Rule;
9
+ use Sabberworm\CSS\Value\ValueList;
10
+ use Sabberworm\CSS\Value\CSSFunction;
11
+
12
+ /**
13
+ * A CSSList is the most generic container available. Its contents include RuleSet as well as other CSSList objects.
14
+ * Also, it may contain Import and Charset objects stemming from @-rules.
15
+ */
16
+ abstract class CSSList {
17
+
18
+ protected $aContents;
19
+
20
+ public function __construct() {
21
+ $this->aContents = array();
22
+ }
23
+
24
+ public function append($oItem) {
25
+ $this->aContents[] = $oItem;
26
+ }
27
+
28
+ /**
29
+ * Removes an item from the CSS list.
30
+ * @param RuleSet|Import|Charset|CSSList $oItemToRemove May be a RuleSet (most likely a DeclarationBlock), a Import, a Charset or another CSSList (most likely a MediaQuery)
31
+ */
32
+ public function remove($oItemToRemove) {
33
+ $iKey = array_search($oItemToRemove, $this->aContents, true);
34
+ if ($iKey !== false) {
35
+ unset($this->aContents[$iKey]);
36
+ }
37
+ }
38
+
39
+ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false) {
40
+ if ($mSelector instanceof DeclarationBlock) {
41
+ $mSelector = $mSelector->getSelectors();
42
+ }
43
+ if (!is_array($mSelector)) {
44
+ $mSelector = explode(',', $mSelector);
45
+ }
46
+ foreach ($mSelector as $iKey => &$mSel) {
47
+ if (!($mSel instanceof Selector)) {
48
+ $mSel = new Selector($mSel);
49
+ }
50
+ }
51
+ foreach ($this->aContents as $iKey => $mItem) {
52
+ if (!($mItem instanceof DeclarationBlock)) {
53
+ continue;
54
+ }
55
+ if ($mItem->getSelectors() == $mSelector) {
56
+ unset($this->aContents[$iKey]);
57
+ if (!$bRemoveAll) {
58
+ return;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ public function __toString() {
65
+ $sResult = '';
66
+ foreach ($this->aContents as $oContent) {
67
+ $sResult .= $oContent->__toString();
68
+ }
69
+ return $sResult;
70
+ }
71
+
72
+ public function getContents() {
73
+ return $this->aContents;
74
+ }
75
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/Document.php ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\CSSList;
4
+
5
+ /**
6
+ * The root CSSList of a parsed file. Contains all top-level css contents, mostly declaration blocks, but also any @-rules encountered.
7
+ */
8
+ class Document extends CSSBlockList {
9
+
10
+ /**
11
+ * Gets all DeclarationBlock objects recursively.
12
+ */
13
+ public function getAllDeclarationBlocks() {
14
+ $aResult = array();
15
+ $this->allDeclarationBlocks($aResult);
16
+ return $aResult;
17
+ }
18
+
19
+ /**
20
+ * @deprecated use getAllDeclarationBlocks()
21
+ */
22
+ public function getAllSelectors() {
23
+ return $this->getAllDeclarationBlocks();
24
+ }
25
+
26
+ /**
27
+ * Returns all RuleSet objects found recursively in the tree.
28
+ */
29
+ public function getAllRuleSets() {
30
+ $aResult = array();
31
+ $this->allRuleSets($aResult);
32
+ return $aResult;
33
+ }
34
+
35
+ /**
36
+ * Returns all Value objects found recursively in the tree.
37
+ * @param (object|string) $mElement the CSSList or RuleSet to start the search from (defaults to the whole document). If a string is given, it is used as rule name filter (@see{RuleSet->getRules()}).
38
+ * @param (bool) $bSearchInFunctionArguments whether to also return Value objects used as Function arguments.
39
+ */
40
+ public function getAllValues($mElement = null, $bSearchInFunctionArguments = false) {
41
+ $sSearchString = null;
42
+ if ($mElement === null) {
43
+ $mElement = $this;
44
+ } else if (is_string($mElement)) {
45
+ $sSearchString = $mElement;
46
+ $mElement = $this;
47
+ }
48
+ $aResult = array();
49
+ $this->allValues($mElement, $aResult, $sSearchString, $bSearchInFunctionArguments);
50
+ return $aResult;
51
+ }
52
+
53
+ /**
54
+ * Returns all Selector objects found recursively in the tree.
55
+ * Note that this does not yield the full DeclarationBlock that the selector belongs to (and, currently, there is no way to get to that).
56
+ * @param $sSpecificitySearch An optional filter by specificity. May contain a comparison operator and a number or just a number (defaults to "==").
57
+ * @example getSelectorsBySpecificity('>= 100')
58
+ */
59
+ public function getSelectorsBySpecificity($sSpecificitySearch = null) {
60
+ if (is_numeric($sSpecificitySearch) || is_numeric($sSpecificitySearch[0])) {
61
+ $sSpecificitySearch = "== $sSpecificitySearch";
62
+ }
63
+ $aResult = array();
64
+ $this->allSelectors($aResult, $sSpecificitySearch);
65
+ return $aResult;
66
+ }
67
+
68
+ /**
69
+ * Expands all shorthand properties to their long value
70
+ */
71
+ public function expandShorthands() {
72
+ foreach ($this->getAllDeclarationBlocks() as $oDeclaration) {
73
+ $oDeclaration->expandShorthands();
74
+ }
75
+ }
76
+
77
+ /*
78
+ * Create shorthands properties whenever possible
79
+ */
80
+
81
+ public function createShorthands() {
82
+ foreach ($this->getAllDeclarationBlocks() as $oDeclaration) {
83
+ $oDeclaration->createShorthands();
84
+ }
85
+ }
86
+
87
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/KeyFrame.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\CSSList;
4
+
5
+ use Sabberworm\CSS\Property\AtRule;
6
+
7
+ class KeyFrame extends CSSList implements AtRule {
8
+
9
+ private $vendorKeyFrame;
10
+ private $animationName;
11
+
12
+ public function __construct() {
13
+ parent::__construct();
14
+ $this->vendorKeyFrame = null;
15
+ $this->animationName = null;
16
+ }
17
+
18
+ public function setVendorKeyFrame($vendorKeyFrame) {
19
+ $this->vendorKeyFrame = $vendorKeyFrame;
20
+ }
21
+
22
+ public function getVendorKeyFrame() {
23
+ return $this->vendorKeyFrame;
24
+ }
25
+
26
+ public function setAnimationName($animationName) {
27
+ $this->animationName = $animationName;
28
+ }
29
+
30
+ public function getAnimationName() {
31
+ return $this->animationName;
32
+ }
33
+
34
+ public function __toString() {
35
+ $sResult = "@{$this->vendorKeyFrame} {$this->animationName} {";
36
+ $sResult .= parent::__toString();
37
+ $sResult .= '}';
38
+ return $sResult;
39
+ }
40
+
41
+ public function atRuleName() {
42
+ return $this->vendorKeyFrame;
43
+ }
44
+
45
+ public function atRuleArgs() {
46
+ return $this->animationName;
47
+ }
48
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parser.php ADDED
@@ -0,0 +1,629 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS;
4
+
5
+ use Sabberworm\CSS\CSSList\CSSList;
6
+ use Sabberworm\CSS\CSSList\Document;
7
+ use Sabberworm\CSS\CSSList\KeyFrame;
8
+ use Sabberworm\CSS\Property\AtRule;
9
+ use Sabberworm\CSS\Property\Import;
10
+ use Sabberworm\CSS\Property\Charset;
11
+ use Sabberworm\CSS\Property\CSSNamespace;
12
+ use Sabberworm\CSS\RuleSet\AtRuleSet;
13
+ use Sabberworm\CSS\CSSList\AtRuleBlockList;
14
+ use Sabberworm\CSS\RuleSet\DeclarationBlock;
15
+ use Sabberworm\CSS\Value\CSSFunction;
16
+ use Sabberworm\CSS\Value\RuleValueList;
17
+ use Sabberworm\CSS\Value\Size;
18
+ use Sabberworm\CSS\Value\Color;
19
+ use Sabberworm\CSS\Value\URL;
20
+ use Sabberworm\CSS\Value\String;
21
+ use Sabberworm\CSS\Rule\Rule;
22
+ use Sabberworm\CSS\Parsing\UnexpectedTokenException;
23
+
24
+ /**
25
+ * Parser class parses CSS from text into a data structure.
26
+ */
27
+ class Parser {
28
+
29
+ private $sText;
30
+ private $iCurrentPosition;
31
+ private $oParserSettings;
32
+ private $sCharset;
33
+ private $iLength;
34
+ private $peekCache = null;
35
+ private $blockRules;
36
+ private $aSizeUnits;
37
+
38
+ public function __construct($sText, Settings $oParserSettings = null) {
39
+ $this->sText = $sText;
40
+ $this->iCurrentPosition = 0;
41
+ if ($oParserSettings === null) {
42
+ $oParserSettings = Settings::create();
43
+ }
44
+ $this->oParserSettings = $oParserSettings;
45
+ $this->blockRules = explode('/', AtRule::BLOCK_RULES);
46
+
47
+ foreach (explode('/', Size::ABSOLUTE_SIZE_UNITS.'/'.Size::RELATIVE_SIZE_UNITS.'/'.Size::NON_SIZE_UNITS) as $val) {
48
+ $iSize = strlen($val);
49
+ if(!isset($this->aSizeUnits[$iSize])) {
50
+ $this->aSizeUnits[$iSize] = array();
51
+ }
52
+ $this->aSizeUnits[$iSize][strtolower($val)] = $val;
53
+ }
54
+ ksort($this->aSizeUnits, SORT_NUMERIC);
55
+ }
56
+
57
+ public function setCharset($sCharset) {
58
+ $this->sCharset = $sCharset;
59
+ $this->iLength = $this->strlen($this->sText);
60
+ }
61
+
62
+ public function getCharset() {
63
+ return $this->sCharset;
64
+ }
65
+
66
+ public function parse() {
67
+ $this->setCharset($this->oParserSettings->sDefaultCharset);
68
+ $oResult = new Document();
69
+ $this->parseDocument($oResult);
70
+ return $oResult;
71
+ }
72
+
73
+ private function parseDocument(Document $oDocument) {
74
+ $this->consumeWhiteSpace();
75
+ $this->parseList($oDocument, true);
76
+ }
77
+
78
+ private function parseList(CSSList $oList, $bIsRoot = false) {
79
+ while (!$this->isEnd()) {
80
+ if ($this->comes('@')) {
81
+ $oList->append($this->parseAtRule());
82
+ } else if ($this->comes('}')) {
83
+ $this->consume('}');
84
+ if ($bIsRoot) {
85
+ throw new \Exception("Unopened {");
86
+ } else {
87
+ return;
88
+ }
89
+ } else {
90
+ if($this->oParserSettings->bLenientParsing) {
91
+ try {
92
+ $oList->append($this->parseSelector());
93
+ } catch (UnexpectedTokenException $e) {}
94
+ } else {
95
+ $oList->append($this->parseSelector());
96
+ }
97
+ }
98
+ $this->consumeWhiteSpace();
99
+ }
100
+ if (!$bIsRoot) {
101
+ throw new \Exception("Unexpected end of document");
102
+ }
103
+ }
104
+
105
+ private function parseAtRule() {
106
+ $this->consume('@');
107
+ $sIdentifier = $this->parseIdentifier();
108
+ $this->consumeWhiteSpace();
109
+ if ($sIdentifier === 'import') {
110
+ $oLocation = $this->parseURLValue();
111
+ $this->consumeWhiteSpace();
112
+ $sMediaQuery = null;
113
+ if (!$this->comes(';')) {
114
+ $sMediaQuery = $this->consumeUntil(';');
115
+ }
116
+ $this->consume(';');
117
+ return new Import($oLocation, $sMediaQuery);
118
+ } else if ($sIdentifier === 'charset') {
119
+ $sCharset = $this->parseStringValue();
120
+ $this->consumeWhiteSpace();
121
+ $this->consume(';');
122
+ $this->setCharset($sCharset->getString());
123
+ return new Charset($sCharset);
124
+ } else if ($this->identifierIs($sIdentifier, 'keyframes')) {
125
+ $oResult = new KeyFrame();
126
+ $oResult->setVendorKeyFrame($sIdentifier);
127
+ $oResult->setAnimationName(trim($this->consumeUntil('{', false, true)));
128
+ $this->consumeWhiteSpace();
129
+ $this->parseList($oResult);
130
+ return $oResult;
131
+ } else if ($sIdentifier === 'namespace') {
132
+ $sPrefix = null;
133
+ $mUrl = $this->parsePrimitiveValue();
134
+ if (!$this->comes(';')) {
135
+ $sPrefix = $mUrl;
136
+ $mUrl = $this->parsePrimitiveValue();
137
+ }
138
+ $this->consume(';');
139
+ if ($sPrefix !== null && !is_string($sPrefix)) {
140
+ throw new \Exception('Wrong namespace prefix '.$sPrefix);
141
+ }
142
+ if (!($mUrl instanceof String || $mUrl instanceof URL)) {
143
+ throw new \Exception('Wrong namespace url of invalid type '.$mUrl);
144
+ }
145
+ return new CSSNamespace($mUrl, $sPrefix);
146
+ } else {
147
+ //Unknown other at rule (font-face or such)
148
+ $sArgs = $this->consumeUntil('{', false, true);
149
+ $this->consumeWhiteSpace();
150
+ $bUseRuleSet = true;
151
+ foreach($this->blockRules as $sBlockRuleName) {
152
+ if($this->identifierIs($sIdentifier, $sBlockRuleName)) {
153
+ $bUseRuleSet = false;
154
+ break;
155
+ }
156
+ }
157
+ if($bUseRuleSet) {
158
+ $oAtRule = new AtRuleSet($sIdentifier, $sArgs);
159
+ $this->parseRuleSet($oAtRule);
160
+ } else {
161
+ $oAtRule = new AtRuleBlockList($sIdentifier, $sArgs);
162
+ $this->parseList($oAtRule);
163
+ }
164
+ return $oAtRule;
165
+ }
166
+ }
167
+
168
+ private function parseIdentifier($bAllowFunctions = true, $bIgnoreCase = true) {
169
+ $sResult = $this->parseCharacter(true);
170
+ if ($sResult === null) {
171
+ throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier');
172
+ }
173
+ $sCharacter = null;
174
+ while (($sCharacter = $this->parseCharacter(true)) !== null) {
175
+ $sResult .= $sCharacter;
176
+ }
177
+ if ($bIgnoreCase) {
178
+ $sResult = $this->strtolower($sResult);
179
+ }
180
+ if ($bAllowFunctions && $this->comes('(')) {
181
+ $this->consume('(');
182
+ $aArguments = $this->parseValue(array('=', ' ', ','));
183
+ $sResult = new CSSFunction($sResult, $aArguments);
184
+ $this->consume(')');
185
+ }
186
+ return $sResult;
187
+ }
188
+
189
+ private function parseStringValue() {
190
+ $sBegin = $this->peek();
191
+ $sQuote = null;
192
+ if ($sBegin === "'") {
193
+ $sQuote = "'";
194
+ } else if ($sBegin === '"') {
195
+ $sQuote = '"';
196
+ }
197
+ if ($sQuote !== null) {
198
+ $this->consume($sQuote);
199
+ }
200
+ $sResult = "";
201
+ $sContent = null;
202
+ if ($sQuote === null) {
203
+ //Unquoted strings end in whitespace or with braces, brackets, parentheses
204
+ while (!preg_match('/[\\s{}()<>\\[\\]]/isu', $this->peek())) {
205
+ $sResult .= $this->parseCharacter(false);
206
+ }
207
+ } else {
208
+ while (!$this->comes($sQuote)) {
209
+ $sContent = $this->parseCharacter(false);
210
+ if ($sContent === null) {
211
+ throw new \Exception("Non-well-formed quoted string {$this->peek(3)}");
212
+ }
213
+ $sResult .= $sContent;
214
+ }
215
+ $this->consume($sQuote);
216
+ }
217
+ return new String($sResult);
218
+ }
219
+
220
+ private function parseCharacter($bIsForIdentifier) {
221
+ if ($this->peek() === '\\') {
222
+ $this->consume('\\');
223
+ if ($this->comes('\n') || $this->comes('\r')) {
224
+ return '';
225
+ }
226
+ if (preg_match('/[0-9a-fA-F]/Su', $this->peek()) === 0) {
227
+ return $this->consume(1);
228
+ }
229
+ $sUnicode = $this->consumeExpression('/^[0-9a-fA-F]{1,6}/u');
230
+ if ($this->strlen($sUnicode) < 6) {
231
+ //Consume whitespace after incomplete unicode escape
232
+ if (preg_match('/\\s/isSu', $this->peek())) {
233
+ if ($this->comes('\r\n')) {
234
+ $this->consume(2);
235
+ } else {
236
+ $this->consume(1);
237
+ }
238
+ }
239
+ }
240
+ $iUnicode = intval($sUnicode, 16);
241
+ $sUtf32 = "";
242
+ for ($i = 0; $i < 4; ++$i) {
243
+ $sUtf32 .= chr($iUnicode & 0xff);
244
+ $iUnicode = $iUnicode >> 8;
245
+ }
246
+ return iconv('utf-32le', $this->sCharset, $sUtf32);
247
+ }
248
+ if ($bIsForIdentifier) {
249
+ $peek = ord($this->peek());
250
+ // Ranges: a-z A-Z 0-9 - _
251
+ if (($peek >= 97 && $peek <= 122) ||
252
+ ($peek >= 65 && $peek <= 90) ||
253
+ ($peek >= 48 && $peek <= 57) ||
254
+ ($peek === 45) ||
255
+ ($peek === 95) ||
256
+ ($peek > 0xa1)) {
257
+ return $this->consume(1);
258
+ }
259
+ } else {
260
+ return $this->consume(1);
261
+ }
262
+ return null;
263
+ }
264
+
265
+ private function parseSelector() {
266
+ $oResult = new DeclarationBlock();
267
+ $oResult->setSelector($this->consumeUntil('{', false, true));
268
+ $this->consumeWhiteSpace();
269
+ $this->parseRuleSet($oResult);
270
+ return $oResult;
271
+ }
272
+
273
+ private function parseRuleSet($oRuleSet) {
274
+ while ($this->comes(';')) {
275
+ $this->consume(';');
276
+ $this->consumeWhiteSpace();
277
+ }
278
+ while (!$this->comes('}')) {
279
+ $oRule = null;
280
+ if($this->oParserSettings->bLenientParsing) {
281
+ try {
282
+ $oRule = $this->parseRule();
283
+ } catch (UnexpectedTokenException $e) {
284
+ try {
285
+ $sConsume = $this->consumeUntil(array("\n", ";", '}'), true);
286
+ // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
287
+ if($this->streql($this->substr($sConsume, $this->strlen($sConsume)-1, 1), '}')) {
288
+ --$this->iCurrentPosition;
289
+ $this->peekCache = null;
290
+ } else {
291
+ $this->consumeWhiteSpace();
292
+ while ($this->comes(';')) {
293
+ $this->consume(';');
294
+ }
295
+ }
296
+ } catch (UnexpectedTokenException $e) {
297
+ // We’ve reached the end of the document. Just close the RuleSet.
298
+ return;
299
+ }
300
+ }
301
+ } else {
302
+ $oRule = $this->parseRule();
303
+ }
304
+ if($oRule) {
305
+ $oRuleSet->addRule($oRule);
306
+ }
307
+ $this->consumeWhiteSpace();
308
+ }
309
+ $this->consume('}');
310
+ }
311
+
312
+ private function parseRule() {
313
+ $oRule = new Rule($this->parseIdentifier());
314
+ $this->consumeWhiteSpace();
315
+ $this->consume(':');
316
+ $oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule()));
317
+ $oRule->setValue($oValue);
318
+ if ($this->comes('!')) {
319
+ $this->consume('!');
320
+ $this->consumeWhiteSpace();
321
+ $this->consume('important');
322
+ $oRule->setIsImportant(true);
323
+ }
324
+ while ($this->comes(';')) {
325
+ $this->consume(';');
326
+ $this->consumeWhiteSpace();
327
+ }
328
+ return $oRule;
329
+ }
330
+
331
+ private function parseValue($aListDelimiters) {
332
+ $aStack = array();
333
+ $this->consumeWhiteSpace();
334
+ //Build a list of delimiters and parsed values
335
+ while (!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) {
336
+ if (count($aStack) > 0) {
337
+ $bFoundDelimiter = false;
338
+ foreach ($aListDelimiters as $sDelimiter) {
339
+ if ($this->comes($sDelimiter)) {
340
+ array_push($aStack, $this->consume($sDelimiter));
341
+ $this->consumeWhiteSpace();
342
+ $bFoundDelimiter = true;
343
+ break;
344
+ }
345
+ }
346
+ if (!$bFoundDelimiter) {
347
+ //Whitespace was the list delimiter
348
+ array_push($aStack, ' ');
349
+ }
350
+ }
351
+ array_push($aStack, $this->parsePrimitiveValue());
352
+ $this->consumeWhiteSpace();
353
+ }
354
+ //Convert the list to list objects
355
+ foreach ($aListDelimiters as $sDelimiter) {
356
+ if (count($aStack) === 1) {
357
+ return $aStack[0];
358
+ }
359
+ $iStartPosition = null;
360
+ while (($iStartPosition = array_search($sDelimiter, $aStack, true)) !== false) {
361
+ $iLength = 2; //Number of elements to be joined
362
+ for ($i = $iStartPosition + 2; $i < count($aStack); $i+=2, ++$iLength) {
363
+ if ($sDelimiter !== $aStack[$i]) {
364
+ break;
365
+ }
366
+ }
367
+ $oList = new RuleValueList($sDelimiter);
368
+ for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i+=2) {
369
+ $oList->addListComponent($aStack[$i]);
370
+ }
371
+ array_splice($aStack, $iStartPosition - 1, $iLength * 2 - 1, array($oList));
372
+ }
373
+ }
374
+ return $aStack[0];
375
+ }
376
+
377
+ private static function listDelimiterForRule($sRule) {
378
+ if (preg_match('/^font($|-)/', $sRule)) {
379
+ return array(',', '/', ' ');
380
+ }
381
+ return array(',', ' ', '/');
382
+ }
383
+
384
+ private function parsePrimitiveValue() {
385
+ $oValue = null;
386
+ $this->consumeWhiteSpace();
387
+ if (is_numeric($this->peek()) || ($this->comes('-.') && is_numeric($this->peek(1, 2))) || (($this->comes('-') || $this->comes('.')) && is_numeric($this->peek(1, 1)))) {
388
+ $oValue = $this->parseNumericValue();
389
+ } else if ($this->comes('#') || $this->comes('rgb', true) || $this->comes('hsl', true)) {
390
+ $oValue = $this->parseColorValue();
391
+ } else if ($this->comes('url', true)) {
392
+ $oValue = $this->parseURLValue();
393
+ } else if ($this->comes("'") || $this->comes('"')) {
394
+ $oValue = $this->parseStringValue();
395
+ } else {
396
+ $oValue = $this->parseIdentifier(true, false);
397
+ }
398
+ $this->consumeWhiteSpace();
399
+ return $oValue;
400
+ }
401
+
402
+ private function parseNumericValue($bForColor = false) {
403
+ $sSize = '';
404
+ if ($this->comes('-')) {
405
+ $sSize .= $this->consume('-');
406
+ }
407
+ while (is_numeric($this->peek()) || $this->comes('.')) {
408
+ if ($this->comes('.')) {
409
+ $sSize .= $this->consume('.');
410
+ } else {
411
+ $sSize .= $this->consume(1);
412
+ }
413
+ }
414
+
415
+ $sUnit = null;
416
+ foreach ($this->aSizeUnits as $iLength => &$aValues) {
417
+ if(($sUnit = @$aValues[strtolower($this->peek($iLength))]) !== null) {
418
+ $this->consume($iLength);
419
+ break;
420
+ }
421
+ }
422
+ return new Size(floatval($sSize), $sUnit, $bForColor);
423
+ }
424
+
425
+ private function parseColorValue() {
426
+ $aColor = array();
427
+ if ($this->comes('#')) {
428
+ $this->consume('#');
429
+ $sValue = $this->parseIdentifier(false);
430
+ if ($this->strlen($sValue) === 3) {
431
+ $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2];
432
+ }
433
+ $aColor = array('r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true));
434
+ } else {
435
+ $sColorMode = $this->parseIdentifier(false);
436
+ $this->consumeWhiteSpace();
437
+ $this->consume('(');
438
+ $iLength = $this->strlen($sColorMode);
439
+ for ($i = 0; $i < $iLength; ++$i) {
440
+ $this->consumeWhiteSpace();
441
+ $aColor[$sColorMode[$i]] = $this->parseNumericValue(true);
442
+ $this->consumeWhiteSpace();
443
+ if ($i < ($iLength - 1)) {
444
+ $this->consume(',');
445
+ }
446
+ }
447
+ $this->consume(')');
448
+ }
449
+ return new Color($aColor);
450
+ }
451
+
452
+ private function parseURLValue() {
453
+ $bUseUrl = $this->comes('url', true);
454
+ if ($bUseUrl) {
455
+ $this->consume('url');
456
+ $this->consumeWhiteSpace();
457
+ $this->consume('(');
458
+ }
459
+ $this->consumeWhiteSpace();
460
+ $oResult = new URL($this->parseStringValue());
461
+ if ($bUseUrl) {
462
+ $this->consumeWhiteSpace();
463
+ $this->consume(')');
464
+ }
465
+ return $oResult;
466
+ }
467
+
468
+ /**
469
+ * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. We need to check for these versions too.
470
+ */
471
+ private function identifierIs($sIdentifier, $sMatch) {
472
+ return (strcasecmp($sIdentifier, $sMatch) === 0)
473
+ ?: preg_match("/^(-\\w+-)?$sMatch$/i", $sIdentifier) === 1;
474
+ }
475
+
476
+ private function comes($sString, $bCaseInsensitive = false) {
477
+ $sPeek = $this->peek(strlen($sString));
478
+ return ($sPeek == '')
479
+ ? false
480
+ : $this->streql($sPeek, $sString, $bCaseInsensitive);
481
+ }
482
+
483
+ private function peek($iLength = 1, $iOffset = 0) {
484
+ if (($peek = (!$iOffset && ($iLength === 1))) &&
485
+ !is_null($this->peekCache)) {
486
+ return $this->peekCache;
487
+ }
488
+ $iOffset += $this->iCurrentPosition;
489
+ if ($iOffset >= $this->iLength) {
490
+ return '';
491
+ }
492
+ $iLength = min($iLength, $this->iLength-$iOffset);
493
+ $out = $this->substr($this->sText, $iOffset, $iLength);
494
+ if ($peek) {
495
+ $this->peekCache = $out;
496
+ }
497
+ return $out;
498
+ }
499
+
500
+ private function consume($mValue = 1) {
501
+ if (is_string($mValue)) {
502
+ $iLength = $this->strlen($mValue);
503
+ if (!$this->streql($this->substr($this->sText, $this->iCurrentPosition, $iLength), $mValue)) {
504
+ throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)));
505
+ }
506
+ $this->iCurrentPosition += $this->strlen($mValue);
507
+ $this->peekCache = null;
508
+ return $mValue;
509
+ } else {
510
+ if ($this->iCurrentPosition + $mValue > $this->iLength) {
511
+ throw new UnexpectedTokenException($mValue, $this->peek(5), 'count');
512
+ }
513
+ $sResult = $this->substr($this->sText, $this->iCurrentPosition, $mValue);
514
+ $this->iCurrentPosition += $mValue;
515
+ $this->peekCache = null;
516
+ return $sResult;
517
+ }
518
+ }
519
+
520
+ private function consumeExpression($mExpression) {
521
+ $aMatches;
522
+ if (preg_match($mExpression, $this->inputLeft(), $aMatches, PREG_OFFSET_CAPTURE) === 1) {
523
+ return $this->consume($aMatches[0][0]);
524
+ }
525
+ throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression');
526
+ }
527
+
528
+ private function consumeWhiteSpace() {
529
+ do {
530
+ while (preg_match('/\\s/isSu', $this->peek()) === 1) {
531
+ $this->consume(1);
532
+ }
533
+ if($this->oParserSettings->bLenientParsing) {
534
+ try {
535
+ $bHasComment = $this->consumeComment();
536
+ } catch(UnexpectedTokenException $e) {
537
+ // When we can’t find the end of a comment, we assume the document is finished.
538
+ $this->iCurrentPosition = $this->iLength;
539
+ return;
540
+ }
541
+ } else {
542
+ $bHasComment = $this->consumeComment();
543
+ }
544
+ } while($bHasComment);
545
+ }
546
+
547
+ private function consumeComment() {
548
+ if ($this->comes('/*')) {
549
+ $this->consume(2);
550
+ while ($this->consumeUntil('*', false, true)) {
551
+ if ($this->comes('/')) {
552
+ $this->consume(1);
553
+ return true;
554
+ }
555
+ }
556
+ }
557
+ return false;
558
+ }
559
+
560
+ private function isEnd() {
561
+ return $this->iCurrentPosition >= $this->iLength;
562
+ }
563
+
564
+ private function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false) {
565
+ $aEnd = is_array($aEnd) ? $aEnd : array($aEnd);
566
+ $out = '';
567
+ $start = $this->iCurrentPosition;
568
+
569
+ while (($char = $this->consume(1)) !== '') {
570
+ if (in_array($char, $aEnd)) {
571
+ if ($bIncludeEnd) {
572
+ $out .= $char;
573
+ } elseif (!$consumeEnd) {
574
+ $this->iCurrentPosition -= $this->strlen($char);
575
+ }
576
+ return $out;
577
+ }
578
+ $out .= $char;
579
+ }
580
+
581
+ $this->iCurrentPosition = $start;
582
+ throw new UnexpectedTokenException('One of ("'.implode('","', $aEnd).'")', $this->peek(5), 'search');
583
+ }
584
+
585
+ private function inputLeft() {
586
+ return $this->substr($this->sText, $this->iCurrentPosition, -1);
587
+ }
588
+
589
+ private function substr($sString, $iStart, $iLength) {
590
+ if ($this->oParserSettings->bMultibyteSupport) {
591
+ return mb_substr($sString, $iStart, $iLength, $this->sCharset);
592
+ } else {
593
+ return substr($sString, $iStart, $iLength);
594
+ }
595
+ }
596
+
597
+ private function strlen($sString) {
598
+ if ($this->oParserSettings->bMultibyteSupport) {
599
+ return mb_strlen($sString, $this->sCharset);
600
+ } else {
601
+ return strlen($sString);
602
+ }
603
+ }
604
+
605
+ private function streql($sString1, $sString2, $bCaseInsensitive = true) {
606
+ if($bCaseInsensitive) {
607
+ return $this->strtolower($sString1) === $this->strtolower($sString2);
608
+ } else {
609
+ return $sString1 === $sString2;
610
+ }
611
+ }
612
+
613
+ private function strtolower($sString) {
614
+ if ($this->oParserSettings->bMultibyteSupport) {
615
+ return mb_strtolower($sString, $this->sCharset);
616
+ } else {
617
+ return strtolower($sString);
618
+ }
619
+ }
620
+
621
+ private function strpos($sString, $sNeedle, $iOffset) {
622
+ if ($this->oParserSettings->bMultibyteSupport) {
623
+ return mb_strpos($sString, $sNeedle, $iOffset, $this->sCharset);
624
+ } else {
625
+ return strpos($sString, $sNeedle, $iOffset);
626
+ }
627
+ }
628
+
629
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parsing/UnexpectedTokenException.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Parsing;
4
+
5
+ /**
6
+ * Thrown if the CSS parsers encounters a token it did not expect
7
+ */
8
+ class UnexpectedTokenException extends \Exception {
9
+ private $sExpected;
10
+ private $sFound;
11
+ // Possible values: literal, identifier, count, expression, search
12
+ private $sMatchType;
13
+
14
+ public function __construct($sExpected, $sFound, $sMatchType = 'literal') {
15
+ $this->sExpected = $sExpected;
16
+ $this->sFound = $sFound;
17
+ $this->sMatchType = $sMatchType;
18
+ $sMessage = "Token “{$sExpected}” ({$sMatchType}) not found. Got “{$sFound}”.";
19
+ if($this->sMatchType === 'search') {
20
+ $sMessage = "Search for “{$sExpected}” returned no results. Context: “{$sFound}”.";
21
+ } else if($this->sMatchType === 'count') {
22
+ $sMessage = "Next token was expected to have {$sExpected} chars. Context: “{$sFound}”.";
23
+ } else if($this->sMatchType === 'identifier') {
24
+ $sMessage = "Identifier expected. Got “{$sFound}”";
25
+ }
26
+ parent::__construct($sMessage);
27
+ }
28
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/AtRule.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Property;
4
+
5
+ interface AtRule {
6
+ const BLOCK_RULES = 'media/document/supports/region-style/font-feature-values';
7
+ // Since there are more set rules than block rules, we’re whitelisting the block rules and have anything else be treated as a set rule.
8
+ const SET_RULES = 'font-face/counter-style/page/swash/styleset/annotation'; //…and more font-specific ones (to be used inside font-feature-values)
9
+
10
+ public function atRuleName();
11
+ public function atRuleArgs();
12
+ public function __toString();
13
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/CSSNamespace.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Property;
4
+
5
+ /**
6
+ * CSSNamespace represents an @namespace rule.
7
+ */
8
+ class CSSNamespace implements AtRule {
9
+ private $mUrl;
10
+ private $sPrefix;
11
+
12
+ public function __construct($mUrl, $sPrefix = null) {
13
+ $this->mUrl = $mUrl;
14
+ $this->sPrefix = $sPrefix;
15
+ }
16
+
17
+ public function __toString() {
18
+ return '@namespace '.($this->sPrefix === null ? '' : $this->sPrefix.' ').$this->mUrl->__toString().';';
19
+ }
20
+
21
+ public function getUrl() {
22
+ return $this->mUrl;
23
+ }
24
+
25
+ public function getPrefix() {
26
+ return $this->sPrefix;
27
+ }
28
+
29
+ public function setUrl($mUrl) {
30
+ $this->mUrl = $mUrl;
31
+ }
32
+
33
+ public function setPrefix($sPrefix) {
34
+ $this->sPrefix = $sPrefix;
35
+ }
36
+
37
+ public function atRuleName() {
38
+ return 'namespace';
39
+ }
40
+
41
+ public function atRuleArgs() {
42
+ $aResult = array($this->mUrl);
43
+ if($this->sPrefix) {
44
+ array_unshift($aResult, $this->sPrefix);
45
+ }
46
+ return $aResult;
47
+ }
48
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Charset.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Property;
4
+
5
+ /**
6
+ * Class representing an @charset rule.
7
+ * The following restrictions apply:
8
+ * • May not be found in any CSSList other than the Document.
9
+ * • May only appear at the very top of a Document’s contents.
10
+ * • Must not appear more than once.
11
+ */
12
+ class Charset implements AtRule {
13
+
14
+ private $sCharset;
15
+
16
+ public function __construct($sCharset) {
17
+ $this->sCharset = $sCharset;
18
+ }
19
+
20
+ public function setCharset($sCharset) {
21
+ $this->sCharset = $sCharset;
22
+ }
23
+
24
+ public function getCharset() {
25
+ return $this->sCharset;
26
+ }
27
+
28
+ public function __toString() {
29
+ return "@charset {$this->sCharset->__toString()};";
30
+ }
31
+
32
+ public function atRuleName() {
33
+ return 'charset';
34
+ }
35
+
36
+ public function atRuleArgs() {
37
+ return $this->sCharset;
38
+ }
39
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Import.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Property;
4
+
5
+ use Sabberworm\CSS\Value\URL;
6
+
7
+ /**
8
+ * Class representing an @import rule.
9
+ */
10
+ class Import implements AtRule {
11
+ private $oLocation;
12
+ private $sMediaQuery;
13
+
14
+ public function __construct(URL $oLocation, $sMediaQuery) {
15
+ $this->oLocation = $oLocation;
16
+ $this->sMediaQuery = $sMediaQuery;
17
+ }
18
+
19
+ public function setLocation($oLocation) {
20
+ $this->oLocation = $oLocation;
21
+ }
22
+
23
+ public function getLocation() {
24
+ return $this->oLocation;
25
+ }
26
+
27
+ public function __toString() {
28
+ return "@import ".$this->oLocation->__toString().($this->sMediaQuery === null ? '' : ' '.$this->sMediaQuery).';';
29
+ }
30
+
31
+ public function atRuleName() {
32
+ return 'import';
33
+ }
34
+
35
+ public function atRuleArgs() {
36
+ $aResult = array($this->oLocation);
37
+ if($this->sMediaQuery) {
38
+ array_push($aResult, $this->sMediaQuery);
39
+ }
40
+ return $aResult;
41
+ }
42
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Selector.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Property;
4
+
5
+ /**
6
+ * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this class.
7
+ */
8
+ class Selector {
9
+
10
+ const
11
+ NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
12
+ (\.[\w]+) # classes
13
+ |
14
+ \[(\w+) # attributes
15
+ |
16
+ (\:( # pseudo classes
17
+ link|visited|active
18
+ |hover|focus
19
+ |lang
20
+ |target
21
+ |enabled|disabled|checked|indeterminate
22
+ |root
23
+ |nth-child|nth-last-child|nth-of-type|nth-last-of-type
24
+ |first-child|last-child|first-of-type|last-of-type
25
+ |only-child|only-of-type
26
+ |empty|contains
27
+ ))
28
+ /ix',
29
+ ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
30
+ ((^|[\s\+\>\~]+)[\w]+ # elements
31
+ |
32
+ \:{1,2}( # pseudo-elements
33
+ after|before
34
+ |first-letter|first-line
35
+ |selection
36
+ )
37
+ )/ix';
38
+
39
+ private $sSelector;
40
+ private $iSpecificity;
41
+
42
+ public function __construct($sSelector, $bCalculateSpecificity = false) {
43
+ $this->setSelector($sSelector);
44
+ if ($bCalculateSpecificity) {
45
+ $this->getSpecificity();
46
+ }
47
+ }
48
+
49
+ public function getSelector() {
50
+ return $this->sSelector;
51
+ }
52
+
53
+ public function setSelector($sSelector) {
54
+ $this->sSelector = trim($sSelector);
55
+ $this->iSpecificity = null;
56
+ }
57
+
58
+ public function __toString() {
59
+ return $this->getSelector();
60
+ }
61
+
62
+ public function getSpecificity() {
63
+ if ($this->iSpecificity === null) {
64
+ $a = 0;
65
+ /// @todo should exclude \# as well as "#"
66
+ $aMatches;
67
+ $b = substr_count($this->sSelector, '#');
68
+ $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches);
69
+ $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches);
70
+ $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
71
+ }
72
+ return $this->iSpecificity;
73
+ }
74
+
75
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Rule/Rule.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Rule;
4
+
5
+ use Sabberworm\CSS\Value\RuleValueList;
6
+ use Sabberworm\CSS\Value\Value;
7
+
8
+ /**
9
+ * RuleSets contains Rule objects which always have a key and a value.
10
+ * In CSS, Rules are expressed as follows: “key: value[0][0] value[0][1], value[1][0] value[1][1];”
11
+ */
12
+ class Rule {
13
+
14
+ private $sRule;
15
+ private $mValue;
16
+ private $bIsImportant;
17
+
18
+ public function __construct($sRule) {
19
+ $this->sRule = $sRule;
20
+ $this->mValue = null;
21
+ $this->bIsImportant = false;
22
+ }
23
+
24
+ public function setRule($sRule) {
25
+ $this->sRule = $sRule;
26
+ }
27
+
28
+ public function getRule() {
29
+ return $this->sRule;
30
+ }
31
+
32
+ public function getValue() {
33
+ return $this->mValue;
34
+ }
35
+
36
+ public function setValue($mValue) {
37
+ $this->mValue = $mValue;
38
+ }
39
+
40
+ /**
41
+ * @deprecated Old-Style 2-dimensional array given. Retained for (some) backwards-compatibility. Use setValue() instead and wrapp the value inside a RuleValueList if necessary.
42
+ */
43
+ public function setValues($aSpaceSeparatedValues) {
44
+ $oSpaceSeparatedList = null;
45
+ if (count($aSpaceSeparatedValues) > 1) {
46
+ $oSpaceSeparatedList = new RuleValueList(' ');
47
+ }
48
+ foreach ($aSpaceSeparatedValues as $aCommaSeparatedValues) {
49
+ $oCommaSeparatedList = null;
50
+ if (count($aCommaSeparatedValues) > 1) {
51
+ $oCommaSeparatedList = new RuleValueList(',');
52
+ }
53
+ foreach ($aCommaSeparatedValues as $mValue) {
54
+ if (!$oSpaceSeparatedList && !$oCommaSeparatedList) {
55
+ $this->mValue = $mValue;
56
+ return $mValue;
57
+ }
58
+ if ($oCommaSeparatedList) {
59
+ $oCommaSeparatedList->addListComponent($mValue);
60
+ } else {
61
+ $oSpaceSeparatedList->addListComponent($mValue);
62
+ }
63
+ }
64
+ if (!$oSpaceSeparatedList) {
65
+ $this->mValue = $oCommaSeparatedList;
66
+ return $oCommaSeparatedList;
67
+ } else {
68
+ $oSpaceSeparatedList->addListComponent($oCommaSeparatedList);
69
+ }
70
+ }
71
+ $this->mValue = $oSpaceSeparatedList;
72
+ return $oSpaceSeparatedList;
73
+ }
74
+
75
+ /**
76
+ * @deprecated Old-Style 2-dimensional array returned. Retained for (some) backwards-compatibility. Use getValue() instead and check for the existance of a (nested set of) ValueList object(s).
77
+ */
78
+ public function getValues() {
79
+ if (!$this->mValue instanceof RuleValueList) {
80
+ return array(array($this->mValue));
81
+ }
82
+ if ($this->mValue->getListSeparator() === ',') {
83
+ return array($this->mValue->getListComponents());
84
+ }
85
+ $aResult = array();
86
+ foreach ($this->mValue->getListComponents() as $mValue) {
87
+ if (!$mValue instanceof RuleValueList || $mValue->getListSeparator() !== ',') {
88
+ $aResult[] = array($mValue);
89
+ continue;
90
+ }
91
+ if ($this->mValue->getListSeparator() === ' ' || count($aResult) === 0) {
92
+ $aResult[] = array();
93
+ }
94
+ foreach ($mValue->getListComponents() as $mValue) {
95
+ $aResult[count($aResult) - 1][] = $mValue;
96
+ }
97
+ }
98
+ return $aResult;
99
+ }
100
+
101
+ /**
102
+ * Adds a value to the existing value. Value will be appended if a RuleValueList exists of the given type. Otherwise, the existing value will be wrapped by one.
103
+ */
104
+ public function addValue($mValue, $sType = ' ') {
105
+ if (!is_array($mValue)) {
106
+ $mValue = array($mValue);
107
+ }
108
+ if (!$this->mValue instanceof RuleValueList || $this->mValue->getListSeparator() !== $sType) {
109
+ $mCurrentValue = $this->mValue;
110
+ $this->mValue = new RuleValueList($sType);
111
+ if ($mCurrentValue) {
112
+ $this->mValue->addListComponent($mCurrentValue);
113
+ }
114
+ }
115
+ foreach ($mValue as $mValueItem) {
116
+ $this->mValue->addListComponent($mValueItem);
117
+ }
118
+ }
119
+
120
+ public function setIsImportant($bIsImportant) {
121
+ $this->bIsImportant = $bIsImportant;
122
+ }
123
+
124
+ public function getIsImportant() {
125
+ return $this->bIsImportant;
126
+ }
127
+
128
+ public function __toString() {
129
+ $sResult = "{$this->sRule}: ";
130
+ if ($this->mValue instanceof Value) { //Can also be a ValueList
131
+ $sResult .= $this->mValue->__toString();
132
+ } else {
133
+ $sResult .= $this->mValue;
134
+ }
135
+ if ($this->bIsImportant) {
136
+ $sResult .= ' !important';
137
+ }
138
+ $sResult .= ';';
139
+ return $sResult;
140
+ }
141
+
142
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/AtRuleSet.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\RuleSet;
4
+
5
+ use Sabberworm\CSS\Property\AtRule;
6
+
7
+ /**
8
+ * A RuleSet constructed by an unknown @-rule. @font-face rules are rendered into AtRule objects.
9
+ */
10
+ class AtRuleSet extends RuleSet implements AtRule {
11
+
12
+ private $sType;
13
+ private $sArgs;
14
+
15
+ public function __construct($sType, $sArgs = '') {
16
+ parent::__construct();
17
+ $this->sType = $sType;
18
+ $this->sArgs = $sArgs;
19
+ }
20
+
21
+ public function atRuleName() {
22
+ return $this->sType;
23
+ }
24
+
25
+ public function atRuleArgs() {
26
+ return $this->sArgs;
27
+ }
28
+
29
+ public function __toString() {
30
+ $sResult = "@{$this->sType} {$this->sArgs}{";
31
+ $sResult .= parent::__toString();
32
+ $sResult .= '}';
33
+ return $sResult;
34
+ }
35
+
36
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php ADDED
@@ -0,0 +1,585 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\RuleSet;
4
+
5
+ use Sabberworm\CSS\Property\Selector;
6
+ use Sabberworm\CSS\Rule\Rule;
7
+ use Sabberworm\CSS\Value\RuleValueList;
8
+ use Sabberworm\CSS\Value\Value;
9
+ use Sabberworm\CSS\Value\Size;
10
+ use Sabberworm\CSS\Value\Color;
11
+ use Sabberworm\CSS\Value\URL;
12
+
13
+ /**
14
+ * Declaration blocks are the parts of a css file which denote the rules belonging to a selector.
15
+ * Declaration blocks usually appear directly inside a Document or another CSSList (mostly a MediaQuery).
16
+ */
17
+ class DeclarationBlock extends RuleSet {
18
+
19
+ private $aSelectors;
20
+
21
+ public function __construct() {
22
+ parent::__construct();
23
+ $this->aSelectors = array();
24
+ }
25
+
26
+ public function setSelectors($mSelector) {
27
+ if (is_array($mSelector)) {
28
+ $this->aSelectors = $mSelector;
29
+ } else {
30
+ $this->aSelectors = explode(',', $mSelector);
31
+ }
32
+ foreach ($this->aSelectors as $iKey => $mSelector) {
33
+ if (!($mSelector instanceof Selector)) {
34
+ $this->aSelectors[$iKey] = new Selector($mSelector);
35
+ }
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @deprecated use getSelectors()
41
+ */
42
+ public function getSelector() {
43
+ return $this->getSelectors();
44
+ }
45
+
46
+ /**
47
+ * @deprecated use setSelectors()
48
+ */
49
+ public function setSelector($mSelector) {
50
+ $this->setSelectors($mSelector);
51
+ }
52
+
53
+ public function getSelectors() {
54
+ return $this->aSelectors;
55
+ }
56
+
57
+ /**
58
+ * Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
59
+ * */
60
+ public function expandShorthands() {
61
+ // border must be expanded before dimensions
62
+ $this->expandBorderShorthand();
63
+ $this->expandDimensionsShorthand();
64
+ $this->expandFontShorthand();
65
+ $this->expandBackgroundShorthand();
66
+ $this->expandListStyleShorthand();
67
+ }
68
+
69
+ /**
70
+ * Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
71
+ * */
72
+ public function createShorthands() {
73
+ $this->createBackgroundShorthand();
74
+ $this->createDimensionsShorthand();
75
+ // border must be shortened after dimensions
76
+ $this->createBorderShorthand();
77
+ $this->createFontShorthand();
78
+ $this->createListStyleShorthand();
79
+ }
80
+
81
+ /**
82
+ * Split shorthand border declarations (e.g. <tt>border: 1px red;</tt>)
83
+ * Additional splitting happens in expandDimensionsShorthand
84
+ * Multiple borders are not yet supported as of 3
85
+ * */
86
+ public function expandBorderShorthand() {
87
+ $aBorderRules = array(
88
+ 'border', 'border-left', 'border-right', 'border-top', 'border-bottom'
89
+ );
90
+ $aBorderSizes = array(
91
+ 'thin', 'medium', 'thick'
92
+ );
93
+ $aRules = $this->getRulesAssoc();
94
+ foreach ($aBorderRules as $sBorderRule) {
95
+ if (!isset($aRules[$sBorderRule]))
96
+ continue;
97
+ $oRule = $aRules[$sBorderRule];
98
+ $mRuleValue = $oRule->getValue();
99
+ $aValues = array();
100
+ if (!$mRuleValue instanceof RuleValueList) {
101
+ $aValues[] = $mRuleValue;
102
+ } else {
103
+ $aValues = $mRuleValue->getListComponents();
104
+ }
105
+ foreach ($aValues as $mValue) {
106
+ if ($mValue instanceof Value) {
107
+ $mNewValue = clone $mValue;
108
+ } else {
109
+ $mNewValue = $mValue;
110
+ }
111
+ if ($mValue instanceof Size) {
112
+ $sNewRuleName = $sBorderRule . "-width";
113
+ } else if ($mValue instanceof Color) {
114
+ $sNewRuleName = $sBorderRule . "-color";
115
+ } else {
116
+ if (in_array($mValue, $aBorderSizes)) {
117
+ $sNewRuleName = $sBorderRule . "-width";
118
+ } else/* if(in_array($mValue, $aBorderStyles)) */ {
119
+ $sNewRuleName = $sBorderRule . "-style";
120
+ }
121
+ }
122
+ $oNewRule = new Rule($sNewRuleName);
123
+ $oNewRule->setIsImportant($oRule->getIsImportant());
124
+ $oNewRule->addValue(array($mNewValue));
125
+ $this->addRule($oNewRule);
126
+ }
127
+ $this->removeRule($sBorderRule);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
133
+ * into their constituent parts.
134
+ * Handles margin, padding, border-color, border-style and border-width.
135
+ * */
136
+ public function expandDimensionsShorthand() {
137
+ $aExpansions = array(
138
+ 'margin' => 'margin-%s',
139
+ 'padding' => 'padding-%s',
140
+ 'border-color' => 'border-%s-color',
141
+ 'border-style' => 'border-%s-style',
142
+ 'border-width' => 'border-%s-width'
143
+ );
144
+ $aRules = $this->getRulesAssoc();
145
+ foreach ($aExpansions as $sProperty => $sExpanded) {
146
+ if (!isset($aRules[$sProperty]))
147
+ continue;
148
+ $oRule = $aRules[$sProperty];
149
+ $mRuleValue = $oRule->getValue();
150
+ $aValues = array();
151
+ if (!$mRuleValue instanceof RuleValueList) {
152
+ $aValues[] = $mRuleValue;
153
+ } else {
154
+ $aValues = $mRuleValue->getListComponents();
155
+ }
156
+ $top = $right = $bottom = $left = null;
157
+ switch (count($aValues)) {
158
+ case 1:
159
+ $top = $right = $bottom = $left = $aValues[0];
160
+ break;
161
+ case 2:
162
+ $top = $bottom = $aValues[0];
163
+ $left = $right = $aValues[1];
164
+ break;
165
+ case 3:
166
+ $top = $aValues[0];
167
+ $left = $right = $aValues[1];
168
+ $bottom = $aValues[2];
169
+ break;
170
+ case 4:
171
+ $top = $aValues[0];
172
+ $right = $aValues[1];
173
+ $bottom = $aValues[2];
174
+ $left = $aValues[3];
175
+ break;
176
+ }
177
+ foreach (array('top', 'right', 'bottom', 'left') as $sPosition) {
178
+ $oNewRule = new Rule(sprintf($sExpanded, $sPosition));
179
+ $oNewRule->setIsImportant($oRule->getIsImportant());
180
+ $oNewRule->addValue(${$sPosition});
181
+ $this->addRule($oNewRule);
182
+ }
183
+ $this->removeRule($sProperty);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Convert shorthand font declarations
189
+ * (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
190
+ * into their constituent parts.
191
+ * */
192
+ public function expandFontShorthand() {
193
+ $aRules = $this->getRulesAssoc();
194
+ if (!isset($aRules['font']))
195
+ return;
196
+ $oRule = $aRules['font'];
197
+ // reset properties to 'normal' per http://www.w3.org/TR/21/fonts.html#font-shorthand
198
+ $aFontProperties = array(
199
+ 'font-style' => 'normal',
200
+ 'font-variant' => 'normal',
201
+ 'font-weight' => 'normal',
202
+ 'font-size' => 'normal',
203
+ 'line-height' => 'normal'
204
+ );
205
+ $mRuleValue = $oRule->getValue();
206
+ $aValues = array();
207
+ if (!$mRuleValue instanceof RuleValueList) {
208
+ $aValues[] = $mRuleValue;
209
+ } else {
210
+ $aValues = $mRuleValue->getListComponents();
211
+ }
212
+ foreach ($aValues as $mValue) {
213
+ if (!$mValue instanceof Value) {
214
+ $mValue = mb_strtolower($mValue);
215
+ }
216
+ if (in_array($mValue, array('normal', 'inherit'))) {
217
+ foreach (array('font-style', 'font-weight', 'font-variant') as $sProperty) {
218
+ if (!isset($aFontProperties[$sProperty])) {
219
+ $aFontProperties[$sProperty] = $mValue;
220
+ }
221
+ }
222
+ } else if (in_array($mValue, array('italic', 'oblique'))) {
223
+ $aFontProperties['font-style'] = $mValue;
224
+ } else if ($mValue == 'small-caps') {
225
+ $aFontProperties['font-variant'] = $mValue;
226
+ } else if (
227
+ in_array($mValue, array('bold', 'bolder', 'lighter'))
228
+ || ($mValue instanceof Size
229
+ && in_array($mValue->getSize(), range(100, 900, 100)))
230
+ ) {
231
+ $aFontProperties['font-weight'] = $mValue;
232
+ } else if ($mValue instanceof RuleValueList && $mValue->getListSeparator() == '/') {
233
+ list($oSize, $oHeight) = $mValue->getListComponents();
234
+ $aFontProperties['font-size'] = $oSize;
235
+ $aFontProperties['line-height'] = $oHeight;
236
+ } else if ($mValue instanceof Size && $mValue->getUnit() !== null) {
237
+ $aFontProperties['font-size'] = $mValue;
238
+ } else {
239
+ $aFontProperties['font-family'] = $mValue;
240
+ }
241
+ }
242
+ foreach ($aFontProperties as $sProperty => $mValue) {
243
+ $oNewRule = new Rule($sProperty);
244
+ $oNewRule->addValue($mValue);
245
+ $oNewRule->setIsImportant($oRule->getIsImportant());
246
+ $this->addRule($oNewRule);
247
+ }
248
+ $this->removeRule('font');
249
+ }
250
+
251
+ /*
252
+ * Convert shorthand background declarations
253
+ * (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>)
254
+ * into their constituent parts.
255
+ * @see http://www.w3.org/TR/21/colors.html#propdef-background
256
+ * */
257
+
258
+ public function expandBackgroundShorthand() {
259
+ $aRules = $this->getRulesAssoc();
260
+ if (!isset($aRules['background']))
261
+ return;
262
+ $oRule = $aRules['background'];
263
+ $aBgProperties = array(
264
+ 'background-color' => array('transparent'), 'background-image' => array('none'),
265
+ 'background-repeat' => array('repeat'), 'background-attachment' => array('scroll'),
266
+ 'background-position' => array(new Size(0, '%'), new Size(0, '%'))
267
+ );
268
+ $mRuleValue = $oRule->getValue();
269
+ $aValues = array();
270
+ if (!$mRuleValue instanceof RuleValueList) {
271
+ $aValues[] = $mRuleValue;
272
+ } else {
273
+ $aValues = $mRuleValue->getListComponents();
274
+ }
275
+ if (count($aValues) == 1 && $aValues[0] == 'inherit') {
276
+ foreach ($aBgProperties as $sProperty => $mValue) {
277
+ $oNewRule = new Rule($sProperty);
278
+ $oNewRule->addValue('inherit');
279
+ $oNewRule->setIsImportant($oRule->getIsImportant());
280
+ $this->addRule($oNewRule);
281
+ }
282
+ $this->removeRule('background');
283
+ return;
284
+ }
285
+ $iNumBgPos = 0;
286
+ foreach ($aValues as $mValue) {
287
+ if (!$mValue instanceof Value) {
288
+ $mValue = mb_strtolower($mValue);
289
+ }
290
+ if ($mValue instanceof URL) {
291
+ $aBgProperties['background-image'] = $mValue;
292
+ } else if ($mValue instanceof Color) {
293
+ $aBgProperties['background-color'] = $mValue;
294
+ } else if (in_array($mValue, array('scroll', 'fixed'))) {
295
+ $aBgProperties['background-attachment'] = $mValue;
296
+ } else if (in_array($mValue, array('repeat', 'no-repeat', 'repeat-x', 'repeat-y'))) {
297
+ $aBgProperties['background-repeat'] = $mValue;
298
+ } else if (in_array($mValue, array('left', 'center', 'right', 'top', 'bottom'))
299
+ || $mValue instanceof Size
300
+ ) {
301
+ if ($iNumBgPos == 0) {
302
+ $aBgProperties['background-position'][0] = $mValue;
303
+ $aBgProperties['background-position'][1] = 'center';
304
+ } else {
305
+ $aBgProperties['background-position'][$iNumBgPos] = $mValue;
306
+ }
307
+ $iNumBgPos++;
308
+ }
309
+ }
310
+ foreach ($aBgProperties as $sProperty => $mValue) {
311
+ $oNewRule = new Rule($sProperty);
312
+ $oNewRule->setIsImportant($oRule->getIsImportant());
313
+ $oNewRule->addValue($mValue);
314
+ $this->addRule($oNewRule);
315
+ }
316
+ $this->removeRule('background');
317
+ }
318
+
319
+ public function expandListStyleShorthand() {
320
+ $aListProperties = array(
321
+ 'list-style-type' => 'disc',
322
+ 'list-style-position' => 'outside',
323
+ 'list-style-image' => 'none'
324
+ );
325
+ $aListStyleTypes = array(
326
+ 'none', 'disc', 'circle', 'square', 'decimal-leading-zero', 'decimal',
327
+ 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin',
328
+ 'upper-alpha', 'upper-latin', 'hebrew', 'armenian', 'georgian', 'cjk-ideographic',
329
+ 'hiragana', 'hira-gana-iroha', 'katakana-iroha', 'katakana'
330
+ );
331
+ $aListStylePositions = array(
332
+ 'inside', 'outside'
333
+ );
334
+ $aRules = $this->getRulesAssoc();
335
+ if (!isset($aRules['list-style']))
336
+ return;
337
+ $oRule = $aRules['list-style'];
338
+ $mRuleValue = $oRule->getValue();
339
+ $aValues = array();
340
+ if (!$mRuleValue instanceof RuleValueList) {
341
+ $aValues[] = $mRuleValue;
342
+ } else {
343
+ $aValues = $mRuleValue->getListComponents();
344
+ }
345
+ if (count($aValues) == 1 && $aValues[0] == 'inherit') {
346
+ foreach ($aListProperties as $sProperty => $mValue) {
347
+ $oNewRule = new Rule($sProperty);
348
+ $oNewRule->addValue('inherit');
349
+ $oNewRule->setIsImportant($oRule->getIsImportant());
350
+ $this->addRule($oNewRule);
351
+ }
352
+ $this->removeRule('list-style');
353
+ return;
354
+ }
355
+ foreach ($aValues as $mValue) {
356
+ if (!$mValue instanceof Value) {
357
+ $mValue = mb_strtolower($mValue);
358
+ }
359
+ if ($mValue instanceof Url) {
360
+ $aListProperties['list-style-image'] = $mValue;
361
+ } else if (in_array($mValue, $aListStyleTypes)) {
362
+ $aListProperties['list-style-types'] = $mValue;
363
+ } else if (in_array($mValue, $aListStylePositions)) {
364
+ $aListProperties['list-style-position'] = $mValue;
365
+ }
366
+ }
367
+ foreach ($aListProperties as $sProperty => $mValue) {
368
+ $oNewRule = new Rule($sProperty);
369
+ $oNewRule->setIsImportant($oRule->getIsImportant());
370
+ $oNewRule->addValue($mValue);
371
+ $this->addRule($oNewRule);
372
+ }
373
+ $this->removeRule('list-style');
374
+ }
375
+
376
+ public function createShorthandProperties(array $aProperties, $sShorthand) {
377
+ $aRules = $this->getRulesAssoc();
378
+ $aNewValues = array();
379
+ foreach ($aProperties as $sProperty) {
380
+ if (!isset($aRules[$sProperty]))
381
+ continue;
382
+ $oRule = $aRules[$sProperty];
383
+ if (!$oRule->getIsImportant()) {
384
+ $mRuleValue = $oRule->getValue();
385
+ $aValues = array();
386
+ if (!$mRuleValue instanceof RuleValueList) {
387
+ $aValues[] = $mRuleValue;
388
+ } else {
389
+ $aValues = $mRuleValue->getListComponents();
390
+ }
391
+ foreach ($aValues as $mValue) {
392
+ $aNewValues[] = $mValue;
393
+ }
394
+ $this->removeRule($sProperty);
395
+ }
396
+ }
397
+ if (count($aNewValues)) {
398
+ $oNewRule = new Rule($sShorthand);
399
+ foreach ($aNewValues as $mValue) {
400
+ $oNewRule->addValue($mValue);
401
+ }
402
+ $this->addRule($oNewRule);
403
+ }
404
+ }
405
+
406
+ public function createBackgroundShorthand() {
407
+ $aProperties = array(
408
+ 'background-color', 'background-image', 'background-repeat',
409
+ 'background-position', 'background-attachment'
410
+ );
411
+ $this->createShorthandProperties($aProperties, 'background');
412
+ }
413
+
414
+ public function createListStyleShorthand() {
415
+ $aProperties = array(
416
+ 'list-style-type', 'list-style-position', 'list-style-image'
417
+ );
418
+ $this->createShorthandProperties($aProperties, 'list-style');
419
+ }
420
+
421
+ /**
422
+ * Combine border-color, border-style and border-width into border
423
+ * Should be run after create_dimensions_shorthand!
424
+ * */
425
+ public function createBorderShorthand() {
426
+ $aProperties = array(
427
+ 'border-width', 'border-style', 'border-color'
428
+ );
429
+ $this->createShorthandProperties($aProperties, 'border');
430
+ }
431
+
432
+ /*
433
+ * Looks for long format CSS dimensional properties
434
+ * (margin, padding, border-color, border-style and border-width)
435
+ * and converts them into shorthand CSS properties.
436
+ * */
437
+
438
+ public function createDimensionsShorthand() {
439
+ $aPositions = array('top', 'right', 'bottom', 'left');
440
+ $aExpansions = array(
441
+ 'margin' => 'margin-%s',
442
+ 'padding' => 'padding-%s',
443
+ 'border-color' => 'border-%s-color',
444
+ 'border-style' => 'border-%s-style',
445
+ 'border-width' => 'border-%s-width'
446
+ );
447
+ $aRules = $this->getRulesAssoc();
448
+ foreach ($aExpansions as $sProperty => $sExpanded) {
449
+ $aFoldable = array();
450
+ foreach ($aRules as $sRuleName => $oRule) {
451
+ foreach ($aPositions as $sPosition) {
452
+ if ($sRuleName == sprintf($sExpanded, $sPosition)) {
453
+ $aFoldable[$sRuleName] = $oRule;
454
+ }
455
+ }
456
+ }
457
+ // All four dimensions must be present
458
+ if (count($aFoldable) == 4) {
459
+ $aValues = array();
460
+ foreach ($aPositions as $sPosition) {
461
+ $oRule = $aRules[sprintf($sExpanded, $sPosition)];
462
+ $mRuleValue = $oRule->getValue();
463
+ $aRuleValues = array();
464
+ if (!$mRuleValue instanceof RuleValueList) {
465
+ $aRuleValues[] = $mRuleValue;
466
+ } else {
467
+ $aRuleValues = $mRuleValue->getListComponents();
468
+ }
469
+ $aValues[$sPosition] = $aRuleValues;
470
+ }
471
+ $oNewRule = new Rule($sProperty);
472
+ if ((string) $aValues['left'][0] == (string) $aValues['right'][0]) {
473
+ if ((string) $aValues['top'][0] == (string) $aValues['bottom'][0]) {
474
+ if ((string) $aValues['top'][0] == (string) $aValues['left'][0]) {
475
+ // All 4 sides are equal
476
+ $oNewRule->addValue($aValues['top']);
477
+ } else {
478
+ // Top and bottom are equal, left and right are equal
479
+ $oNewRule->addValue($aValues['top']);
480
+ $oNewRule->addValue($aValues['left']);
481
+ }
482
+ } else {
483
+ // Only left and right are equal
484
+ $oNewRule->addValue($aValues['top']);
485
+ $oNewRule->addValue($aValues['left']);
486
+ $oNewRule->addValue($aValues['bottom']);
487
+ }
488
+ } else {
489
+ // No sides are equal
490
+ $oNewRule->addValue($aValues['top']);
491
+ $oNewRule->addValue($aValues['left']);
492
+ $oNewRule->addValue($aValues['bottom']);
493
+ $oNewRule->addValue($aValues['right']);
494
+ }
495
+ $this->addRule($oNewRule);
496
+ foreach ($aPositions as $sPosition) {
497
+ $this->removeRule(sprintf($sExpanded, $sPosition));
498
+ }
499
+ }
500
+ }
501
+ }
502
+
503
+ /**
504
+ * Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
505
+ * tries to convert them into a shorthand CSS <tt>font</tt> property.
506
+ * At least font-size AND font-family must be present in order to create a shorthand declaration.
507
+ * */
508
+ public function createFontShorthand() {
509
+ $aFontProperties = array(
510
+ 'font-style', 'font-variant', 'font-weight', 'font-size', 'line-height', 'font-family'
511
+ );
512
+ $aRules = $this->getRulesAssoc();
513
+ if (!isset($aRules['font-size']) || !isset($aRules['font-family'])) {
514
+ return;
515
+ }
516
+ $oNewRule = new Rule('font');
517
+ foreach (array('font-style', 'font-variant', 'font-weight') as $sProperty) {
518
+ if (isset($aRules[$sProperty])) {
519
+ $oRule = $aRules[$sProperty];
520
+ $mRuleValue = $oRule->getValue();
521
+ $aValues = array();
522
+ if (!$mRuleValue instanceof RuleValueList) {
523
+ $aValues[] = $mRuleValue;
524
+ } else {
525
+ $aValues = $mRuleValue->getListComponents();
526
+ }
527
+ if ($aValues[0] !== 'normal') {
528
+ $oNewRule->addValue($aValues[0]);
529
+ }
530
+ }
531
+ }
532
+ // Get the font-size value
533
+ $oRule = $aRules['font-size'];
534
+ $mRuleValue = $oRule->getValue();
535
+ $aFSValues = array();
536
+ if (!$mRuleValue instanceof RuleValueList) {
537
+ $aFSValues[] = $mRuleValue;
538
+ } else {
539
+ $aFSValues = $mRuleValue->getListComponents();
540
+ }
541
+ // But wait to know if we have line-height to add it
542
+ if (isset($aRules['line-height'])) {
543
+ $oRule = $aRules['line-height'];
544
+ $mRuleValue = $oRule->getValue();
545
+ $aLHValues = array();
546
+ if (!$mRuleValue instanceof RuleValueList) {
547
+ $aLHValues[] = $mRuleValue;
548
+ } else {
549
+ $aLHValues = $mRuleValue->getListComponents();
550
+ }
551
+ if ($aLHValues[0] !== 'normal') {
552
+ $val = new RuleValueList('/');
553
+ $val->addListComponent($aFSValues[0]);
554
+ $val->addListComponent($aLHValues[0]);
555
+ $oNewRule->addValue($val);
556
+ }
557
+ } else {
558
+ $oNewRule->addValue($aFSValues[0]);
559
+ }
560
+ $oRule = $aRules['font-family'];
561
+ $mRuleValue = $oRule->getValue();
562
+ $aFFValues = array();
563
+ if (!$mRuleValue instanceof RuleValueList) {
564
+ $aFFValues[] = $mRuleValue;
565
+ } else {
566
+ $aFFValues = $mRuleValue->getListComponents();
567
+ }
568
+ $oFFValue = new RuleValueList(',');
569
+ $oFFValue->setListComponents($aFFValues);
570
+ $oNewRule->addValue($oFFValue);
571
+
572
+ $this->addRule($oNewRule);
573
+ foreach ($aFontProperties as $sProperty) {
574
+ $this->removeRule($sProperty);
575
+ }
576
+ }
577
+
578
+ public function __toString() {
579
+ $sResult = implode(', ', $this->aSelectors) . ' {';
580
+ $sResult .= parent::__toString();
581
+ $sResult .= '}' . "\n";
582
+ return $sResult;
583
+ }
584
+
585
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/RuleSet.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\RuleSet;
4
+
5
+ use Sabberworm\CSS\Rule\Rule;
6
+
7
+ /**
8
+ * RuleSet is a generic superclass denoting rules. The typical example for rule sets are declaration block.
9
+ * However, unknown At-Rules (like @font-face) are also rule sets.
10
+ */
11
+ abstract class RuleSet {
12
+
13
+ private $aRules;
14
+
15
+ public function __construct() {
16
+ $this->aRules = array();
17
+ }
18
+
19
+ public function addRule(Rule $oRule) {
20
+ $sRule = $oRule->getRule();
21
+ if(!isset($this->aRules[$sRule])) {
22
+ $this->aRules[$sRule] = array();
23
+ }
24
+ $this->aRules[$sRule][] = $oRule;
25
+ }
26
+
27
+ /**
28
+ * Returns all rules matching the given rule name
29
+ * @param (null|string|Rule) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()).
30
+ * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching font.
31
+ * @example $oRuleSet->getRules('font') //returns array(0 => $oRule, …) or array().
32
+ */
33
+ public function getRules($mRule = null) {
34
+ if ($mRule instanceof Rule) {
35
+ $mRule = $mRule->getRule();
36
+ }
37
+ $aResult = array();
38
+ foreach($this->aRules as $sName => $aRules) {
39
+ // Either no search rule is given or the search rule matches the found rule exactly or the search rule ends in “-” and the found rule starts with the search rule.
40
+ if(!$mRule || $sName === $mRule || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))) {
41
+ $aResult = array_merge($aResult, $aRules);
42
+ }
43
+ }
44
+ return $aResult;
45
+ }
46
+
47
+ /**
48
+ * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
49
+ * @param (string) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()).
50
+ * Note: This method loses some information: Calling this (with an argument of 'background-') on a declaration block like { background-color: green; background-color; rgba(0, 127, 0, 0.7); } will only yield an associative array containing the rgba-valued rule while @link{getRules()} would yield an indexed array containing both.
51
+ */
52
+ public function getRulesAssoc($mRule = null) {
53
+ $aResult = array();
54
+ foreach($this->getRules($mRule) as $oRule) {
55
+ $aResult[$oRule->getRule()] = $oRule;
56
+ }
57
+ return $aResult;
58
+ }
59
+
60
+ /**
61
+ * Remove a rule from this RuleSet. This accepts all the possible values that @link{getRules()} accepts. If given a Rule, it will only remove this particular rule (by identity). If given a name, it will remove all rules by that name. Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would remove all rules with the same name. To get the old behvaiour, use removeRule($oRule->getRule()).
62
+ * @param (null|string|Rule) $mRule pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, all rules starting with the pattern are removed as well as one matching the pattern with the dash excluded. Passing a Rule behaves matches by identity.
63
+ */
64
+ public function removeRule($mRule) {
65
+ if($mRule instanceof Rule) {
66
+ $sRule = $mRule->getRule();
67
+ if(!isset($this->aRules[$sRule])) {
68
+ return;
69
+ }
70
+ foreach($this->aRules[$sRule] as $iKey => $oRule) {
71
+ if($oRule === $mRule) {
72
+ unset($this->aRules[$sRule][$iKey]);
73
+ }
74
+ }
75
+ } else {
76
+ foreach($this->aRules as $sName => $aRules) {
77
+ // Either no search rule is given or the search rule matches the found rule exactly or the search rule ends in “-” and the found rule starts with the search rule or equals it (without the trailing dash).
78
+ if(!$mRule || $sName === $mRule || (strrpos($mRule, '-') === strlen($mRule) - strlen('-') && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1)))) {
79
+ unset($this->aRules[$sName]);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ public function __toString() {
86
+ $sResult = '';
87
+ foreach ($this->aRules as $aRules) {
88
+ foreach($aRules as $oRule) {
89
+ $sResult .= $oRule->__toString();
90
+ }
91
+ }
92
+ return $sResult;
93
+ }
94
+
95
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Settings.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS;
4
+
5
+ use Sabberworm\CSS\Rule\Rule;
6
+
7
+ /**
8
+ * Parser settings class.
9
+ *
10
+ * Configure parser behaviour here.
11
+ */
12
+ class Settings {
13
+ /**
14
+ * Multi-byte string support. If true (mbstring extension must be enabled), will use (slower) mb_strlen, mb_convert_case, mb_substr and mb_strpos functions. Otherwise, the normal (ASCII-Only) functions will be used.
15
+ */
16
+ public $bMultibyteSupport;
17
+
18
+ /**
19
+ * The default charset for the CSS if no `@charset` rule is found. Defaults to utf-8.
20
+ */
21
+ public $sDefaultCharset = 'utf-8';
22
+
23
+ /**
24
+ * Lenient parsing. When used (which is true by default), the parser will not choke on unexpected tokens but simply ignore them.
25
+ */
26
+ public $bLenientParsing = true;
27
+
28
+ private function __construct() {
29
+ $this->bMultibyteSupport = extension_loaded('mbstring');
30
+ }
31
+
32
+ public static function create() {
33
+ return new Settings();
34
+ }
35
+
36
+ public function withMultibyteSupport($bMultibyteSupport = true) {
37
+ $this->bMultibyteSupport = $bMultibyteSupport;
38
+ return $this;
39
+ }
40
+
41
+ public function withDefaultCharset($sDefaultCharset) {
42
+ $this->sDefaultCharset = $sDefaultCharset;
43
+ return $this;
44
+ }
45
+
46
+ public function withLenientParsing($bLenientParsing = true) {
47
+ $this->bLenientParsing = $bLenientParsing;
48
+ return $this;
49
+ }
50
+
51
+ public function beStrict() {
52
+ return $this->withLenientParsing(false);
53
+ }
54
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/CSSFunction.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ class CSSFunction extends ValueList {
6
+
7
+ private $sName;
8
+
9
+ public function __construct($sName, $aArguments, $sSeparator = ',') {
10
+ if($aArguments instanceof RuleValueList) {
11
+ $sSeparator = $aArguments->getListSeparator();
12
+ $aArguments = $aArguments->getListComponents();
13
+ }
14
+ $this->sName = $sName;
15
+ parent::__construct($aArguments, $sSeparator);
16
+ }
17
+
18
+ public function getName() {
19
+ return $this->sName;
20
+ }
21
+
22
+ public function setName($sName) {
23
+ $this->sName = $sName;
24
+ }
25
+
26
+ public function getArguments() {
27
+ return $this->aComponents;
28
+ }
29
+
30
+ public function __toString() {
31
+ $aArguments = parent::__toString();
32
+ return "{$this->sName}({$aArguments})";
33
+ }
34
+
35
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Color.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ class Color extends CSSFunction {
6
+
7
+ public function __construct($aColor) {
8
+ parent::__construct(implode('', array_keys($aColor)), $aColor);
9
+ }
10
+
11
+ public function getColor() {
12
+ return $this->aComponents;
13
+ }
14
+
15
+ public function setColor($aColor) {
16
+ $this->setName(implode('', array_keys($aColor)));
17
+ $this->aComponents = $aColor;
18
+ }
19
+
20
+ public function getColorDescription() {
21
+ return $this->getName();
22
+ }
23
+
24
+ public function __toString() {
25
+ // Shorthand RGB color values
26
+ // TODO: Include in output settings (once they’re done)
27
+ if(implode('', array_keys($this->aComponents)) === 'rgb') {
28
+ $sResult = sprintf(
29
+ '%02x%02x%02x',
30
+ $this->aComponents['r']->__toString(),
31
+ $this->aComponents['g']->__toString(),
32
+ $this->aComponents['b']->__toString()
33
+ );
34
+ return '#'.(($sResult[0] == $sResult[1]) && ($sResult[2] == $sResult[3]) && ($sResult[4] == $sResult[5]) ? "$sResult[0]$sResult[2]$sResult[4]" : $sResult);
35
+ }
36
+ return parent::__toString();
37
+ }
38
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/PrimitiveValue.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ abstract class PrimitiveValue extends Value {
6
+
7
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/RuleValueList.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ class RuleValueList extends ValueList {
6
+
7
+ public function __construct($sSeparator = ',') {
8
+ parent::__construct(array(), $sSeparator);
9
+ }
10
+
11
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Size.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ class Size extends PrimitiveValue {
6
+
7
+ const ABSOLUTE_SIZE_UNITS = 'px/cm/mm/mozmm/in/pt/pc/vh/vw/vm/vmin/vmax/rem'; //vh/vw/vm(ax)/vmin/rem are absolute insofar as they don’t scale to the immediate parent (only the viewport)
8
+ const RELATIVE_SIZE_UNITS = '%/em/ex/ch';
9
+ const NON_SIZE_UNITS = 'deg/grad/rad/s/ms/turns/Hz/kHz';
10
+
11
+ private $fSize;
12
+ private $sUnit;
13
+ private $bIsColorComponent;
14
+
15
+ public function __construct($fSize, $sUnit = null, $bIsColorComponent = false) {
16
+ $this->fSize = floatval($fSize);
17
+ $this->sUnit = $sUnit;
18
+ $this->bIsColorComponent = $bIsColorComponent;
19
+ }
20
+
21
+ public function setUnit($sUnit) {
22
+ $this->sUnit = $sUnit;
23
+ }
24
+
25
+ public function getUnit() {
26
+ return $this->sUnit;
27
+ }
28
+
29
+ public function setSize($fSize) {
30
+ $this->fSize = floatval($fSize);
31
+ }
32
+
33
+ public function getSize() {
34
+ return $this->fSize;
35
+ }
36
+
37
+ public function isColorComponent() {
38
+ return $this->bIsColorComponent;
39
+ }
40
+
41
+ /**
42
+ * Returns whether the number stored in this Size really represents a size (as in a length of something on screen).
43
+ * @return false if the unit an angle, a duration, a frequency or the number is a component in a Color object.
44
+ */
45
+ public function isSize() {
46
+ if (in_array($this->sUnit, explode('/', self::NON_SIZE_UNITS))) {
47
+ return false;
48
+ }
49
+ return !$this->isColorComponent();
50
+ }
51
+
52
+ public function isRelative() {
53
+ if (in_array($this->sUnit, explode('/', self::RELATIVE_SIZE_UNITS))) {
54
+ return true;
55
+ }
56
+ if ($this->sUnit === null && $this->fSize != 0) {
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ public function __toString() {
63
+ $l = localeconv();
64
+ $sPoint = preg_quote($l['decimal_point'], '/');
65
+ return preg_replace(array("/$sPoint/", "/^(-?)0\./"), array('.', '$1.'), $this->fSize) . ($this->sUnit === null ? '' : $this->sUnit);
66
+ }
67
+
68
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/String.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ class String extends PrimitiveValue {
6
+
7
+ private $sString;
8
+
9
+ public function __construct($sString) {
10
+ $this->sString = $sString;
11
+ }
12
+
13
+ public function setString($sString) {
14
+ $this->sString = $sString;
15
+ }
16
+
17
+ public function getString() {
18
+ return $this->sString;
19
+ }
20
+
21
+ public function __toString() {
22
+ $sString = addslashes($this->sString);
23
+ $sString = str_replace("\n", '\A', $sString);
24
+ return '"' . $sString . '"';
25
+ }
26
+
27
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/URL.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+
6
+ class URL extends PrimitiveValue {
7
+
8
+ private $oURL;
9
+
10
+ public function __construct(String $oURL) {
11
+ $this->oURL = $oURL;
12
+ }
13
+
14
+ public function setURL(String $oURL) {
15
+ $this->oURL = $oURL;
16
+ }
17
+
18
+ public function getURL() {
19
+ return $this->oURL;
20
+ }
21
+
22
+ public function __toString() {
23
+ return "url({$this->oURL->__toString()})";
24
+ }
25
+
26
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Value.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ abstract class Value {
6
+
7
+ public abstract function __toString();
8
+ }
includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/ValueList.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Sabberworm\CSS\Value;
4
+
5
+ abstract class ValueList extends Value {
6
+
7
+ protected $aComponents;
8
+ protected $sSeparator;
9
+
10
+ public function __construct($aComponents = array(), $sSeparator = ',') {
11
+ if (!is_array($aComponents)) {
12
+ $aComponents = array($aComponents);
13
+ }
14
+ $this->aComponents = $aComponents;
15
+ $this->sSeparator = $sSeparator;
16
+ }
17
+
18
+ public function addListComponent($mComponent) {
19
+ $this->aComponents[] = $mComponent;
20
+ }
21
+
22
+ public function getListComponents() {
23
+ return $this->aComponents;
24
+ }
25
+
26
+ public function setListComponents($aComponents) {
27
+ $this->aComponents = $aComponents;
28
+ }
29
+
30
+ public function getListSeparator() {
31
+ return $this->sSeparator;
32
+ }
33
+
34
+ public function setListSeparator($sSeparator) {
35
+ $this->sSeparator = $sSeparator;
36
+ }
37
+
38
+ function __toString() {
39
+ return implode($this->sSeparator, $this->aComponents);
40
+ }
41
+
42
+ }
includes/admin-menu-editor-mu.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  /*
3
- Plugin Name: Admin Menu Editor Pro (Multisite module)
4
- Plugin URI: http://w-shadow.com/admin-menu-editor-pro/
5
  Description: Lets you edit the WordPress admin menu. To access the editor, go to the Dashboard of one of your network sites and open the Settings -&gt; Menu Editor page.
6
  Author: Janis Elsts
7
  Author URI: http://w-shadow.com/
@@ -49,3 +49,13 @@ function ws_ame_installation_error(){
49
  </div>
50
  <?php
51
  }
 
 
 
 
 
 
 
 
 
 
1
  <?php
2
  /*
3
+ Plugin Name: Admin Menu Editor [Multisite module]
4
+ Plugin URI: http://adminmenueditor.com/
5
  Description: Lets you edit the WordPress admin menu. To access the editor, go to the Dashboard of one of your network sites and open the Settings -&gt; Menu Editor page.
6
  Author: Janis Elsts
7
  Author URI: http://w-shadow.com/
49
  </div>
50
  <?php
51
  }
52
+
53
+ //Add the license management link(s) to our must-use module.
54
+ function ws_ame_add_mu_license_link($actions) {
55
+ global $ameLicensingUi;
56
+ if ( isset($ameLicensingUi) && is_callable(array($ameLicensingUi, 'addLicenseActionLink')) ) {
57
+ $actions = $ameLicensingUi->addLicenseActionLink($actions);
58
+ }
59
+ return $actions;
60
+ }
61
+ add_filter('network_admin_plugin_action_links_' . basename(__FILE__), 'ws_ame_add_mu_license_link');
includes/editor-page.php CHANGED
@@ -82,7 +82,12 @@ if ( $show_whats_new ):
82
  endif;
83
  ?>
84
 
85
- <?php include dirname(__FILE__) . '/access-editor-dialog.php'; ?>
 
 
 
 
 
86
 
87
  <div id='ws_menu_editor'>
88
  <div id="ws_actor_selector_container">
@@ -264,7 +269,8 @@ endif;
264
  ?>
265
 
266
  <!-- Menu icon selector widget -->
267
- <div id="ws_icon_selector" style="display: none;">
 
268
  <?php
269
  //Let the user select a custom icon via the media uploader.
270
  //We only support the new WP 3.5+ media API. Hence the function_exists() check.
@@ -280,18 +286,78 @@ endif;
280
  ?>
281
 
282
  <?php
283
- $defaultWpIcons = array(
284
- 'generic', 'dashboard', 'post', 'media', 'links', 'page', 'comments',
285
- 'appearance', 'plugins', 'users', 'tools', 'settings', 'site',
286
- );
287
- foreach($defaultWpIcons as $icon) {
288
- printf(
289
- '<div class="ws_icon_option" title="%1$s" data-icon-class="menu-icon-%2$s">
290
- <div class="ws_icon_image icon16 icon-%2$s"><br></div>
291
- </div>',
292
- esc_attr(ucwords($icon)),
293
- $icon
294
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  }
296
 
297
  $defaultIconImages = array(
@@ -305,21 +371,40 @@ endif;
305
  esc_attr($icon)
306
  );
307
  }
 
308
  ?>
309
  <div class="ws_icon_option ws_custom_image_icon" title="Custom image" style="display: none;">
310
  <img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>">
311
  </div>
 
 
 
 
 
 
 
 
 
 
312
  <div class="clear"></div>
313
  </div>
314
 
315
  <span id="ws-ame-screen-meta-contents" style="display:none;">
316
- <label for="ws-hide-advanced-settings">
317
- <input type="checkbox" id="ws-hide-advanced-settings"<?php
318
- if ( $this->options['hide_advanced_settings'] ){
 
 
 
 
 
 
 
 
319
  echo ' checked="checked"';
320
  }
321
- ?> /> Hide advanced options
322
- </label>
323
  </span>
324
 
325
 
82
  endif;
83
  ?>
84
 
85
+ <?php
86
+ include dirname(__FILE__) . '/access-editor-dialog.php';
87
+ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
88
+ include dirname(__FILE__) . '/../extras/menu-color-dialog.php';
89
+ }
90
+ ?>
91
 
92
  <div id='ws_menu_editor'>
93
  <div id="ws_actor_selector_container">
269
  ?>
270
 
271
  <!-- Menu icon selector widget -->
272
+ <?php $iconSelectorClass = $editor_data['show_extra_icons'] ? 'ws_with_more_icons' : ''; ?>
273
+ <div id="ws_icon_selector" class="<?php echo $iconSelectorClass; ?>" style="display: none;">
274
  <?php
275
  //Let the user select a custom icon via the media uploader.
276
  //We only support the new WP 3.5+ media API. Hence the function_exists() check.
286
  ?>
287
 
288
  <?php
289
+ //The old "menu-icon-something" icons are only available in WP 3.8.x and below. Newer versions use Dashicons.
290
+ //Plugins can change $wp_version to something useless for security, so lets check if Dashicons are available
291
+ //before we throw away the old icons.
292
+ $oldMenuIconsAvailable = ( !$editor_data['dashicons_available'] )
293
+ || version_compare($GLOBALS['wp_version'], '3.9-beta', '<');
294
+
295
+ if ($oldMenuIconsAvailable) {
296
+ $defaultWpIcons = array(
297
+ 'generic', 'dashboard', 'post', 'media', 'links', 'page', 'comments',
298
+ 'appearance', 'plugins', 'users', 'tools', 'settings', 'site',
 
299
  );
300
+ foreach($defaultWpIcons as $icon) {
301
+ printf(
302
+ '<div class="ws_icon_option" title="%1$s" data-icon-class="menu-icon-%2$s">
303
+ <div class="ws_icon_image icon16 icon-%2$s"><br></div>
304
+ </div>',
305
+ esc_attr(ucwords($icon)),
306
+ $icon
307
+ );
308
+ }
309
+ }
310
+
311
+ //These dashicons are used in the default admin menu.
312
+ $defaultDashicons = array(
313
+ 'admin-generic', 'dashboard', 'admin-post', 'admin-media', 'admin-links', 'admin-page', 'admin-comments',
314
+ 'admin-appearance', 'admin-plugins', 'admin-users', 'admin-tools', 'admin-settings', 'admin-network',
315
+ );
316
+
317
+ //The rest of Dashicons. Some icons were manually removed as they wouldn't look good as menu icons.
318
+ $dashicons = array(
319
+ 'admin-site', 'admin-home',
320
+ 'align-center', 'align-left', 'align-none', 'align-right', 'analytics', 'art', 'awards', 'backup',
321
+ 'book', 'book-alt', 'businessman', 'calendar', 'camera', 'cart', 'category', 'chart-area', 'chart-bar',
322
+ 'chart-line', 'chart-pie', 'clock', 'cloud', 'desktop', 'dismiss', 'download', 'edit', 'editor-customchar',
323
+ 'editor-distractionfree', 'editor-help', 'editor-insertmore',
324
+ 'editor-justify', 'editor-kitchensink', 'editor-ol', 'editor-paste-text',
325
+ 'editor-paste-word', 'editor-quote', 'editor-removeformatting', 'editor-rtl', 'editor-spellcheck',
326
+ 'editor-ul', 'editor-unlink', 'editor-video',
327
+ 'email', 'email-alt', 'exerpt-view', 'facebook', 'facebook-alt', 'feedback', 'flag', 'format-aside',
328
+ 'format-audio', 'format-chat', 'format-gallery', 'format-image', 'format-quote', 'format-status',
329
+ 'format-video', 'forms', 'googleplus', 'groups', 'hammer', 'id', 'id-alt', 'image-crop',
330
+ 'image-flip-horizontal', 'image-flip-vertical', 'image-rotate-left', 'image-rotate-right', 'images-alt',
331
+ 'images-alt2', 'info', 'leftright', 'lightbulb', 'list-view', 'location', 'location-alt', 'lock', 'marker',
332
+ 'menu', 'migrate', 'minus', 'networking', 'no', 'no-alt', 'performance', 'plus', 'portfolio', 'post-status',
333
+ 'pressthis', 'products', 'redo', 'rss', 'screenoptions', 'search', 'share', 'share-alt',
334
+ 'share-alt2', 'share1', 'shield', 'shield-alt', 'slides', 'smartphone', 'smiley', 'sort', 'sos', 'star-empty',
335
+ 'star-filled', 'star-half', 'tablet', 'tag', 'testimonial', 'translation', 'twitter', 'undo',
336
+ 'update', 'upload', 'vault', 'video-alt', 'video-alt2', 'video-alt3', 'visibility', 'welcome-add-page',
337
+ 'welcome-comments', 'welcome-learn-more', 'welcome-view-site', 'welcome-widgets-menus', 'welcome-write-blog',
338
+ 'wordpress', 'wordpress-alt', 'yes'
339
+ );
340
+
341
+ if ($editor_data['dashicons_available']) {
342
+ function ws_ame_print_dashicon_option($icon, $isExtraIcon = false) {
343
+ printf(
344
+ '<div class="ws_icon_option%3$s" title="%1$s" data-icon-url="dashicons-%2$s">
345
+ <div class="ws_icon_image icon16 dashicons dashicons-%2$s"><br></div>
346
+ </div>',
347
+ esc_attr(ucwords(str_replace('-', ' ', $icon))),
348
+ $icon,
349
+ $isExtraIcon ? ' ws_icon_extra' : ''
350
+ );
351
+ }
352
+
353
+ if ( !$oldMenuIconsAvailable ) {
354
+ foreach($defaultDashicons as $icon) {
355
+ ws_ame_print_dashicon_option($icon);
356
+ }
357
+ }
358
+ foreach($dashicons as $icon) {
359
+ ws_ame_print_dashicon_option($icon, true);
360
+ }
361
  }
362
 
363
  $defaultIconImages = array(
371
  esc_attr($icon)
372
  );
373
  }
374
+
375
  ?>
376
  <div class="ws_icon_option ws_custom_image_icon" title="Custom image" style="display: none;">
377
  <img src="<?php echo esc_attr(admin_url('images/loading.gif')); ?>">
378
  </div>
379
+
380
+
381
+ <?php if ($editor_data['dashicons_available']): ?>
382
+ <!-- Only show this button on recent WP versions where Dashicons are included. -->
383
+ <input type="button" class="button"
384
+ id="ws_show_more_icons"
385
+ title="Toggle additional icons"
386
+ value="<?php echo esc_attr($editor_data['show_extra_icons'] ? 'Less &#x25B2;' : 'More &#x25BC;'); ?>">
387
+ <?php endif; ?>
388
+
389
  <div class="clear"></div>
390
  </div>
391
 
392
  <span id="ws-ame-screen-meta-contents" style="display:none;">
393
+ <label for="ws-hide-advanced-settings">
394
+ <input type="checkbox" id="ws-hide-advanced-settings"<?php
395
+ if ( $this->options['hide_advanced_settings'] ){
396
+ echo ' checked="checked"';
397
+ }
398
+ ?> /> Hide advanced options
399
+ </label><br>
400
+
401
+ <label for="ws-show-extra-icons">
402
+ <input type="checkbox" id="ws-show-extra-icons"<?php
403
+ if ( $this->options['show_extra_icons'] ){
404
  echo ' checked="checked"';
405
  }
406
+ ?> /> Show extra menu icons
407
+ </label>
408
  </span>
409
 
410
 
includes/generate-menu-dashicons.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * This utility script generates menu icons metadata based on the Dashicons icon font included in WordPress.
4
+ */
5
+
6
+ if ( !defined('ABSPATH') ) {
7
+ die('No direct script access');
8
+ }
9
+ if ( !constant('WP_DEBUG') || !current_user_can('edit_plugins') ) {
10
+ echo "Permission denied. You need the edit_plugins cap to run this script and WP_DEBUG must be enabled.";
11
+ return;
12
+ }
13
+
14
+ require_once dirname(__FILE__) . '/PHP-CSS-Parser/autoloader.php';
15
+ $dashiconsStylesheet = ABSPATH . WPINC . '/css/dashicons.css';
16
+
17
+ $icons = array();
18
+
19
+ $ignoreIcons = array('dashboard', 'editor-bold', 'editor-italic');
20
+ $ignoreIcons = array_flip($ignoreIcons);
21
+
22
+ $parser = new Sabberworm\CSS\Parser(file_get_contents($dashiconsStylesheet));
23
+ $cssDocument = $parser->parse();
24
+
25
+ $blocks = $cssDocument->getAllDeclarationBlocks();
26
+ foreach($blocks as $block) {
27
+ /** @var Sabberworm\CSS\RuleSet\DeclarationBlock $block */
28
+
29
+ //We want the ".dashicons-*:before" selectors.
30
+ $selectors = $block->getSelectors();
31
+ foreach($selectors as $selector) {
32
+ /** @var Sabberworm\CSS\Property\Selector $selector */
33
+
34
+ if ( preg_match('/\.dashicons-(?P<name>[\w\-]+):before/', $selector->getSelector(), $matches) ) {
35
+ $name = $matches['name'];
36
+
37
+ //We already have styles for icons that start with "admin-", and the arrow icons
38
+ //aren't really suitable as menu icons.
39
+ if ( preg_match('/^(admin|arrow)-/', $name) ) {
40
+ break;
41
+ }
42
+
43
+ //Some icons are duplicates of the "admin-" icons or just wouldn't look very good in a menu.
44
+ if ( array_key_exists($name, $ignoreIcons) ) {
45
+ break;
46
+ }
47
+
48
+ $rules = $block->getRules('content'); //Expect something like "content: '\f123'".
49
+ foreach($rules as $rule) {
50
+ /** @var Sabberworm\CSS\Rule\Rule $rule */
51
+ $value = $rule->getValue();
52
+ if ($value instanceof Sabberworm\CSS\Value\String) {
53
+ //The parser defaults to UTF-8. Convert the char to a hexadecimal escape code
54
+ //so we don't have to worry about our CSS charset.
55
+ $char = ltrim(bin2hex(iconv('UTF-8', 'UCS-4', $value->getString())), '0');
56
+ $icons[$name] = '\\' . $char;
57
+ }
58
+ }
59
+
60
+
61
+ break;
62
+ }
63
+ }
64
+ }
65
+
66
+ ?>
67
+ <div class="wrap">
68
+ <h2>Dashicons to Menu Icons</h2>
69
+ <style type="text/css" scoped="scoped">
70
+ .ame-debug-dashicon {
71
+ display: inline-block;
72
+ margin: 2px;
73
+ min-width: 180px;
74
+ }
75
+ </style>
76
+ <?php
77
+
78
+ ksort($icons);
79
+ $arrayDefinition = "array(\n";
80
+ $currentLine = "\t";
81
+
82
+ foreach($icons as $name => $character) {
83
+ //Output each icon for visual verification.
84
+ printf(
85
+ '<div class="ame-debug-dashicon"><div class="dashicons dashicons-%1$s"></div> %1$s</div>',
86
+ $name
87
+ );
88
+
89
+ //Wrap the array definition at about 80 characters for legibility.
90
+ $item = "'" . $name . "', ";
91
+ if ( strlen($currentLine . $item) > 80 ) {
92
+ $arrayDefinition .= $currentLine . "\n";
93
+ $currentLine = "\t";
94
+ }
95
+
96
+ $currentLine .= $item;
97
+ }
98
+
99
+ if (strlen($currentLine) > 1) {
100
+ $arrayDefinition .= $currentLine . "\n";
101
+ }
102
+ $arrayDefinition .= ')';
103
+
104
+ echo '<div class="clear"></div><br>';
105
+ echo '<textarea cols="100" rows="20">', esc_textarea($arrayDefinition), '</textarea>';
106
+
107
+ echo '</div>';
includes/menu-editor-core.php CHANGED
@@ -85,6 +85,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
85
  }
86
  $this->defaults = array(
87
  'hide_advanced_settings' => true,
 
88
  'custom_menu' => null,
89
  'first_install_time' => null,
90
  'display_survey_notice' => true,
@@ -166,7 +167,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
166
  }
167
 
168
  if ( $should_save_options ) {
169
- $this->save_options();
 
 
 
 
 
 
 
 
170
  }
171
 
172
  //This is here and not in init() because it relies on $options being initialized.
@@ -439,6 +448,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
439
  wp_register_auto_versioned_script('jquery-qtip', plugins_url('js/jquery.qtip.min.js', $this->plugin_file), array('jquery'));
440
  //jQuery Form plugin. This is a more recent version than the one included with WP.
441
  wp_register_auto_versioned_script('ame-jquery-form', plugins_url('js/jquery.form.js', $this->plugin_file), array('jquery'));
 
 
442
 
443
  //Editor's scripts
444
  wp_register_auto_versioned_script(
@@ -447,7 +458,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
447
  array(
448
  'jquery', 'jquery-ui-sortable', 'jquery-ui-dialog',
449
  'ame-jquery-form', 'jquery-ui-droppable', 'jquery-qtip',
450
- 'jquery-sort', 'jquery-json'
 
451
  )
452
  );
453
 
@@ -501,6 +513,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
501
  //Note: Users do NOT get added to the actor list because that feature
502
  //is not fully implemented.
503
 
 
 
 
 
 
504
  //The editor will need access to some of the plugin data and WP data.
505
  wp_localize_script(
506
  'menu-editor',
@@ -509,7 +526,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
509
  'imagesUrl' => plugins_url('images', $this->plugin_file),
510
  'adminAjaxUrl' => admin_url('admin-ajax.php'),
511
  'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
 
512
  'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
 
513
  'captionShowAdvanced' => 'Show advanced options',
514
  'captionHideAdvanced' => 'Hide advanced options',
515
  'wsMenuEditorPro' => false, //Will be overwritten if extras are loaded
@@ -607,6 +626,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
607
  }
608
 
609
  wp_enqueue_style('menu-editor-colours-classic');
 
610
  }
611
 
612
  /**
@@ -615,6 +635,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
615
  * @param array|null $custom_menu
616
  */
617
  function set_custom_menu($custom_menu) {
 
 
618
  $previous_custom_menu = $this->load_custom_menu();
619
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
620
 
@@ -1313,6 +1335,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1313
  $sub_section = isset($this->get['sub_section']) ? $this->get['sub_section'] : null;
1314
  if ( $sub_section === 'settings' ) {
1315
  $this->display_plugin_settings_ui();
 
 
1316
  } else {
1317
  $this->display_editor_ui();
1318
  }
@@ -1416,8 +1440,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1416
  'message' => isset($this->get['message']) ? intval($this->get['message']) : null,
1417
  'images_url' => plugins_url('images', $this->plugin_file),
1418
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
 
1419
  'settings_page_url' => $this->get_settings_page_url(),
1420
  'show_deprecated_hide_button' => $this->options['show_deprecated_hide_button'],
 
1421
  );
1422
 
1423
  //Build a tree struct. for the default menu
@@ -1432,6 +1458,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1432
  $custom_menu = $default_menu;
1433
  }
1434
 
 
 
 
 
1435
  //Encode both menus as JSON
1436
  $editor_data['default_menu_js'] = ameMenu::to_json($default_menu);
1437
  $editor_data['custom_menu_js'] = ameMenu::to_json($custom_menu);
@@ -1612,6 +1642,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1612
  }
1613
 
1614
  $this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
 
1615
  $this->save_options();
1616
  die('1');
1617
  }
@@ -1775,6 +1806,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1775
 
1776
  $current_url = $this->parse_url($current_url);
1777
 
 
 
 
 
 
 
 
 
 
1778
  foreach($this->reverse_item_lookup as $url => $item) {
1779
  $item_url = $url;
1780
  //Convert to absolute URL. Caution: directory traversal (../, etc) is not handled.
@@ -1791,9 +1831,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1791
  }
1792
  $item_url = $this->parse_url($item_url);
1793
 
1794
- //Must match scheme, host, port, user, pass and path.
1795
  $components = array('scheme', 'host', 'port', 'user', 'pass');
1796
  $is_close_match = $this->urlPathsMatch($current_url['path'], $item_url['path']);
 
 
 
 
 
1797
  foreach($components as $component) {
1798
  $is_close_match = $is_close_match && ($current_url[$component] == $item_url[$component]);
1799
  if ( !$is_close_match ) {
@@ -1967,8 +2012,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1967
  'ame-helper-script',
1968
  plugins_url('js/admin-helpers.js', $this->plugin_file),
1969
  array('jquery'),
1970
- '20121121'
1971
  );
 
 
 
 
 
 
 
 
 
 
 
 
1972
  }
1973
 
1974
  public function enqueue_helper_styles() {
@@ -1976,7 +2033,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1976
  'ame-helper-style',
1977
  plugins_url('css/admin.css', $this->plugin_file),
1978
  array(),
1979
- '20130211'
1980
  );
1981
  }
1982
 
85
  }
86
  $this->defaults = array(
87
  'hide_advanced_settings' => true,
88
+ 'show_extra_icons' => false,
89
  'custom_menu' => null,
90
  'first_install_time' => null,
91
  'display_survey_notice' => true,
167
  }
168
 
169
  if ( $should_save_options ) {
170
+ //Skip saving options if the plugin hasn't been fully activated yet.
171
+ if ( $this->is_plugin_active($this->plugin_basename) ) {
172
+ $this->save_options();
173
+ } else {
174
+ //Yes, this method can actually run before WP updates the list of active plugins. That means functions
175
+ //like is_plugin_active_for_network() will return false. As as result, we can't determine whether
176
+ //the plugin has been network-activated yet, so lets skip setting up the default config until
177
+ //the next page load.
178
+ }
179
  }
180
 
181
  //This is here and not in init() because it relies on $options being initialized.
448
  wp_register_auto_versioned_script('jquery-qtip', plugins_url('js/jquery.qtip.min.js', $this->plugin_file), array('jquery'));
449
  //jQuery Form plugin. This is a more recent version than the one included with WP.
450
  wp_register_auto_versioned_script('ame-jquery-form', plugins_url('js/jquery.form.js', $this->plugin_file), array('jquery'));
451
+ //jQuery cookie plugin
452
+ wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.cookie.js', $this->plugin_file), array('jquery'));
453
 
454
  //Editor's scripts
455
  wp_register_auto_versioned_script(
458
  array(
459
  'jquery', 'jquery-ui-sortable', 'jquery-ui-dialog',
460
  'ame-jquery-form', 'jquery-ui-droppable', 'jquery-qtip',
461
+ 'jquery-sort', 'jquery-json', 'jquery-cookie',
462
+ 'wp-color-picker'
463
  )
464
  );
465
 
513
  //Note: Users do NOT get added to the actor list because that feature
514
  //is not fully implemented.
515
 
516
+ $showExtraIcons = (boolean)$this->options['show_extra_icons'];
517
+ if ( isset($_COOKIE['ame-show-extra-icons']) && is_numeric($_COOKIE['ame-show-extra-icons']) ) {
518
+ $showExtraIcons = intval($_COOKIE['ame-show-extra-icons']) > 0;
519
+ }
520
+
521
  //The editor will need access to some of the plugin data and WP data.
522
  wp_localize_script(
523
  'menu-editor',
526
  'imagesUrl' => plugins_url('images', $this->plugin_file),
527
  'adminAjaxUrl' => admin_url('admin-ajax.php'),
528
  'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
529
+ 'showExtraIcons' => $showExtraIcons,
530
  'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
531
+ 'dashiconsAvailable' => wp_style_is('dashicons', 'registered'),
532
  'captionShowAdvanced' => 'Show advanced options',
533
  'captionHideAdvanced' => 'Hide advanced options',
534
  'wsMenuEditorPro' => false, //Will be overwritten if extras are loaded
626
  }
627
 
628
  wp_enqueue_style('menu-editor-colours-classic');
629
+ wp_enqueue_style('wp-color-picker');
630
  }
631
 
632
  /**
635
  * @param array|null $custom_menu
636
  */
637
  function set_custom_menu($custom_menu) {
638
+ $custom_menu = apply_filters('ame_pre_set_custom_menu', $custom_menu);
639
+
640
  $previous_custom_menu = $this->load_custom_menu();
641
  $this->update_wpml_strings($previous_custom_menu, $custom_menu);
642
 
1335
  $sub_section = isset($this->get['sub_section']) ? $this->get['sub_section'] : null;
1336
  if ( $sub_section === 'settings' ) {
1337
  $this->display_plugin_settings_ui();
1338
+ } else if ($sub_section == 'generate-menu-dashicons') {
1339
+ require dirname(__FILE__) . '/generate-menu-dashicons.php';
1340
  } else {
1341
  $this->display_editor_ui();
1342
  }
1440
  'message' => isset($this->get['message']) ? intval($this->get['message']) : null,
1441
  'images_url' => plugins_url('images', $this->plugin_file),
1442
  'hide_advanced_settings' => $this->options['hide_advanced_settings'],
1443
+ 'show_extra_icons' => $this->options['show_extra_icons'],
1444
  'settings_page_url' => $this->get_settings_page_url(),
1445
  'show_deprecated_hide_button' => $this->options['show_deprecated_hide_button'],
1446
+ 'dashicons_available' => wp_style_is('dashicons', 'done'),
1447
  );
1448
 
1449
  //Build a tree struct. for the default menu
1458
  $custom_menu = $default_menu;
1459
  }
1460
 
1461
+ //The editor doesn't use the color CSS. Including it would just make the page bigger and waste bandwidth.
1462
+ unset($custom_menu['color_css']);
1463
+ unset($custom_menu['color_css_modified']);
1464
+
1465
  //Encode both menus as JSON
1466
  $editor_data['default_menu_js'] = ameMenu::to_json($default_menu);
1467
  $editor_data['custom_menu_js'] = ameMenu::to_json($custom_menu);
1642
  }
1643
 
1644
  $this->options['hide_advanced_settings'] = !empty($this->post['hide_advanced_settings']);
1645
+ $this->options['show_extra_icons'] = !empty($this->post['show_extra_icons']);
1646
  $this->save_options();
1647
  die('1');
1648
  }
1806
 
1807
  $current_url = $this->parse_url($current_url);
1808
 
1809
+ //Hook-based submenu pages can be accessed via both "parent-page.php?page=foo" and "admin.php?page=foo".
1810
+ //WP has a private API function for determining the canonical parent page for the current request.
1811
+ if ( $this->endsWith($current_url['path'], '/admin.php') && is_callable('get_admin_page_parent') ) {
1812
+ $real_parent = get_admin_page_parent('admin.php');
1813
+ if ( !empty($real_parent) && ($real_parent !== 'admin.php') ) {
1814
+ $current_url['alt_path'] = str_replace('/admin.php', '/' . $real_parent, $current_url['path']);
1815
+ }
1816
+ }
1817
+
1818
  foreach($this->reverse_item_lookup as $url => $item) {
1819
  $item_url = $url;
1820
  //Convert to absolute URL. Caution: directory traversal (../, etc) is not handled.
1831
  }
1832
  $item_url = $this->parse_url($item_url);
1833
 
1834
+ //Must match scheme, host, port, user, pass and path or alt_path.
1835
  $components = array('scheme', 'host', 'port', 'user', 'pass');
1836
  $is_close_match = $this->urlPathsMatch($current_url['path'], $item_url['path']);
1837
+ if ( !$is_close_match && isset($current_url['alt_path']) ) {
1838
+ $is_close_match = $this->urlPathsMatch($current_url['alt_path'], $item_url['path']);
1839
+ //Technically, we should also compare current[path] vs item[alt_path],
1840
+ //but generating the alt_path for each menu item would be complicated.
1841
+ }
1842
  foreach($components as $component) {
1843
  $is_close_match = $is_close_match && ($current_url[$component] == $item_url[$component]);
1844
  if ( !$is_close_match ) {
2012
  'ame-helper-script',
2013
  plugins_url('js/admin-helpers.js', $this->plugin_file),
2014
  array('jquery'),
2015
+ '20140312'
2016
  );
2017
+
2018
+ //The helper script needs to know the custom page heading (if any) to apply it.
2019
+ $currentItem = $this->get_current_menu_item();
2020
+ if ( $currentItem && !empty($currentItem['page_heading']) ) {
2021
+ wp_localize_script(
2022
+ 'ame-helper-script',
2023
+ 'wsAmeCurrentMenuItem',
2024
+ array(
2025
+ 'customPageHeading' => $currentItem['page_heading']
2026
+ )
2027
+ );
2028
+ }
2029
  }
2030
 
2031
  public function enqueue_helper_styles() {
2033
  'ame-helper-style',
2034
  plugins_url('css/admin.css', $this->plugin_file),
2035
  array(),
2036
+ '20140220-2'
2037
  );
2038
  }
2039
 
includes/menu-item.php CHANGED
@@ -87,6 +87,7 @@ abstract class ameMenuItem {
87
  'access_level' => 'read',
88
  'extra_capability' => '',
89
  'file' => '',
 
90
  'position' => 0,
91
  'parent' => '',
92
 
@@ -95,6 +96,7 @@ abstract class ameMenuItem {
95
  'hookname' => '',
96
  'icon_url' => 'images/generic.png',
97
  'separator' => false,
 
98
 
99
  //Internal fields that may not map directly to WP menu structures.
100
  'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
@@ -119,6 +121,7 @@ abstract class ameMenuItem {
119
  'items' => array(), //List of sub-menu items.
120
  'grant_access' => array(), //Per-role and per-user access. Supersedes role_access.
121
  'role_access' => array(), //Per-role access settings.
 
122
 
123
  'custom' => false, //True if item is made-from-scratch and has no template.
124
  'missing' => false, //True if our template is no longer present in the default admin menu. Note: Stored values will be ignored. Set upon merging.
@@ -140,6 +143,8 @@ abstract class ameMenuItem {
140
  'icon_url' => 'images/generic.png',
141
  'open_in' => 'same_window',
142
  'is_plugin_page' => false,
 
 
143
  );
144
  }
145
 
87
  'access_level' => 'read',
88
  'extra_capability' => '',
89
  'file' => '',
90
+ 'page_heading' => '',
91
  'position' => 0,
92
  'parent' => '',
93
 
96
  'hookname' => '',
97
  'icon_url' => 'images/generic.png',
98
  'separator' => false,
99
+ 'colors' => false,
100
 
101
  //Internal fields that may not map directly to WP menu structures.
102
  'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
121
  'items' => array(), //List of sub-menu items.
122
  'grant_access' => array(), //Per-role and per-user access. Supersedes role_access.
123
  'role_access' => array(), //Per-role access settings.
124
+ 'colors' => null,
125
 
126
  'custom' => false, //True if item is made-from-scratch and has no template.
127
  'missing' => false, //True if our template is no longer present in the default admin menu. Note: Stored values will be ignored. Set upon merging.
143
  'icon_url' => 'images/generic.png',
144
  'open_in' => 'same_window',
145
  'is_plugin_page' => false,
146
+ 'page_heading' => '',
147
+ 'colors' => false,
148
  );
149
  }
150
 
includes/menu.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
- const format_version = '5.1';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
@@ -10,7 +10,7 @@ abstract class ameMenu {
10
  *
11
  * @param string $json A JSON-encoded menu structure.
12
  * @param bool $assume_correct_format Skip the format header check and assume everything is fine. Defaults to false.
13
- * @param bool $always_normalize Always normalize the menu structure, even if format.is_normalized is true.
14
  * @throws InvalidMenuException
15
  * @return array
16
  */
@@ -39,7 +39,11 @@ abstract class ameMenu {
39
  if ( isset($arr['format']) && ($arr['format']['name'] == self::format_name) ) {
40
  $compared = version_compare($arr['format']['version'], self::format_version);
41
  if ( $compared > 0 ) {
42
- throw new InvalidMenuException("Can't load a menu created by a newer version of the plugin.");
 
 
 
 
43
  }
44
  //We can skip normalization if the version number matches exactly and the menu is already normalized.
45
  if ( ($compared === 0) && isset($arr['format']['is_normalized']) ) {
@@ -66,6 +70,11 @@ abstract class ameMenu {
66
  $menu['format']['is_normalized'] = true;
67
  }
68
 
 
 
 
 
 
69
  return $menu;
70
  }
71
 
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
+ const format_version = '5.41';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
10
  *
11
  * @param string $json A JSON-encoded menu structure.
12
  * @param bool $assume_correct_format Skip the format header check and assume everything is fine. Defaults to false.
13
+ * @param bool $always_normalize Always normalize the menu structure, even if format[is_normalized] is true.
14
  * @throws InvalidMenuException
15
  * @return array
16
  */
39
  if ( isset($arr['format']) && ($arr['format']['name'] == self::format_name) ) {
40
  $compared = version_compare($arr['format']['version'], self::format_version);
41
  if ( $compared > 0 ) {
42
+ throw new InvalidMenuException(sprintf(
43
+ "Can't load a menu created by a newer version of the plugin. Menu format: '%s', newest supported format: '%s'.",
44
+ $arr['format']['version'],
45
+ self::format_version
46
+ ));
47
  }
48
  //We can skip normalization if the version number matches exactly and the menu is already normalized.
49
  if ( ($compared === 0) && isset($arr['format']['is_normalized']) ) {
70
  $menu['format']['is_normalized'] = true;
71
  }
72
 
73
+ if ( isset($arr['color_css']) && is_string($arr['color_css']) ) {
74
+ $menu['color_css'] = $arr['color_css'];
75
+ $menu['color_css_modified'] = isset($arr['color_css_modified']) ? intval($arr['color_css_modified']) : 0;
76
+ }
77
+
78
  return $menu;
79
  }
80
 
includes/shadow_plugin_framework.php CHANGED
@@ -234,12 +234,12 @@ class MenuEd_ShadowPluginFramework {
234
 
235
  /**
236
  * ShadowPluginFramework::activate()
237
- * Stub function for the activation hook. Simply stores the default configuration.
238
  *
239
  * @return void
240
  */
241
  function activate(){
242
- $this->save_options();
243
  }
244
 
245
  /**
@@ -338,7 +338,20 @@ class MenuEd_ShadowPluginFramework {
338
 
339
  return false;
340
  }
341
-
342
- }
343
 
344
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
  /**
236
  * ShadowPluginFramework::activate()
237
+ * Stub function for the activation hook.
238
  *
239
  * @return void
240
  */
241
  function activate(){
242
+
243
  }
244
 
245
  /**
338
 
339
  return false;
340
  }
 
 
341
 
342
+ /**
343
+ * Check whether the plugin is active.
344
+ *
345
+ * @see self::is_plugin_active_for_network
346
+ *
347
+ * @param string $plugin
348
+ * @return bool
349
+ */
350
+ function is_plugin_active($plugin) {
351
+ if ( function_exists('is_plugin_active') ) {
352
+ return is_plugin_active($plugin);
353
+ }
354
+ return in_array( $plugin, (array) get_option('active_plugins', array()) ) || $this->is_plugin_active_for_network($plugin);
355
+ }
356
+
357
+ }
js/admin-helpers.js CHANGED
@@ -1,9 +1,51 @@
1
- jQuery(function($) {
2
- //Menu separators shouldn't be clickable and should have a custom class.
3
- $('#adminmenu')
4
- .find('.ws-submenu-separator')
5
- .closest('a').click(function() {
6
- return false;
7
- })
8
- .closest('li').addClass('ws-submenu-separator-wrap');
9
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*global wsAmeCurrentMenuItem*/
2
+
3
+ (function($) {
4
+ var currentItem = (typeof wsAmeCurrentMenuItem !== 'undefined') ? wsAmeCurrentMenuItem : {};
5
+
6
+ //The page heading is typically hardcoded and/or not configurable, so we need to use JS to change it.
7
+ var customPageHeading = currentItem.hasOwnProperty('customPageHeading') ? currentItem['customPageHeading'] : null;
8
+ var ameHideHeading = null;
9
+ if ( customPageHeading ) {
10
+ //Temporarily hide the heading to prevent the original text from showing up briefly
11
+ //before being replaced when the DOM is ready (see below).
12
+ ameHideHeading = $('<style type="text/css">.wrap > h2:first-child { visibility: hidden; }</style>')
13
+ .appendTo('head');
14
+ }
15
+
16
+ jQuery(function($) {
17
+ //Menu separators shouldn't be clickable and should have a custom class.
18
+ $('#adminmenu')
19
+ .find('.ws-submenu-separator')
20
+ .closest('a').click(function() {
21
+ return false;
22
+ })
23
+ .closest('li').addClass('ws-submenu-separator-wrap');
24
+
25
+ //Replace the original page heading with the custom heading.
26
+ if ( customPageHeading ) {
27
+ function replaceAdminPageHeading(newText) {
28
+ var headingText = $('.wrap > h2:first')
29
+ .contents()
30
+ .filter(function() {
31
+ //Find text nodes.
32
+ if ((this.nodeType != 3) || (!this.nodeValue)) {
33
+ return false;
34
+ }
35
+ //Skip whitespace.
36
+ return /\S/.test(this.nodeValue);
37
+ }).get(0);
38
+
39
+ if (headingText && headingText.nodeValue) {
40
+ headingText.nodeValue = newText;
41
+ }
42
+ }
43
+
44
+ //Normal headings have at least one tab's worth of trailing whitespace. We need to replicate that
45
+ //to keep the page layout roughly the same.
46
+ replaceAdminPageHeading(customPageHeading + '\t');
47
+ ameHideHeading.remove(); //Make the heading visible.
48
+ }
49
+ });
50
+
51
+ })(jQuery);
js/jquery.cookie.js ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ * jQuery Cookie Plugin v1.4.0
3
+ * https://github.com/carhartl/jquery-cookie
4
+ *
5
+ * Copyright 2013 Klaus Hartl
6
+ * Released under the MIT license
7
+ */
8
+ (function (factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as anonymous module.
11
+ define(['jquery'], factory);
12
+ } else {
13
+ // Browser globals.
14
+ factory(jQuery);
15
+ }
16
+ }(function ($) {
17
+
18
+ var pluses = /\+/g;
19
+
20
+ function encode(s) {
21
+ return config.raw ? s : encodeURIComponent(s);
22
+ }
23
+
24
+ function decode(s) {
25
+ return config.raw ? s : decodeURIComponent(s);
26
+ }
27
+
28
+ function stringifyCookieValue(value) {
29
+ return encode(config.json ? JSON.stringify(value) : String(value));
30
+ }
31
+
32
+ function parseCookieValue(s) {
33
+ if (s.indexOf('"') === 0) {
34
+ // This is a quoted cookie as according to RFC2068, unescape...
35
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
36
+ }
37
+
38
+ try {
39
+ // Replace server-side written pluses with spaces.
40
+ // If we can't decode the cookie, ignore it, it's unusable.
41
+ s = decodeURIComponent(s.replace(pluses, ' '));
42
+ } catch(e) {
43
+ return;
44
+ }
45
+
46
+ try {
47
+ // If we can't parse the cookie, ignore it, it's unusable.
48
+ return config.json ? JSON.parse(s) : s;
49
+ } catch(e) {}
50
+ }
51
+
52
+ function read(s, converter) {
53
+ var value = config.raw ? s : parseCookieValue(s);
54
+ return $.isFunction(converter) ? converter(value) : value;
55
+ }
56
+
57
+ var config = $.cookie = function (key, value, options) {
58
+
59
+ // Write
60
+ if (value !== undefined && !$.isFunction(value)) {
61
+ options = $.extend({}, config.defaults, options);
62
+
63
+ if (typeof options.expires === 'number') {
64
+ var days = options.expires, t = options.expires = new Date();
65
+ t.setDate(t.getDate() + days);
66
+ }
67
+
68
+ return (document.cookie = [
69
+ encode(key), '=', stringifyCookieValue(value),
70
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
71
+ options.path ? '; path=' + options.path : '',
72
+ options.domain ? '; domain=' + options.domain : '',
73
+ options.secure ? '; secure' : ''
74
+ ].join(''));
75
+ }
76
+
77
+ // Read
78
+
79
+ var result = key ? undefined : {};
80
+
81
+ // To prevent the for loop in the first place assign an empty array
82
+ // in case there are no cookies at all. Also prevents odd result when
83
+ // calling $.cookie().
84
+ var cookies = document.cookie ? document.cookie.split('; ') : [];
85
+
86
+ for (var i = 0, l = cookies.length; i < l; i++) {
87
+ var parts = cookies[i].split('=');
88
+ var name = decode(parts.shift());
89
+ var cookie = parts.join('=');
90
+
91
+ if (key && key === name) {
92
+ // If second argument (value) is a function it's a converter...
93
+ result = read(cookie, value);
94
+ break;
95
+ }
96
+
97
+ // Prevent storing a cookie that we couldn't decode.
98
+ if (!key && (cookie = read(cookie)) !== undefined) {
99
+ result[name] = cookie;
100
+ }
101
+ }
102
+
103
+ return result;
104
+ };
105
+
106
+ config.defaults = {};
107
+
108
+ $.removeCookie = function (key, options) {
109
+ if ($.cookie(key) !== undefined) {
110
+ // Must not alter options, thus extending a fresh object...
111
+ $.cookie(key, '', $.extend({}, options, { expires: -1 }));
112
+ return true;
113
+ }
114
+ return false;
115
+ };
116
+
117
+ }));
js/menu-editor.js CHANGED
@@ -623,14 +623,22 @@ var knownMenuFields = {
623
  var cssIcon = selectButton.find('.icon16');
624
  var imageIcon = selectButton.find('img');
625
 
626
- var matches = cssClass.match(/\bmenu-icon-([^\s]+)\b/);
 
 
627
  //Icon URL take precedence over icon class.
628
- if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' ) {
629
  cssIcon.hide();
630
  imageIcon.prop('src', iconUrl).show();
 
 
 
 
631
  } else if ( matches ) {
 
632
  imageIcon.hide();
633
- cssIcon.removeClass().addClass('icon16 icon-' + matches[1]).show();
 
634
  } else {
635
  //This menu has no icon at all. This is actually a valid state
636
  //and WordPress will display a menu like that correctly.
@@ -642,6 +650,51 @@ var knownMenuFields = {
642
  }
643
  }),
644
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  'hookname' : $.extend({}, baseField, {
646
  caption: 'Hook name',
647
  advanced : true,
@@ -734,6 +787,11 @@ function buildEditboxField(entry, field_name, field_settings){
734
  .add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
735
  break;
736
 
 
 
 
 
 
737
  case 'text': //Intentional fall-through.
738
  default:
739
  inputBox = $(basicTextField);
@@ -876,21 +934,6 @@ function updateItemEditor(containerNode) {
876
  displayValue = knownMenuFields[fieldName].display(menuItem, displayValue, input, containerNode);
877
  }
878
 
879
- if (fieldName == 'access_level') {
880
- //Permissions display is a little complicated and could use improvement.
881
- var requiredCap = getFieldValue(menuItem, 'access_level', '');
882
- var extraCap = getFieldValue(menuItem, 'extra_capability', '');
883
-
884
- displayValue = (menuItem.template_id === '') ? '< Custom >' : requiredCap;
885
- if (extraCap !== '') {
886
- if (menuItem.template_id === '') {
887
- displayValue = extraCap;
888
- } else {
889
- displayValue = displayValue + '+' + extraCap;
890
- }
891
- }
892
- }
893
-
894
  setInputValue(input, displayValue);
895
  });
896
  }
@@ -1202,6 +1245,8 @@ $(document).ready(function(){
1202
  if (wsEditorData.wsMenuEditorPro) {
1203
  knownMenuFields['open_in'].visible = true;
1204
  knownMenuFields['access_level'].visible = true;
 
 
1205
  knownMenuFields['extra_capability'].visible = false; //Superseded by the "access_level" field.
1206
  $('.ws_hide_if_pro').hide();
1207
  }
@@ -1264,10 +1309,8 @@ $(document).ready(function(){
1264
  //Find the field div (it holds the field name)
1265
  var field = $(this).parents('.ws_edit_field');
1266
  var fieldName = field.data('field_name');
1267
- //Find the related input field
1268
- var input = field.find('.ws_field_value');
1269
 
1270
- if ( (input.length > 0) && (field.length > 0) && fieldName ) {
1271
  //Extract the default value from the menu item.
1272
  var containerNode = field.closest('.ws_container');
1273
  var menuItem = containerNode.data('menu_item');
@@ -1722,7 +1765,7 @@ $(document).ready(function(){
1722
 
1723
  //Remove the existing icon class, if any.
1724
  var cssClass = getFieldValue(item, 'css_class', '');
1725
- cssClass = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
1726
 
1727
  if (selectedIcon.data('icon-class')) {
1728
  //Add the new class.
@@ -1761,8 +1804,13 @@ $(document).ready(function(){
1761
 
1762
  //Highlight the currently selected icon.
1763
  iconSelector.find('.ws_selected_icon').removeClass('ws_selected_icon');
1764
- var matches = cssClass.match(/\bmenu-icon-([^\s]+)\b/);
1765
- if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' ) {
 
 
 
 
 
1766
  var currentIcon = iconSelector.find('.ws_icon_option img[src="' + iconUrl + '"]').first().closest('.ws_icon_option');
1767
  if ( currentIcon.length > 0 ) {
1768
  currentIcon.addClass('ws_selected_icon').show();
@@ -1771,15 +1819,24 @@ $(document).ready(function(){
1771
  customImageOption.find('img').prop('src', iconUrl);
1772
  customImageOption.addClass('ws_selected_icon').show().data('icon-url', iconUrl);
1773
  }
1774
- } else if ( matches ) {
1775
- //Highlight the icon that corresponds to the current CSS class.
1776
- iconSelector.find('.icon-' + matches[1]).closest('.ws_icon_option').addClass('ws_selected_icon');
 
 
 
 
 
1777
  }
1778
 
 
 
 
 
1779
  iconSelector.show();
1780
  iconSelector.position({ //Requires jQuery UI.
1781
- my: 'right top',
1782
- at: 'right bottom',
1783
  of: button
1784
  });
1785
  });
@@ -1832,7 +1889,7 @@ $(document).ready(function(){
1832
 
1833
  //Remove the existing icon class, if any.
1834
  var cssClass = getFieldValue(item, 'css_class', '');
1835
- item.css_class = jsTrim( cssClass.replace(/\bmenu-icon-[^\s]+\b/, '') );
1836
 
1837
  //Set the new icon URL.
1838
  item.icon_url = attachment.attributes.url;
@@ -1852,6 +1909,16 @@ $(document).ready(function(){
1852
  iconSelector.hide();
1853
  });
1854
 
 
 
 
 
 
 
 
 
 
 
1855
  //Hide the icon selector if the user clicks outside of it.
1856
  //Exception: Clicks on "Select icon" buttons are handled above.
1857
  $(document).on('mouseup', function(event) {
@@ -1870,6 +1937,129 @@ $(document).ready(function(){
1870
  });
1871
 
1872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1873
  /*************************************************************************
1874
  Menu toolbar buttons
1875
  *************************************************************************/
@@ -2267,6 +2457,8 @@ $(document).ready(function(){
2267
  }
2268
  }
2269
 
 
 
2270
  var data = encodeMenuAsJSON(tree);
2271
  $('#ws_data').val(data);
2272
  $('#ws_data_length').val(data.length);
@@ -2542,24 +2734,27 @@ $(document).ready(function(){
2542
 
2543
  jQuery(function($){
2544
  var screenOptions = $('#ws-ame-screen-meta-contents');
2545
- var checkbox = screenOptions.find('#ws-hide-advanced-settings');
 
2546
 
2547
- if ( wsEditorData.hideAdvancedSettings ){
2548
- checkbox.attr('checked', 'checked');
2549
- } else {
2550
- checkbox.removeAttr('checked');
2551
- }
2552
 
2553
  //Update editor state when settings change
2554
- checkbox.click(function(){
2555
- wsEditorData.hideAdvancedSettings = $(this).attr('checked'); //Using '$(this)' instead of 'checkbox' due to jQuery bugs
2556
- var menuEditorNode = $('#ws_menu_editor');
2557
- if ( wsEditorData.hideAdvancedSettings ){
2558
- menuEditorNode.find('div.ws_advanced').hide();
2559
- menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionShowAdvanced).show();
2560
- } else {
2561
- menuEditorNode.find('div.ws_advanced').show();
2562
- menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionHideAdvanced).hide();
 
 
 
 
 
2563
  }
2564
 
2565
  $.post(
@@ -2567,9 +2762,13 @@ jQuery(function($){
2567
  {
2568
  'action' : 'ws_ame_save_screen_options',
2569
  'hide_advanced_settings' : wsEditorData.hideAdvancedSettings ? 1 : 0,
 
2570
  '_ajax_nonce' : wsEditorData.hideAdvancedSettingsNonce
2571
  }
2572
  );
 
 
 
2573
  });
2574
 
2575
  //Move our options into the screen meta panel
623
  var cssIcon = selectButton.find('.icon16');
624
  var imageIcon = selectButton.find('img');
625
 
626
+ var matches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
627
+ var dashiconMatches = iconUrl && iconUrl.match('^\s*(dashicons-[a-z0-9\-]+)');
628
+
629
  //Icon URL take precedence over icon class.
630
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !dashiconMatches ) {
631
  cssIcon.hide();
632
  imageIcon.prop('src', iconUrl).show();
633
+ } else if ( dashiconMatches ) {
634
+ //Dashicon.
635
+ imageIcon.hide();
636
+ cssIcon.removeClass().addClass('icon16 dashicons ' + dashiconMatches[1]).show();
637
  } else if ( matches ) {
638
+ //Other CSS-based icon.
639
  imageIcon.hide();
640
+ var iconClass = (matches[1] ? matches[1] : '') + 'icon-' + matches[2];
641
+ cssIcon.removeClass().addClass('icon16 ' + iconClass).show();
642
  } else {
643
  //This menu has no icon at all. This is actually a valid state
644
  //and WordPress will display a menu like that correctly.
650
  }
651
  }),
652
 
653
+ 'colors' : $.extend({}, baseField, {
654
+ caption: 'Color scheme',
655
+ defaultValue: 'Default',
656
+ type: 'color_scheme_editor',
657
+ onlyForTopMenus: true,
658
+ visible: false,
659
+ advanced : true,
660
+
661
+ display: function(menuItem, displayValue, input, containerNode) {
662
+ var colors = getFieldValue(menuItem, 'colors', {});
663
+ var colorList = containerNode.find('.ws_color_scheme_display');
664
+
665
+ colorList.empty();
666
+ var count = 0, maxColorsToShow = 7;
667
+
668
+ $.each(colors, function(name, value) {
669
+ if ( !value || (count >= maxColorsToShow) ) {
670
+ return;
671
+ }
672
+
673
+ colorList.append(
674
+ $('<span></span>').addClass('ws_color_display_item').css('background-color', value)
675
+ );
676
+ count++;
677
+ });
678
+
679
+ if (count === 0) {
680
+ colorList.append('Default');
681
+ }
682
+
683
+ return 'Placeholder. You should never see this.';
684
+ },
685
+
686
+ write: function(menuItem) {
687
+ //Menu colors can't be directly edited.
688
+ }
689
+ }),
690
+
691
+ 'page_heading' : $.extend({}, baseField, {
692
+ caption: 'Page heading',
693
+ advanced : true,
694
+ onlyForTopMenus: false,
695
+ visible: false
696
+ }),
697
+
698
  'hookname' : $.extend({}, baseField, {
699
  caption: 'Hook name',
700
  advanced : true,
787
  .add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
788
  break;
789
 
790
+ case 'color_scheme_editor':
791
+ inputBox = $('<span class="ws_color_scheme_display">Placeholder</span>')
792
+ .add('<input type="button" class="button ws_open_color_editor" value="Edit...">');
793
+ break;
794
+
795
  case 'text': //Intentional fall-through.
796
  default:
797
  inputBox = $(basicTextField);
934
  displayValue = knownMenuFields[fieldName].display(menuItem, displayValue, input, containerNode);
935
  }
936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937
  setInputValue(input, displayValue);
938
  });
939
  }
1245
  if (wsEditorData.wsMenuEditorPro) {
1246
  knownMenuFields['open_in'].visible = true;
1247
  knownMenuFields['access_level'].visible = true;
1248
+ knownMenuFields['page_heading'].visible = true;
1249
+ knownMenuFields['colors'].visible = true;
1250
  knownMenuFields['extra_capability'].visible = false; //Superseded by the "access_level" field.
1251
  $('.ws_hide_if_pro').hide();
1252
  }
1309
  //Find the field div (it holds the field name)
1310
  var field = $(this).parents('.ws_edit_field');
1311
  var fieldName = field.data('field_name');
 
 
1312
 
1313
+ if ( (field.length > 0) && fieldName ) {
1314
  //Extract the default value from the menu item.
1315
  var containerNode = field.closest('.ws_container');
1316
  var menuItem = containerNode.data('menu_item');
1765
 
1766
  //Remove the existing icon class, if any.
1767
  var cssClass = getFieldValue(item, 'css_class', '');
1768
+ cssClass = jsTrim( cssClass.replace(/\b(ame-)?menu-icon-[^\s]+\b/, '') );
1769
 
1770
  if (selectedIcon.data('icon-class')) {
1771
  //Add the new class.
1804
 
1805
  //Highlight the currently selected icon.
1806
  iconSelector.find('.ws_selected_icon').removeClass('ws_selected_icon');
1807
+
1808
+ var expandSelector = false;
1809
+ var classMatches = cssClass.match(/\b(ame-)?menu-icon-([^\s]+)\b/);
1810
+ //Dashicons are set via the icon URL field, but they are actually CSS-based.
1811
+ var dashiconMatches = iconUrl && iconUrl.match('^\s*(dashicons-[a-z0-9\-]+)\s*$');
1812
+
1813
+ if ( iconUrl && iconUrl !== 'none' && iconUrl !== 'div' && !dashiconMatches ) {
1814
  var currentIcon = iconSelector.find('.ws_icon_option img[src="' + iconUrl + '"]').first().closest('.ws_icon_option');
1815
  if ( currentIcon.length > 0 ) {
1816
  currentIcon.addClass('ws_selected_icon').show();
1819
  customImageOption.find('img').prop('src', iconUrl);
1820
  customImageOption.addClass('ws_selected_icon').show().data('icon-url', iconUrl);
1821
  }
1822
+ } else if ( classMatches || dashiconMatches ) {
1823
+ //Highlight the icon that corresponds to the current CSS class or Dashicon name.
1824
+ var iconClass = dashiconMatches ? dashiconMatches[1] : ((classMatches[1] ? classMatches[1] : '') + 'icon-' + classMatches[2]);
1825
+ var selectedIcon = iconSelector.find('.' + iconClass).closest('.ws_icon_option').addClass('ws_selected_icon');
1826
+ //If the icon is one of those hidden by default, automatically expand the selector so it becomes visible.
1827
+ if (selectedIcon.hasClass('ws_icon_extra')) {
1828
+ expandSelector = true;
1829
+ }
1830
  }
1831
 
1832
+ expandSelector = expandSelector || (!!wsEditorData.showExtraIcons); //Second argument to toggleClass() must be a boolean, not just truthy/falsy.
1833
+ iconSelector.toggleClass('ws_with_more_icons', expandSelector);
1834
+ $('#ws_show_more_icons').val(expandSelector ? 'Less \u25B2' : 'More \u25BC');
1835
+
1836
  iconSelector.show();
1837
  iconSelector.position({ //Requires jQuery UI.
1838
+ my: 'left top',
1839
+ at: 'left bottom',
1840
  of: button
1841
  });
1842
  });
1889
 
1890
  //Remove the existing icon class, if any.
1891
  var cssClass = getFieldValue(item, 'css_class', '');
1892
+ item.css_class = jsTrim( cssClass.replace(/\b(ame-)?menu-icon-[^\s]+\b/, '') );
1893
 
1894
  //Set the new icon URL.
1895
  item.icon_url = attachment.attributes.url;
1909
  iconSelector.hide();
1910
  });
1911
 
1912
+ //Show/hide additional icons.
1913
+ $('#ws_show_more_icons').click(function() {
1914
+ iconSelector.toggleClass('ws_with_more_icons');
1915
+ wsEditorData.showExtraIcons = iconSelector.hasClass('ws_with_more_icons');
1916
+ $(this).val(wsEditorData.showExtraIcons ? 'Less \u25B2' : 'More \u25BC');
1917
+
1918
+ //Remember the user's choice.
1919
+ $.cookie('ame-show-extra-icons', wsEditorData.showExtraIcons ? '1' : '0', {expires: 90});
1920
+ });
1921
+
1922
  //Hide the icon selector if the user clicks outside of it.
1923
  //Exception: Clicks on "Select icon" buttons are handled above.
1924
  $(document).on('mouseup', function(event) {
1937
  });
1938
 
1939
 
1940
+ /*************************************************************************
1941
+ Color picker
1942
+ *************************************************************************/
1943
+
1944
+ var menuColorDialog = $('#ws-ame-menu-color-settings');
1945
+ if (menuColorDialog.length > 0) {
1946
+ menuColorDialog.dialog({
1947
+ autoOpen: false,
1948
+ closeText: ' ',
1949
+ draggable: false,
1950
+ modal: true,
1951
+ minHeight: 400,
1952
+ minWidth: 520
1953
+ });
1954
+ }
1955
+
1956
+ var colorDialogState = {
1957
+ menuItem: null
1958
+ };
1959
+
1960
+ var menuColorVariables = [
1961
+ 'base-color',
1962
+ 'text-color',
1963
+ 'highlight-color',
1964
+ 'icon-color',
1965
+
1966
+ 'menu-highlight-text',
1967
+ 'menu-highlight-icon',
1968
+ 'menu-highlight-background',
1969
+
1970
+ 'menu-current-text',
1971
+ 'menu-current-icon',
1972
+ 'menu-current-background',
1973
+
1974
+ 'menu-submenu-text',
1975
+ 'menu-submenu-background',
1976
+ 'menu-submenu-focus-text',
1977
+ 'menu-submenu-current-text',
1978
+
1979
+ 'menu-bubble-text',
1980
+ 'menu-bubble-background',
1981
+ 'menu-bubble-current-text',
1982
+ 'menu-bubble-current-background'
1983
+ ];
1984
+
1985
+ //Show only the primary color settings by default.
1986
+ var showAdvancedColors = false;
1987
+ $('#ws-ame-show-advanced-colors').click(function() {
1988
+ showAdvancedColors = !showAdvancedColors;
1989
+ $('#ws-ame-menu-color-settings').find('.ame-advanced-menu-color').toggle(showAdvancedColors);
1990
+ $(this).text(showAdvancedColors ? 'Hide advanced options' : 'Show advanced options');
1991
+ });
1992
+
1993
+ //"Edit.." color schemes.
1994
+ var colorPickersInitialized = false;
1995
+ menuEditorNode.on('click', '.ws_open_color_editor, .ws_color_scheme_display', function() {
1996
+ //Initializing the color pickers takes a while, so we only do it when needed instead of on document ready.
1997
+ if ( !colorPickersInitialized ) {
1998
+ menuColorDialog.find('.ame-color-picker').wpColorPicker();
1999
+ colorPickersInitialized = true;
2000
+ }
2001
+
2002
+ var containerNode = $(this).parents('.ws_container').first();
2003
+ var menuItem = containerNode.data('menu_item');
2004
+
2005
+ colorDialogState.containerNode = containerNode;
2006
+ colorDialogState.menuItem = menuItem;
2007
+
2008
+ var colors = getFieldValue(menuItem, 'colors', {});
2009
+ var customColorCount = 0;
2010
+ for (var i = 0; i < menuColorVariables.length; i++) {
2011
+ var name = menuColorVariables[i];
2012
+ var value = colors.hasOwnProperty(name) ? colors[name] : false;
2013
+
2014
+ if ( value ) {
2015
+ $('#ame-color-' + name).wpColorPicker('color', value);
2016
+ customColorCount++;
2017
+ } else {
2018
+ $('#ame-color-' + name).closest('.wp-picker-container').find('.wp-picker-clear').click();
2019
+ }
2020
+ }
2021
+
2022
+ if ( customColorCount > 0 ) {
2023
+ menuItem.colors = colors;
2024
+ } else {
2025
+ menuItem.colors = null;
2026
+ }
2027
+
2028
+ //Add menu title to the dialog caption.
2029
+ var title = getFieldValue(menuItem, 'menu_title', null);
2030
+ menuColorDialog.dialog(
2031
+ 'option',
2032
+ 'title',
2033
+ title ? ('Colors: ' + title.substring(0, 30)) : 'Colors'
2034
+ );
2035
+ menuColorDialog.dialog('open');
2036
+ });
2037
+
2038
+ //The "Save Changes" button in the color dialog.
2039
+ $('#ws-ame-save-menu-colors').click(function() {
2040
+ menuColorDialog.dialog('close');
2041
+ if ( !colorDialogState.menuItem ) {
2042
+ return;
2043
+ }
2044
+ var menuItem = colorDialogState.menuItem;
2045
+ var colors = {}, colorCount = 0;
2046
+
2047
+ for (var i = 0; i < menuColorVariables.length; i++) {
2048
+ var name = menuColorVariables[i];
2049
+ var value = $('#ame-color-' + name).val();
2050
+ if (value) {
2051
+ colors[name] = value;
2052
+ colorCount++;
2053
+ }
2054
+ }
2055
+
2056
+ menuItem.colors = colorCount > 0 ? colors : null;
2057
+ updateItemEditor(colorDialogState.containerNode);
2058
+
2059
+ colorDialogState.containerNode = null;
2060
+ colorDialogState.menuItem = null;
2061
+ });
2062
+
2063
  /*************************************************************************
2064
  Menu toolbar buttons
2065
  *************************************************************************/
2457
  }
2458
  }
2459
 
2460
+ console.log(tree);
2461
+ //return;
2462
  var data = encodeMenuAsJSON(tree);
2463
  $('#ws_data').val(data);
2464
  $('#ws_data_length').val(data.length);
2734
 
2735
  jQuery(function($){
2736
  var screenOptions = $('#ws-ame-screen-meta-contents');
2737
+ var hideSettingsCheckbox = screenOptions.find('#ws-hide-advanced-settings');
2738
+ var extraIconsCheckbox = screenOptions.find('#ws-show-extra-icons');
2739
 
2740
+ hideSettingsCheckbox.prop('checked', wsEditorData.hideAdvancedSettings);
2741
+ extraIconsCheckbox.prop('checked', wsEditorData.showExtraIcons);
 
 
 
2742
 
2743
  //Update editor state when settings change
2744
+ $('#ws-hide-advanced-settings, #ws-show-extra-icons').click(function(){
2745
+ wsEditorData.hideAdvancedSettings = hideSettingsCheckbox.prop('checked');
2746
+ wsEditorData.showExtraIcons = extraIconsCheckbox.prop('checked');
2747
+
2748
+ //Show/hide advanced settings dynamically as the user changes the setting.
2749
+ if ($(this).is(hideSettingsCheckbox)) {
2750
+ var menuEditorNode = $('#ws_menu_editor');
2751
+ if ( wsEditorData.hideAdvancedSettings ){
2752
+ menuEditorNode.find('div.ws_advanced').hide();
2753
+ menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionShowAdvanced).show();
2754
+ } else {
2755
+ menuEditorNode.find('div.ws_advanced').show();
2756
+ menuEditorNode.find('a.ws_toggle_advanced_fields').text(wsEditorData.captionHideAdvanced).hide();
2757
+ }
2758
  }
2759
 
2760
  $.post(
2762
  {
2763
  'action' : 'ws_ame_save_screen_options',
2764
  'hide_advanced_settings' : wsEditorData.hideAdvancedSettings ? 1 : 0,
2765
+ 'show_extra_icons' : wsEditorData.showExtraIcons ? 1 : 0,
2766
  '_ajax_nonce' : wsEditorData.hideAdvancedSettingsNonce
2767
  }
2768
  );
2769
+
2770
+ //We also have a cookie for the current user.
2771
+ $.cookie('ame-show-extra-icons', wsEditorData.showExtraIcons ? '1' : '0', {expires: 90});
2772
  });
2773
 
2774
  //Move our options into the screen meta panel
js/menu-highlight-fix.js CHANGED
@@ -55,9 +55,9 @@ jQuery(function($) {
55
  adminMenu.find('li > a').each(function(index, link) {
56
  var $link = $(link);
57
 
58
- //Skip links that contain nothing but an "#anchor". Both AME and some
59
  //other plugins (e.g. S2Member 120703) use them as separators.
60
- if ($link.attr('href').substring(0, 1) == '#') {
61
  return;
62
  }
63
 
55
  adminMenu.find('li > a').each(function(index, link) {
56
  var $link = $(link);
57
 
58
+ //Skip links that have no href or contain nothing but an "#anchor". Both AME and some
59
  //other plugins (e.g. S2Member 120703) use them as separators.
60
+ if ( !$link.is('[href]') || ($link.attr('href').substring(0, 1) == '#') ) {
61
  return;
62
  }
63
 
menu-editor.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
- Version: 1.3.1
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
3
  Plugin Name: Admin Menu Editor
4
  Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
5
  Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
6
+ Version: 1.3.2
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
- Requires at least: 3.3
6
- Tested up to: 3.8
7
- Stable tag: 1.3.1
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
@@ -19,7 +19,7 @@ Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the
19
  * Move a menu item to a different submenu.
20
  * Create custom menus that point to any part of the Dashboard or an external URL.
21
 
22
- The [Pro version](http://w-shadow.com/AdminMenuEditor/) lets you set per-role menu permissions, hide a menu from everyone except a specific user, export your admin menu, drag items between menu levels, make menus open in a new window and more. [Try live demo](http://amedemo.com/wpdemo/demo.php).
23
 
24
  **Notes**
25
 
@@ -63,6 +63,14 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
66
  = 1.3.1 =
67
  * Tested with WordPress 3.8.
68
  * Fixed several minor UI/layout issues related to the new 3.8 admin style.
2
  Contributors: whiteshadow
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
4
  Tags: admin, dashboard, menu, security, wpmu
5
+ Requires at least: 3.5
6
+ Tested up to: 4.0-alpha
7
+ Stable tag: 1.3.2
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
19
  * Move a menu item to a different submenu.
20
  * Create custom menus that point to any part of the Dashboard or an external URL.
21
 
22
+ The [Pro version](http://w-shadow.com/AdminMenuEditor/) lets you set per-role menu permissions, hide a menu from everyone except a specific user, export your admin menu, drag items between menu levels, make menus open in a new window and more. [Try online demo](http://amedemo.com/wpdemo/demo.php).
23
 
24
  **Notes**
25
 
63
 
64
  == Changelog ==
65
 
66
+ = 1.3.2 =
67
+ * Added a large number of menu icons based on the Dashicons icon font.
68
+ * Fixed default menu icons not showing up in WP 3.9.
69
+ * Fixed a rare "$link.attr(...) is undefined" JavaScript error.
70
+ * Fixed a bug where a hidden submenu page with a URL like "options-general.php?page=something" would still be accessible via "admin.php?page=something".
71
+ * Fixed several other minor bugs.
72
+ * Tested up to WordPress 3.9-RC1. Minimum requirements increased to WP 3.5.
73
+
74
  = 1.3.1 =
75
  * Tested with WordPress 3.8.
76
  * Fixed several minor UI/layout issues related to the new 3.8 admin style.