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 | Admin Menu Editor |
Version | 1.3.2 |
Comparing to | |
See all releases |
Code changes from version 1.3.1 to 1.3.2
- css/admin.css +1 -9
- css/menu-editor.css +94 -5
- includes/.htaccess +2 -0
- includes/PHP-CSS-Parser/CHANGELOG.md +121 -0
- includes/PHP-CSS-Parser/README.md +519 -0
- includes/PHP-CSS-Parser/autoloader.php +10 -0
- includes/PHP-CSS-Parser/composer.json +17 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/AtRuleBlockList.php +36 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSBlockList.php +78 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/CSSList.php +75 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/Document.php +87 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/CSSList/KeyFrame.php +48 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parser.php +629 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Parsing/UnexpectedTokenException.php +28 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/AtRule.php +13 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/CSSNamespace.php +48 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Charset.php +39 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Import.php +42 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Property/Selector.php +75 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Rule/Rule.php +142 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/AtRuleSet.php +36 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php +585 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/RuleSet/RuleSet.php +95 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Settings.php +54 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/CSSFunction.php +35 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Color.php +38 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/PrimitiveValue.php +7 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/RuleValueList.php +11 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Size.php +68 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/String.php +27 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/URL.php +26 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/Value.php +8 -0
- includes/PHP-CSS-Parser/lib/Sabberworm/CSS/Value/ValueList.php +42 -0
- includes/admin-menu-editor-mu.php +12 -2
- includes/editor-page.php +103 -18
- includes/generate-menu-dashicons.php +107 -0
- includes/menu-editor-core.php +62 -5
- includes/menu-item.php +5 -0
- includes/menu.php +12 -3
- includes/shadow_plugin_framework.php +18 -5
- js/admin-helpers.js +51 -9
- js/jquery.cookie.js +117 -0
- js/menu-editor.js +244 -45
- js/menu-highlight-fix.js +2 -2
- menu-editor.php +1 -1
- 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:
|
229 |
-
height:
|
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,
|
|
|
|
|
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:
|
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:
|
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
|
4 |
-
Plugin URI: http://
|
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 -> 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 -> 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
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 |
-
|
284 |
-
|
285 |
-
|
286 |
-
)
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
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 |
-
|
318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
319 |
echo ' checked="checked"';
|
320 |
}
|
321 |
-
|
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 ▲' : 'More ▼'); ?>">
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
'
|
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 |
-
'
|
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.
|
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
|
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(
|
|
|
|
|
|
|
|
|
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.
|
238 |
*
|
239 |
* @return void
|
240 |
*/
|
241 |
function activate(){
|
242 |
-
|
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 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
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(/\
|
|
|
|
|
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 |
-
|
|
|
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 ( (
|
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(/\
|
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 |
-
|
1765 |
-
|
|
|
|
|
|
|
|
|
|
|
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 (
|
1775 |
-
//Highlight the icon that corresponds to the current CSS class.
|
1776 |
-
|
|
|
|
|
|
|
|
|
|
|
1777 |
}
|
1778 |
|
|
|
|
|
|
|
|
|
1779 |
iconSelector.show();
|
1780 |
iconSelector.position({ //Requires jQuery UI.
|
1781 |
-
my: '
|
1782 |
-
at: '
|
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(/\
|
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
|
|
|
2546 |
|
2547 |
-
|
2548 |
-
|
2549 |
-
} else {
|
2550 |
-
checkbox.removeAttr('checked');
|
2551 |
-
}
|
2552 |
|
2553 |
//Update editor state when settings change
|
2554 |
-
|
2555 |
-
wsEditorData.hideAdvancedSettings =
|
2556 |
-
|
2557 |
-
|
2558 |
-
|
2559 |
-
|
2560 |
-
|
2561 |
-
|
2562 |
-
|
|
|
|
|
|
|
|
|
|
|
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.
|
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.
|
6 |
-
Tested up to:
|
7 |
-
Stable tag: 1.3.
|
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
|
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.
|