Version Description
Release Date: November 14th, 2017
- Initial Release.
Download this release
Release Info
Developer | rramo012 |
Plugin | BoldGrid Easy SEO – Simple and Effective SEO |
Version | 1.5.1 |
Comparing to | |
See all releases |
Version 1.5.1
- LICENSE +339 -0
- assets/css/boldgrid-seo-admin.css +153 -0
- assets/css/boldgrid-seo-admin.min.css +1 -0
- assets/js/bgseo.js +3106 -0
- assets/js/bgseo.min.js +1 -0
- assets/js/bgseo/boldgrid-seo-admin.js +112 -0
- assets/js/bgseo/boldgrid-seo-content-analysis.js +121 -0
- assets/js/bgseo/boldgrid-seo-dashboard.js +140 -0
- assets/js/bgseo/boldgrid-seo-description.js +150 -0
- assets/js/bgseo/boldgrid-seo-headings.js +258 -0
- assets/js/bgseo/boldgrid-seo-init.js +35 -0
- assets/js/bgseo/boldgrid-seo-keywords.js +559 -0
- assets/js/bgseo/boldgrid-seo-readability.js +144 -0
- assets/js/bgseo/boldgrid-seo-report.js +300 -0
- assets/js/bgseo/boldgrid-seo-robots.js +145 -0
- assets/js/bgseo/boldgrid-seo-sections.js +110 -0
- assets/js/bgseo/boldgrid-seo-tinymce.js +235 -0
- assets/js/bgseo/boldgrid-seo-title.js +156 -0
- assets/js/bgseo/boldgrid-seo-tooltips.js +100 -0
- assets/js/bgseo/boldgrid-seo-util.js +193 -0
- assets/js/bgseo/boldgrid-seo-wordcount.js +46 -0
- assets/js/bgseo/boldgrid-seo-words.js +121 -0
- assets/js/bgseo/boldgrid-seo.js +13 -0
- assets/js/control/boldgrid-seo-control-dashboard.js +106 -0
- assets/js/control/boldgrid-seo-control-keywords.js +43 -0
- assets/js/text-statistics/LICENSE.md +20 -0
- assets/js/text-statistics/README.md +15 -0
- assets/js/text-statistics/index.js +239 -0
- assets/partials/control-bgseo-textarea.php +9 -0
- assets/partials/control-dashboard.php +20 -0
- assets/partials/control-keywords.php +18 -0
- autoload.php +56 -0
- boldgrid-easy-seo.php +119 -0
- changelog.txt +95 -0
- includes/class-boldgrid-seo-activator.php +36 -0
- includes/class-boldgrid-seo-admin.php +425 -0
- includes/class-boldgrid-seo-butterbean.php +101 -0
- includes/class-boldgrid-seo-config.php +88 -0
- includes/class-boldgrid-seo-control-dashboard.php +49 -0
- includes/class-boldgrid-seo-control-keywords.php +41 -0
- includes/class-boldgrid-seo-deactivator.php +36 -0
- includes/class-boldgrid-seo-i18n.php +57 -0
- includes/class-boldgrid-seo-loader.php +121 -0
- includes/class-boldgrid-seo-scripts.php +97 -0
- includes/class-boldgrid-seo-upgrade.php +177 -0
- includes/class-boldgrid-seo-util.php +248 -0
- includes/class-boldgrid-seo.php +209 -0
- includes/configs/.gitignore +1 -0
- includes/configs/admin.config.php +19 -0
- includes/configs/base.config.php +10 -0
- includes/configs/config.sample.php +7 -0
- includes/configs/i18n/content.config.php +71 -0
- includes/configs/i18n/headings.config.php +43 -0
- includes/configs/i18n/image.config.php +15 -0
- includes/configs/i18n/keywords.config.php +30 -0
- includes/configs/i18n/noFollow.config.php +13 -0
- includes/configs/i18n/noIndex.config.php +13 -0
- includes/configs/i18n/readingEase.config.php +11 -0
- includes/configs/i18n/seoDescription.config.php +50 -0
- includes/configs/i18n/seoTitle.config.php +50 -0
- includes/configs/i18n/stopWords.config.php +2 -0
- includes/configs/index.php +2 -0
- includes/configs/meta-box.config.php +113 -0
- includes/index.php +1 -0
- includes/interface-boldgrid-seo-config.php +17 -0
- includes/lib/butterbean/butterbean.php +44 -0
- includes/lib/butterbean/changelog.md +5 -0
- includes/lib/butterbean/class-butterbean.php +867 -0
- includes/lib/butterbean/contributing.md +31 -0
- includes/lib/butterbean/css/butterbean.css +340 -0
- includes/lib/butterbean/css/butterbean.min.css +1 -0
- includes/lib/butterbean/inc/class-control.php +399 -0
- includes/lib/butterbean/inc/class-manager.php +550 -0
- includes/lib/butterbean/inc/class-section.php +291 -0
- includes/lib/butterbean/inc/class-setting.php +226 -0
- includes/lib/butterbean/inc/controls/class-control-checkboxes.php +43 -0
- includes/lib/butterbean/inc/controls/class-control-color.php +104 -0
- includes/lib/butterbean/inc/controls/class-control-datetime.php +140 -0
- includes/lib/butterbean/inc/controls/class-control-excerpt.php +82 -0
- includes/lib/butterbean/inc/controls/class-control-image.php +112 -0
- includes/lib/butterbean/inc/controls/class-control-multi-avatars.php +90 -0
- includes/lib/butterbean/inc/controls/class-control-palette.php +51 -0
- includes/lib/butterbean/inc/controls/class-control-parent.php +95 -0
- includes/lib/butterbean/inc/controls/class-control-radio-image.php +46 -0
- includes/lib/butterbean/inc/controls/class-control-radio.php +46 -0
- includes/lib/butterbean/inc/controls/class-control-select-group.php +53 -0
- includes/lib/butterbean/inc/controls/class-control-textarea.php +42 -0
- includes/lib/butterbean/inc/functions-core.php +169 -0
- includes/lib/butterbean/inc/settings/class-setting-array.php +80 -0
- includes/lib/butterbean/inc/settings/class-setting-datetime.php +105 -0
- includes/lib/butterbean/inc/settings/class-setting-multiple.php +154 -0
- includes/lib/butterbean/js/butterbean.js +1062 -0
- includes/lib/butterbean/js/butterbean.min.js +1 -0
- includes/lib/butterbean/license.md +339 -0
- includes/lib/butterbean/readme.md +152 -0
- includes/lib/butterbean/tmpl/control-checkbox.php +11 -0
- includes/lib/butterbean/tmpl/control-checkboxes.php +22 -0
- includes/lib/butterbean/tmpl/control-color.php +11 -0
- includes/lib/butterbean/tmpl/control-datetime.php +55 -0
- includes/lib/butterbean/tmpl/control-image.php +24 -0
- includes/lib/butterbean/tmpl/control-multi-avatars.php +23 -0
- includes/lib/butterbean/tmpl/control-palette.php +23 -0
- includes/lib/butterbean/tmpl/control-parent.php +17 -0
- includes/lib/butterbean/tmpl/control-radio-image.php +17 -0
- includes/lib/butterbean/tmpl/control-radio.php +22 -0
- includes/lib/butterbean/tmpl/control-select-group.php +33 -0
- includes/lib/butterbean/tmpl/control-select.php +20 -0
- includes/lib/butterbean/tmpl/control-textarea.php +11 -0
- includes/lib/butterbean/tmpl/control.php +11 -0
- includes/lib/butterbean/tmpl/manager.php +2 -0
- includes/lib/butterbean/tmpl/nav.php +1 -0
- includes/lib/butterbean/tmpl/section.php +3 -0
- index.php +1 -0
- languages/bgseo.pot +325 -0
- readme.txt +84 -0
- uninstall.php +31 -0
LICENSE
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 2, June 1991
|
3 |
+
|
4 |
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
5 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
6 |
+
Everyone is permitted to copy and distribute verbatim copies
|
7 |
+
of this license document, but changing it is not allowed.
|
8 |
+
|
9 |
+
Preamble
|
10 |
+
|
11 |
+
The licenses for most software are designed to take away your
|
12 |
+
freedom to share and change it. By contrast, the GNU General Public
|
13 |
+
License is intended to guarantee your freedom to share and change free
|
14 |
+
software--to make sure the software is free for all its users. This
|
15 |
+
General Public License applies to most of the Free Software
|
16 |
+
Foundation's software and to any other program whose authors commit to
|
17 |
+
using it. (Some other Free Software Foundation software is covered by
|
18 |
+
the GNU Lesser General Public License instead.) You can apply it to
|
19 |
+
your programs, too.
|
20 |
+
|
21 |
+
When we speak of free software, we are referring to freedom, not
|
22 |
+
price. Our General Public Licenses are designed to make sure that you
|
23 |
+
have the freedom to distribute copies of free software (and charge for
|
24 |
+
this service if you wish), that you receive source code or can get it
|
25 |
+
if you want it, that you can change the software or use pieces of it
|
26 |
+
in new free programs; and that you know you can do these things.
|
27 |
+
|
28 |
+
To protect your rights, we need to make restrictions that forbid
|
29 |
+
anyone to deny you these rights or to ask you to surrender the rights.
|
30 |
+
These restrictions translate to certain responsibilities for you if you
|
31 |
+
distribute copies of the software, or if you modify it.
|
32 |
+
|
33 |
+
For example, if you distribute copies of such a program, whether
|
34 |
+
gratis or for a fee, you must give the recipients all the rights that
|
35 |
+
you have. You must make sure that they, too, receive or can get the
|
36 |
+
source code. And you must show them these terms so they know their
|
37 |
+
rights.
|
38 |
+
|
39 |
+
We protect your rights with two steps: (1) copyright the software, and
|
40 |
+
(2) offer you this license which gives you legal permission to copy,
|
41 |
+
distribute and/or modify the software.
|
42 |
+
|
43 |
+
Also, for each author's protection and ours, we want to make certain
|
44 |
+
that everyone understands that there is no warranty for this free
|
45 |
+
software. If the software is modified by someone else and passed on, we
|
46 |
+
want its recipients to know that what they have is not the original, so
|
47 |
+
that any problems introduced by others will not reflect on the original
|
48 |
+
authors' reputations.
|
49 |
+
|
50 |
+
Finally, any free program is threatened constantly by software
|
51 |
+
patents. We wish to avoid the danger that redistributors of a free
|
52 |
+
program will individually obtain patent licenses, in effect making the
|
53 |
+
program proprietary. To prevent this, we have made it clear that any
|
54 |
+
patent must be licensed for everyone's free use or not licensed at all.
|
55 |
+
|
56 |
+
The precise terms and conditions for copying, distribution and
|
57 |
+
modification follow.
|
58 |
+
|
59 |
+
GNU GENERAL PUBLIC LICENSE
|
60 |
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
61 |
+
|
62 |
+
0. This License applies to any program or other work which contains
|
63 |
+
a notice placed by the copyright holder saying it may be distributed
|
64 |
+
under the terms of this General Public License. The "Program", below,
|
65 |
+
refers to any such program or work, and a "work based on the Program"
|
66 |
+
means either the Program or any derivative work under copyright law:
|
67 |
+
that is to say, a work containing the Program or a portion of it,
|
68 |
+
either verbatim or with modifications and/or translated into another
|
69 |
+
language. (Hereinafter, translation is included without limitation in
|
70 |
+
the term "modification".) Each licensee is addressed as "you".
|
71 |
+
|
72 |
+
Activities other than copying, distribution and modification are not
|
73 |
+
covered by this License; they are outside its scope. The act of
|
74 |
+
running the Program is not restricted, and the output from the Program
|
75 |
+
is covered only if its contents constitute a work based on the
|
76 |
+
Program (independent of having been made by running the Program).
|
77 |
+
Whether that is true depends on what the Program does.
|
78 |
+
|
79 |
+
1. You may copy and distribute verbatim copies of the Program's
|
80 |
+
source code as you receive it, in any medium, provided that you
|
81 |
+
conspicuously and appropriately publish on each copy an appropriate
|
82 |
+
copyright notice and disclaimer of warranty; keep intact all the
|
83 |
+
notices that refer to this License and to the absence of any warranty;
|
84 |
+
and give any other recipients of the Program a copy of this License
|
85 |
+
along with the Program.
|
86 |
+
|
87 |
+
You may charge a fee for the physical act of transferring a copy, and
|
88 |
+
you may at your option offer warranty protection in exchange for a fee.
|
89 |
+
|
90 |
+
2. You may modify your copy or copies of the Program or any portion
|
91 |
+
of it, thus forming a work based on the Program, and copy and
|
92 |
+
distribute such modifications or work under the terms of Section 1
|
93 |
+
above, provided that you also meet all of these conditions:
|
94 |
+
|
95 |
+
a) You must cause the modified files to carry prominent notices
|
96 |
+
stating that you changed the files and the date of any change.
|
97 |
+
|
98 |
+
b) You must cause any work that you distribute or publish, that in
|
99 |
+
whole or in part contains or is derived from the Program or any
|
100 |
+
part thereof, to be licensed as a whole at no charge to all third
|
101 |
+
parties under the terms of this License.
|
102 |
+
|
103 |
+
c) If the modified program normally reads commands interactively
|
104 |
+
when run, you must cause it, when started running for such
|
105 |
+
interactive use in the most ordinary way, to print or display an
|
106 |
+
announcement including an appropriate copyright notice and a
|
107 |
+
notice that there is no warranty (or else, saying that you provide
|
108 |
+
a warranty) and that users may redistribute the program under
|
109 |
+
these conditions, and telling the user how to view a copy of this
|
110 |
+
License. (Exception: if the Program itself is interactive but
|
111 |
+
does not normally print such an announcement, your work based on
|
112 |
+
the Program is not required to print an announcement.)
|
113 |
+
|
114 |
+
These requirements apply to the modified work as a whole. If
|
115 |
+
identifiable sections of that work are not derived from the Program,
|
116 |
+
and can be reasonably considered independent and separate works in
|
117 |
+
themselves, then this License, and its terms, do not apply to those
|
118 |
+
sections when you distribute them as separate works. But when you
|
119 |
+
distribute the same sections as part of a whole which is a work based
|
120 |
+
on the Program, the distribution of the whole must be on the terms of
|
121 |
+
this License, whose permissions for other licensees extend to the
|
122 |
+
entire whole, and thus to each and every part regardless of who wrote it.
|
123 |
+
|
124 |
+
Thus, it is not the intent of this section to claim rights or contest
|
125 |
+
your rights to work written entirely by you; rather, the intent is to
|
126 |
+
exercise the right to control the distribution of derivative or
|
127 |
+
collective works based on the Program.
|
128 |
+
|
129 |
+
In addition, mere aggregation of another work not based on the Program
|
130 |
+
with the Program (or with a work based on the Program) on a volume of
|
131 |
+
a storage or distribution medium does not bring the other work under
|
132 |
+
the scope of this License.
|
133 |
+
|
134 |
+
3. You may copy and distribute the Program (or a work based on it,
|
135 |
+
under Section 2) in object code or executable form under the terms of
|
136 |
+
Sections 1 and 2 above provided that you also do one of the following:
|
137 |
+
|
138 |
+
a) Accompany it with the complete corresponding machine-readable
|
139 |
+
source code, which must be distributed under the terms of Sections
|
140 |
+
1 and 2 above on a medium customarily used for software interchange; or,
|
141 |
+
|
142 |
+
b) Accompany it with a written offer, valid for at least three
|
143 |
+
years, to give any third party, for a charge no more than your
|
144 |
+
cost of physically performing source distribution, a complete
|
145 |
+
machine-readable copy of the corresponding source code, to be
|
146 |
+
distributed under the terms of Sections 1 and 2 above on a medium
|
147 |
+
customarily used for software interchange; or,
|
148 |
+
|
149 |
+
c) Accompany it with the information you received as to the offer
|
150 |
+
to distribute corresponding source code. (This alternative is
|
151 |
+
allowed only for noncommercial distribution and only if you
|
152 |
+
received the program in object code or executable form with such
|
153 |
+
an offer, in accord with Subsection b above.)
|
154 |
+
|
155 |
+
The source code for a work means the preferred form of the work for
|
156 |
+
making modifications to it. For an executable work, complete source
|
157 |
+
code means all the source code for all modules it contains, plus any
|
158 |
+
associated interface definition files, plus the scripts used to
|
159 |
+
control compilation and installation of the executable. However, as a
|
160 |
+
special exception, the source code distributed need not include
|
161 |
+
anything that is normally distributed (in either source or binary
|
162 |
+
form) with the major components (compiler, kernel, and so on) of the
|
163 |
+
operating system on which the executable runs, unless that component
|
164 |
+
itself accompanies the executable.
|
165 |
+
|
166 |
+
If distribution of executable or object code is made by offering
|
167 |
+
access to copy from a designated place, then offering equivalent
|
168 |
+
access to copy the source code from the same place counts as
|
169 |
+
distribution of the source code, even though third parties are not
|
170 |
+
compelled to copy the source along with the object code.
|
171 |
+
|
172 |
+
4. You may not copy, modify, sublicense, or distribute the Program
|
173 |
+
except as expressly provided under this License. Any attempt
|
174 |
+
otherwise to copy, modify, sublicense or distribute the Program is
|
175 |
+
void, and will automatically terminate your rights under this License.
|
176 |
+
However, parties who have received copies, or rights, from you under
|
177 |
+
this License will not have their licenses terminated so long as such
|
178 |
+
parties remain in full compliance.
|
179 |
+
|
180 |
+
5. You are not required to accept this License, since you have not
|
181 |
+
signed it. However, nothing else grants you permission to modify or
|
182 |
+
distribute the Program or its derivative works. These actions are
|
183 |
+
prohibited by law if you do not accept this License. Therefore, by
|
184 |
+
modifying or distributing the Program (or any work based on the
|
185 |
+
Program), you indicate your acceptance of this License to do so, and
|
186 |
+
all its terms and conditions for copying, distributing or modifying
|
187 |
+
the Program or works based on it.
|
188 |
+
|
189 |
+
6. Each time you redistribute the Program (or any work based on the
|
190 |
+
Program), the recipient automatically receives a license from the
|
191 |
+
original licensor to copy, distribute or modify the Program subject to
|
192 |
+
these terms and conditions. You may not impose any further
|
193 |
+
restrictions on the recipients' exercise of the rights granted herein.
|
194 |
+
You are not responsible for enforcing compliance by third parties to
|
195 |
+
this License.
|
196 |
+
|
197 |
+
7. If, as a consequence of a court judgment or allegation of patent
|
198 |
+
infringement or for any other reason (not limited to patent issues),
|
199 |
+
conditions are imposed on you (whether by court order, agreement or
|
200 |
+
otherwise) that contradict the conditions of this License, they do not
|
201 |
+
excuse you from the conditions of this License. If you cannot
|
202 |
+
distribute so as to satisfy simultaneously your obligations under this
|
203 |
+
License and any other pertinent obligations, then as a consequence you
|
204 |
+
may not distribute the Program at all. For example, if a patent
|
205 |
+
license would not permit royalty-free redistribution of the Program by
|
206 |
+
all those who receive copies directly or indirectly through you, then
|
207 |
+
the only way you could satisfy both it and this License would be to
|
208 |
+
refrain entirely from distribution of the Program.
|
209 |
+
|
210 |
+
If any portion of this section is held invalid or unenforceable under
|
211 |
+
any particular circumstance, the balance of the section is intended to
|
212 |
+
apply and the section as a whole is intended to apply in other
|
213 |
+
circumstances.
|
214 |
+
|
215 |
+
It is not the purpose of this section to induce you to infringe any
|
216 |
+
patents or other property right claims or to contest validity of any
|
217 |
+
such claims; this section has the sole purpose of protecting the
|
218 |
+
integrity of the free software distribution system, which is
|
219 |
+
implemented by public license practices. Many people have made
|
220 |
+
generous contributions to the wide range of software distributed
|
221 |
+
through that system in reliance on consistent application of that
|
222 |
+
system; it is up to the author/donor to decide if he or she is willing
|
223 |
+
to distribute software through any other system and a licensee cannot
|
224 |
+
impose that choice.
|
225 |
+
|
226 |
+
This section is intended to make thoroughly clear what is believed to
|
227 |
+
be a consequence of the rest of this License.
|
228 |
+
|
229 |
+
8. If the distribution and/or use of the Program is restricted in
|
230 |
+
certain countries either by patents or by copyrighted interfaces, the
|
231 |
+
original copyright holder who places the Program under this License
|
232 |
+
may add an explicit geographical distribution limitation excluding
|
233 |
+
those countries, so that distribution is permitted only in or among
|
234 |
+
countries not thus excluded. In such case, this License incorporates
|
235 |
+
the limitation as if written in the body of this License.
|
236 |
+
|
237 |
+
9. The Free Software Foundation may publish revised and/or new versions
|
238 |
+
of the General Public License from time to time. Such new versions will
|
239 |
+
be similar in spirit to the present version, but may differ in detail to
|
240 |
+
address new problems or concerns.
|
241 |
+
|
242 |
+
Each version is given a distinguishing version number. If the Program
|
243 |
+
specifies a version number of this License which applies to it and "any
|
244 |
+
later version", you have the option of following the terms and conditions
|
245 |
+
either of that version or of any later version published by the Free
|
246 |
+
Software Foundation. If the Program does not specify a version number of
|
247 |
+
this License, you may choose any version ever published by the Free Software
|
248 |
+
Foundation.
|
249 |
+
|
250 |
+
10. If you wish to incorporate parts of the Program into other free
|
251 |
+
programs whose distribution conditions are different, write to the author
|
252 |
+
to ask for permission. For software which is copyrighted by the Free
|
253 |
+
Software Foundation, write to the Free Software Foundation; we sometimes
|
254 |
+
make exceptions for this. Our decision will be guided by the two goals
|
255 |
+
of preserving the free status of all derivatives of our free software and
|
256 |
+
of promoting the sharing and reuse of software generally.
|
257 |
+
|
258 |
+
NO WARRANTY
|
259 |
+
|
260 |
+
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
261 |
+
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
262 |
+
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
263 |
+
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
264 |
+
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
265 |
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
266 |
+
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
267 |
+
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
268 |
+
REPAIR OR CORRECTION.
|
269 |
+
|
270 |
+
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
271 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
272 |
+
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
273 |
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
274 |
+
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
275 |
+
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
276 |
+
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
277 |
+
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
278 |
+
POSSIBILITY OF SUCH DAMAGES.
|
279 |
+
|
280 |
+
END OF TERMS AND CONDITIONS
|
281 |
+
|
282 |
+
How to Apply These Terms to Your New Programs
|
283 |
+
|
284 |
+
If you develop a new program, and you want it to be of the greatest
|
285 |
+
possible use to the public, the best way to achieve this is to make it
|
286 |
+
free software which everyone can redistribute and change under these terms.
|
287 |
+
|
288 |
+
To do so, attach the following notices to the program. It is safest
|
289 |
+
to attach them to the start of each source file to most effectively
|
290 |
+
convey the exclusion of warranty; and each file should have at least
|
291 |
+
the "copyright" line and a pointer to where the full notice is found.
|
292 |
+
|
293 |
+
{description}
|
294 |
+
Copyright (C) {year} {fullname}
|
295 |
+
|
296 |
+
This program is free software; you can redistribute it and/or modify
|
297 |
+
it under the terms of the GNU General Public License as published by
|
298 |
+
the Free Software Foundation; either version 2 of the License, or
|
299 |
+
(at your option) any later version.
|
300 |
+
|
301 |
+
This program is distributed in the hope that it will be useful,
|
302 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
303 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
304 |
+
GNU General Public License for more details.
|
305 |
+
|
306 |
+
You should have received a copy of the GNU General Public License along
|
307 |
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
308 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
309 |
+
|
310 |
+
Also add information on how to contact you by electronic and paper mail.
|
311 |
+
|
312 |
+
If the program is interactive, make it output a short notice like this
|
313 |
+
when it starts in an interactive mode:
|
314 |
+
|
315 |
+
Gnomovision version 69, Copyright (C) year name of author
|
316 |
+
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
317 |
+
This is free software, and you are welcome to redistribute it
|
318 |
+
under certain conditions; type `show c' for details.
|
319 |
+
|
320 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
321 |
+
parts of the General Public License. Of course, the commands you use may
|
322 |
+
be called something other than `show w' and `show c'; they could even be
|
323 |
+
mouse-clicks or menu items--whatever suits your program.
|
324 |
+
|
325 |
+
You should also get your employer (if you work as a programmer) or your
|
326 |
+
school, if any, to sign a "copyright disclaimer" for the program, if
|
327 |
+
necessary. Here is a sample; alter the names:
|
328 |
+
|
329 |
+
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
330 |
+
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
331 |
+
|
332 |
+
{signature of Ty Coon}, 1 April 1989
|
333 |
+
Ty Coon, President of Vice
|
334 |
+
|
335 |
+
This General Public License does not permit incorporating your program into
|
336 |
+
proprietary programs. If your program is a subroutine library, you may
|
337 |
+
consider it more useful to permit linking proprietary applications with the
|
338 |
+
library. If this is what you want to do, use the GNU Lesser General
|
339 |
+
Public License instead of this License.
|
assets/css/boldgrid-seo-admin.css
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/** Analysis suggestions **/
|
2 |
+
.bgseo-recommendations {
|
3 |
+
display: flex;
|
4 |
+
margin-left: -12px;
|
5 |
+
}
|
6 |
+
.analysis-suggestion::before {
|
7 |
+
display: inline-block;
|
8 |
+
float: left;
|
9 |
+
margin: 3px 10px 3px -22px;
|
10 |
+
width: 13px;
|
11 |
+
height: 13px;
|
12 |
+
content: '';
|
13 |
+
vertical-align: middle;
|
14 |
+
border-radius: 100%;
|
15 |
+
}
|
16 |
+
.analysis-suggestion {
|
17 |
+
float: left;
|
18 |
+
margin-left: 32px;
|
19 |
+
}
|
20 |
+
|
21 |
+
/** Analysis suggestions in metabox sidebar **/
|
22 |
+
#side-sortables .bgseo-recommendations {
|
23 |
+
display: flex;
|
24 |
+
margin-left: -12px;
|
25 |
+
margin-bottom: 8px;
|
26 |
+
}
|
27 |
+
#side-sortables .bgseo-recommendations:last-of-type {
|
28 |
+
margin-bottom : 0;
|
29 |
+
}
|
30 |
+
|
31 |
+
/** Overview Status **/
|
32 |
+
.overview-status::before {
|
33 |
+
display: inline-block;
|
34 |
+
margin: 3px 10px 3px 0;
|
35 |
+
width: 13px;
|
36 |
+
height: 13px;
|
37 |
+
content: '';
|
38 |
+
vertical-align: middle;
|
39 |
+
border-radius: 100%;
|
40 |
+
}
|
41 |
+
|
42 |
+
/** Status Indicator Colors **/
|
43 |
+
.overview-status.red::before,
|
44 |
+
.analysis-suggestion.red::before {
|
45 |
+
background: #EA4335;
|
46 |
+
}
|
47 |
+
.overview-status.green::before,
|
48 |
+
.analysis-suggestion.green::before {
|
49 |
+
background: #34A853;
|
50 |
+
}
|
51 |
+
.overview-status.yellow::before,
|
52 |
+
.analysis-suggestion.yellow::before {
|
53 |
+
background: #FBBC05;
|
54 |
+
}
|
55 |
+
|
56 |
+
/** Nofollow/Noindex styles **/
|
57 |
+
#butterbean-control-bgseo_robots_index .butterbean-radio-list li,
|
58 |
+
#butterbean-control-bgseo_robots_follow .butterbean-radio-list li {
|
59 |
+
display: inline-block;
|
60 |
+
padding-right: 1em;
|
61 |
+
}
|
62 |
+
|
63 |
+
/** Tooltip Styles **/
|
64 |
+
.bgseo-tooltip {
|
65 |
+
padding-left: 0.3em;
|
66 |
+
font-size: 18px;
|
67 |
+
vertical-align: bottom;
|
68 |
+
cursor: pointer;
|
69 |
+
}
|
70 |
+
|
71 |
+
/** Nav Styles **/
|
72 |
+
.butterbean-manager-default .butterbean-nav {
|
73 |
+
background-color: #eee;
|
74 |
+
}
|
75 |
+
.butterbean-nav li[aria-selected="false"] a {
|
76 |
+
background-color: #fafafa;
|
77 |
+
}
|
78 |
+
.butterbean-manager-default .butterbean-nav li.green a {
|
79 |
+
border-right: 5px solid #34A853;
|
80 |
+
border-bottom: none;
|
81 |
+
margin-bottom: 1px;
|
82 |
+
}
|
83 |
+
.butterbean-manager-default .butterbean-nav li.yellow a {
|
84 |
+
border-right: 5px solid #FBBC05;
|
85 |
+
border-bottom: none;
|
86 |
+
margin-bottom: 1px;
|
87 |
+
}
|
88 |
+
.butterbean-manager-default .butterbean-nav li.red a {
|
89 |
+
border-right: 5px solid #EA4335;
|
90 |
+
border-bottom: none;
|
91 |
+
margin-bottom: 1px;
|
92 |
+
}
|
93 |
+
|
94 |
+
/** Nav in metabox sidebar **/
|
95 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li.green a {
|
96 |
+
border-right: none;
|
97 |
+
border-bottom: 4px solid #34A853;
|
98 |
+
margin-bottom: 0;
|
99 |
+
}
|
100 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li.yellow a {
|
101 |
+
border-right: none;
|
102 |
+
border-bottom: 4px solid #FBBC05;
|
103 |
+
margin-bottom: 0;
|
104 |
+
}
|
105 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li.red a {
|
106 |
+
border-right: none;
|
107 |
+
border-bottom: 4px solid #EA4335;
|
108 |
+
margin-bottom: 0;
|
109 |
+
}
|
110 |
+
/** General Control Style **/
|
111 |
+
.bgseo-keywords:last-of-type {
|
112 |
+
margin-bottom: 10px;
|
113 |
+
}
|
114 |
+
|
115 |
+
/** In metabox sidebar **/
|
116 |
+
#side-sortables .butterbean-manager-default .butterbean-control {
|
117 |
+
margin-bottom: 0;
|
118 |
+
}
|
119 |
+
#side-sortables .butterbean-manager-default #butterbean-control-bgseo_dash_html.butterbean-control {
|
120 |
+
margin-bottom: 20px;
|
121 |
+
}
|
122 |
+
#side-sortables .butterbean-manager-default #butterbean-control-bgseo_description.butterbean-control {
|
123 |
+
margin-bottom: 20px;
|
124 |
+
}
|
125 |
+
#side-sortables .butterbean-manager-default #butterbean-control-bgseo_canonical.butterbean-control {
|
126 |
+
margin-bottom: 20px;
|
127 |
+
}
|
128 |
+
#side-sortables .butterbean-manager-default #butterbean-control-bgseo_custom_keyword.butterbean-control {
|
129 |
+
margin-bottom: 20px;
|
130 |
+
}
|
131 |
+
#side-sortables .butterbean-manager-default #butterbean-control-bgseo_meta_analaysis.butterbean-control {
|
132 |
+
margin-bottom: 10px;
|
133 |
+
}
|
134 |
+
#side-sortables .bgseo-keywords {
|
135 |
+
margin-bottom: 10px;
|
136 |
+
}
|
137 |
+
|
138 |
+
/** Keywords Section Styles **/
|
139 |
+
.butterbean-manager-default #butterbean-control-bgseo_keywords_html.butterbean-control {
|
140 |
+
margin-bottom: 0;
|
141 |
+
}
|
142 |
+
.butterbean-manager-default #butterbean-control-bgseo_keyword_analaysis.butterbean-control {
|
143 |
+
margin-bottom: 11px;
|
144 |
+
}
|
145 |
+
|
146 |
+
#butterbean-ui-boldgrid_seo .butterbean-manager-default .butterbean-content {
|
147 |
+
width: calc( 100% - 181px );
|
148 |
+
}
|
149 |
+
@media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) {
|
150 |
+
#butterbean-ui-boldgrid_seo .butterbean-manager-default .butterbean-content {
|
151 |
+
width: calc( 100% - 48px );
|
152 |
+
}
|
153 |
+
}
|
assets/css/boldgrid-seo-admin.min.css
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
.bgseo-recommendations{display:flex;margin-left:-12px}.analysis-suggestion:before{display:inline-block;float:left;margin:3px 10px 3px -22px;width:13px;height:13px;content:"";vertical-align:middle;border-radius:100%}.analysis-suggestion{float:left;margin-left:32px}#side-sortables .bgseo-recommendations{display:flex;margin-left:-12px;margin-bottom:8px}#side-sortables .bgseo-recommendations:last-of-type{margin-bottom:0}.overview-status:before{display:inline-block;margin:3px 10px 3px 0;width:13px;height:13px;content:"";vertical-align:middle;border-radius:100%}.analysis-suggestion.red:before,.overview-status.red:before{background:#ea4335}.analysis-suggestion.green:before,.overview-status.green:before{background:#34a853}.analysis-suggestion.yellow:before,.overview-status.yellow:before{background:#fbbc05}#butterbean-control-bgseo_robots_follow .butterbean-radio-list li,#butterbean-control-bgseo_robots_index .butterbean-radio-list li{display:inline-block;padding-right:1em}.bgseo-tooltip{padding-left:.3em;font-size:18px;vertical-align:bottom;cursor:pointer}.butterbean-manager-default .butterbean-nav{background-color:#eee}.butterbean-nav li[aria-selected=false] a{background-color:#fafafa}.butterbean-manager-default .butterbean-nav li.green a{border-right:5px solid #34a853;border-bottom:none;margin-bottom:1px}.butterbean-manager-default .butterbean-nav li.yellow a{border-right:5px solid #fbbc05;border-bottom:none;margin-bottom:1px}.butterbean-manager-default .butterbean-nav li.red a{border-right:5px solid #ea4335;border-bottom:none;margin-bottom:1px}#side-sortables .butterbean-manager-default .butterbean-nav li.green a{border-right:none;border-bottom:4px solid #34a853;margin-bottom:0}#side-sortables .butterbean-manager-default .butterbean-nav li.yellow a{border-right:none;border-bottom:4px solid #fbbc05;margin-bottom:0}#side-sortables .butterbean-manager-default .butterbean-nav li.red a{border-right:none;border-bottom:4px solid #ea4335;margin-bottom:0}.bgseo-keywords:last-of-type{margin-bottom:10px}#side-sortables .butterbean-manager-default .butterbean-control{margin-bottom:0}#side-sortables .butterbean-manager-default #butterbean-control-bgseo_canonical.butterbean-control,#side-sortables .butterbean-manager-default #butterbean-control-bgseo_custom_keyword.butterbean-control,#side-sortables .butterbean-manager-default #butterbean-control-bgseo_dash_html.butterbean-control,#side-sortables .butterbean-manager-default #butterbean-control-bgseo_description.butterbean-control{margin-bottom:20px}#side-sortables .bgseo-keywords,#side-sortables .butterbean-manager-default #butterbean-control-bgseo_meta_analaysis.butterbean-control{margin-bottom:10px}.butterbean-manager-default #butterbean-control-bgseo_keywords_html.butterbean-control{margin-bottom:0}.butterbean-manager-default #butterbean-control-bgseo_keyword_analaysis.butterbean-control{margin-bottom:11px}#butterbean-ui-boldgrid_seo .butterbean-manager-default .butterbean-content{width:calc(100% - 181px)}@media (max-width:980px) and (min-width:851px),only screen and (max-width:782px){#butterbean-ui-boldgrid_seo .butterbean-manager-default .butterbean-content{width:calc(100% - 48px)}}
|
assets/js/bgseo.js
ADDED
@@ -0,0 +1,3106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Setup the BOLDGRID Object if it doesn't exist already.
|
2 |
+
var BOLDGRID = BOLDGRID || {};
|
3 |
+
// Create the BOLDGRID.SEO object.
|
4 |
+
BOLDGRID.SEO = {
|
5 |
+
// Add the analysis report to the BOLDGRID.SEO object.
|
6 |
+
report : {
|
7 |
+
bgseo_visibility : {},
|
8 |
+
bgseo_keywords : {},
|
9 |
+
bgseo_meta : {},
|
10 |
+
rawstatistics : {},
|
11 |
+
textstatistics : {},
|
12 |
+
},
|
13 |
+
};
|
14 |
+
|
15 |
+
( function ( $ ) {
|
16 |
+
|
17 |
+
'use strict';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Registers dashboard display as control.
|
21 |
+
*
|
22 |
+
* @since 1.4
|
23 |
+
*/
|
24 |
+
butterbean.views.register_control( 'dashboard', {
|
25 |
+
// Wrapper element for the control.
|
26 |
+
tagName : 'div',
|
27 |
+
|
28 |
+
// Custom attributes for the control wrapper.
|
29 |
+
attributes : function() {
|
30 |
+
return {
|
31 |
+
'id' : 'butterbean-control-' + this.model.get( 'name' ),
|
32 |
+
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
|
33 |
+
};
|
34 |
+
},
|
35 |
+
initialize : function() {
|
36 |
+
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
|
37 |
+
|
38 |
+
this.bgseo_template = wp.template( 'butterbean-control-dashboard' );
|
39 |
+
|
40 |
+
// Bind changes so that the view is re-rendered when the model changes.
|
41 |
+
_.bindAll( this, 'render' );
|
42 |
+
this.model.bind( 'change', this.render );
|
43 |
+
},
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Get the results report for a given section.
|
47 |
+
*
|
48 |
+
* @since 1.3.1
|
49 |
+
*
|
50 |
+
* @param {Object} section The section name to get report for.
|
51 |
+
*
|
52 |
+
* @returns {Object} report The report for the section to display.
|
53 |
+
*/
|
54 |
+
results : function( data ) {
|
55 |
+
var report = {};
|
56 |
+
_.each( data, function( key ) {
|
57 |
+
_.extend( report, key );
|
58 |
+
});
|
59 |
+
|
60 |
+
return report;
|
61 |
+
},
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Gets the analysis for the section from the reporter.
|
65 |
+
*
|
66 |
+
* This is bound to the bgseo-report event, and will process
|
67 |
+
* the report and add only the analysis for the current section displayed.
|
68 |
+
*
|
69 |
+
* @since 1.3.1
|
70 |
+
*
|
71 |
+
* @param {Object} report The full report as it's updated by reporter.
|
72 |
+
*/
|
73 |
+
setAnalysis: function( e, report ) {
|
74 |
+
var sectionScore,
|
75 |
+
section = this.model.get( 'section' ),
|
76 |
+
data = _.pick( report, section );
|
77 |
+
|
78 |
+
// Get each of the analysis results to pass for template rendering.
|
79 |
+
this.sectionReport = this.results( data );
|
80 |
+
|
81 |
+
// Set the section's report in the model's attributes.
|
82 |
+
this.model.set( 'analysis', this.sectionReport );
|
83 |
+
|
84 |
+
// Get score for each section, and set a status for sections.
|
85 |
+
_( report ).each( function( section ) {
|
86 |
+
// sectionScore should be set.
|
87 |
+
if ( ! _.isUndefined ( section.sectionScore ) ) {
|
88 |
+
sectionScore = BOLDGRID.SEO.Sections.score( section );
|
89 |
+
_( section ).extend( sectionScore );
|
90 |
+
}
|
91 |
+
});
|
92 |
+
|
93 |
+
// Add the overview score to report.
|
94 |
+
_( report.bgseo_keywords ).extend({
|
95 |
+
overview : {
|
96 |
+
score : BOLDGRID.SEO.Dashboard.overviewScore( report ),
|
97 |
+
},
|
98 |
+
});
|
99 |
+
|
100 |
+
// Get the status based on the overview score, and add to report.
|
101 |
+
_( report.bgseo_keywords.overview ).extend({
|
102 |
+
status : BOLDGRID.SEO.Dashboard.overviewStatus( report.bgseo_keywords.overview.score ),
|
103 |
+
});
|
104 |
+
|
105 |
+
// Set the nav highlight indicator for each section's tab.
|
106 |
+
BOLDGRID.SEO.Sections.navHighlight( report );
|
107 |
+
BOLDGRID.SEO.Sections.overviewStatus( report );
|
108 |
+
},
|
109 |
+
|
110 |
+
// Renders the control template.
|
111 |
+
render : function() {
|
112 |
+
// Only render template if model is active.
|
113 |
+
if ( this.model.get( 'active' ) )
|
114 |
+
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
|
115 |
+
|
116 |
+
return this;
|
117 |
+
},
|
118 |
+
});
|
119 |
+
|
120 |
+
})( jQuery );
|
121 |
+
|
122 |
+
( function ( $ ) {
|
123 |
+
|
124 |
+
'use strict';
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Registers the keywords display as a control.
|
128 |
+
*
|
129 |
+
* @since 1.4
|
130 |
+
*/
|
131 |
+
butterbean.views.register_control( 'keywords', {
|
132 |
+
// Wrapper element for the control.
|
133 |
+
tagName : 'div',
|
134 |
+
|
135 |
+
// Custom attributes for the control wrapper.
|
136 |
+
attributes : function() {
|
137 |
+
return {
|
138 |
+
'id' : 'butterbean-control-' + this.model.get( 'name' ),
|
139 |
+
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
|
140 |
+
};
|
141 |
+
},
|
142 |
+
initialize : function() {
|
143 |
+
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
|
144 |
+
|
145 |
+
this.bgseo_template = wp.template( 'butterbean-control-keywords' );
|
146 |
+
|
147 |
+
// Bind changes so that the view is re-rendered when the model changes.
|
148 |
+
_.bindAll( this, 'render' );
|
149 |
+
this.model.bind( 'change', this.render );
|
150 |
+
},
|
151 |
+
setAnalysis: function( e, report ) {
|
152 |
+
this.model.set( report );
|
153 |
+
},
|
154 |
+
|
155 |
+
// Renders the control template.
|
156 |
+
render : function() {
|
157 |
+
// Only render template if model is active.
|
158 |
+
if ( this.model.get( 'active' ) )
|
159 |
+
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
|
160 |
+
return this;
|
161 |
+
},
|
162 |
+
});
|
163 |
+
|
164 |
+
})( jQuery );
|
165 |
+
|
166 |
+
var BOLDGRID = BOLDGRID || {};
|
167 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
168 |
+
|
169 |
+
( function ( $ ) {
|
170 |
+
|
171 |
+
'use strict';
|
172 |
+
|
173 |
+
var self, report, api;
|
174 |
+
|
175 |
+
api = BOLDGRID.SEO;
|
176 |
+
report = api.report;
|
177 |
+
|
178 |
+
/**
|
179 |
+
* BoldGrid SEO Util.
|
180 |
+
*
|
181 |
+
* This will contain any utility functions needed across
|
182 |
+
* all classes.
|
183 |
+
*
|
184 |
+
* @since 1.3.1
|
185 |
+
*/
|
186 |
+
api.Util = {
|
187 |
+
|
188 |
+
/**
|
189 |
+
* Initialize Utilities.
|
190 |
+
*
|
191 |
+
* @since 1.3.1
|
192 |
+
*/
|
193 |
+
init : function () {
|
194 |
+
|
195 |
+
_.mixin({
|
196 |
+
/**
|
197 |
+
* Return a copy of the object only containing the whitelisted properties.
|
198 |
+
* Nested properties are concatenated with dots notation.
|
199 |
+
*
|
200 |
+
* Example:
|
201 |
+
* a = { min: 0.5, max : 2.5 };
|
202 |
+
* _.modifyObject( a, function( item ){ return item * item; });
|
203 |
+
*
|
204 |
+
* Returns:
|
205 |
+
* { min: 0.25, max : 6.25 };
|
206 |
+
*
|
207 |
+
* @since 1.3.1
|
208 |
+
*
|
209 |
+
* @param obj
|
210 |
+
*
|
211 |
+
* @returns {Object} Modified object.
|
212 |
+
*/
|
213 |
+
modifyObject : function( object, iteratee ) {
|
214 |
+
return _.object( _.map( object, function( value, key ) {
|
215 |
+
return [ key, iteratee( value ) ];
|
216 |
+
}));
|
217 |
+
},
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Return a copy of the object only containing the whitelisted properties.
|
221 |
+
* Nested properties are concatenated with dots notation.
|
222 |
+
*
|
223 |
+
* Example:
|
224 |
+
* a = {a:'a', b:{c:'c', d:'d', e:'e'}};
|
225 |
+
* _.pickDeep(a, 'b.c','b.d')
|
226 |
+
*
|
227 |
+
* Returns:
|
228 |
+
* {b:{c:'c',d:'d'}}
|
229 |
+
*
|
230 |
+
* @since 1.3.1
|
231 |
+
*
|
232 |
+
* @param obj
|
233 |
+
*
|
234 |
+
* @returns {Object} copy Object containing only properties requested.
|
235 |
+
*/
|
236 |
+
pickDeep : function( obj ) {
|
237 |
+
var copy = {},
|
238 |
+
keys = Array.prototype.concat.apply( Array.prototype, Array.prototype.slice.call( arguments, 1 ) );
|
239 |
+
|
240 |
+
this.each( keys, function( key ) {
|
241 |
+
var subKeys = key.split( '.' );
|
242 |
+
key = subKeys.shift();
|
243 |
+
|
244 |
+
if ( key in obj ) {
|
245 |
+
// pick nested properties
|
246 |
+
if( subKeys.length > 0 ) {
|
247 |
+
// extend property (if defined before)
|
248 |
+
if( copy[ key ] ) {
|
249 |
+
_.extend( copy[ key ], _.pickDeep( obj[ key ], subKeys.join( '.' ) ) );
|
250 |
+
}
|
251 |
+
else {
|
252 |
+
copy[ key ] = _.pickDeep( obj[ key ], subKeys.join( '.' ) );
|
253 |
+
}
|
254 |
+
}
|
255 |
+
else {
|
256 |
+
copy[ key ] = obj[ key ];
|
257 |
+
}
|
258 |
+
}
|
259 |
+
});
|
260 |
+
|
261 |
+
return copy;
|
262 |
+
},
|
263 |
+
});
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Usage: ( n ).isBetween( min, max )
|
267 |
+
*
|
268 |
+
* Gives you bool response if number is within the minimum
|
269 |
+
* and maximum numbers specified for the range.
|
270 |
+
*
|
271 |
+
* @since 1.3.1
|
272 |
+
*
|
273 |
+
* @param {Number} min Minimum number in range to check.
|
274 |
+
* @param {Number} max Maximum number in range to check.
|
275 |
+
*
|
276 |
+
* @returns {bool} Number is/isn't within range passed in params.
|
277 |
+
*/
|
278 |
+
if ( ! Number.prototype.isBetween ) {
|
279 |
+
Number.prototype.isBetween = function( min, max ) {
|
280 |
+
if ( _.isUndefined( min ) ) min = 0;
|
281 |
+
if ( _.isUndefined( max ) ) max = 0;
|
282 |
+
var newMax = Math.max( min, max );
|
283 |
+
var newMin = Math.min( min, max );
|
284 |
+
return this > newMin && this < newMax;
|
285 |
+
};
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Usage: ( n ).rounded( digits )
|
290 |
+
*
|
291 |
+
* Rounds a number to the closest decimal you specify.
|
292 |
+
*
|
293 |
+
* @since 1.3.1
|
294 |
+
*
|
295 |
+
* @param {Number} number Number to round.
|
296 |
+
* @param {Number} digits how many decimal places to round to.
|
297 |
+
*
|
298 |
+
* @returns {Number} rounded The number rounded to specified digits.
|
299 |
+
*/
|
300 |
+
if ( ! Number.prototype.rounded ) {
|
301 |
+
Number.prototype.rounded = function( digits ) {
|
302 |
+
|
303 |
+
if ( _.isUndefined( digits ) ) digits = 0;
|
304 |
+
|
305 |
+
var multiple = Math.pow( 10, digits );
|
306 |
+
var rounded = Math.round( this * multiple ) / multiple;
|
307 |
+
|
308 |
+
return rounded;
|
309 |
+
};
|
310 |
+
}
|
311 |
+
|
312 |
+
if ( ! String.prototype.printf ) {
|
313 |
+
String.prototype.printf = function() {
|
314 |
+
var newStr = this, i = 0;
|
315 |
+
while ( /%s/.test( newStr ) ){
|
316 |
+
newStr = newStr.replace( "%s", arguments[i++] );
|
317 |
+
}
|
318 |
+
|
319 |
+
return newStr;
|
320 |
+
};
|
321 |
+
}
|
322 |
+
|
323 |
+
/**
|
324 |
+
* Function that counts occurrences of a substring in a string;
|
325 |
+
*
|
326 |
+
* @param {String} string The string
|
327 |
+
* @param {String} subString The sub string to search for
|
328 |
+
* @param {Boolean} [allowOverlapping] Optional. (Default:false)
|
329 |
+
*
|
330 |
+
* @returns {Number} n The number of times a substring appears in a string.
|
331 |
+
*/
|
332 |
+
if ( ! String.prototype.occurences ) {
|
333 |
+
String.prototype.occurences = function( needle, allowOverlapping ) {
|
334 |
+
|
335 |
+
needle += "";
|
336 |
+
if ( needle.length <= 0 ) return ( this.length + 1 );
|
337 |
+
|
338 |
+
var n = 0,
|
339 |
+
pos = 0,
|
340 |
+
step = allowOverlapping ? 1 : needle.length;
|
341 |
+
|
342 |
+
while ( true ) {
|
343 |
+
pos = this.indexOf( needle, pos );
|
344 |
+
if ( pos >= 0 ) {
|
345 |
+
++n;
|
346 |
+
pos += step;
|
347 |
+
} else break;
|
348 |
+
}
|
349 |
+
|
350 |
+
return n;
|
351 |
+
};
|
352 |
+
}
|
353 |
+
},
|
354 |
+
};
|
355 |
+
|
356 |
+
self = api.Util;
|
357 |
+
|
358 |
+
})( jQuery );
|
359 |
+
|
360 |
+
( function() {
|
361 |
+
|
362 |
+
'use strict';
|
363 |
+
|
364 |
+
var self, report, api;
|
365 |
+
|
366 |
+
api = BOLDGRID.SEO;
|
367 |
+
|
368 |
+
api.Words = {
|
369 |
+
|
370 |
+
init : function( settings ) {
|
371 |
+
var key,
|
372 |
+
shortcodes;
|
373 |
+
|
374 |
+
if ( settings ) {
|
375 |
+
for ( key in settings ) {
|
376 |
+
if ( settings.hasOwnProperty( key ) ) {
|
377 |
+
self.settings[ key ] = settings[ key ];
|
378 |
+
}
|
379 |
+
}
|
380 |
+
}
|
381 |
+
|
382 |
+
shortcodes = self.settings.l10n.shortcodes;
|
383 |
+
|
384 |
+
if ( shortcodes && shortcodes.length ) {
|
385 |
+
self.settings.shortcodesRegExp = new RegExp( '\\[\\/?(?:' + shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
|
386 |
+
}
|
387 |
+
},
|
388 |
+
|
389 |
+
settings : {
|
390 |
+
HTMLRegExp: /<\/?[a-z][^>]*?>/gi,
|
391 |
+
HTMLcommentRegExp: /<!--[\s\S]*?-->/g,
|
392 |
+
spaceRegExp: / | /gi,
|
393 |
+
HTMLEntityRegExp: /&\S+?;/g,
|
394 |
+
connectorRegExp: /--|\u2014/g,
|
395 |
+
removeRegExp: new RegExp( [
|
396 |
+
'[',
|
397 |
+
// Basic Latin (extract)
|
398 |
+
'\u0021-\u0040\u005B-\u0060\u007B-\u007E',
|
399 |
+
// Latin-1 Supplement (extract)
|
400 |
+
'\u0080-\u00BF\u00D7\u00F7',
|
401 |
+
// General Punctuation
|
402 |
+
// Superscripts and Subscripts
|
403 |
+
// Currency Symbols
|
404 |
+
// Combining Diacritical Marks for Symbols
|
405 |
+
// Letterlike Symbols
|
406 |
+
// Number Forms
|
407 |
+
// Arrows
|
408 |
+
// Mathematical Operators
|
409 |
+
// Miscellaneous Technical
|
410 |
+
// Control Pictures
|
411 |
+
// Optical Character Recognition
|
412 |
+
// Enclosed Alphanumerics
|
413 |
+
// Box Drawing
|
414 |
+
// Block Elements
|
415 |
+
// Geometric Shapes
|
416 |
+
// Miscellaneous Symbols
|
417 |
+
// Dingbats
|
418 |
+
// Miscellaneous Mathematical Symbols-A
|
419 |
+
// Supplemental Arrows-A
|
420 |
+
// Braille Patterns
|
421 |
+
// Supplemental Arrows-B
|
422 |
+
// Miscellaneous Mathematical Symbols-B
|
423 |
+
// Supplemental Mathematical Operators
|
424 |
+
// Miscellaneous Symbols and Arrows
|
425 |
+
'\u2000-\u2BFF',
|
426 |
+
// Supplemental Punctuation
|
427 |
+
'\u2E00-\u2E7F',
|
428 |
+
']'
|
429 |
+
].join( '' ), 'g' ),
|
430 |
+
astralRegExp: /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
431 |
+
// regex tested : https://regex101.com/r/vHAwas/2
|
432 |
+
wordsRegExp: /.+?\s+/g,
|
433 |
+
characters_excluding_spacesRegExp: /\S/g,
|
434 |
+
characters_including_spacesRegExp: /[^\f\n\r\t\v\u00AD\u2028\u2029]/g,
|
435 |
+
l10n: window.wordCountL10n || {}
|
436 |
+
},
|
437 |
+
|
438 |
+
words : function( text, type ) {
|
439 |
+
var count = 0;
|
440 |
+
|
441 |
+
type = type || self.settings.l10n.type;
|
442 |
+
|
443 |
+
if ( type !== 'characters_excluding_spaces' && type !== 'characters_including_spaces' ) {
|
444 |
+
type = 'words';
|
445 |
+
}
|
446 |
+
|
447 |
+
if ( text ) {
|
448 |
+
text = text + '\n';
|
449 |
+
|
450 |
+
text = text.replace( self.settings.HTMLRegExp, '\n' );
|
451 |
+
text = text.replace( self.settings.HTMLcommentRegExp, '' );
|
452 |
+
|
453 |
+
if ( self.settings.shortcodesRegExp ) {
|
454 |
+
text = text.replace( self.settings.shortcodesRegExp, '\n' );
|
455 |
+
}
|
456 |
+
|
457 |
+
text = text.replace( self.settings.spaceRegExp, ' ' );
|
458 |
+
|
459 |
+
if ( type === 'words' ) {
|
460 |
+
text = text.replace( self.settings.HTMLEntityRegExp, '' );
|
461 |
+
text = text.replace( self.settings.connectorRegExp, ' ' );
|
462 |
+
text = text.replace( self.settings.removeRegExp, '' );
|
463 |
+
} else {
|
464 |
+
text = text.replace( self.settings.HTMLEntityRegExp, 'a' );
|
465 |
+
text = text.replace( self.settings.astralRegExp, 'a' );
|
466 |
+
}
|
467 |
+
text = text.match( self.settings[ type + 'RegExp' ] );
|
468 |
+
|
469 |
+
if ( text ) {
|
470 |
+
count = text;
|
471 |
+
}
|
472 |
+
}
|
473 |
+
|
474 |
+
return count;
|
475 |
+
},
|
476 |
+
};
|
477 |
+
|
478 |
+
self = api.Words;
|
479 |
+
|
480 |
+
} )();
|
481 |
+
|
482 |
+
( function( $, counter ) {
|
483 |
+
|
484 |
+
$( function() {
|
485 |
+
|
486 |
+
var $content = $( '#content' ),
|
487 |
+
$count = $( '#wp-word-count' ).find( '.word-count' ),
|
488 |
+
prevCount = 0,
|
489 |
+
contentEditor,
|
490 |
+
words;
|
491 |
+
|
492 |
+
function update() {
|
493 |
+
var text, count;
|
494 |
+
|
495 |
+
if ( ! contentEditor || contentEditor.isHidden() ) {
|
496 |
+
text = $content.val();
|
497 |
+
} else {
|
498 |
+
text = contentEditor.getContent( { format: 'raw' } );
|
499 |
+
}
|
500 |
+
|
501 |
+
count = counter.count( text );
|
502 |
+
words = BOLDGRID.SEO.Words.words( text );
|
503 |
+
|
504 |
+
if ( count !== prevCount ) {
|
505 |
+
$content.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
|
506 |
+
}
|
507 |
+
|
508 |
+
prevCount = count;
|
509 |
+
}
|
510 |
+
|
511 |
+
$( document ).on( 'tinymce-editor-init', function( event, editor ) {
|
512 |
+
if ( editor.id !== 'content' ) {
|
513 |
+
return;
|
514 |
+
}
|
515 |
+
|
516 |
+
contentEditor = editor;
|
517 |
+
|
518 |
+
editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
|
519 |
+
} );
|
520 |
+
|
521 |
+
$content.on( 'input keyup', _.debounce( update, 1000 ) );
|
522 |
+
|
523 |
+
update();
|
524 |
+
|
525 |
+
} );
|
526 |
+
|
527 |
+
} )( jQuery, new wp.utils.WordCounter() );
|
528 |
+
|
529 |
+
( function ( $ ) {
|
530 |
+
|
531 |
+
'use strict';
|
532 |
+
|
533 |
+
var self;
|
534 |
+
|
535 |
+
/**
|
536 |
+
* BoldGrid SEO Admin.
|
537 |
+
*
|
538 |
+
* This is responsible for setting the counters for the SEO Title &
|
539 |
+
* Description tab.
|
540 |
+
*
|
541 |
+
* @since 1.2.1
|
542 |
+
*/
|
543 |
+
BOLDGRID.SEO.Admin = {
|
544 |
+
|
545 |
+
/**
|
546 |
+
* Initialize Word Count.
|
547 |
+
*
|
548 |
+
* @since 1.2.1
|
549 |
+
*/
|
550 |
+
init : function () {
|
551 |
+
$( document ).ready( function() {
|
552 |
+
self._setWordCounts();
|
553 |
+
});
|
554 |
+
},
|
555 |
+
|
556 |
+
/**
|
557 |
+
* Get the word count of a metabox field.
|
558 |
+
*
|
559 |
+
* @since 1.2.1
|
560 |
+
*
|
561 |
+
* @param {Object} $element The element to apply the word counter to.
|
562 |
+
*/
|
563 |
+
wordCount : function( $element ) {
|
564 |
+
var limit = $element.attr( 'maxlength' ),
|
565 |
+
$counter = $( '<span />', {
|
566 |
+
'class' : 'boldgrid-seo-meta-counter',
|
567 |
+
'style' : 'font-weight: bold'
|
568 |
+
}),
|
569 |
+
$container = $( '<div />', {
|
570 |
+
'class' : 'boldgrid-seo-meta-countdown boldgrid-seo-meta-extra',
|
571 |
+
'html' : ' characters left'
|
572 |
+
});
|
573 |
+
|
574 |
+
if ( limit ) {
|
575 |
+
$element
|
576 |
+
.removeAttr( 'maxlength' )
|
577 |
+
.after( $container.prepend( $counter ) )
|
578 |
+
.on( 'keyup focus' , function() {
|
579 |
+
self.setCounter( $counter, $element, limit );
|
580 |
+
});
|
581 |
+
}
|
582 |
+
|
583 |
+
self.setCounter( $counter, $element, limit );
|
584 |
+
},
|
585 |
+
|
586 |
+
/**
|
587 |
+
* Set the colors of the count to reflect ideal lengths.
|
588 |
+
*
|
589 |
+
* @since 1.2.1
|
590 |
+
*
|
591 |
+
* @param {Object} $counter New element to create for counter.
|
592 |
+
* @param {Object} $target Element to check the input value of.
|
593 |
+
* @param {Number} limit The maxlength of the input to calculate on.
|
594 |
+
*/
|
595 |
+
setCounter : function( $counter, $target, limit ) {
|
596 |
+
var text = $target.val(),
|
597 |
+
chars = text.length;
|
598 |
+
|
599 |
+
$counter.html( limit - chars );
|
600 |
+
|
601 |
+
if ( $target.context.id === 'boldgrid-seo-field-meta_description' ) {
|
602 |
+
if ( chars > limit ) {
|
603 |
+
$counter.css( { 'color' : '#EA4335' } );
|
604 |
+
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoDescription.length.okScore ) ) {
|
605 |
+
$counter.css( { 'color' : '#FBBC05' } );
|
606 |
+
} else if ( chars.isBetween( _bgseoContentAnalysis.seoDescription.length.okScore -1, _bgseoContentAnalysis.seoDescription.length.goodScore + 1 ) ) {
|
607 |
+
$counter.css( { 'color' : '#34A853' } );
|
608 |
+
} else {
|
609 |
+
$counter.css( { 'color' : 'black' } );
|
610 |
+
}
|
611 |
+
} else {
|
612 |
+
if ( chars > limit ) {
|
613 |
+
$counter.css( { 'color' : '#EA4335' } );
|
614 |
+
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoTitle.length.okScore ) ) {
|
615 |
+
$counter.css( { 'color' : '#FBBC05' } );
|
616 |
+
} else if ( chars > _bgseoContentAnalysis.seoTitle.length.okScore - 1 ) {
|
617 |
+
$counter.css( { 'color' : '#34A853' } );
|
618 |
+
} else {
|
619 |
+
$counter.css( { 'color' : 'black' } );
|
620 |
+
}
|
621 |
+
}
|
622 |
+
},
|
623 |
+
|
624 |
+
/**
|
625 |
+
* Set the word counts for each field in the SEO Title & Description Tab.
|
626 |
+
*
|
627 |
+
* @since 1.2.1
|
628 |
+
*/
|
629 |
+
_setWordCounts : function() {
|
630 |
+
// Apply our wordcount counter to the meta title and meta description textarea fields.
|
631 |
+
$( '#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description' )
|
632 |
+
.each( function() {
|
633 |
+
self.wordCount( $( this ) );
|
634 |
+
});
|
635 |
+
},
|
636 |
+
};
|
637 |
+
|
638 |
+
self = BOLDGRID.SEO.Admin;
|
639 |
+
|
640 |
+
})( jQuery );
|
641 |
+
|
642 |
+
( function ( $ ) {
|
643 |
+
|
644 |
+
'use strict';
|
645 |
+
|
646 |
+
var self, report, api;
|
647 |
+
|
648 |
+
api = BOLDGRID.SEO;
|
649 |
+
report = api.report;
|
650 |
+
|
651 |
+
/**
|
652 |
+
* BoldGrid TinyMCE Analysis.
|
653 |
+
*
|
654 |
+
* This is responsible for generating the actual reports
|
655 |
+
* displayed within the BoldGrid SEO Dashboard when the user
|
656 |
+
* is on a page or a post.
|
657 |
+
*
|
658 |
+
* @since 1.3.1
|
659 |
+
*/
|
660 |
+
api.TinyMCE = {
|
661 |
+
|
662 |
+
/**
|
663 |
+
* Initialize TinyMCE Content.
|
664 |
+
*
|
665 |
+
* @since 1.3.1
|
666 |
+
*/
|
667 |
+
init : function () {
|
668 |
+
self.onloadContent();
|
669 |
+
$( document ).ready( function() {
|
670 |
+
self.editorChange();
|
671 |
+
});
|
672 |
+
},
|
673 |
+
|
674 |
+
/**
|
675 |
+
* Runs actions on window load to prepare for analysis.
|
676 |
+
*
|
677 |
+
* This method gets the current editor in use by the user on the
|
678 |
+
* initial page load ( text editor or visual editor ), and also
|
679 |
+
* is responsible for creating the iframe preview of the page/post
|
680 |
+
* so we can get the raw html in use by the template/theme the user
|
681 |
+
* has activated.
|
682 |
+
*
|
683 |
+
* @since 1.3.1
|
684 |
+
*/
|
685 |
+
onloadContent: function() {
|
686 |
+
var text,
|
687 |
+
editor = $( '#content.wp-editor-area[aria-hidden=false]' );
|
688 |
+
|
689 |
+
$( window ).on( 'load bgseo-media-inserted', function() {
|
690 |
+
var content = self.getContent();
|
691 |
+
|
692 |
+
// Get rendered page content from frontend site.
|
693 |
+
self.getRenderedContent();
|
694 |
+
|
695 |
+
// Trigger the content analysis for the tinyMCE content.
|
696 |
+
_.defer( function() {
|
697 |
+
$( '#content' ).trigger( 'bgseo-analysis', [content] );
|
698 |
+
});
|
699 |
+
});
|
700 |
+
},
|
701 |
+
|
702 |
+
/**
|
703 |
+
* Gets the content from TinyMCE or the text editor for analysis.
|
704 |
+
*
|
705 |
+
* @since 1.3.1
|
706 |
+
*
|
707 |
+
* @returns {Object} content Contains content in raw and text formats.
|
708 |
+
*/
|
709 |
+
getContent : function() {
|
710 |
+
var content;
|
711 |
+
// Get the content of the visual editor or text editor that's present.
|
712 |
+
if ( tinymce.ActiveEditor ) {
|
713 |
+
content = tinyMCE.get( wpActiveEditor ).getContent();
|
714 |
+
} else {
|
715 |
+
content = $( '#content' ).val();
|
716 |
+
// Remove newlines and carriage returns.
|
717 |
+
content = content.replace( /\r?\n|\r/g, '' );
|
718 |
+
}
|
719 |
+
|
720 |
+
var rawContent = $.parseHTML( content );
|
721 |
+
|
722 |
+
// Stores raw and stripped down versions of the content for analysis.
|
723 |
+
content = {
|
724 |
+
'raw': rawContent,
|
725 |
+
'text': self.stripper( content.toLowerCase() ),
|
726 |
+
};
|
727 |
+
|
728 |
+
return content;
|
729 |
+
},
|
730 |
+
|
731 |
+
/**
|
732 |
+
* Only ajax for preview if permalink is available. This only
|
733 |
+
* impacts "New" page and posts. To counter
|
734 |
+
* this we will disable the checks made until the content has had
|
735 |
+
* a chance to be updated. We will store the found headings minus
|
736 |
+
* the initial found headings in the content, so we know what the
|
737 |
+
* template has in use on the actual rendered page.
|
738 |
+
*
|
739 |
+
* @since 1.3.1
|
740 |
+
*
|
741 |
+
* @returns null No return.
|
742 |
+
*/
|
743 |
+
getRenderedContent : function() {
|
744 |
+
var renderedContent, preview;
|
745 |
+
|
746 |
+
// Get the preview url from WordPress.
|
747 |
+
preview = $( '#preview-action > .preview.button' ).attr( 'href' );
|
748 |
+
|
749 |
+
if ( $( '#sample-permalink' ).length ) {
|
750 |
+
// Only run this once after the initial iframe has loaded to get current template stats.
|
751 |
+
$.get( preview, function( renderedTemplate ) {
|
752 |
+
var headings, h1, h2, $rendered;
|
753 |
+
|
754 |
+
// The rendered page content.
|
755 |
+
$rendered = $( renderedTemplate );
|
756 |
+
|
757 |
+
// H1's that appear in rendered content.
|
758 |
+
h1 = $rendered.find( 'h1' );
|
759 |
+
// HS's that appear in rendered content.
|
760 |
+
h2 = $rendered.find( 'h2' );
|
761 |
+
|
762 |
+
// The rendered content stats.
|
763 |
+
renderedContent = {
|
764 |
+
h1Count : h1.length - report.rawstatistics.h1Count,
|
765 |
+
h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
|
766 |
+
return ! _.findWhere( report.rawstatistics.h1text, obj );
|
767 |
+
}),
|
768 |
+
h2Count : h2.length - report.rawstatistics.h2Count,
|
769 |
+
h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
|
770 |
+
return ! _.findWhere( report.rawstatistics.h2text, obj );
|
771 |
+
}),
|
772 |
+
};
|
773 |
+
|
774 |
+
// Add the rendered stats to our report for use later.
|
775 |
+
_.extend( report, { rendered : renderedContent } );
|
776 |
+
|
777 |
+
// Trigger the SEO report to rebuild in the template after initial stats are created.
|
778 |
+
$( '#content' ).trigger( 'bgseo-analysis', [ self.getContent() ] );
|
779 |
+
|
780 |
+
}, 'html' );
|
781 |
+
}
|
782 |
+
},
|
783 |
+
/**
|
784 |
+
* Listens for changes made in the text editor mode.
|
785 |
+
*
|
786 |
+
* @since 1.3.1
|
787 |
+
*
|
788 |
+
* @returns {string} text The new content to perform analysis on.
|
789 |
+
*/
|
790 |
+
editorChange: function() {
|
791 |
+
var text, targetId;
|
792 |
+
$( '#content.wp-editor-area' ).on( 'input propertychange paste nodechange', function() {
|
793 |
+
targetId = $( this ).attr( 'id' );
|
794 |
+
text = self.wpContent( targetId );
|
795 |
+
});
|
796 |
+
|
797 |
+
return text;
|
798 |
+
},
|
799 |
+
|
800 |
+
/**
|
801 |
+
* This gets the content from the TinyMCE Visual editor.
|
802 |
+
*
|
803 |
+
* @since 1.3.1
|
804 |
+
*
|
805 |
+
* @returns {string} text
|
806 |
+
*/
|
807 |
+
tmceChange: function( e ) {
|
808 |
+
var text, targetId;
|
809 |
+
|
810 |
+
targetId = e.target.id;
|
811 |
+
text = self.wpContent( targetId );
|
812 |
+
|
813 |
+
return text;
|
814 |
+
},
|
815 |
+
|
816 |
+
/**
|
817 |
+
* Checks which editor is the active editor.
|
818 |
+
*
|
819 |
+
* After checking the editor, it will obtain the content and trigger
|
820 |
+
* the report generation with the new user input.
|
821 |
+
*
|
822 |
+
* @since 1.3.1
|
823 |
+
*/
|
824 |
+
wpContent : function( targetId ) {
|
825 |
+
var text = {};
|
826 |
+
|
827 |
+
switch ( targetId ) {
|
828 |
+
// Grab text from TinyMCE Editor.
|
829 |
+
case 'tinymce' :
|
830 |
+
// Only do this if page/post editor has TinyMCE as active editor.
|
831 |
+
if ( tinymce.activeEditor )
|
832 |
+
// Define text as the content of the current TinyMCE instance.
|
833 |
+
text = tinyMCE.get( wpActiveEditor ).getContent();
|
834 |
+
break;
|
835 |
+
case 'content' :
|
836 |
+
text = $( '#content' ).val();
|
837 |
+
text = text.replace( /\r?\n|\r/g, '' );
|
838 |
+
break;
|
839 |
+
}
|
840 |
+
|
841 |
+
// Convert raw text to DOM nodes.
|
842 |
+
var rawText = $.parseHTML( text );
|
843 |
+
|
844 |
+
text = {
|
845 |
+
'raw': rawText,
|
846 |
+
'text': self.stripper( text.toLowerCase() ),
|
847 |
+
};
|
848 |
+
|
849 |
+
// Trigger the text analysis for report.
|
850 |
+
$( '#content' ).trigger( 'bgseo-analysis', [text] );
|
851 |
+
},
|
852 |
+
|
853 |
+
/**
|
854 |
+
* Strips out unwanted html.
|
855 |
+
*
|
856 |
+
* This is helpful in removing the remaining traces of HTML
|
857 |
+
* that is sometimes leftover to form our clean text output and
|
858 |
+
* run our text analysis on.
|
859 |
+
*
|
860 |
+
* @since 1.3.1
|
861 |
+
*
|
862 |
+
* @returns {string} The content with any remaining html removed.
|
863 |
+
*/
|
864 |
+
stripper: function( html ) {
|
865 |
+
var tmp;
|
866 |
+
|
867 |
+
tmp = document.implementation.createHTMLDocument( 'New' ).body;
|
868 |
+
tmp.innerHTML = html;
|
869 |
+
|
870 |
+
return tmp.textContent || tmp.innerText || " ";
|
871 |
+
},
|
872 |
+
};
|
873 |
+
|
874 |
+
self = api.TinyMCE;
|
875 |
+
|
876 |
+
})( jQuery );
|
877 |
+
|
878 |
+
( function ( $ ) {
|
879 |
+
|
880 |
+
'use strict';
|
881 |
+
|
882 |
+
var self, report, api;
|
883 |
+
|
884 |
+
api = BOLDGRID.SEO;
|
885 |
+
report = api.report;
|
886 |
+
|
887 |
+
/**
|
888 |
+
* BoldGrid SEO Content Analysis.
|
889 |
+
*
|
890 |
+
* This is responsible for general analysis of the user's content.
|
891 |
+
*
|
892 |
+
* @since 1.3.1
|
893 |
+
*/
|
894 |
+
api.ContentAnalysis = {
|
895 |
+
|
896 |
+
/**
|
897 |
+
* Content Length Score.
|
898 |
+
*
|
899 |
+
* This is responsible for the user's content length scoring. The content
|
900 |
+
* length for this method is based on the word count, and not character
|
901 |
+
* counts.
|
902 |
+
*
|
903 |
+
* @since 1.3.1
|
904 |
+
*
|
905 |
+
* @param {Number} contentLength The length of the content to provide score on.
|
906 |
+
*
|
907 |
+
* @returns {Object} msg Contains the status indicator color and message.
|
908 |
+
*/
|
909 |
+
seoContentLengthScore: function( contentLength ) {
|
910 |
+
var content, displayed, msg = {};
|
911 |
+
|
912 |
+
// Cast to int to avoid errors in scoring.
|
913 |
+
contentLength = Number( contentLength );
|
914 |
+
|
915 |
+
// Content var.
|
916 |
+
content = _bgseoContentAnalysis.content.length;
|
917 |
+
|
918 |
+
// Displayed Message.
|
919 |
+
displayed = content.contentLength.printf( contentLength ) + ' ';
|
920 |
+
|
921 |
+
if ( contentLength === 0 ) {
|
922 |
+
msg = {
|
923 |
+
status: 'red',
|
924 |
+
msg: content.badEmpty,
|
925 |
+
};
|
926 |
+
}
|
927 |
+
|
928 |
+
if ( contentLength.isBetween( 0, content.badShortScore ) ) {
|
929 |
+
msg = {
|
930 |
+
status: 'red',
|
931 |
+
msg: displayed + content.badShort,
|
932 |
+
};
|
933 |
+
}
|
934 |
+
|
935 |
+
if ( contentLength.isBetween( content.badShortScore -1, content.okScore ) ) {
|
936 |
+
msg = {
|
937 |
+
status: 'yellow',
|
938 |
+
msg: displayed + content.ok,
|
939 |
+
};
|
940 |
+
}
|
941 |
+
|
942 |
+
if ( contentLength > content.okScore -1 ) {
|
943 |
+
msg = {
|
944 |
+
status: 'green',
|
945 |
+
msg: displayed + content.good,
|
946 |
+
};
|
947 |
+
}
|
948 |
+
|
949 |
+
return msg;
|
950 |
+
},
|
951 |
+
|
952 |
+
/**
|
953 |
+
* Checks if user has any images in their content.
|
954 |
+
*
|
955 |
+
* This provides a status and message if the user has included an
|
956 |
+
* image in their content for their page/post running analysis.
|
957 |
+
*
|
958 |
+
* @since 1.3.1
|
959 |
+
*
|
960 |
+
* @param {Number} imageLength Count of images found within content.
|
961 |
+
*
|
962 |
+
* @returns {Object} msg Contains the status indicator color and message.
|
963 |
+
*/
|
964 |
+
seoImageLengthScore: function( imageLength ) {
|
965 |
+
var msg = {
|
966 |
+
status: 'green',
|
967 |
+
msg: _bgseoContentAnalysis.image.length.good,
|
968 |
+
};
|
969 |
+
if ( ! imageLength ) {
|
970 |
+
msg = {
|
971 |
+
status: 'red',
|
972 |
+
msg: _bgseoContentAnalysis.image.length.bad,
|
973 |
+
};
|
974 |
+
}
|
975 |
+
|
976 |
+
return msg;
|
977 |
+
},
|
978 |
+
|
979 |
+
/**
|
980 |
+
* Get count of keywords used in content.
|
981 |
+
*
|
982 |
+
* This checks the content for occurences of the keyword used throughout.
|
983 |
+
*
|
984 |
+
* @since 1.3.1
|
985 |
+
*
|
986 |
+
* @param {string} content The content to search for the keyword in.
|
987 |
+
*
|
988 |
+
* @returns {Number} Count of times keyword appears in content.
|
989 |
+
*/
|
990 |
+
keywords : function( content ) {
|
991 |
+
var keyword = api.Keywords.getKeyword();
|
992 |
+
return content.occurences( keyword );
|
993 |
+
},
|
994 |
+
};
|
995 |
+
|
996 |
+
self = api.ContentAnalysis;
|
997 |
+
|
998 |
+
})( jQuery );
|
999 |
+
|
1000 |
+
( function ( $ ) {
|
1001 |
+
|
1002 |
+
'use strict';
|
1003 |
+
|
1004 |
+
var self, report, api;
|
1005 |
+
|
1006 |
+
api = BOLDGRID.SEO;
|
1007 |
+
report = api.report;
|
1008 |
+
|
1009 |
+
/**
|
1010 |
+
* BoldGrid SEO Dashboard.
|
1011 |
+
*
|
1012 |
+
* This is responsible for any Dashboard section specific functionality.
|
1013 |
+
*
|
1014 |
+
* @since 1.3.1
|
1015 |
+
*/
|
1016 |
+
api.Dashboard = {
|
1017 |
+
|
1018 |
+
/**
|
1019 |
+
* This gets the overview score.
|
1020 |
+
*
|
1021 |
+
* Number is a percentage.
|
1022 |
+
*
|
1023 |
+
* @since 1.3.1
|
1024 |
+
*
|
1025 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
1026 |
+
*
|
1027 |
+
* @returns {Number} The rounded percentage value for overall score.
|
1028 |
+
*/
|
1029 |
+
overviewScore : function( report ) {
|
1030 |
+
var max,
|
1031 |
+
total = self.totalScore( report ),
|
1032 |
+
sections = _.size( butterbean.models.sections );
|
1033 |
+
|
1034 |
+
max = sections * 2;
|
1035 |
+
|
1036 |
+
return ( total / max * 100 ).rounded( 2 );
|
1037 |
+
},
|
1038 |
+
|
1039 |
+
/**
|
1040 |
+
* This gets the overview status.
|
1041 |
+
*
|
1042 |
+
* @since 1.3.1
|
1043 |
+
*
|
1044 |
+
* @param {Number} score The BoldGrid SEO overview status score.
|
1045 |
+
*
|
1046 |
+
* @returns {string} The status indicator color of the overall scoring.
|
1047 |
+
*/
|
1048 |
+
overviewStatus : function( score ) {
|
1049 |
+
var status;
|
1050 |
+
|
1051 |
+
// Default overview status.
|
1052 |
+
status = 'green';
|
1053 |
+
|
1054 |
+
// If status is below 40%.
|
1055 |
+
if ( score < 40 ) {
|
1056 |
+
status = 'red';
|
1057 |
+
}
|
1058 |
+
|
1059 |
+
// Status is 40% - 75%.
|
1060 |
+
if ( score.isBetween( 39, 76 ) ) {
|
1061 |
+
status = 'yellow';
|
1062 |
+
}
|
1063 |
+
|
1064 |
+
return status;
|
1065 |
+
},
|
1066 |
+
|
1067 |
+
/**
|
1068 |
+
* Get the combined statuses for each section in BoldGrid SEO metabox.
|
1069 |
+
*
|
1070 |
+
* @since 1.3.1
|
1071 |
+
*
|
1072 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
1073 |
+
*
|
1074 |
+
* @returns {Object} status The combined statuses for all sections.
|
1075 |
+
*/
|
1076 |
+
getStatuses : function( report ) {
|
1077 |
+
var status = {};
|
1078 |
+
|
1079 |
+
_.each( butterbean.models.sections, function( section ) {
|
1080 |
+
var score, name = section.get( 'name' );
|
1081 |
+
score = report[name].sectionStatus;
|
1082 |
+
status[name] = score;
|
1083 |
+
_( status[name] ).extend( score );
|
1084 |
+
});
|
1085 |
+
|
1086 |
+
return status;
|
1087 |
+
},
|
1088 |
+
|
1089 |
+
/**
|
1090 |
+
* Assigns numbers to represent the statuses.
|
1091 |
+
*
|
1092 |
+
* @since 1.3.1
|
1093 |
+
*
|
1094 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
1095 |
+
*
|
1096 |
+
* @returns {Object} score The numerical values based on status rank.
|
1097 |
+
*/
|
1098 |
+
assignNumbers : function( report ) {
|
1099 |
+
var score, statuses;
|
1100 |
+
|
1101 |
+
statuses = self.getStatuses( report );
|
1102 |
+
|
1103 |
+
// Map strings into score values.
|
1104 |
+
score = _.mapObject( statuses, function( status ) {
|
1105 |
+
var score;
|
1106 |
+
|
1107 |
+
if ( status === 'red' ) score = 0;
|
1108 |
+
if ( status === 'yellow' ) score = 1;
|
1109 |
+
if ( status === 'green' ) score = 2;
|
1110 |
+
|
1111 |
+
return score;
|
1112 |
+
});
|
1113 |
+
|
1114 |
+
return score;
|
1115 |
+
},
|
1116 |
+
|
1117 |
+
/**
|
1118 |
+
* Combines all the status scores into a final sum.
|
1119 |
+
*
|
1120 |
+
* @since 1.3.1
|
1121 |
+
*
|
1122 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
1123 |
+
*
|
1124 |
+
* @returns {Object} total The total overall numerical value for statuses.
|
1125 |
+
*/
|
1126 |
+
totalScore : function( report ) {
|
1127 |
+
var total, statuses = self.assignNumbers( report );
|
1128 |
+
|
1129 |
+
total = _( statuses ).reduce( function( initial, number ) {
|
1130 |
+
return initial + number;
|
1131 |
+
}, 0 );
|
1132 |
+
|
1133 |
+
return total;
|
1134 |
+
}
|
1135 |
+
};
|
1136 |
+
|
1137 |
+
self = api.Dashboard;
|
1138 |
+
|
1139 |
+
})( jQuery );
|
1140 |
+
|
1141 |
+
( function ( $ ) {
|
1142 |
+
|
1143 |
+
'use strict';
|
1144 |
+
|
1145 |
+
var self, report, api;
|
1146 |
+
|
1147 |
+
api = BOLDGRID.SEO;
|
1148 |
+
report = api.report;
|
1149 |
+
|
1150 |
+
/**
|
1151 |
+
* BoldGrid SEO Description.
|
1152 |
+
*
|
1153 |
+
* This is responsible for the SEO Description Grading.
|
1154 |
+
*
|
1155 |
+
* @since 1.3.1
|
1156 |
+
*/
|
1157 |
+
api.Description = {
|
1158 |
+
|
1159 |
+
/**
|
1160 |
+
* Initialize SEO Description Analysis.
|
1161 |
+
*
|
1162 |
+
* @since 1.3.1
|
1163 |
+
*/
|
1164 |
+
init : function () {
|
1165 |
+
$( document ).ready( self.onReady );
|
1166 |
+
},
|
1167 |
+
|
1168 |
+
/**
|
1169 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
1170 |
+
*
|
1171 |
+
* @since 1.3.1
|
1172 |
+
*/
|
1173 |
+
onReady : function() {
|
1174 |
+
self.getSettings();
|
1175 |
+
self._description();
|
1176 |
+
},
|
1177 |
+
|
1178 |
+
/**
|
1179 |
+
* Cache selectors
|
1180 |
+
*
|
1181 |
+
* @since 1.3.1
|
1182 |
+
*/
|
1183 |
+
getSettings : function() {
|
1184 |
+
self.settings = {
|
1185 |
+
description : $( '#boldgrid-seo-field-meta_description' ),
|
1186 |
+
};
|
1187 |
+
},
|
1188 |
+
|
1189 |
+
/**
|
1190 |
+
* Sets up event listener for changes made to the SEO Description.
|
1191 |
+
*
|
1192 |
+
* Listens for changes being made to the SEO Description, and then
|
1193 |
+
* triggers the reporter to be updated with new status/score.
|
1194 |
+
*
|
1195 |
+
* @since 1.3.1
|
1196 |
+
*/
|
1197 |
+
_description : function() {
|
1198 |
+
// Listen for changes to input value.
|
1199 |
+
self.settings.description.on( 'input propertychange paste', _.debounce( function() {
|
1200 |
+
$( this ).trigger( 'bgseo-analysis', [{ descLength : self.settings.description.val().length }] );
|
1201 |
+
}, 1000 ) );
|
1202 |
+
},
|
1203 |
+
|
1204 |
+
/**
|
1205 |
+
* Gets the SEO Description.
|
1206 |
+
*
|
1207 |
+
* @since 1.3.1
|
1208 |
+
*
|
1209 |
+
* @returns {Object} description Contains wrapped set with BoldGrid SEO Description.
|
1210 |
+
*/
|
1211 |
+
getDescription : function() {
|
1212 |
+
return self.settings.description;
|
1213 |
+
},
|
1214 |
+
|
1215 |
+
/**
|
1216 |
+
* Gets score of the SEO Description.
|
1217 |
+
*
|
1218 |
+
* Checks the length provided and returns a score and status color
|
1219 |
+
* for the SEO description. This score is based on character count.
|
1220 |
+
*
|
1221 |
+
* @since 1.3.1
|
1222 |
+
*
|
1223 |
+
* @param {Number} descriptionLength Length of the user's SEO Description.
|
1224 |
+
*
|
1225 |
+
* @returns {Object} msg Contains status indicator color and message to update.
|
1226 |
+
*/
|
1227 |
+
descriptionScore : function( descriptionLength ) {
|
1228 |
+
var msg = {}, desc;
|
1229 |
+
|
1230 |
+
desc = _bgseoContentAnalysis.seoDescription.length;
|
1231 |
+
|
1232 |
+
// No description has been entered.
|
1233 |
+
if ( descriptionLength === 0 ) {
|
1234 |
+
msg = {
|
1235 |
+
status: 'red',
|
1236 |
+
msg: desc.badEmpty,
|
1237 |
+
};
|
1238 |
+
}
|
1239 |
+
|
1240 |
+
// Character count is 1-124.
|
1241 |
+
if ( descriptionLength.isBetween( 0, desc.okScore ) ) {
|
1242 |
+
msg = {
|
1243 |
+
status: 'yellow',
|
1244 |
+
msg: desc.ok,
|
1245 |
+
};
|
1246 |
+
}
|
1247 |
+
|
1248 |
+
// Character count is 125-156.
|
1249 |
+
if ( descriptionLength.isBetween( desc.okScore - 1, desc.goodScore + 1 ) ) {
|
1250 |
+
msg = {
|
1251 |
+
status: 'green',
|
1252 |
+
msg: desc.good,
|
1253 |
+
};
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
// Character coutn is over 156.
|
1257 |
+
if ( descriptionLength > desc.goodScore ) {
|
1258 |
+
msg = {
|
1259 |
+
status: 'red',
|
1260 |
+
msg: desc.badLong,
|
1261 |
+
};
|
1262 |
+
}
|
1263 |
+
|
1264 |
+
return msg;
|
1265 |
+
},
|
1266 |
+
|
1267 |
+
/**
|
1268 |
+
* Gets the number of occurences in the SEO Description.
|
1269 |
+
*
|
1270 |
+
* @since 1.3.1
|
1271 |
+
*
|
1272 |
+
* @returns {Number} Frequency that keyword appears in description.
|
1273 |
+
*/
|
1274 |
+
keywords : function() {
|
1275 |
+
var keyword, description;
|
1276 |
+
|
1277 |
+
// Get keyword.
|
1278 |
+
keyword = api.Keywords.getKeyword();
|
1279 |
+
// Get text from input.
|
1280 |
+
description = self.getDescription().val();
|
1281 |
+
// Normalize user input.
|
1282 |
+
description = description.toLowerCase();
|
1283 |
+
|
1284 |
+
return description.occurences( keyword );
|
1285 |
+
},
|
1286 |
+
};
|
1287 |
+
|
1288 |
+
self = api.Description;
|
1289 |
+
|
1290 |
+
})( jQuery );
|
1291 |
+
|
1292 |
+
( function ( $ ) {
|
1293 |
+
|
1294 |
+
'use strict';
|
1295 |
+
|
1296 |
+
var self, report, api;
|
1297 |
+
|
1298 |
+
api = BOLDGRID.SEO;
|
1299 |
+
report = api.report;
|
1300 |
+
|
1301 |
+
/**
|
1302 |
+
* BoldGrid SEO Headings.
|
1303 |
+
*
|
1304 |
+
* This is responsible for the SEO Headings Grading.
|
1305 |
+
*
|
1306 |
+
* @since 1.3.1
|
1307 |
+
*/
|
1308 |
+
api.Headings = {
|
1309 |
+
|
1310 |
+
/**
|
1311 |
+
* Initialize SEO Headings Analysis.
|
1312 |
+
*
|
1313 |
+
* @since 1.3.1
|
1314 |
+
*/
|
1315 |
+
init : function () {
|
1316 |
+
$( document ).ready( self.onReady );
|
1317 |
+
},
|
1318 |
+
|
1319 |
+
/**
|
1320 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
1321 |
+
*
|
1322 |
+
* @since 1.3.1
|
1323 |
+
*/
|
1324 |
+
onReady : function() {
|
1325 |
+
self.getSettings();
|
1326 |
+
self._checkbox();
|
1327 |
+
},
|
1328 |
+
|
1329 |
+
/**
|
1330 |
+
* Cache selectors
|
1331 |
+
*
|
1332 |
+
* @since 1.3.1
|
1333 |
+
*/
|
1334 |
+
getSettings : function() {
|
1335 |
+
self.settings = {
|
1336 |
+
displayTitle : $( '[name="boldgrid-display-post-title"]' ).last(),
|
1337 |
+
};
|
1338 |
+
},
|
1339 |
+
|
1340 |
+
/**
|
1341 |
+
* Sets up event listener for Display page title checkbox.
|
1342 |
+
*
|
1343 |
+
* Listens for checkbox changes and updates the status message.
|
1344 |
+
*
|
1345 |
+
* @since 1.3.1
|
1346 |
+
*/
|
1347 |
+
_checkbox : function() {
|
1348 |
+
// Listen for changes to input value.
|
1349 |
+
self.settings.displayTitle.on( 'change', _.debounce( function() {
|
1350 |
+
$( this ).trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
1351 |
+
}, 1000 ) );
|
1352 |
+
},
|
1353 |
+
|
1354 |
+
/**
|
1355 |
+
* Initialize BoldGrid SEO Headings Analysis.
|
1356 |
+
*
|
1357 |
+
* @since 1.3.1
|
1358 |
+
*/
|
1359 |
+
score : function( count ) {
|
1360 |
+
var msg;
|
1361 |
+
|
1362 |
+
// Set default message for h1 headings score.
|
1363 |
+
msg = {
|
1364 |
+
status : 'green',
|
1365 |
+
msg : _bgseoContentAnalysis.headings.h1.good,
|
1366 |
+
};
|
1367 |
+
|
1368 |
+
// If we have more than one H1 tag rendered.
|
1369 |
+
if ( count > 1 ) {
|
1370 |
+
msg = {
|
1371 |
+
status : 'red',
|
1372 |
+
msg : _bgseoContentAnalysis.headings.h1.badMultiple,
|
1373 |
+
};
|
1374 |
+
}
|
1375 |
+
|
1376 |
+
// If we have more than one H1 tag rendered.
|
1377 |
+
if ( count > 1 && self.settings.displayTitle.is( ':checked' ) ) {
|
1378 |
+
msg = {
|
1379 |
+
status : 'red',
|
1380 |
+
msg : _bgseoContentAnalysis.headings.h1.badBoldgridTheme,
|
1381 |
+
};
|
1382 |
+
}
|
1383 |
+
|
1384 |
+
// If no H1 tag is present.
|
1385 |
+
if ( 0 === count ) {
|
1386 |
+
msg = {
|
1387 |
+
status : 'red',
|
1388 |
+
msg : _bgseoContentAnalysis.headings.h1.badEmpty,
|
1389 |
+
};
|
1390 |
+
}
|
1391 |
+
|
1392 |
+
return msg;
|
1393 |
+
},
|
1394 |
+
|
1395 |
+
/**
|
1396 |
+
* Gets count of how many times keywords appear in headings.
|
1397 |
+
*
|
1398 |
+
* @since 1.3.1
|
1399 |
+
*
|
1400 |
+
* @param {Object} headings The headings count object to check against.
|
1401 |
+
*
|
1402 |
+
* @returns {Number} How many times the keyword appears in the headings.
|
1403 |
+
*/
|
1404 |
+
keywords : function( headings ) {
|
1405 |
+
var found = { length : 0 },
|
1406 |
+
keyword = api.Keywords.getKeyword();
|
1407 |
+
|
1408 |
+
// If not passing in headings, attempt to find default headings.
|
1409 |
+
if ( _.isUndefined( headings ) ) {
|
1410 |
+
headings = { count : self.getRealHeadingCount() };
|
1411 |
+
}
|
1412 |
+
|
1413 |
+
// Don't process report item if headings are empty.
|
1414 |
+
if ( _.isEmpty( headings ) ) return;
|
1415 |
+
// Get the count.
|
1416 |
+
_( headings.count ).each( function( value, key ) {
|
1417 |
+
var text = value.text;
|
1418 |
+
// Add to the found object for total occurences found for keyword in headings.
|
1419 |
+
_( text ).each( function( item ) {
|
1420 |
+
found.length = Number( found.length ) + Number( item.heading.occurences( keyword ) * item.count );
|
1421 |
+
});
|
1422 |
+
});
|
1423 |
+
|
1424 |
+
return found.length;
|
1425 |
+
},
|
1426 |
+
|
1427 |
+
/**
|
1428 |
+
* Get the text inside of headings.
|
1429 |
+
*
|
1430 |
+
* @since 1.3.1
|
1431 |
+
*
|
1432 |
+
* @param {Object} selectors jQuery wrapped selector object.
|
1433 |
+
*
|
1434 |
+
* @returns {Array} headingText Contains each selectors' text.
|
1435 |
+
*/
|
1436 |
+
getHeadingText : function( selectors ) {
|
1437 |
+
var headingText = {};
|
1438 |
+
|
1439 |
+
headingText = _.countBy( selectors, function( value, key ) {
|
1440 |
+
return $.trim( $( value ).text().toLowerCase() );
|
1441 |
+
});
|
1442 |
+
headingText = _.map( headingText, function( value, key ) {
|
1443 |
+
return _( headingText ).has({ heading : key, count : value }) ? false : {
|
1444 |
+
heading : key,
|
1445 |
+
count : value,
|
1446 |
+
};
|
1447 |
+
});
|
1448 |
+
|
1449 |
+
return headingText;
|
1450 |
+
},
|
1451 |
+
|
1452 |
+
/**
|
1453 |
+
* Gets the actual headings count based on the rendered page and the content.
|
1454 |
+
*
|
1455 |
+
* This only needs to be fired if the rendered report
|
1456 |
+
* data is available for analysis. The calculations take
|
1457 |
+
* into account the template in use for the page/post and
|
1458 |
+
* are stored earlier on in the load process when the user
|
1459 |
+
* first enters the editor.
|
1460 |
+
*
|
1461 |
+
* @since 1.3.1
|
1462 |
+
*
|
1463 |
+
* @returns {Object} headings Count of H1, H2, and H3 tags used for page/post.
|
1464 |
+
*/
|
1465 |
+
getRealHeadingCount : function() {
|
1466 |
+
var headings = {};
|
1467 |
+
|
1468 |
+
// Only get this score if rendered content score has been provided.
|
1469 |
+
if ( ! _.isUndefined( report.rendered ) ) {
|
1470 |
+
// Stores the heading coutns for h1-h3 for later analysis.
|
1471 |
+
headings = {
|
1472 |
+
count: {
|
1473 |
+
h1 : {
|
1474 |
+
length : report.rendered.h1Count + report.rawstatistics.h1Count,
|
1475 |
+
text : _( report.rendered.h1text ).union( report.rawstatistics.h1text ),
|
1476 |
+
},
|
1477 |
+
h2 : {
|
1478 |
+
length : report.rendered.h2Count + report.rawstatistics.h2Count,
|
1479 |
+
text : _( report.rendered.h2text ).union( report.rawstatistics.h2text ),
|
1480 |
+
},
|
1481 |
+
},
|
1482 |
+
};
|
1483 |
+
// Add the score of H1 presence to the headings object.
|
1484 |
+
_( headings ).extend({
|
1485 |
+
lengthScore : self.score( headings.count.h1.length ),
|
1486 |
+
});
|
1487 |
+
} else {
|
1488 |
+
headings = self.getContentHeadings();
|
1489 |
+
}
|
1490 |
+
|
1491 |
+
return headings;
|
1492 |
+
},
|
1493 |
+
|
1494 |
+
/**
|
1495 |
+
* Get the headings that exist in the raw content.
|
1496 |
+
*
|
1497 |
+
* This will get the content and check if any h1s or
|
1498 |
+
* h2s exist in the raw markup. If they are present, it will
|
1499 |
+
* update the report with new count information and text.
|
1500 |
+
*
|
1501 |
+
* @since 1.3.1
|
1502 |
+
*
|
1503 |
+
* @returns {Object} headings Counts of h1 and h2 tags in content.
|
1504 |
+
*/
|
1505 |
+
getContentHeadings : function() {
|
1506 |
+
var headings, h1s, h2s, content;
|
1507 |
+
|
1508 |
+
// Set default counts.
|
1509 |
+
headings = {
|
1510 |
+
count: {
|
1511 |
+
h1 : {
|
1512 |
+
length : 0,
|
1513 |
+
text : {},
|
1514 |
+
},
|
1515 |
+
h2 : {
|
1516 |
+
length : 0,
|
1517 |
+
text : {},
|
1518 |
+
},
|
1519 |
+
},
|
1520 |
+
};
|
1521 |
+
|
1522 |
+
content = api.TinyMCE.getContent();
|
1523 |
+
|
1524 |
+
h1s = $( content.raw ).find( 'h1' );
|
1525 |
+
h2s = $( content.raw ).find( 'h2' );
|
1526 |
+
|
1527 |
+
// If no h1s or h2s are found return the defaults.
|
1528 |
+
if ( ! h1s.length && ! h2s.length ) return headings;
|
1529 |
+
|
1530 |
+
headings = {
|
1531 |
+
count: {
|
1532 |
+
h1 : {
|
1533 |
+
length : h1s.length,
|
1534 |
+
text : self.getHeadingText( h1s ),
|
1535 |
+
},
|
1536 |
+
h2 : {
|
1537 |
+
length : h2s.length,
|
1538 |
+
text : self.getHeadingText( h2s ),
|
1539 |
+
},
|
1540 |
+
},
|
1541 |
+
};
|
1542 |
+
|
1543 |
+
return headings;
|
1544 |
+
},
|
1545 |
+
};
|
1546 |
+
|
1547 |
+
self = api.Headings;
|
1548 |
+
|
1549 |
+
})( jQuery );
|
1550 |
+
|
1551 |
+
( function( $ ) {
|
1552 |
+
|
1553 |
+
'use strict';
|
1554 |
+
|
1555 |
+
var self, report, api;
|
1556 |
+
|
1557 |
+
api = BOLDGRID.SEO;
|
1558 |
+
report = api.report;
|
1559 |
+
|
1560 |
+
/**
|
1561 |
+
* BoldGrid SEO Keywords.
|
1562 |
+
*
|
1563 |
+
* This is responsible for the SEO Keywords Analysis and Scoring.
|
1564 |
+
*
|
1565 |
+
* @since 1.3.1
|
1566 |
+
*/
|
1567 |
+
api.Keywords = {
|
1568 |
+
/**
|
1569 |
+
* Initialize BoldGrid SEO Keyword Analysis.
|
1570 |
+
*
|
1571 |
+
* @since 1.3.1
|
1572 |
+
*/
|
1573 |
+
init : function () {
|
1574 |
+
$( document ).ready( self.onReady );
|
1575 |
+
},
|
1576 |
+
|
1577 |
+
/**
|
1578 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
1579 |
+
*
|
1580 |
+
* @since 1.3.1
|
1581 |
+
*/
|
1582 |
+
onReady : function() {
|
1583 |
+
self.getSettings();
|
1584 |
+
self._keywords();
|
1585 |
+
self.setPlaceholder();
|
1586 |
+
},
|
1587 |
+
|
1588 |
+
/**
|
1589 |
+
* Cache selectors
|
1590 |
+
*
|
1591 |
+
* @since 1.3.1
|
1592 |
+
*/
|
1593 |
+
getSettings : function() {
|
1594 |
+
self.settings = {
|
1595 |
+
keyword : $( '#bgseo-custom-keyword' ),
|
1596 |
+
content : $( '#content' ),
|
1597 |
+
};
|
1598 |
+
},
|
1599 |
+
|
1600 |
+
/**
|
1601 |
+
* Sets up event listener for changes made to the custom keyword input.
|
1602 |
+
*
|
1603 |
+
* Listens for changes being made to the custom keyword input, and then
|
1604 |
+
* triggers the reporter to be updated with new status/score.
|
1605 |
+
*
|
1606 |
+
* @since 1.3.1
|
1607 |
+
*/
|
1608 |
+
_keywords: function() {
|
1609 |
+
self.settings.keyword.on( 'input propertychange paste', _.debounce( function() {
|
1610 |
+
var msg = {},
|
1611 |
+
length = self.settings.keyword.val().length;
|
1612 |
+
|
1613 |
+
msg = {
|
1614 |
+
keywords : {
|
1615 |
+
title : {
|
1616 |
+
length : api.Title.keywords(),
|
1617 |
+
lengthScore : 0,
|
1618 |
+
},
|
1619 |
+
description : {
|
1620 |
+
length : api.Description.keywords(),
|
1621 |
+
lengthScore : 0,
|
1622 |
+
},
|
1623 |
+
keyword : self.getCustomKeyword(),
|
1624 |
+
},
|
1625 |
+
};
|
1626 |
+
|
1627 |
+
self.settings.keyword.trigger( 'bgseo-analysis', [msg] );
|
1628 |
+
|
1629 |
+
}, 1000 ) );
|
1630 |
+
},
|
1631 |
+
|
1632 |
+
setPlaceholder : function( keyword ) {
|
1633 |
+
self.settings.keyword.attr( 'placeholder', keyword );
|
1634 |
+
},
|
1635 |
+
|
1636 |
+
/**
|
1637 |
+
* Gets the count of the keywords in the content passed in.
|
1638 |
+
*
|
1639 |
+
* @since 1.3.1
|
1640 |
+
*
|
1641 |
+
* @param {string} content The content to count keyword frequency in.
|
1642 |
+
* @param {string} keyword The keyword/phrase to search for.
|
1643 |
+
*
|
1644 |
+
* @returns {Number} keywordCount Represents how many times a keyword appears.
|
1645 |
+
*/
|
1646 |
+
keywordCount: function( content, keyword ) {
|
1647 |
+
var keywordCount;
|
1648 |
+
|
1649 |
+
keywordCount = content.split( keyword ).length - 1;
|
1650 |
+
|
1651 |
+
return keywordCount;
|
1652 |
+
},
|
1653 |
+
|
1654 |
+
/**
|
1655 |
+
* Gets the count of words in the keyword phrase section.
|
1656 |
+
*
|
1657 |
+
* @since 1.3.1
|
1658 |
+
*
|
1659 |
+
* @param {string} keywordPhrase The content to count words in.
|
1660 |
+
*
|
1661 |
+
* @returns {Number} Number of words in keywordPhrase.
|
1662 |
+
*/
|
1663 |
+
phraseLength: function( keywordPhrase ) {
|
1664 |
+
|
1665 |
+
// Check for empty strings.
|
1666 |
+
if ( keywordPhrase.length === 0 ) {
|
1667 |
+
return 0;
|
1668 |
+
}
|
1669 |
+
|
1670 |
+
// Excludes start and end white-space.
|
1671 |
+
keywordPhrase = keywordPhrase.replace( /(^\s*)|(\s*$)/gi, '' );
|
1672 |
+
|
1673 |
+
// 2 or more space to 1.
|
1674 |
+
keywordPhrase = keywordPhrase.replace( /[ ]{2,}/gi, ' ' );
|
1675 |
+
|
1676 |
+
// Exclude newline with a start spacing.
|
1677 |
+
keywordPhrase = keywordPhrase.replace( /\n /, '\n' );
|
1678 |
+
|
1679 |
+
return keywordPhrase.split( ' ' ).length;
|
1680 |
+
},
|
1681 |
+
|
1682 |
+
/**
|
1683 |
+
* Calculates keyword density for content and keyword passed in.
|
1684 |
+
*
|
1685 |
+
* @since 1.3.1
|
1686 |
+
*
|
1687 |
+
* @param {string} content The content to calculate density for.
|
1688 |
+
*
|
1689 |
+
* @returns {Number} result Calculated density of keyword in content passed.
|
1690 |
+
*/
|
1691 |
+
keywordDensity : function( content ) {
|
1692 |
+
var result, keywordCount, wordCount, keyword;
|
1693 |
+
|
1694 |
+
keyword = self.getKeyword();
|
1695 |
+
|
1696 |
+
// Return 0 without calculation if no custom keyword is found.
|
1697 |
+
if ( _.isUndefined( keyword ) ) return 0;
|
1698 |
+
|
1699 |
+
// Normalize.
|
1700 |
+
keyword = keyword.toLowerCase();
|
1701 |
+
|
1702 |
+
keywordCount = self.keywordCount( content, keyword );
|
1703 |
+
wordCount = api.Report.getWordCount();
|
1704 |
+
// Get the density.
|
1705 |
+
result = ( ( keywordCount / wordCount ) * 100 );
|
1706 |
+
// Round it off.
|
1707 |
+
result = Math.round( result * 10 ) / 10;
|
1708 |
+
|
1709 |
+
return result;
|
1710 |
+
},
|
1711 |
+
|
1712 |
+
/**
|
1713 |
+
* Normalizes the stop words to match the words returned by the WP
|
1714 |
+
* WordCount.
|
1715 |
+
*
|
1716 |
+
* @since 1.3.2
|
1717 |
+
*
|
1718 |
+
* @param {string} str Word to normalize.
|
1719 |
+
*
|
1720 |
+
* @returns {string} Normalized word.
|
1721 |
+
*/
|
1722 |
+
normalizeWords: function( str ) {
|
1723 |
+
return str.replace( '\'', '' );
|
1724 |
+
},
|
1725 |
+
|
1726 |
+
/**
|
1727 |
+
* Trims values of whitespace.
|
1728 |
+
*
|
1729 |
+
* @since 1.3.2
|
1730 |
+
*
|
1731 |
+
* @param {string} str Word to trim.
|
1732 |
+
*
|
1733 |
+
* @returns {string} Trimmed word.
|
1734 |
+
*/
|
1735 |
+
trim: function( str ) {
|
1736 |
+
return str.trim();
|
1737 |
+
},
|
1738 |
+
|
1739 |
+
/**
|
1740 |
+
* Gets the recommended keywords from content.
|
1741 |
+
*
|
1742 |
+
* This is what gets suggested to a user that their content is about this
|
1743 |
+
* keyword if they do not enter in a custom target keyword or phrase.
|
1744 |
+
*
|
1745 |
+
* @since 1.3.1
|
1746 |
+
*
|
1747 |
+
* @param {Array} words The words to search through.
|
1748 |
+
* @param {Number} n How many keywords to return back.
|
1749 |
+
*
|
1750 |
+
* @returns {Array} result An array of n* most frequent keywords.
|
1751 |
+
*/
|
1752 |
+
recommendedKeywords: function( words, n ) {
|
1753 |
+
var stopWords = _bgseoContentAnalysis.stopWords,
|
1754 |
+
positions = {},
|
1755 |
+
wordCounts = [],
|
1756 |
+
result;
|
1757 |
+
|
1758 |
+
// Abort if no words are passed in.
|
1759 |
+
if ( _.isEmpty( words ) ) return;
|
1760 |
+
|
1761 |
+
// Create array from string passed, and trim array values.
|
1762 |
+
stopWords = stopWords.split( ',' ).map( self.trim );
|
1763 |
+
|
1764 |
+
// Normalize the stopWords to watch WordPress words.
|
1765 |
+
stopWords = stopWords.map( self.normalizeWords );
|
1766 |
+
|
1767 |
+
for ( var i = 0; i < words.length; i++ ) {
|
1768 |
+
var word = $.trim( words[i] ).toLowerCase();
|
1769 |
+
|
1770 |
+
// Make sure word isn't in our stop words and is longer than 3 characters.
|
1771 |
+
if ( ! word || word.length < 3 || stopWords.indexOf( word ) > -1 ) {
|
1772 |
+
continue;
|
1773 |
+
}
|
1774 |
+
|
1775 |
+
if ( _.isUndefined( positions[ word ] ) ) {
|
1776 |
+
positions[ word ] = wordCounts.length;
|
1777 |
+
wordCounts.push( [ word, 1 ] );
|
1778 |
+
} else {
|
1779 |
+
wordCounts[ positions[ word ] ][1]++;
|
1780 |
+
}
|
1781 |
+
}
|
1782 |
+
// Put most frequent words at the beginning.
|
1783 |
+
wordCounts.sort( function ( a, b ) {
|
1784 |
+
return b[1] - a[1];
|
1785 |
+
});
|
1786 |
+
|
1787 |
+
// Return the first n items
|
1788 |
+
result = wordCounts.slice( 0, n );
|
1789 |
+
|
1790 |
+
return result;
|
1791 |
+
},
|
1792 |
+
|
1793 |
+
/**
|
1794 |
+
* Retrieves User's Custom SEO Keyword.
|
1795 |
+
*
|
1796 |
+
* If the user has entered in a custom keyword to run evaluation on,
|
1797 |
+
* then we will retrieve this value instead of the automatically
|
1798 |
+
* generated keyword recommendation.
|
1799 |
+
*
|
1800 |
+
* @since 1.3.1
|
1801 |
+
*
|
1802 |
+
* @returns {string} Trimmed output of user supplied custom keyword.
|
1803 |
+
*/
|
1804 |
+
getCustomKeyword : function() {
|
1805 |
+
return $.trim( self.settings.keyword.val() ).toLowerCase();
|
1806 |
+
},
|
1807 |
+
|
1808 |
+
/**
|
1809 |
+
* Used to get the keyword for the report.
|
1810 |
+
*
|
1811 |
+
* Checks if a custom keyword has been set by the user, and
|
1812 |
+
* if it hasn't it will use the autogenerated keyword that was
|
1813 |
+
* determined based on the content.
|
1814 |
+
*
|
1815 |
+
* @since 1.3.1
|
1816 |
+
*
|
1817 |
+
* @returns {string} customKeyword Contains the customKeyword to add to report.
|
1818 |
+
*/
|
1819 |
+
getKeyword : function() {
|
1820 |
+
var customKeyword,
|
1821 |
+
content = api.TinyMCE.getContent();
|
1822 |
+
|
1823 |
+
if ( self.getCustomKeyword().length ) {
|
1824 |
+
customKeyword = self.getCustomKeyword();
|
1825 |
+
} else if ( ! _.isUndefined( report.textstatistics.recommendedKeywords ) &&
|
1826 |
+
! _.isUndefined( report.textstatistics.recommendedKeywords[0] ) ) {
|
1827 |
+
// Set customKeyword to recommended keyword search.
|
1828 |
+
customKeyword = report.textstatistics.recommendedKeywords[0][0];
|
1829 |
+
} else if ( _.isEmpty( $.trim( content.text ) ) ) {
|
1830 |
+
customKeyword = undefined;
|
1831 |
+
} else {
|
1832 |
+
self.recommendedKeywords( api.Words.words( content.raw ), 1 );
|
1833 |
+
}
|
1834 |
+
|
1835 |
+
return customKeyword;
|
1836 |
+
},
|
1837 |
+
|
1838 |
+
/**
|
1839 |
+
* Used to get the recommended keyword count.
|
1840 |
+
*
|
1841 |
+
* Gets the percentages provided for minimum and maximum keyword
|
1842 |
+
* densities from the configs. The number is based on the amount of words
|
1843 |
+
* that make up the current page/post.
|
1844 |
+
*
|
1845 |
+
* @since 1.3.1
|
1846 |
+
*
|
1847 |
+
* @returns {Object} count Range for count of keywords based on content length.
|
1848 |
+
*/
|
1849 |
+
getRecommendedCount : function( markup ) {
|
1850 |
+
var count;
|
1851 |
+
|
1852 |
+
if ( _.isUndefined( markup ) ) {
|
1853 |
+
markup = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ?
|
1854 |
+
api.Words.words( self.settings.content.val() ) :
|
1855 |
+
api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
|
1856 |
+
}
|
1857 |
+
|
1858 |
+
count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
|
1859 |
+
var numb = Number( ( item / 100 ) * api.Words.words( markup ).length ).rounded( 0 );
|
1860 |
+
// Set minimum recommended count to at least once.
|
1861 |
+
return numb > 0 ? numb : 1;
|
1862 |
+
});
|
1863 |
+
|
1864 |
+
return count;
|
1865 |
+
},
|
1866 |
+
|
1867 |
+
/**
|
1868 |
+
* Used to get the keyword for the report.
|
1869 |
+
*
|
1870 |
+
* Checks if a custom keyword has been set by the user, and
|
1871 |
+
* if it hasn't it will use the autogenerated keyword that was
|
1872 |
+
* determined based on the content.
|
1873 |
+
*
|
1874 |
+
* @since 1.3.1
|
1875 |
+
*
|
1876 |
+
* @returns {Object} msg Contains the scoring for each keyword related item.
|
1877 |
+
*/
|
1878 |
+
score : function() {
|
1879 |
+
var msg = {};
|
1880 |
+
msg = {
|
1881 |
+
title : self.titleScore(),
|
1882 |
+
description : self.descriptionScore(),
|
1883 |
+
};
|
1884 |
+
return msg;
|
1885 |
+
},
|
1886 |
+
|
1887 |
+
/**
|
1888 |
+
* Used to get the keyword usage scoring description for the title.
|
1889 |
+
*
|
1890 |
+
* Checks the count provided for the number of times the keyword was
|
1891 |
+
* used in the SEO Title.
|
1892 |
+
*
|
1893 |
+
* @since 1.3.1
|
1894 |
+
*
|
1895 |
+
* @param {Number} count The number of times keyword is used in the title.
|
1896 |
+
*
|
1897 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
1898 |
+
*/
|
1899 |
+
titleScore : function( count ) {
|
1900 |
+
var msg;
|
1901 |
+
|
1902 |
+
// Default status and message.
|
1903 |
+
msg = {
|
1904 |
+
status: 'green',
|
1905 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.good,
|
1906 |
+
};
|
1907 |
+
|
1908 |
+
// Keyword not used in title.
|
1909 |
+
if ( 0 === count ) {
|
1910 |
+
msg = {
|
1911 |
+
status: 'red',
|
1912 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.bad,
|
1913 |
+
};
|
1914 |
+
}
|
1915 |
+
|
1916 |
+
// Keyword used in title at least once.
|
1917 |
+
if ( count > 1 ) {
|
1918 |
+
msg = {
|
1919 |
+
status: 'yellow',
|
1920 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.ok,
|
1921 |
+
};
|
1922 |
+
}
|
1923 |
+
|
1924 |
+
return msg;
|
1925 |
+
},
|
1926 |
+
|
1927 |
+
/**
|
1928 |
+
* Used to get the keyword usage scoring description for the description.
|
1929 |
+
*
|
1930 |
+
* Checks the count provided for the number of times the keyword was
|
1931 |
+
* used in the SEO Description field.
|
1932 |
+
*
|
1933 |
+
* @since 1.3.1
|
1934 |
+
*
|
1935 |
+
* @param {Number} count The number of times keyword is used in the description.
|
1936 |
+
*
|
1937 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
1938 |
+
*/
|
1939 |
+
descriptionScore : function( count ) {
|
1940 |
+
var msg;
|
1941 |
+
|
1942 |
+
// Default status and message.
|
1943 |
+
msg = {
|
1944 |
+
status: 'green',
|
1945 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.good,
|
1946 |
+
};
|
1947 |
+
|
1948 |
+
// If not used at all in description.
|
1949 |
+
if ( 0 === count ) {
|
1950 |
+
msg = {
|
1951 |
+
status: 'red',
|
1952 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.bad,
|
1953 |
+
};
|
1954 |
+
}
|
1955 |
+
|
1956 |
+
// If used at least one time in description.
|
1957 |
+
if ( count > 1 ) {
|
1958 |
+
msg = {
|
1959 |
+
status: 'yellow',
|
1960 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.ok,
|
1961 |
+
};
|
1962 |
+
}
|
1963 |
+
|
1964 |
+
return msg;
|
1965 |
+
},
|
1966 |
+
|
1967 |
+
/**
|
1968 |
+
* Gets keyword score for content.
|
1969 |
+
*
|
1970 |
+
* Used to get the status and message for the content's keyword usage.
|
1971 |
+
*
|
1972 |
+
* @since 1.3.1
|
1973 |
+
*
|
1974 |
+
* @param {Number} count The number of times keyword is used in the content.
|
1975 |
+
*
|
1976 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
1977 |
+
*/
|
1978 |
+
contentScore : function( count ) {
|
1979 |
+
var msg, range, description;
|
1980 |
+
|
1981 |
+
// Get the keyword range based on the content length.
|
1982 |
+
range = self.getRecommendedCount();
|
1983 |
+
|
1984 |
+
// Keyword not used at all in content.
|
1985 |
+
if ( 0 === count ) {
|
1986 |
+
msg = {
|
1987 |
+
status: 'red',
|
1988 |
+
msg : _bgseoContentAnalysis.content.keywordUsage.bad,
|
1989 |
+
};
|
1990 |
+
}
|
1991 |
+
// Keyword used within the range calculated based on content length.
|
1992 |
+
if ( count.isBetween( range.min - 1, range.max + 1 ) ) {
|
1993 |
+
description = 1 === range.min ?
|
1994 |
+
_bgseoContentAnalysis.content.keywordUsage.goodSingular :
|
1995 |
+
_bgseoContentAnalysis.content.keywordUsage.good.printf( range.min );
|
1996 |
+
|
1997 |
+
msg = {
|
1998 |
+
status: 'green',
|
1999 |
+
msg : description,
|
2000 |
+
};
|
2001 |
+
}
|
2002 |
+
// Keyword used less than the minimum of the range specified, but not 0 times.
|
2003 |
+
if ( count < range.min && count !== 0 ) {
|
2004 |
+
description = 1 === range.min ?
|
2005 |
+
_bgseoContentAnalysis.content.keywordUsage.okShortSingular :
|
2006 |
+
_bgseoContentAnalysis.content.keywordUsage.okShort.printf( range.min );
|
2007 |
+
|
2008 |
+
msg = {
|
2009 |
+
status: 'yellow',
|
2010 |
+
msg : description,
|
2011 |
+
};
|
2012 |
+
}
|
2013 |
+
|
2014 |
+
// Key word used more than 3 times in the content.
|
2015 |
+
if ( count > range.max ) {
|
2016 |
+
description = 1 === range.min ?
|
2017 |
+
_bgseoContentAnalysis.content.keywordUsage.okLongSingular :
|
2018 |
+
_bgseoContentAnalysis.content.keywordUsage.okLong.printf( range.min );
|
2019 |
+
|
2020 |
+
msg = {
|
2021 |
+
status: 'red',
|
2022 |
+
msg : description,
|
2023 |
+
};
|
2024 |
+
}
|
2025 |
+
|
2026 |
+
return msg;
|
2027 |
+
},
|
2028 |
+
|
2029 |
+
/**
|
2030 |
+
* Gets keyword score for headings.
|
2031 |
+
*
|
2032 |
+
* Used to get the status and message for the heading's keyword usage.
|
2033 |
+
*
|
2034 |
+
* @since 1.3.1
|
2035 |
+
*
|
2036 |
+
* @param {Number} count The number of times keyword is used in the headings.
|
2037 |
+
*
|
2038 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
2039 |
+
*/
|
2040 |
+
headingScore : function( count ) {
|
2041 |
+
var msg;
|
2042 |
+
|
2043 |
+
// Default message.
|
2044 |
+
msg = {
|
2045 |
+
status: 'green',
|
2046 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.good,
|
2047 |
+
};
|
2048 |
+
|
2049 |
+
// Keyword not used at all in content.
|
2050 |
+
if ( 0 === count ) {
|
2051 |
+
msg = {
|
2052 |
+
status: 'red',
|
2053 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.bad,
|
2054 |
+
};
|
2055 |
+
}
|
2056 |
+
// Key word used more than 3 times in the content.
|
2057 |
+
if ( count > 3 ) {
|
2058 |
+
msg = {
|
2059 |
+
status: 'yellow',
|
2060 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.ok,
|
2061 |
+
};
|
2062 |
+
}
|
2063 |
+
|
2064 |
+
return msg;
|
2065 |
+
},
|
2066 |
+
|
2067 |
+
/**
|
2068 |
+
* Used to get the scoring description for the keyword phrase.
|
2069 |
+
*
|
2070 |
+
* Returns the status message based on how many words are in the phrase.
|
2071 |
+
*
|
2072 |
+
* @since 1.3.1
|
2073 |
+
*
|
2074 |
+
* @param {Number} count WordCount for phrase.
|
2075 |
+
*
|
2076 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
2077 |
+
*/
|
2078 |
+
keywordPhraseScore : function( count ) {
|
2079 |
+
var msg;
|
2080 |
+
|
2081 |
+
// Default status and message.
|
2082 |
+
msg = {
|
2083 |
+
status: 'green',
|
2084 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.good,
|
2085 |
+
};
|
2086 |
+
|
2087 |
+
// Keyword used in title at least once.
|
2088 |
+
if ( 1 === count ) {
|
2089 |
+
msg = {
|
2090 |
+
status: 'yellow',
|
2091 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.ok,
|
2092 |
+
};
|
2093 |
+
}
|
2094 |
+
|
2095 |
+
// Keyword not used in title.
|
2096 |
+
if ( 0 === count ) {
|
2097 |
+
msg = {
|
2098 |
+
status: 'red',
|
2099 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.bad,
|
2100 |
+
};
|
2101 |
+
}
|
2102 |
+
|
2103 |
+
return msg;
|
2104 |
+
},
|
2105 |
+
};
|
2106 |
+
|
2107 |
+
self = api.Keywords;
|
2108 |
+
|
2109 |
+
})( jQuery );
|
2110 |
+
|
2111 |
+
( function ( $ ) {
|
2112 |
+
|
2113 |
+
'use strict';
|
2114 |
+
|
2115 |
+
var self, report, api;
|
2116 |
+
|
2117 |
+
api = BOLDGRID.SEO;
|
2118 |
+
report = api.report;
|
2119 |
+
|
2120 |
+
/**
|
2121 |
+
* BoldGrid SEO Readability.
|
2122 |
+
*
|
2123 |
+
* This is responsible for the SEO Reading Score and Grading.
|
2124 |
+
*
|
2125 |
+
* @since 1.3.1
|
2126 |
+
*/
|
2127 |
+
api.Readability = {
|
2128 |
+
|
2129 |
+
/**
|
2130 |
+
* Gets the Flesch Kincaid Grade based on the content.
|
2131 |
+
*
|
2132 |
+
* @since 1.3.1
|
2133 |
+
*
|
2134 |
+
* @param {String} content The content to run the analysis on.
|
2135 |
+
*
|
2136 |
+
* @returns {Number} result A number representing the grade of the content.
|
2137 |
+
*/
|
2138 |
+
gradeLevel : function( content ) {
|
2139 |
+
var grade, result = {};
|
2140 |
+
grade = textstatistics( content ).fleschKincaidReadingEase();
|
2141 |
+
result = self.gradeAnalysis( grade );
|
2142 |
+
return result;
|
2143 |
+
},
|
2144 |
+
|
2145 |
+
/**
|
2146 |
+
* Returns information about the grade for display.
|
2147 |
+
*
|
2148 |
+
* This will give back human readable explanations of the grading, so
|
2149 |
+
* the user can make changes based on their score accurately.
|
2150 |
+
*
|
2151 |
+
* @since 1.3.1
|
2152 |
+
*
|
2153 |
+
* @param {Number} grade The grade to evalute and return response for.
|
2154 |
+
*
|
2155 |
+
* @returns {Object} description Contains status, explanation and associated grade level.
|
2156 |
+
*/
|
2157 |
+
gradeAnalysis : function( grade ) {
|
2158 |
+
var scoreTranslated, description = {};
|
2159 |
+
|
2160 |
+
// Grade is higher than 90.
|
2161 |
+
if ( grade > 90 ) {
|
2162 |
+
description = {
|
2163 |
+
'score' : grade,
|
2164 |
+
'gradeLevel' : '5th grade',
|
2165 |
+
'explanation': 'Very easy to read. Easily understood by an average 11-year-old student.',
|
2166 |
+
lengthScore : {
|
2167 |
+
'status' : 'green',
|
2168 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodHigh,
|
2169 |
+
},
|
2170 |
+
};
|
2171 |
+
}
|
2172 |
+
// Grade is 80-90.
|
2173 |
+
if ( grade.isBetween( 79, 91 ) ) {
|
2174 |
+
description = {
|
2175 |
+
'score' : grade,
|
2176 |
+
'gradeLevel' : '6th grade',
|
2177 |
+
'explanation': 'Easy to read. Conversational English for consumers.',
|
2178 |
+
lengthScore : {
|
2179 |
+
'status' : 'green',
|
2180 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodMedHigh,
|
2181 |
+
},
|
2182 |
+
};
|
2183 |
+
}
|
2184 |
+
// Grade is 70-90.
|
2185 |
+
if ( grade.isBetween( 69, 81 ) ) {
|
2186 |
+
description = {
|
2187 |
+
'score' : grade,
|
2188 |
+
'gradeLevel' : '7th grade',
|
2189 |
+
'explanation': 'Fairly easy to read.',
|
2190 |
+
lengthScore : {
|
2191 |
+
'status' : 'green',
|
2192 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodMedLow,
|
2193 |
+
}
|
2194 |
+
};
|
2195 |
+
}
|
2196 |
+
// Grade is 60-70.
|
2197 |
+
if ( grade.isBetween( 59, 71 ) ) {
|
2198 |
+
description = {
|
2199 |
+
'score' : grade,
|
2200 |
+
'gradeLevel' : '8th & 9th',
|
2201 |
+
'explanation': 'Plain English. Easily understood by 13- to 15-year-old students.',
|
2202 |
+
lengthScore : {
|
2203 |
+
'status' : 'green',
|
2204 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodLow,
|
2205 |
+
},
|
2206 |
+
};
|
2207 |
+
}
|
2208 |
+
// Grade is 50-60.
|
2209 |
+
if ( grade.isBetween( 49, 61 ) ) {
|
2210 |
+
description = {
|
2211 |
+
'score' : grade,
|
2212 |
+
'gradeLevel' : '10th to 12th',
|
2213 |
+
'explanation': 'Fairly difficult to read.',
|
2214 |
+
lengthScore : {
|
2215 |
+
'status' : 'yellow',
|
2216 |
+
'msg' : _bgseoContentAnalysis.readingEase.ok,
|
2217 |
+
},
|
2218 |
+
};
|
2219 |
+
}
|
2220 |
+
// Grade is 30-50.
|
2221 |
+
if ( grade.isBetween( 29, 51 ) ) {
|
2222 |
+
description = {
|
2223 |
+
'score' : grade,
|
2224 |
+
'gradeLevel' : 'College Student',
|
2225 |
+
'explanation': 'Difficult to read.',
|
2226 |
+
lengthScore : {
|
2227 |
+
'status' : 'red',
|
2228 |
+
'msg' : _bgseoContentAnalysis.readingEase.badHigh,
|
2229 |
+
},
|
2230 |
+
};
|
2231 |
+
}
|
2232 |
+
// Grade is less than 30.
|
2233 |
+
if ( grade < 30 ) {
|
2234 |
+
description = {
|
2235 |
+
'score' : grade,
|
2236 |
+
'gradeLevel' : 'College Graduate',
|
2237 |
+
'explanation': 'Difficult to read.',
|
2238 |
+
lengthScore : {
|
2239 |
+
'status' : 'red',
|
2240 |
+
'msg' : _bgseoContentAnalysis.readingEase.badLow,
|
2241 |
+
},
|
2242 |
+
};
|
2243 |
+
}
|
2244 |
+
// Add translated score string to message.
|
2245 |
+
scoreTranslated = _bgseoContentAnalysis.readingEase.score.printf( grade ) + ' ';
|
2246 |
+
description.lengthScore.msg = description.lengthScore.msg.replace( /^/, scoreTranslated );
|
2247 |
+
|
2248 |
+
return description;
|
2249 |
+
},
|
2250 |
+
};
|
2251 |
+
|
2252 |
+
self = api.Readability;
|
2253 |
+
|
2254 |
+
})( jQuery );
|
2255 |
+
|
2256 |
+
( function ( $ ) {
|
2257 |
+
|
2258 |
+
'use strict';
|
2259 |
+
|
2260 |
+
var self, report, api;
|
2261 |
+
|
2262 |
+
api = BOLDGRID.SEO;
|
2263 |
+
report = api.report;
|
2264 |
+
|
2265 |
+
/**
|
2266 |
+
* BoldGrid TinyMCE Analysis.
|
2267 |
+
*
|
2268 |
+
* This is responsible for generating the actual reports
|
2269 |
+
* displayed within the BoldGrid SEO Dashboard when the user
|
2270 |
+
* is on a page or a post.
|
2271 |
+
*
|
2272 |
+
* @since 1.3.1
|
2273 |
+
*/
|
2274 |
+
api.Report = {
|
2275 |
+
|
2276 |
+
/**
|
2277 |
+
* Initialize TinyMCE Content.
|
2278 |
+
*
|
2279 |
+
* @since 1.3.1
|
2280 |
+
*/
|
2281 |
+
init : function () {
|
2282 |
+
$( document ).ready( self.onReady );
|
2283 |
+
},
|
2284 |
+
|
2285 |
+
/**
|
2286 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
2287 |
+
*
|
2288 |
+
* @since 1.3.1
|
2289 |
+
*/
|
2290 |
+
onReady : function() {
|
2291 |
+
self.getSettings();
|
2292 |
+
self.generateReport();
|
2293 |
+
},
|
2294 |
+
|
2295 |
+
/**
|
2296 |
+
* Cache selectors
|
2297 |
+
*
|
2298 |
+
* @since 1.3.1
|
2299 |
+
*/
|
2300 |
+
getSettings : function() {
|
2301 |
+
self.settings = {
|
2302 |
+
title : $( '#boldgrid-seo-field-meta_title' ),
|
2303 |
+
description : $( '#boldgrid-seo-field-meta_description' ),
|
2304 |
+
wordCounter : $( '#wp-word-count .word-count' ),
|
2305 |
+
content : $( '#content' ),
|
2306 |
+
};
|
2307 |
+
},
|
2308 |
+
|
2309 |
+
getWordCount : function() {
|
2310 |
+
return Number( self.settings.wordCounter.text() );
|
2311 |
+
},
|
2312 |
+
|
2313 |
+
/**
|
2314 |
+
* Generate the Report based on analysis done.
|
2315 |
+
*
|
2316 |
+
* This will generate a report object and then trigger the
|
2317 |
+
* reporter event, so that the model is updated and changes
|
2318 |
+
* are reflected live for the user in their SEO Dashboard.
|
2319 |
+
*
|
2320 |
+
* @since 1.3.1
|
2321 |
+
*/
|
2322 |
+
generateReport : function() {
|
2323 |
+
if ( _.isUndefined( self.settings ) ) return;
|
2324 |
+
$( document ).on( 'bgseo-analysis', function( e, eventInfo ) {
|
2325 |
+
var words, titleLength, descriptionLength;
|
2326 |
+
|
2327 |
+
// Get length of title field.
|
2328 |
+
titleLength = self.settings.title.val().length;
|
2329 |
+
|
2330 |
+
// Get length of description field.
|
2331 |
+
descriptionLength = self.settings.description.val().length;
|
2332 |
+
|
2333 |
+
if ( eventInfo.words ) {
|
2334 |
+
_( report.textstatistics ).extend({
|
2335 |
+
recommendedKeywords : api.Keywords.recommendedKeywords( eventInfo.words, 1 ),
|
2336 |
+
customKeyword : api.Keywords.getKeyword(),
|
2337 |
+
});
|
2338 |
+
}
|
2339 |
+
|
2340 |
+
// Listen for event changes being triggered.
|
2341 |
+
if ( eventInfo ) {
|
2342 |
+
// Listen for changes to raw HTML in editor.
|
2343 |
+
if ( eventInfo.raw ) {
|
2344 |
+
var raws = eventInfo.raw;
|
2345 |
+
|
2346 |
+
var h1 = $( raws ).find( 'h1' ),
|
2347 |
+
h2 = $( raws ).find( 'h2' ),
|
2348 |
+
headings = {};
|
2349 |
+
|
2350 |
+
headings = {
|
2351 |
+
h1Count : h1.length,
|
2352 |
+
h1text : api.Headings.getHeadingText( h1 ),
|
2353 |
+
h2Count : h2.length,
|
2354 |
+
h2text : api.Headings.getHeadingText( h2 ),
|
2355 |
+
imageCount: $( raws ).find( 'img' ).length,
|
2356 |
+
};
|
2357 |
+
// Set the heading counts and image count found in new content update.
|
2358 |
+
_( report.rawstatistics ).extend( headings );
|
2359 |
+
}
|
2360 |
+
|
2361 |
+
if ( eventInfo.keywords ) {
|
2362 |
+
_( report.bgseo_keywords ).extend({
|
2363 |
+
keywordPhrase: {
|
2364 |
+
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
|
2365 |
+
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
|
2366 |
+
},
|
2367 |
+
keywordTitle : {
|
2368 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
2369 |
+
},
|
2370 |
+
keywordDescription : {
|
2371 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
2372 |
+
},
|
2373 |
+
keywordContent : {
|
2374 |
+
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
|
2375 |
+
},
|
2376 |
+
keywordHeadings : {
|
2377 |
+
length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
|
2378 |
+
lengthScore : api.Keywords.headingScore( api.Headings.keywords( api.Headings.getRealHeadingCount() ) ),
|
2379 |
+
},
|
2380 |
+
customKeyword : eventInfo.keywords.keyword,
|
2381 |
+
});
|
2382 |
+
}
|
2383 |
+
|
2384 |
+
// Listen for changes to the actual text entered by user.
|
2385 |
+
if ( eventInfo.text ) {
|
2386 |
+
var kw, headingCount = api.Headings.getRealHeadingCount(),
|
2387 |
+
content = eventInfo.text,
|
2388 |
+
raw = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ? api.Words.words( self.settings.content.val() ) : api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
|
2389 |
+
|
2390 |
+
// Get length of title field.
|
2391 |
+
titleLength = self.settings.title.val().length;
|
2392 |
+
|
2393 |
+
// Get length of description field.
|
2394 |
+
descriptionLength = self.settings.description.val().length;
|
2395 |
+
|
2396 |
+
// Set the placeholder attribute once the keyword has been obtained.
|
2397 |
+
kw = api.Keywords.recommendedKeywords( raw, 1 );
|
2398 |
+
if ( ! _.isUndefined( kw ) && ! _.isUndefined( kw[0] ) ) api.Keywords.setPlaceholder( kw[0][0] );
|
2399 |
+
|
2400 |
+
// Set the default report items.
|
2401 |
+
_( report ).extend({
|
2402 |
+
bgseo_meta : {
|
2403 |
+
title : {
|
2404 |
+
length : titleLength,
|
2405 |
+
lengthScore : api.Title.titleScore( titleLength ),
|
2406 |
+
},
|
2407 |
+
description : {
|
2408 |
+
length : descriptionLength,
|
2409 |
+
lengthScore : api.Description.descriptionScore( descriptionLength ),
|
2410 |
+
keywordUsage : api.Description.keywords(),
|
2411 |
+
},
|
2412 |
+
titleKeywordUsage : {
|
2413 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
2414 |
+
},
|
2415 |
+
descKeywordUsage : {
|
2416 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
2417 |
+
},
|
2418 |
+
sectionScore : {},
|
2419 |
+
sectionStatus : {},
|
2420 |
+
},
|
2421 |
+
|
2422 |
+
bgseo_visibility : {
|
2423 |
+
robotIndex : {
|
2424 |
+
lengthScore: api.Robots.indexScore(),
|
2425 |
+
},
|
2426 |
+
robotFollow : {
|
2427 |
+
lengthScore: api.Robots.followScore(),
|
2428 |
+
},
|
2429 |
+
sectionScore : {},
|
2430 |
+
sectionStatus : {},
|
2431 |
+
},
|
2432 |
+
|
2433 |
+
bgseo_keywords : {
|
2434 |
+
|
2435 |
+
keywordPhrase: {
|
2436 |
+
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
|
2437 |
+
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
|
2438 |
+
},
|
2439 |
+
keywordTitle : {
|
2440 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
2441 |
+
},
|
2442 |
+
keywordDescription : {
|
2443 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
2444 |
+
},
|
2445 |
+
keywordContent : {
|
2446 |
+
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
|
2447 |
+
},
|
2448 |
+
keywordHeadings : {
|
2449 |
+
length : api.Headings.keywords( headingCount ),
|
2450 |
+
lengthScore : api.Keywords.headingScore( api.Headings.keywords( headingCount ) ),
|
2451 |
+
},
|
2452 |
+
image : {
|
2453 |
+
length : report.rawstatistics.imageCount,
|
2454 |
+
lengthScore : api.ContentAnalysis.seoImageLengthScore( report.rawstatistics.imageCount ),
|
2455 |
+
},
|
2456 |
+
headings : headingCount,
|
2457 |
+
wordCount : {
|
2458 |
+
length : self.getWordCount(),
|
2459 |
+
lengthScore : api.ContentAnalysis.seoContentLengthScore( self.getWordCount() ),
|
2460 |
+
},
|
2461 |
+
sectionScore: {},
|
2462 |
+
sectionStatus: {},
|
2463 |
+
},
|
2464 |
+
|
2465 |
+
textstatistics : {
|
2466 |
+
recommendedKeywords : kw,
|
2467 |
+
recommendedCount : api.Keywords.getRecommendedCount( raw ),
|
2468 |
+
keywordDensity : api.Keywords.keywordDensity( content, api.Keywords.getKeyword() ),
|
2469 |
+
},
|
2470 |
+
|
2471 |
+
});
|
2472 |
+
}
|
2473 |
+
|
2474 |
+
// Listen to changes to the SEO Title and update report.
|
2475 |
+
if ( eventInfo.titleLength ) {
|
2476 |
+
_( report.bgseo_meta.title ).extend({
|
2477 |
+
length : eventInfo.titleLength,
|
2478 |
+
lengthScore : api.Title.titleScore( eventInfo.titleLength ),
|
2479 |
+
});
|
2480 |
+
|
2481 |
+
_( report.bgseo_meta.titleKeywordUsage ).extend({
|
2482 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
2483 |
+
});
|
2484 |
+
|
2485 |
+
_( report.bgseo_keywords.keywordTitle ).extend({
|
2486 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
2487 |
+
});
|
2488 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
2489 |
+
}
|
2490 |
+
|
2491 |
+
// Listen to changes to the SEO Description and update report.
|
2492 |
+
if ( eventInfo.descLength ) {
|
2493 |
+
|
2494 |
+
_( report.bgseo_meta.description ).extend({
|
2495 |
+
length : eventInfo.descLength,
|
2496 |
+
lengthScore: api.Description.descriptionScore( eventInfo.descLength ),
|
2497 |
+
});
|
2498 |
+
|
2499 |
+
_( report.bgseo_meta.descKeywordUsage ).extend({
|
2500 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
2501 |
+
});
|
2502 |
+
|
2503 |
+
_( report.bgseo_keywords.keywordDescription ).extend({
|
2504 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
2505 |
+
});
|
2506 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
2507 |
+
}
|
2508 |
+
|
2509 |
+
// Listen for changes to noindex/index and update report.
|
2510 |
+
if ( eventInfo.robotIndex ) {
|
2511 |
+
_( report.bgseo_visibility.robotIndex ).extend({
|
2512 |
+
lengthScore : eventInfo.robotIndex,
|
2513 |
+
});
|
2514 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
2515 |
+
}
|
2516 |
+
|
2517 |
+
// Listen for changes to nofollow/follow and update report.
|
2518 |
+
if ( eventInfo.robotFollow ) {
|
2519 |
+
_( report.bgseo_visibility.robotFollow ).extend({
|
2520 |
+
lengthScore : eventInfo.robotFollow,
|
2521 |
+
});
|
2522 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
2523 |
+
}
|
2524 |
+
}
|
2525 |
+
|
2526 |
+
// Send the final analysis to display the report.
|
2527 |
+
self.settings.content.trigger( 'bgseo-report', [ report ] );
|
2528 |
+
});
|
2529 |
+
},
|
2530 |
+
|
2531 |
+
/**
|
2532 |
+
* Get's the current report that's generated for output.
|
2533 |
+
*
|
2534 |
+
* This is used for debugging, and to also obtain the current report in
|
2535 |
+
* other classes to perform scoring, analysis, and status indicator updates.
|
2536 |
+
*
|
2537 |
+
* @since 1.3.1
|
2538 |
+
*
|
2539 |
+
* @returns {Object} report The report data that's currently displayed.
|
2540 |
+
*/
|
2541 |
+
get : function( key ) {
|
2542 |
+
var data = {};
|
2543 |
+
if ( _.isUndefined( key ) ) {
|
2544 |
+
data = report;
|
2545 |
+
} else {
|
2546 |
+
data = _.pickDeep( report, key );
|
2547 |
+
}
|
2548 |
+
|
2549 |
+
return data;
|
2550 |
+
},
|
2551 |
+
};
|
2552 |
+
|
2553 |
+
self = api.Report;
|
2554 |
+
|
2555 |
+
})( jQuery );
|
2556 |
+
|
2557 |
+
var BOLDGRID = BOLDGRID || {};
|
2558 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
2559 |
+
|
2560 |
+
( function ( $ ) {
|
2561 |
+
|
2562 |
+
'use strict';
|
2563 |
+
|
2564 |
+
var self, report, api;
|
2565 |
+
|
2566 |
+
api = BOLDGRID.SEO;
|
2567 |
+
report = api.report;
|
2568 |
+
|
2569 |
+
|
2570 |
+
|
2571 |
+
/**
|
2572 |
+
* BoldGrid SEO Robots.
|
2573 |
+
*
|
2574 |
+
* This is responsible for the noindex and nofollow checkbox
|
2575 |
+
* listeners, and returning status/scores for each.
|
2576 |
+
*
|
2577 |
+
* @since 1.3.1
|
2578 |
+
*/
|
2579 |
+
api.Robots = {
|
2580 |
+
|
2581 |
+
/**
|
2582 |
+
* Initialize BoldGrid SEO Robots.
|
2583 |
+
*
|
2584 |
+
* @since 1.3.1
|
2585 |
+
*/
|
2586 |
+
init : function () {
|
2587 |
+
$( document ).ready( self.onReady );
|
2588 |
+
},
|
2589 |
+
|
2590 |
+
/**
|
2591 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
2592 |
+
*
|
2593 |
+
* @since 1.3.1
|
2594 |
+
*/
|
2595 |
+
onReady : function() {
|
2596 |
+
self.getSettings();
|
2597 |
+
self._index();
|
2598 |
+
self._follow();
|
2599 |
+
},
|
2600 |
+
|
2601 |
+
/**
|
2602 |
+
* Cache selectors
|
2603 |
+
*
|
2604 |
+
* @since 1.3.1
|
2605 |
+
*/
|
2606 |
+
getSettings : function() {
|
2607 |
+
self.settings = {
|
2608 |
+
indexInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]' ),
|
2609 |
+
noIndex : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]' ),
|
2610 |
+
followInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]' ),
|
2611 |
+
noFollow : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]' ),
|
2612 |
+
};
|
2613 |
+
},
|
2614 |
+
|
2615 |
+
/**
|
2616 |
+
* Sets up event listener for index/noindex radios.
|
2617 |
+
*
|
2618 |
+
* Listens for changes being made on the radios, and then
|
2619 |
+
* triggers the reporter to be updated with new status/score.
|
2620 |
+
*
|
2621 |
+
* @since 1.3.1
|
2622 |
+
*/
|
2623 |
+
_index : function() {
|
2624 |
+
self.settings.indexInput.on( 'change', function() {
|
2625 |
+
$( this ).trigger( 'bgseo-analysis', [{ 'robotIndex': self.indexScore() }] );
|
2626 |
+
});
|
2627 |
+
},
|
2628 |
+
|
2629 |
+
/**
|
2630 |
+
* Gets score of index/noindex status.
|
2631 |
+
*
|
2632 |
+
* Checks if index/noindex is checked and returns appropriate
|
2633 |
+
* status message and indicator.
|
2634 |
+
*
|
2635 |
+
* @since 1.3.1
|
2636 |
+
* @returns {Object} Contains status indicator color and message to update.
|
2637 |
+
*/
|
2638 |
+
indexScore : function() {
|
2639 |
+
var msg;
|
2640 |
+
|
2641 |
+
// Index radio is selected.
|
2642 |
+
msg = {
|
2643 |
+
status: 'green',
|
2644 |
+
msg: _bgseoContentAnalysis.noIndex.good,
|
2645 |
+
};
|
2646 |
+
|
2647 |
+
// Noindex radio is selected.
|
2648 |
+
if ( self.settings.noIndex.is( ':checked' ) ) {
|
2649 |
+
msg = {
|
2650 |
+
status: 'red',
|
2651 |
+
msg: _bgseoContentAnalysis.noIndex.bad,
|
2652 |
+
};
|
2653 |
+
}
|
2654 |
+
|
2655 |
+
return msg;
|
2656 |
+
},
|
2657 |
+
|
2658 |
+
/**
|
2659 |
+
* Sets up event listener for follow/nofollow radios.
|
2660 |
+
*
|
2661 |
+
* Listens for changes being made on the radios, and then
|
2662 |
+
* triggers the reporter to be updated with new status/score.
|
2663 |
+
*
|
2664 |
+
* @since 1.3.1
|
2665 |
+
*/
|
2666 |
+
_follow : function() {
|
2667 |
+
// Listen for changes to input value.
|
2668 |
+
self.settings.followInput.on( 'change', function() {
|
2669 |
+
$( this ).trigger( 'bgseo-analysis', [{ 'robotFollow': self.followScore() }] );
|
2670 |
+
});
|
2671 |
+
},
|
2672 |
+
|
2673 |
+
/**
|
2674 |
+
* Gets score of follow/nofollow status.
|
2675 |
+
*
|
2676 |
+
* Checks if follow or nofollow is checked, and returns appropriate
|
2677 |
+
* status message and indicator.
|
2678 |
+
*
|
2679 |
+
* @since 1.3.1
|
2680 |
+
* @returns {Object} Contains status indicator color and message to update.
|
2681 |
+
*/
|
2682 |
+
followScore : function() {
|
2683 |
+
var msg = {
|
2684 |
+
status: 'green',
|
2685 |
+
msg: _bgseoContentAnalysis.noFollow.good,
|
2686 |
+
};
|
2687 |
+
|
2688 |
+
if ( self.settings.noFollow.is( ':checked' ) ) {
|
2689 |
+
msg = {
|
2690 |
+
status: 'yellow',
|
2691 |
+
msg: _bgseoContentAnalysis.noFollow.bad,
|
2692 |
+
};
|
2693 |
+
}
|
2694 |
+
|
2695 |
+
return msg;
|
2696 |
+
},
|
2697 |
+
};
|
2698 |
+
|
2699 |
+
self = api.Robots;
|
2700 |
+
|
2701 |
+
})( jQuery );
|
2702 |
+
|
2703 |
+
( function ( $ ) {
|
2704 |
+
|
2705 |
+
'use strict';
|
2706 |
+
|
2707 |
+
var self, report, api;
|
2708 |
+
|
2709 |
+
api = BOLDGRID.SEO;
|
2710 |
+
report = api.report;
|
2711 |
+
|
2712 |
+
/**
|
2713 |
+
* BoldGrid SEO Sections.
|
2714 |
+
*
|
2715 |
+
* This is responsible for section related statuses and modifications.
|
2716 |
+
*
|
2717 |
+
* @since 1.3.1
|
2718 |
+
*/
|
2719 |
+
api.Sections = {
|
2720 |
+
|
2721 |
+
/**
|
2722 |
+
* Gets the status for a section.
|
2723 |
+
*
|
2724 |
+
* This will get the status based on the scores received for each
|
2725 |
+
* section and return the status color as the report is updated.
|
2726 |
+
*
|
2727 |
+
* @since 1.3.1
|
2728 |
+
*
|
2729 |
+
* @param {Object} sectionScores The scores for the section.
|
2730 |
+
*
|
2731 |
+
* @returns {string} status The status color to assign to the section.
|
2732 |
+
*/
|
2733 |
+
status : function( sectionScores ) {
|
2734 |
+
// Default status is set to green.
|
2735 |
+
var status = 'green';
|
2736 |
+
|
2737 |
+
// Check if we have any red or yellow statuses and update as needed.
|
2738 |
+
if ( sectionScores.red > 0 ) {
|
2739 |
+
status = 'red';
|
2740 |
+
} else if ( sectionScores.yellow > 0 ) {
|
2741 |
+
status = 'yellow';
|
2742 |
+
}
|
2743 |
+
|
2744 |
+
return status;
|
2745 |
+
},
|
2746 |
+
|
2747 |
+
/**
|
2748 |
+
* Gets the score and status of a section.
|
2749 |
+
*
|
2750 |
+
* This is responsible for getting the count of statuses that
|
2751 |
+
* are set for each item in the report for a section. It will
|
2752 |
+
* return the data that is added to the report..
|
2753 |
+
*
|
2754 |
+
* @since 1.3.1
|
2755 |
+
*
|
2756 |
+
* @param {Object} section The section to get a score for.
|
2757 |
+
*
|
2758 |
+
* @returns {Object} data Contains the section status scores and section status.
|
2759 |
+
*/
|
2760 |
+
score : function( section ) {
|
2761 |
+
|
2762 |
+
var sectionScores, score, data;
|
2763 |
+
|
2764 |
+
// Set default counters for each status.
|
2765 |
+
sectionScores = { red: 0, green : 0, yellow : 0 };
|
2766 |
+
|
2767 |
+
// Get the count of scores in object by status.
|
2768 |
+
score = _( section ).countBy( function( items ) {
|
2769 |
+
return ! _.isUndefined( items.lengthScore ) && 'sectionScore' !== _.property( 'sectionScore' )( section ) ? items.lengthScore.status : '';
|
2770 |
+
});
|
2771 |
+
|
2772 |
+
// Update the object with the new count.
|
2773 |
+
_( score ).each( function( value, key ) {
|
2774 |
+
if ( _.has( sectionScores , key ) ) {
|
2775 |
+
sectionScores[key] = value;
|
2776 |
+
}
|
2777 |
+
});
|
2778 |
+
|
2779 |
+
// Update the section's score and status.
|
2780 |
+
data = {
|
2781 |
+
sectionScore : sectionScores,
|
2782 |
+
sectionStatus: self.status( sectionScores ),
|
2783 |
+
};
|
2784 |
+
|
2785 |
+
return data;
|
2786 |
+
},
|
2787 |
+
|
2788 |
+
removeStatus : function( selector ) {
|
2789 |
+
selector.removeClass( 'red yellow green' );
|
2790 |
+
},
|
2791 |
+
|
2792 |
+
navHighlight : function( report ) {
|
2793 |
+
_.each( butterbean.models.sections, function( item ) {
|
2794 |
+
var selector,
|
2795 |
+
manager = item.get( 'manager' ),
|
2796 |
+
name = item.get( 'name' );
|
2797 |
+
|
2798 |
+
selector = $( '[href="#butterbean-' + manager + '-section-' + name + '"]' ).closest( 'li' );
|
2799 |
+
self.removeStatus( selector );
|
2800 |
+
selector.addClass( report[name].sectionStatus );
|
2801 |
+
});
|
2802 |
+
},
|
2803 |
+
overviewStatus : function( report ) {
|
2804 |
+
var selector = $( "#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')" );
|
2805 |
+
self.removeStatus( selector );
|
2806 |
+
selector.addClass( 'overview-status ' + report.bgseo_keywords.overview.status );
|
2807 |
+
}
|
2808 |
+
};
|
2809 |
+
|
2810 |
+
self = api.Sections;
|
2811 |
+
|
2812 |
+
})( jQuery );
|
2813 |
+
|
2814 |
+
( function ( $ ) {
|
2815 |
+
|
2816 |
+
'use strict';
|
2817 |
+
|
2818 |
+
var self, report, api;
|
2819 |
+
|
2820 |
+
api = BOLDGRID.SEO;
|
2821 |
+
report = api.report;
|
2822 |
+
|
2823 |
+
/**
|
2824 |
+
* BoldGrid SEO Title.
|
2825 |
+
*
|
2826 |
+
* This is responsible for the SEO Title Grading.
|
2827 |
+
*
|
2828 |
+
* @since 1.3.1
|
2829 |
+
*/
|
2830 |
+
api.Title = {
|
2831 |
+
|
2832 |
+
/**
|
2833 |
+
* Initialize SEO Title Analysis.
|
2834 |
+
*
|
2835 |
+
* @since 1.3.1
|
2836 |
+
*/
|
2837 |
+
init : function () {
|
2838 |
+
$( document ).ready( self.onReady );
|
2839 |
+
},
|
2840 |
+
|
2841 |
+
/**
|
2842 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
2843 |
+
*
|
2844 |
+
* @since 1.3.1
|
2845 |
+
*/
|
2846 |
+
onReady : function() {
|
2847 |
+
self.getSettings();
|
2848 |
+
self._title();
|
2849 |
+
},
|
2850 |
+
|
2851 |
+
/**
|
2852 |
+
* Cache selectors
|
2853 |
+
*
|
2854 |
+
* @since 1.3.1
|
2855 |
+
*/
|
2856 |
+
getSettings : function() {
|
2857 |
+
self.settings = {
|
2858 |
+
title : $( '#boldgrid-seo-field-meta_title' ),
|
2859 |
+
};
|
2860 |
+
},
|
2861 |
+
|
2862 |
+
/**
|
2863 |
+
* Gets the SEO Title.
|
2864 |
+
*
|
2865 |
+
* @since 1.3.1
|
2866 |
+
*
|
2867 |
+
* @returns {Object} title Contains wrapped set with BoldGrid SEO Title.
|
2868 |
+
*/
|
2869 |
+
getTitle : function() {
|
2870 |
+
return self.settings.title;
|
2871 |
+
},
|
2872 |
+
|
2873 |
+
/**
|
2874 |
+
* Sets up event listener for changes made to the SEO Title.
|
2875 |
+
*
|
2876 |
+
* Listens for changes being made to the SEO Title, and then
|
2877 |
+
* triggers the reporter to be updated with new status/score.
|
2878 |
+
*
|
2879 |
+
* @since 1.3.1
|
2880 |
+
*/
|
2881 |
+
_title: function() {
|
2882 |
+
// Listen for changes to input value.
|
2883 |
+
self.settings.title.on( 'input propertychange paste', _.debounce( function() {
|
2884 |
+
self.settings.title.trigger( 'bgseo-analysis', [{ titleLength : self.settings.title.val().length }] );
|
2885 |
+
}, 1000 ) );
|
2886 |
+
},
|
2887 |
+
|
2888 |
+
/**
|
2889 |
+
* Gets score of the SEO Title.
|
2890 |
+
*
|
2891 |
+
* Checks the length provided and returns a score for the SEO
|
2892 |
+
* title. This score is based on character count.
|
2893 |
+
*
|
2894 |
+
* @since 1.3.1
|
2895 |
+
*
|
2896 |
+
* @param {Number} titleLength The length of the title to generate score for.
|
2897 |
+
*
|
2898 |
+
* @returns {Object} msg Contains status indicator color and message to update.
|
2899 |
+
*/
|
2900 |
+
titleScore: function( titleLength ) {
|
2901 |
+
var msg = {}, title;
|
2902 |
+
|
2903 |
+
title = _bgseoContentAnalysis.seoTitle.length;
|
2904 |
+
|
2905 |
+
// No title entered.
|
2906 |
+
if ( titleLength === 0 ) {
|
2907 |
+
msg = {
|
2908 |
+
status: 'red',
|
2909 |
+
msg: title.badEmpty,
|
2910 |
+
};
|
2911 |
+
}
|
2912 |
+
|
2913 |
+
// Title is 1-30 characters.
|
2914 |
+
if ( titleLength.isBetween( 0, title.okScore + 1 ) ) {
|
2915 |
+
msg = {
|
2916 |
+
status: 'yellow',
|
2917 |
+
msg: title.ok,
|
2918 |
+
};
|
2919 |
+
}
|
2920 |
+
|
2921 |
+
// Title is 30-70 characters.
|
2922 |
+
if ( titleLength.isBetween( title.okScore - 1, title.goodScore + 1 ) ) {
|
2923 |
+
msg = {
|
2924 |
+
status: 'green',
|
2925 |
+
msg: title.good,
|
2926 |
+
};
|
2927 |
+
}
|
2928 |
+
|
2929 |
+
// Title is grater than 70 characters.
|
2930 |
+
if ( titleLength > title.goodScore ) {
|
2931 |
+
msg = {
|
2932 |
+
status: 'red',
|
2933 |
+
msg: title.badLong,
|
2934 |
+
};
|
2935 |
+
}
|
2936 |
+
|
2937 |
+
return msg;
|
2938 |
+
},
|
2939 |
+
|
2940 |
+
/**
|
2941 |
+
* Get count of keywords used in the title.
|
2942 |
+
*
|
2943 |
+
* This checks the title for keyword frequency.
|
2944 |
+
*
|
2945 |
+
* @since 1.3.1
|
2946 |
+
*
|
2947 |
+
* @param {String} text (Optional) The text to search for keyword in.
|
2948 |
+
* @param {String} keyword (Optional) The keyword to search for.
|
2949 |
+
*
|
2950 |
+
* @returns {Number} Count of times keyword appears in text.
|
2951 |
+
*/
|
2952 |
+
keywords : function( text, keyword ) {
|
2953 |
+
if ( 0 === arguments.length ) {
|
2954 |
+
keyword = api.Keywords.getKeyword();
|
2955 |
+
text = self.getTitle().val();
|
2956 |
+
} else if ( 1 === arguments.length ) {
|
2957 |
+
keyword = api.Keywords.getKeyword();
|
2958 |
+
}
|
2959 |
+
|
2960 |
+
// Normalize user input.
|
2961 |
+
text = text.toLowerCase();
|
2962 |
+
|
2963 |
+
return text.occurences( keyword );
|
2964 |
+
},
|
2965 |
+
};
|
2966 |
+
|
2967 |
+
self = api.Title;
|
2968 |
+
|
2969 |
+
})( jQuery );
|
2970 |
+
|
2971 |
+
( function ( $ ) {
|
2972 |
+
|
2973 |
+
'use strict';
|
2974 |
+
|
2975 |
+
var self, report, api;
|
2976 |
+
|
2977 |
+
api = BOLDGRID.SEO;
|
2978 |
+
report = api.report;
|
2979 |
+
|
2980 |
+
/**
|
2981 |
+
* BoldGrid SEO Tooltips.
|
2982 |
+
*
|
2983 |
+
* This will add the neccessary functionality for tooltips to be displayed
|
2984 |
+
* for each control we create and display.
|
2985 |
+
*
|
2986 |
+
* @since 1.3.1
|
2987 |
+
*/
|
2988 |
+
api.Tooltips = {
|
2989 |
+
|
2990 |
+
/**
|
2991 |
+
* Initializes BoldGrid SEO Tooltips.
|
2992 |
+
*
|
2993 |
+
* @since 1.3.1
|
2994 |
+
*/
|
2995 |
+
init : function () {
|
2996 |
+
$( document ).ready( self.onReady );
|
2997 |
+
},
|
2998 |
+
|
2999 |
+
/**
|
3000 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
3001 |
+
*
|
3002 |
+
* @since 1.3.1
|
3003 |
+
*/
|
3004 |
+
onReady : function() {
|
3005 |
+
self.getSettings();
|
3006 |
+
self.hideTooltips();
|
3007 |
+
self._enableTooltips();
|
3008 |
+
self._toggleTooltip();
|
3009 |
+
},
|
3010 |
+
|
3011 |
+
/**
|
3012 |
+
* Cache selectors
|
3013 |
+
*
|
3014 |
+
* @since 1.3.1
|
3015 |
+
*/
|
3016 |
+
getSettings : function() {
|
3017 |
+
self.settings = {
|
3018 |
+
description : $( '.butterbean-control .butterbean-description' ),
|
3019 |
+
tooltip : $( '<span />', { 'class' : 'bgseo-tooltip dashicons dashicons-editor-help', 'aria-expanded' : 'false' }),
|
3020 |
+
onClick : $( '.butterbean-label, .bgseo-tooltip' ),
|
3021 |
+
};
|
3022 |
+
},
|
3023 |
+
|
3024 |
+
/**
|
3025 |
+
* Toggle Tooltips
|
3026 |
+
*
|
3027 |
+
* This sets up the event listener for clicks on tooltips or control labels,
|
3028 |
+
* which will hide and show the description of the control for the user.
|
3029 |
+
*
|
3030 |
+
* @since 1.3.1
|
3031 |
+
*/
|
3032 |
+
_toggleTooltip : function() {
|
3033 |
+
self.settings.onClick.on( 'click', function( e ) {
|
3034 |
+
self.toggleTooltip( e );
|
3035 |
+
});
|
3036 |
+
},
|
3037 |
+
|
3038 |
+
/**
|
3039 |
+
* Enables tooltips for any controls that utilize the description field.
|
3040 |
+
*
|
3041 |
+
* @since 1.3.1
|
3042 |
+
*/
|
3043 |
+
_enableTooltips : function() {
|
3044 |
+
self.settings.description.prev().append( self.settings.tooltip );
|
3045 |
+
},
|
3046 |
+
|
3047 |
+
/**
|
3048 |
+
* This handles the toggle of the tooltip open/close.
|
3049 |
+
*
|
3050 |
+
* @param {Object} e Selector passed from click event.
|
3051 |
+
*
|
3052 |
+
* @since 1.3.1
|
3053 |
+
*/
|
3054 |
+
toggleTooltip : function( e ) {
|
3055 |
+
$( e.currentTarget ).next( '.butterbean-description' ).slideToggle();
|
3056 |
+
},
|
3057 |
+
|
3058 |
+
/**
|
3059 |
+
* This hides all tooltips when api.Tooltips is initialized.
|
3060 |
+
*
|
3061 |
+
* @since 1.3.1
|
3062 |
+
*/
|
3063 |
+
hideTooltips : function() {
|
3064 |
+
self.settings.description.hide();
|
3065 |
+
},
|
3066 |
+
};
|
3067 |
+
|
3068 |
+
self = api.Tooltips;
|
3069 |
+
|
3070 |
+
})( jQuery );
|
3071 |
+
|
3072 |
+
var BOLDGRID = BOLDGRID || {};
|
3073 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
3074 |
+
|
3075 |
+
( function ( $ ) {
|
3076 |
+
|
3077 |
+
'use strict';
|
3078 |
+
|
3079 |
+
var api;
|
3080 |
+
|
3081 |
+
api = BOLDGRID.SEO;
|
3082 |
+
|
3083 |
+
/**
|
3084 |
+
* BoldGrid SEO Initialize.
|
3085 |
+
*
|
3086 |
+
* This initializes BoldGrid SEO.
|
3087 |
+
*
|
3088 |
+
* @since 1.3.1
|
3089 |
+
*/
|
3090 |
+
api.Init = {
|
3091 |
+
|
3092 |
+
/**
|
3093 |
+
* Initialize Utilities.
|
3094 |
+
*
|
3095 |
+
* @since 1.3.1
|
3096 |
+
*/
|
3097 |
+
load : function () {
|
3098 |
+
_.each( api, function( obj ) {
|
3099 |
+
return obj.init && obj.init();
|
3100 |
+
});
|
3101 |
+
},
|
3102 |
+
};
|
3103 |
+
|
3104 |
+
})( jQuery );
|
3105 |
+
|
3106 |
+
BOLDGRID.SEO.Init.load();
|
assets/js/bgseo.min.js
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO={report:{bgseo_visibility:{},bgseo_keywords:{},bgseo_meta:{},rawstatistics:{},textstatistics:{}}},function(e){"use strict";butterbean.views.register_control("dashboard",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-dashboard"),_.bindAll(this,"render"),this.model.bind("change",this.render)},results:function(e){var t={};return _.each(e,function(e){_.extend(t,e)}),t},setAnalysis:function(e,t){var n,o=this.model.get("section"),s=_.pick(t,o);this.sectionReport=this.results(s),this.model.set("analysis",this.sectionReport),_(t).each(function(e){_.isUndefined(e.sectionScore)||(n=BOLDGRID.SEO.Sections.score(e),_(e).extend(n))}),_(t.bgseo_keywords).extend({overview:{score:BOLDGRID.SEO.Dashboard.overviewScore(t)}}),_(t.bgseo_keywords.overview).extend({status:BOLDGRID.SEO.Dashboard.overviewStatus(t.bgseo_keywords.overview.score)}),BOLDGRID.SEO.Sections.navHighlight(t),BOLDGRID.SEO.Sections.overviewStatus(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery),function(e){"use strict";butterbean.views.register_control("keywords",{tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){e(window).bind("bgseo-report",_.bind(this.setAnalysis,this)),this.bgseo_template=wp.template("butterbean-control-keywords"),_.bindAll(this,"render"),this.model.bind("change",this.render)},setAnalysis:function(e,t){this.model.set(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.bgseo_template(this.model.toJSON())),this}})}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Util={init:function(){_.mixin({modifyObject:function(e,t){return _.object(_.map(e,function(e,n){return[n,t(e)]}))},pickDeep:function(e){var t={},n=Array.prototype.concat.apply(Array.prototype,Array.prototype.slice.call(arguments,1));return this.each(n,function(n){var o=n.split(".");n=o.shift(),n in e&&(o.length>0?t[n]?_.extend(t[n],_.pickDeep(e[n],o.join("."))):t[n]=_.pickDeep(e[n],o.join(".")):t[n]=e[n])}),t}}),Number.prototype.isBetween||(Number.prototype.isBetween=function(e,t){_.isUndefined(e)&&(e=0),_.isUndefined(t)&&(t=0);var n=Math.max(e,t),o=Math.min(e,t);return this>o&&this<n}),Number.prototype.rounded||(Number.prototype.rounded=function(e){_.isUndefined(e)&&(e=0);var t=Math.pow(10,e),n=Math.round(this*t)/t;return n}),String.prototype.printf||(String.prototype.printf=function(){for(var e=this,t=0;/%s/.test(e);)e=e.replace("%s",arguments[t++]);return e}),String.prototype.occurences||(String.prototype.occurences=function(e,t){if(e+="",e.length<=0)return this.length+1;for(var n=0,o=0,s=t?1:e.length;;){if(o=this.indexOf(e,o),!(o>=0))break;++n,o+=s}return n})}},t=o.Util}(jQuery),function(){"use strict";var e,t;t=BOLDGRID.SEO,t.Words={init:function(t){var n,o;if(t)for(n in t)t.hasOwnProperty(n)&&(e.settings[n]=t[n]);o=e.settings.l10n.shortcodes,o&&o.length&&(e.settings.shortcodesRegExp=new RegExp("\\[\\/?(?:"+o.join("|")+")[^\\]]*?\\]","g"))},settings:{HTMLRegExp:/<\/?[a-z][^>]*?>/gi,HTMLcommentRegExp:/<!--[\s\S]*?-->/g,spaceRegExp:/ | /gi,HTMLEntityRegExp:/&\S+?;/g,connectorRegExp:/--|\u2014/g,removeRegExp:new RegExp(["[","!-@[-`{-~","-¿×÷"," -⯿","⸀-","]"].join(""),"g"),astralRegExp:/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,wordsRegExp:/.+?\s+/g,characters_excluding_spacesRegExp:/\S/g,characters_including_spacesRegExp:/[^\f\n\r\t\v\u00AD\u2028\u2029]/g,l10n:window.wordCountL10n||{}},words:function(t,n){var o=0;return n=n||e.settings.l10n.type,"characters_excluding_spaces"!==n&&"characters_including_spaces"!==n&&(n="words"),t&&(t+="\n",t=t.replace(e.settings.HTMLRegExp,"\n"),t=t.replace(e.settings.HTMLcommentRegExp,""),e.settings.shortcodesRegExp&&(t=t.replace(e.settings.shortcodesRegExp,"\n")),t=t.replace(e.settings.spaceRegExp," "),"words"===n?(t=t.replace(e.settings.HTMLEntityRegExp,""),t=t.replace(e.settings.connectorRegExp," "),t=t.replace(e.settings.removeRegExp,"")):(t=t.replace(e.settings.HTMLEntityRegExp,"a"),t=t.replace(e.settings.astralRegExp,"a")),t=t.match(e.settings[n+"RegExp"]),t&&(o=t)),o}},e=t.Words}(),function(e,t){e(function(){function n(){var e,n;e=!o||o.isHidden()?r.val():o.getContent({format:"raw"}),n=t.count(e),s=BOLDGRID.SEO.Words.words(e),n!==i&&r.trigger("bgseo-analysis",[{words:s,count:n}]),i=n}var o,s,r=e("#content"),i=(e("#wp-word-count").find(".word-count"),0);e(document).on("tinymce-editor-init",function(e,t){"content"===t.id&&(o=t,t.on("nodechange keyup",_.debounce(n,1e3)))}),r.on("input keyup",_.debounce(n,1e3)),n()})}(jQuery,new wp.utils.WordCounter),function(e){"use strict";var t;BOLDGRID.SEO.Admin={init:function(){e(document).ready(function(){t._setWordCounts()})},wordCount:function(n){var o=n.attr("maxlength"),s=e("<span />",{"class":"boldgrid-seo-meta-counter",style:"font-weight: bold"}),r=e("<div />",{"class":"boldgrid-seo-meta-countdown boldgrid-seo-meta-extra",html:" characters left"});o&&n.removeAttr("maxlength").after(r.prepend(s)).on("keyup focus",function(){t.setCounter(s,n,o)}),t.setCounter(s,n,o)},setCounter:function(e,t,n){var o=t.val(),s=o.length;e.html(n-s),"boldgrid-seo-field-meta_description"===t.context.id?s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoDescription.length.okScore)?e.css({color:"#FBBC05"}):s.isBetween(_bgseoContentAnalysis.seoDescription.length.okScore-1,_bgseoContentAnalysis.seoDescription.length.goodScore+1)?e.css({color:"#34A853"}):e.css({color:"black"}):s>n?e.css({color:"#EA4335"}):s.isBetween(0,_bgseoContentAnalysis.seoTitle.length.okScore)?e.css({color:"#FBBC05"}):s>_bgseoContentAnalysis.seoTitle.length.okScore-1?e.css({color:"#34A853"}):e.css({color:"black"})},_setWordCounts:function(){e("#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description").each(function(){t.wordCount(e(this))})}},t=BOLDGRID.SEO.Admin}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.TinyMCE={init:function(){t.onloadContent(),e(document).ready(function(){t.editorChange()})},onloadContent:function(){e("#content.wp-editor-area[aria-hidden=false]");e(window).on("load bgseo-media-inserted",function(){var n=t.getContent();t.getRenderedContent(),_.defer(function(){e("#content").trigger("bgseo-analysis",[n])})})},getContent:function(){var n;tinymce.ActiveEditor?n=tinyMCE.get(wpActiveEditor).getContent():(n=e("#content").val(),n=n.replace(/\r?\n|\r/g,""));var o=e.parseHTML(n);return n={raw:o,text:t.stripper(n.toLowerCase())}},getRenderedContent:function(){var s,r;r=e("#preview-action > .preview.button").attr("href"),e("#sample-permalink").length&&e.get(r,function(r){var i,a,d;d=e(r),i=d.find("h1"),a=d.find("h2"),s={h1Count:i.length-n.rawstatistics.h1Count,h1text:_.filter(o.Headings.getHeadingText(i),function(e){return!_.findWhere(n.rawstatistics.h1text,e)}),h2Count:a.length-n.rawstatistics.h2Count,h2text:_.filter(o.Headings.getHeadingText(a),function(e){return!_.findWhere(n.rawstatistics.h2text,e)})},_.extend(n,{rendered:s}),e("#content").trigger("bgseo-analysis",[t.getContent()])},"html")},editorChange:function(){var n,o;return e("#content.wp-editor-area").on("input propertychange paste nodechange",function(){o=e(this).attr("id"),n=t.wpContent(o)}),n},tmceChange:function(e){var n,o;return o=e.target.id,n=t.wpContent(o)},wpContent:function(n){var o={};switch(n){case"tinymce":tinymce.activeEditor&&(o=tinyMCE.get(wpActiveEditor).getContent());break;case"content":o=e("#content").val(),o=o.replace(/\r?\n|\r/g,"")}var s=e.parseHTML(o);o={raw:s,text:t.stripper(o.toLowerCase())},e("#content").trigger("bgseo-analysis",[o])},stripper:function(e){var t;return t=document.implementation.createHTMLDocument("New").body,t.innerHTML=e,t.textContent||t.innerText||" "}},t=o.TinyMCE}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.ContentAnalysis={seoContentLengthScore:function(e){var t,n,o={};return e=Number(e),t=_bgseoContentAnalysis.content.length,n=t.contentLength.printf(e)+" ",0===e&&(o={status:"red",msg:t.badEmpty}),e.isBetween(0,t.badShortScore)&&(o={status:"red",msg:n+t.badShort}),e.isBetween(t.badShortScore-1,t.okScore)&&(o={status:"yellow",msg:n+t.ok}),e>t.okScore-1&&(o={status:"green",msg:n+t.good}),o},seoImageLengthScore:function(e){var t={status:"green",msg:_bgseoContentAnalysis.image.length.good};return e||(t={status:"red",msg:_bgseoContentAnalysis.image.length.bad}),t},keywords:function(e){var t=o.Keywords.getKeyword();return e.occurences(t)}},t=o.ContentAnalysis}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Dashboard={overviewScore:function(e){var n,o=t.totalScore(e),s=_.size(butterbean.models.sections);return n=2*s,(o/n*100).rounded(2)},overviewStatus:function(e){var t;return t="green",e<40&&(t="red"),e.isBetween(39,76)&&(t="yellow"),t},getStatuses:function(e){var t={};return _.each(butterbean.models.sections,function(n){var o,s=n.get("name");o=e[s].sectionStatus,t[s]=o,_(t[s]).extend(o)}),t},assignNumbers:function(e){var n,o;return o=t.getStatuses(e),n=_.mapObject(o,function(e){var t;return"red"===e&&(t=0),"yellow"===e&&(t=1),"green"===e&&(t=2),t})},totalScore:function(e){var n,o=t.assignNumbers(e);return n=_(o).reduce(function(e,t){return e+t},0)}},t=o.Dashboard}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Description={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._description()},getSettings:function(){t.settings={description:e("#boldgrid-seo-field-meta_description")}},_description:function(){t.settings.description.on("input propertychange paste",_.debounce(function(){e(this).trigger("bgseo-analysis",[{descLength:t.settings.description.val().length}])},1e3))},getDescription:function(){return t.settings.description},descriptionScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoDescription.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(){var e,n;return e=o.Keywords.getKeyword(),n=t.getDescription().val(),n=n.toLowerCase(),n.occurences(e)}},t=o.Description}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Headings={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._checkbox()},getSettings:function(){t.settings={displayTitle:e('[name="boldgrid-display-post-title"]').last()}},_checkbox:function(){t.settings.displayTitle.on("change",_.debounce(function(){e(this).trigger("bgseo-analysis",[o.TinyMCE.getContent()])},1e3))},score:function(e){var n;return n={status:"green",msg:_bgseoContentAnalysis.headings.h1.good},e>1&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badMultiple}),e>1&&t.settings.displayTitle.is(":checked")&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badBoldgridTheme}),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.headings.h1.badEmpty}),n},keywords:function(e){var n={length:0},s=o.Keywords.getKeyword();if(_.isUndefined(e)&&(e={count:t.getRealHeadingCount()}),!_.isEmpty(e))return _(e.count).each(function(e,t){var o=e.text;_(o).each(function(e){n.length=Number(n.length)+Number(e.heading.occurences(s)*e.count)})}),n.length},getHeadingText:function(t){var n={};return n=_.countBy(t,function(t,n){return e.trim(e(t).text().toLowerCase())}),n=_.map(n,function(e,t){return!_(n).has({heading:t,count:e})&&{heading:t,count:e}})},getRealHeadingCount:function(){var e={};return _.isUndefined(n.rendered)?e=t.getContentHeadings():(e={count:{h1:{length:n.rendered.h1Count+n.rawstatistics.h1Count,text:_(n.rendered.h1text).union(n.rawstatistics.h1text)},h2:{length:n.rendered.h2Count+n.rawstatistics.h2Count,text:_(n.rendered.h2text).union(n.rawstatistics.h2text)}}},_(e).extend({lengthScore:t.score(e.count.h1.length)})),e},getContentHeadings:function(){var n,s,r,i;return n={count:{h1:{length:0,text:{}},h2:{length:0,text:{}}}},i=o.TinyMCE.getContent(),s=e(i.raw).find("h1"),r=e(i.raw).find("h2"),s.length||r.length?n={count:{h1:{length:s.length,text:t.getHeadingText(s)},h2:{length:r.length,text:t.getHeadingText(r)}}}:n}},t=o.Headings}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Keywords={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._keywords(),t.setPlaceholder()},getSettings:function(){t.settings={keyword:e("#bgseo-custom-keyword"),content:e("#content")}},_keywords:function(){t.settings.keyword.on("input propertychange paste",_.debounce(function(){var e={};t.settings.keyword.val().length;e={keywords:{title:{length:o.Title.keywords(),lengthScore:0},description:{length:o.Description.keywords(),lengthScore:0},keyword:t.getCustomKeyword()}},t.settings.keyword.trigger("bgseo-analysis",[e])},1e3))},setPlaceholder:function(e){t.settings.keyword.attr("placeholder",e)},keywordCount:function(e,t){var n;return n=e.split(t).length-1},phraseLength:function(e){return 0===e.length?0:(e=e.replace(/(^\s*)|(\s*$)/gi,""),e=e.replace(/[ ]{2,}/gi," "),e=e.replace(/\n /,"\n"),e.split(" ").length)},keywordDensity:function(e){var n,s,r,i;return i=t.getKeyword(),_.isUndefined(i)?0:(i=i.toLowerCase(),s=t.keywordCount(e,i),r=o.Report.getWordCount(),n=s/r*100,n=Math.round(10*n)/10)},normalizeWords:function(e){return e.replace("'","")},trim:function(e){return e.trim()},recommendedKeywords:function(n,o){var s,r=_bgseoContentAnalysis.stopWords,i={},a=[];if(!_.isEmpty(n)){r=r.split(",").map(t.trim),r=r.map(t.normalizeWords);for(var d=0;d<n.length;d++){var g=e.trim(n[d]).toLowerCase();!g||g.length<3||r.indexOf(g)>-1||(_.isUndefined(i[g])?(i[g]=a.length,a.push([g,1])):a[i[g]][1]++)}return a.sort(function(e,t){return t[1]-e[1]}),s=a.slice(0,o)}},getCustomKeyword:function(){return e.trim(t.settings.keyword.val()).toLowerCase()},getKeyword:function(){var s,r=o.TinyMCE.getContent();return t.getCustomKeyword().length?s=t.getCustomKeyword():_.isUndefined(n.textstatistics.recommendedKeywords)||_.isUndefined(n.textstatistics.recommendedKeywords[0])?_.isEmpty(e.trim(r.text))?s=void 0:t.recommendedKeywords(o.Words.words(r.raw),1):s=n.textstatistics.recommendedKeywords[0][0],s},getRecommendedCount:function(e){var n;return _.isUndefined(e)&&(e=!tinyMCE.activeEditor||tinyMCE.activeEditor.hidden?o.Words.words(t.settings.content.val()):o.Words.words(tinyMCE.activeEditor.getContent({format:"raw"}))),n=_.modifyObject(_bgseoContentAnalysis.keywords.recommendedCount,function(t){var n=Number(t/100*o.Words.words(e).length).rounded(0);return n>0?n:1})},score:function(){var e={};return e={title:t.titleScore(),description:t.descriptionScore()}},titleScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoTitle.keywordUsage.ok}),t},descriptionScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.bad}),e>1&&(t={status:"yellow",msg:_bgseoContentAnalysis.seoDescription.keywordUsage.ok}),t},contentScore:function(e){var n,o,s;return o=t.getRecommendedCount(),0===e&&(n={status:"red",msg:_bgseoContentAnalysis.content.keywordUsage.bad}),e.isBetween(o.min-1,o.max+1)&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.goodSingular:_bgseoContentAnalysis.content.keywordUsage.good.printf(o.min),n={status:"green",msg:s}),e<o.min&&0!==e&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okShortSingular:_bgseoContentAnalysis.content.keywordUsage.okShort.printf(o.min),n={status:"yellow",msg:s}),e>o.max&&(s=1===o.min?_bgseoContentAnalysis.content.keywordUsage.okLongSingular:_bgseoContentAnalysis.content.keywordUsage.okLong.printf(o.min),n={status:"red",msg:s}),n},headingScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.headings.keywordUsage.good},0===e&&(t={status:"red",msg:_bgseoContentAnalysis.headings.keywordUsage.bad}),e>3&&(t={status:"yellow",msg:_bgseoContentAnalysis.headings.keywordUsage.ok}),t},keywordPhraseScore:function(e){var t;return t={status:"green",msg:_bgseoContentAnalysis.keywords.keywordPhrase.good},1===e&&(t={status:"yellow",msg:_bgseoContentAnalysis.keywords.keywordPhrase.ok}),0===e&&(t={status:"red",msg:_bgseoContentAnalysis.keywords.keywordPhrase.bad}),t}},t=o.Keywords}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Readability={gradeLevel:function(e){var n,o={};return n=textstatistics(e).fleschKincaidReadingEase(),o=t.gradeAnalysis(n)},gradeAnalysis:function(e){var t,n={};return e>90&&(n={score:e,gradeLevel:"5th grade",explanation:"Very easy to read. Easily understood by an average 11-year-old student.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodHigh}}),e.isBetween(79,91)&&(n={score:e,gradeLevel:"6th grade",explanation:"Easy to read. Conversational English for consumers.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedHigh}}),e.isBetween(69,81)&&(n={score:e,gradeLevel:"7th grade",explanation:"Fairly easy to read.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodMedLow}}),e.isBetween(59,71)&&(n={score:e,gradeLevel:"8th & 9th",explanation:"Plain English. Easily understood by 13- to 15-year-old students.",lengthScore:{status:"green",msg:_bgseoContentAnalysis.readingEase.goodLow}}),e.isBetween(49,61)&&(n={score:e,gradeLevel:"10th to 12th",explanation:"Fairly difficult to read.",lengthScore:{status:"yellow",msg:_bgseoContentAnalysis.readingEase.ok}}),e.isBetween(29,51)&&(n={score:e,gradeLevel:"College Student",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badHigh}}),e<30&&(n={score:e,gradeLevel:"College Graduate",explanation:"Difficult to read.",lengthScore:{status:"red",msg:_bgseoContentAnalysis.readingEase.badLow}}),t=_bgseoContentAnalysis.readingEase.score.printf(e)+" ",n.lengthScore.msg=n.lengthScore.msg.replace(/^/,t),n}},t=o.Readability}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Report={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.generateReport()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title"),description:e("#boldgrid-seo-field-meta_description"),wordCounter:e("#wp-word-count .word-count"),content:e("#content")}},getWordCount:function(){return Number(t.settings.wordCounter.text())},generateReport:function(){_.isUndefined(t.settings)||e(document).on("bgseo-analysis",function(s,r){var i,a;if(i=t.settings.title.val().length,a=t.settings.description.val().length,r.words&&_(n.textstatistics).extend({recommendedKeywords:o.Keywords.recommendedKeywords(r.words,1),customKeyword:o.Keywords.getKeyword()}),r){if(r.raw){var d=r.raw,g=e(d).find("h1"),c=e(d).find("h2"),l={};l={h1Count:g.length,h1text:o.Headings.getHeadingText(g),h2Count:c.length,h2text:o.Headings.getHeadingText(c),imageCount:e(d).find("img").length},_(n.rawstatistics).extend(l)}if(r.keywords&&_(n.bgseo_keywords).extend({keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.TinyMCE.getContent().text))},keywordHeadings:{length:o.Headings.keywords(o.Headings.getRealHeadingCount()),lengthScore:o.Keywords.headingScore(o.Headings.keywords(o.Headings.getRealHeadingCount()))},customKeyword:r.keywords.keyword}),r.text){var u,y=o.Headings.getRealHeadingCount(),h=r.text,w=!tinyMCE.activeEditor||tinyMCE.activeEditor.hidden?o.Words.words(t.settings.content.val()):o.Words.words(tinyMCE.activeEditor.getContent({format:"raw"}));i=t.settings.title.val().length,a=t.settings.description.val().length,u=o.Keywords.recommendedKeywords(w,1),_.isUndefined(u)||_.isUndefined(u[0])||o.Keywords.setPlaceholder(u[0][0]),_(n).extend({bgseo_meta:{title:{length:i,lengthScore:o.Title.titleScore(i)},description:{length:a,lengthScore:o.Description.descriptionScore(a),keywordUsage:o.Description.keywords()},titleKeywordUsage:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},descKeywordUsage:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},sectionScore:{},sectionStatus:{}},bgseo_visibility:{robotIndex:{lengthScore:o.Robots.indexScore()},robotFollow:{lengthScore:o.Robots.followScore()},sectionScore:{},sectionStatus:{}},bgseo_keywords:{keywordPhrase:{length:o.Keywords.phraseLength(o.Keywords.settings.keyword.val()),lengthScore:o.Keywords.keywordPhraseScore(o.Keywords.phraseLength(o.Keywords.settings.keyword.val()))},keywordTitle:{lengthScore:o.Keywords.titleScore(o.Title.keywords())},keywordDescription:{lengthScore:o.Keywords.descriptionScore(o.Description.keywords())},keywordContent:{lengthScore:o.Keywords.contentScore(o.ContentAnalysis.keywords(o.TinyMCE.getContent().text))},keywordHeadings:{length:o.Headings.keywords(y),lengthScore:o.Keywords.headingScore(o.Headings.keywords(y))},image:{length:n.rawstatistics.imageCount,lengthScore:o.ContentAnalysis.seoImageLengthScore(n.rawstatistics.imageCount)},headings:y,wordCount:{length:t.getWordCount(),lengthScore:o.ContentAnalysis.seoContentLengthScore(t.getWordCount())},sectionScore:{},sectionStatus:{}},textstatistics:{recommendedKeywords:u,recommendedCount:o.Keywords.getRecommendedCount(w),keywordDensity:o.Keywords.keywordDensity(h,o.Keywords.getKeyword())}})}r.titleLength&&(_(n.bgseo_meta.title).extend({length:r.titleLength,lengthScore:o.Title.titleScore(r.titleLength)}),_(n.bgseo_meta.titleKeywordUsage).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),_(n.bgseo_keywords.keywordTitle).extend({lengthScore:o.Keywords.titleScore(o.Title.keywords())}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.descLength&&(_(n.bgseo_meta.description).extend({length:r.descLength,lengthScore:o.Description.descriptionScore(r.descLength)}),_(n.bgseo_meta.descKeywordUsage).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),_(n.bgseo_keywords.keywordDescription).extend({lengthScore:o.Keywords.descriptionScore(o.Description.keywords())}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.robotIndex&&(_(n.bgseo_visibility.robotIndex).extend({lengthScore:r.robotIndex}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()])),r.robotFollow&&(_(n.bgseo_visibility.robotFollow).extend({lengthScore:r.robotFollow}),t.settings.content.trigger("bgseo-analysis",[o.TinyMCE.getContent()]))}t.settings.content.trigger("bgseo-report",[n])})},get:function(e){var t={};return t=_.isUndefined(e)?n:_.pickDeep(n,e)}},t=o.Report}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Robots={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._index(),t._follow()},getSettings:function(){t.settings={indexInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]"),noIndex:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]'),followInput:e("input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]"),noFollow:e('input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]')}},_index:function(){t.settings.indexInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotIndex:t.indexScore()}])})},indexScore:function(){var e;return e={status:"green",msg:_bgseoContentAnalysis.noIndex.good},t.settings.noIndex.is(":checked")&&(e={status:"red",msg:_bgseoContentAnalysis.noIndex.bad}),e},_follow:function(){t.settings.followInput.on("change",function(){e(this).trigger("bgseo-analysis",[{robotFollow:t.followScore()}])})},followScore:function(){var e={status:"green",msg:_bgseoContentAnalysis.noFollow.good};return t.settings.noFollow.is(":checked")&&(e={status:"yellow",msg:_bgseoContentAnalysis.noFollow.bad}),e}},t=o.Robots}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Sections={status:function(e){var t="green";return e.red>0?t="red":e.yellow>0&&(t="yellow"),t},score:function(e){var n,o,s;return n={red:0,green:0,yellow:0},o=_(e).countBy(function(t){return _.isUndefined(t.lengthScore)||"sectionScore"===_.property("sectionScore")(e)?"":t.lengthScore.status}),_(o).each(function(e,t){_.has(n,t)&&(n[t]=e)}),s={sectionScore:n,sectionStatus:t.status(n)}},removeStatus:function(e){e.removeClass("red yellow green")},navHighlight:function(n){_.each(butterbean.models.sections,function(o){var s,r=o.get("manager"),i=o.get("name");s=e('[href="#butterbean-'+r+"-section-"+i+'"]').closest("li"),t.removeStatus(s),s.addClass(n[i].sectionStatus)})},overviewStatus:function(n){var o=e("#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')");t.removeStatus(o),o.addClass("overview-status "+n.bgseo_keywords.overview.status)}},t=o.Sections}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Title={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t._title()},getSettings:function(){t.settings={title:e("#boldgrid-seo-field-meta_title")}},getTitle:function(){return t.settings.title},_title:function(){t.settings.title.on("input propertychange paste",_.debounce(function(){t.settings.title.trigger("bgseo-analysis",[{titleLength:t.settings.title.val().length}])},1e3))},titleScore:function(e){var t,n={};return t=_bgseoContentAnalysis.seoTitle.length,0===e&&(n={status:"red",msg:t.badEmpty}),e.isBetween(0,t.okScore+1)&&(n={status:"yellow",msg:t.ok}),e.isBetween(t.okScore-1,t.goodScore+1)&&(n={status:"green",msg:t.good}),e>t.goodScore&&(n={status:"red",msg:t.badLong}),n},keywords:function(e,n){return 0===arguments.length?(n=o.Keywords.getKeyword(),e=t.getTitle().val()):1===arguments.length&&(n=o.Keywords.getKeyword()),e=e.toLowerCase(),e.occurences(n)}},t=o.Title}(jQuery),function(e){"use strict";var t,n,o;o=BOLDGRID.SEO,n=o.report,o.Tooltips={init:function(){e(document).ready(t.onReady)},onReady:function(){t.getSettings(),t.hideTooltips(),t._enableTooltips(),t._toggleTooltip()},getSettings:function(){t.settings={description:e(".butterbean-control .butterbean-description"),tooltip:e("<span />",{"class":"bgseo-tooltip dashicons dashicons-editor-help","aria-expanded":"false"}),onClick:e(".butterbean-label, .bgseo-tooltip")}},_toggleTooltip:function(){t.settings.onClick.on("click",function(e){t.toggleTooltip(e)})},_enableTooltips:function(){t.settings.description.prev().append(t.settings.tooltip)},toggleTooltip:function(t){e(t.currentTarget).next(".butterbean-description").slideToggle()},hideTooltips:function(){t.settings.description.hide()}},t=o.Tooltips}(jQuery);var BOLDGRID=BOLDGRID||{};BOLDGRID.SEO=BOLDGRID.SEO||{},function(e){"use strict";var t;t=BOLDGRID.SEO,t.Init={load:function(){_.each(t,function(e){return e.init&&e.init()})}}}(jQuery),BOLDGRID.SEO.Init.load();
|
assets/js/bgseo/boldgrid-seo-admin.js
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* BoldGrid SEO Admin.
|
9 |
+
*
|
10 |
+
* This is responsible for setting the counters for the SEO Title &
|
11 |
+
* Description tab.
|
12 |
+
*
|
13 |
+
* @since 1.2.1
|
14 |
+
*/
|
15 |
+
BOLDGRID.SEO.Admin = {
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Initialize Word Count.
|
19 |
+
*
|
20 |
+
* @since 1.2.1
|
21 |
+
*/
|
22 |
+
init : function () {
|
23 |
+
$( document ).ready( function() {
|
24 |
+
self._setWordCounts();
|
25 |
+
});
|
26 |
+
},
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Get the word count of a metabox field.
|
30 |
+
*
|
31 |
+
* @since 1.2.1
|
32 |
+
*
|
33 |
+
* @param {Object} $element The element to apply the word counter to.
|
34 |
+
*/
|
35 |
+
wordCount : function( $element ) {
|
36 |
+
var limit = $element.attr( 'maxlength' ),
|
37 |
+
$counter = $( '<span />', {
|
38 |
+
'class' : 'boldgrid-seo-meta-counter',
|
39 |
+
'style' : 'font-weight: bold'
|
40 |
+
}),
|
41 |
+
$container = $( '<div />', {
|
42 |
+
'class' : 'boldgrid-seo-meta-countdown boldgrid-seo-meta-extra',
|
43 |
+
'html' : ' characters left'
|
44 |
+
});
|
45 |
+
|
46 |
+
if ( limit ) {
|
47 |
+
$element
|
48 |
+
.removeAttr( 'maxlength' )
|
49 |
+
.after( $container.prepend( $counter ) )
|
50 |
+
.on( 'keyup focus' , function() {
|
51 |
+
self.setCounter( $counter, $element, limit );
|
52 |
+
});
|
53 |
+
}
|
54 |
+
|
55 |
+
self.setCounter( $counter, $element, limit );
|
56 |
+
},
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Set the colors of the count to reflect ideal lengths.
|
60 |
+
*
|
61 |
+
* @since 1.2.1
|
62 |
+
*
|
63 |
+
* @param {Object} $counter New element to create for counter.
|
64 |
+
* @param {Object} $target Element to check the input value of.
|
65 |
+
* @param {Number} limit The maxlength of the input to calculate on.
|
66 |
+
*/
|
67 |
+
setCounter : function( $counter, $target, limit ) {
|
68 |
+
var text = $target.val(),
|
69 |
+
chars = text.length;
|
70 |
+
|
71 |
+
$counter.html( limit - chars );
|
72 |
+
|
73 |
+
if ( $target.context.id === 'boldgrid-seo-field-meta_description' ) {
|
74 |
+
if ( chars > limit ) {
|
75 |
+
$counter.css( { 'color' : '#EA4335' } );
|
76 |
+
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoDescription.length.okScore ) ) {
|
77 |
+
$counter.css( { 'color' : '#FBBC05' } );
|
78 |
+
} else if ( chars.isBetween( _bgseoContentAnalysis.seoDescription.length.okScore -1, _bgseoContentAnalysis.seoDescription.length.goodScore + 1 ) ) {
|
79 |
+
$counter.css( { 'color' : '#34A853' } );
|
80 |
+
} else {
|
81 |
+
$counter.css( { 'color' : 'black' } );
|
82 |
+
}
|
83 |
+
} else {
|
84 |
+
if ( chars > limit ) {
|
85 |
+
$counter.css( { 'color' : '#EA4335' } );
|
86 |
+
} else if ( chars.isBetween( 0, _bgseoContentAnalysis.seoTitle.length.okScore ) ) {
|
87 |
+
$counter.css( { 'color' : '#FBBC05' } );
|
88 |
+
} else if ( chars > _bgseoContentAnalysis.seoTitle.length.okScore - 1 ) {
|
89 |
+
$counter.css( { 'color' : '#34A853' } );
|
90 |
+
} else {
|
91 |
+
$counter.css( { 'color' : 'black' } );
|
92 |
+
}
|
93 |
+
}
|
94 |
+
},
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Set the word counts for each field in the SEO Title & Description Tab.
|
98 |
+
*
|
99 |
+
* @since 1.2.1
|
100 |
+
*/
|
101 |
+
_setWordCounts : function() {
|
102 |
+
// Apply our wordcount counter to the meta title and meta description textarea fields.
|
103 |
+
$( '#boldgrid-seo-field-meta_title, #boldgrid-seo-field-meta_description' )
|
104 |
+
.each( function() {
|
105 |
+
self.wordCount( $( this ) );
|
106 |
+
});
|
107 |
+
},
|
108 |
+
};
|
109 |
+
|
110 |
+
self = BOLDGRID.SEO.Admin;
|
111 |
+
|
112 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-content-analysis.js
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Content Analysis.
|
12 |
+
*
|
13 |
+
* This is responsible for general analysis of the user's content.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.ContentAnalysis = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Content Length Score.
|
21 |
+
*
|
22 |
+
* This is responsible for the user's content length scoring. The content
|
23 |
+
* length for this method is based on the word count, and not character
|
24 |
+
* counts.
|
25 |
+
*
|
26 |
+
* @since 1.3.1
|
27 |
+
*
|
28 |
+
* @param {Number} contentLength The length of the content to provide score on.
|
29 |
+
*
|
30 |
+
* @returns {Object} msg Contains the status indicator color and message.
|
31 |
+
*/
|
32 |
+
seoContentLengthScore: function( contentLength ) {
|
33 |
+
var content, displayed, msg = {};
|
34 |
+
|
35 |
+
// Cast to int to avoid errors in scoring.
|
36 |
+
contentLength = Number( contentLength );
|
37 |
+
|
38 |
+
// Content var.
|
39 |
+
content = _bgseoContentAnalysis.content.length;
|
40 |
+
|
41 |
+
// Displayed Message.
|
42 |
+
displayed = content.contentLength.printf( contentLength ) + ' ';
|
43 |
+
|
44 |
+
if ( contentLength === 0 ) {
|
45 |
+
msg = {
|
46 |
+
status: 'red',
|
47 |
+
msg: content.badEmpty,
|
48 |
+
};
|
49 |
+
}
|
50 |
+
|
51 |
+
if ( contentLength.isBetween( 0, content.badShortScore ) ) {
|
52 |
+
msg = {
|
53 |
+
status: 'red',
|
54 |
+
msg: displayed + content.badShort,
|
55 |
+
};
|
56 |
+
}
|
57 |
+
|
58 |
+
if ( contentLength.isBetween( content.badShortScore -1, content.okScore ) ) {
|
59 |
+
msg = {
|
60 |
+
status: 'yellow',
|
61 |
+
msg: displayed + content.ok,
|
62 |
+
};
|
63 |
+
}
|
64 |
+
|
65 |
+
if ( contentLength > content.okScore -1 ) {
|
66 |
+
msg = {
|
67 |
+
status: 'green',
|
68 |
+
msg: displayed + content.good,
|
69 |
+
};
|
70 |
+
}
|
71 |
+
|
72 |
+
return msg;
|
73 |
+
},
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Checks if user has any images in their content.
|
77 |
+
*
|
78 |
+
* This provides a status and message if the user has included an
|
79 |
+
* image in their content for their page/post running analysis.
|
80 |
+
*
|
81 |
+
* @since 1.3.1
|
82 |
+
*
|
83 |
+
* @param {Number} imageLength Count of images found within content.
|
84 |
+
*
|
85 |
+
* @returns {Object} msg Contains the status indicator color and message.
|
86 |
+
*/
|
87 |
+
seoImageLengthScore: function( imageLength ) {
|
88 |
+
var msg = {
|
89 |
+
status: 'green',
|
90 |
+
msg: _bgseoContentAnalysis.image.length.good,
|
91 |
+
};
|
92 |
+
if ( ! imageLength ) {
|
93 |
+
msg = {
|
94 |
+
status: 'red',
|
95 |
+
msg: _bgseoContentAnalysis.image.length.bad,
|
96 |
+
};
|
97 |
+
}
|
98 |
+
|
99 |
+
return msg;
|
100 |
+
},
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Get count of keywords used in content.
|
104 |
+
*
|
105 |
+
* This checks the content for occurences of the keyword used throughout.
|
106 |
+
*
|
107 |
+
* @since 1.3.1
|
108 |
+
*
|
109 |
+
* @param {string} content The content to search for the keyword in.
|
110 |
+
*
|
111 |
+
* @returns {Number} Count of times keyword appears in content.
|
112 |
+
*/
|
113 |
+
keywords : function( content ) {
|
114 |
+
var keyword = api.Keywords.getKeyword();
|
115 |
+
return content.occurences( keyword );
|
116 |
+
},
|
117 |
+
};
|
118 |
+
|
119 |
+
self = api.ContentAnalysis;
|
120 |
+
|
121 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-dashboard.js
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Dashboard.
|
12 |
+
*
|
13 |
+
* This is responsible for any Dashboard section specific functionality.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Dashboard = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* This gets the overview score.
|
21 |
+
*
|
22 |
+
* Number is a percentage.
|
23 |
+
*
|
24 |
+
* @since 1.3.1
|
25 |
+
*
|
26 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
27 |
+
*
|
28 |
+
* @returns {Number} The rounded percentage value for overall score.
|
29 |
+
*/
|
30 |
+
overviewScore : function( report ) {
|
31 |
+
var max,
|
32 |
+
total = self.totalScore( report ),
|
33 |
+
sections = _.size( butterbean.models.sections );
|
34 |
+
|
35 |
+
max = sections * 2;
|
36 |
+
|
37 |
+
return ( total / max * 100 ).rounded( 2 );
|
38 |
+
},
|
39 |
+
|
40 |
+
/**
|
41 |
+
* This gets the overview status.
|
42 |
+
*
|
43 |
+
* @since 1.3.1
|
44 |
+
*
|
45 |
+
* @param {Number} score The BoldGrid SEO overview status score.
|
46 |
+
*
|
47 |
+
* @returns {string} The status indicator color of the overall scoring.
|
48 |
+
*/
|
49 |
+
overviewStatus : function( score ) {
|
50 |
+
var status;
|
51 |
+
|
52 |
+
// Default overview status.
|
53 |
+
status = 'green';
|
54 |
+
|
55 |
+
// If status is below 40%.
|
56 |
+
if ( score < 40 ) {
|
57 |
+
status = 'red';
|
58 |
+
}
|
59 |
+
|
60 |
+
// Status is 40% - 75%.
|
61 |
+
if ( score.isBetween( 39, 76 ) ) {
|
62 |
+
status = 'yellow';
|
63 |
+
}
|
64 |
+
|
65 |
+
return status;
|
66 |
+
},
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Get the combined statuses for each section in BoldGrid SEO metabox.
|
70 |
+
*
|
71 |
+
* @since 1.3.1
|
72 |
+
*
|
73 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
74 |
+
*
|
75 |
+
* @returns {Object} status The combined statuses for all sections.
|
76 |
+
*/
|
77 |
+
getStatuses : function( report ) {
|
78 |
+
var status = {};
|
79 |
+
|
80 |
+
_.each( butterbean.models.sections, function( section ) {
|
81 |
+
var score, name = section.get( 'name' );
|
82 |
+
score = report[name].sectionStatus;
|
83 |
+
status[name] = score;
|
84 |
+
_( status[name] ).extend( score );
|
85 |
+
});
|
86 |
+
|
87 |
+
return status;
|
88 |
+
},
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Assigns numbers to represent the statuses.
|
92 |
+
*
|
93 |
+
* @since 1.3.1
|
94 |
+
*
|
95 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
96 |
+
*
|
97 |
+
* @returns {Object} score The numerical values based on status rank.
|
98 |
+
*/
|
99 |
+
assignNumbers : function( report ) {
|
100 |
+
var score, statuses;
|
101 |
+
|
102 |
+
statuses = self.getStatuses( report );
|
103 |
+
|
104 |
+
// Map strings into score values.
|
105 |
+
score = _.mapObject( statuses, function( status ) {
|
106 |
+
var score;
|
107 |
+
|
108 |
+
if ( status === 'red' ) score = 0;
|
109 |
+
if ( status === 'yellow' ) score = 1;
|
110 |
+
if ( status === 'green' ) score = 2;
|
111 |
+
|
112 |
+
return score;
|
113 |
+
});
|
114 |
+
|
115 |
+
return score;
|
116 |
+
},
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Combines all the status scores into a final sum.
|
120 |
+
*
|
121 |
+
* @since 1.3.1
|
122 |
+
*
|
123 |
+
* @param {Object} report The BoldGrid SEO Analysis report.
|
124 |
+
*
|
125 |
+
* @returns {Object} total The total overall numerical value for statuses.
|
126 |
+
*/
|
127 |
+
totalScore : function( report ) {
|
128 |
+
var total, statuses = self.assignNumbers( report );
|
129 |
+
|
130 |
+
total = _( statuses ).reduce( function( initial, number ) {
|
131 |
+
return initial + number;
|
132 |
+
}, 0 );
|
133 |
+
|
134 |
+
return total;
|
135 |
+
}
|
136 |
+
};
|
137 |
+
|
138 |
+
self = api.Dashboard;
|
139 |
+
|
140 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-description.js
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Description.
|
12 |
+
*
|
13 |
+
* This is responsible for the SEO Description Grading.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Description = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Initialize SEO Description Analysis.
|
21 |
+
*
|
22 |
+
* @since 1.3.1
|
23 |
+
*/
|
24 |
+
init : function () {
|
25 |
+
$( document ).ready( self.onReady );
|
26 |
+
},
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
30 |
+
*
|
31 |
+
* @since 1.3.1
|
32 |
+
*/
|
33 |
+
onReady : function() {
|
34 |
+
self.getSettings();
|
35 |
+
self._description();
|
36 |
+
},
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Cache selectors
|
40 |
+
*
|
41 |
+
* @since 1.3.1
|
42 |
+
*/
|
43 |
+
getSettings : function() {
|
44 |
+
self.settings = {
|
45 |
+
description : $( '#boldgrid-seo-field-meta_description' ),
|
46 |
+
};
|
47 |
+
},
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Sets up event listener for changes made to the SEO Description.
|
51 |
+
*
|
52 |
+
* Listens for changes being made to the SEO Description, and then
|
53 |
+
* triggers the reporter to be updated with new status/score.
|
54 |
+
*
|
55 |
+
* @since 1.3.1
|
56 |
+
*/
|
57 |
+
_description : function() {
|
58 |
+
// Listen for changes to input value.
|
59 |
+
self.settings.description.on( 'input propertychange paste', _.debounce( function() {
|
60 |
+
$( this ).trigger( 'bgseo-analysis', [{ descLength : self.settings.description.val().length }] );
|
61 |
+
}, 1000 ) );
|
62 |
+
},
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Gets the SEO Description.
|
66 |
+
*
|
67 |
+
* @since 1.3.1
|
68 |
+
*
|
69 |
+
* @returns {Object} description Contains wrapped set with BoldGrid SEO Description.
|
70 |
+
*/
|
71 |
+
getDescription : function() {
|
72 |
+
return self.settings.description;
|
73 |
+
},
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Gets score of the SEO Description.
|
77 |
+
*
|
78 |
+
* Checks the length provided and returns a score and status color
|
79 |
+
* for the SEO description. This score is based on character count.
|
80 |
+
*
|
81 |
+
* @since 1.3.1
|
82 |
+
*
|
83 |
+
* @param {Number} descriptionLength Length of the user's SEO Description.
|
84 |
+
*
|
85 |
+
* @returns {Object} msg Contains status indicator color and message to update.
|
86 |
+
*/
|
87 |
+
descriptionScore : function( descriptionLength ) {
|
88 |
+
var msg = {}, desc;
|
89 |
+
|
90 |
+
desc = _bgseoContentAnalysis.seoDescription.length;
|
91 |
+
|
92 |
+
// No description has been entered.
|
93 |
+
if ( descriptionLength === 0 ) {
|
94 |
+
msg = {
|
95 |
+
status: 'red',
|
96 |
+
msg: desc.badEmpty,
|
97 |
+
};
|
98 |
+
}
|
99 |
+
|
100 |
+
// Character count is 1-124.
|
101 |
+
if ( descriptionLength.isBetween( 0, desc.okScore ) ) {
|
102 |
+
msg = {
|
103 |
+
status: 'yellow',
|
104 |
+
msg: desc.ok,
|
105 |
+
};
|
106 |
+
}
|
107 |
+
|
108 |
+
// Character count is 125-156.
|
109 |
+
if ( descriptionLength.isBetween( desc.okScore - 1, desc.goodScore + 1 ) ) {
|
110 |
+
msg = {
|
111 |
+
status: 'green',
|
112 |
+
msg: desc.good,
|
113 |
+
};
|
114 |
+
}
|
115 |
+
|
116 |
+
// Character coutn is over 156.
|
117 |
+
if ( descriptionLength > desc.goodScore ) {
|
118 |
+
msg = {
|
119 |
+
status: 'red',
|
120 |
+
msg: desc.badLong,
|
121 |
+
};
|
122 |
+
}
|
123 |
+
|
124 |
+
return msg;
|
125 |
+
},
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Gets the number of occurences in the SEO Description.
|
129 |
+
*
|
130 |
+
* @since 1.3.1
|
131 |
+
*
|
132 |
+
* @returns {Number} Frequency that keyword appears in description.
|
133 |
+
*/
|
134 |
+
keywords : function() {
|
135 |
+
var keyword, description;
|
136 |
+
|
137 |
+
// Get keyword.
|
138 |
+
keyword = api.Keywords.getKeyword();
|
139 |
+
// Get text from input.
|
140 |
+
description = self.getDescription().val();
|
141 |
+
// Normalize user input.
|
142 |
+
description = description.toLowerCase();
|
143 |
+
|
144 |
+
return description.occurences( keyword );
|
145 |
+
},
|
146 |
+
};
|
147 |
+
|
148 |
+
self = api.Description;
|
149 |
+
|
150 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-headings.js
ADDED
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Headings.
|
12 |
+
*
|
13 |
+
* This is responsible for the SEO Headings Grading.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Headings = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Initialize SEO Headings Analysis.
|
21 |
+
*
|
22 |
+
* @since 1.3.1
|
23 |
+
*/
|
24 |
+
init : function () {
|
25 |
+
$( document ).ready( self.onReady );
|
26 |
+
},
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
30 |
+
*
|
31 |
+
* @since 1.3.1
|
32 |
+
*/
|
33 |
+
onReady : function() {
|
34 |
+
self.getSettings();
|
35 |
+
self._checkbox();
|
36 |
+
},
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Cache selectors
|
40 |
+
*
|
41 |
+
* @since 1.3.1
|
42 |
+
*/
|
43 |
+
getSettings : function() {
|
44 |
+
self.settings = {
|
45 |
+
displayTitle : $( '[name="boldgrid-display-post-title"]' ).last(),
|
46 |
+
};
|
47 |
+
},
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Sets up event listener for Display page title checkbox.
|
51 |
+
*
|
52 |
+
* Listens for checkbox changes and updates the status message.
|
53 |
+
*
|
54 |
+
* @since 1.3.1
|
55 |
+
*/
|
56 |
+
_checkbox : function() {
|
57 |
+
// Listen for changes to input value.
|
58 |
+
self.settings.displayTitle.on( 'change', _.debounce( function() {
|
59 |
+
$( this ).trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
60 |
+
}, 1000 ) );
|
61 |
+
},
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Initialize BoldGrid SEO Headings Analysis.
|
65 |
+
*
|
66 |
+
* @since 1.3.1
|
67 |
+
*/
|
68 |
+
score : function( count ) {
|
69 |
+
var msg;
|
70 |
+
|
71 |
+
// Set default message for h1 headings score.
|
72 |
+
msg = {
|
73 |
+
status : 'green',
|
74 |
+
msg : _bgseoContentAnalysis.headings.h1.good,
|
75 |
+
};
|
76 |
+
|
77 |
+
// If we have more than one H1 tag rendered.
|
78 |
+
if ( count > 1 ) {
|
79 |
+
msg = {
|
80 |
+
status : 'red',
|
81 |
+
msg : _bgseoContentAnalysis.headings.h1.badMultiple,
|
82 |
+
};
|
83 |
+
}
|
84 |
+
|
85 |
+
// If we have more than one H1 tag rendered.
|
86 |
+
if ( count > 1 && self.settings.displayTitle.is( ':checked' ) ) {
|
87 |
+
msg = {
|
88 |
+
status : 'red',
|
89 |
+
msg : _bgseoContentAnalysis.headings.h1.badBoldgridTheme,
|
90 |
+
};
|
91 |
+
}
|
92 |
+
|
93 |
+
// If no H1 tag is present.
|
94 |
+
if ( 0 === count ) {
|
95 |
+
msg = {
|
96 |
+
status : 'red',
|
97 |
+
msg : _bgseoContentAnalysis.headings.h1.badEmpty,
|
98 |
+
};
|
99 |
+
}
|
100 |
+
|
101 |
+
return msg;
|
102 |
+
},
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Gets count of how many times keywords appear in headings.
|
106 |
+
*
|
107 |
+
* @since 1.3.1
|
108 |
+
*
|
109 |
+
* @param {Object} headings The headings count object to check against.
|
110 |
+
*
|
111 |
+
* @returns {Number} How many times the keyword appears in the headings.
|
112 |
+
*/
|
113 |
+
keywords : function( headings ) {
|
114 |
+
var found = { length : 0 },
|
115 |
+
keyword = api.Keywords.getKeyword();
|
116 |
+
|
117 |
+
// If not passing in headings, attempt to find default headings.
|
118 |
+
if ( _.isUndefined( headings ) ) {
|
119 |
+
headings = { count : self.getRealHeadingCount() };
|
120 |
+
}
|
121 |
+
|
122 |
+
// Don't process report item if headings are empty.
|
123 |
+
if ( _.isEmpty( headings ) ) return;
|
124 |
+
// Get the count.
|
125 |
+
_( headings.count ).each( function( value, key ) {
|
126 |
+
var text = value.text;
|
127 |
+
// Add to the found object for total occurences found for keyword in headings.
|
128 |
+
_( text ).each( function( item ) {
|
129 |
+
found.length = Number( found.length ) + Number( item.heading.occurences( keyword ) * item.count );
|
130 |
+
});
|
131 |
+
});
|
132 |
+
|
133 |
+
return found.length;
|
134 |
+
},
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Get the text inside of headings.
|
138 |
+
*
|
139 |
+
* @since 1.3.1
|
140 |
+
*
|
141 |
+
* @param {Object} selectors jQuery wrapped selector object.
|
142 |
+
*
|
143 |
+
* @returns {Array} headingText Contains each selectors' text.
|
144 |
+
*/
|
145 |
+
getHeadingText : function( selectors ) {
|
146 |
+
var headingText = {};
|
147 |
+
|
148 |
+
headingText = _.countBy( selectors, function( value, key ) {
|
149 |
+
return $.trim( $( value ).text().toLowerCase() );
|
150 |
+
});
|
151 |
+
headingText = _.map( headingText, function( value, key ) {
|
152 |
+
return _( headingText ).has({ heading : key, count : value }) ? false : {
|
153 |
+
heading : key,
|
154 |
+
count : value,
|
155 |
+
};
|
156 |
+
});
|
157 |
+
|
158 |
+
return headingText;
|
159 |
+
},
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Gets the actual headings count based on the rendered page and the content.
|
163 |
+
*
|
164 |
+
* This only needs to be fired if the rendered report
|
165 |
+
* data is available for analysis. The calculations take
|
166 |
+
* into account the template in use for the page/post and
|
167 |
+
* are stored earlier on in the load process when the user
|
168 |
+
* first enters the editor.
|
169 |
+
*
|
170 |
+
* @since 1.3.1
|
171 |
+
*
|
172 |
+
* @returns {Object} headings Count of H1, H2, and H3 tags used for page/post.
|
173 |
+
*/
|
174 |
+
getRealHeadingCount : function() {
|
175 |
+
var headings = {};
|
176 |
+
|
177 |
+
// Only get this score if rendered content score has been provided.
|
178 |
+
if ( ! _.isUndefined( report.rendered ) ) {
|
179 |
+
// Stores the heading coutns for h1-h3 for later analysis.
|
180 |
+
headings = {
|
181 |
+
count: {
|
182 |
+
h1 : {
|
183 |
+
length : report.rendered.h1Count + report.rawstatistics.h1Count,
|
184 |
+
text : _( report.rendered.h1text ).union( report.rawstatistics.h1text ),
|
185 |
+
},
|
186 |
+
h2 : {
|
187 |
+
length : report.rendered.h2Count + report.rawstatistics.h2Count,
|
188 |
+
text : _( report.rendered.h2text ).union( report.rawstatistics.h2text ),
|
189 |
+
},
|
190 |
+
},
|
191 |
+
};
|
192 |
+
// Add the score of H1 presence to the headings object.
|
193 |
+
_( headings ).extend({
|
194 |
+
lengthScore : self.score( headings.count.h1.length ),
|
195 |
+
});
|
196 |
+
} else {
|
197 |
+
headings = self.getContentHeadings();
|
198 |
+
}
|
199 |
+
|
200 |
+
return headings;
|
201 |
+
},
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Get the headings that exist in the raw content.
|
205 |
+
*
|
206 |
+
* This will get the content and check if any h1s or
|
207 |
+
* h2s exist in the raw markup. If they are present, it will
|
208 |
+
* update the report with new count information and text.
|
209 |
+
*
|
210 |
+
* @since 1.3.1
|
211 |
+
*
|
212 |
+
* @returns {Object} headings Counts of h1 and h2 tags in content.
|
213 |
+
*/
|
214 |
+
getContentHeadings : function() {
|
215 |
+
var headings, h1s, h2s, content;
|
216 |
+
|
217 |
+
// Set default counts.
|
218 |
+
headings = {
|
219 |
+
count: {
|
220 |
+
h1 : {
|
221 |
+
length : 0,
|
222 |
+
text : {},
|
223 |
+
},
|
224 |
+
h2 : {
|
225 |
+
length : 0,
|
226 |
+
text : {},
|
227 |
+
},
|
228 |
+
},
|
229 |
+
};
|
230 |
+
|
231 |
+
content = api.TinyMCE.getContent();
|
232 |
+
|
233 |
+
h1s = $( content.raw ).find( 'h1' );
|
234 |
+
h2s = $( content.raw ).find( 'h2' );
|
235 |
+
|
236 |
+
// If no h1s or h2s are found return the defaults.
|
237 |
+
if ( ! h1s.length && ! h2s.length ) return headings;
|
238 |
+
|
239 |
+
headings = {
|
240 |
+
count: {
|
241 |
+
h1 : {
|
242 |
+
length : h1s.length,
|
243 |
+
text : self.getHeadingText( h1s ),
|
244 |
+
},
|
245 |
+
h2 : {
|
246 |
+
length : h2s.length,
|
247 |
+
text : self.getHeadingText( h2s ),
|
248 |
+
},
|
249 |
+
},
|
250 |
+
};
|
251 |
+
|
252 |
+
return headings;
|
253 |
+
},
|
254 |
+
};
|
255 |
+
|
256 |
+
self = api.Headings;
|
257 |
+
|
258 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-init.js
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var BOLDGRID = BOLDGRID || {};
|
2 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
3 |
+
|
4 |
+
( function ( $ ) {
|
5 |
+
|
6 |
+
'use strict';
|
7 |
+
|
8 |
+
var api;
|
9 |
+
|
10 |
+
api = BOLDGRID.SEO;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* BoldGrid SEO Initialize.
|
14 |
+
*
|
15 |
+
* This initializes BoldGrid SEO.
|
16 |
+
*
|
17 |
+
* @since 1.3.1
|
18 |
+
*/
|
19 |
+
api.Init = {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Initialize Utilities.
|
23 |
+
*
|
24 |
+
* @since 1.3.1
|
25 |
+
*/
|
26 |
+
load : function () {
|
27 |
+
_.each( api, function( obj ) {
|
28 |
+
return obj.init && obj.init();
|
29 |
+
});
|
30 |
+
},
|
31 |
+
};
|
32 |
+
|
33 |
+
})( jQuery );
|
34 |
+
|
35 |
+
BOLDGRID.SEO.Init.load();
|
assets/js/bgseo/boldgrid-seo-keywords.js
ADDED
@@ -0,0 +1,559 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Keywords.
|
12 |
+
*
|
13 |
+
* This is responsible for the SEO Keywords Analysis and Scoring.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Keywords = {
|
18 |
+
/**
|
19 |
+
* Initialize BoldGrid SEO Keyword Analysis.
|
20 |
+
*
|
21 |
+
* @since 1.3.1
|
22 |
+
*/
|
23 |
+
init : function () {
|
24 |
+
$( document ).ready( self.onReady );
|
25 |
+
},
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
29 |
+
*
|
30 |
+
* @since 1.3.1
|
31 |
+
*/
|
32 |
+
onReady : function() {
|
33 |
+
self.getSettings();
|
34 |
+
self._keywords();
|
35 |
+
self.setPlaceholder();
|
36 |
+
},
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Cache selectors
|
40 |
+
*
|
41 |
+
* @since 1.3.1
|
42 |
+
*/
|
43 |
+
getSettings : function() {
|
44 |
+
self.settings = {
|
45 |
+
keyword : $( '#bgseo-custom-keyword' ),
|
46 |
+
content : $( '#content' ),
|
47 |
+
};
|
48 |
+
},
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Sets up event listener for changes made to the custom keyword input.
|
52 |
+
*
|
53 |
+
* Listens for changes being made to the custom keyword input, and then
|
54 |
+
* triggers the reporter to be updated with new status/score.
|
55 |
+
*
|
56 |
+
* @since 1.3.1
|
57 |
+
*/
|
58 |
+
_keywords: function() {
|
59 |
+
self.settings.keyword.on( 'input propertychange paste', _.debounce( function() {
|
60 |
+
var msg = {},
|
61 |
+
length = self.settings.keyword.val().length;
|
62 |
+
|
63 |
+
msg = {
|
64 |
+
keywords : {
|
65 |
+
title : {
|
66 |
+
length : api.Title.keywords(),
|
67 |
+
lengthScore : 0,
|
68 |
+
},
|
69 |
+
description : {
|
70 |
+
length : api.Description.keywords(),
|
71 |
+
lengthScore : 0,
|
72 |
+
},
|
73 |
+
keyword : self.getCustomKeyword(),
|
74 |
+
},
|
75 |
+
};
|
76 |
+
|
77 |
+
self.settings.keyword.trigger( 'bgseo-analysis', [msg] );
|
78 |
+
|
79 |
+
}, 1000 ) );
|
80 |
+
},
|
81 |
+
|
82 |
+
setPlaceholder : function( keyword ) {
|
83 |
+
self.settings.keyword.attr( 'placeholder', keyword );
|
84 |
+
},
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Gets the count of the keywords in the content passed in.
|
88 |
+
*
|
89 |
+
* @since 1.3.1
|
90 |
+
*
|
91 |
+
* @param {string} content The content to count keyword frequency in.
|
92 |
+
* @param {string} keyword The keyword/phrase to search for.
|
93 |
+
*
|
94 |
+
* @returns {Number} keywordCount Represents how many times a keyword appears.
|
95 |
+
*/
|
96 |
+
keywordCount: function( content, keyword ) {
|
97 |
+
var keywordCount;
|
98 |
+
|
99 |
+
keywordCount = content.split( keyword ).length - 1;
|
100 |
+
|
101 |
+
return keywordCount;
|
102 |
+
},
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Gets the count of words in the keyword phrase section.
|
106 |
+
*
|
107 |
+
* @since 1.3.1
|
108 |
+
*
|
109 |
+
* @param {string} keywordPhrase The content to count words in.
|
110 |
+
*
|
111 |
+
* @returns {Number} Number of words in keywordPhrase.
|
112 |
+
*/
|
113 |
+
phraseLength: function( keywordPhrase ) {
|
114 |
+
|
115 |
+
// Check for empty strings.
|
116 |
+
if ( keywordPhrase.length === 0 ) {
|
117 |
+
return 0;
|
118 |
+
}
|
119 |
+
|
120 |
+
// Excludes start and end white-space.
|
121 |
+
keywordPhrase = keywordPhrase.replace( /(^\s*)|(\s*$)/gi, '' );
|
122 |
+
|
123 |
+
// 2 or more space to 1.
|
124 |
+
keywordPhrase = keywordPhrase.replace( /[ ]{2,}/gi, ' ' );
|
125 |
+
|
126 |
+
// Exclude newline with a start spacing.
|
127 |
+
keywordPhrase = keywordPhrase.replace( /\n /, '\n' );
|
128 |
+
|
129 |
+
return keywordPhrase.split( ' ' ).length;
|
130 |
+
},
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Calculates keyword density for content and keyword passed in.
|
134 |
+
*
|
135 |
+
* @since 1.3.1
|
136 |
+
*
|
137 |
+
* @param {string} content The content to calculate density for.
|
138 |
+
*
|
139 |
+
* @returns {Number} result Calculated density of keyword in content passed.
|
140 |
+
*/
|
141 |
+
keywordDensity : function( content ) {
|
142 |
+
var result, keywordCount, wordCount, keyword;
|
143 |
+
|
144 |
+
keyword = self.getKeyword();
|
145 |
+
|
146 |
+
// Return 0 without calculation if no custom keyword is found.
|
147 |
+
if ( _.isUndefined( keyword ) ) return 0;
|
148 |
+
|
149 |
+
// Normalize.
|
150 |
+
keyword = keyword.toLowerCase();
|
151 |
+
|
152 |
+
keywordCount = self.keywordCount( content, keyword );
|
153 |
+
wordCount = api.Report.getWordCount();
|
154 |
+
// Get the density.
|
155 |
+
result = ( ( keywordCount / wordCount ) * 100 );
|
156 |
+
// Round it off.
|
157 |
+
result = Math.round( result * 10 ) / 10;
|
158 |
+
|
159 |
+
return result;
|
160 |
+
},
|
161 |
+
|
162 |
+
/**
|
163 |
+
* Normalizes the stop words to match the words returned by the WP
|
164 |
+
* WordCount.
|
165 |
+
*
|
166 |
+
* @since 1.3.2
|
167 |
+
*
|
168 |
+
* @param {string} str Word to normalize.
|
169 |
+
*
|
170 |
+
* @returns {string} Normalized word.
|
171 |
+
*/
|
172 |
+
normalizeWords: function( str ) {
|
173 |
+
return str.replace( '\'', '' );
|
174 |
+
},
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Trims values of whitespace.
|
178 |
+
*
|
179 |
+
* @since 1.3.2
|
180 |
+
*
|
181 |
+
* @param {string} str Word to trim.
|
182 |
+
*
|
183 |
+
* @returns {string} Trimmed word.
|
184 |
+
*/
|
185 |
+
trim: function( str ) {
|
186 |
+
return str.trim();
|
187 |
+
},
|
188 |
+
|
189 |
+
/**
|
190 |
+
* Gets the recommended keywords from content.
|
191 |
+
*
|
192 |
+
* This is what gets suggested to a user that their content is about this
|
193 |
+
* keyword if they do not enter in a custom target keyword or phrase.
|
194 |
+
*
|
195 |
+
* @since 1.3.1
|
196 |
+
*
|
197 |
+
* @param {Array} words The words to search through.
|
198 |
+
* @param {Number} n How many keywords to return back.
|
199 |
+
*
|
200 |
+
* @returns {Array} result An array of n* most frequent keywords.
|
201 |
+
*/
|
202 |
+
recommendedKeywords: function( words, n ) {
|
203 |
+
var stopWords = _bgseoContentAnalysis.stopWords,
|
204 |
+
positions = {},
|
205 |
+
wordCounts = [],
|
206 |
+
result;
|
207 |
+
|
208 |
+
// Abort if no words are passed in.
|
209 |
+
if ( _.isEmpty( words ) ) return;
|
210 |
+
|
211 |
+
// Create array from string passed, and trim array values.
|
212 |
+
stopWords = stopWords.split( ',' ).map( self.trim );
|
213 |
+
|
214 |
+
// Normalize the stopWords to watch WordPress words.
|
215 |
+
stopWords = stopWords.map( self.normalizeWords );
|
216 |
+
|
217 |
+
for ( var i = 0; i < words.length; i++ ) {
|
218 |
+
var word = $.trim( words[i] ).toLowerCase();
|
219 |
+
|
220 |
+
// Make sure word isn't in our stop words and is longer than 3 characters.
|
221 |
+
if ( ! word || word.length < 3 || stopWords.indexOf( word ) > -1 ) {
|
222 |
+
continue;
|
223 |
+
}
|
224 |
+
|
225 |
+
if ( _.isUndefined( positions[ word ] ) ) {
|
226 |
+
positions[ word ] = wordCounts.length;
|
227 |
+
wordCounts.push( [ word, 1 ] );
|
228 |
+
} else {
|
229 |
+
wordCounts[ positions[ word ] ][1]++;
|
230 |
+
}
|
231 |
+
}
|
232 |
+
// Put most frequent words at the beginning.
|
233 |
+
wordCounts.sort( function ( a, b ) {
|
234 |
+
return b[1] - a[1];
|
235 |
+
});
|
236 |
+
|
237 |
+
// Return the first n items
|
238 |
+
result = wordCounts.slice( 0, n );
|
239 |
+
|
240 |
+
return result;
|
241 |
+
},
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Retrieves User's Custom SEO Keyword.
|
245 |
+
*
|
246 |
+
* If the user has entered in a custom keyword to run evaluation on,
|
247 |
+
* then we will retrieve this value instead of the automatically
|
248 |
+
* generated keyword recommendation.
|
249 |
+
*
|
250 |
+
* @since 1.3.1
|
251 |
+
*
|
252 |
+
* @returns {string} Trimmed output of user supplied custom keyword.
|
253 |
+
*/
|
254 |
+
getCustomKeyword : function() {
|
255 |
+
return $.trim( self.settings.keyword.val() ).toLowerCase();
|
256 |
+
},
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Used to get the keyword for the report.
|
260 |
+
*
|
261 |
+
* Checks if a custom keyword has been set by the user, and
|
262 |
+
* if it hasn't it will use the autogenerated keyword that was
|
263 |
+
* determined based on the content.
|
264 |
+
*
|
265 |
+
* @since 1.3.1
|
266 |
+
*
|
267 |
+
* @returns {string} customKeyword Contains the customKeyword to add to report.
|
268 |
+
*/
|
269 |
+
getKeyword : function() {
|
270 |
+
var customKeyword,
|
271 |
+
content = api.TinyMCE.getContent();
|
272 |
+
|
273 |
+
if ( self.getCustomKeyword().length ) {
|
274 |
+
customKeyword = self.getCustomKeyword();
|
275 |
+
} else if ( ! _.isUndefined( report.textstatistics.recommendedKeywords ) &&
|
276 |
+
! _.isUndefined( report.textstatistics.recommendedKeywords[0] ) ) {
|
277 |
+
// Set customKeyword to recommended keyword search.
|
278 |
+
customKeyword = report.textstatistics.recommendedKeywords[0][0];
|
279 |
+
} else if ( _.isEmpty( $.trim( content.text ) ) ) {
|
280 |
+
customKeyword = undefined;
|
281 |
+
} else {
|
282 |
+
self.recommendedKeywords( api.Words.words( content.raw ), 1 );
|
283 |
+
}
|
284 |
+
|
285 |
+
return customKeyword;
|
286 |
+
},
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Used to get the recommended keyword count.
|
290 |
+
*
|
291 |
+
* Gets the percentages provided for minimum and maximum keyword
|
292 |
+
* densities from the configs. The number is based on the amount of words
|
293 |
+
* that make up the current page/post.
|
294 |
+
*
|
295 |
+
* @since 1.3.1
|
296 |
+
*
|
297 |
+
* @returns {Object} count Range for count of keywords based on content length.
|
298 |
+
*/
|
299 |
+
getRecommendedCount : function( markup ) {
|
300 |
+
var count;
|
301 |
+
|
302 |
+
if ( _.isUndefined( markup ) ) {
|
303 |
+
markup = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ?
|
304 |
+
api.Words.words( self.settings.content.val() ) :
|
305 |
+
api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
|
306 |
+
}
|
307 |
+
|
308 |
+
count = _.modifyObject( _bgseoContentAnalysis.keywords.recommendedCount, function( item ) {
|
309 |
+
var numb = Number( ( item / 100 ) * api.Words.words( markup ).length ).rounded( 0 );
|
310 |
+
// Set minimum recommended count to at least once.
|
311 |
+
return numb > 0 ? numb : 1;
|
312 |
+
});
|
313 |
+
|
314 |
+
return count;
|
315 |
+
},
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Used to get the keyword for the report.
|
319 |
+
*
|
320 |
+
* Checks if a custom keyword has been set by the user, and
|
321 |
+
* if it hasn't it will use the autogenerated keyword that was
|
322 |
+
* determined based on the content.
|
323 |
+
*
|
324 |
+
* @since 1.3.1
|
325 |
+
*
|
326 |
+
* @returns {Object} msg Contains the scoring for each keyword related item.
|
327 |
+
*/
|
328 |
+
score : function() {
|
329 |
+
var msg = {};
|
330 |
+
msg = {
|
331 |
+
title : self.titleScore(),
|
332 |
+
description : self.descriptionScore(),
|
333 |
+
};
|
334 |
+
return msg;
|
335 |
+
},
|
336 |
+
|
337 |
+
/**
|
338 |
+
* Used to get the keyword usage scoring description for the title.
|
339 |
+
*
|
340 |
+
* Checks the count provided for the number of times the keyword was
|
341 |
+
* used in the SEO Title.
|
342 |
+
*
|
343 |
+
* @since 1.3.1
|
344 |
+
*
|
345 |
+
* @param {Number} count The number of times keyword is used in the title.
|
346 |
+
*
|
347 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
348 |
+
*/
|
349 |
+
titleScore : function( count ) {
|
350 |
+
var msg;
|
351 |
+
|
352 |
+
// Default status and message.
|
353 |
+
msg = {
|
354 |
+
status: 'green',
|
355 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.good,
|
356 |
+
};
|
357 |
+
|
358 |
+
// Keyword not used in title.
|
359 |
+
if ( 0 === count ) {
|
360 |
+
msg = {
|
361 |
+
status: 'red',
|
362 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.bad,
|
363 |
+
};
|
364 |
+
}
|
365 |
+
|
366 |
+
// Keyword used in title at least once.
|
367 |
+
if ( count > 1 ) {
|
368 |
+
msg = {
|
369 |
+
status: 'yellow',
|
370 |
+
msg : _bgseoContentAnalysis.seoTitle.keywordUsage.ok,
|
371 |
+
};
|
372 |
+
}
|
373 |
+
|
374 |
+
return msg;
|
375 |
+
},
|
376 |
+
|
377 |
+
/**
|
378 |
+
* Used to get the keyword usage scoring description for the description.
|
379 |
+
*
|
380 |
+
* Checks the count provided for the number of times the keyword was
|
381 |
+
* used in the SEO Description field.
|
382 |
+
*
|
383 |
+
* @since 1.3.1
|
384 |
+
*
|
385 |
+
* @param {Number} count The number of times keyword is used in the description.
|
386 |
+
*
|
387 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
388 |
+
*/
|
389 |
+
descriptionScore : function( count ) {
|
390 |
+
var msg;
|
391 |
+
|
392 |
+
// Default status and message.
|
393 |
+
msg = {
|
394 |
+
status: 'green',
|
395 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.good,
|
396 |
+
};
|
397 |
+
|
398 |
+
// If not used at all in description.
|
399 |
+
if ( 0 === count ) {
|
400 |
+
msg = {
|
401 |
+
status: 'red',
|
402 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.bad,
|
403 |
+
};
|
404 |
+
}
|
405 |
+
|
406 |
+
// If used at least one time in description.
|
407 |
+
if ( count > 1 ) {
|
408 |
+
msg = {
|
409 |
+
status: 'yellow',
|
410 |
+
msg : _bgseoContentAnalysis.seoDescription.keywordUsage.ok,
|
411 |
+
};
|
412 |
+
}
|
413 |
+
|
414 |
+
return msg;
|
415 |
+
},
|
416 |
+
|
417 |
+
/**
|
418 |
+
* Gets keyword score for content.
|
419 |
+
*
|
420 |
+
* Used to get the status and message for the content's keyword usage.
|
421 |
+
*
|
422 |
+
* @since 1.3.1
|
423 |
+
*
|
424 |
+
* @param {Number} count The number of times keyword is used in the content.
|
425 |
+
*
|
426 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
427 |
+
*/
|
428 |
+
contentScore : function( count ) {
|
429 |
+
var msg, range, description;
|
430 |
+
|
431 |
+
// Get the keyword range based on the content length.
|
432 |
+
range = self.getRecommendedCount();
|
433 |
+
|
434 |
+
// Keyword not used at all in content.
|
435 |
+
if ( 0 === count ) {
|
436 |
+
msg = {
|
437 |
+
status: 'red',
|
438 |
+
msg : _bgseoContentAnalysis.content.keywordUsage.bad,
|
439 |
+
};
|
440 |
+
}
|
441 |
+
// Keyword used within the range calculated based on content length.
|
442 |
+
if ( count.isBetween( range.min - 1, range.max + 1 ) ) {
|
443 |
+
description = 1 === range.min ?
|
444 |
+
_bgseoContentAnalysis.content.keywordUsage.goodSingular :
|
445 |
+
_bgseoContentAnalysis.content.keywordUsage.good.printf( range.min );
|
446 |
+
|
447 |
+
msg = {
|
448 |
+
status: 'green',
|
449 |
+
msg : description,
|
450 |
+
};
|
451 |
+
}
|
452 |
+
// Keyword used less than the minimum of the range specified, but not 0 times.
|
453 |
+
if ( count < range.min && count !== 0 ) {
|
454 |
+
description = 1 === range.min ?
|
455 |
+
_bgseoContentAnalysis.content.keywordUsage.okShortSingular :
|
456 |
+
_bgseoContentAnalysis.content.keywordUsage.okShort.printf( range.min );
|
457 |
+
|
458 |
+
msg = {
|
459 |
+
status: 'yellow',
|
460 |
+
msg : description,
|
461 |
+
};
|
462 |
+
}
|
463 |
+
|
464 |
+
// Key word used more than 3 times in the content.
|
465 |
+
if ( count > range.max ) {
|
466 |
+
description = 1 === range.min ?
|
467 |
+
_bgseoContentAnalysis.content.keywordUsage.okLongSingular :
|
468 |
+
_bgseoContentAnalysis.content.keywordUsage.okLong.printf( range.min );
|
469 |
+
|
470 |
+
msg = {
|
471 |
+
status: 'red',
|
472 |
+
msg : description,
|
473 |
+
};
|
474 |
+
}
|
475 |
+
|
476 |
+
return msg;
|
477 |
+
},
|
478 |
+
|
479 |
+
/**
|
480 |
+
* Gets keyword score for headings.
|
481 |
+
*
|
482 |
+
* Used to get the status and message for the heading's keyword usage.
|
483 |
+
*
|
484 |
+
* @since 1.3.1
|
485 |
+
*
|
486 |
+
* @param {Number} count The number of times keyword is used in the headings.
|
487 |
+
*
|
488 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
489 |
+
*/
|
490 |
+
headingScore : function( count ) {
|
491 |
+
var msg;
|
492 |
+
|
493 |
+
// Default message.
|
494 |
+
msg = {
|
495 |
+
status: 'green',
|
496 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.good,
|
497 |
+
};
|
498 |
+
|
499 |
+
// Keyword not used at all in content.
|
500 |
+
if ( 0 === count ) {
|
501 |
+
msg = {
|
502 |
+
status: 'red',
|
503 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.bad,
|
504 |
+
};
|
505 |
+
}
|
506 |
+
// Key word used more than 3 times in the content.
|
507 |
+
if ( count > 3 ) {
|
508 |
+
msg = {
|
509 |
+
status: 'yellow',
|
510 |
+
msg : _bgseoContentAnalysis.headings.keywordUsage.ok,
|
511 |
+
};
|
512 |
+
}
|
513 |
+
|
514 |
+
return msg;
|
515 |
+
},
|
516 |
+
|
517 |
+
/**
|
518 |
+
* Used to get the scoring description for the keyword phrase.
|
519 |
+
*
|
520 |
+
* Returns the status message based on how many words are in the phrase.
|
521 |
+
*
|
522 |
+
* @since 1.3.1
|
523 |
+
*
|
524 |
+
* @param {Number} count WordCount for phrase.
|
525 |
+
*
|
526 |
+
* @returns {Object} msg Contains the status indicator color and message for report.
|
527 |
+
*/
|
528 |
+
keywordPhraseScore : function( count ) {
|
529 |
+
var msg;
|
530 |
+
|
531 |
+
// Default status and message.
|
532 |
+
msg = {
|
533 |
+
status: 'green',
|
534 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.good,
|
535 |
+
};
|
536 |
+
|
537 |
+
// Keyword used in title at least once.
|
538 |
+
if ( 1 === count ) {
|
539 |
+
msg = {
|
540 |
+
status: 'yellow',
|
541 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.ok,
|
542 |
+
};
|
543 |
+
}
|
544 |
+
|
545 |
+
// Keyword not used in title.
|
546 |
+
if ( 0 === count ) {
|
547 |
+
msg = {
|
548 |
+
status: 'red',
|
549 |
+
msg : _bgseoContentAnalysis.keywords.keywordPhrase.bad,
|
550 |
+
};
|
551 |
+
}
|
552 |
+
|
553 |
+
return msg;
|
554 |
+
},
|
555 |
+
};
|
556 |
+
|
557 |
+
self = api.Keywords;
|
558 |
+
|
559 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-readability.js
ADDED
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Readability.
|
12 |
+
*
|
13 |
+
* This is responsible for the SEO Reading Score and Grading.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Readability = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Gets the Flesch Kincaid Grade based on the content.
|
21 |
+
*
|
22 |
+
* @since 1.3.1
|
23 |
+
*
|
24 |
+
* @param {String} content The content to run the analysis on.
|
25 |
+
*
|
26 |
+
* @returns {Number} result A number representing the grade of the content.
|
27 |
+
*/
|
28 |
+
gradeLevel : function( content ) {
|
29 |
+
var grade, result = {};
|
30 |
+
grade = textstatistics( content ).fleschKincaidReadingEase();
|
31 |
+
result = self.gradeAnalysis( grade );
|
32 |
+
return result;
|
33 |
+
},
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Returns information about the grade for display.
|
37 |
+
*
|
38 |
+
* This will give back human readable explanations of the grading, so
|
39 |
+
* the user can make changes based on their score accurately.
|
40 |
+
*
|
41 |
+
* @since 1.3.1
|
42 |
+
*
|
43 |
+
* @param {Number} grade The grade to evalute and return response for.
|
44 |
+
*
|
45 |
+
* @returns {Object} description Contains status, explanation and associated grade level.
|
46 |
+
*/
|
47 |
+
gradeAnalysis : function( grade ) {
|
48 |
+
var scoreTranslated, description = {};
|
49 |
+
|
50 |
+
// Grade is higher than 90.
|
51 |
+
if ( grade > 90 ) {
|
52 |
+
description = {
|
53 |
+
'score' : grade,
|
54 |
+
'gradeLevel' : '5th grade',
|
55 |
+
'explanation': 'Very easy to read. Easily understood by an average 11-year-old student.',
|
56 |
+
lengthScore : {
|
57 |
+
'status' : 'green',
|
58 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodHigh,
|
59 |
+
},
|
60 |
+
};
|
61 |
+
}
|
62 |
+
// Grade is 80-90.
|
63 |
+
if ( grade.isBetween( 79, 91 ) ) {
|
64 |
+
description = {
|
65 |
+
'score' : grade,
|
66 |
+
'gradeLevel' : '6th grade',
|
67 |
+
'explanation': 'Easy to read. Conversational English for consumers.',
|
68 |
+
lengthScore : {
|
69 |
+
'status' : 'green',
|
70 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodMedHigh,
|
71 |
+
},
|
72 |
+
};
|
73 |
+
}
|
74 |
+
// Grade is 70-90.
|
75 |
+
if ( grade.isBetween( 69, 81 ) ) {
|
76 |
+
description = {
|
77 |
+
'score' : grade,
|
78 |
+
'gradeLevel' : '7th grade',
|
79 |
+
'explanation': 'Fairly easy to read.',
|
80 |
+
lengthScore : {
|
81 |
+
'status' : 'green',
|
82 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodMedLow,
|
83 |
+
}
|
84 |
+
};
|
85 |
+
}
|
86 |
+
// Grade is 60-70.
|
87 |
+
if ( grade.isBetween( 59, 71 ) ) {
|
88 |
+
description = {
|
89 |
+
'score' : grade,
|
90 |
+
'gradeLevel' : '8th & 9th',
|
91 |
+
'explanation': 'Plain English. Easily understood by 13- to 15-year-old students.',
|
92 |
+
lengthScore : {
|
93 |
+
'status' : 'green',
|
94 |
+
'msg' : _bgseoContentAnalysis.readingEase.goodLow,
|
95 |
+
},
|
96 |
+
};
|
97 |
+
}
|
98 |
+
// Grade is 50-60.
|
99 |
+
if ( grade.isBetween( 49, 61 ) ) {
|
100 |
+
description = {
|
101 |
+
'score' : grade,
|
102 |
+
'gradeLevel' : '10th to 12th',
|
103 |
+
'explanation': 'Fairly difficult to read.',
|
104 |
+
lengthScore : {
|
105 |
+
'status' : 'yellow',
|
106 |
+
'msg' : _bgseoContentAnalysis.readingEase.ok,
|
107 |
+
},
|
108 |
+
};
|
109 |
+
}
|
110 |
+
// Grade is 30-50.
|
111 |
+
if ( grade.isBetween( 29, 51 ) ) {
|
112 |
+
description = {
|
113 |
+
'score' : grade,
|
114 |
+
'gradeLevel' : 'College Student',
|
115 |
+
'explanation': 'Difficult to read.',
|
116 |
+
lengthScore : {
|
117 |
+
'status' : 'red',
|
118 |
+
'msg' : _bgseoContentAnalysis.readingEase.badHigh,
|
119 |
+
},
|
120 |
+
};
|
121 |
+
}
|
122 |
+
// Grade is less than 30.
|
123 |
+
if ( grade < 30 ) {
|
124 |
+
description = {
|
125 |
+
'score' : grade,
|
126 |
+
'gradeLevel' : 'College Graduate',
|
127 |
+
'explanation': 'Difficult to read.',
|
128 |
+
lengthScore : {
|
129 |
+
'status' : 'red',
|
130 |
+
'msg' : _bgseoContentAnalysis.readingEase.badLow,
|
131 |
+
},
|
132 |
+
};
|
133 |
+
}
|
134 |
+
// Add translated score string to message.
|
135 |
+
scoreTranslated = _bgseoContentAnalysis.readingEase.score.printf( grade ) + ' ';
|
136 |
+
description.lengthScore.msg = description.lengthScore.msg.replace( /^/, scoreTranslated );
|
137 |
+
|
138 |
+
return description;
|
139 |
+
},
|
140 |
+
};
|
141 |
+
|
142 |
+
self = api.Readability;
|
143 |
+
|
144 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-report.js
ADDED
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid TinyMCE Analysis.
|
12 |
+
*
|
13 |
+
* This is responsible for generating the actual reports
|
14 |
+
* displayed within the BoldGrid SEO Dashboard when the user
|
15 |
+
* is on a page or a post.
|
16 |
+
*
|
17 |
+
* @since 1.3.1
|
18 |
+
*/
|
19 |
+
api.Report = {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Initialize TinyMCE Content.
|
23 |
+
*
|
24 |
+
* @since 1.3.1
|
25 |
+
*/
|
26 |
+
init : function () {
|
27 |
+
$( document ).ready( self.onReady );
|
28 |
+
},
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
32 |
+
*
|
33 |
+
* @since 1.3.1
|
34 |
+
*/
|
35 |
+
onReady : function() {
|
36 |
+
self.getSettings();
|
37 |
+
self.generateReport();
|
38 |
+
},
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Cache selectors
|
42 |
+
*
|
43 |
+
* @since 1.3.1
|
44 |
+
*/
|
45 |
+
getSettings : function() {
|
46 |
+
self.settings = {
|
47 |
+
title : $( '#boldgrid-seo-field-meta_title' ),
|
48 |
+
description : $( '#boldgrid-seo-field-meta_description' ),
|
49 |
+
wordCounter : $( '#wp-word-count .word-count' ),
|
50 |
+
content : $( '#content' ),
|
51 |
+
};
|
52 |
+
},
|
53 |
+
|
54 |
+
getWordCount : function() {
|
55 |
+
return Number( self.settings.wordCounter.text() );
|
56 |
+
},
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Generate the Report based on analysis done.
|
60 |
+
*
|
61 |
+
* This will generate a report object and then trigger the
|
62 |
+
* reporter event, so that the model is updated and changes
|
63 |
+
* are reflected live for the user in their SEO Dashboard.
|
64 |
+
*
|
65 |
+
* @since 1.3.1
|
66 |
+
*/
|
67 |
+
generateReport : function() {
|
68 |
+
if ( _.isUndefined( self.settings ) ) return;
|
69 |
+
$( document ).on( 'bgseo-analysis', function( e, eventInfo ) {
|
70 |
+
var words, titleLength, descriptionLength;
|
71 |
+
|
72 |
+
// Get length of title field.
|
73 |
+
titleLength = self.settings.title.val().length;
|
74 |
+
|
75 |
+
// Get length of description field.
|
76 |
+
descriptionLength = self.settings.description.val().length;
|
77 |
+
|
78 |
+
if ( eventInfo.words ) {
|
79 |
+
_( report.textstatistics ).extend({
|
80 |
+
recommendedKeywords : api.Keywords.recommendedKeywords( eventInfo.words, 1 ),
|
81 |
+
customKeyword : api.Keywords.getKeyword(),
|
82 |
+
});
|
83 |
+
}
|
84 |
+
|
85 |
+
// Listen for event changes being triggered.
|
86 |
+
if ( eventInfo ) {
|
87 |
+
// Listen for changes to raw HTML in editor.
|
88 |
+
if ( eventInfo.raw ) {
|
89 |
+
var raws = eventInfo.raw;
|
90 |
+
|
91 |
+
var h1 = $( raws ).find( 'h1' ),
|
92 |
+
h2 = $( raws ).find( 'h2' ),
|
93 |
+
headings = {};
|
94 |
+
|
95 |
+
headings = {
|
96 |
+
h1Count : h1.length,
|
97 |
+
h1text : api.Headings.getHeadingText( h1 ),
|
98 |
+
h2Count : h2.length,
|
99 |
+
h2text : api.Headings.getHeadingText( h2 ),
|
100 |
+
imageCount: $( raws ).find( 'img' ).length,
|
101 |
+
};
|
102 |
+
// Set the heading counts and image count found in new content update.
|
103 |
+
_( report.rawstatistics ).extend( headings );
|
104 |
+
}
|
105 |
+
|
106 |
+
if ( eventInfo.keywords ) {
|
107 |
+
_( report.bgseo_keywords ).extend({
|
108 |
+
keywordPhrase: {
|
109 |
+
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
|
110 |
+
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
|
111 |
+
},
|
112 |
+
keywordTitle : {
|
113 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
114 |
+
},
|
115 |
+
keywordDescription : {
|
116 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
117 |
+
},
|
118 |
+
keywordContent : {
|
119 |
+
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
|
120 |
+
},
|
121 |
+
keywordHeadings : {
|
122 |
+
length : api.Headings.keywords( api.Headings.getRealHeadingCount() ),
|
123 |
+
lengthScore : api.Keywords.headingScore( api.Headings.keywords( api.Headings.getRealHeadingCount() ) ),
|
124 |
+
},
|
125 |
+
customKeyword : eventInfo.keywords.keyword,
|
126 |
+
});
|
127 |
+
}
|
128 |
+
|
129 |
+
// Listen for changes to the actual text entered by user.
|
130 |
+
if ( eventInfo.text ) {
|
131 |
+
var kw, headingCount = api.Headings.getRealHeadingCount(),
|
132 |
+
content = eventInfo.text,
|
133 |
+
raw = ! tinyMCE.activeEditor || tinyMCE.activeEditor.hidden ? api.Words.words( self.settings.content.val() ) : api.Words.words( tinyMCE.activeEditor.getContent({ format : 'raw' }) );
|
134 |
+
|
135 |
+
// Get length of title field.
|
136 |
+
titleLength = self.settings.title.val().length;
|
137 |
+
|
138 |
+
// Get length of description field.
|
139 |
+
descriptionLength = self.settings.description.val().length;
|
140 |
+
|
141 |
+
// Set the placeholder attribute once the keyword has been obtained.
|
142 |
+
kw = api.Keywords.recommendedKeywords( raw, 1 );
|
143 |
+
if ( ! _.isUndefined( kw ) && ! _.isUndefined( kw[0] ) ) api.Keywords.setPlaceholder( kw[0][0] );
|
144 |
+
|
145 |
+
// Set the default report items.
|
146 |
+
_( report ).extend({
|
147 |
+
bgseo_meta : {
|
148 |
+
title : {
|
149 |
+
length : titleLength,
|
150 |
+
lengthScore : api.Title.titleScore( titleLength ),
|
151 |
+
},
|
152 |
+
description : {
|
153 |
+
length : descriptionLength,
|
154 |
+
lengthScore : api.Description.descriptionScore( descriptionLength ),
|
155 |
+
keywordUsage : api.Description.keywords(),
|
156 |
+
},
|
157 |
+
titleKeywordUsage : {
|
158 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
159 |
+
},
|
160 |
+
descKeywordUsage : {
|
161 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
162 |
+
},
|
163 |
+
sectionScore : {},
|
164 |
+
sectionStatus : {},
|
165 |
+
},
|
166 |
+
|
167 |
+
bgseo_visibility : {
|
168 |
+
robotIndex : {
|
169 |
+
lengthScore: api.Robots.indexScore(),
|
170 |
+
},
|
171 |
+
robotFollow : {
|
172 |
+
lengthScore: api.Robots.followScore(),
|
173 |
+
},
|
174 |
+
sectionScore : {},
|
175 |
+
sectionStatus : {},
|
176 |
+
},
|
177 |
+
|
178 |
+
bgseo_keywords : {
|
179 |
+
|
180 |
+
keywordPhrase: {
|
181 |
+
length : api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ),
|
182 |
+
lengthScore : api.Keywords.keywordPhraseScore( api.Keywords.phraseLength( api.Keywords.settings.keyword.val() ) ),
|
183 |
+
},
|
184 |
+
keywordTitle : {
|
185 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
186 |
+
},
|
187 |
+
keywordDescription : {
|
188 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
189 |
+
},
|
190 |
+
keywordContent : {
|
191 |
+
lengthScore : api.Keywords.contentScore( api.ContentAnalysis.keywords( api.TinyMCE.getContent().text ) ),
|
192 |
+
},
|
193 |
+
keywordHeadings : {
|
194 |
+
length : api.Headings.keywords( headingCount ),
|
195 |
+
lengthScore : api.Keywords.headingScore( api.Headings.keywords( headingCount ) ),
|
196 |
+
},
|
197 |
+
image : {
|
198 |
+
length : report.rawstatistics.imageCount,
|
199 |
+
lengthScore : api.ContentAnalysis.seoImageLengthScore( report.rawstatistics.imageCount ),
|
200 |
+
},
|
201 |
+
headings : headingCount,
|
202 |
+
wordCount : {
|
203 |
+
length : self.getWordCount(),
|
204 |
+
lengthScore : api.ContentAnalysis.seoContentLengthScore( self.getWordCount() ),
|
205 |
+
},
|
206 |
+
sectionScore: {},
|
207 |
+
sectionStatus: {},
|
208 |
+
},
|
209 |
+
|
210 |
+
textstatistics : {
|
211 |
+
recommendedKeywords : kw,
|
212 |
+
recommendedCount : api.Keywords.getRecommendedCount( raw ),
|
213 |
+
keywordDensity : api.Keywords.keywordDensity( content, api.Keywords.getKeyword() ),
|
214 |
+
},
|
215 |
+
|
216 |
+
});
|
217 |
+
}
|
218 |
+
|
219 |
+
// Listen to changes to the SEO Title and update report.
|
220 |
+
if ( eventInfo.titleLength ) {
|
221 |
+
_( report.bgseo_meta.title ).extend({
|
222 |
+
length : eventInfo.titleLength,
|
223 |
+
lengthScore : api.Title.titleScore( eventInfo.titleLength ),
|
224 |
+
});
|
225 |
+
|
226 |
+
_( report.bgseo_meta.titleKeywordUsage ).extend({
|
227 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
228 |
+
});
|
229 |
+
|
230 |
+
_( report.bgseo_keywords.keywordTitle ).extend({
|
231 |
+
lengthScore : api.Keywords.titleScore( api.Title.keywords() ),
|
232 |
+
});
|
233 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
234 |
+
}
|
235 |
+
|
236 |
+
// Listen to changes to the SEO Description and update report.
|
237 |
+
if ( eventInfo.descLength ) {
|
238 |
+
|
239 |
+
_( report.bgseo_meta.description ).extend({
|
240 |
+
length : eventInfo.descLength,
|
241 |
+
lengthScore: api.Description.descriptionScore( eventInfo.descLength ),
|
242 |
+
});
|
243 |
+
|
244 |
+
_( report.bgseo_meta.descKeywordUsage ).extend({
|
245 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
246 |
+
});
|
247 |
+
|
248 |
+
_( report.bgseo_keywords.keywordDescription ).extend({
|
249 |
+
lengthScore : api.Keywords.descriptionScore( api.Description.keywords() ),
|
250 |
+
});
|
251 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
252 |
+
}
|
253 |
+
|
254 |
+
// Listen for changes to noindex/index and update report.
|
255 |
+
if ( eventInfo.robotIndex ) {
|
256 |
+
_( report.bgseo_visibility.robotIndex ).extend({
|
257 |
+
lengthScore : eventInfo.robotIndex,
|
258 |
+
});
|
259 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
260 |
+
}
|
261 |
+
|
262 |
+
// Listen for changes to nofollow/follow and update report.
|
263 |
+
if ( eventInfo.robotFollow ) {
|
264 |
+
_( report.bgseo_visibility.robotFollow ).extend({
|
265 |
+
lengthScore : eventInfo.robotFollow,
|
266 |
+
});
|
267 |
+
self.settings.content.trigger( 'bgseo-analysis', [ api.TinyMCE.getContent() ] );
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
// Send the final analysis to display the report.
|
272 |
+
self.settings.content.trigger( 'bgseo-report', [ report ] );
|
273 |
+
});
|
274 |
+
},
|
275 |
+
|
276 |
+
/**
|
277 |
+
* Get's the current report that's generated for output.
|
278 |
+
*
|
279 |
+
* This is used for debugging, and to also obtain the current report in
|
280 |
+
* other classes to perform scoring, analysis, and status indicator updates.
|
281 |
+
*
|
282 |
+
* @since 1.3.1
|
283 |
+
*
|
284 |
+
* @returns {Object} report The report data that's currently displayed.
|
285 |
+
*/
|
286 |
+
get : function( key ) {
|
287 |
+
var data = {};
|
288 |
+
if ( _.isUndefined( key ) ) {
|
289 |
+
data = report;
|
290 |
+
} else {
|
291 |
+
data = _.pickDeep( report, key );
|
292 |
+
}
|
293 |
+
|
294 |
+
return data;
|
295 |
+
},
|
296 |
+
};
|
297 |
+
|
298 |
+
self = api.Report;
|
299 |
+
|
300 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-robots.js
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var BOLDGRID = BOLDGRID || {};
|
2 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
3 |
+
|
4 |
+
( function ( $ ) {
|
5 |
+
|
6 |
+
'use strict';
|
7 |
+
|
8 |
+
var self, report, api;
|
9 |
+
|
10 |
+
api = BOLDGRID.SEO;
|
11 |
+
report = api.report;
|
12 |
+
|
13 |
+
|
14 |
+
|
15 |
+
/**
|
16 |
+
* BoldGrid SEO Robots.
|
17 |
+
*
|
18 |
+
* This is responsible for the noindex and nofollow checkbox
|
19 |
+
* listeners, and returning status/scores for each.
|
20 |
+
*
|
21 |
+
* @since 1.3.1
|
22 |
+
*/
|
23 |
+
api.Robots = {
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Initialize BoldGrid SEO Robots.
|
27 |
+
*
|
28 |
+
* @since 1.3.1
|
29 |
+
*/
|
30 |
+
init : function () {
|
31 |
+
$( document ).ready( self.onReady );
|
32 |
+
},
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
36 |
+
*
|
37 |
+
* @since 1.3.1
|
38 |
+
*/
|
39 |
+
onReady : function() {
|
40 |
+
self.getSettings();
|
41 |
+
self._index();
|
42 |
+
self._follow();
|
43 |
+
},
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Cache selectors
|
47 |
+
*
|
48 |
+
* @since 1.3.1
|
49 |
+
*/
|
50 |
+
getSettings : function() {
|
51 |
+
self.settings = {
|
52 |
+
indexInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index]' ),
|
53 |
+
noIndex : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_index][value="noindex"]' ),
|
54 |
+
followInput : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow]' ),
|
55 |
+
noFollow : $( 'input[name=butterbean_boldgrid_seo_setting_bgseo_robots_follow][value="nofollow"]' ),
|
56 |
+
};
|
57 |
+
},
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Sets up event listener for index/noindex radios.
|
61 |
+
*
|
62 |
+
* Listens for changes being made on the radios, and then
|
63 |
+
* triggers the reporter to be updated with new status/score.
|
64 |
+
*
|
65 |
+
* @since 1.3.1
|
66 |
+
*/
|
67 |
+
_index : function() {
|
68 |
+
self.settings.indexInput.on( 'change', function() {
|
69 |
+
$( this ).trigger( 'bgseo-analysis', [{ 'robotIndex': self.indexScore() }] );
|
70 |
+
});
|
71 |
+
},
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Gets score of index/noindex status.
|
75 |
+
*
|
76 |
+
* Checks if index/noindex is checked and returns appropriate
|
77 |
+
* status message and indicator.
|
78 |
+
*
|
79 |
+
* @since 1.3.1
|
80 |
+
* @returns {Object} Contains status indicator color and message to update.
|
81 |
+
*/
|
82 |
+
indexScore : function() {
|
83 |
+
var msg;
|
84 |
+
|
85 |
+
// Index radio is selected.
|
86 |
+
msg = {
|
87 |
+
status: 'green',
|
88 |
+
msg: _bgseoContentAnalysis.noIndex.good,
|
89 |
+
};
|
90 |
+
|
91 |
+
// Noindex radio is selected.
|
92 |
+
if ( self.settings.noIndex.is( ':checked' ) ) {
|
93 |
+
msg = {
|
94 |
+
status: 'red',
|
95 |
+
msg: _bgseoContentAnalysis.noIndex.bad,
|
96 |
+
};
|
97 |
+
}
|
98 |
+
|
99 |
+
return msg;
|
100 |
+
},
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Sets up event listener for follow/nofollow radios.
|
104 |
+
*
|
105 |
+
* Listens for changes being made on the radios, and then
|
106 |
+
* triggers the reporter to be updated with new status/score.
|
107 |
+
*
|
108 |
+
* @since 1.3.1
|
109 |
+
*/
|
110 |
+
_follow : function() {
|
111 |
+
// Listen for changes to input value.
|
112 |
+
self.settings.followInput.on( 'change', function() {
|
113 |
+
$( this ).trigger( 'bgseo-analysis', [{ 'robotFollow': self.followScore() }] );
|
114 |
+
});
|
115 |
+
},
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Gets score of follow/nofollow status.
|
119 |
+
*
|
120 |
+
* Checks if follow or nofollow is checked, and returns appropriate
|
121 |
+
* status message and indicator.
|
122 |
+
*
|
123 |
+
* @since 1.3.1
|
124 |
+
* @returns {Object} Contains status indicator color and message to update.
|
125 |
+
*/
|
126 |
+
followScore : function() {
|
127 |
+
var msg = {
|
128 |
+
status: 'green',
|
129 |
+
msg: _bgseoContentAnalysis.noFollow.good,
|
130 |
+
};
|
131 |
+
|
132 |
+
if ( self.settings.noFollow.is( ':checked' ) ) {
|
133 |
+
msg = {
|
134 |
+
status: 'yellow',
|
135 |
+
msg: _bgseoContentAnalysis.noFollow.bad,
|
136 |
+
};
|
137 |
+
}
|
138 |
+
|
139 |
+
return msg;
|
140 |
+
},
|
141 |
+
};
|
142 |
+
|
143 |
+
self = api.Robots;
|
144 |
+
|
145 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-sections.js
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Sections.
|
12 |
+
*
|
13 |
+
* This is responsible for section related statuses and modifications.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Sections = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Gets the status for a section.
|
21 |
+
*
|
22 |
+
* This will get the status based on the scores received for each
|
23 |
+
* section and return the status color as the report is updated.
|
24 |
+
*
|
25 |
+
* @since 1.3.1
|
26 |
+
*
|
27 |
+
* @param {Object} sectionScores The scores for the section.
|
28 |
+
*
|
29 |
+
* @returns {string} status The status color to assign to the section.
|
30 |
+
*/
|
31 |
+
status : function( sectionScores ) {
|
32 |
+
// Default status is set to green.
|
33 |
+
var status = 'green';
|
34 |
+
|
35 |
+
// Check if we have any red or yellow statuses and update as needed.
|
36 |
+
if ( sectionScores.red > 0 ) {
|
37 |
+
status = 'red';
|
38 |
+
} else if ( sectionScores.yellow > 0 ) {
|
39 |
+
status = 'yellow';
|
40 |
+
}
|
41 |
+
|
42 |
+
return status;
|
43 |
+
},
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Gets the score and status of a section.
|
47 |
+
*
|
48 |
+
* This is responsible for getting the count of statuses that
|
49 |
+
* are set for each item in the report for a section. It will
|
50 |
+
* return the data that is added to the report..
|
51 |
+
*
|
52 |
+
* @since 1.3.1
|
53 |
+
*
|
54 |
+
* @param {Object} section The section to get a score for.
|
55 |
+
*
|
56 |
+
* @returns {Object} data Contains the section status scores and section status.
|
57 |
+
*/
|
58 |
+
score : function( section ) {
|
59 |
+
|
60 |
+
var sectionScores, score, data;
|
61 |
+
|
62 |
+
// Set default counters for each status.
|
63 |
+
sectionScores = { red: 0, green : 0, yellow : 0 };
|
64 |
+
|
65 |
+
// Get the count of scores in object by status.
|
66 |
+
score = _( section ).countBy( function( items ) {
|
67 |
+
return ! _.isUndefined( items.lengthScore ) && 'sectionScore' !== _.property( 'sectionScore' )( section ) ? items.lengthScore.status : '';
|
68 |
+
});
|
69 |
+
|
70 |
+
// Update the object with the new count.
|
71 |
+
_( score ).each( function( value, key ) {
|
72 |
+
if ( _.has( sectionScores , key ) ) {
|
73 |
+
sectionScores[key] = value;
|
74 |
+
}
|
75 |
+
});
|
76 |
+
|
77 |
+
// Update the section's score and status.
|
78 |
+
data = {
|
79 |
+
sectionScore : sectionScores,
|
80 |
+
sectionStatus: self.status( sectionScores ),
|
81 |
+
};
|
82 |
+
|
83 |
+
return data;
|
84 |
+
},
|
85 |
+
|
86 |
+
removeStatus : function( selector ) {
|
87 |
+
selector.removeClass( 'red yellow green' );
|
88 |
+
},
|
89 |
+
|
90 |
+
navHighlight : function( report ) {
|
91 |
+
_.each( butterbean.models.sections, function( item ) {
|
92 |
+
var selector,
|
93 |
+
manager = item.get( 'manager' ),
|
94 |
+
name = item.get( 'name' );
|
95 |
+
|
96 |
+
selector = $( '[href="#butterbean-' + manager + '-section-' + name + '"]' ).closest( 'li' );
|
97 |
+
self.removeStatus( selector );
|
98 |
+
selector.addClass( report[name].sectionStatus );
|
99 |
+
});
|
100 |
+
},
|
101 |
+
overviewStatus : function( report ) {
|
102 |
+
var selector = $( "#butterbean-ui-boldgrid_seo.postbox > h2 > span:contains('Easy SEO')" );
|
103 |
+
self.removeStatus( selector );
|
104 |
+
selector.addClass( 'overview-status ' + report.bgseo_keywords.overview.status );
|
105 |
+
}
|
106 |
+
};
|
107 |
+
|
108 |
+
self = api.Sections;
|
109 |
+
|
110 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-tinymce.js
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid TinyMCE Analysis.
|
12 |
+
*
|
13 |
+
* This is responsible for generating the actual reports
|
14 |
+
* displayed within the BoldGrid SEO Dashboard when the user
|
15 |
+
* is on a page or a post.
|
16 |
+
*
|
17 |
+
* @since 1.3.1
|
18 |
+
*/
|
19 |
+
api.TinyMCE = {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Initialize TinyMCE Content.
|
23 |
+
*
|
24 |
+
* @since 1.3.1
|
25 |
+
*/
|
26 |
+
init : function () {
|
27 |
+
self.onloadContent();
|
28 |
+
$( document ).ready( function() {
|
29 |
+
self.editorChange();
|
30 |
+
});
|
31 |
+
},
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Runs actions on window load to prepare for analysis.
|
35 |
+
*
|
36 |
+
* This method gets the current editor in use by the user on the
|
37 |
+
* initial page load ( text editor or visual editor ), and also
|
38 |
+
* is responsible for creating the iframe preview of the page/post
|
39 |
+
* so we can get the raw html in use by the template/theme the user
|
40 |
+
* has activated.
|
41 |
+
*
|
42 |
+
* @since 1.3.1
|
43 |
+
*/
|
44 |
+
onloadContent: function() {
|
45 |
+
var text,
|
46 |
+
editor = $( '#content.wp-editor-area[aria-hidden=false]' );
|
47 |
+
|
48 |
+
$( window ).on( 'load bgseo-media-inserted', function() {
|
49 |
+
var content = self.getContent();
|
50 |
+
|
51 |
+
// Get rendered page content from frontend site.
|
52 |
+
self.getRenderedContent();
|
53 |
+
|
54 |
+
// Trigger the content analysis for the tinyMCE content.
|
55 |
+
_.defer( function() {
|
56 |
+
$( '#content' ).trigger( 'bgseo-analysis', [content] );
|
57 |
+
});
|
58 |
+
});
|
59 |
+
},
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Gets the content from TinyMCE or the text editor for analysis.
|
63 |
+
*
|
64 |
+
* @since 1.3.1
|
65 |
+
*
|
66 |
+
* @returns {Object} content Contains content in raw and text formats.
|
67 |
+
*/
|
68 |
+
getContent : function() {
|
69 |
+
var content;
|
70 |
+
// Get the content of the visual editor or text editor that's present.
|
71 |
+
if ( tinymce.ActiveEditor ) {
|
72 |
+
content = tinyMCE.get( wpActiveEditor ).getContent();
|
73 |
+
} else {
|
74 |
+
content = $( '#content' ).val();
|
75 |
+
// Remove newlines and carriage returns.
|
76 |
+
content = content.replace( /\r?\n|\r/g, '' );
|
77 |
+
}
|
78 |
+
|
79 |
+
var rawContent = $.parseHTML( content );
|
80 |
+
|
81 |
+
// Stores raw and stripped down versions of the content for analysis.
|
82 |
+
content = {
|
83 |
+
'raw': rawContent,
|
84 |
+
'text': self.stripper( content.toLowerCase() ),
|
85 |
+
};
|
86 |
+
|
87 |
+
return content;
|
88 |
+
},
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Only ajax for preview if permalink is available. This only
|
92 |
+
* impacts "New" page and posts. To counter
|
93 |
+
* this we will disable the checks made until the content has had
|
94 |
+
* a chance to be updated. We will store the found headings minus
|
95 |
+
* the initial found headings in the content, so we know what the
|
96 |
+
* template has in use on the actual rendered page.
|
97 |
+
*
|
98 |
+
* @since 1.3.1
|
99 |
+
*
|
100 |
+
* @returns null No return.
|
101 |
+
*/
|
102 |
+
getRenderedContent : function() {
|
103 |
+
var renderedContent, preview;
|
104 |
+
|
105 |
+
// Get the preview url from WordPress.
|
106 |
+
preview = $( '#preview-action > .preview.button' ).attr( 'href' );
|
107 |
+
|
108 |
+
if ( $( '#sample-permalink' ).length ) {
|
109 |
+
// Only run this once after the initial iframe has loaded to get current template stats.
|
110 |
+
$.get( preview, function( renderedTemplate ) {
|
111 |
+
var headings, h1, h2, $rendered;
|
112 |
+
|
113 |
+
// The rendered page content.
|
114 |
+
$rendered = $( renderedTemplate );
|
115 |
+
|
116 |
+
// H1's that appear in rendered content.
|
117 |
+
h1 = $rendered.find( 'h1' );
|
118 |
+
// HS's that appear in rendered content.
|
119 |
+
h2 = $rendered.find( 'h2' );
|
120 |
+
|
121 |
+
// The rendered content stats.
|
122 |
+
renderedContent = {
|
123 |
+
h1Count : h1.length - report.rawstatistics.h1Count,
|
124 |
+
h1text : _.filter( api.Headings.getHeadingText( h1 ), function( obj ){
|
125 |
+
return ! _.findWhere( report.rawstatistics.h1text, obj );
|
126 |
+
}),
|
127 |
+
h2Count : h2.length - report.rawstatistics.h2Count,
|
128 |
+
h2text : _.filter( api.Headings.getHeadingText( h2 ), function( obj ){
|
129 |
+
return ! _.findWhere( report.rawstatistics.h2text, obj );
|
130 |
+
}),
|
131 |
+
};
|
132 |
+
|
133 |
+
// Add the rendered stats to our report for use later.
|
134 |
+
_.extend( report, { rendered : renderedContent } );
|
135 |
+
|
136 |
+
// Trigger the SEO report to rebuild in the template after initial stats are created.
|
137 |
+
$( '#content' ).trigger( 'bgseo-analysis', [ self.getContent() ] );
|
138 |
+
|
139 |
+
}, 'html' );
|
140 |
+
}
|
141 |
+
},
|
142 |
+
/**
|
143 |
+
* Listens for changes made in the text editor mode.
|
144 |
+
*
|
145 |
+
* @since 1.3.1
|
146 |
+
*
|
147 |
+
* @returns {string} text The new content to perform analysis on.
|
148 |
+
*/
|
149 |
+
editorChange: function() {
|
150 |
+
var text, targetId;
|
151 |
+
$( '#content.wp-editor-area' ).on( 'input propertychange paste nodechange', function() {
|
152 |
+
targetId = $( this ).attr( 'id' );
|
153 |
+
text = self.wpContent( targetId );
|
154 |
+
});
|
155 |
+
|
156 |
+
return text;
|
157 |
+
},
|
158 |
+
|
159 |
+
/**
|
160 |
+
* This gets the content from the TinyMCE Visual editor.
|
161 |
+
*
|
162 |
+
* @since 1.3.1
|
163 |
+
*
|
164 |
+
* @returns {string} text
|
165 |
+
*/
|
166 |
+
tmceChange: function( e ) {
|
167 |
+
var text, targetId;
|
168 |
+
|
169 |
+
targetId = e.target.id;
|
170 |
+
text = self.wpContent( targetId );
|
171 |
+
|
172 |
+
return text;
|
173 |
+
},
|
174 |
+
|
175 |
+
/**
|
176 |
+
* Checks which editor is the active editor.
|
177 |
+
*
|
178 |
+
* After checking the editor, it will obtain the content and trigger
|
179 |
+
* the report generation with the new user input.
|
180 |
+
*
|
181 |
+
* @since 1.3.1
|
182 |
+
*/
|
183 |
+
wpContent : function( targetId ) {
|
184 |
+
var text = {};
|
185 |
+
|
186 |
+
switch ( targetId ) {
|
187 |
+
// Grab text from TinyMCE Editor.
|
188 |
+
case 'tinymce' :
|
189 |
+
// Only do this if page/post editor has TinyMCE as active editor.
|
190 |
+
if ( tinymce.activeEditor )
|
191 |
+
// Define text as the content of the current TinyMCE instance.
|
192 |
+
text = tinyMCE.get( wpActiveEditor ).getContent();
|
193 |
+
break;
|
194 |
+
case 'content' :
|
195 |
+
text = $( '#content' ).val();
|
196 |
+
text = text.replace( /\r?\n|\r/g, '' );
|
197 |
+
break;
|
198 |
+
}
|
199 |
+
|
200 |
+
// Convert raw text to DOM nodes.
|
201 |
+
var rawText = $.parseHTML( text );
|
202 |
+
|
203 |
+
text = {
|
204 |
+
'raw': rawText,
|
205 |
+
'text': self.stripper( text.toLowerCase() ),
|
206 |
+
};
|
207 |
+
|
208 |
+
// Trigger the text analysis for report.
|
209 |
+
$( '#content' ).trigger( 'bgseo-analysis', [text] );
|
210 |
+
},
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Strips out unwanted html.
|
214 |
+
*
|
215 |
+
* This is helpful in removing the remaining traces of HTML
|
216 |
+
* that is sometimes leftover to form our clean text output and
|
217 |
+
* run our text analysis on.
|
218 |
+
*
|
219 |
+
* @since 1.3.1
|
220 |
+
*
|
221 |
+
* @returns {string} The content with any remaining html removed.
|
222 |
+
*/
|
223 |
+
stripper: function( html ) {
|
224 |
+
var tmp;
|
225 |
+
|
226 |
+
tmp = document.implementation.createHTMLDocument( 'New' ).body;
|
227 |
+
tmp.innerHTML = html;
|
228 |
+
|
229 |
+
return tmp.textContent || tmp.innerText || " ";
|
230 |
+
},
|
231 |
+
};
|
232 |
+
|
233 |
+
self = api.TinyMCE;
|
234 |
+
|
235 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-title.js
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Title.
|
12 |
+
*
|
13 |
+
* This is responsible for the SEO Title Grading.
|
14 |
+
*
|
15 |
+
* @since 1.3.1
|
16 |
+
*/
|
17 |
+
api.Title = {
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Initialize SEO Title Analysis.
|
21 |
+
*
|
22 |
+
* @since 1.3.1
|
23 |
+
*/
|
24 |
+
init : function () {
|
25 |
+
$( document ).ready( self.onReady );
|
26 |
+
},
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
30 |
+
*
|
31 |
+
* @since 1.3.1
|
32 |
+
*/
|
33 |
+
onReady : function() {
|
34 |
+
self.getSettings();
|
35 |
+
self._title();
|
36 |
+
},
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Cache selectors
|
40 |
+
*
|
41 |
+
* @since 1.3.1
|
42 |
+
*/
|
43 |
+
getSettings : function() {
|
44 |
+
self.settings = {
|
45 |
+
title : $( '#boldgrid-seo-field-meta_title' ),
|
46 |
+
};
|
47 |
+
},
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Gets the SEO Title.
|
51 |
+
*
|
52 |
+
* @since 1.3.1
|
53 |
+
*
|
54 |
+
* @returns {Object} title Contains wrapped set with BoldGrid SEO Title.
|
55 |
+
*/
|
56 |
+
getTitle : function() {
|
57 |
+
return self.settings.title;
|
58 |
+
},
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Sets up event listener for changes made to the SEO Title.
|
62 |
+
*
|
63 |
+
* Listens for changes being made to the SEO Title, and then
|
64 |
+
* triggers the reporter to be updated with new status/score.
|
65 |
+
*
|
66 |
+
* @since 1.3.1
|
67 |
+
*/
|
68 |
+
_title: function() {
|
69 |
+
// Listen for changes to input value.
|
70 |
+
self.settings.title.on( 'input propertychange paste', _.debounce( function() {
|
71 |
+
self.settings.title.trigger( 'bgseo-analysis', [{ titleLength : self.settings.title.val().length }] );
|
72 |
+
}, 1000 ) );
|
73 |
+
},
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Gets score of the SEO Title.
|
77 |
+
*
|
78 |
+
* Checks the length provided and returns a score for the SEO
|
79 |
+
* title. This score is based on character count.
|
80 |
+
*
|
81 |
+
* @since 1.3.1
|
82 |
+
*
|
83 |
+
* @param {Number} titleLength The length of the title to generate score for.
|
84 |
+
*
|
85 |
+
* @returns {Object} msg Contains status indicator color and message to update.
|
86 |
+
*/
|
87 |
+
titleScore: function( titleLength ) {
|
88 |
+
var msg = {}, title;
|
89 |
+
|
90 |
+
title = _bgseoContentAnalysis.seoTitle.length;
|
91 |
+
|
92 |
+
// No title entered.
|
93 |
+
if ( titleLength === 0 ) {
|
94 |
+
msg = {
|
95 |
+
status: 'red',
|
96 |
+
msg: title.badEmpty,
|
97 |
+
};
|
98 |
+
}
|
99 |
+
|
100 |
+
// Title is 1-30 characters.
|
101 |
+
if ( titleLength.isBetween( 0, title.okScore + 1 ) ) {
|
102 |
+
msg = {
|
103 |
+
status: 'yellow',
|
104 |
+
msg: title.ok,
|
105 |
+
};
|
106 |
+
}
|
107 |
+
|
108 |
+
// Title is 30-70 characters.
|
109 |
+
if ( titleLength.isBetween( title.okScore - 1, title.goodScore + 1 ) ) {
|
110 |
+
msg = {
|
111 |
+
status: 'green',
|
112 |
+
msg: title.good,
|
113 |
+
};
|
114 |
+
}
|
115 |
+
|
116 |
+
// Title is grater than 70 characters.
|
117 |
+
if ( titleLength > title.goodScore ) {
|
118 |
+
msg = {
|
119 |
+
status: 'red',
|
120 |
+
msg: title.badLong,
|
121 |
+
};
|
122 |
+
}
|
123 |
+
|
124 |
+
return msg;
|
125 |
+
},
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Get count of keywords used in the title.
|
129 |
+
*
|
130 |
+
* This checks the title for keyword frequency.
|
131 |
+
*
|
132 |
+
* @since 1.3.1
|
133 |
+
*
|
134 |
+
* @param {String} text (Optional) The text to search for keyword in.
|
135 |
+
* @param {String} keyword (Optional) The keyword to search for.
|
136 |
+
*
|
137 |
+
* @returns {Number} Count of times keyword appears in text.
|
138 |
+
*/
|
139 |
+
keywords : function( text, keyword ) {
|
140 |
+
if ( 0 === arguments.length ) {
|
141 |
+
keyword = api.Keywords.getKeyword();
|
142 |
+
text = self.getTitle().val();
|
143 |
+
} else if ( 1 === arguments.length ) {
|
144 |
+
keyword = api.Keywords.getKeyword();
|
145 |
+
}
|
146 |
+
|
147 |
+
// Normalize user input.
|
148 |
+
text = text.toLowerCase();
|
149 |
+
|
150 |
+
return text.occurences( keyword );
|
151 |
+
},
|
152 |
+
};
|
153 |
+
|
154 |
+
self = api.Title;
|
155 |
+
|
156 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-tooltips.js
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
report = api.report;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* BoldGrid SEO Tooltips.
|
12 |
+
*
|
13 |
+
* This will add the neccessary functionality for tooltips to be displayed
|
14 |
+
* for each control we create and display.
|
15 |
+
*
|
16 |
+
* @since 1.3.1
|
17 |
+
*/
|
18 |
+
api.Tooltips = {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Initializes BoldGrid SEO Tooltips.
|
22 |
+
*
|
23 |
+
* @since 1.3.1
|
24 |
+
*/
|
25 |
+
init : function () {
|
26 |
+
$( document ).ready( self.onReady );
|
27 |
+
},
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Sets up event listeners and selector cache in settings on document ready.
|
31 |
+
*
|
32 |
+
* @since 1.3.1
|
33 |
+
*/
|
34 |
+
onReady : function() {
|
35 |
+
self.getSettings();
|
36 |
+
self.hideTooltips();
|
37 |
+
self._enableTooltips();
|
38 |
+
self._toggleTooltip();
|
39 |
+
},
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Cache selectors
|
43 |
+
*
|
44 |
+
* @since 1.3.1
|
45 |
+
*/
|
46 |
+
getSettings : function() {
|
47 |
+
self.settings = {
|
48 |
+
description : $( '.butterbean-control .butterbean-description' ),
|
49 |
+
tooltip : $( '<span />', { 'class' : 'bgseo-tooltip dashicons dashicons-editor-help', 'aria-expanded' : 'false' }),
|
50 |
+
onClick : $( '.butterbean-label, .bgseo-tooltip' ),
|
51 |
+
};
|
52 |
+
},
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Toggle Tooltips
|
56 |
+
*
|
57 |
+
* This sets up the event listener for clicks on tooltips or control labels,
|
58 |
+
* which will hide and show the description of the control for the user.
|
59 |
+
*
|
60 |
+
* @since 1.3.1
|
61 |
+
*/
|
62 |
+
_toggleTooltip : function() {
|
63 |
+
self.settings.onClick.on( 'click', function( e ) {
|
64 |
+
self.toggleTooltip( e );
|
65 |
+
});
|
66 |
+
},
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Enables tooltips for any controls that utilize the description field.
|
70 |
+
*
|
71 |
+
* @since 1.3.1
|
72 |
+
*/
|
73 |
+
_enableTooltips : function() {
|
74 |
+
self.settings.description.prev().append( self.settings.tooltip );
|
75 |
+
},
|
76 |
+
|
77 |
+
/**
|
78 |
+
* This handles the toggle of the tooltip open/close.
|
79 |
+
*
|
80 |
+
* @param {Object} e Selector passed from click event.
|
81 |
+
*
|
82 |
+
* @since 1.3.1
|
83 |
+
*/
|
84 |
+
toggleTooltip : function( e ) {
|
85 |
+
$( e.currentTarget ).next( '.butterbean-description' ).slideToggle();
|
86 |
+
},
|
87 |
+
|
88 |
+
/**
|
89 |
+
* This hides all tooltips when api.Tooltips is initialized.
|
90 |
+
*
|
91 |
+
* @since 1.3.1
|
92 |
+
*/
|
93 |
+
hideTooltips : function() {
|
94 |
+
self.settings.description.hide();
|
95 |
+
},
|
96 |
+
};
|
97 |
+
|
98 |
+
self = api.Tooltips;
|
99 |
+
|
100 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-util.js
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var BOLDGRID = BOLDGRID || {};
|
2 |
+
BOLDGRID.SEO = BOLDGRID.SEO || {};
|
3 |
+
|
4 |
+
( function ( $ ) {
|
5 |
+
|
6 |
+
'use strict';
|
7 |
+
|
8 |
+
var self, report, api;
|
9 |
+
|
10 |
+
api = BOLDGRID.SEO;
|
11 |
+
report = api.report;
|
12 |
+
|
13 |
+
/**
|
14 |
+
* BoldGrid SEO Util.
|
15 |
+
*
|
16 |
+
* This will contain any utility functions needed across
|
17 |
+
* all classes.
|
18 |
+
*
|
19 |
+
* @since 1.3.1
|
20 |
+
*/
|
21 |
+
api.Util = {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Initialize Utilities.
|
25 |
+
*
|
26 |
+
* @since 1.3.1
|
27 |
+
*/
|
28 |
+
init : function () {
|
29 |
+
|
30 |
+
_.mixin({
|
31 |
+
/**
|
32 |
+
* Return a copy of the object only containing the whitelisted properties.
|
33 |
+
* Nested properties are concatenated with dots notation.
|
34 |
+
*
|
35 |
+
* Example:
|
36 |
+
* a = { min: 0.5, max : 2.5 };
|
37 |
+
* _.modifyObject( a, function( item ){ return item * item; });
|
38 |
+
*
|
39 |
+
* Returns:
|
40 |
+
* { min: 0.25, max : 6.25 };
|
41 |
+
*
|
42 |
+
* @since 1.3.1
|
43 |
+
*
|
44 |
+
* @param obj
|
45 |
+
*
|
46 |
+
* @returns {Object} Modified object.
|
47 |
+
*/
|
48 |
+
modifyObject : function( object, iteratee ) {
|
49 |
+
return _.object( _.map( object, function( value, key ) {
|
50 |
+
return [ key, iteratee( value ) ];
|
51 |
+
}));
|
52 |
+
},
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Return a copy of the object only containing the whitelisted properties.
|
56 |
+
* Nested properties are concatenated with dots notation.
|
57 |
+
*
|
58 |
+
* Example:
|
59 |
+
* a = {a:'a', b:{c:'c', d:'d', e:'e'}};
|
60 |
+
* _.pickDeep(a, 'b.c','b.d')
|
61 |
+
*
|
62 |
+
* Returns:
|
63 |
+
* {b:{c:'c',d:'d'}}
|
64 |
+
*
|
65 |
+
* @since 1.3.1
|
66 |
+
*
|
67 |
+
* @param obj
|
68 |
+
*
|
69 |
+
* @returns {Object} copy Object containing only properties requested.
|
70 |
+
*/
|
71 |
+
pickDeep : function( obj ) {
|
72 |
+
var copy = {},
|
73 |
+
keys = Array.prototype.concat.apply( Array.prototype, Array.prototype.slice.call( arguments, 1 ) );
|
74 |
+
|
75 |
+
this.each( keys, function( key ) {
|
76 |
+
var subKeys = key.split( '.' );
|
77 |
+
key = subKeys.shift();
|
78 |
+
|
79 |
+
if ( key in obj ) {
|
80 |
+
// pick nested properties
|
81 |
+
if( subKeys.length > 0 ) {
|
82 |
+
// extend property (if defined before)
|
83 |
+
if( copy[ key ] ) {
|
84 |
+
_.extend( copy[ key ], _.pickDeep( obj[ key ], subKeys.join( '.' ) ) );
|
85 |
+
}
|
86 |
+
else {
|
87 |
+
copy[ key ] = _.pickDeep( obj[ key ], subKeys.join( '.' ) );
|
88 |
+
}
|
89 |
+
}
|
90 |
+
else {
|
91 |
+
copy[ key ] = obj[ key ];
|
92 |
+
}
|
93 |
+
}
|
94 |
+
});
|
95 |
+
|
96 |
+
return copy;
|
97 |
+
},
|
98 |
+
});
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Usage: ( n ).isBetween( min, max )
|
102 |
+
*
|
103 |
+
* Gives you bool response if number is within the minimum
|
104 |
+
* and maximum numbers specified for the range.
|
105 |
+
*
|
106 |
+
* @since 1.3.1
|
107 |
+
*
|
108 |
+
* @param {Number} min Minimum number in range to check.
|
109 |
+
* @param {Number} max Maximum number in range to check.
|
110 |
+
*
|
111 |
+
* @returns {bool} Number is/isn't within range passed in params.
|
112 |
+
*/
|
113 |
+
if ( ! Number.prototype.isBetween ) {
|
114 |
+
Number.prototype.isBetween = function( min, max ) {
|
115 |
+
if ( _.isUndefined( min ) ) min = 0;
|
116 |
+
if ( _.isUndefined( max ) ) max = 0;
|
117 |
+
var newMax = Math.max( min, max );
|
118 |
+
var newMin = Math.min( min, max );
|
119 |
+
return this > newMin && this < newMax;
|
120 |
+
};
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Usage: ( n ).rounded( digits )
|
125 |
+
*
|
126 |
+
* Rounds a number to the closest decimal you specify.
|
127 |
+
*
|
128 |
+
* @since 1.3.1
|
129 |
+
*
|
130 |
+
* @param {Number} number Number to round.
|
131 |
+
* @param {Number} digits how many decimal places to round to.
|
132 |
+
*
|
133 |
+
* @returns {Number} rounded The number rounded to specified digits.
|
134 |
+
*/
|
135 |
+
if ( ! Number.prototype.rounded ) {
|
136 |
+
Number.prototype.rounded = function( digits ) {
|
137 |
+
|
138 |
+
if ( _.isUndefined( digits ) ) digits = 0;
|
139 |
+
|
140 |
+
var multiple = Math.pow( 10, digits );
|
141 |
+
var rounded = Math.round( this * multiple ) / multiple;
|
142 |
+
|
143 |
+
return rounded;
|
144 |
+
};
|
145 |
+
}
|
146 |
+
|
147 |
+
if ( ! String.prototype.printf ) {
|
148 |
+
String.prototype.printf = function() {
|
149 |
+
var newStr = this, i = 0;
|
150 |
+
while ( /%s/.test( newStr ) ){
|
151 |
+
newStr = newStr.replace( "%s", arguments[i++] );
|
152 |
+
}
|
153 |
+
|
154 |
+
return newStr;
|
155 |
+
};
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Function that counts occurrences of a substring in a string;
|
160 |
+
*
|
161 |
+
* @param {String} string The string
|
162 |
+
* @param {String} subString The sub string to search for
|
163 |
+
* @param {Boolean} [allowOverlapping] Optional. (Default:false)
|
164 |
+
*
|
165 |
+
* @returns {Number} n The number of times a substring appears in a string.
|
166 |
+
*/
|
167 |
+
if ( ! String.prototype.occurences ) {
|
168 |
+
String.prototype.occurences = function( needle, allowOverlapping ) {
|
169 |
+
|
170 |
+
needle += "";
|
171 |
+
if ( needle.length <= 0 ) return ( this.length + 1 );
|
172 |
+
|
173 |
+
var n = 0,
|
174 |
+
pos = 0,
|
175 |
+
step = allowOverlapping ? 1 : needle.length;
|
176 |
+
|
177 |
+
while ( true ) {
|
178 |
+
pos = this.indexOf( needle, pos );
|
179 |
+
if ( pos >= 0 ) {
|
180 |
+
++n;
|
181 |
+
pos += step;
|
182 |
+
} else break;
|
183 |
+
}
|
184 |
+
|
185 |
+
return n;
|
186 |
+
};
|
187 |
+
}
|
188 |
+
},
|
189 |
+
};
|
190 |
+
|
191 |
+
self = api.Util;
|
192 |
+
|
193 |
+
})( jQuery );
|
assets/js/bgseo/boldgrid-seo-wordcount.js
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function( $, counter ) {
|
2 |
+
|
3 |
+
$( function() {
|
4 |
+
|
5 |
+
var $content = $( '#content' ),
|
6 |
+
$count = $( '#wp-word-count' ).find( '.word-count' ),
|
7 |
+
prevCount = 0,
|
8 |
+
contentEditor,
|
9 |
+
words;
|
10 |
+
|
11 |
+
function update() {
|
12 |
+
var text, count;
|
13 |
+
|
14 |
+
if ( ! contentEditor || contentEditor.isHidden() ) {
|
15 |
+
text = $content.val();
|
16 |
+
} else {
|
17 |
+
text = contentEditor.getContent( { format: 'raw' } );
|
18 |
+
}
|
19 |
+
|
20 |
+
count = counter.count( text );
|
21 |
+
words = BOLDGRID.SEO.Words.words( text );
|
22 |
+
|
23 |
+
if ( count !== prevCount ) {
|
24 |
+
$content.trigger( 'bgseo-analysis', [{ words : words, count : count }] );
|
25 |
+
}
|
26 |
+
|
27 |
+
prevCount = count;
|
28 |
+
}
|
29 |
+
|
30 |
+
$( document ).on( 'tinymce-editor-init', function( event, editor ) {
|
31 |
+
if ( editor.id !== 'content' ) {
|
32 |
+
return;
|
33 |
+
}
|
34 |
+
|
35 |
+
contentEditor = editor;
|
36 |
+
|
37 |
+
editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
|
38 |
+
} );
|
39 |
+
|
40 |
+
$content.on( 'input keyup', _.debounce( update, 1000 ) );
|
41 |
+
|
42 |
+
update();
|
43 |
+
|
44 |
+
} );
|
45 |
+
|
46 |
+
} )( jQuery, new wp.utils.WordCounter() );
|
assets/js/bgseo/boldgrid-seo-words.js
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function() {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
var self, report, api;
|
6 |
+
|
7 |
+
api = BOLDGRID.SEO;
|
8 |
+
|
9 |
+
api.Words = {
|
10 |
+
|
11 |
+
init : function( settings ) {
|
12 |
+
var key,
|
13 |
+
shortcodes;
|
14 |
+
|
15 |
+
if ( settings ) {
|
16 |
+
for ( key in settings ) {
|
17 |
+
if ( settings.hasOwnProperty( key ) ) {
|
18 |
+
self.settings[ key ] = settings[ key ];
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
22 |
+
|
23 |
+
shortcodes = self.settings.l10n.shortcodes;
|
24 |
+
|
25 |
+
if ( shortcodes && shortcodes.length ) {
|
26 |
+
self.settings.shortcodesRegExp = new RegExp( '\\[\\/?(?:' + shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' );
|
27 |
+
}
|
28 |
+
},
|
29 |
+
|
30 |
+
settings : {
|
31 |
+
HTMLRegExp: /<\/?[a-z][^>]*?>/gi,
|
32 |
+
HTMLcommentRegExp: /<!--[\s\S]*?-->/g,
|
33 |
+
spaceRegExp: / | /gi,
|
34 |
+
HTMLEntityRegExp: /&\S+?;/g,
|
35 |
+
connectorRegExp: /--|\u2014/g,
|
36 |
+
removeRegExp: new RegExp( [
|
37 |
+
'[',
|
38 |
+
// Basic Latin (extract)
|
39 |
+
'\u0021-\u0040\u005B-\u0060\u007B-\u007E',
|
40 |
+
// Latin-1 Supplement (extract)
|
41 |
+
'\u0080-\u00BF\u00D7\u00F7',
|
42 |
+
// General Punctuation
|
43 |
+
// Superscripts and Subscripts
|
44 |
+
// Currency Symbols
|
45 |
+
// Combining Diacritical Marks for Symbols
|
46 |
+
// Letterlike Symbols
|
47 |
+
// Number Forms
|
48 |
+
// Arrows
|
49 |
+
// Mathematical Operators
|
50 |
+
// Miscellaneous Technical
|
51 |
+
// Control Pictures
|
52 |
+
// Optical Character Recognition
|
53 |
+
// Enclosed Alphanumerics
|
54 |
+
// Box Drawing
|
55 |
+
// Block Elements
|
56 |
+
// Geometric Shapes
|
57 |
+
// Miscellaneous Symbols
|
58 |
+
// Dingbats
|
59 |
+
// Miscellaneous Mathematical Symbols-A
|
60 |
+
// Supplemental Arrows-A
|
61 |
+
// Braille Patterns
|
62 |
+
// Supplemental Arrows-B
|
63 |
+
// Miscellaneous Mathematical Symbols-B
|
64 |
+
// Supplemental Mathematical Operators
|
65 |
+
// Miscellaneous Symbols and Arrows
|
66 |
+
'\u2000-\u2BFF',
|
67 |
+
// Supplemental Punctuation
|
68 |
+
'\u2E00-\u2E7F',
|
69 |
+
']'
|
70 |
+
].join( '' ), 'g' ),
|
71 |
+
astralRegExp: /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
72 |
+
// regex tested : https://regex101.com/r/vHAwas/2
|
73 |
+
wordsRegExp: /.+?\s+/g,
|
74 |
+
characters_excluding_spacesRegExp: /\S/g,
|
75 |
+
characters_including_spacesRegExp: /[^\f\n\r\t\v\u00AD\u2028\u2029]/g,
|
76 |
+
l10n: window.wordCountL10n || {}
|
77 |
+
},
|
78 |
+
|
79 |
+
words : function( text, type ) {
|
80 |
+
var count = 0;
|
81 |
+
|
82 |
+
type = type || self.settings.l10n.type;
|
83 |
+
|
84 |
+
if ( type !== 'characters_excluding_spaces' && type !== 'characters_including_spaces' ) {
|
85 |
+
type = 'words';
|
86 |
+
}
|
87 |
+
|
88 |
+
if ( text ) {
|
89 |
+
text = text + '\n';
|
90 |
+
|
91 |
+
text = text.replace( self.settings.HTMLRegExp, '\n' );
|
92 |
+
text = text.replace( self.settings.HTMLcommentRegExp, '' );
|
93 |
+
|
94 |
+
if ( self.settings.shortcodesRegExp ) {
|
95 |
+
text = text.replace( self.settings.shortcodesRegExp, '\n' );
|
96 |
+
}
|
97 |
+
|
98 |
+
text = text.replace( self.settings.spaceRegExp, ' ' );
|
99 |
+
|
100 |
+
if ( type === 'words' ) {
|
101 |
+
text = text.replace( self.settings.HTMLEntityRegExp, '' );
|
102 |
+
text = text.replace( self.settings.connectorRegExp, ' ' );
|
103 |
+
text = text.replace( self.settings.removeRegExp, '' );
|
104 |
+
} else {
|
105 |
+
text = text.replace( self.settings.HTMLEntityRegExp, 'a' );
|
106 |
+
text = text.replace( self.settings.astralRegExp, 'a' );
|
107 |
+
}
|
108 |
+
text = text.match( self.settings[ type + 'RegExp' ] );
|
109 |
+
|
110 |
+
if ( text ) {
|
111 |
+
count = text;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
return count;
|
116 |
+
},
|
117 |
+
};
|
118 |
+
|
119 |
+
self = api.Words;
|
120 |
+
|
121 |
+
} )();
|
assets/js/bgseo/boldgrid-seo.js
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Setup the BOLDGRID Object if it doesn't exist already.
|
2 |
+
var BOLDGRID = BOLDGRID || {};
|
3 |
+
// Create the BOLDGRID.SEO object.
|
4 |
+
BOLDGRID.SEO = {
|
5 |
+
// Add the analysis report to the BOLDGRID.SEO object.
|
6 |
+
report : {
|
7 |
+
bgseo_visibility : {},
|
8 |
+
bgseo_keywords : {},
|
9 |
+
bgseo_meta : {},
|
10 |
+
rawstatistics : {},
|
11 |
+
textstatistics : {},
|
12 |
+
},
|
13 |
+
};
|
assets/js/control/boldgrid-seo-control-dashboard.js
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Registers dashboard display as control.
|
7 |
+
*
|
8 |
+
* @since 1.4
|
9 |
+
*/
|
10 |
+
butterbean.views.register_control( 'dashboard', {
|
11 |
+
// Wrapper element for the control.
|
12 |
+
tagName : 'div',
|
13 |
+
|
14 |
+
// Custom attributes for the control wrapper.
|
15 |
+
attributes : function() {
|
16 |
+
return {
|
17 |
+
'id' : 'butterbean-control-' + this.model.get( 'name' ),
|
18 |
+
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
|
19 |
+
};
|
20 |
+
},
|
21 |
+
initialize : function() {
|
22 |
+
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
|
23 |
+
|
24 |
+
this.bgseo_template = wp.template( 'butterbean-control-dashboard' );
|
25 |
+
|
26 |
+
// Bind changes so that the view is re-rendered when the model changes.
|
27 |
+
_.bindAll( this, 'render' );
|
28 |
+
this.model.bind( 'change', this.render );
|
29 |
+
},
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Get the results report for a given section.
|
33 |
+
*
|
34 |
+
* @since 1.3.1
|
35 |
+
*
|
36 |
+
* @param {Object} section The section name to get report for.
|
37 |
+
*
|
38 |
+
* @returns {Object} report The report for the section to display.
|
39 |
+
*/
|
40 |
+
results : function( data ) {
|
41 |
+
var report = {};
|
42 |
+
_.each( data, function( key ) {
|
43 |
+
_.extend( report, key );
|
44 |
+
});
|
45 |
+
|
46 |
+
return report;
|
47 |
+
},
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Gets the analysis for the section from the reporter.
|
51 |
+
*
|
52 |
+
* This is bound to the bgseo-report event, and will process
|
53 |
+
* the report and add only the analysis for the current section displayed.
|
54 |
+
*
|
55 |
+
* @since 1.3.1
|
56 |
+
*
|
57 |
+
* @param {Object} report The full report as it's updated by reporter.
|
58 |
+
*/
|
59 |
+
setAnalysis: function( e, report ) {
|
60 |
+
var sectionScore,
|
61 |
+
section = this.model.get( 'section' ),
|
62 |
+
data = _.pick( report, section );
|
63 |
+
|
64 |
+
// Get each of the analysis results to pass for template rendering.
|
65 |
+
this.sectionReport = this.results( data );
|
66 |
+
|
67 |
+
// Set the section's report in the model's attributes.
|
68 |
+
this.model.set( 'analysis', this.sectionReport );
|
69 |
+
|
70 |
+
// Get score for each section, and set a status for sections.
|
71 |
+
_( report ).each( function( section ) {
|
72 |
+
// sectionScore should be set.
|
73 |
+
if ( ! _.isUndefined ( section.sectionScore ) ) {
|
74 |
+
sectionScore = BOLDGRID.SEO.Sections.score( section );
|
75 |
+
_( section ).extend( sectionScore );
|
76 |
+
}
|
77 |
+
});
|
78 |
+
|
79 |
+
// Add the overview score to report.
|
80 |
+
_( report.bgseo_keywords ).extend({
|
81 |
+
overview : {
|
82 |
+
score : BOLDGRID.SEO.Dashboard.overviewScore( report ),
|
83 |
+
},
|
84 |
+
});
|
85 |
+
|
86 |
+
// Get the status based on the overview score, and add to report.
|
87 |
+
_( report.bgseo_keywords.overview ).extend({
|
88 |
+
status : BOLDGRID.SEO.Dashboard.overviewStatus( report.bgseo_keywords.overview.score ),
|
89 |
+
});
|
90 |
+
|
91 |
+
// Set the nav highlight indicator for each section's tab.
|
92 |
+
BOLDGRID.SEO.Sections.navHighlight( report );
|
93 |
+
BOLDGRID.SEO.Sections.overviewStatus( report );
|
94 |
+
},
|
95 |
+
|
96 |
+
// Renders the control template.
|
97 |
+
render : function() {
|
98 |
+
// Only render template if model is active.
|
99 |
+
if ( this.model.get( 'active' ) )
|
100 |
+
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
|
101 |
+
|
102 |
+
return this;
|
103 |
+
},
|
104 |
+
});
|
105 |
+
|
106 |
+
})( jQuery );
|
assets/js/control/boldgrid-seo-control-keywords.js
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
( function ( $ ) {
|
2 |
+
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Registers the keywords display as a control.
|
7 |
+
*
|
8 |
+
* @since 1.4
|
9 |
+
*/
|
10 |
+
butterbean.views.register_control( 'keywords', {
|
11 |
+
// Wrapper element for the control.
|
12 |
+
tagName : 'div',
|
13 |
+
|
14 |
+
// Custom attributes for the control wrapper.
|
15 |
+
attributes : function() {
|
16 |
+
return {
|
17 |
+
'id' : 'butterbean-control-' + this.model.get( 'name' ),
|
18 |
+
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
|
19 |
+
};
|
20 |
+
},
|
21 |
+
initialize : function() {
|
22 |
+
$( window ).bind( 'bgseo-report', _.bind( this.setAnalysis, this ) );
|
23 |
+
|
24 |
+
this.bgseo_template = wp.template( 'butterbean-control-keywords' );
|
25 |
+
|
26 |
+
// Bind changes so that the view is re-rendered when the model changes.
|
27 |
+
_.bindAll( this, 'render' );
|
28 |
+
this.model.bind( 'change', this.render );
|
29 |
+
},
|
30 |
+
setAnalysis: function( e, report ) {
|
31 |
+
this.model.set( report );
|
32 |
+
},
|
33 |
+
|
34 |
+
// Renders the control template.
|
35 |
+
render : function() {
|
36 |
+
// Only render template if model is active.
|
37 |
+
if ( this.model.get( 'active' ) )
|
38 |
+
this.el.innerHTML = this.bgseo_template( this.model.toJSON() );
|
39 |
+
return this;
|
40 |
+
},
|
41 |
+
});
|
42 |
+
|
43 |
+
})( jQuery );
|
assets/js/text-statistics/LICENSE.md
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
The MIT License (MIT)
|
2 |
+
|
3 |
+
Copyright (c) 2013 Christopher Giffard
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6 |
+
this software and associated documentation files (the "Software"), to deal in
|
7 |
+
the Software without restriction, including without limitation the rights to
|
8 |
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9 |
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10 |
+
subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17 |
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18 |
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19 |
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20 |
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
assets/js/text-statistics/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
TextStatistics.js
|
2 |
+
=================
|
3 |
+
|
4 |
+
JavaScript port of [TextStatistics.php](https://github.com/DaveChild/Text-Statistics)!
|
5 |
+
|
6 |
+
I've done what I think is a reasonably faithful port. Documentation incoming!
|
7 |
+
I removed a lot of the original comments during the port, but seeing as the API remained largely the same, I'll add them in shortly.
|
8 |
+
|
9 |
+
The beginning of a test suite in [Mocha](https://mochajs.org/) is here, covering cleaning the text and some cases of word and sentence counting.
|
10 |
+
|
11 |
+
## Installation
|
12 |
+
|
13 |
+
Run this in the browser using a simple `<script>` include - or you can install for node with `npm install text-statistics`.
|
14 |
+
|
15 |
+
**[Famous! As seen in Time!](http://time.com/2958650/twitter-reading-level/)** (heh.)
|
assets/js/text-statistics/index.js
ADDED
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// TextStatistics.js
|
2 |
+
// Christopher Giffard (2012)
|
3 |
+
// 1:1 API Fork of TextStatistics.php by Dave Child (Thanks mate!)
|
4 |
+
// https://github.com/DaveChild/Text-Statistics
|
5 |
+
|
6 |
+
|
7 |
+
(function(glob) {
|
8 |
+
|
9 |
+
function cleanText(text) {
|
10 |
+
// all these tags should be preceeded by a full stop.
|
11 |
+
var fullStopTags = ['li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd'];
|
12 |
+
|
13 |
+
fullStopTags.forEach(function(tag) {
|
14 |
+
text = text.replace("</" + tag + ">",".");
|
15 |
+
});
|
16 |
+
|
17 |
+
text = text
|
18 |
+
.replace(/<[^>]+>/g, "") // Strip tags
|
19 |
+
.replace(/[,:;()\/&+]|\-\-/g, " ") // Replace commas, hyphens etc (count them as spaces)
|
20 |
+
.replace(/[\.!?]/g, ".") // Unify terminators
|
21 |
+
.replace(/^\s+/, "") // Strip leading whitespace
|
22 |
+
.replace(/[\.]?(\w+)[\.]?(\w+)@(\w+)[\.](\w+)[\.]?/g, "$1$2@$3$4") // strip periods in email addresses (so they remain counted as one word)
|
23 |
+
.replace(/[ ]*(\n|\r\n|\r)[ ]*/g, ".") // Replace new lines with periods
|
24 |
+
.replace(/([\.])[\.]+/g, ".") // Check for duplicated terminators
|
25 |
+
.replace(/[ ]*([\.])/g, ". ") // Pad sentence terminators
|
26 |
+
.replace(/\s+/g, " ") // Remove multiple spaces
|
27 |
+
.replace(/\s+$/, ""); // Strip trailing whitespace
|
28 |
+
|
29 |
+
if(text.slice(-1) != '.') {
|
30 |
+
text += "."; // Add final terminator, just in case it's missing.
|
31 |
+
}
|
32 |
+
return text;
|
33 |
+
}
|
34 |
+
|
35 |
+
var TextStatistics = function TextStatistics(text) {
|
36 |
+
this.text = text ? cleanText(text) : this.text;
|
37 |
+
};
|
38 |
+
|
39 |
+
TextStatistics.prototype.fleschKincaidReadingEase = function(text) {
|
40 |
+
text = text ? cleanText(text) : this.text;
|
41 |
+
return Math.round((206.835 - (1.015 * this.averageWordsPerSentence(text)) - (84.6 * this.averageSyllablesPerWord(text)))*10)/10;
|
42 |
+
};
|
43 |
+
|
44 |
+
TextStatistics.prototype.fleschKincaidGradeLevel = function(text) {
|
45 |
+
text = text ? cleanText(text) : this.text;
|
46 |
+
return Math.round(((0.39 * this.averageWordsPerSentence(text)) + (11.8 * this.averageSyllablesPerWord(text)) - 15.59)*10)/10;
|
47 |
+
};
|
48 |
+
|
49 |
+
TextStatistics.prototype.gunningFogScore = function(text) {
|
50 |
+
text = text ? cleanText(text) : this.text;
|
51 |
+
return Math.round(((this.averageWordsPerSentence(text) + this.percentageWordsWithThreeSyllables(text, false)) * 0.4)*10)/10;
|
52 |
+
};
|
53 |
+
|
54 |
+
TextStatistics.prototype.colemanLiauIndex = function(text) {
|
55 |
+
text = text ? cleanText(text) : this.text;
|
56 |
+
return Math.round(((5.89 * (this.letterCount(text) / this.wordCount(text))) - (0.3 * (this.sentenceCount(text) / this.wordCount(text))) - 15.8 ) *10)/10;
|
57 |
+
};
|
58 |
+
|
59 |
+
TextStatistics.prototype.smogIndex = function(text) {
|
60 |
+
text = text ? cleanText(text) : this.text;
|
61 |
+
return Math.round(1.043 * Math.sqrt((this.wordsWithThreeSyllables(text) * (30 / this.sentenceCount(text))) + 3.1291)*10)/10;
|
62 |
+
};
|
63 |
+
|
64 |
+
TextStatistics.prototype.automatedReadabilityIndex = function(text) {
|
65 |
+
text = text ? cleanText(text) : this.text;
|
66 |
+
return Math.round(((4.71 * (this.letterCount(text) / this.wordCount(text))) + (0.5 * (this.wordCount(text) / this.sentenceCount(text))) - 21.43)*10)/10;
|
67 |
+
};
|
68 |
+
|
69 |
+
TextStatistics.prototype.textLength = function(text) {
|
70 |
+
text = text ? cleanText(text) : this.text;
|
71 |
+
return text.length;
|
72 |
+
};
|
73 |
+
|
74 |
+
TextStatistics.prototype.letterCount = function(text) {
|
75 |
+
text = text ? cleanText(text) : this.text;
|
76 |
+
text = text.replace(/[^a-z]+/ig,"");
|
77 |
+
return text.length;
|
78 |
+
};
|
79 |
+
|
80 |
+
TextStatistics.prototype.sentenceCount = function(text) {
|
81 |
+
text = text ? cleanText(text) : this.text;
|
82 |
+
|
83 |
+
// Will be tripped up by "Mr." or "U.K.". Not a major concern at this point.
|
84 |
+
return text.replace(/[^\.!?]/g, '').length || 1;
|
85 |
+
};
|
86 |
+
|
87 |
+
TextStatistics.prototype.wordCount = function(text) {
|
88 |
+
text = text ? cleanText(text) : this.text;
|
89 |
+
return text.split(/[^a-z0-9\'@\.\-]+/i).length || 1;
|
90 |
+
};
|
91 |
+
|
92 |
+
TextStatistics.prototype.averageWordsPerSentence = function(text) {
|
93 |
+
text = text ? cleanText(text) : this.text;
|
94 |
+
return this.wordCount(text) / this.sentenceCount(text);
|
95 |
+
};
|
96 |
+
|
97 |
+
TextStatistics.prototype.averageSyllablesPerWord = function(text) {
|
98 |
+
text = text ? cleanText(text) : this.text;
|
99 |
+
var syllableCount = 0, wordCount = this.wordCount(text), self = this;
|
100 |
+
|
101 |
+
text.split(/\s+/).forEach(function(word) {
|
102 |
+
syllableCount += self.syllableCount(word);
|
103 |
+
});
|
104 |
+
|
105 |
+
// Prevent NaN...
|
106 |
+
return (syllableCount||1) / (wordCount||1);
|
107 |
+
};
|
108 |
+
|
109 |
+
TextStatistics.prototype.wordsWithThreeSyllables = function(text, countProperNouns) {
|
110 |
+
text = text ? cleanText(text) : this.text;
|
111 |
+
var longWordCount = 0, self = this;
|
112 |
+
|
113 |
+
countProperNouns = countProperNouns === false ? false : true;
|
114 |
+
|
115 |
+
text.split(/\s+/).forEach(function(word) {
|
116 |
+
|
117 |
+
// We don't count proper nouns or capitalised words if the countProperNouns attribute is set.
|
118 |
+
// Defaults to true.
|
119 |
+
if (!word.match(/^[A-Z]/) || countProperNouns) {
|
120 |
+
if (self.syllableCount(word) > 2) longWordCount ++;
|
121 |
+
}
|
122 |
+
});
|
123 |
+
|
124 |
+
return longWordCount;
|
125 |
+
};
|
126 |
+
|
127 |
+
TextStatistics.prototype.percentageWordsWithThreeSyllables = function(text, countProperNouns) {
|
128 |
+
text = text ? cleanText(text) : this.text;
|
129 |
+
|
130 |
+
return (this.wordsWithThreeSyllables(text,countProperNouns) / this.wordCount(text)) * 100;
|
131 |
+
};
|
132 |
+
|
133 |
+
TextStatistics.prototype.syllableCount = function(word) {
|
134 |
+
var syllableCount = 0,
|
135 |
+
prefixSuffixCount = 0,
|
136 |
+
wordPartCount = 0;
|
137 |
+
|
138 |
+
// Prepare word - make lower case and remove non-word characters
|
139 |
+
word = word.toLowerCase().replace(/[^a-z]/g,"");
|
140 |
+
|
141 |
+
// Specific common exceptions that don't follow the rule set below are handled individually
|
142 |
+
// Array of problem words (with word as key, syllable count as value)
|
143 |
+
var problemWords = {
|
144 |
+
"simile": 3,
|
145 |
+
"forever": 3,
|
146 |
+
"shoreline": 2
|
147 |
+
};
|
148 |
+
|
149 |
+
// Return if we've hit one of those...
|
150 |
+
if (problemWords.hasOwnProperty(word)) return problemWords[word];
|
151 |
+
|
152 |
+
// These syllables would be counted as two but should be one
|
153 |
+
var subSyllables = [
|
154 |
+
/cial/,
|
155 |
+
/tia/,
|
156 |
+
/cius/,
|
157 |
+
/cious/,
|
158 |
+
/giu/,
|
159 |
+
/ion/,
|
160 |
+
/iou/,
|
161 |
+
/sia$/,
|
162 |
+
/[^aeiuoyt]{2,}ed$/,
|
163 |
+
/.ely$/,
|
164 |
+
/[cg]h?e[rsd]?$/,
|
165 |
+
/rved?$/,
|
166 |
+
/[aeiouy][dt]es?$/,
|
167 |
+
/[aeiouy][^aeiouydt]e[rsd]?$/,
|
168 |
+
/^[dr]e[aeiou][^aeiou]+$/, // Sorts out deal, deign etc
|
169 |
+
/[aeiouy]rse$/ // Purse, hearse
|
170 |
+
];
|
171 |
+
|
172 |
+
// These syllables would be counted as one but should be two
|
173 |
+
var addSyllables = [
|
174 |
+
/ia/,
|
175 |
+
/riet/,
|
176 |
+
/dien/,
|
177 |
+
/iu/,
|
178 |
+
/io/,
|
179 |
+
/ii/,
|
180 |
+
/[aeiouym]bl$/,
|
181 |
+
/[aeiou]{3}/,
|
182 |
+
/^mc/,
|
183 |
+
/ism$/,
|
184 |
+
/([^aeiouy])\1l$/,
|
185 |
+
/[^l]lien/,
|
186 |
+
/^coa[dglx]./,
|
187 |
+
/[^gq]ua[^auieo]/,
|
188 |
+
/dnt$/,
|
189 |
+
/uity$/,
|
190 |
+
/ie(r|st)$/
|
191 |
+
];
|
192 |
+
|
193 |
+
// Single syllable prefixes and suffixes
|
194 |
+
var prefixSuffix = [
|
195 |
+
/^un/,
|
196 |
+
/^fore/,
|
197 |
+
/ly$/,
|
198 |
+
/less$/,
|
199 |
+
/ful$/,
|
200 |
+
/ers?$/,
|
201 |
+
/ings?$/
|
202 |
+
];
|
203 |
+
|
204 |
+
// Remove prefixes and suffixes and count how many were taken
|
205 |
+
prefixSuffix.forEach(function(regex) {
|
206 |
+
if (word.match(regex)) {
|
207 |
+
word = word.replace(regex,"");
|
208 |
+
prefixSuffixCount ++;
|
209 |
+
}
|
210 |
+
});
|
211 |
+
|
212 |
+
wordPartCount = word
|
213 |
+
.split(/[^aeiouy]+/ig)
|
214 |
+
.filter(function(wordPart) {
|
215 |
+
return !!wordPart.replace(/\s+/ig,"").length;
|
216 |
+
})
|
217 |
+
.length;
|
218 |
+
|
219 |
+
// Get preliminary syllable count...
|
220 |
+
syllableCount = wordPartCount + prefixSuffixCount;
|
221 |
+
|
222 |
+
// Some syllables do not follow normal rules - check for them
|
223 |
+
subSyllables.forEach(function(syllable) {
|
224 |
+
if (word.match(syllable)) syllableCount --;
|
225 |
+
});
|
226 |
+
|
227 |
+
addSyllables.forEach(function(syllable) {
|
228 |
+
if (word.match(syllable)) syllableCount ++;
|
229 |
+
});
|
230 |
+
|
231 |
+
return syllableCount || 1;
|
232 |
+
};
|
233 |
+
|
234 |
+
function textStatistics(text) {
|
235 |
+
return new TextStatistics(text);
|
236 |
+
}
|
237 |
+
|
238 |
+
(typeof module != "undefined" && module.exports) ? (module.exports = textStatistics) : (typeof define != "undefined" ? (define("textstatistics", [], function() { return textStatistics; })) : (glob.textstatistics = textStatistics));
|
239 |
+
})(this);
|
assets/partials/control-bgseo-textarea.php
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<# if ( data.label ) { #>
|
3 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
4 |
+
<# } #>
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
<textarea {{{ data.attr }}}>{{{ data.value }}}</textarea>
|
9 |
+
</label>
|
assets/partials/control-dashboard.php
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<div class="bgseo-analysis">
|
2 |
+
<# _.each( data.analysis, function( recommendation ) { #>
|
3 |
+
<# if ( ! _.isUndefined( recommendation.lengthScore ) ) { #>
|
4 |
+
<div class="bgseo-recommendations">
|
5 |
+
<span class="analysis-suggestion {{{ recommendation.lengthScore.status }}}">
|
6 |
+
{{{ recommendation.lengthScore.msg }}}
|
7 |
+
</span>
|
8 |
+
</div>
|
9 |
+
<# } #>
|
10 |
+
<# } ); #>
|
11 |
+
</div>
|
12 |
+
<# if ( ! _.isUndefined( data.textstatistics ) ) { #>
|
13 |
+
<# if ( ! _.isUndefined( data.textstatistics.gradeLevel ) ) { #>
|
14 |
+
<div class="bgseo-recommendations">
|
15 |
+
<span class="analysis-suggestion {{{ data.textstatistics.gradeLevel.status }}}">
|
16 |
+
Score: {{{ data.textstatistics.gradeLevel.score }}}%. {{{ data.textstatistics.gradeLevel.msg }}}
|
17 |
+
</span>
|
18 |
+
</div>
|
19 |
+
<# } #>
|
20 |
+
<# } #>
|
assets/partials/control-keywords.php
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( ! _.isUndefined( data.textstatistics ) ) { #>
|
2 |
+
<# if ( ! _.isUndefined( data.textstatistics.recommendedKeywords ) ) { #>
|
3 |
+
<div class="bgseo-keywords">
|
4 |
+
<# if ( ! _.isUndefined( data.textstatistics.recommendedKeywords[0] ) ) { #>
|
5 |
+
<span class="bgseo-keyword-recommendation">
|
6 |
+
Based on your content and frequency, search engines will likely think your content is about: <b>{{{ data.textstatistics.recommendedKeywords[0][0] }}}</b>.
|
7 |
+
</span>
|
8 |
+
<# } #>
|
9 |
+
</div>
|
10 |
+
<div class="bgseo-keywords set-new-target">
|
11 |
+
<# if ( ! _.isUndefined( data.textstatistics.recommendedKeywords[0] ) ) { #>
|
12 |
+
<span class="bgseo-keyword-recommendation">
|
13 |
+
Set a new target keyword below, and the dashboard will be updated with new stats! First time? Read our guide on <a href="https://boldgrid.com/support/seo/keywords" target="_blank">SEO and Keywords</a>.
|
14 |
+
</span>
|
15 |
+
<# } #>
|
16 |
+
</div>
|
17 |
+
<# } #>
|
18 |
+
<# } #>
|
autoload.php
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Handles autoloading of the BoldGrid SEO class/interface structure.
|
5 |
+
*
|
6 |
+
* @since 1.3.1
|
7 |
+
* @package Boldgrid_Seo
|
8 |
+
* @subpackage Boldgrid_Seo/includes
|
9 |
+
* @author BoldGrid <support@boldgrid.com>
|
10 |
+
* @link https://boldgrid.com
|
11 |
+
*/
|
12 |
+
|
13 |
+
if ( ! function_exists( 'boldgrid_seo_autoload' ) ) {
|
14 |
+
/**
|
15 |
+
* The BoldGrid SEO class autoloader.
|
16 |
+
*
|
17 |
+
* Finds the path to a class that we're requiring and includes the file.
|
18 |
+
*
|
19 |
+
* @since 1.3.1
|
20 |
+
*/
|
21 |
+
function boldgrid_seo_autoload( $class_name ) {
|
22 |
+
$paths = array();
|
23 |
+
$our_class = ( 0 === stripos( $class_name, 'Boldgrid_Seo' ) );
|
24 |
+
|
25 |
+
if ( $our_class ) {
|
26 |
+
$path = dirname( __FILE__ ) . '/includes/';
|
27 |
+
$is_interface = ( substr( $class_name, -strlen( 'Interface' ) ) == 'Interface' );
|
28 |
+
$filename = 'class-' . strtolower( str_replace( '_', '-', $class_name ) ) . '.php';
|
29 |
+
if ( $is_interface ) {
|
30 |
+
$interface = str_replace( '_Interface', '', $class_name );
|
31 |
+
$filename = 'interface-' . strtolower( str_replace( '_', '-', $interface ) ) . '.php';
|
32 |
+
}
|
33 |
+
|
34 |
+
$paths[] = $path . $filename;
|
35 |
+
|
36 |
+
$substr = str_replace( 'Boldgrid_Seo_', '', $class_name );
|
37 |
+
$exploded = explode( '_', $substr );
|
38 |
+
$levels = count( $exploded );
|
39 |
+
|
40 |
+
$previous_path = '';
|
41 |
+
for ( $i = 0; $i < $levels; $i++ ) {
|
42 |
+
$paths[] = $path . $previous_path . strtolower( $exploded[ $i ] ) . '/' . $filename;
|
43 |
+
$previous_path .= strtolower( $exploded[ $i ] ) . '/';
|
44 |
+
}
|
45 |
+
foreach ( $paths as $path ) {
|
46 |
+
$path = wp_normalize_path( $path );
|
47 |
+
if ( file_exists( $path ) ) {
|
48 |
+
include $path;
|
49 |
+
return;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
}
|
53 |
+
}
|
54 |
+
// Run the autoloader.
|
55 |
+
spl_autoload_register( 'boldgrid_seo_autoload' );
|
56 |
+
}
|
boldgrid-easy-seo.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The plugin bootstrap file
|
4 |
+
*
|
5 |
+
* This file is read by WordPress to generate the plugin information in the plugin
|
6 |
+
* admin area. This file also includes all of the dependencies used by the plugin,
|
7 |
+
* registers the activation and deactivation functions, and defines a function
|
8 |
+
* that starts the plugin.
|
9 |
+
*
|
10 |
+
* @link http://www.boldgrid.com
|
11 |
+
* @since 1.0.0
|
12 |
+
* @package Boldgrid_Seo
|
13 |
+
*
|
14 |
+
* Plugin Name: BoldGrid Easy SEO
|
15 |
+
* Plugin URI: https://www.boldgrid.com/boldgrid-seo/
|
16 |
+
* Description: Easily manage your website's search engine optimization with Easy SEO by BoldGrid!
|
17 |
+
* Version: 1.5.1
|
18 |
+
* Author: BoldGrid.com <wpb@boldgrid.com>
|
19 |
+
* Author URI: https://www.boldgrid.com/
|
20 |
+
* License: GPL-2.0+
|
21 |
+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
22 |
+
* Text Domain: bgseo
|
23 |
+
* Domain Path: /languages
|
24 |
+
*
|
25 |
+
* BoldGrid SEO is free software: you can redistribute it and/or modify
|
26 |
+
* it under the terms of the GNU General Public License as published by
|
27 |
+
* the Free Software Foundation, either version 2 of the License, or
|
28 |
+
* any later version.
|
29 |
+
*
|
30 |
+
* BoldGrid SEO is distributed in the hope that it will be useful,
|
31 |
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
32 |
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
33 |
+
* GNU General Public License for more details.
|
34 |
+
*
|
35 |
+
* You should have received a copy of the GNU General Public License
|
36 |
+
* along with BoldGrid SEO. If not, see https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html.
|
37 |
+
*
|
38 |
+
* This plugin is also inspired by and/or uses code from the following plugins/libraries:
|
39 |
+
*
|
40 |
+
* ButterBean[https://github.com/justintadlock/butterbean], licensed under GNU General Public License v2.0.
|
41 |
+
* TextStatistics.js[https://github.com/cgiffard/TextStatistics.js], licensed under MIT License.
|
42 |
+
* WordPress Plugin Boilerplate[https://github.com/DevinVinson/WordPress-Plugin-Boilerplate], licensed under GNU General Public License v2.0.
|
43 |
+
* Sewn In Simple SEO[https://github.com/jupitercow/sewn-in-simple-seo], licensed under GNU General Public License v3.0.
|
44 |
+
* All In One SEO Pack[https://github.com/semperfiwebdesign/all-in-one-seo-pack], licensed under GNU General Public License v2.0.
|
45 |
+
*/
|
46 |
+
|
47 |
+
// If this file is called directly, abort.
|
48 |
+
defined( 'WPINC' ) ? : die();
|
49 |
+
|
50 |
+
// Include the autoloader
|
51 |
+
include_once wp_normalize_path( plugin_dir_path( __FILE__ ) . '/autoload.php' );
|
52 |
+
|
53 |
+
// Define version.
|
54 |
+
defined( 'BOLDGRID_SEO_VERSION' ) || define( 'BOLDGRID_SEO_VERSION', implode( get_file_data( __FILE__, array( 'Version' ), 'plugin' ) ) );
|
55 |
+
|
56 |
+
// Define boldgrid-seo path.
|
57 |
+
defined( 'BOLDGRID_SEO_PATH' ) || define( 'BOLDGRID_SEO_PATH', dirname( __FILE__ ) );
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Check Versions.
|
61 |
+
*
|
62 |
+
* This will check to make sure the user has PHP 5.3 or higher, and will also
|
63 |
+
* check to make sure they are using WordPress 4.0 or higher.
|
64 |
+
*
|
65 |
+
* @var $boldgrid_seo_php_version This checks that PHP is 5.3.0 or higher.
|
66 |
+
* @var $boldgrid_seo_wp_version This checks that WordPress is 4.0 or higher.
|
67 |
+
*
|
68 |
+
* @since 1.0.0
|
69 |
+
*/
|
70 |
+
$easy_seo_php_version = version_compare( phpversion(), '5.3.0', '>=' );
|
71 |
+
$easy_seo_wp_version = version_compare( get_bloginfo( 'version' ), '4.0', '>=' );
|
72 |
+
|
73 |
+
if ( ! $easy_seo_php_version or ! $easy_seo_wp_version ) :
|
74 |
+
function easy_seo_php_error() {
|
75 |
+
printf( '<div class="error"><p>%s</p></div>',
|
76 |
+
esc_html__( 'Easy SEO Error: Easy SEO Supports WordPress version 4.0+, and PHP version 5.3+', 'bgseo' )
|
77 |
+
);
|
78 |
+
deactivate_plugins( plugin_basename( __FILE__ ) );
|
79 |
+
}
|
80 |
+
|
81 |
+
if ( defined( 'WP_CLI' ) ) :
|
82 |
+
deactivate_plugins( plugin_basename( __FILE__ ) );
|
83 |
+
WP_CLI::warning( __( 'Easy SEO Error: You must have PHP 5.3 or higher and WordPress 4.0 or higher to use this plugin.', 'bgseo' ) );
|
84 |
+
else :
|
85 |
+
add_action( 'admin_notices', 'easy_seo_php_error' );
|
86 |
+
endif;
|
87 |
+
else : // Load the rest of the plugin that contains code suited for passing the version check.
|
88 |
+
function activate_easy_seo() {
|
89 |
+
require_once wp_normalize_path( plugin_dir_path( __FILE__ ) . 'includes/class-boldgrid-seo-activator.php' );
|
90 |
+
Boldgrid_Seo_Activator::activate();
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* The code that runs during plugin deactivation.
|
95 |
+
* This action is documented in includes/class-boldgrid-seo-deactivator.php
|
96 |
+
*/
|
97 |
+
function deactivate_easy_seo() {
|
98 |
+
require_once wp_normalize_path( plugin_dir_path( __FILE__ ) . 'includes/class-boldgrid-seo-deactivator.php' );
|
99 |
+
Boldgrid_Seo_Deactivator::deactivate();
|
100 |
+
}
|
101 |
+
|
102 |
+
register_activation_hook( __FILE__, 'activate_easy_seo' );
|
103 |
+
register_deactivation_hook( __FILE__, 'deactivate_easy_seo' );
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Begins execution of the plugin.
|
107 |
+
*
|
108 |
+
* Since everything within the plugin is registered via hooks,
|
109 |
+
* then kicking off the plugin from this point in the file does
|
110 |
+
* not affect the page life cycle.
|
111 |
+
*
|
112 |
+
* @since 1.0.0
|
113 |
+
*/
|
114 |
+
function run_easy_seo() {
|
115 |
+
$plugin = new Boldgrid_Seo();
|
116 |
+
$plugin->run();
|
117 |
+
}
|
118 |
+
run_easy_seo();
|
119 |
+
endif;
|
changelog.txt
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
== Changelog ==
|
2 |
+
|
3 |
+
= 1.5 =
|
4 |
+
* Update: Bump version for release.
|
5 |
+
|
6 |
+
= 1.4.4 =
|
7 |
+
* Update: JIRA WPB-3292 Updated plugin URI.
|
8 |
+
|
9 |
+
= 1.4.3 =
|
10 |
+
* Bug fix: JIRA WPB-3161 Fixed auto plugin update.
|
11 |
+
|
12 |
+
= 1.4.2 =
|
13 |
+
* Bug fix: JIRA WPB-3151 Added check and load before using get_plugin_data() for updates.
|
14 |
+
|
15 |
+
= 1.4.1 =
|
16 |
+
* Bug fix: Fixed undefined BOLDGRID console errors on custom post types.
|
17 |
+
|
18 |
+
= 1.4 =
|
19 |
+
* Bug fix: JIRA WPB-2912 Fixed issue when installing plugins from the Tools Import page.
|
20 |
+
|
21 |
+
= 1.3.6 =
|
22 |
+
* Bug fix: JIRA WPB-2892 Fixed plugin update checks for some scenarios (WP-CLI, Plesk, etc).
|
23 |
+
* Update: Updating keyword to keyword phrase in verbiage.
|
24 |
+
* Update: Detect if BoldGrid theme has page title hidden.
|
25 |
+
|
26 |
+
= 1.3.5 =
|
27 |
+
* Bug fix: JIRA WPB-2821 Fixed plugin update checks for some scenarios (WP-CLI, Plesk, etc).
|
28 |
+
* Update: Added support links for the keyword phrase length status messages.
|
29 |
+
* Bug fix: Keyword detection was finding single characters and combining it with next word.
|
30 |
+
|
31 |
+
= 1.3.4 =
|
32 |
+
* Update: Converted SEO stop words to single string for translators.
|
33 |
+
* New Feature: Added Keyword Phrase length status indicator and messages.
|
34 |
+
|
35 |
+
= 1.3.3 =
|
36 |
+
* Testing: JIRA WPB-2744 Tested on WordPress 4.7.
|
37 |
+
* Update: Removing SEO Dashboard from metabox and moving controls to keywords section.
|
38 |
+
* Update: Adding more context to index/follow and canonical controls.
|
39 |
+
* Update: Status messages now include links to help sections on boldgrid.com/support.
|
40 |
+
|
41 |
+
= 1.3.2 =
|
42 |
+
* Update: Updated status message verbiage for analysis reports.
|
43 |
+
|
44 |
+
= 1.3.1 =
|
45 |
+
* Update: Added SEO Analysis, and new features.
|
46 |
+
* Update: Upgrade class has been added to handle upgrades from older version to newer ones.
|
47 |
+
|
48 |
+
= 1.3 =
|
49 |
+
* Misc: JIRA WPB-2420 Added EOF line breaks.
|
50 |
+
|
51 |
+
= 1.2.1 =
|
52 |
+
* Misc: JIRA WPB-2344 Updated readme.txt for Tested up to 4.6.1.
|
53 |
+
* Bug fix: JIRA WPB-2336 Load BoldGrid settings from the correct WP option (site/blog).
|
54 |
+
* Update: JIRA WPB-2368 Setting version constant from plugin file.
|
55 |
+
|
56 |
+
= 1.2 =
|
57 |
+
* Misc: JIRA WPB-2256 Updated readme.txt for Tested up to: 4.6.
|
58 |
+
* Rework: JIRA WPB-1825 Formatting.
|
59 |
+
* Bug Fix: Fixing issue where HTML prints inside of title tags.
|
60 |
+
|
61 |
+
= 1.1.1 =
|
62 |
+
* New feature: JIRA WPB-2037 Added capability for auto-updates by BoldGrid API response.
|
63 |
+
* Testing: JIRA WPB-2046 Tested on WordPress 4.5.3.
|
64 |
+
|
65 |
+
= 1.1.0.1 =
|
66 |
+
* Bug fix: JIRA WPB-1816 Fixed update class interference with the Add Plugins page.
|
67 |
+
|
68 |
+
= 1.1 =
|
69 |
+
* Bug fix: JIRA WPB-1809 Fixed undefined index "action" for some scenarios. Optimized update class and addressed CodeSniffer items.
|
70 |
+
* Bug fix: Fixing undefined index error.
|
71 |
+
* Bug fix: Fixing extraneous markup printing to author pages.
|
72 |
+
|
73 |
+
= 1.0.6 =
|
74 |
+
* Misc: Update JS to pass JSHint.
|
75 |
+
* Misc: JIRA WPB-1361 Added license file.
|
76 |
+
|
77 |
+
= 1.0.5 =
|
78 |
+
* Bug fix: JIRA WPB-1692 Update nonce verification.
|
79 |
+
|
80 |
+
= 1.0.4 =
|
81 |
+
* Rework: JIRA WPB-1619 Updated require and include statements for standards.
|
82 |
+
* Bug fix: JIRA WPB-1638 Fixing updated filter from WordPress Update.
|
83 |
+
|
84 |
+
= 1.0.3 =
|
85 |
+
* Bug fix: JIRA WPB-1553 Changed __DIR__ to dirname( __FILE__ ) for PHP <=5.2.
|
86 |
+
* Misc JIRA WPB-1468 Updated readme.txt for Tested up to: 4.4.1
|
87 |
+
|
88 |
+
= 1.0.2 =
|
89 |
+
* New feature: JIRA WPB-1363 Updated readme.txt for WordPress standards.
|
90 |
+
|
91 |
+
= 1.0.1 =
|
92 |
+
* Bug fix: Updated plugin author from BoldGrid to BoldGrid.com
|
93 |
+
|
94 |
+
= 1.0 =
|
95 |
+
* Initial public release.
|
includes/class-boldgrid-seo-activator.php
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Fired during plugin activation
|
5 |
+
*
|
6 |
+
* @link https://boldgrid.com
|
7 |
+
* @since 1.0.0
|
8 |
+
*
|
9 |
+
* @package Boldgrid_Seo
|
10 |
+
* @subpackage Boldgrid_Seo/includes
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Fired during plugin activation.
|
15 |
+
*
|
16 |
+
* This class defines all code necessary to run during the plugin's activation.
|
17 |
+
*
|
18 |
+
* @since 1.0.0
|
19 |
+
* @package Boldgrid_Seo
|
20 |
+
* @subpackage Boldgrid_Seo/includes
|
21 |
+
* @author BoldGrid <support@boldgrid.com>
|
22 |
+
*/
|
23 |
+
class Boldgrid_Seo_Activator {
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Short Description. (use period)
|
27 |
+
*
|
28 |
+
* Long Description.
|
29 |
+
*
|
30 |
+
* @since 1.0.0
|
31 |
+
*/
|
32 |
+
public static function activate() {
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
}
|
includes/class-boldgrid-seo-admin.php
ADDED
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The admin-specific functionality of the plugin.
|
4 |
+
*
|
5 |
+
* Defines the plugin name, version, and two examples hooks for how to
|
6 |
+
* enqueue the admin-specific stylesheet and JavaScript.
|
7 |
+
*
|
8 |
+
* @package Boldgrid_Seo
|
9 |
+
* @subpackage Boldgrid_Seo/admin
|
10 |
+
* @author BoldGrid <support@boldgrid.com>
|
11 |
+
* @link https://boldgrid.com
|
12 |
+
* @since 1.0.0
|
13 |
+
*/
|
14 |
+
// If called directly, abort.
|
15 |
+
defined( 'WPINC' ) ? : die;
|
16 |
+
|
17 |
+
class Boldgrid_Seo_Admin {
|
18 |
+
/**
|
19 |
+
* The unique prefix for BoldGrid SEO.
|
20 |
+
*
|
21 |
+
* @since 1.0.0
|
22 |
+
* @access protected
|
23 |
+
* @var string $prefix The string used to uniquely prefix for BoldGrid SEO.
|
24 |
+
*/
|
25 |
+
protected $prefix;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* The unique identifier of this plugin.
|
29 |
+
*
|
30 |
+
* @since 1.0.0
|
31 |
+
* @access protected
|
32 |
+
* @var string $plugin_name The string used to uniquely identify this plugin.
|
33 |
+
*/
|
34 |
+
protected $plugin_name;
|
35 |
+
|
36 |
+
/**
|
37 |
+
* The unique identifier of this plugin.
|
38 |
+
*
|
39 |
+
* @since 1.0.0
|
40 |
+
* @access protected
|
41 |
+
* @var string $settings The array used for settings.
|
42 |
+
*/
|
43 |
+
protected $settings;
|
44 |
+
|
45 |
+
protected $configs;
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Define the core functionality of the plugin.
|
49 |
+
*
|
50 |
+
* @since 1.0.0
|
51 |
+
*/
|
52 |
+
|
53 |
+
public function __construct( $configs ) {
|
54 |
+
$this->prefix = 'boldgrid-seo';
|
55 |
+
$this->plugin_name = strtolower( __CLASS__ );
|
56 |
+
$this->configs = $configs;
|
57 |
+
$this->settings = $this->configs['admin'];
|
58 |
+
$this->settings = apply_filters( "{$this->prefix}/seo/settings", $this->settings );
|
59 |
+
$this->util = new Boldgrid_Seo_Util();
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* The prefix BoldGrid SEO prefix for actions and filters.
|
64 |
+
*
|
65 |
+
* @since 1.0.0
|
66 |
+
* @return string The prefix 'boldgrid-seo'.
|
67 |
+
*/
|
68 |
+
public function get_prefix( ) {
|
69 |
+
return $this->prefix;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* The name of the plugin.
|
74 |
+
*
|
75 |
+
* @since 1.0.0
|
76 |
+
* @return string The name of the plugin 'boldgrid_seo'.
|
77 |
+
*/
|
78 |
+
public function get_plugin_name( ) {
|
79 |
+
return $this->plugin_name;
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Inject JS to have repeater active for TinyMCE content.
|
84 |
+
*
|
85 |
+
* @since 1.0.0
|
86 |
+
*/
|
87 |
+
public function boldgrid_tinymce_init( $init ) {
|
88 |
+
$init['setup'] = "function( ed ) { ed.onKeyUp.add( function( ed, e ) { repeater( e ); } ); }";
|
89 |
+
return $init;
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Get post types.
|
94 |
+
*
|
95 |
+
* @since 1.0.0
|
96 |
+
*/
|
97 |
+
public function post_types( ) {
|
98 |
+
$this->settings['post_types'] = get_post_types(
|
99 |
+
array(
|
100 |
+
'public' => true,
|
101 |
+
)
|
102 |
+
);
|
103 |
+
|
104 |
+
unset( $this->settings['post_types']['attachment'] );
|
105 |
+
|
106 |
+
return apply_filters( "{$this->prefix}/seo/post_types", apply_filters( "{$this->plugin_name}/post_types", $this->settings['post_types'] ) );
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* wp_head
|
111 |
+
*
|
112 |
+
* If automate is turned on, automate the header items.
|
113 |
+
*
|
114 |
+
* @since 1.0.0
|
115 |
+
* @return void
|
116 |
+
*/
|
117 |
+
public function wp_head() {
|
118 |
+
do_action( "{$this->prefix}/seo/before" );
|
119 |
+
do_action( "{$this->prefix}/seo/description" );
|
120 |
+
do_action( "{$this->prefix}/seo/robots" );
|
121 |
+
do_action( "{$this->prefix}/seo/canonical" );
|
122 |
+
do_action( "{$this->prefix}/seo/og:locale" );
|
123 |
+
do_action( "{$this->prefix}/seo/og:type" );
|
124 |
+
do_action( "{$this->prefix}/seo/og:title" );
|
125 |
+
do_action( "{$this->prefix}/seo/og:description" );
|
126 |
+
do_action( "{$this->prefix}/seo/og:url" );
|
127 |
+
do_action( "{$this->prefix}/seo/og:site_name" );
|
128 |
+
do_action( "{$this->prefix}/seo/og:image" );
|
129 |
+
do_action( "{$this->prefix}/seo/after" );
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Set the title.
|
134 |
+
*
|
135 |
+
* @since 1.0.0
|
136 |
+
* @return void
|
137 |
+
*/
|
138 |
+
public function wp_title( $title, $sep = "|" ) {
|
139 |
+
if ( ! $sep && false !== $sep ) {
|
140 |
+
$sep = "|";
|
141 |
+
}
|
142 |
+
|
143 |
+
$title = "$title $sep " . get_bloginfo( 'blogname' );
|
144 |
+
|
145 |
+
if ( is_feed() ) {
|
146 |
+
return $title;
|
147 |
+
}
|
148 |
+
|
149 |
+
$content = $this->seo_title( $sep );
|
150 |
+
|
151 |
+
// Add the site name
|
152 |
+
if ( $content ) {
|
153 |
+
$title = $content;
|
154 |
+
}
|
155 |
+
$title = trim( str_replace( ',', ' |', wp_strip_all_tags( $title ) ) );
|
156 |
+
return $title;
|
157 |
+
}
|
158 |
+
|
159 |
+
public function canonical_url( ) {
|
160 |
+
global $wp_query, $posts;
|
161 |
+
$content = $this->util->get_url( $wp_query );
|
162 |
+
if ( ! empty( $GLOBALS['post']->ID ) && $canonical = get_post_meta( $GLOBALS['post']->ID, 'bgseo_canonical', true ) ) {
|
163 |
+
// Look for a custom canonical url to override the default permalink.
|
164 |
+
$content = $canonical;
|
165 |
+
}
|
166 |
+
|
167 |
+
if ( ! empty( $content ) ) : printf( $this->settings['meta_fields']['canonical'] . "\n", esc_url( $content ) ); endif;
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Meta title.
|
172 |
+
*
|
173 |
+
* @since 1.0.0
|
174 |
+
* @return void
|
175 |
+
*/
|
176 |
+
public function seo_title( $sep = "|" ) {
|
177 |
+
if ( ',' != $sep ) {
|
178 |
+
$sep = " $sep";
|
179 |
+
}
|
180 |
+
|
181 |
+
$content = '';
|
182 |
+
|
183 |
+
global $post, $paged, $page;
|
184 |
+
|
185 |
+
if ( is_404() ) {
|
186 |
+
$content = apply_filters( "{$this->prefix}/seo/404_title", "Not Found, Error 404" );
|
187 |
+
}
|
188 |
+
|
189 |
+
elseif ( is_archive() ) {
|
190 |
+
$content = get_the_archive_title() . "$sep " . get_bloginfo( 'blogname' );
|
191 |
+
$content = $this->simplify_archive_title( $content );
|
192 |
+
}
|
193 |
+
|
194 |
+
elseif ( is_search() ) {
|
195 |
+
$s = get_search_query();
|
196 |
+
$content = apply_filters( "{$this->prefix}/seo/search_title", 'Search for ' . "$s$sep " . get_bloginfo( 'blogname' ) );
|
197 |
+
}
|
198 |
+
|
199 |
+
elseif ( is_home() ) {
|
200 |
+
$posts_page_id = get_option( 'page_for_posts' );
|
201 |
+
$front_page_id = get_option( 'page_on_front' );
|
202 |
+
|
203 |
+
// If pages are default with home being posts and a site meta exists
|
204 |
+
if ( ! $posts_page_id
|
205 |
+
&& ! $front_page_id
|
206 |
+
&& $meta = get_option( 'options_meta_title' ) ) {
|
207 |
+
$content = $meta;
|
208 |
+
}
|
209 |
+
|
210 |
+
// Look for a custom meta on a posts page
|
211 |
+
elseif ( $posts_page_id
|
212 |
+
&& $meta = get_post_meta( $posts_page_id, 'bgseo_title', true ) ) {
|
213 |
+
$content = $meta;
|
214 |
+
}
|
215 |
+
|
216 |
+
// Look for a posts page title
|
217 |
+
elseif ( $posts_page_id
|
218 |
+
&& $meta = get_the_title( $posts_page_id ) ) {
|
219 |
+
$content = "$meta$sep " . get_bloginfo( 'blogname' );
|
220 |
+
// Use a default that can be filtered
|
221 |
+
} else {
|
222 |
+
$content = apply_filters( "{$this->prefix}/seo/home_title", get_bloginfo( 'blogname' ) );
|
223 |
+
}
|
224 |
+
} else {
|
225 |
+
// Look for a custom meta title and override post title
|
226 |
+
if ( ! empty( $GLOBALS['post']->ID ) ) {
|
227 |
+
if ( $meta_title = get_post_meta( $GLOBALS['post']->ID, 'bgseo_title', true ) ) {
|
228 |
+
$content = $meta_title;
|
229 |
+
}
|
230 |
+
|
231 |
+
elseif ( $meta_title = get_the_title( $GLOBALS['post']->ID ) ) {
|
232 |
+
$content = "$meta_title$sep " . get_bloginfo( 'blogname' );
|
233 |
+
}
|
234 |
+
}
|
235 |
+
}
|
236 |
+
// Add pagination
|
237 |
+
if ( $content
|
238 |
+
&& ( 1 < $GLOBALS['paged']
|
239 |
+
|| 1 < $GLOBALS['page'] ) ) {
|
240 |
+
$content .= "$sep Page " . max( $GLOBALS['paged'], $GLOBALS['page'] );
|
241 |
+
}
|
242 |
+
|
243 |
+
return $content;
|
244 |
+
}
|
245 |
+
|
246 |
+
public function simplify_archive_title( $title ) {
|
247 |
+
$delimiter = ': ';
|
248 |
+
$array = explode( $delimiter, $title );
|
249 |
+
if ( 1 < count( $array ) ) {
|
250 |
+
array_shift( $array );
|
251 |
+
return implode( $delimiter, $array );
|
252 |
+
}
|
253 |
+
return $title;
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Get the meta description.
|
258 |
+
*
|
259 |
+
* @since 1.2.1
|
260 |
+
* @return string $content String containing content of meta description.
|
261 |
+
*/
|
262 |
+
public function get_meta_description() {
|
263 |
+
$content = '';
|
264 |
+
|
265 |
+
if ( is_archive() ) {
|
266 |
+
$content = apply_filters( "{$this->prefix}/seo/archive_description",
|
267 |
+
strip_tags(
|
268 |
+
str_replace(
|
269 |
+
array ( "\r","\n" ),
|
270 |
+
'',
|
271 |
+
term_description()
|
272 |
+
)
|
273 |
+
)
|
274 |
+
);
|
275 |
+
} elseif ( is_home() ) {
|
276 |
+
$posts_page_id = get_option( 'page_for_posts' );
|
277 |
+
// Look for custom meta on a posts page.
|
278 |
+
if ( $posts_page_id
|
279 |
+
&& $meta = get_post_meta( $posts_page_id, 'bgseo_description', true ) ) {
|
280 |
+
$content = $meta;
|
281 |
+
}
|
282 |
+
|
283 |
+
// Look for a posts page content.
|
284 |
+
elseif ( $posts_page_id
|
285 |
+
&& $meta = get_post_field( 'post_content', $posts_page_id ) ) {
|
286 |
+
$content = wp_trim_words( $meta, '40', '' );
|
287 |
+
$content = $this->util->get_sentences( $content );
|
288 |
+
}
|
289 |
+
} else {
|
290 |
+
if ( ! empty( $GLOBALS['post']->ID )
|
291 |
+
&& $meta = get_post_meta( $GLOBALS['post']->ID, 'meta_description', true ) ) {
|
292 |
+
update_post_meta( $GLOBALS['post']->ID, 'bgseo_description', $meta );
|
293 |
+
delete_post_meta( $GLOBALS['post']->ID, 'meta_description' );
|
294 |
+
$content = get_post_meta( $GLOBALS['post']->ID, 'bgseo_description', true );
|
295 |
+
}
|
296 |
+
elseif ( ! empty( $GLOBALS['post']->ID )
|
297 |
+
&& $meta = get_post_meta( $GLOBALS['post']->ID, 'bgseo_description', true ) ) {
|
298 |
+
$content = $meta;
|
299 |
+
}
|
300 |
+
elseif ( ! empty( $GLOBALS['post']->ID )
|
301 |
+
&& $meta = get_post_field( 'post_content', $GLOBALS['post']->ID ) ) {
|
302 |
+
$content = wp_trim_words( $meta, '40', '' );
|
303 |
+
$content = $this->util->get_sentences( $content );
|
304 |
+
}
|
305 |
+
}
|
306 |
+
|
307 |
+
return $content;
|
308 |
+
}
|
309 |
+
|
310 |
+
public function meta_description() {
|
311 |
+
$content = $this->get_meta_description();
|
312 |
+
if ( $content ) : printf( $this->settings['meta_fields']['description'] . "\n", $content ); endif;
|
313 |
+
}
|
314 |
+
|
315 |
+
public function meta_og_description() {
|
316 |
+
$content = $this->get_meta_description();
|
317 |
+
if ( $content ) : printf( $this->settings['meta_fields']['og_description'] . "\n", $content ); endif;
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Site name.
|
322 |
+
*
|
323 |
+
* @since 1.0.0
|
324 |
+
* @return void
|
325 |
+
*/
|
326 |
+
public function meta_site_name( ) {
|
327 |
+
$site_name = get_option( 'blogname' );
|
328 |
+
if ( $site_name ) : printf( $this->settings['meta_fields']['site_name'] . "\n", $site_name ); endif;
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Open Graph title.
|
333 |
+
*
|
334 |
+
* @since 1.0.0
|
335 |
+
* @return void
|
336 |
+
*/
|
337 |
+
public function meta_og_title( ) {
|
338 |
+
$content = $this->seo_title( ',' );
|
339 |
+
if ( is_author() ) {
|
340 |
+
$content = str_replace( ',', ' |', wp_strip_all_tags( $content ) );
|
341 |
+
}
|
342 |
+
if ( $content ) {
|
343 |
+
printf( $this->settings['meta_fields']['title'] . "\n", $content );
|
344 |
+
}
|
345 |
+
}
|
346 |
+
|
347 |
+
public function meta_og_url() {
|
348 |
+
global $wp_query, $posts;
|
349 |
+
$content = $this->util->get_url( $wp_query );
|
350 |
+
if ( ! empty( $GLOBALS['post']->ID ) && $canonical = get_post_meta( $GLOBALS['post']->ID, 'bgseo_canonical', true ) ) {
|
351 |
+
// Look for a custom canonical url to override the default permalink.
|
352 |
+
$content = $canonical;
|
353 |
+
}
|
354 |
+
if ( ! empty( $content ) ) : printf( $this->settings['meta_fields']['og_url'] . "\n", esc_url( $content ) ); endif;
|
355 |
+
}
|
356 |
+
|
357 |
+
/**
|
358 |
+
* Open Graph image from featured image.
|
359 |
+
*
|
360 |
+
* @since 1.0.0
|
361 |
+
* @return void
|
362 |
+
*/
|
363 |
+
public function meta_og_image( ) {
|
364 |
+
$content = '';
|
365 |
+
// Check for feature image and use this as the open graph image.
|
366 |
+
if ( ! empty( $GLOBALS['post']->ID )
|
367 |
+
&& $meta = wp_get_attachment_image_src( get_post_thumbnail_id( $GLOBALS['post']->ID ), 'full' ) ) {
|
368 |
+
if ( ! empty( $meta[0] ) ) {
|
369 |
+
$content = $meta[0];
|
370 |
+
}
|
371 |
+
}
|
372 |
+
|
373 |
+
if ( $content ) : printf( $this->settings['meta_fields']['image'] . "\n", $content ); endif;
|
374 |
+
}
|
375 |
+
|
376 |
+
/**
|
377 |
+
* Set metarobots follow/index.
|
378 |
+
*
|
379 |
+
* @since 1.2.1
|
380 |
+
* @return void
|
381 |
+
*/
|
382 |
+
public function robots() {
|
383 |
+
$follow = 'follow';
|
384 |
+
$index = 'index';
|
385 |
+
if ( is_404() || is_search() ) {
|
386 |
+
$index = 'noindex';
|
387 |
+
}
|
388 |
+
if ( ! empty( $GLOBALS['post']->ID ) ) {
|
389 |
+
$follow = get_post_meta( $GLOBALS['post']->ID, 'bgseo_robots_follow', true );
|
390 |
+
$index = get_post_meta( $GLOBALS['post']->ID, 'bgseo_robots_index', true );
|
391 |
+
}
|
392 |
+
printf( $this->settings['meta_fields']['robots'] . "\n", esc_attr( $index ), esc_attr( $follow ) );
|
393 |
+
}
|
394 |
+
|
395 |
+
/**
|
396 |
+
* Get the blog's locale for OpenGraph.
|
397 |
+
*
|
398 |
+
* @since 1.2.1
|
399 |
+
*/
|
400 |
+
public function meta_og_locale() {
|
401 |
+
$locale = get_locale();
|
402 |
+
printf( $this->settings['meta_fields']['locale'] . "\n", $locale );
|
403 |
+
}
|
404 |
+
/**
|
405 |
+
* Open graph type.
|
406 |
+
*
|
407 |
+
* @since 1.2.1
|
408 |
+
*/
|
409 |
+
public function meta_og_type( ) {
|
410 |
+
$type = 'object';
|
411 |
+
if ( is_singular() ) {
|
412 |
+
$type = 'article';
|
413 |
+
}
|
414 |
+
if ( is_front_page() || is_home() ) {
|
415 |
+
$type = 'website';
|
416 |
+
}
|
417 |
+
|
418 |
+
printf( $this->settings['meta_fields']['og_type'] . "\n", $type );
|
419 |
+
}
|
420 |
+
|
421 |
+
public function meta_og_site_name() {
|
422 |
+
$name = get_bloginfo( 'name' );
|
423 |
+
printf( $this->settings['meta_fields']['og_site_name'] . "\n", $name );
|
424 |
+
}
|
425 |
+
}
|
includes/class-boldgrid-seo-butterbean.php
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
class Boldgrid_Seo_Butterbean {
|
3 |
+
public function __construct( $configs ) {
|
4 |
+
$this->configs = $configs;
|
5 |
+
$this->util = new Boldgrid_Seo_Util();
|
6 |
+
}
|
7 |
+
|
8 |
+
public function load() {
|
9 |
+
require_once( BOLDGRID_SEO_PATH . '/includes/lib/butterbean/butterbean.php' );
|
10 |
+
}
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Get custom control templates and load them in to render in our metabox.
|
14 |
+
*
|
15 |
+
* @since 1.2.1
|
16 |
+
*/
|
17 |
+
public function get_html_template( $located, $slug ) {
|
18 |
+
// Get the analysis control template.
|
19 |
+
if ( $slug === 'dashboard' ) {
|
20 |
+
$located = plugin_dir_path( dirname( __FILE__ ) ) . "/assets/partials/control-dashboard.php";
|
21 |
+
}
|
22 |
+
// Get the keywords control template.
|
23 |
+
if ( $slug === 'keywords' ) {
|
24 |
+
$located = plugin_dir_path( dirname( __FILE__ ) ) . "/assets/partials/control-keywords.php";
|
25 |
+
}
|
26 |
+
// Override the default textarea template.
|
27 |
+
if ( $slug === 'textarea' ) {
|
28 |
+
$located = plugin_dir_path( dirname( __FILE__ ) ) . "/assets/partials/control-bgseo-textarea.php";
|
29 |
+
}
|
30 |
+
|
31 |
+
return $located;
|
32 |
+
}
|
33 |
+
|
34 |
+
public function register( $butterbean, $post_type ) {
|
35 |
+
if ( 'page' !== $post_type && 'post' !== $post_type )
|
36 |
+
return;
|
37 |
+
/* === Register Managers === */
|
38 |
+
$butterbean->register_manager( 'boldgrid_seo', $this->configs['meta-box']['manager'] );
|
39 |
+
$manager = $butterbean->get_manager( 'boldgrid_seo' );
|
40 |
+
|
41 |
+
// Custom Analysis Control.
|
42 |
+
if ( ! class_exists( 'Boldgrid_Seo_Control_Dashboard' ) ) {
|
43 |
+
include_once plugin_dir_path( __FILE__ ) . "/class-boldgrid-seo-control-dashboard.php";
|
44 |
+
}
|
45 |
+
$butterbean->register_control_type( 'dashboard', 'Boldgrid_Seo_Control_Dashboard' );
|
46 |
+
|
47 |
+
// Custom Keywords Control.
|
48 |
+
if ( ! class_exists( 'Boldgrid_Seo_Control_Keywords' ) ) {
|
49 |
+
include_once plugin_dir_path( __FILE__ ) . "/class-boldgrid-seo-control-keywords.php";
|
50 |
+
}
|
51 |
+
$butterbean->register_control_type( 'keywords', 'Boldgrid_Seo_Control_Keywords' );
|
52 |
+
|
53 |
+
/* === Register Sections === */
|
54 |
+
$sections = $this->configs['meta-box']['section'];
|
55 |
+
foreach( $sections as $section => $settings ) {
|
56 |
+
$manager->register_section( $section, $settings );
|
57 |
+
}
|
58 |
+
/* === Register Controls === */
|
59 |
+
|
60 |
+
|
61 |
+
$controls = $this->configs['meta-box']['control'];
|
62 |
+
$controls['bgseo_canonical']['attr']['placeholder'] = ( isset( $_GET['post'] ) && ! empty( $_GET['post'] ) ) ? get_permalink( $_GET['post'] ) : '';
|
63 |
+
foreach( $controls as $control => $settings ) {
|
64 |
+
$manager->register_control( $control, $settings );
|
65 |
+
}
|
66 |
+
|
67 |
+
/* === Register Settings === */
|
68 |
+
$manager->register_setting(
|
69 |
+
'bgseo_title',
|
70 |
+
array( 'sanitize_callback' => 'wp_filter_nohtml_kses' )
|
71 |
+
);
|
72 |
+
$manager->register_setting(
|
73 |
+
'bgseo_description',
|
74 |
+
array( 'sanitize_callback' => 'wp_kses_post' )
|
75 |
+
);
|
76 |
+
$manager->register_setting(
|
77 |
+
'bgseo_canonical',
|
78 |
+
array( 'sanitize_callback' => 'esc_url_raw' )
|
79 |
+
);
|
80 |
+
$manager->register_setting(
|
81 |
+
'bgseo_robots_index',
|
82 |
+
array(
|
83 |
+
'default' => 'index',
|
84 |
+
'sanitize_callback' => 'sanitize_key'
|
85 |
+
)
|
86 |
+
);
|
87 |
+
$manager->register_setting(
|
88 |
+
'bgseo_robots_follow',
|
89 |
+
array(
|
90 |
+
'default' => 'follow',
|
91 |
+
'sanitize_callback' => 'sanitize_key'
|
92 |
+
)
|
93 |
+
);
|
94 |
+
$manager->register_setting(
|
95 |
+
'bgseo_custom_keyword',
|
96 |
+
array( 'sanitize_callback' => 'wp_filter_nohtml_kses' )
|
97 |
+
);
|
98 |
+
|
99 |
+
}
|
100 |
+
}
|
101 |
+
?>
|
includes/class-boldgrid-seo-config.php
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* BoldGrid Source Code
|
5 |
+
*
|
6 |
+
* @package Boldgrid_Seo_Config
|
7 |
+
* @copyright BoldGrid.com
|
8 |
+
* @version $Id$
|
9 |
+
* @author BoldGrid.com <wpb@boldgrod.com>
|
10 |
+
*/
|
11 |
+
|
12 |
+
// Prevent direct calls
|
13 |
+
if ( ! defined( 'WPINC' ) ) {
|
14 |
+
header( 'Status: 403 Forbidden' );
|
15 |
+
header( 'HTTP/1.1 403 Forbidden' );
|
16 |
+
exit();
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* BoldGrid Form configuration class
|
21 |
+
*/
|
22 |
+
class Boldgrid_Seo_Config implements Boldgrid_Seo_Config_Interface {
|
23 |
+
/**
|
24 |
+
* Configs.
|
25 |
+
*
|
26 |
+
* @var array
|
27 |
+
*/
|
28 |
+
protected $configs;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Get configs.
|
32 |
+
*/
|
33 |
+
public function get_configs() {
|
34 |
+
return $this->configs;
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Set configs.
|
39 |
+
*
|
40 |
+
* @param array $Configs
|
41 |
+
*
|
42 |
+
* @return bool
|
43 |
+
*/
|
44 |
+
protected function set_configs( $configs ) {
|
45 |
+
$this->configs = $configs;
|
46 |
+
return true;
|
47 |
+
}
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Constructor.
|
51 |
+
*/
|
52 |
+
public function __construct() {
|
53 |
+
$this->util = new Boldgrid_Seo_Util();
|
54 |
+
self::assign_configs();
|
55 |
+
self::assign_configs( 'i18n' );
|
56 |
+
$configs = $this->configs;
|
57 |
+
$local = BOLDGRID_SEO_PATH . '/includes/configs/config.local.php';
|
58 |
+
if ( file_exists( $local ) ) {
|
59 |
+
$file = include $local;
|
60 |
+
$configs = array_replace_recursive( $configs, $file );
|
61 |
+
}
|
62 |
+
$this->set_configs( $configs );
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Include customizer configuration options to assign.
|
67 |
+
*
|
68 |
+
* Configuration files for the customizer are loaded from
|
69 |
+
* includes/configs/customizer-options/.
|
70 |
+
*
|
71 |
+
* @since 1.1
|
72 |
+
* @access private
|
73 |
+
*/
|
74 |
+
public function assign_configs( $folder = '' ) {
|
75 |
+
$path = __DIR__ . '/configs/'. $folder;
|
76 |
+
if ( $folder === '' ) $this->configs = include $path . '/base.config.php';
|
77 |
+
foreach ( glob( $path . '/*.config.php' ) as $filename ) {
|
78 |
+
$option = basename( str_replace( '.config.php', '', $filename ) );
|
79 |
+
if ( ! empty( $folder ) ) {
|
80 |
+
$this->configs[ $folder ][ $option ] = include $filename;
|
81 |
+
} elseif ( 'base' === $option ) {
|
82 |
+
continue;
|
83 |
+
} else {
|
84 |
+
$this->configs[ $option ] = include $filename;
|
85 |
+
}
|
86 |
+
}
|
87 |
+
}
|
88 |
+
}
|
includes/class-boldgrid-seo-control-dashboard.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* BoldGrid SEO Dashboard Control.
|
4 |
+
*
|
5 |
+
* This is used to just rendour custom templates within a section.
|
6 |
+
*
|
7 |
+
* @package BoldGrid SEO
|
8 |
+
* @since 1.3.1
|
9 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Radio image control class.
|
14 |
+
*
|
15 |
+
* @since 1.0.0
|
16 |
+
* @access public
|
17 |
+
*/
|
18 |
+
class Boldgrid_Seo_Control_Dashboard extends ButterBean_Control {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* The type of control.
|
22 |
+
*
|
23 |
+
* @since 1.0.0
|
24 |
+
* @access public
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
public $type = 'dashboard';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access public
|
34 |
+
* @return void
|
35 |
+
*/
|
36 |
+
|
37 |
+
public function to_json() {
|
38 |
+
parent::to_json();
|
39 |
+
$this->json['value'] = $this->type;
|
40 |
+
}
|
41 |
+
/**
|
42 |
+
* Prints Underscore.js template.
|
43 |
+
*
|
44 |
+
* @since 1.0.0
|
45 |
+
* @access public
|
46 |
+
* @return void
|
47 |
+
*/
|
48 |
+
|
49 |
+
}
|
includes/class-boldgrid-seo-control-keywords.php
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* BoldGrid SEO Keywords Control.
|
4 |
+
*
|
5 |
+
* This is used to just rendour custom templates within a section.
|
6 |
+
*
|
7 |
+
* @package BoldGrid SEO
|
8 |
+
* @since 1.3.1
|
9 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Radio image control class.
|
14 |
+
*
|
15 |
+
* @since 1.0.0
|
16 |
+
* @access public
|
17 |
+
*/
|
18 |
+
class Boldgrid_Seo_Control_Keywords extends ButterBean_Control {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* The type of control.
|
22 |
+
*
|
23 |
+
* @since 1.0.0
|
24 |
+
* @access public
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
public $type = 'keywords';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access public
|
34 |
+
* @return void
|
35 |
+
*/
|
36 |
+
|
37 |
+
public function to_json() {
|
38 |
+
parent::to_json();
|
39 |
+
$this->json['value'] = $this->type;
|
40 |
+
}
|
41 |
+
}
|
includes/class-boldgrid-seo-deactivator.php
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Fired during plugin deactivation
|
5 |
+
*
|
6 |
+
* @link https://boldgrid.com
|
7 |
+
* @since 1.0.0
|
8 |
+
*
|
9 |
+
* @package Boldgrid_Seo
|
10 |
+
* @subpackage Boldgrid_Seo/includes
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Fired during plugin deactivation.
|
15 |
+
*
|
16 |
+
* This class defines all code necessary to run during the plugin's deactivation.
|
17 |
+
*
|
18 |
+
* @since 1.0.0
|
19 |
+
* @package Boldgrid_Seo
|
20 |
+
* @subpackage Boldgrid_Seo/includes
|
21 |
+
* @author BoldGrid <support@boldgrid.com>
|
22 |
+
*/
|
23 |
+
class Boldgrid_Seo_Deactivator {
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Short Description. (use period)
|
27 |
+
*
|
28 |
+
* Long Description.
|
29 |
+
*
|
30 |
+
* @since 1.0.0
|
31 |
+
*/
|
32 |
+
public static function deactivate() {
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
}
|
includes/class-boldgrid-seo-i18n.php
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Define the internationalization functionality
|
5 |
+
*
|
6 |
+
* Loads and defines the internationalization files for this plugin
|
7 |
+
* so that it is ready for translation.
|
8 |
+
*
|
9 |
+
* @link https://boldgrid.com
|
10 |
+
* @since 1.0.0
|
11 |
+
*
|
12 |
+
* @package Boldgrid_Seo
|
13 |
+
* @subpackage Boldgrid_Seo/includes
|
14 |
+
*/
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Define the internationalization functionality.
|
18 |
+
*
|
19 |
+
* Loads and defines the internationalization files for this plugin
|
20 |
+
* so that it is ready for translation.
|
21 |
+
*
|
22 |
+
* @since 1.0.0
|
23 |
+
* @package Boldgrid_Seo
|
24 |
+
* @subpackage Boldgrid_Seo/includes
|
25 |
+
* @author BoldGrid <support@boldgrid.com>
|
26 |
+
*/
|
27 |
+
class Boldgrid_Seo_i18n {
|
28 |
+
|
29 |
+
/**
|
30 |
+
* The domain specified for this plugin.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access private
|
34 |
+
* @var string $domain The domain identifier for this plugin.
|
35 |
+
*/
|
36 |
+
private $domain;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Load the plugin text domain for translation.
|
40 |
+
*
|
41 |
+
* @since 1.0.0
|
42 |
+
*/
|
43 |
+
public function load_plugin_textdomain() {
|
44 |
+
load_plugin_textdomain( $this->domain, false, BOLDGRID_SEO_PATH . '/languages/' );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Set the domain equal to that of the specified domain.
|
49 |
+
*
|
50 |
+
* @since 1.0.0
|
51 |
+
* @param string $domain
|
52 |
+
* The domain that represents the locale of this plugin.
|
53 |
+
*/
|
54 |
+
public function set_domain( $domain ) {
|
55 |
+
$this->domain = $domain;
|
56 |
+
}
|
57 |
+
}
|
includes/class-boldgrid-seo-loader.php
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Register all actions and filters for the plugin
|
5 |
+
*
|
6 |
+
* @link https://boldgrid.com
|
7 |
+
* @since 1.0.0
|
8 |
+
*
|
9 |
+
* @package Boldgrid_Seo
|
10 |
+
* @subpackage Boldgrid_Seo/includes
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Register all actions and filters for the plugin.
|
15 |
+
*
|
16 |
+
* Maintain a list of all hooks that are registered throughout
|
17 |
+
* the plugin, and register them with the WordPress API. Call the
|
18 |
+
* run function to execute the list of actions and filters.
|
19 |
+
*
|
20 |
+
* @package Boldgrid_Seo
|
21 |
+
* @subpackage Boldgrid_Seo/includes
|
22 |
+
* @author BoldGrid <support@boldgrid.com>
|
23 |
+
*/
|
24 |
+
class Boldgrid_Seo_Loader {
|
25 |
+
|
26 |
+
/**
|
27 |
+
* The array of actions registered with WordPress.
|
28 |
+
*
|
29 |
+
* @since 1.0.0
|
30 |
+
* @access protected
|
31 |
+
* @var array $actions The actions registered with WordPress to fire when the plugin loads.
|
32 |
+
*/
|
33 |
+
protected $actions;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* The array of filters registered with WordPress.
|
37 |
+
*
|
38 |
+
* @since 1.0.0
|
39 |
+
* @access protected
|
40 |
+
* @var array $filters The filters registered with WordPress to fire when the plugin loads.
|
41 |
+
*/
|
42 |
+
protected $filters;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Initialize the collections used to maintain the actions and filters.
|
46 |
+
*
|
47 |
+
* @since 1.0.0
|
48 |
+
*/
|
49 |
+
public function __construct() {
|
50 |
+
$this->actions = array();
|
51 |
+
$this->filters = array();
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Add a new action to the collection to be registered with WordPress.
|
56 |
+
*
|
57 |
+
* @since 1.0.0
|
58 |
+
* @param string $hook The name of the WordPress action that is being registered.
|
59 |
+
* @param object $component A reference to the instance of the object on which the action is defined.
|
60 |
+
* @param string $callback The name of the function definition on the $component.
|
61 |
+
* @param int Optional $priority The priority at which the function should be fired.
|
62 |
+
* @param int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
63 |
+
*/
|
64 |
+
public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
|
65 |
+
$this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Add a new filter to the collection to be registered with WordPress.
|
70 |
+
*
|
71 |
+
* @since 1.0.0
|
72 |
+
* @param string $hook The name of the WordPress filter that is being registered.
|
73 |
+
* @param object $component A reference to the instance of the object on which the filter is defined.
|
74 |
+
* @param string $callback The name of the function definition on the $component.
|
75 |
+
* @param int Optional $priority The priority at which the function should be fired.
|
76 |
+
* @param int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
77 |
+
*/
|
78 |
+
public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
|
79 |
+
$this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* A utility function that is used to register the actions and hooks into a single
|
84 |
+
* collection.
|
85 |
+
*
|
86 |
+
* @since 1.0.0
|
87 |
+
* @access private
|
88 |
+
* @param array $hooks The collection of hooks that is being registered (that is, actions or filters).
|
89 |
+
* @param string $hook The name of the WordPress filter that is being registered.
|
90 |
+
* @param object $component A reference to the instance of the object on which the filter is defined.
|
91 |
+
* @param string $callback The name of the function definition on the $component.
|
92 |
+
* @param int Optional $priority The priority at which the function should be fired.
|
93 |
+
* @param int Optional $accepted_args The number of arguments that should be passed to the $callback.
|
94 |
+
* @return type The collection of actions and filters registered with WordPress.
|
95 |
+
*/
|
96 |
+
private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
|
97 |
+
$hooks[] = array(
|
98 |
+
'hook' => $hook,
|
99 |
+
'component' => $component,
|
100 |
+
'callback' => $callback,
|
101 |
+
'priority' => $priority,
|
102 |
+
'accepted_args' => $accepted_args
|
103 |
+
);
|
104 |
+
|
105 |
+
return $hooks;
|
106 |
+
}
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Register the filters and actions with WordPress.
|
110 |
+
*
|
111 |
+
* @since 1.0.0
|
112 |
+
*/
|
113 |
+
public function run() {
|
114 |
+
foreach ( $this->filters as $hook ) {
|
115 |
+
add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
|
116 |
+
}
|
117 |
+
foreach ( $this->actions as $hook ) {
|
118 |
+
add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
|
119 |
+
}
|
120 |
+
}
|
121 |
+
}
|
includes/class-boldgrid-seo-scripts.php
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* BoldGrid Source Code
|
4 |
+
*
|
5 |
+
* @package Boldgrid_Seo_Config
|
6 |
+
* @copyright BoldGrid.com
|
7 |
+
* @version $Id$
|
8 |
+
* @author BoldGrid.com <wpb@boldgrod.com>
|
9 |
+
*/
|
10 |
+
|
11 |
+
/**
|
12 |
+
* BoldGrid SEO Script and Style Enqueue
|
13 |
+
*/
|
14 |
+
class Boldgrid_Seo_Scripts {
|
15 |
+
|
16 |
+
protected $configs;
|
17 |
+
|
18 |
+
public function __construct( $configs ) {
|
19 |
+
$this->configs = $configs;
|
20 |
+
$this->admin = new Boldgrid_Seo_Admin( $this->configs );
|
21 |
+
}
|
22 |
+
|
23 |
+
public function tiny_mce( $init ) {
|
24 |
+
$init['setup'] = "function( editor ) {
|
25 |
+
var timer;
|
26 |
+
editor.on( 'keyup propertychange paste', function ( e ) {
|
27 |
+
clearTimeout( timer );
|
28 |
+
timer = setTimeout( function() {
|
29 |
+
if ( typeof BOLDGRID !== 'undefined' && typeof BOLDGRID.SEO !== 'undefined' ) {
|
30 |
+
BOLDGRID.SEO.TinyMCE.tmceChange( e );
|
31 |
+
}
|
32 |
+
}, 2000 );
|
33 |
+
} );
|
34 |
+
}";
|
35 |
+
return $init;
|
36 |
+
}
|
37 |
+
/**
|
38 |
+
* Register the stylesheets for the admin area.
|
39 |
+
*
|
40 |
+
* @since 1.0.0
|
41 |
+
*/
|
42 |
+
public function enqueue_styles( $hook ) {
|
43 |
+
if ( ! in_array( $hook, array ( 'post.php','post-new.php' ) ) || ! in_array( $GLOBALS['post_type'], $this->admin->post_types() ) ) {
|
44 |
+
return;
|
45 |
+
}
|
46 |
+
|
47 |
+
// Check if script debug is disabled for minified assets.
|
48 |
+
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
49 |
+
|
50 |
+
wp_enqueue_style(
|
51 |
+
$this->configs['plugin_name'],
|
52 |
+
"{$this->configs['plugin_url']}/assets/css/boldgrid-seo-admin{$min}.css",
|
53 |
+
array(),
|
54 |
+
$this->configs['version'],
|
55 |
+
'all'
|
56 |
+
);
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Register the JavaScript for the admin area.
|
61 |
+
*
|
62 |
+
* @since 1.0.0
|
63 |
+
*/
|
64 |
+
public function enqueue_scripts( $hook ) {
|
65 |
+
if ( ! in_array( $hook, array ( 'post.php','post-new.php' ) ) || ! in_array( $GLOBALS['post_type'], $this->admin->post_types() ) ) {
|
66 |
+
return;
|
67 |
+
}
|
68 |
+
|
69 |
+
// Check if script debug is disabled for minified assets.
|
70 |
+
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
71 |
+
|
72 |
+
wp_register_script(
|
73 |
+
"{$this->configs['plugin_name']}-bgseo",
|
74 |
+
"{$this->configs['plugin_url']}/assets/js/bgseo{$min}.js",
|
75 |
+
array ( 'jquery', 'backbone', 'underscore', 'wp-util', 'word-count', 'butterbean' ),
|
76 |
+
$this->configs['version'],
|
77 |
+
true
|
78 |
+
);
|
79 |
+
|
80 |
+
// Register the script
|
81 |
+
wp_register_script(
|
82 |
+
"{$this->configs['plugin_name']}-text-statistics",
|
83 |
+
"{$this->configs['plugin_url']}/assets/js/text-statistics/index.js",
|
84 |
+
array ( 'jquery' ),
|
85 |
+
$this->configs['version'],
|
86 |
+
false
|
87 |
+
);
|
88 |
+
|
89 |
+
wp_enqueue_script( "{$this->configs['plugin_name']}-text-statistics" );
|
90 |
+
|
91 |
+
// Localize the script with new data.
|
92 |
+
wp_localize_script( "{$this->configs['plugin_name']}-bgseo", '_bgseoContentAnalysis', $this->configs['i18n'] );
|
93 |
+
|
94 |
+
// Enqueued script with localized data.
|
95 |
+
wp_enqueue_script( "{$this->configs['plugin_name']}-bgseo" );
|
96 |
+
}
|
97 |
+
}
|
includes/class-boldgrid-seo-upgrade.php
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* BoldGrid Source Code
|
5 |
+
*
|
6 |
+
* @package Boldgrid_Seo_Config
|
7 |
+
* @copyright BoldGrid.com
|
8 |
+
* @version $Id$
|
9 |
+
* @author BoldGrid.com <wpb@boldgrod.com>
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Boldgrid Upgrade Class
|
14 |
+
*
|
15 |
+
* Responsible for performing any upgrade methods that
|
16 |
+
* are version specific needs.
|
17 |
+
*
|
18 |
+
* @since 1.3.1
|
19 |
+
*/
|
20 |
+
class Boldgrid_Seo_Upgrade {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* BoldGrid SEO Configs array.
|
24 |
+
*
|
25 |
+
* @var array
|
26 |
+
*
|
27 |
+
* @access protected
|
28 |
+
*
|
29 |
+
* @since 1.3.1
|
30 |
+
*/
|
31 |
+
protected $configs;
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Prefix string used in plugin.
|
35 |
+
*
|
36 |
+
* @var string
|
37 |
+
*
|
38 |
+
* @access protected
|
39 |
+
*
|
40 |
+
* @since 1.3.1
|
41 |
+
*/
|
42 |
+
protected $prefix;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Constructor.
|
46 |
+
*
|
47 |
+
* @access public
|
48 |
+
*
|
49 |
+
* @since 1.3.1
|
50 |
+
*/
|
51 |
+
public function __construct( $configs ) {
|
52 |
+
$this->configs = $configs;
|
53 |
+
$this->prefix = str_replace( '-', '_', $this->configs['plugin_name'] );
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Checks the DB for current version number, and compares to version set by configs.
|
58 |
+
*
|
59 |
+
* If there's a method upgrade_to_MAJOR_MINOR_SUBMINOR() then that method
|
60 |
+
* will be executed if the method's specified version is less than/equal to the
|
61 |
+
* current version in configs, and greater than the stored version in the DB.
|
62 |
+
*
|
63 |
+
* Since we didn't need any upgrade methods initially, we will set the default
|
64 |
+
* version in the DB to 1.0.0 and run any upgrade methods required from then
|
65 |
+
* on. All additional upgrade methods in the future should be added here in
|
66 |
+
* the same format to be automatically managed and handled.
|
67 |
+
*
|
68 |
+
* @access public
|
69 |
+
*
|
70 |
+
* @since 1.3.1
|
71 |
+
*/
|
72 |
+
public function upgrade_db_check() {
|
73 |
+
$this->set_option( '1.0.0' );
|
74 |
+
// Set the default version in db if no version is set.
|
75 |
+
if ( ! $this->get_option() ) $this->set_option( '1.0.0' );
|
76 |
+
// Get current version from configs.
|
77 |
+
$version = $this->configs['version'];
|
78 |
+
// If the db version doesn't match the config version then run upgrade methods.
|
79 |
+
if ( $this->get_option() !== $version ) {
|
80 |
+
$methods = $this->get_upgrade_methods();
|
81 |
+
// Format found methods to versions.
|
82 |
+
foreach( $methods as $method ) {
|
83 |
+
$ver = substr( $method, 11 );
|
84 |
+
$ver = str_replace( '_', '.', $ver );
|
85 |
+
// Gives precedence to minor version specific upgrades over subminors.
|
86 |
+
$verHigh = str_replace( 'x', '9999', $ver );
|
87 |
+
$verLow = str_replace( 'x', '0', $ver );
|
88 |
+
// If upgrade method version is greater than stored DB version.
|
89 |
+
if ( version_compare( $verHigh, $this->get_option(), 'gt' ) &&
|
90 |
+
// The config version is less than or equal to upgrade method versions.
|
91 |
+
version_compare( $verLow, $version, 'le' ) ) {
|
92 |
+
if ( is_callable( array( $this, $method ) ) ) $this->$method();
|
93 |
+
}
|
94 |
+
}
|
95 |
+
|
96 |
+
// Once done with method calls, update the version number.
|
97 |
+
$this->set_option( $this->configs['version'] );
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Gets an array of upgrade methods.
|
103 |
+
*
|
104 |
+
* This checks __CLASS__ to see what methods are available
|
105 |
+
* as class
|
106 |
+
*
|
107 |
+
* @access public
|
108 |
+
*
|
109 |
+
* @since 1.3.1
|
110 |
+
*
|
111 |
+
* @return array $methods List of available upgrade methods.
|
112 |
+
*/
|
113 |
+
public function get_upgrade_methods() {
|
114 |
+
$methods = get_class_methods( $this );
|
115 |
+
$methods = array_filter( $methods, function( $key ) {
|
116 |
+
return strpos( $key, 'upgrade_to_' ) !== false;
|
117 |
+
});
|
118 |
+
|
119 |
+
return $methods;
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Get option.
|
124 |
+
*
|
125 |
+
* This checks if option has been set in db.
|
126 |
+
*
|
127 |
+
* @access public
|
128 |
+
*
|
129 |
+
* @since 1.3.1
|
130 |
+
*
|
131 |
+
* @return mixed Version as a string or false.
|
132 |
+
*/
|
133 |
+
public function get_option() {
|
134 |
+
return get_site_option( "{$this->prefix}_version" );
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Set option for version.
|
139 |
+
*
|
140 |
+
* This sets the version option in the db.
|
141 |
+
*
|
142 |
+
* @access public
|
143 |
+
*
|
144 |
+
* @since 1.3.1
|
145 |
+
*/
|
146 |
+
public function set_option( $version ) {
|
147 |
+
update_site_option( "{$this->prefix}_version", $version );
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Upgrade to version 1.3.x
|
152 |
+
*
|
153 |
+
* This will perform upgrade tasks for 1.3 as a minor version as a whole. This
|
154 |
+
* updates old postmeta meta_key naming to fit in with the new naming
|
155 |
+
* convention used in the plugin.
|
156 |
+
*
|
157 |
+
* @link https://codex.wordpress.org/Class_Reference/wpdb#UPDATE_rows
|
158 |
+
*
|
159 |
+
* @access public
|
160 |
+
*
|
161 |
+
* @since 1.3.1
|
162 |
+
*/
|
163 |
+
public function upgrade_to_1_3_x() {
|
164 |
+
global $wpdb;
|
165 |
+
$wpdb->update(
|
166 |
+
$wpdb->postmeta,
|
167 |
+
array(
|
168 |
+
'meta_key' => 'bgseo_description',
|
169 |
+
'meta_key' => 'bgseo_title',
|
170 |
+
),
|
171 |
+
array(
|
172 |
+
'meta_key' => 'meta_description',
|
173 |
+
'meta_key' => 'meta_title',
|
174 |
+
)
|
175 |
+
);
|
176 |
+
}
|
177 |
+
}
|
includes/class-boldgrid-seo-util.php
ADDED
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* BoldGrid Source Code
|
5 |
+
*
|
6 |
+
* @package Boldgrid_Seo_Config
|
7 |
+
* @copyright BoldGrid.com
|
8 |
+
* @version $Id$
|
9 |
+
* @author BoldGrid.com <wpb@boldgrod.com>
|
10 |
+
*/
|
11 |
+
|
12 |
+
// Prevent direct calls
|
13 |
+
if ( ! defined( 'WPINC' ) ) {
|
14 |
+
header( 'Status: 403 Forbidden' );
|
15 |
+
header( 'HTTP/1.1 403 Forbidden' );
|
16 |
+
exit();
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* BoldGrid Form configuration class
|
21 |
+
*/
|
22 |
+
class Boldgrid_Seo_Util {
|
23 |
+
/**
|
24 |
+
* Prepares our excerpt string.
|
25 |
+
*
|
26 |
+
* This takes our recommended length for a meta
|
27 |
+
* description, and returns only full words back.
|
28 |
+
*
|
29 |
+
* @since 1.2.1
|
30 |
+
*
|
31 |
+
* @param string $string String to prepare.
|
32 |
+
* @param int $length Max length of string.
|
33 |
+
*
|
34 |
+
* @return string $string Prepared string.
|
35 |
+
*/
|
36 |
+
public function prepare_words( $string, $length ) {
|
37 |
+
// Length passed should be an integer value.
|
38 |
+
if ( ! is_int( $length ) ) {
|
39 |
+
return $string;
|
40 |
+
}
|
41 |
+
// Recommended Length is 156 Characters for our Meta Description.
|
42 |
+
$string = substr( $string, 0, $length );
|
43 |
+
// If the string doesn't end with a space and still has spaces.
|
44 |
+
if ( substr( $string, -1 ) !== ' ' && substr_count( trim( $string ), ' ' ) > 0 ) {
|
45 |
+
// Get the position of the last space.
|
46 |
+
$position = strrpos( $string, ' ' );
|
47 |
+
// Then remove everything from that point on.
|
48 |
+
$string = substr( $string, 0, $position );
|
49 |
+
}
|
50 |
+
// Trim any whitespace.
|
51 |
+
$string = trim( $string );
|
52 |
+
|
53 |
+
return $string;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Attempts to grab complete sentences from excerpt.
|
58 |
+
*
|
59 |
+
* This calls prepare_words() to reduce string
|
60 |
+
*
|
61 |
+
* @since 1.2.1
|
62 |
+
*/
|
63 |
+
public function get_sentences( $string ) {
|
64 |
+
// Prepare our string.
|
65 |
+
$string = $this->prepare_words( $string, 156 );
|
66 |
+
// Seperate string into array based on sentences.
|
67 |
+
$strings = explode( '.', $string );
|
68 |
+
// Avoid abbreviations and numbered lists.
|
69 |
+
$sentences = array();
|
70 |
+
foreach( $strings as $sentence ) {
|
71 |
+
// Construct our sentences string
|
72 |
+
if ( strlen( $sentence ) > 2 && ! is_numeric( $sentence ) ) {
|
73 |
+
$sentences[] = $sentence;
|
74 |
+
}
|
75 |
+
}
|
76 |
+
// Check how many setences we have left and prepare the string for output.
|
77 |
+
$string = $this->construct_sentences( $sentences );
|
78 |
+
|
79 |
+
// Remove whitespace from string.
|
80 |
+
$string = trim( $string );
|
81 |
+
|
82 |
+
return $string;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Construct Sentences.
|
87 |
+
*
|
88 |
+
* This will check out the number of sentences in the
|
89 |
+
* array and format them for output.
|
90 |
+
*
|
91 |
+
* @since 1.2.1
|
92 |
+
*
|
93 |
+
* @param array $sentences An array containing sentences to format.
|
94 |
+
* @return string $string A String containing our formatted sentences.
|
95 |
+
*/
|
96 |
+
public function construct_sentences( $sentences ) {
|
97 |
+
$count = count( $sentences );
|
98 |
+
switch( $count ) {
|
99 |
+
// Check out a single sentence returned.
|
100 |
+
case 1 :
|
101 |
+
// Create string with our setence.
|
102 |
+
$sentences = implode( $sentences, '' ) . '.';
|
103 |
+
// If it's a longer sentence it should have ellipses.
|
104 |
+
strlen( $sentences ) < 130 ? : $sentences = $sentences . '..';
|
105 |
+
break;
|
106 |
+
// Two sentences retuned might contain a partial.
|
107 |
+
case 2 :
|
108 |
+
// Remove the partial sentences from the end.
|
109 |
+
array_pop( $sentences );
|
110 |
+
// Create single sentence with period at the end.
|
111 |
+
$sentences = implode( $sentences, '' ) . '.';
|
112 |
+
break;
|
113 |
+
// Multiple sentences are the most likely scenario.
|
114 |
+
default :
|
115 |
+
// Remove last sentence since it's likely a partial.
|
116 |
+
array_pop( $sentences );
|
117 |
+
// Create string with whole sentences and puncuation.
|
118 |
+
$sentences = implode( $sentences, '. ' );
|
119 |
+
// Remove any whitespace for output.
|
120 |
+
$sentences = trim( $sentences );
|
121 |
+
}
|
122 |
+
$string = $sentences;
|
123 |
+
|
124 |
+
return $string;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Set the default title per each page & post.
|
129 |
+
*
|
130 |
+
* @since 1.0.0
|
131 |
+
* @return string Page Title - Blog Name
|
132 |
+
*/
|
133 |
+
public function meta_title() {
|
134 |
+
if ( isset( $_GET['action'] ) && 'edit' === $_GET['action'] && isset( $_GET['post'] ) ) {
|
135 |
+
return apply_filters( 'the_title', get_the_title( $_GET['post'] ) ) . ' - ' . get_bloginfo( 'name' );
|
136 |
+
}
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Set the default meta description for each page & post.
|
141 |
+
*
|
142 |
+
* @since 1.2.1
|
143 |
+
* @return string $description A meta description that will be used by default.
|
144 |
+
*/
|
145 |
+
public function meta_description() {
|
146 |
+
$description = '';
|
147 |
+
if ( isset( $_GET['action'] ) && 'edit' === $_GET['action'] &&
|
148 |
+
isset( $_GET['post'] ) && $meta = get_post_field( 'post_content', $_GET['post'] ) ) {
|
149 |
+
// Get the first words of the page or post.
|
150 |
+
$description = wp_trim_words( strip_shortcodes( $meta ), '30', '' );
|
151 |
+
// Trim leading/trailing whitespace and html entities.
|
152 |
+
$description = trim( html_entity_decode( $description ), " \t\n\r\0\x0B\xC2\xA0" );
|
153 |
+
// Clean up description.
|
154 |
+
$description = $this->get_sentences( $description );
|
155 |
+
}
|
156 |
+
|
157 |
+
return $description;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Get the current url from query.
|
162 |
+
*
|
163 |
+
* @thanks All In One SEO for this this approach.
|
164 |
+
*
|
165 |
+
* @since 1.2.1
|
166 |
+
* @return $link A link for the current page in query.
|
167 |
+
*/
|
168 |
+
public function get_url( $query, $show_page = true ) {
|
169 |
+
if ( $query->is_404 ) {
|
170 |
+
return false;
|
171 |
+
}
|
172 |
+
$link = '';
|
173 |
+
$haspost = count( $query->posts ) > 0;
|
174 |
+
if ( get_query_var( 'm' ) ) {
|
175 |
+
$m = preg_replace( '/[^0-9]/', '', get_query_var( 'm' ) );
|
176 |
+
switch ( $p ) {
|
177 |
+
case 4:
|
178 |
+
$link = get_year_link( $m );
|
179 |
+
break;
|
180 |
+
case 6:
|
181 |
+
$link = get_month_link( $this->substr( $m, 0, 4 ), $this->substr( $m, 4, 2 ) );
|
182 |
+
break;
|
183 |
+
case 8:
|
184 |
+
$link = get_day_link( $this->substr( $m, 0, 4 ), $this->substr( $m, 4, 2 ), $this->substr( $m, 6, 2 ) );
|
185 |
+
break;
|
186 |
+
default:
|
187 |
+
return false;
|
188 |
+
}
|
189 |
+
} elseif ( $query->is_home && ( get_option( 'show_on_front' ) == 'page' ) && ( $pageid = get_option( 'page_for_posts' ) ) ) {
|
190 |
+
$link = get_permalink( $pageid );
|
191 |
+
} elseif ( is_front_page() || ( $query->is_home && ( get_option( 'show_on_front' ) != 'page' || ! get_option( 'page_for_posts' ) ) ) ) {
|
192 |
+
if ( function_exists( 'icl_get_home_url' ) ) {
|
193 |
+
$link = icl_get_home_url();
|
194 |
+
} else {
|
195 |
+
$link = trailingslashit( home_url() );
|
196 |
+
}
|
197 |
+
} elseif ( ( $query->is_single || $query->is_page ) && $haspost ) {
|
198 |
+
$post = $query->posts[0];
|
199 |
+
$link = get_permalink( $post->ID );
|
200 |
+
} elseif ( $query->is_author && $haspost ) {
|
201 |
+
$author = get_userdata( get_query_var( 'author' ) );
|
202 |
+
if ( false === $author ) {
|
203 |
+
return false;
|
204 |
+
}
|
205 |
+
$link = get_author_posts_url( $author->ID, $author->user_nicename );
|
206 |
+
} elseif ( $query->is_category && $haspost ) {
|
207 |
+
$link = get_category_link( get_query_var( 'cat' ) );
|
208 |
+
} elseif ( $query->is_tag && $haspost ) {
|
209 |
+
$tag = get_term_by( 'slug', get_query_var( 'tag' ), 'post_tag' );
|
210 |
+
if ( ! empty( $tag->term_id ) ) {
|
211 |
+
$link = get_tag_link( $tag->term_id );
|
212 |
+
}
|
213 |
+
} elseif ( $query->is_day && $haspost ) {
|
214 |
+
$link = get_day_link( get_query_var( 'year' ),
|
215 |
+
get_query_var( 'monthnum' ),
|
216 |
+
get_query_var( 'day' ) );
|
217 |
+
} elseif ( $query->is_month && $haspost ) {
|
218 |
+
$link = get_month_link( get_query_var( 'year' ),
|
219 |
+
get_query_var( 'monthnum' ) );
|
220 |
+
} elseif ( $query->is_year && $haspost ) {
|
221 |
+
$link = get_year_link( get_query_var( 'year' ) );
|
222 |
+
} elseif ( $query->is_tax && $haspost ) {
|
223 |
+
$taxonomy = get_query_var( 'taxonomy' );
|
224 |
+
$term = get_query_var( 'term' );
|
225 |
+
if ( ! empty( $term ) ) {
|
226 |
+
$link = get_term_link( $term, $taxonomy );
|
227 |
+
}
|
228 |
+
} elseif ( $query->is_archive && function_exists( 'get_post_type_archive_link' ) && ( $post_type = get_query_var( 'post_type' ) ) ) {
|
229 |
+
if ( is_array( $post_type ) ) {
|
230 |
+
$post_type = reset( $post_type );
|
231 |
+
}
|
232 |
+
$link = get_post_type_archive_link( $post_type );
|
233 |
+
} elseif ( $query->is_search ) {
|
234 |
+
$search_query = get_search_query();
|
235 |
+
// Regex catches case when /search/page/N without search term is itself mistaken for search term. R.
|
236 |
+
if ( ! empty( $search_query ) && ! preg_match( '|^page/\d+$|', $search_query ) ) {
|
237 |
+
$link = get_search_link();
|
238 |
+
}
|
239 |
+
} else {
|
240 |
+
return false;
|
241 |
+
}
|
242 |
+
if ( empty( $link ) || ! is_string( $link ) ) {
|
243 |
+
return false;
|
244 |
+
}
|
245 |
+
|
246 |
+
return $link;
|
247 |
+
}
|
248 |
+
}
|
includes/class-boldgrid-seo.php
ADDED
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* The core plugin class.
|
4 |
+
*
|
5 |
+
* This is used to define internationalization, admin-specific hooks, and
|
6 |
+
* public-facing site hooks.
|
7 |
+
*
|
8 |
+
* Also maintains the unique identifier of this plugin as well as the current
|
9 |
+
* version of the plugin.
|
10 |
+
*
|
11 |
+
* @since 1.0.0
|
12 |
+
* @package Boldgrid_Seo
|
13 |
+
* @subpackage Boldgrid_Seo/includes
|
14 |
+
* @author BoldGrid <support@boldgrid.com>
|
15 |
+
* @link https://boldgrid.com
|
16 |
+
*/
|
17 |
+
|
18 |
+
// If this file is called directly, abort.
|
19 |
+
defined( 'WPINC' ) ? : die();
|
20 |
+
class Boldgrid_Seo {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The loader that's responsible for maintaining and registering all hooks that power
|
24 |
+
* the plugin.
|
25 |
+
*
|
26 |
+
* @since 1.0.0
|
27 |
+
* @access protected
|
28 |
+
* @var Boldgrid_Seo_Loader $loader Maintains and registers all hooks for the plugin.
|
29 |
+
*/
|
30 |
+
protected $loader;
|
31 |
+
|
32 |
+
/**
|
33 |
+
* The unique identifier of this plugin.
|
34 |
+
*
|
35 |
+
* @since 1.0.0
|
36 |
+
* @access protected
|
37 |
+
* @var string $plugin_name The string used to uniquely identify this plugin.
|
38 |
+
*/
|
39 |
+
protected $plugin_name;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* The plugins configs.
|
43 |
+
*
|
44 |
+
* @since 1.0.0
|
45 |
+
* @access protected
|
46 |
+
* @var array An array of the plugins configurations.
|
47 |
+
*/
|
48 |
+
protected $configs = array();
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Define the core functionality of the plugin.
|
52 |
+
*
|
53 |
+
* Set the plugin name and the plugin version that can be used throughout the plugin.
|
54 |
+
* Load the dependencies, define the locale, and set the hooks for the admin area and
|
55 |
+
* the public-facing side of the site.
|
56 |
+
*
|
57 |
+
* @since 1.0.0
|
58 |
+
*/
|
59 |
+
public function __construct() {
|
60 |
+
$this->plugin_name = 'boldgrid-easy-seo';
|
61 |
+
$this->prefix = 'boldgrid-seo';
|
62 |
+
$this->load_dependencies();
|
63 |
+
$this->set_locale();
|
64 |
+
$this->boldgrid_seo_config();
|
65 |
+
$this->upgrade();
|
66 |
+
$this->boldgrid_seo_admin();
|
67 |
+
$this->load_butterbean();
|
68 |
+
$this->enqueue_scripts();
|
69 |
+
}
|
70 |
+
/**
|
71 |
+
* Load the BoldGrid SEO JS and CSS Files.
|
72 |
+
*/
|
73 |
+
public function enqueue_scripts() {
|
74 |
+
$scripts = new Boldgrid_Seo_Scripts( $this->configs );
|
75 |
+
$this->loader->add_action( 'admin_enqueue_scripts', $scripts, 'enqueue_styles' );
|
76 |
+
$this->loader->add_action( 'admin_enqueue_scripts', $scripts, 'enqueue_scripts' );
|
77 |
+
$this->loader->add_filter( 'tiny_mce_before_init', $scripts, 'tiny_mce' );
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Load the BoldGrid SEO update class
|
82 |
+
*/
|
83 |
+
public function boldgrid_seo_config() {
|
84 |
+
$configs = new Boldgrid_Seo_Config();
|
85 |
+
$this->configs = $configs->get_configs();
|
86 |
+
}
|
87 |
+
|
88 |
+
public function upgrade() {
|
89 |
+
$upgrade = new Boldgrid_Seo_Upgrade( $this->configs );
|
90 |
+
$this->loader->add_action( 'plugins_loaded', $upgrade, 'upgrade_db_check' );
|
91 |
+
|
92 |
+
}
|
93 |
+
/**
|
94 |
+
* Load the BoldGrid SEO update class
|
95 |
+
*/
|
96 |
+
public function load_butterbean() {
|
97 |
+
$butterbean = new Boldgrid_Seo_Butterbean( $this->configs );
|
98 |
+
$this->loader->add_action( 'plugins_loaded', $butterbean, 'load' );
|
99 |
+
//$this->loader->add_action( 'load-post-new.php', $butterbean, 'load' );
|
100 |
+
$this->loader->add_action( 'butterbean_register', $butterbean, 'register', 10, 2 );
|
101 |
+
// Add our custom template checks.
|
102 |
+
$this->loader->add_filter( 'butterbean_control_template', $butterbean, 'get_html_template', 10, 2 );
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Load the required dependencies for this plugin.
|
107 |
+
*
|
108 |
+
* Include the following files that make up the plugin:
|
109 |
+
*
|
110 |
+
* - Boldgrid_Seo_Loader. Orchestrates the hooks of the plugin.
|
111 |
+
* - Boldgrid_Seo_i18n. Defines internationalization functionality.
|
112 |
+
* - Boldgrid_Seo_Admin. Defines all hooks for the admin area.
|
113 |
+
* - Boldgrid_Seo_Meta_Field. Defines all the hooks for the Metaboxes in Page/Post Editor
|
114 |
+
*
|
115 |
+
* Create an instance of the loader which will be used to register the hooks
|
116 |
+
* with WordPress.
|
117 |
+
*
|
118 |
+
* @since 1.0.0
|
119 |
+
* @access private
|
120 |
+
*/
|
121 |
+
private function load_dependencies() {
|
122 |
+
$this->loader = new Boldgrid_Seo_Loader();
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Define the locale for this plugin for internationalization.
|
127 |
+
*
|
128 |
+
* Uses the Boldgrid_Seo_i18n class in order to set the domain and to register the hook
|
129 |
+
* with WordPress.
|
130 |
+
*
|
131 |
+
* @since 1.0.0
|
132 |
+
* @access private
|
133 |
+
*/
|
134 |
+
private function set_locale() {
|
135 |
+
$plugin_i18n = new Boldgrid_Seo_i18n();
|
136 |
+
$plugin_file = plugin_dir_path( dirname( __FILE__ ) ) . $this->plugin_name . '.php';
|
137 |
+
$plugin_i18n->set_domain( implode( get_file_data( $plugin_file , array( 'Version' ), 'plugin' ) ) );
|
138 |
+
$this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' );
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Register all of the hooks related to the admin area functionality
|
143 |
+
* of the plugin.
|
144 |
+
*
|
145 |
+
* @since 1.0.0
|
146 |
+
* @access private
|
147 |
+
*/
|
148 |
+
private function boldgrid_seo_admin() {
|
149 |
+
$admin = new Boldgrid_Seo_Admin( $this->configs );
|
150 |
+
$this->loader->add_action( 'wp_head', $admin, 'wp_head', 1 );
|
151 |
+
$this->loader->add_action( "{$this->prefix}/seo/description", $admin, 'meta_description' );
|
152 |
+
$this->loader->add_action( "{$this->prefix}/seo/robots", $admin, 'robots' );
|
153 |
+
$this->loader->add_action( "{$this->prefix}/seo/canonical", $admin, 'canonical_url' );
|
154 |
+
$this->loader->add_action( "{$this->prefix}/seo/canonical", $admin, 'meta_og_locale' );
|
155 |
+
$this->loader->add_action( "{$this->prefix}/seo/og:title", $admin, 'meta_og_title' );
|
156 |
+
$this->loader->add_action( "{$this->prefix}/seo/og:site_name", $admin, 'meta_og_site_name' );
|
157 |
+
$this->loader->add_action( "{$this->prefix}/seo/og:type", $admin, 'meta_og_type' );
|
158 |
+
$this->loader->add_action( "{$this->prefix}/seo/og:url", $admin, 'meta_og_url' );
|
159 |
+
$this->loader->add_action( "{$this->prefix}/seo/og:description", $admin, 'meta_og_description' );
|
160 |
+
|
161 |
+
// Check version for updated filters
|
162 |
+
$wp_version = version_compare( get_bloginfo( 'version' ), '4.4', '>=' );
|
163 |
+
if ( $wp_version ) {
|
164 |
+
$this->loader->add_filter( 'pre_get_document_title', $admin, 'wp_title', 99, 2 );
|
165 |
+
} else {
|
166 |
+
$this->loader->add_filter( 'wp_title', $admin, 'wp_title', 99, 2 );
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Run the loader to execute all of the hooks with WordPress.
|
172 |
+
*
|
173 |
+
* @since 1.0.0
|
174 |
+
*/
|
175 |
+
public function run() {
|
176 |
+
$this->loader->run();
|
177 |
+
}
|
178 |
+
|
179 |
+
/**
|
180 |
+
* The name of the plugin used to uniquely identify it within the context of
|
181 |
+
* WordPress and to define internationalization functionality.
|
182 |
+
*
|
183 |
+
* @since 1.0.0
|
184 |
+
* @return string The name of the plugin.
|
185 |
+
*/
|
186 |
+
public function get_plugin_name() {
|
187 |
+
return $this->plugin_name;
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* The reference to the class that orchestrates the hooks with the plugin.
|
192 |
+
*
|
193 |
+
* @since 1.0.0
|
194 |
+
* @return Boldgrid_Seo_Loader Orchestrates the hooks of the plugin.
|
195 |
+
*/
|
196 |
+
public function get_loader() {
|
197 |
+
return $this->loader;
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* The unique prefix used in the plugin.
|
202 |
+
*
|
203 |
+
* @since 1.0.0
|
204 |
+
* @return string The prefix used for BoldGrid SEO.
|
205 |
+
*/
|
206 |
+
public function get_prefix() {
|
207 |
+
return $this->prefix;
|
208 |
+
}
|
209 |
+
}
|
includes/configs/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
*.local.php
|
includes/configs/admin.config.php
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'post_types' => array( '' ),
|
4 |
+
'meta_fields' => array(
|
5 |
+
'description' => '<meta name="description" content="%s" />',
|
6 |
+
'robots' => '<meta name="robots" content="%s,%s" />',
|
7 |
+
'keywords' => '<meta name="keywords" content="%s" />',
|
8 |
+
'locale' => '<meta property="og:locale" content="%s" />',
|
9 |
+
'og_description' => '<meta property="og:description" content="%s" />',
|
10 |
+
'classification' => '<meta property="og:classification" content="%s" />',
|
11 |
+
'site_name' => '<meta property="og:site_name" name="copyright" content="%s" />',
|
12 |
+
'title' => '<meta property="og:title" content="%s" />',
|
13 |
+
'image' => '<meta property="og:image" content="%s" />',
|
14 |
+
'og_type' => '<meta property="og:type" content="%s" />',
|
15 |
+
'og_url' => '<meta property="og:url" content="%s" />',
|
16 |
+
'og_site_name' => '<meta property="og:site_name" content="%s" />',
|
17 |
+
'canonical' => '<link rel="canonical" href="%s" />',
|
18 |
+
),
|
19 |
+
);
|
includes/configs/base.config.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
$plugin = 'boldgrid-easy-seo';
|
3 |
+
$base_path = wp_normalize_path( plugin_dir_path( dirname( dirname(__FILE__) ) ) );
|
4 |
+
$base_url = dirname( plugin_dir_url( __DIR__ ) );
|
5 |
+
return array(
|
6 |
+
'version' => implode( get_file_data( $base_path . $plugin . '.php', array( 'Version' ), 'plugin' ) ),
|
7 |
+
'plugin_path' => $base_path,
|
8 |
+
'plugin_url' => $base_url,
|
9 |
+
'plugin_name' => $plugin,
|
10 |
+
);
|
includes/configs/config.sample.php
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Copy this sample file to config.local.php and update it with any variables that you would like to override
|
4 |
+
*/
|
5 |
+
return array(
|
6 |
+
'asset_server' => 'https://wp-assets-dev.boldgrid.com',
|
7 |
+
);
|
includes/configs/i18n/content.config.php
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'length' => array(
|
4 |
+
/** Translators: %s is the total Word Count of the content. **/
|
5 |
+
'contentLength' => __( 'Word Count: %s.', 'bgseo' ),
|
6 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
7 |
+
'badEmpty' => sprintf( __( 'You haven\'t entered any %1$scontent%2$s yet! As you start writing your content, we\'ll make recommendations for better SEO!', 'bgseo' ),
|
8 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content-length" target="_blank">',
|
9 |
+
'</a>'
|
10 |
+
),
|
11 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
12 |
+
'badShort' => sprintf( __( 'The content should be longer, we recommend %1$sat least 300 words%2$s. Try writing more about the focus keyword phrase of this page.', 'bgseo' ),
|
13 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content-length" target="_blank">',
|
14 |
+
'</a>'
|
15 |
+
),
|
16 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
17 |
+
'ok' => sprintf( __( 'We recommend a %1$sminimum of 300 words%2$s for the best SEO results.', 'bgseo' ),
|
18 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content-length" target="_blank">',
|
19 |
+
'</a>'
|
20 |
+
),
|
21 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
22 |
+
'good' => sprintf( __( 'Your content is over the recommended %1$sminimum of 300 words%2$s, good job!', 'bgseo' ),
|
23 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content-length" target="_blank">',
|
24 |
+
'</a>'
|
25 |
+
),
|
26 |
+
// Max value.
|
27 |
+
'badShortScore' => 199,
|
28 |
+
// Max value.
|
29 |
+
'okScore' => 300,
|
30 |
+
),
|
31 |
+
// Since there's no _n() in the js implementation, I'll just add a singular and plural translation for now.
|
32 |
+
'keywordUsage' => array(
|
33 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
34 |
+
'bad' => sprintf( __( 'You haven\'t used your %1$skeyword phrase in your content%2$s at all. Try adding it naturally by talking about the subject more.', 'bgseo' ),
|
35 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
36 |
+
'</a>'
|
37 |
+
),
|
38 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
39 |
+
'okShortSingular' => sprintf( __( 'Your %1$skeyword phrase is being used in your content%2$s, but we recommend using it at least 1 time.', 'bgseo' ),
|
40 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
41 |
+
'</a>'
|
42 |
+
),
|
43 |
+
|
44 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag %%s: is equal to the count that the keyword should appear and added by JS */
|
45 |
+
'okShort' => sprintf( __( 'Your %1$skeyword phrase is being used in your content%2$s, but we recommend using it at least %%s times.', 'bgseo' ),
|
46 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
47 |
+
'</a>'
|
48 |
+
),
|
49 |
+
|
50 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
51 |
+
'okLongSingular' => sprintf( __( 'Your %1$skeyword phrase appears excessively in your content%2$s. Try to only use it 1 time and use other words and variations that are related to it.', 'bgseo' ),
|
52 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
53 |
+
'</a>'
|
54 |
+
),
|
55 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag %%s: is equal to the count that the keyword should appear and added by JS */
|
56 |
+
'okLong' => sprintf( __( 'Your %1$skeyword phrase appears excessively in your content%2$s. Try to only use it %%s times and use other words and variations that are related to it.', 'bgseo' ),
|
57 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
58 |
+
'</a>'
|
59 |
+
),
|
60 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
61 |
+
'goodSingular' => sprintf( __( 'Great, you have included the %1$skeyword in your content%2$s at least 1 time. This helps get you a better SEO score!', 'bgseo' ),
|
62 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
63 |
+
'</a>'
|
64 |
+
),
|
65 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag %%s: is equal to the count that the keyword should appear and added by JS */
|
66 |
+
'good' => sprintf( __( 'Great, you have included the %1$skeyword phrase in your content%2$s at least %%s times. This helps get you a better SEO score!', 'bgseo' ),
|
67 |
+
'<a href="https://boldgrid.com/support/seo/keywords#content" target="_blank">',
|
68 |
+
'</a>'
|
69 |
+
),
|
70 |
+
),
|
71 |
+
);
|
includes/configs/i18n/headings.config.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'h1' => array(
|
4 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
5 |
+
'good' => sprintf( __( 'It looks like this post is using %1$sonly one H1 tag%2$s! Good job!', 'bgseo' ),
|
6 |
+
'<a href="https://boldgrid.com/support/seo/keywords#h1-usage" target="_blank">',
|
7 |
+
'</a>'
|
8 |
+
),
|
9 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
10 |
+
'badMultiple' => sprintf( __ ( 'This post has %1$smore than one H1 tag%2$s which can negatively impact your SEO. You should try to only have one H1 on your page.', 'bgseo' ),
|
11 |
+
'<a href="https://boldgrid.com/support/seo/keywords#h1-usage" target="_blank">',
|
12 |
+
'</a>'
|
13 |
+
),
|
14 |
+
'badBoldgridTheme' => sprintf( __ ( 'This post has %1$smore than one H1 tag%2$s. Unchecking the %3$s"Display page title"%4$s box at the top of this page will remove an H1 from your page.', 'bgseo' ),
|
15 |
+
'<a href="https://boldgrid.com/support/seo/keywords#h1-usage" target="_blank">',
|
16 |
+
'</a>',
|
17 |
+
'<a href="https://boldgrid.com/support/seo/keywords#hide-page-title" target="_blank">',
|
18 |
+
'</a>'
|
19 |
+
),
|
20 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
21 |
+
'badEmpty' => sprintf( __( 'Your page %1$sdoesn\'t have any H1 tags%2$s on it, you should considering adding one that includes your target keyword!', 'bgseo' ),
|
22 |
+
'<a href="https://boldgrid.com/support/seo/keywords#h1-usage" target="_blank">',
|
23 |
+
'</a>'
|
24 |
+
),
|
25 |
+
),
|
26 |
+
'keywordUsage' => array(
|
27 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
28 |
+
'good' => sprintf( __( 'Your %1$skeyword appears in your H1 and H2 tags%2$s, which is good for your search engine optimization!', 'bgseo' ),
|
29 |
+
'<a href="https://boldgrid.com/support/seo/keywords#headings" target="_blank">',
|
30 |
+
'</a>'
|
31 |
+
),
|
32 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
33 |
+
'bad' => sprintf( __( 'You have not %1$sused your keyword in any H1 or H2 tags%2$s. You should try to include this at least once.', 'bgseo' ),
|
34 |
+
'<a href="https://boldgrid.com/support/seo/keywords#headings" target="_blank">',
|
35 |
+
'</a>'
|
36 |
+
),
|
37 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
38 |
+
'ok' => sprintf( __( 'The %1$skeyword appears too much in your H1 and H2 tags%2$s.', 'bgseo' ),
|
39 |
+
'<a href="https://boldgrid.com/support/seo/keywords#headings" target="_blank">',
|
40 |
+
'</a>'
|
41 |
+
),
|
42 |
+
),
|
43 |
+
);
|
includes/configs/i18n/image.config.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'length' => array(
|
4 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
5 |
+
'good' => sprintf( __( 'Your article %1$scontains at least one image%2$s, which is great for SEO, awesome!', 'bgseo' ),
|
6 |
+
'<a href="https://boldgrid.com/support/seo/keywords#images" target="_blank">',
|
7 |
+
'</a>'
|
8 |
+
),
|
9 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
10 |
+
'bad' => sprintf( __( 'Try adding %1$sat least one image%2$s that\'s relevant to your content\'s topic to further optimize your page.', 'bgseo' ),
|
11 |
+
'<a href="https://boldgrid.com/support/seo/keywords#images" target="_blank">',
|
12 |
+
'</a>'
|
13 |
+
),
|
14 |
+
),
|
15 |
+
);
|
includes/configs/i18n/keywords.config.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'recommendedKeyword' => array(
|
4 |
+
'template' => '%s: <b>%s</b>.',
|
5 |
+
'text' => __( 'Based on your content and frequency, search engines will likely think your content is about', 'bgseo' ),
|
6 |
+
'setNewTarget' => __( 'Set a new target keyword below, and the dashboard will be updated with new stats!', 'bgseo' ),
|
7 |
+
),
|
8 |
+
// Values are percentages.
|
9 |
+
'recommendedCount' => array(
|
10 |
+
'min' => 0.5,
|
11 |
+
'max' => 2.5,
|
12 |
+
),
|
13 |
+
'keywordPhrase' => array(
|
14 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
15 |
+
'good' => sprintf( __( 'Great, you\'ve entered a %1$skeyword phrase%2$s for the focus of your content!', 'bgseo' ),
|
16 |
+
'<a href="https://boldgrid.com/support/seo/keywords#keyword-phrase-length" target="_blank">',
|
17 |
+
'</a>'
|
18 |
+
),
|
19 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
20 |
+
'ok' => sprintf( __( 'It looks like you have entered a single word for the keyword. We recommend adding a %1$skeyword phrase%2$s instead of a single word for better results.', 'bgseo' ),
|
21 |
+
'<a href="https://boldgrid.com/support/seo/keywords#keyword-phrase-length" target="_blank">',
|
22 |
+
'</a>'
|
23 |
+
),
|
24 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
25 |
+
'bad' => sprintf( __( 'You haven\'t entered a %1$skeyword phrase%2$s for the focus of this content. This helps guide you in writing better optimized content!', 'bgseo' ),
|
26 |
+
'<a href="https://boldgrid.com/support/seo/keywords#keyword-phrase-length" target="_blank">',
|
27 |
+
'</a>'
|
28 |
+
),
|
29 |
+
),
|
30 |
+
);
|
includes/configs/i18n/noFollow.config.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
4 |
+
'good' => sprintf( __( 'Great, your links are set to %1$sfollow%2$s for search engines!', 'bgseo' ),
|
5 |
+
'<a href="https://boldgrid.com/support/seo/search-visibility#follow" target="_blank">',
|
6 |
+
'</a>'
|
7 |
+
),
|
8 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
9 |
+
'bad' => sprintf( __( 'Your links are set to %1$snofollow%2$s for search engines!', 'bgseo' ),
|
10 |
+
'<a href="https://boldgrid.com/support/seo/search-visibility#follow" target="_blank">',
|
11 |
+
'</a>'
|
12 |
+
),
|
13 |
+
);
|
includes/configs/i18n/noIndex.config.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
4 |
+
'good' => sprintf( __( 'This article is set to %1$sindex%2$s, so it is being indexed by search engines!', 'bgseo' ),
|
5 |
+
'<a href="https://boldgrid.com/support/seo/search-visibility#index" target="_blank">',
|
6 |
+
'</a>'
|
7 |
+
),
|
8 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
9 |
+
'bad' => sprintf( __( 'This page is set to %1$snoindex%2$s, so it is being blocked from search engine indexing!', 'bgseo' ),
|
10 |
+
'<a href="https://boldgrid.com/support/seo/search-visibility#index" target="_blank">',
|
11 |
+
'</a>'
|
12 |
+
),
|
13 |
+
);
|
includes/configs/i18n/readingEase.config.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'score' => __( 'Score: %s%.', 'bgseo' ), // Score generated based on content's readability.
|
4 |
+
'goodHigh' => __( 'Your content\'s readability is looking great! It\'s very easy to understand by the majority of readers!', 'bgseo' ),
|
5 |
+
'goodMedHigh' => __( 'The readability of your content is easy to understand by most readers! Awesome!', 'bgseo' ),
|
6 |
+
'goodMedLow' => __( 'Your content is pretty easy to understand by most readers.', 'bgseo' ),
|
7 |
+
'goodLow' => __( 'The readability of this content is okay, but could use some improvements to reach a wider audience.', 'bgseo' ),
|
8 |
+
'ok' => __( 'Your content is pretty hard to read, so you should try to simplify it.', 'bgseo' ),
|
9 |
+
'badHigh' => __( 'The content is hard to read. Try shortening some sentences and using less complex wording to improve this score.', 'bgseo' ),
|
10 |
+
'badLow' => __( 'The text here is very hard to read, and is best understood by university graduates. You should consider making your sentences shorter and using easier words for people to understand.', 'bgseo' ),
|
11 |
+
);
|
includes/configs/i18n/seoDescription.config.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'length' => array(
|
4 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
5 |
+
'badEmpty' => sprintf( __( 'Your custom %1$sSEO Description%2$s is empty! Try adding a description with your focus keyword phrase.', 'bgseo' ),
|
6 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
|
7 |
+
'</a>'
|
8 |
+
),
|
9 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
10 |
+
'badLong' => sprintf( __( 'Your custom %1$sSEO Description%2$s is over the 156 character recommended length, you should consider making it shorter.', 'bgseo' ),
|
11 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
|
12 |
+
'</a>'
|
13 |
+
),
|
14 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
15 |
+
'ok' => sprintf( __( 'You should make your %1$sSEO Description%2$s longer! We recommend 125-156 characters for the best results.', 'bgseo' ),
|
16 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
|
17 |
+
'</a>'
|
18 |
+
),
|
19 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
20 |
+
'good' => sprintf( __( 'Your %1$sSEO Description%2$s looks great, and is optimized for search engines!', 'bgseo' ),
|
21 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#description" target="_blank">',
|
22 |
+
'</a>'
|
23 |
+
),
|
24 |
+
// Max value.
|
25 |
+
'okScore' => 125,
|
26 |
+
// Max value.
|
27 |
+
'goodScore' => 156,
|
28 |
+
),
|
29 |
+
'keywordUsage' => array(
|
30 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
31 |
+
'bad' => sprintf( __( 'Try incorporating your focus keyword phrase to your custom %1$sSEO Description%2$s for better optimization!', 'bgseo' ),
|
32 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#description" target="_blank">',
|
33 |
+
'</a>'
|
34 |
+
),
|
35 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
36 |
+
'ok' => sprintf( __( 'Your focus keyword phrase is used too frequently in your %1$sSEO Description%2$s. You should try removing some of the references.', 'bgseo' ),
|
37 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#description" target="_blank">',
|
38 |
+
'</a>'
|
39 |
+
),
|
40 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
41 |
+
'good' => sprintf( __( 'The %1$sSEO Description%2$s is properly optimized by using your focus keyword phrase! Good job!', 'bgseo' ),
|
42 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#description" target="_blank">',
|
43 |
+
'</a>'
|
44 |
+
),
|
45 |
+
),
|
46 |
+
'stopWords' => array(
|
47 |
+
'ok' => __( 'Your title makes use of a stop word. We don\'t recommend using these as they can negatively imapct your SEO efforts', 'bgseo' ),
|
48 |
+
'good' => __( 'Your title doesn\'t use any stop words that will negatively impact your SEO ranking! Good Job!', 'bgseo' ),
|
49 |
+
),
|
50 |
+
);
|
includes/configs/i18n/seoTitle.config.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'length' => array(
|
4 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
5 |
+
'badEmpty' => sprintf( __( 'You haven\'t entered a custom %1$sSEO Title%2$s to your page, you should consider adding one.', 'bgseo' ),
|
6 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#title" target="_blank">',
|
7 |
+
'</a>'
|
8 |
+
),
|
9 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
10 |
+
'badLong' => sprintf( __( 'Your custom %1$sSEO Title%2$s is longer than the recommended 70 characters, you should consider making it shorter.', 'bgseo' ),
|
11 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#title" target="_blank">',
|
12 |
+
'</a>'
|
13 |
+
),
|
14 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
15 |
+
'ok' => sprintf( __( 'We suggest making your %1$sSEO Title%2$s at least 30 characters.', 'bgseo' ),
|
16 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#title" target="_blank">',
|
17 |
+
'</a>'
|
18 |
+
),
|
19 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
20 |
+
'good' => sprintf( __( 'Your %1$sSEO Title%2$s is a good length, and optimized for search engines!', 'bgseo' ),
|
21 |
+
'<a href="https://www.boldgrid.com/support/seo/title-and-description#title" target="_blank">',
|
22 |
+
'</a>'
|
23 |
+
),
|
24 |
+
// Max value.
|
25 |
+
'okScore' => 30,
|
26 |
+
// Max value.
|
27 |
+
'goodScore' => 70,
|
28 |
+
),
|
29 |
+
'keywordUsage' => array(
|
30 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
31 |
+
'bad' => sprintf( __( 'You should try to use your focus keyword phrase at least one time in your %1$sSEO Title%2$s.', 'bgseo' ),
|
32 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#title" target="_blank">',
|
33 |
+
'</a>'
|
34 |
+
),
|
35 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
36 |
+
'ok' => sprintf( __( 'It’s great you’ve used the focus keyword phrase in your %1$sSEO Title%2$s, but you should try to only use that one time.', 'bgseo' ),
|
37 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#title" target="_blank">',
|
38 |
+
'</a>'
|
39 |
+
),
|
40 |
+
/* translators: 1: opening <a> tag 2: closing </a> tag */
|
41 |
+
'good' => sprintf( __( 'Your %1$sSEO Title%2$s is optimized by using your focus keyword phrase!', 'bgseo' ),
|
42 |
+
'<a href="https://www.boldgrid.com/support/seo/keywords#title" target="_blank">',
|
43 |
+
'</a>'
|
44 |
+
),
|
45 |
+
),
|
46 |
+
'stopWords' => array(
|
47 |
+
'ok' => __( 'Your title makes use of a stop word. We don\'t recommend using these as they can negatively imapct your SEO efforts', 'bgseo' ),
|
48 |
+
'good' => __( 'Your title doesn\'t use any stop words that will negatively impact your SEO ranking! Good Job!', 'bgseo' ),
|
49 |
+
),
|
50 |
+
);
|
includes/configs/i18n/stopWords.config.php
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return __( "a,about,above,after,again,against,all,am,an,and,any,are,aren't,as,at,be,because,been,before,being,below,between,both,but,by,can,can't,cannot,could,couldn't,did,didn't,do,does,doesn't,doing,don't,down,during,each,every,few,for,from,further,had,hadn't,has,hasn't,have,haven't,having,he,he'd,he'll,he's,her,here,here's,hers,herself,him,himself,his,how,how's,i,i'd,i'll,i'm,i've,if,in,into,is,isn't,it,it's,its,itself,let's,me,more,most,mustn't,my,myself,no,nor,not,of,off,on,once,only,or,other,ought,our,ours,ourselves,out,over,own,same,shan't,she,she'd,she'll,she's,should,shouldn't,so,some,such,than,that,that's,the,their,theirs,them,themselves,then,there,there's,these,they,they'd,they'll,they're,they've,this,those,through,to,too,under,until,up,very,was,wasn't,we,we'd,we'll,we're,we've,were,weren't,what,what's,when,when's,where,where's,which,while,who,who's,whom,why,why's,with,won't,would,wouldn't,you,you'd,you'll,you're,you've,your,yours,yourself,yourselves" , 'bgseo' );
|
includes/configs/index.php
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<?php
|
2 |
+
// Silence is golden.
|
includes/configs/meta-box.config.php
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
return array(
|
3 |
+
'post_types' => array(
|
4 |
+
'post',
|
5 |
+
'page'
|
6 |
+
),
|
7 |
+
'nonce' => array(
|
8 |
+
'action' => 'boldgrid-seo',
|
9 |
+
'name' => 'boldgrid-seo_nonce',
|
10 |
+
),
|
11 |
+
'manager' => array(
|
12 |
+
'label' => __( 'Easy SEO', 'bgseo' ),
|
13 |
+
'post_type' => array( 'post', 'page' ),
|
14 |
+
'context' => 'normal',
|
15 |
+
'priority' => 'high',
|
16 |
+
),
|
17 |
+
'section' => array(
|
18 |
+
'bgseo_keywords' => array(
|
19 |
+
'label' => __( 'Keyword Phrase', 'bgseo' ),
|
20 |
+
'icon' => 'dashicons-search',
|
21 |
+
),
|
22 |
+
'bgseo_meta' => array(
|
23 |
+
'label' => __( 'Title & Description', 'bgseo' ),
|
24 |
+
'icon' => 'dashicons-edit',
|
25 |
+
),
|
26 |
+
'bgseo_visibility' => array(
|
27 |
+
'label' => __( 'Search Visibility', 'bgseo' ),
|
28 |
+
'icon' => 'dashicons-visibility',
|
29 |
+
),
|
30 |
+
),
|
31 |
+
'control' => array(
|
32 |
+
'bgseo_meta_analaysis' => array(
|
33 |
+
'type' => 'dashboard',
|
34 |
+
'section' => 'bgseo_meta',
|
35 |
+
),
|
36 |
+
'bgseo_title' => array(
|
37 |
+
'type' => 'text',
|
38 |
+
'section' => 'bgseo_meta',
|
39 |
+
'attr' => array(
|
40 |
+
'id' => 'boldgrid-seo-field-meta_title',
|
41 |
+
'placeholder' => $this->util->meta_title(),
|
42 |
+
'maxlength' => '70',
|
43 |
+
'class' => 'widefat',
|
44 |
+
),
|
45 |
+
'label' => __( 'SEO Title', 'bgseo' ),
|
46 |
+
'description' => __( 'This is very important for search engines. The SEO Title is what usually shows as the link to your page in a Search Engine Results Page (SERP).', 'bgseo' ),
|
47 |
+
),
|
48 |
+
'bgseo_description' => array(
|
49 |
+
'type' => 'textarea',
|
50 |
+
'section' => 'bgseo_meta',
|
51 |
+
'attr' => array(
|
52 |
+
'id' => 'boldgrid-seo-field-meta_description',
|
53 |
+
'placeholder' => $this->util->meta_description(),
|
54 |
+
'maxlength' => '156',
|
55 |
+
'class' => 'widefat',
|
56 |
+
),
|
57 |
+
'label' => __( 'SEO Description', 'bgseo' ),
|
58 |
+
'description' => __( 'Typically what will show in a Search Engine Results Page (SERP). This is important, but secondary to your SEO Title.', 'bgseo' ),
|
59 |
+
),
|
60 |
+
'bgseo_visibility_analaysis' => array(
|
61 |
+
'type' => 'dashboard',
|
62 |
+
'section' => 'bgseo_visibility',
|
63 |
+
),
|
64 |
+
'bgseo_robots_index' => array(
|
65 |
+
'type' => 'radio',
|
66 |
+
'section' => 'bgseo_visibility',
|
67 |
+
'label' => __( 'Tell search engines to read and index this page', 'bgseo' ),
|
68 |
+
'choices' => array(
|
69 |
+
'index' => __( 'Yes ( index )', 'bgseo' ),
|
70 |
+
'noindex' => __( 'No ( noindex )', 'bgseo' ),
|
71 |
+
),
|
72 |
+
'description' => __( 'Setting this to index means that search engines are encouraged to show your website in their search results.', 'bgseo' ),
|
73 |
+
),
|
74 |
+
'bgseo_robots_follow' => array(
|
75 |
+
'type' => 'radio',
|
76 |
+
'section' => 'bgseo_visibility',
|
77 |
+
'label' => __( 'Tell search engines to follow links in this page', 'bgseo' ),
|
78 |
+
'choices' => array(
|
79 |
+
'follow' => __( 'Yes ( follow )', 'bgseo' ),
|
80 |
+
'nofollow' => __( 'No ( nofollow )', 'bgseo' ),
|
81 |
+
),
|
82 |
+
'description' => __( 'Having this set to follow means that search engines are able to count and follow where your links go to.', 'bgseo' ),
|
83 |
+
),
|
84 |
+
'bgseo_canonical' => array(
|
85 |
+
'type' => 'text',
|
86 |
+
'section' => 'bgseo_visibility',
|
87 |
+
'attr' => array(
|
88 |
+
'class' => 'widefat',
|
89 |
+
),
|
90 |
+
'label' => __( 'Tell search engines that another page should be read/indexed in place of this page', 'bgseo' ),
|
91 |
+
'description' => __( 'This is called the canonical URL. We recommend that you leave this field empty, so it will use the default permalink.', 'bgseo' ),
|
92 |
+
),
|
93 |
+
'bgseo_keywords_html' => array(
|
94 |
+
'type' => 'keywords',
|
95 |
+
'section' => 'bgseo_keywords',
|
96 |
+
),
|
97 |
+
'bgseo_custom_keyword' => array(
|
98 |
+
'type' => 'text',
|
99 |
+
'section' => 'bgseo_keywords',
|
100 |
+
'attr' => array(
|
101 |
+
'id' => 'bgseo-custom-keyword',
|
102 |
+
'maxlength' => '60',
|
103 |
+
'class' => 'widefat',
|
104 |
+
),
|
105 |
+
'label' => __( 'Target Keyword or Phrase', 'bgseo' ),
|
106 |
+
'description' => __( 'This should be what the main focus of this page or post is about.', 'bgseo' ),
|
107 |
+
),
|
108 |
+
'bgseo_keyword_analaysis' => array(
|
109 |
+
'type' => 'dashboard',
|
110 |
+
'section' => 'bgseo_keywords',
|
111 |
+
),
|
112 |
+
),
|
113 |
+
);
|
includes/index.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php // Silence is golden
|
includes/interface-boldgrid-seo-config.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
interface Boldgrid_Seo_Config_Interface {
|
3 |
+
/**
|
4 |
+
* Get configuration options to use within the plugin.
|
5 |
+
*
|
6 |
+
* @since 1.3
|
7 |
+
*/
|
8 |
+
public function get_configs();
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Include configuration files.
|
12 |
+
*
|
13 |
+
* @since 1.3
|
14 |
+
*/
|
15 |
+
public function assign_configs();
|
16 |
+
}
|
17 |
+
?>
|
includes/lib/butterbean/butterbean.php
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Name: ButterBean
|
4 |
+
* Plugin URI: https://github.com/justintadlock/butterbean
|
5 |
+
* Description: A little post meta framework.
|
6 |
+
* Version: 1.0.1-dev
|
7 |
+
* Author: Justin Tadlock
|
8 |
+
* Author URI: http://themehybrid.com
|
9 |
+
*
|
10 |
+
* @package ButterBean
|
11 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
12 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
13 |
+
* @link https://github.com/justintadlock/butterbean
|
14 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
15 |
+
*/
|
16 |
+
|
17 |
+
// For each version release, the priority needs to decrement by 1. This is so that
|
18 |
+
// we can load newer versions earlier than older versions when there's a conflict.
|
19 |
+
add_action( 'init', 'butterbean_loader_101', 9998 );
|
20 |
+
|
21 |
+
if ( ! function_exists( 'butterbean_loader_101' ) ) {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Loader function. Note to change the name of this function to use the
|
25 |
+
* current version number of the plugin. `1.0.0` is `100`, `1.3.4` = `134`.
|
26 |
+
*
|
27 |
+
* @since 1.0.1
|
28 |
+
* @access public
|
29 |
+
* @return void
|
30 |
+
*/
|
31 |
+
function butterbean_loader_101() {
|
32 |
+
|
33 |
+
// If not in the admin, bail.
|
34 |
+
if ( ! is_admin() )
|
35 |
+
return;
|
36 |
+
|
37 |
+
// If ButterBean hasn't been loaded, let's load it.
|
38 |
+
if ( ! defined( 'BUTTERBEAN_LOADED' ) ) {
|
39 |
+
define( 'BUTTERBEAN_LOADED', true );
|
40 |
+
|
41 |
+
require_once( trailingslashit( plugin_dir_path( __FILE__ ) ) . 'class-butterbean.php' );
|
42 |
+
}
|
43 |
+
}
|
44 |
+
}
|
includes/lib/butterbean/changelog.md
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Change Log
|
2 |
+
|
3 |
+
## [1.0.0] - 2016-08-29
|
4 |
+
|
5 |
+
* Plugin launch. Everything's new!
|
includes/lib/butterbean/class-butterbean.php
ADDED
@@ -0,0 +1,867 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Primary plugin class. This sets up and runs the show.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
7 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
8 |
+
* @link https://github.com/justintadlock/butterbean
|
9 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
10 |
+
*/
|
11 |
+
|
12 |
+
if ( ! class_exists( 'ButterBean' ) ) {
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Main ButterBean class. Runs the show.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
final class ButterBean {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Directory path to the plugin folder.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $dir_path = '';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Directory URI to the plugin folder.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
public $dir_uri = '';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Directory path to the template folder.
|
42 |
+
*
|
43 |
+
* @since 1.0.0
|
44 |
+
* @access public
|
45 |
+
* @var string
|
46 |
+
*/
|
47 |
+
public $tmpl_path = '';
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Array of managers.
|
51 |
+
*
|
52 |
+
* @since 1.0.0
|
53 |
+
* @access public
|
54 |
+
* @var array
|
55 |
+
*/
|
56 |
+
public $managers = array();
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Array of manager types.
|
60 |
+
*
|
61 |
+
* @since 1.0.0
|
62 |
+
* @access public
|
63 |
+
* @var array
|
64 |
+
*/
|
65 |
+
public $manager_types = array();
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Array of section types.
|
69 |
+
*
|
70 |
+
* @since 1.0.0
|
71 |
+
* @access public
|
72 |
+
* @var array
|
73 |
+
*/
|
74 |
+
public $section_types = array();
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Array of control types.
|
78 |
+
*
|
79 |
+
* @since 1.0.0
|
80 |
+
* @access public
|
81 |
+
* @var array
|
82 |
+
*/
|
83 |
+
public $control_types = array();
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Array of setting types.
|
87 |
+
*
|
88 |
+
* @since 1.0.0
|
89 |
+
* @access public
|
90 |
+
* @var array
|
91 |
+
*/
|
92 |
+
public $setting_types = array();
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Whether this is a new post. Once the post is saved and we're
|
96 |
+
* no longer on the `post-new.php` screen, this is going to be
|
97 |
+
* `false`.
|
98 |
+
*
|
99 |
+
* @since 1.0.0
|
100 |
+
* @access public
|
101 |
+
* @var bool
|
102 |
+
*/
|
103 |
+
public $is_new_post = false;
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Returns the instance.
|
107 |
+
*
|
108 |
+
* @since 1.0.0
|
109 |
+
* @access public
|
110 |
+
* @return object
|
111 |
+
*/
|
112 |
+
public static function get_instance() {
|
113 |
+
|
114 |
+
static $instance = null;
|
115 |
+
|
116 |
+
if ( is_null( $instance ) ) {
|
117 |
+
$instance = new self;
|
118 |
+
$instance->setup();
|
119 |
+
$instance->includes();
|
120 |
+
$instance->setup_actions();
|
121 |
+
}
|
122 |
+
|
123 |
+
return $instance;
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Constructor method.
|
128 |
+
*
|
129 |
+
* @since 1.0.0
|
130 |
+
* @access private
|
131 |
+
* @return void
|
132 |
+
*/
|
133 |
+
private function __construct() {}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Initial plugin setup.
|
137 |
+
*
|
138 |
+
* @since 1.0.0
|
139 |
+
* @access private
|
140 |
+
* @return void
|
141 |
+
*/
|
142 |
+
private function setup() {
|
143 |
+
|
144 |
+
$this->dir_path = apply_filters( 'butterbean_dir_path', trailingslashit( plugin_dir_path( __FILE__ ) ) );
|
145 |
+
$this->dir_uri = apply_filters( 'butterbean_dir_uri', trailingslashit( plugin_dir_url( __FILE__ ) ) );
|
146 |
+
|
147 |
+
$this->tmpl_path = trailingslashit( $this->dir_path . 'tmpl' );
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Loads include and admin files for the plugin.
|
152 |
+
*
|
153 |
+
* @since 1.0.0
|
154 |
+
* @access private
|
155 |
+
* @return void
|
156 |
+
*/
|
157 |
+
private function includes() {
|
158 |
+
|
159 |
+
// If not in the admin, bail.
|
160 |
+
if ( ! is_admin() )
|
161 |
+
return;
|
162 |
+
|
163 |
+
// Load base classes.
|
164 |
+
require_once( $this->dir_path . 'inc/class-manager.php' );
|
165 |
+
require_once( $this->dir_path . 'inc/class-section.php' );
|
166 |
+
require_once( $this->dir_path . 'inc/class-control.php' );
|
167 |
+
require_once( $this->dir_path . 'inc/class-setting.php' );
|
168 |
+
|
169 |
+
// Load control sub-classes.
|
170 |
+
require_once( $this->dir_path . 'inc/controls/class-control-checkboxes.php' );
|
171 |
+
require_once( $this->dir_path . 'inc/controls/class-control-color.php' );
|
172 |
+
require_once( $this->dir_path . 'inc/controls/class-control-datetime.php' );
|
173 |
+
require_once( $this->dir_path . 'inc/controls/class-control-image.php' );
|
174 |
+
require_once( $this->dir_path . 'inc/controls/class-control-palette.php' );
|
175 |
+
require_once( $this->dir_path . 'inc/controls/class-control-radio.php' );
|
176 |
+
require_once( $this->dir_path . 'inc/controls/class-control-radio-image.php' );
|
177 |
+
require_once( $this->dir_path . 'inc/controls/class-control-select-group.php' );
|
178 |
+
require_once( $this->dir_path . 'inc/controls/class-control-textarea.php' );
|
179 |
+
|
180 |
+
require_once( $this->dir_path . 'inc/controls/class-control-excerpt.php' );
|
181 |
+
require_once( $this->dir_path . 'inc/controls/class-control-multi-avatars.php' );
|
182 |
+
require_once( $this->dir_path . 'inc/controls/class-control-parent.php' );
|
183 |
+
|
184 |
+
// Load setting sub-classes.
|
185 |
+
require_once( $this->dir_path . 'inc/settings/class-setting-multiple.php' );
|
186 |
+
require_once( $this->dir_path . 'inc/settings/class-setting-datetime.php' );
|
187 |
+
require_once( $this->dir_path . 'inc/settings/class-setting-array.php' );
|
188 |
+
|
189 |
+
// Load functions.
|
190 |
+
require_once( $this->dir_path . 'inc/functions-core.php' );
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Sets up initial actions.
|
195 |
+
*
|
196 |
+
* @since 1.0.0
|
197 |
+
* @access private
|
198 |
+
* @return void
|
199 |
+
*/
|
200 |
+
private function setup_actions() {
|
201 |
+
|
202 |
+
// Call the register function.
|
203 |
+
add_action( 'load-post.php', array( $this, 'register' ), 95 );
|
204 |
+
add_action( 'load-post-new.php', array( $this, 'register' ), 95 );
|
205 |
+
|
206 |
+
// Register default types.
|
207 |
+
add_action( 'butterbean_register', array( $this, 'register_manager_types' ), -95 );
|
208 |
+
add_action( 'butterbean_register', array( $this, 'register_section_types' ), -95 );
|
209 |
+
add_action( 'butterbean_register', array( $this, 'register_control_types' ), -95 );
|
210 |
+
add_action( 'butterbean_register', array( $this, 'register_setting_types' ), -95 );
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* Registration callback. Fires the `butterbean_register` action hook to
|
215 |
+
* allow plugins to register their managers.
|
216 |
+
*
|
217 |
+
* @since 1.0.0
|
218 |
+
* @access public
|
219 |
+
* @return void
|
220 |
+
*/
|
221 |
+
public function register() {
|
222 |
+
|
223 |
+
// If this is a new post, set the new post boolean.
|
224 |
+
if ( 'load-post-new.php' === current_action() )
|
225 |
+
$this->is_new_post = true;
|
226 |
+
|
227 |
+
// Get the current post type.
|
228 |
+
$post_type = get_current_screen()->post_type;
|
229 |
+
|
230 |
+
// Action hook for registering managers.
|
231 |
+
do_action( 'butterbean_register', $this, $post_type );
|
232 |
+
|
233 |
+
// Loop through the managers to see if we're using on on this screen.
|
234 |
+
foreach ( $this->managers as $manager ) {
|
235 |
+
|
236 |
+
// If we found a matching post type, add our actions/filters.
|
237 |
+
if ( ! in_array( $post_type, (array) $manager->post_type ) ) {
|
238 |
+
$this->unregister_manager( $manager->name );
|
239 |
+
continue;
|
240 |
+
}
|
241 |
+
|
242 |
+
// Sort controls and sections by priority.
|
243 |
+
uasort( $manager->controls, array( $this, 'priority_sort' ) );
|
244 |
+
uasort( $manager->sections, array( $this, 'priority_sort' ) );
|
245 |
+
}
|
246 |
+
|
247 |
+
// If no managers registered, bail.
|
248 |
+
if ( ! $this->managers )
|
249 |
+
return;
|
250 |
+
|
251 |
+
// Add meta boxes.
|
252 |
+
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 5 );
|
253 |
+
|
254 |
+
// Save settings.
|
255 |
+
add_action( 'save_post', array( $this, 'update' ) );
|
256 |
+
|
257 |
+
// Load scripts and styles.
|
258 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
|
259 |
+
add_action( 'butterbean_enqueue_scripts', array( $this, 'enqueue' ) );
|
260 |
+
|
261 |
+
// Localize scripts and Undescore templates.
|
262 |
+
add_action( 'admin_footer', array( $this, 'localize_scripts' ) );
|
263 |
+
add_action( 'admin_footer', array( $this, 'print_templates' ) );
|
264 |
+
|
265 |
+
// Renders our Backbone views.
|
266 |
+
add_action( 'admin_print_footer_scripts', array( $this, 'render_views' ), 95 );
|
267 |
+
}
|
268 |
+
|
269 |
+
/**
|
270 |
+
* Register a manager.
|
271 |
+
*
|
272 |
+
* @since 1.0.0
|
273 |
+
* @access public
|
274 |
+
* @param object|string $manager
|
275 |
+
* @param array $args
|
276 |
+
* @return void
|
277 |
+
*/
|
278 |
+
public function register_manager( $manager, $args = array() ) {
|
279 |
+
|
280 |
+
if ( ! is_object( $manager ) ) {
|
281 |
+
|
282 |
+
$type = isset( $args['type'] ) ? $this->get_manager_type( $args['type'] ) : $this->get_manager_type( 'default' );
|
283 |
+
|
284 |
+
$manager = new $type( $manager, $args );
|
285 |
+
}
|
286 |
+
|
287 |
+
if ( ! $this->manager_exists( $manager->name ) )
|
288 |
+
$this->managers[ $manager->name ] = $manager;
|
289 |
+
|
290 |
+
return $manager;
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* Unregisters a manager object.
|
295 |
+
*
|
296 |
+
* @since 1.0.0
|
297 |
+
* @access public
|
298 |
+
* @param string $name
|
299 |
+
* @return void
|
300 |
+
*/
|
301 |
+
public function unregister_manager( $name ) {
|
302 |
+
|
303 |
+
if ( $this->manager_exists( $name ) )
|
304 |
+
unset( $this->managers[ $name ] );
|
305 |
+
}
|
306 |
+
|
307 |
+
/**
|
308 |
+
* Returns a manager object.
|
309 |
+
*
|
310 |
+
* @since 1.0.0
|
311 |
+
* @access public
|
312 |
+
* @param string $name
|
313 |
+
* @return object|bool
|
314 |
+
*/
|
315 |
+
public function get_manager( $name ) {
|
316 |
+
|
317 |
+
return $this->manager_exists( $name ) ? $this->managers[ $name ] : false;
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Checks if a manager exists.
|
322 |
+
*
|
323 |
+
* @since 1.0.0
|
324 |
+
* @access public
|
325 |
+
* @param string $name
|
326 |
+
* @return bool
|
327 |
+
*/
|
328 |
+
public function manager_exists( $name ) {
|
329 |
+
|
330 |
+
return isset( $this->managers[ $name ] );
|
331 |
+
}
|
332 |
+
|
333 |
+
/**
|
334 |
+
* Registers a manager type. This is just a method of telling ButterBean
|
335 |
+
* the class of your custom manager type. It allows the manager to be
|
336 |
+
* called without having to pass an object to `register_manager()`.
|
337 |
+
*
|
338 |
+
* @since 1.0.0
|
339 |
+
* @access public
|
340 |
+
* @param string $type
|
341 |
+
* @param string $class
|
342 |
+
* @return void
|
343 |
+
*/
|
344 |
+
public function register_manager_type( $type, $class ) {
|
345 |
+
|
346 |
+
if ( ! $this->manager_type_exists( $type ) )
|
347 |
+
$this->manager_types[ $type ] = $class;
|
348 |
+
}
|
349 |
+
|
350 |
+
/**
|
351 |
+
* Unregisters a manager type.
|
352 |
+
*
|
353 |
+
* @since 1.0.0
|
354 |
+
* @access public
|
355 |
+
* @param string $type
|
356 |
+
* @return void
|
357 |
+
*/
|
358 |
+
public function unregister_manager_type( $type ) {
|
359 |
+
|
360 |
+
if ( $this->manager_type_exists( $type ) )
|
361 |
+
unset( $this->manager_types[ $type ] );
|
362 |
+
}
|
363 |
+
|
364 |
+
/**
|
365 |
+
* Returns the class name for the manager type.
|
366 |
+
*
|
367 |
+
* @since 1.0.0
|
368 |
+
* @access public
|
369 |
+
* @param string $type
|
370 |
+
* @return string
|
371 |
+
*/
|
372 |
+
public function get_manager_type( $type ) {
|
373 |
+
|
374 |
+
return $this->manager_type_exists( $type ) ? $this->manager_types[ $type ] : $this->manager_types[ 'default' ];
|
375 |
+
}
|
376 |
+
|
377 |
+
/**
|
378 |
+
* Checks if a manager type exists.
|
379 |
+
*
|
380 |
+
* @since 1.0.0
|
381 |
+
* @access public
|
382 |
+
* @param string $type
|
383 |
+
* @return bool
|
384 |
+
*/
|
385 |
+
public function manager_type_exists( $type ) {
|
386 |
+
|
387 |
+
return isset( $this->manager_types[ $type ] );
|
388 |
+
}
|
389 |
+
|
390 |
+
/**
|
391 |
+
* Registers a section type. This is just a method of telling ButterBean
|
392 |
+
* the class of your custom section type. It allows the section to be
|
393 |
+
* called without having to pass an object to `register_section()`.
|
394 |
+
*
|
395 |
+
* @since 1.0.0
|
396 |
+
* @access public
|
397 |
+
* @param string $type
|
398 |
+
* @param string $class
|
399 |
+
* @return void
|
400 |
+
*/
|
401 |
+
public function register_section_type( $type, $class ) {
|
402 |
+
|
403 |
+
if ( ! $this->section_type_exists( $type ) )
|
404 |
+
$this->section_types[ $type ] = $class;
|
405 |
+
}
|
406 |
+
|
407 |
+
/**
|
408 |
+
* Unregisters a section type.
|
409 |
+
*
|
410 |
+
* @since 1.0.0
|
411 |
+
* @access public
|
412 |
+
* @param string $type
|
413 |
+
* @return void
|
414 |
+
*/
|
415 |
+
public function unregister_section_type( $type ) {
|
416 |
+
|
417 |
+
if ( $this->section_type_exists( $type ) )
|
418 |
+
unset( $this->section_types[ $type ] );
|
419 |
+
}
|
420 |
+
|
421 |
+
/**
|
422 |
+
* Returns the class name for the section type.
|
423 |
+
*
|
424 |
+
* @since 1.0.0
|
425 |
+
* @access public
|
426 |
+
* @param string $type
|
427 |
+
* @return string
|
428 |
+
*/
|
429 |
+
public function get_section_type( $type ) {
|
430 |
+
|
431 |
+
return $this->section_type_exists( $type ) ? $this->section_types[ $type ] : $this->section_types[ 'default' ];
|
432 |
+
}
|
433 |
+
|
434 |
+
/**
|
435 |
+
* Checks if a section type exists.
|
436 |
+
*
|
437 |
+
* @since 1.0.0
|
438 |
+
* @access public
|
439 |
+
* @param string $type
|
440 |
+
* @return bool
|
441 |
+
*/
|
442 |
+
public function section_type_exists( $type ) {
|
443 |
+
|
444 |
+
return isset( $this->section_types[ $type ] );
|
445 |
+
}
|
446 |
+
|
447 |
+
/**
|
448 |
+
* Registers a control type. This is just a method of telling ButterBean
|
449 |
+
* the class of your custom control type. It allows the control to be
|
450 |
+
* called without having to pass an object to `register_control()`.
|
451 |
+
*
|
452 |
+
* @since 1.0.0
|
453 |
+
* @access public
|
454 |
+
* @param string $type
|
455 |
+
* @param string $class
|
456 |
+
* @return void
|
457 |
+
*/
|
458 |
+
public function register_control_type( $type, $class ) {
|
459 |
+
|
460 |
+
if ( ! $this->control_type_exists( $type ) )
|
461 |
+
$this->control_types[ $type ] = $class;
|
462 |
+
}
|
463 |
+
|
464 |
+
/**
|
465 |
+
* Unregisters a control type.
|
466 |
+
*
|
467 |
+
* @since 1.0.0
|
468 |
+
* @access public
|
469 |
+
* @param string $type
|
470 |
+
* @return void
|
471 |
+
*/
|
472 |
+
public function unregister_control_type( $type ) {
|
473 |
+
|
474 |
+
if ( $this->control_type_exists( $type ) )
|
475 |
+
unset( $this->control_types[ $type ] );
|
476 |
+
}
|
477 |
+
|
478 |
+
/**
|
479 |
+
* Returns the class name for the control type.
|
480 |
+
*
|
481 |
+
* @since 1.0.0
|
482 |
+
* @access public
|
483 |
+
* @param string $type
|
484 |
+
* @return string
|
485 |
+
*/
|
486 |
+
public function get_control_type( $type ) {
|
487 |
+
|
488 |
+
return $this->control_type_exists( $type ) ? $this->control_types[ $type ] : $this->control_types[ 'default' ];
|
489 |
+
}
|
490 |
+
|
491 |
+
/**
|
492 |
+
* Checks if a control type exists.
|
493 |
+
*
|
494 |
+
* @since 1.0.0
|
495 |
+
* @access public
|
496 |
+
* @param string $type
|
497 |
+
* @return bool
|
498 |
+
*/
|
499 |
+
public function control_type_exists( $type ) {
|
500 |
+
|
501 |
+
return isset( $this->control_types[ $type ] );
|
502 |
+
}
|
503 |
+
|
504 |
+
/**
|
505 |
+
* Registers a setting type. This is just a method of telling ButterBean
|
506 |
+
* the class of your custom setting type. It allows the setting to be
|
507 |
+
* called without having to pass an object to `register_setting()`.
|
508 |
+
*
|
509 |
+
* @since 1.0.0
|
510 |
+
* @access public
|
511 |
+
* @param string $type
|
512 |
+
* @param string $class
|
513 |
+
* @return void
|
514 |
+
*/
|
515 |
+
public function register_setting_type( $type, $class ) {
|
516 |
+
|
517 |
+
if ( ! $this->setting_type_exists( $type ) )
|
518 |
+
$this->setting_types[ $type ] = $class;
|
519 |
+
}
|
520 |
+
|
521 |
+
/**
|
522 |
+
* Unregisters a setting type.
|
523 |
+
*
|
524 |
+
* @since 1.0.0
|
525 |
+
* @access public
|
526 |
+
* @param string $type
|
527 |
+
* @return void
|
528 |
+
*/
|
529 |
+
public function unregister_setting_type( $type ) {
|
530 |
+
|
531 |
+
if ( $this->setting_type_exists( $type ) )
|
532 |
+
unset( $this->setting_types[ $type ] );
|
533 |
+
}
|
534 |
+
|
535 |
+
/**
|
536 |
+
* Returns the class name for the setting type.
|
537 |
+
*
|
538 |
+
* @since 1.0.0
|
539 |
+
* @access public
|
540 |
+
* @param string $type
|
541 |
+
* @return string
|
542 |
+
*/
|
543 |
+
public function get_setting_type( $type ) {
|
544 |
+
|
545 |
+
return $this->setting_type_exists( $type ) ? $this->setting_types[ $type ] : $this->setting_types[ 'default' ];
|
546 |
+
}
|
547 |
+
|
548 |
+
/**
|
549 |
+
* Checks if a setting type exists.
|
550 |
+
*
|
551 |
+
* @since 1.0.0
|
552 |
+
* @access public
|
553 |
+
* @param string $type
|
554 |
+
* @return bool
|
555 |
+
*/
|
556 |
+
public function setting_type_exists( $type ) {
|
557 |
+
|
558 |
+
return isset( $this->setting_types[ $type ] );
|
559 |
+
}
|
560 |
+
|
561 |
+
/**
|
562 |
+
* Registers our manager types so that devs don't have to directly instantiate
|
563 |
+
* the class each time they register a manager. Instead, they can use the
|
564 |
+
* `type` argument.
|
565 |
+
*
|
566 |
+
* @since 1.0.0
|
567 |
+
* @access public
|
568 |
+
* @return void
|
569 |
+
*/
|
570 |
+
public function register_manager_types() {
|
571 |
+
|
572 |
+
$this->register_manager_type( 'default', 'ButterBean_Manager' );
|
573 |
+
}
|
574 |
+
|
575 |
+
/**
|
576 |
+
* Registers our section types so that devs don't have to directly instantiate
|
577 |
+
* the class each time they register a section. Instead, they can use the
|
578 |
+
* `type` argument.
|
579 |
+
*
|
580 |
+
* @since 1.0.0
|
581 |
+
* @access public
|
582 |
+
* @return void
|
583 |
+
*/
|
584 |
+
public function register_section_types() {
|
585 |
+
|
586 |
+
$this->register_section_type( 'default', 'ButterBean_Section' );
|
587 |
+
}
|
588 |
+
|
589 |
+
/**
|
590 |
+
* Registers our control types so that devs don't have to directly instantiate
|
591 |
+
* the class each time they register a control. Instead, they can use the
|
592 |
+
* `type` argument.
|
593 |
+
*
|
594 |
+
* @since 1.0.0
|
595 |
+
* @access public
|
596 |
+
* @return void
|
597 |
+
*/
|
598 |
+
public function register_control_types() {
|
599 |
+
|
600 |
+
$this->register_control_type( 'default', 'ButterBean_Control' );
|
601 |
+
$this->register_control_type( 'checkboxes', 'ButterBean_Control_Checkboxes' );
|
602 |
+
$this->register_control_type( 'color', 'ButterBean_Control_Color' );
|
603 |
+
$this->register_control_type( 'datetime', 'ButterBean_Control_Datetime' );
|
604 |
+
$this->register_control_type( 'excerpt', 'ButterBean_Control_Excerpt' );
|
605 |
+
$this->register_control_type( 'image', 'ButterBean_Control_Image' );
|
606 |
+
$this->register_control_type( 'palette', 'ButterBean_Control_Palette' );
|
607 |
+
$this->register_control_type( 'radio', 'ButterBean_Control_Radio' );
|
608 |
+
$this->register_control_type( 'radio-image', 'ButterBean_Control_Radio_Image' );
|
609 |
+
$this->register_control_type( 'select-group', 'ButterBean_Control_Select_Group' );
|
610 |
+
$this->register_control_type( 'textarea', 'ButterBean_Control_Textarea' );
|
611 |
+
$this->register_control_type( 'multi-avatars', 'ButterBean_Control_Multi_Avatars' );
|
612 |
+
$this->register_control_type( 'parent', 'ButterBean_Control_Parent' );
|
613 |
+
}
|
614 |
+
|
615 |
+
/**
|
616 |
+
* Registers our setting types so that devs don't have to directly instantiate
|
617 |
+
* the class each time they register a setting. Instead, they can use the
|
618 |
+
* `type` argument.
|
619 |
+
*
|
620 |
+
* @since 1.0.0
|
621 |
+
* @access public
|
622 |
+
* @return void
|
623 |
+
*/
|
624 |
+
public function register_setting_types() {
|
625 |
+
|
626 |
+
$this->register_setting_type( 'default', 'ButterBean_Setting' );
|
627 |
+
$this->register_setting_type( 'single', 'ButterBean_Setting' );
|
628 |
+
$this->register_setting_type( 'multiple', 'ButterBean_Setting_Multiple' );
|
629 |
+
$this->register_setting_type( 'array', 'ButterBean_Setting_Array' );
|
630 |
+
$this->register_setting_type( 'datetime', 'ButterBean_Setting_Datetime' );
|
631 |
+
}
|
632 |
+
|
633 |
+
/**
|
634 |
+
* Fires an action hook to register/enqueue scripts/styles.
|
635 |
+
*
|
636 |
+
* @since 1.0.0
|
637 |
+
* @access public
|
638 |
+
* @return void
|
639 |
+
*/
|
640 |
+
public function enqueue_scripts() {
|
641 |
+
|
642 |
+
do_action( 'butterbean_enqueue_scripts' );
|
643 |
+
}
|
644 |
+
|
645 |
+
/**
|
646 |
+
* Loads scripts and styles.
|
647 |
+
*
|
648 |
+
* @since 1.0.0
|
649 |
+
* @access public
|
650 |
+
* @return void
|
651 |
+
*/
|
652 |
+
public function enqueue() {
|
653 |
+
$min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
654 |
+
|
655 |
+
// Enqueue the main plugin script.
|
656 |
+
wp_enqueue_script( 'butterbean', $this->dir_uri . "js/butterbean{$min}.js", array( 'backbone', 'wp-util' ), '', true );
|
657 |
+
|
658 |
+
// Enqueue the main plugin style.
|
659 |
+
wp_enqueue_style( 'butterbean', $this->dir_uri . "css/butterbean{$min}.css" );
|
660 |
+
|
661 |
+
// Loop through the manager and its controls and call each control's `enqueue()` method.
|
662 |
+
foreach ( $this->managers as $manager ) {
|
663 |
+
|
664 |
+
$manager->enqueue();
|
665 |
+
|
666 |
+
foreach ( $manager->sections as $section )
|
667 |
+
$section->enqueue();
|
668 |
+
|
669 |
+
foreach ( $manager->controls as $control )
|
670 |
+
$control->enqueue();
|
671 |
+
}
|
672 |
+
}
|
673 |
+
|
674 |
+
/**
|
675 |
+
* Callback function for adding meta boxes. This function adds a meta box
|
676 |
+
* for each of the managers.
|
677 |
+
*
|
678 |
+
* @since 1.0.0
|
679 |
+
* @access public
|
680 |
+
* @param string $post_type
|
681 |
+
* @return void
|
682 |
+
*/
|
683 |
+
public function add_meta_boxes( $post_type ) {
|
684 |
+
|
685 |
+
foreach ( $this->managers as $manager ) {
|
686 |
+
|
687 |
+
// If the manager is registered for the current post type, add a meta box.
|
688 |
+
if ( in_array( $post_type, (array) $manager->post_type ) && $manager->check_capabilities() ) {
|
689 |
+
|
690 |
+
add_meta_box(
|
691 |
+
"butterbean-ui-{$manager->name}",
|
692 |
+
$manager->label,
|
693 |
+
array( $this, 'meta_box' ),
|
694 |
+
$post_type,
|
695 |
+
$manager->context,
|
696 |
+
$manager->priority,
|
697 |
+
array( 'manager' => $manager )
|
698 |
+
);
|
699 |
+
}
|
700 |
+
}
|
701 |
+
}
|
702 |
+
|
703 |
+
/**
|
704 |
+
* Displays the meta box. Note that the actual content of the meta box is
|
705 |
+
* handled via Underscore.js templates. The only thing we're outputting here
|
706 |
+
* is the nonce field.
|
707 |
+
*
|
708 |
+
* @since 1.0.0
|
709 |
+
* @access public
|
710 |
+
* @param object $post
|
711 |
+
* @param array $metabox
|
712 |
+
* @return void
|
713 |
+
*/
|
714 |
+
public function meta_box( $post, $metabox ) {
|
715 |
+
|
716 |
+
$manager = $metabox['args']['manager'];
|
717 |
+
|
718 |
+
$manager->post_id = $this->post_id = $post->ID;
|
719 |
+
|
720 |
+
// Nonce field to validate on save.
|
721 |
+
wp_nonce_field( "butterbean_{$manager->name}_nonce", "butterbean_{$manager->name}" );
|
722 |
+
}
|
723 |
+
|
724 |
+
/**
|
725 |
+
* Passes the appropriate section and control json data to the JS file.
|
726 |
+
*
|
727 |
+
* @since 1.0.0
|
728 |
+
* @access public
|
729 |
+
* @return void
|
730 |
+
*/
|
731 |
+
public function localize_scripts() {
|
732 |
+
|
733 |
+
$json = array( 'managers' => array() );
|
734 |
+
|
735 |
+
foreach ( $this->managers as $manager ) {
|
736 |
+
|
737 |
+
if ( $manager->check_capabilities() )
|
738 |
+
$json['managers'][] = $manager->get_json();
|
739 |
+
}
|
740 |
+
|
741 |
+
wp_localize_script( 'butterbean', 'butterbean_data', $json );
|
742 |
+
}
|
743 |
+
|
744 |
+
/**
|
745 |
+
* Prints the Underscore.js templates.
|
746 |
+
*
|
747 |
+
* @since 1.0.0
|
748 |
+
* @access public
|
749 |
+
* @return void
|
750 |
+
*/
|
751 |
+
public function print_templates() {
|
752 |
+
|
753 |
+
$m_templates = array();
|
754 |
+
$s_templates = array();
|
755 |
+
$c_templates = array(); ?>
|
756 |
+
|
757 |
+
<script type="text/html" id="tmpl-butterbean-nav">
|
758 |
+
<?php butterbean_get_nav_template(); ?>
|
759 |
+
</script>
|
760 |
+
|
761 |
+
<?php foreach ( $this->managers as $manager ) {
|
762 |
+
|
763 |
+
if ( ! $manager->check_capabilities() )
|
764 |
+
continue;
|
765 |
+
|
766 |
+
if ( ! in_array( $manager->type, $m_templates ) ) {
|
767 |
+
$m_templates[] = $manager->type;
|
768 |
+
|
769 |
+
$manager->print_template();
|
770 |
+
}
|
771 |
+
|
772 |
+
foreach ( $manager->sections as $section ) {
|
773 |
+
|
774 |
+
if ( ! in_array( $section->type, $s_templates ) ) {
|
775 |
+
$s_templates[] = $section->type;
|
776 |
+
|
777 |
+
$section->print_template();
|
778 |
+
}
|
779 |
+
}
|
780 |
+
|
781 |
+
foreach ( $manager->controls as $control ) {
|
782 |
+
|
783 |
+
if ( ! in_array( $control->type, $c_templates ) ) {
|
784 |
+
$c_templates[] = $control->type;
|
785 |
+
|
786 |
+
$control->print_template();
|
787 |
+
}
|
788 |
+
}
|
789 |
+
}
|
790 |
+
}
|
791 |
+
|
792 |
+
/**
|
793 |
+
* Renders our Backbone views. We're calling this late in the page load so
|
794 |
+
* that other scripts have an opportunity to extend with their own, custom
|
795 |
+
* views for custom controls and such.
|
796 |
+
*
|
797 |
+
* @since 1.0.0
|
798 |
+
* @access public
|
799 |
+
* @return void
|
800 |
+
*/
|
801 |
+
public function render_views() { ?>
|
802 |
+
|
803 |
+
<script type="text/javascript">
|
804 |
+
( function( api ) {
|
805 |
+
if ( _.isObject( api ) && _.isFunction( api.render ) ) {
|
806 |
+
api.render();
|
807 |
+
}
|
808 |
+
}( butterbean ) );
|
809 |
+
</script>
|
810 |
+
<?php }
|
811 |
+
|
812 |
+
/**
|
813 |
+
* Saves the settings.
|
814 |
+
*
|
815 |
+
* @since 1.0.0
|
816 |
+
* @access public
|
817 |
+
* @return void
|
818 |
+
*/
|
819 |
+
public function update( $post_id ) {
|
820 |
+
|
821 |
+
$do_autosave = defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE;
|
822 |
+
$is_autosave = wp_is_post_autosave( $post_id );
|
823 |
+
$is_revision = wp_is_post_revision( $post_id );
|
824 |
+
|
825 |
+
if ( $do_autosave || $is_autosave || $is_revision )
|
826 |
+
return;
|
827 |
+
|
828 |
+
foreach ( $this->managers as $manager ) {
|
829 |
+
|
830 |
+
if ( $manager->check_capabilities() )
|
831 |
+
$manager->save( $post_id );
|
832 |
+
}
|
833 |
+
}
|
834 |
+
|
835 |
+
/**
|
836 |
+
* Helper method for sorting sections and controls by priority.
|
837 |
+
*
|
838 |
+
* @since 1.0.0
|
839 |
+
* @access protected
|
840 |
+
* @param object $a
|
841 |
+
* @param object $b
|
842 |
+
* @return int
|
843 |
+
*/
|
844 |
+
protected function priority_sort( $a, $b ) {
|
845 |
+
|
846 |
+
if ( $a->priority === $b->priority )
|
847 |
+
return $a->instance_number - $b->instance_number;
|
848 |
+
|
849 |
+
return $a->priority - $b->priority;
|
850 |
+
}
|
851 |
+
}
|
852 |
+
|
853 |
+
/**
|
854 |
+
* Gets the instance of the `ButterBean` class. This function is useful for quickly grabbing data
|
855 |
+
* used throughout the plugin.
|
856 |
+
*
|
857 |
+
* @since 1.0.0
|
858 |
+
* @access public
|
859 |
+
* @return object
|
860 |
+
*/
|
861 |
+
function butterbean() {
|
862 |
+
return ButterBean::get_instance();
|
863 |
+
}
|
864 |
+
|
865 |
+
// Let's do this thang!
|
866 |
+
butterbean();
|
867 |
+
}
|
includes/lib/butterbean/contributing.md
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Contributing
|
2 |
+
|
3 |
+
The code for the project is handled via its [GitHub Repository](https://github.com/justintadlock/butterbean). You can open tickets, create patches, and send pull requests there.
|
4 |
+
|
5 |
+
## Pull requests
|
6 |
+
|
7 |
+
Problem first. Solution second.
|
8 |
+
|
9 |
+
Pull requests should have a ticket open for discussion first. I rarely accept pull requests that aren't for a specific issue for various reasons. It's far better to post an issue and let me or the community provide feedback prior to creating a pull request.
|
10 |
+
|
11 |
+
Please don't make pull requests against the `master` branch. This is the latest, stable code. You can make a pull request against one of the point branches or the `dev` (future release) branch.
|
12 |
+
|
13 |
+
## Coding standards
|
14 |
+
|
15 |
+
In general, the project follows all WordPress [coding standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards). There are instances where it doesn't, opting for personal choices of my own, but in terms of contributing, following the WordPress standards is best practice.
|
16 |
+
|
17 |
+
## Script and style files
|
18 |
+
|
19 |
+
The project consists of several script and style files. When making patches or pull requests with changes to these files, only do so to the primary file. Don't create patches for the minified (`.min`) versions of the files. Those will be minified after a patch is merged into the code base.
|
20 |
+
|
21 |
+
## Language
|
22 |
+
|
23 |
+
All text strings follow U.S. English by default. While such guides are generally unneeded, in cases where style considerations are necessary, these will typically follow conventions laid out in *Elements of Style* or the *AP Stylebook*.
|
24 |
+
|
25 |
+
## Licensing
|
26 |
+
|
27 |
+
Any code contributed to the project via patches, pull requests, or other means will be licensed under the [GPL version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html) or later. By contributing code to the project, you provide consent to use such code under this license. The exception to this rule is when bringing in third-party code with an alternate open source license.
|
28 |
+
|
29 |
+
## Versioning
|
30 |
+
|
31 |
+
The project uses [semantic versioning](http://semver.org). Version numbers will look like `3.2.1` where `3` is the "major" release, `2` is the minor release, and `1` is the patch release.
|
includes/lib/butterbean/css/butterbean.css
ADDED
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
/* Wrapper box */
|
3 |
+
|
4 |
+
.butterbean-ui { }
|
5 |
+
|
6 |
+
.butterbean-ui > .hndle {
|
7 |
+
padding: 10px !important;
|
8 |
+
border-bottom: 1px solid #eee;
|
9 |
+
}
|
10 |
+
|
11 |
+
.butterbean-ui .inside {
|
12 |
+
margin: 0 !important;
|
13 |
+
padding: 0;
|
14 |
+
}
|
15 |
+
|
16 |
+
/* Tabs wrapper. */
|
17 |
+
|
18 |
+
.butterbean-manager-default {
|
19 |
+
overflow: hidden;
|
20 |
+
background: #fff;
|
21 |
+
background: linear-gradient( 90deg, #fafafa 0%, #fafafa 180px, #fff 180px, #fff 100% );
|
22 |
+
}
|
23 |
+
|
24 |
+
#side-sortables .butterbean-manager-default {
|
25 |
+
background: linear-gradient( 90deg, #fafafa 0%, #fafafa 48px, #fff 48px, #fff 100% );
|
26 |
+
}
|
27 |
+
|
28 |
+
@media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) {
|
29 |
+
|
30 |
+
.butterbean-manager-default {
|
31 |
+
background: linear-gradient( 90deg, #fafafa 0%, #fafafa 48px, #fff 48px, #fff 100% );
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
.butterbean-manager-default::before,
|
36 |
+
.butterbean-manager-default::after {
|
37 |
+
content: "";
|
38 |
+
display: table;
|
39 |
+
}
|
40 |
+
|
41 |
+
.butterbean-manager-default::after {
|
42 |
+
clear: both;
|
43 |
+
}
|
44 |
+
|
45 |
+
/* Tab nav. */
|
46 |
+
|
47 |
+
.butterbean-manager-default .butterbean-nav {
|
48 |
+
position: relative;
|
49 |
+
float: left;
|
50 |
+
list-style: none;
|
51 |
+
width: 180px;/*20%;*/
|
52 |
+
line-height: 1em;
|
53 |
+
margin: 0 0 -1px 0;
|
54 |
+
padding: 0;
|
55 |
+
background-color: #fafafa;
|
56 |
+
border-right: 1px solid #eee;
|
57 |
+
box-sizing: border-box;
|
58 |
+
}
|
59 |
+
|
60 |
+
.butterbean-manager-default .butterbean-nav li {
|
61 |
+
display: block;
|
62 |
+
position: relative;
|
63 |
+
margin: 0;
|
64 |
+
padding: 0;
|
65 |
+
line-height: 20px;
|
66 |
+
}
|
67 |
+
|
68 |
+
.butterbean-manager-default .butterbean-nav li a {
|
69 |
+
display: block;
|
70 |
+
margin: 0;
|
71 |
+
padding: 10px;
|
72 |
+
line-height: 20px !important;
|
73 |
+
text-decoration: none;
|
74 |
+
border-bottom: 1px solid #eee;
|
75 |
+
box-shadow: none;
|
76 |
+
}
|
77 |
+
|
78 |
+
.butterbean-manager-default .butterbean-nav .dashicons {
|
79 |
+
line-height: 20px;
|
80 |
+
margin-right: 3px;
|
81 |
+
}
|
82 |
+
|
83 |
+
.butterbean-manager-default .butterbean-nav li[aria-selected="true"] a {
|
84 |
+
position: relative;
|
85 |
+
font-weight: bold;
|
86 |
+
color: #555;
|
87 |
+
background-color: #e0e0e0;
|
88 |
+
}
|
89 |
+
|
90 |
+
@media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) {
|
91 |
+
.butterbean-manager-default .butterbean-nav { width: 48px; }
|
92 |
+
|
93 |
+
.butterbean-manager-default .butterbean-nav .dashicons {
|
94 |
+
width: 24px;
|
95 |
+
height: 24px;
|
96 |
+
font-size: 24px;
|
97 |
+
line-height: 24px;
|
98 |
+
}
|
99 |
+
|
100 |
+
.butterbean-manager-default .butterbean-nav .dashicons::before {
|
101 |
+
width: 24px;
|
102 |
+
height: 24px;
|
103 |
+
}
|
104 |
+
|
105 |
+
.butterbean-manager-default .butterbean-nav .label {
|
106 |
+
overflow: hidden;
|
107 |
+
position: absolute;
|
108 |
+
top: -1000em;
|
109 |
+
left: -1000em;
|
110 |
+
width: 1px;
|
111 |
+
height: 1px;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
/* Tab content wrapper */
|
116 |
+
|
117 |
+
.butterbean-manager-default .butterbean-content {
|
118 |
+
float: left;
|
119 |
+
width: calc( 100% - 180px );
|
120 |
+
margin-left: -1px;
|
121 |
+
border-left: 1px solid #eee;
|
122 |
+
}
|
123 |
+
|
124 |
+
@media only screen and ( max-width: 782px ), ( max-width: 980px ) and ( min-width: 851px ) {
|
125 |
+
|
126 |
+
.butterbean-manager-default .butterbean-content {
|
127 |
+
width: calc( 100% - 48px );
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
+
/* === Manager when in the side meta box. === */
|
132 |
+
|
133 |
+
@media only screen and ( min-width: 850px ) {
|
134 |
+
|
135 |
+
#side-sortables .butterbean-manager-default { background: #fff; }
|
136 |
+
|
137 |
+
#side-sortables .butterbean-manager-default .butterbean-content { width: 100%; }
|
138 |
+
|
139 |
+
#side-sortables .butterbean-manager-default .butterbean-nav {
|
140 |
+
display: table;
|
141 |
+
width: 100%;
|
142 |
+
}
|
143 |
+
|
144 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li {
|
145 |
+
display: table-cell;
|
146 |
+
text-align: center;
|
147 |
+
border-right: 1px solid #eee;
|
148 |
+
}
|
149 |
+
|
150 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li:last-of-type { border-right: none; }
|
151 |
+
|
152 |
+
#side-sortables .butterbean-manager-default .butterbean-nav li a {
|
153 |
+
padding: 10px 0;
|
154 |
+
}
|
155 |
+
|
156 |
+
#side-sortables .butterbean-manager-default .butterbean-nav .dashicons {
|
157 |
+
width: 24px;
|
158 |
+
height: 24px;
|
159 |
+
font-size: 24px;
|
160 |
+
line-height: 24px;
|
161 |
+
}
|
162 |
+
|
163 |
+
#side-sortables .butterbean-manager-default .butterbean-nav .dashicons::before {
|
164 |
+
width: 24px;
|
165 |
+
height: 24px;
|
166 |
+
}
|
167 |
+
|
168 |
+
#side-sortables .butterbean-manager-default .butterbean-nav .label {
|
169 |
+
overflow: hidden;
|
170 |
+
position: absolute;
|
171 |
+
top: -1000em;
|
172 |
+
left: -1000em;
|
173 |
+
width: 1px;
|
174 |
+
height: 1px;
|
175 |
+
}
|
176 |
+
}
|
177 |
+
|
178 |
+
|
179 |
+
/* === Content === */
|
180 |
+
|
181 |
+
.butterbean-manager-default .butterbean-section {
|
182 |
+
padding: 12px 12px 0;
|
183 |
+
box-sizing: border-box;
|
184 |
+
}
|
185 |
+
|
186 |
+
.butterbean-manager-default .butterbean-section[aria-hidden="true"] { display: none; }
|
187 |
+
.butterbean-manager-default .butterbean-section[aria-hidden="false"] { display: block; }
|
188 |
+
|
189 |
+
.butterbean-manager-default .butterbean-control {
|
190 |
+
margin-bottom: 20px;
|
191 |
+
}
|
192 |
+
|
193 |
+
.butterbean-manager-default .butterbean-label {
|
194 |
+
display : block !important; /* this is getting overwritten somewhere */
|
195 |
+
font-weight : bold;
|
196 |
+
display : inline-block;
|
197 |
+
margin-bottom : 4px;
|
198 |
+
}
|
199 |
+
|
200 |
+
.butterbean-manager-default .butterbean-control-checkbox .butterbean-label {
|
201 |
+
display: inline !important;
|
202 |
+
}
|
203 |
+
|
204 |
+
.butterbean-manager-default .butterbean-description {
|
205 |
+
display : block;
|
206 |
+
font-style : italic;
|
207 |
+
margin-top : 4px;
|
208 |
+
}
|
209 |
+
|
210 |
+
.butterbean-manager-default .butterbean-label + .butterbean-description {
|
211 |
+
margin-top : 0;
|
212 |
+
margin-bottom : 4px;
|
213 |
+
}
|
214 |
+
|
215 |
+
/* === Media === */
|
216 |
+
|
217 |
+
.butterbean-control-image .butterbean-img {
|
218 |
+
display: block;
|
219 |
+
max-width: 100%;
|
220 |
+
max-height: 300px;
|
221 |
+
height: auto;
|
222 |
+
}
|
223 |
+
|
224 |
+
.butterbean-placeholder {
|
225 |
+
width: 100%;
|
226 |
+
position: relative;
|
227 |
+
text-align: center;
|
228 |
+
padding: 9px 0;
|
229 |
+
line-height: 20px;
|
230 |
+
border: 1px dashed rgb(180, 185, 190);
|
231 |
+
box-sizing: border-box;
|
232 |
+
}
|
233 |
+
|
234 |
+
/* === Textarea Control === */
|
235 |
+
|
236 |
+
.butterbean-control-textarea textarea,
|
237 |
+
.butterbean-control-excerpt textarea {
|
238 |
+
display: block;
|
239 |
+
width: 100%;
|
240 |
+
height: 105px;
|
241 |
+
}
|
242 |
+
|
243 |
+
/* === Date Control === */
|
244 |
+
|
245 |
+
.butterbean-control-datetime select {
|
246 |
+
vertical-align: top;
|
247 |
+
}
|
248 |
+
|
249 |
+
/* === Palette Control === */
|
250 |
+
|
251 |
+
.butterbean-control-palette label {
|
252 |
+
display: block;
|
253 |
+
padding: 0 10px 10px;
|
254 |
+
}
|
255 |
+
|
256 |
+
.butterbean-control-palette label[aria-selected="true"] {
|
257 |
+
padding-top: 5px;
|
258 |
+
background-color: #ddd;
|
259 |
+
}
|
260 |
+
|
261 |
+
.butterbean-palette-label {
|
262 |
+
line-height: 28px;
|
263 |
+
}
|
264 |
+
|
265 |
+
.butterbean-palette-block {
|
266 |
+
display: table;
|
267 |
+
width: 100%;
|
268 |
+
height: 45px;
|
269 |
+
border: 1px solid rgb(238, 238, 238);
|
270 |
+
box-sizing: border-box;
|
271 |
+
}
|
272 |
+
|
273 |
+
.butterbean-palette-color {
|
274 |
+
display: table-cell;
|
275 |
+
height: 100%;
|
276 |
+
}
|
277 |
+
|
278 |
+
/* === Radio Image Control === */
|
279 |
+
|
280 |
+
.butterbean-control-radio-image input[type="radio"] {
|
281 |
+
clip: rect(1px, 1px, 1px, 1px);
|
282 |
+
height: 1px;
|
283 |
+
overflow: hidden;
|
284 |
+
position: absolute !important;
|
285 |
+
width: 1px;
|
286 |
+
}
|
287 |
+
|
288 |
+
.butterbean-control-radio-image img {
|
289 |
+
box-sizing: border-box;
|
290 |
+
max-width: 100%;
|
291 |
+
height: auto;
|
292 |
+
padding: 1px;
|
293 |
+
border: 4px solid transparent;
|
294 |
+
}
|
295 |
+
|
296 |
+
.butterbean-control-radio-image img:hover,
|
297 |
+
.butterbean-control-radio-image img:focus {
|
298 |
+
border-color: #ccc;
|
299 |
+
}
|
300 |
+
|
301 |
+
.butterbean-control-radio-image input:checked + span + img {
|
302 |
+
border-color: #00a0d2;
|
303 |
+
}
|
304 |
+
|
305 |
+
/* === Multi-avatars Control === */
|
306 |
+
|
307 |
+
.butterbean-multi-avatars-wrap label {
|
308 |
+
display: inline-block;
|
309 |
+
margin-top: 8px;
|
310 |
+
}
|
311 |
+
|
312 |
+
.butterbean-multi-avatars-wrap input[type="checkbox"] {
|
313 |
+
clip: rect( 1px, 1px, 1px, 1px );
|
314 |
+
height: 1px;
|
315 |
+
overflow: hidden;
|
316 |
+
position: absolute !important;
|
317 |
+
width: 1px;
|
318 |
+
}
|
319 |
+
|
320 |
+
.butterbean-multi-avatars-wrap .avatar {
|
321 |
+
box-sizing: border-box;
|
322 |
+
max-width: 100%;
|
323 |
+
height: auto;
|
324 |
+
padding: 1px;
|
325 |
+
border: 4px solid transparent;
|
326 |
+
}
|
327 |
+
|
328 |
+
#side-sortables .butterbean-multi-avatars-wrap .avatar {
|
329 |
+
max-width: 60px;
|
330 |
+
max-height: 60px;
|
331 |
+
}
|
332 |
+
|
333 |
+
.butterbean-multi-avatars-wrap img:hover,
|
334 |
+
.butterbean-multi-avatars-wrap img:focus {
|
335 |
+
border-color: #ccc;
|
336 |
+
}
|
337 |
+
|
338 |
+
.butterbean-multi-avatars-wrap input:checked + span + img {
|
339 |
+
border-color: #00a0d2;
|
340 |
+
}
|
includes/lib/butterbean/css/butterbean.min.css
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
.butterbean-ui>.hndle{padding:10px!important;border-bottom:1px solid #eee}.butterbean-ui .inside{margin:0!important;padding:0}.butterbean-manager-default{overflow:hidden;background:#fff;background:linear-gradient(90deg,#fafafa 0,#fafafa 180px,#fff 180px,#fff 100%)}#side-sortables .butterbean-manager-default{background:linear-gradient(90deg,#fafafa 0,#fafafa 48px,#fff 48px,#fff 100%)}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default{background:linear-gradient(90deg,#fafafa 0,#fafafa 48px,#fff 48px,#fff 100%)}}.butterbean-manager-default::before,.butterbean-manager-default::after{content:"";display:table}.butterbean-manager-default::after{clear:both}.butterbean-manager-default .butterbean-nav{position:relative;float:left;list-style:none;width:180px;line-height:1em;margin:0 0 -1px 0;padding:0;background-color:#fafafa;border-right:1px solid #eee;box-sizing:border-box}.butterbean-manager-default .butterbean-nav li{display:block;position:relative;margin:0;padding:0;line-height:20px}.butterbean-manager-default .butterbean-nav li a{display:block;margin:0;padding:10px;line-height:20px!important;text-decoration:none;border-bottom:1px solid #eee;box-shadow:none}.butterbean-manager-default .butterbean-nav .dashicons{line-height:20px;margin-right:3px}.butterbean-manager-default .butterbean-nav li[aria-selected=true] a{position:relative;font-weight:700;color:#555;background-color:#e0e0e0}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default .butterbean-nav{width:48px}.butterbean-manager-default .butterbean-nav .dashicons{width:24px;height:24px;font-size:24px;line-height:24px}.butterbean-manager-default .butterbean-nav .dashicons::before{width:24px;height:24px}.butterbean-manager-default .butterbean-nav .label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}.butterbean-manager-default .butterbean-content{float:left;width:calc(100% - 180px);margin-left:-1px;border-left:1px solid #eee}@media only screen and (max-width:782px),(max-width:980px) and (min-width:851px){.butterbean-manager-default .butterbean-content{width:calc(100% - 48px)}}@media only screen and (min-width:850px){#side-sortables .butterbean-manager-default{background:#fff}#side-sortables .butterbean-manager-default .butterbean-content{width:100%}#side-sortables .butterbean-manager-default .butterbean-nav{display:table;width:100%}#side-sortables .butterbean-manager-default .butterbean-nav li{display:table-cell;text-align:center;border-right:1px solid #eee}#side-sortables .butterbean-manager-default .butterbean-nav li:last-of-type{border-right:0}#side-sortables .butterbean-manager-default .butterbean-nav li a{padding:10px 0}#side-sortables .butterbean-manager-default .butterbean-nav .dashicons{width:24px;height:24px;font-size:24px;line-height:24px}#side-sortables .butterbean-manager-default .butterbean-nav .dashicons::before{width:24px;height:24px}#side-sortables .butterbean-manager-default .butterbean-nav .label{overflow:hidden;position:absolute;top:-1000em;left:-1000em;width:1px;height:1px}}.butterbean-manager-default .butterbean-section{padding:12px 12px 0;box-sizing:border-box}.butterbean-manager-default .butterbean-section[aria-hidden=true]{display:none}.butterbean-manager-default .butterbean-section[aria-hidden=false]{display:block}.butterbean-manager-default .butterbean-control{margin-bottom:20px}.butterbean-manager-default .butterbean-label{display:block!important;font-weight:700;display:inline-block;margin-bottom:4px}.butterbean-manager-default .butterbean-control-checkbox .butterbean-label{display:inline!important}.butterbean-manager-default .butterbean-description{display:block;font-style:italic;margin-top:4px}.butterbean-manager-default .butterbean-label+.butterbean-description{margin-top:0;margin-bottom:4px}.butterbean-control-image .butterbean-img{display:block;max-width:100%;max-height:300px;height:auto}.butterbean-placeholder{width:100%;position:relative;text-align:center;padding:9px 0;line-height:20px;border:1px dashed #b4b9be;box-sizing:border-box}.butterbean-control-textarea textarea,.butterbean-control-excerpt textarea{display:block;width:100%;height:105px}.butterbean-control-datetime select{vertical-align:top}.butterbean-control-palette label{display:block;padding:0 10px 10px}.butterbean-control-palette label[aria-selected=true]{padding-top:5px;background-color:#ddd}.butterbean-palette-label{line-height:28px}.butterbean-palette-block{display:table;width:100%;height:45px;border:1px solid #eee;box-sizing:border-box}.butterbean-palette-color{display:table-cell;height:100%}.butterbean-control-radio-image input[type=radio]{clip:rect(1px,1px,1px,1px);height:1px;overflow:hidden;position:absolute!important;width:1px}.butterbean-control-radio-image img{box-sizing:border-box;max-width:100%;height:auto;padding:1px;border:4px solid transparent}.butterbean-control-radio-image img:hover,.butterbean-control-radio-image img:focus{border-color:#ccc}.butterbean-control-radio-image input:checked+span+img{border-color:#00a0d2}.butterbean-multi-avatars-wrap label{display:inline-block;margin-top:8px}.butterbean-multi-avatars-wrap input[type=checkbox]{clip:rect(1px,1px,1px,1px);height:1px;overflow:hidden;position:absolute!important;width:1px}.butterbean-multi-avatars-wrap .avatar{box-sizing:border-box;max-width:100%;height:auto;padding:1px;border:4px solid transparent}#side-sortables .butterbean-multi-avatars-wrap .avatar{max-width:60px;max-height:60px}.butterbean-multi-avatars-wrap img:hover,.butterbean-multi-avatars-wrap img:focus{border-color:#ccc}.butterbean-multi-avatars-wrap input:checked+span+img{border-color:#00a0d2}
|
includes/lib/butterbean/inc/class-control.php
ADDED
@@ -0,0 +1,399 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for handling controls. Controls are the form fields for the manager. Each
|
4 |
+
* control should be tied to a section.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Base control class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Control {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Stores the manager object.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var object
|
27 |
+
*/
|
28 |
+
public $manager;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Name/ID of the control.
|
32 |
+
*
|
33 |
+
* @since 1.0.0
|
34 |
+
* @access public
|
35 |
+
* @var string
|
36 |
+
*/
|
37 |
+
public $name = '';
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Label for the control.
|
41 |
+
*
|
42 |
+
* @since 1.0.0
|
43 |
+
* @access public
|
44 |
+
* @var string
|
45 |
+
*/
|
46 |
+
public $label = '';
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Description for the control.
|
50 |
+
*
|
51 |
+
* @since 1.0.0
|
52 |
+
* @access public
|
53 |
+
* @var string
|
54 |
+
*/
|
55 |
+
public $description = '';
|
56 |
+
|
57 |
+
/**
|
58 |
+
* ID of the section the control is for.
|
59 |
+
*
|
60 |
+
* @since 1.0.0
|
61 |
+
* @access public
|
62 |
+
* @var string
|
63 |
+
*/
|
64 |
+
public $section = '';
|
65 |
+
|
66 |
+
/**
|
67 |
+
* The setting key for the specific setting the control is tied to.
|
68 |
+
* Controls can have multiple settings attached to them. The default
|
69 |
+
* setting is `default`.
|
70 |
+
*
|
71 |
+
* @since 1.0.0
|
72 |
+
* @access public
|
73 |
+
* @var string
|
74 |
+
*/
|
75 |
+
public $setting = 'default';
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Array of settings if the control has multiple settings.
|
79 |
+
*
|
80 |
+
* @since 1.0.0
|
81 |
+
* @access public
|
82 |
+
* @var array
|
83 |
+
*/
|
84 |
+
public $settings = array();
|
85 |
+
|
86 |
+
/**
|
87 |
+
* The type of control.
|
88 |
+
*
|
89 |
+
* @since 1.0.0
|
90 |
+
* @access public
|
91 |
+
* @var string
|
92 |
+
*/
|
93 |
+
public $type = 'text';
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Form field attributes.
|
97 |
+
*
|
98 |
+
* @since 1.0.0
|
99 |
+
* @access public
|
100 |
+
* @var array
|
101 |
+
*/
|
102 |
+
public $attr = '';
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Choices for fields with multiple choices.
|
106 |
+
*
|
107 |
+
* @since 1.0.0
|
108 |
+
* @access public
|
109 |
+
* @var array
|
110 |
+
*/
|
111 |
+
public $choices = array();
|
112 |
+
|
113 |
+
/**
|
114 |
+
* Priority (order) the control should be output.
|
115 |
+
*
|
116 |
+
* @since 1.0.0
|
117 |
+
* @access public
|
118 |
+
* @var int
|
119 |
+
*/
|
120 |
+
public $priority = 10;
|
121 |
+
|
122 |
+
/**
|
123 |
+
* The number of instances created.
|
124 |
+
*
|
125 |
+
* @since 1.0.0
|
126 |
+
* @access protected
|
127 |
+
* @var int
|
128 |
+
*/
|
129 |
+
protected static $instance_count = 0;
|
130 |
+
|
131 |
+
/**
|
132 |
+
* The instance of the current control.
|
133 |
+
*
|
134 |
+
* @since 1.0.0
|
135 |
+
* @access public
|
136 |
+
* @var int
|
137 |
+
*/
|
138 |
+
public $instance_number;
|
139 |
+
|
140 |
+
/**
|
141 |
+
* A callback function for deciding if a control is active.
|
142 |
+
*
|
143 |
+
* @since 1.0.0
|
144 |
+
* @access public
|
145 |
+
* @var callable
|
146 |
+
*/
|
147 |
+
public $active_callback = '';
|
148 |
+
|
149 |
+
/**
|
150 |
+
* A user role capability required to show the control.
|
151 |
+
*
|
152 |
+
* @since 1.0.0
|
153 |
+
* @access public
|
154 |
+
* @var string|array
|
155 |
+
*/
|
156 |
+
public $capability = '';
|
157 |
+
|
158 |
+
/**
|
159 |
+
* A feature that the current post type must support to show the control.
|
160 |
+
*
|
161 |
+
* @since 1.0.0
|
162 |
+
* @access public
|
163 |
+
* @var string
|
164 |
+
*/
|
165 |
+
public $post_type_supports = '';
|
166 |
+
|
167 |
+
/**
|
168 |
+
* A feature that the current theme must support to show the control.
|
169 |
+
*
|
170 |
+
* @since 1.0.0
|
171 |
+
* @access public
|
172 |
+
* @var string|array
|
173 |
+
*/
|
174 |
+
public $theme_supports = '';
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Stores the JSON data for the control.
|
178 |
+
*
|
179 |
+
* @since 1.0.0
|
180 |
+
* @access public
|
181 |
+
* @var array()
|
182 |
+
*/
|
183 |
+
public $json = array();
|
184 |
+
|
185 |
+
/**
|
186 |
+
* Creates a new control object.
|
187 |
+
*
|
188 |
+
* @since 1.0.0
|
189 |
+
* @access public
|
190 |
+
* @param object $manager
|
191 |
+
* @param string $name
|
192 |
+
* @param array $args
|
193 |
+
* @return void
|
194 |
+
*/
|
195 |
+
public function __construct( $manager, $name, $args = array() ) {
|
196 |
+
|
197 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
198 |
+
|
199 |
+
if ( isset( $args[ $key ] ) )
|
200 |
+
$this->$key = $args[ $key ];
|
201 |
+
}
|
202 |
+
|
203 |
+
$this->manager = $manager;
|
204 |
+
$this->name = $name;
|
205 |
+
|
206 |
+
if ( empty( $args['settings'] ) || ! is_array( $args['settings'] ) )
|
207 |
+
$this->settings['default'] = $name;
|
208 |
+
|
209 |
+
// Increment the instance count and set the instance number.
|
210 |
+
self::$instance_count += 1;
|
211 |
+
$this->instance_number = self::$instance_count;
|
212 |
+
|
213 |
+
// Set the active callback function if not set.
|
214 |
+
if ( ! $this->active_callback )
|
215 |
+
$this->active_callback = array( $this, 'active_callback' );
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Enqueue scripts/styles for the control.
|
220 |
+
*
|
221 |
+
* @since 1.0.0
|
222 |
+
* @access public
|
223 |
+
* @return void
|
224 |
+
*/
|
225 |
+
public function enqueue() {}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Get the value for the setting.
|
229 |
+
*
|
230 |
+
* @since 1.0.0
|
231 |
+
* @access public
|
232 |
+
* @param string $setting
|
233 |
+
* @return mixed
|
234 |
+
*/
|
235 |
+
public function get_value( $setting = 'default' ) {
|
236 |
+
|
237 |
+
$setting = $this->get_setting( $setting );
|
238 |
+
|
239 |
+
return $setting ? $setting->get_value() : '';
|
240 |
+
}
|
241 |
+
|
242 |
+
/**
|
243 |
+
* Returns the setting object associated with this control. If no setting is
|
244 |
+
* found, `false` is returned.
|
245 |
+
*
|
246 |
+
* @since 1.0.0
|
247 |
+
* @access public
|
248 |
+
* @param string $setting
|
249 |
+
* @return object|bool
|
250 |
+
*/
|
251 |
+
public function get_setting( $setting = 'default' ) {
|
252 |
+
|
253 |
+
return $this->manager->get_setting( $this->settings[ $setting ] );
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Gets the attributes for the control.
|
258 |
+
*
|
259 |
+
* @since 1.0.0
|
260 |
+
* @access public
|
261 |
+
* @return array
|
262 |
+
*/
|
263 |
+
public function get_attr() {
|
264 |
+
|
265 |
+
$defaults = array();
|
266 |
+
|
267 |
+
if ( isset( $this->settings[ $this->setting ] ) )
|
268 |
+
$defaults['name'] = $this->get_field_name( $this->setting );
|
269 |
+
|
270 |
+
return wp_parse_args( $this->attr, $defaults );
|
271 |
+
}
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Returns the HTML field name for the control.
|
275 |
+
*
|
276 |
+
* @since 1.0.0
|
277 |
+
* @access public
|
278 |
+
* @param string $setting
|
279 |
+
* @return array
|
280 |
+
*/
|
281 |
+
public function get_field_name( $setting = 'default' ) {
|
282 |
+
|
283 |
+
return "butterbean_{$this->manager->name}_setting_{$this->settings[ $setting ]}";
|
284 |
+
}
|
285 |
+
|
286 |
+
/**
|
287 |
+
* Returns the json array.
|
288 |
+
*
|
289 |
+
* @since 1.0.0
|
290 |
+
* @access public
|
291 |
+
* @return array
|
292 |
+
*/
|
293 |
+
public function get_json() {
|
294 |
+
$this->to_json();
|
295 |
+
|
296 |
+
return $this->json;
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
301 |
+
*
|
302 |
+
* @since 1.0.0
|
303 |
+
* @access public
|
304 |
+
* @return void
|
305 |
+
*/
|
306 |
+
public function to_json() {
|
307 |
+
|
308 |
+
$this->json['manager'] = $this->manager->name;
|
309 |
+
$this->json['section'] = $this->section;
|
310 |
+
$this->json['setting'] = $this->setting;
|
311 |
+
$this->json['settings'] = $this->settings;
|
312 |
+
$this->json['name'] = $this->name;
|
313 |
+
$this->json['label'] = $this->label;
|
314 |
+
$this->json['type'] = $this->type;
|
315 |
+
$this->json['description'] = $this->description;
|
316 |
+
$this->json['choices'] = $this->choices;
|
317 |
+
$this->json['active'] = $this->is_active();
|
318 |
+
|
319 |
+
$this->json['value'] = isset( $this->settings[ $this->setting ] ) ? $this->get_value( $this->setting ) : '';
|
320 |
+
$this->json['field_name'] = isset( $this->settings[ $this->setting ] ) ? $this->get_field_name( $this->setting ) : '';
|
321 |
+
|
322 |
+
$this->json['attr'] = '';
|
323 |
+
|
324 |
+
foreach ( $this->get_attr() as $attr => $value ) {
|
325 |
+
$this->json['attr'] .= sprintf( '%s="%s" ', esc_html( $attr ), esc_attr( $value ) );
|
326 |
+
}
|
327 |
+
}
|
328 |
+
|
329 |
+
/**
|
330 |
+
* Returns whether the control is active.
|
331 |
+
*
|
332 |
+
* @since 1.0.0
|
333 |
+
* @access public
|
334 |
+
* @return bool
|
335 |
+
*/
|
336 |
+
public function is_active() {
|
337 |
+
|
338 |
+
$is_active = call_user_func( $this->active_callback, $this );
|
339 |
+
|
340 |
+
return apply_filters( 'butterbean_is_control_active', $is_active, $this );
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Default active callback.
|
345 |
+
*
|
346 |
+
* @since 1.0.0
|
347 |
+
* @access public
|
348 |
+
* @return bool
|
349 |
+
*/
|
350 |
+
public function active_callback() {
|
351 |
+
return true;
|
352 |
+
}
|
353 |
+
|
354 |
+
/**
|
355 |
+
* Checks if the control should be allowed at all.
|
356 |
+
*
|
357 |
+
* @since 1.0.0
|
358 |
+
* @access public
|
359 |
+
* @return bool
|
360 |
+
*/
|
361 |
+
public function check_capabilities() {
|
362 |
+
|
363 |
+
if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
|
364 |
+
return false;
|
365 |
+
|
366 |
+
if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) )
|
367 |
+
return false;
|
368 |
+
|
369 |
+
if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) )
|
370 |
+
return false;
|
371 |
+
|
372 |
+
return true;
|
373 |
+
}
|
374 |
+
|
375 |
+
/**
|
376 |
+
* Prints Underscore.js template.
|
377 |
+
*
|
378 |
+
* @since 1.0.0
|
379 |
+
* @access public
|
380 |
+
* @return void
|
381 |
+
*/
|
382 |
+
public function print_template() { ?>
|
383 |
+
|
384 |
+
<script type="text/html" id="tmpl-butterbean-control-<?php echo esc_attr( $this->type ); ?>">
|
385 |
+
<?php $this->get_template(); ?>
|
386 |
+
</script>
|
387 |
+
<?php }
|
388 |
+
|
389 |
+
/**
|
390 |
+
* Gets the Underscore.js template.
|
391 |
+
*
|
392 |
+
* @since 1.0.0
|
393 |
+
* @access public
|
394 |
+
* @return void
|
395 |
+
*/
|
396 |
+
public function get_template() {
|
397 |
+
butterbean_get_control_template( $this->type );
|
398 |
+
}
|
399 |
+
}
|
includes/lib/butterbean/inc/class-manager.php
ADDED
@@ -0,0 +1,550 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for handling managers. Managers are groups of sections, which are groups of
|
4 |
+
* controls + settings. Managers are output as a metabox. This essentially allows
|
5 |
+
* developers to output multiple post meta fields within a single metabox.
|
6 |
+
*
|
7 |
+
* @package ButterBean
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Base manager class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Manager {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of manager.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'default';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Name of this instance of the manager.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
public $name = '';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Label for the manager.
|
42 |
+
*
|
43 |
+
* @since 1.0.0
|
44 |
+
* @access public
|
45 |
+
* @var string
|
46 |
+
*/
|
47 |
+
public $label = '';
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Post type this manager is used on.
|
51 |
+
*
|
52 |
+
* @since 1.0.0
|
53 |
+
* @access public
|
54 |
+
* @var string|array
|
55 |
+
*/
|
56 |
+
public $post_type = 'post';
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Location of the meta box. Accepted values: 'normal', 'advanced', 'side'.
|
60 |
+
*
|
61 |
+
* @link https://developer.wordpress.org/reference/functions/add_meta_box/
|
62 |
+
* @since 1.0.0
|
63 |
+
* @access public
|
64 |
+
* @var string
|
65 |
+
*/
|
66 |
+
public $context = 'advanced';
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Priority of the meta box. Accepted values: 'high', 'core', 'default', 'low'.
|
70 |
+
*
|
71 |
+
* @link https://developer.wordpress.org/reference/functions/add_meta_box/
|
72 |
+
* @since 1.0.0
|
73 |
+
* @access public
|
74 |
+
* @var string
|
75 |
+
*/
|
76 |
+
public $priority = 'default';
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Array of sections.
|
80 |
+
*
|
81 |
+
* @since 1.0.0
|
82 |
+
* @access public
|
83 |
+
* @var array
|
84 |
+
*/
|
85 |
+
public $sections = array();
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Array of controls.
|
89 |
+
*
|
90 |
+
* @since 1.0.0
|
91 |
+
* @access public
|
92 |
+
* @var array
|
93 |
+
*/
|
94 |
+
public $controls = array();
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Array of settings.
|
98 |
+
*
|
99 |
+
* @since 1.0.0
|
100 |
+
* @access public
|
101 |
+
* @var array
|
102 |
+
*/
|
103 |
+
public $settings = array();
|
104 |
+
|
105 |
+
/**
|
106 |
+
* A user role capability required to show the manager.
|
107 |
+
*
|
108 |
+
* @since 1.0.0
|
109 |
+
* @access public
|
110 |
+
* @var string|array
|
111 |
+
*/
|
112 |
+
public $capability = '';
|
113 |
+
|
114 |
+
/**
|
115 |
+
* A feature that the current post type must support to show the manager.
|
116 |
+
*
|
117 |
+
* @since 1.0.0
|
118 |
+
* @access public
|
119 |
+
* @var string
|
120 |
+
*/
|
121 |
+
public $post_type_supports = '';
|
122 |
+
|
123 |
+
/**
|
124 |
+
* A feature that the current theme must support to show the manager.
|
125 |
+
*
|
126 |
+
* @since 1.0.0
|
127 |
+
* @access public
|
128 |
+
* @var string|array
|
129 |
+
*/
|
130 |
+
public $theme_supports = '';
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Stores the JSON data for the manager.
|
134 |
+
*
|
135 |
+
* @since 1.0.0
|
136 |
+
* @access public
|
137 |
+
* @var array()
|
138 |
+
*/
|
139 |
+
public $json = array();
|
140 |
+
|
141 |
+
/**
|
142 |
+
* ID of the post that's being edited.
|
143 |
+
*
|
144 |
+
* @since 1.0.0
|
145 |
+
* @access public
|
146 |
+
* @var int
|
147 |
+
*/
|
148 |
+
public $post_id = 0;
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Sets up the manager.
|
152 |
+
*
|
153 |
+
* @since 1.0.0
|
154 |
+
* @access public
|
155 |
+
* @param string $name
|
156 |
+
* @param array $args
|
157 |
+
* @return void
|
158 |
+
*/
|
159 |
+
public function __construct( $name, $args = array() ) {
|
160 |
+
|
161 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
162 |
+
|
163 |
+
if ( isset( $args[ $key ] ) )
|
164 |
+
$this->$key = $args[ $key ];
|
165 |
+
}
|
166 |
+
|
167 |
+
// Make sure the post type is an array.
|
168 |
+
$this->post_type = (array) $this->post_type;
|
169 |
+
|
170 |
+
// Set the manager name.
|
171 |
+
$this->name = sanitize_key( $name );
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Enqueue scripts/styles for the manager.
|
176 |
+
*
|
177 |
+
* @since 1.0.0
|
178 |
+
* @access public
|
179 |
+
* @return void
|
180 |
+
*/
|
181 |
+
public function enqueue() {}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Register a section.
|
185 |
+
*
|
186 |
+
* @since 1.0.0
|
187 |
+
* @access public
|
188 |
+
* @param object|string $section
|
189 |
+
* @param array $args
|
190 |
+
* @return void
|
191 |
+
*/
|
192 |
+
public function register_section( $section, $args = array() ) {
|
193 |
+
|
194 |
+
if ( ! is_object( $section ) ) {
|
195 |
+
|
196 |
+
$type = isset( $args['type'] ) ? butterbean()->get_section_type( $args['type'] ) : butterbean()->get_section_type( 'default' );
|
197 |
+
|
198 |
+
$section = new $type( $this, $section, $args );
|
199 |
+
}
|
200 |
+
|
201 |
+
if ( ! $this->section_exists( $section->name ) )
|
202 |
+
$this->sections[ $section->name ] = $section;
|
203 |
+
}
|
204 |
+
|
205 |
+
/**
|
206 |
+
* Register a control.
|
207 |
+
*
|
208 |
+
* @since 1.0.0
|
209 |
+
* @access public
|
210 |
+
* @param object|string $control
|
211 |
+
* @param array $args
|
212 |
+
* @return void
|
213 |
+
*/
|
214 |
+
public function register_control( $control, $args = array() ) {
|
215 |
+
|
216 |
+
if ( ! is_object( $control ) ) {
|
217 |
+
|
218 |
+
$type = isset( $args['type'] ) ? butterbean()->get_control_type( $args['type'] ) : butterbean()->get_control_type( 'default' );
|
219 |
+
|
220 |
+
$control = new $type( $this, $control, $args );
|
221 |
+
}
|
222 |
+
|
223 |
+
if ( ! $this->control_exists( $control->name ) )
|
224 |
+
$this->controls[ $control->name ] = $control;
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Register a setting.
|
229 |
+
*
|
230 |
+
* @since 1.0.0
|
231 |
+
* @access public
|
232 |
+
* @param object|string $setting
|
233 |
+
* @param array $args
|
234 |
+
* @return void
|
235 |
+
*/
|
236 |
+
public function register_setting( $setting, $args = array() ) {
|
237 |
+
|
238 |
+
if ( ! is_object( $setting ) ) {
|
239 |
+
|
240 |
+
$type = isset( $args['type'] ) ? butterbean()->get_setting_type( $args['type'] ) : butterbean()->get_setting_type( 'default' );
|
241 |
+
|
242 |
+
$setting = new $type( $this, $setting, $args );
|
243 |
+
}
|
244 |
+
|
245 |
+
if ( ! $this->setting_exists( $setting->name ) )
|
246 |
+
$this->settings[ $setting->name ] = $setting;
|
247 |
+
}
|
248 |
+
|
249 |
+
/**
|
250 |
+
* Register a control and setting object.
|
251 |
+
*
|
252 |
+
* @since 1.0.0
|
253 |
+
* @access public
|
254 |
+
* @param string $name
|
255 |
+
* @param object|array $control Control object or array of control arguments.
|
256 |
+
* @param object|array $setting Setting object or array of setting arguments.
|
257 |
+
* @return void
|
258 |
+
*/
|
259 |
+
public function register_field( $name, $control, $setting ) {
|
260 |
+
|
261 |
+
is_object( $control ) ? $this->register_control( $control ) : $this->register_control( $name, $control );
|
262 |
+
is_object( $setting ) ? $this->register_setting( $setting ) : $this->register_setting( $name, $setting );
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Unregisters a section object.
|
267 |
+
*
|
268 |
+
* @since 1.0.0
|
269 |
+
* @access public
|
270 |
+
* @param string $name
|
271 |
+
* @return void
|
272 |
+
*/
|
273 |
+
public function unregister_section( $name ) {
|
274 |
+
|
275 |
+
if ( $this->section_exists( $name ) )
|
276 |
+
unset( $this->sections[ $name ] );
|
277 |
+
}
|
278 |
+
|
279 |
+
/**
|
280 |
+
* Unregisters a control object.
|
281 |
+
*
|
282 |
+
* @since 1.0.0
|
283 |
+
* @access public
|
284 |
+
* @param string $name
|
285 |
+
* @return void
|
286 |
+
*/
|
287 |
+
public function unregister_control( $name ) {
|
288 |
+
|
289 |
+
if ( $this->control_exists( $name ) )
|
290 |
+
unset( $this->controls[ $name ] );
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* Unregisters a setting object.
|
295 |
+
*
|
296 |
+
* @since 1.0.0
|
297 |
+
* @access public
|
298 |
+
* @param string $name
|
299 |
+
* @return void
|
300 |
+
*/
|
301 |
+
public function unregister_setting( $name ) {
|
302 |
+
|
303 |
+
if ( $this->setting_exists( $name ) )
|
304 |
+
unset( $this->settings[ $name ] );
|
305 |
+
}
|
306 |
+
|
307 |
+
/**
|
308 |
+
* Unregisters a control and setting object.
|
309 |
+
*
|
310 |
+
* @since 1.0.0
|
311 |
+
* @access public
|
312 |
+
* @param string $name
|
313 |
+
* @return void
|
314 |
+
*/
|
315 |
+
public function unregister_field( $name ) {
|
316 |
+
|
317 |
+
$this->unregister_control( $name );
|
318 |
+
$this->unregister_setting( $name );
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* Returns a section object.
|
323 |
+
*
|
324 |
+
* @since 1.0.0
|
325 |
+
* @access public
|
326 |
+
* @param string $name
|
327 |
+
* @return object|bool
|
328 |
+
*/
|
329 |
+
public function get_section( $name ) {
|
330 |
+
|
331 |
+
return $this->section_exists( $name ) ? $this->sections[ $name ] : false;
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Returns a control object.
|
336 |
+
*
|
337 |
+
* @since 1.0.0
|
338 |
+
* @access public
|
339 |
+
* @param string $name
|
340 |
+
* @return object|bool
|
341 |
+
*/
|
342 |
+
public function get_control( $name ) {
|
343 |
+
|
344 |
+
return $this->control_exists( $name ) ? $this->controls[ $name ] : false;
|
345 |
+
}
|
346 |
+
|
347 |
+
/**
|
348 |
+
* Returns a setting object.
|
349 |
+
*
|
350 |
+
* @since 1.0.0
|
351 |
+
* @access public
|
352 |
+
* @param string $name
|
353 |
+
* @return object|bool
|
354 |
+
*/
|
355 |
+
public function get_setting( $name ) {
|
356 |
+
|
357 |
+
return $this->setting_exists( $name ) ? $this->settings[ $name ] : false;
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* Returns an object that contains both the control and setting objects.
|
362 |
+
*
|
363 |
+
* @since 1.0.0
|
364 |
+
* @access public
|
365 |
+
* @param string $name
|
366 |
+
* @return object|bool
|
367 |
+
*/
|
368 |
+
public function get_field( $name ) {
|
369 |
+
|
370 |
+
$control = $this->get_control( $name );
|
371 |
+
$setting = $this->get_setting( $name );
|
372 |
+
|
373 |
+
$field = array( 'name' => $name, 'control' => $control, 'setting' => $setting );
|
374 |
+
|
375 |
+
return $control && $setting ? (object) $field : false;
|
376 |
+
}
|
377 |
+
|
378 |
+
/**
|
379 |
+
* Checks if a section exists.
|
380 |
+
*
|
381 |
+
* @since 1.0.0
|
382 |
+
* @access public
|
383 |
+
* @param string $name
|
384 |
+
* @return bool
|
385 |
+
*/
|
386 |
+
public function section_exists( $name ) {
|
387 |
+
|
388 |
+
return isset( $this->sections[ $name ] );
|
389 |
+
}
|
390 |
+
|
391 |
+
/**
|
392 |
+
* Checks if a control exists.
|
393 |
+
*
|
394 |
+
* @since 1.0.0
|
395 |
+
* @access public
|
396 |
+
* @param string $name
|
397 |
+
* @return bool
|
398 |
+
*/
|
399 |
+
public function control_exists( $name ) {
|
400 |
+
|
401 |
+
return isset( $this->controls[ $name ] );
|
402 |
+
}
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Checks if a setting exists.
|
406 |
+
*
|
407 |
+
* @since 1.0.0
|
408 |
+
* @access public
|
409 |
+
* @param string $name
|
410 |
+
* @return bool
|
411 |
+
*/
|
412 |
+
public function setting_exists( $name ) {
|
413 |
+
|
414 |
+
return isset( $this->settings[ $name ] );
|
415 |
+
}
|
416 |
+
|
417 |
+
/**
|
418 |
+
* Checks if a both a control and setting exist.
|
419 |
+
*
|
420 |
+
* @since 1.0.0
|
421 |
+
* @access public
|
422 |
+
* @param string $name
|
423 |
+
* @return bool
|
424 |
+
*/
|
425 |
+
public function field_exists( $name ) {
|
426 |
+
|
427 |
+
return $this->control_exists( $name ) && $this->setting_exists( $name );
|
428 |
+
}
|
429 |
+
|
430 |
+
/**
|
431 |
+
* Returns the json array.
|
432 |
+
*
|
433 |
+
* @since 1.0.0
|
434 |
+
* @access public
|
435 |
+
* @return array
|
436 |
+
*/
|
437 |
+
public function get_json() {
|
438 |
+
$this->to_json();
|
439 |
+
|
440 |
+
return $this->json;
|
441 |
+
}
|
442 |
+
|
443 |
+
/**
|
444 |
+
* Adds custom data to the JSON array. This data is passed to the Underscore template.
|
445 |
+
*
|
446 |
+
* @since 1.0.0
|
447 |
+
* @access public
|
448 |
+
* @return void
|
449 |
+
*/
|
450 |
+
public function to_json() {
|
451 |
+
|
452 |
+
$sections_with_controls = array();
|
453 |
+
$blocked_sections = array();
|
454 |
+
|
455 |
+
$this->json['name'] = $this->name;
|
456 |
+
$this->json['type'] = $this->type;
|
457 |
+
|
458 |
+
// Get all sections that have controls.
|
459 |
+
foreach ( $this->controls as $control )
|
460 |
+
$sections_with_controls[] = $control->section;
|
461 |
+
|
462 |
+
$sections_with_controls = array_unique( $sections_with_controls );
|
463 |
+
|
464 |
+
// Get the JSON data for each section.
|
465 |
+
foreach ( $this->sections as $section ) {
|
466 |
+
|
467 |
+
$caps = $section->check_capabilities();
|
468 |
+
|
469 |
+
if ( $caps && in_array( $section->name, $sections_with_controls ) )
|
470 |
+
$this->json['sections'][] = $section->get_json();
|
471 |
+
|
472 |
+
if ( ! $caps )
|
473 |
+
$blocked_sections[] = $section->name;
|
474 |
+
}
|
475 |
+
|
476 |
+
// Get the JSON data for each control.
|
477 |
+
foreach ( $this->controls as $control ) {
|
478 |
+
|
479 |
+
if ( $control->check_capabilities() && ! in_array( $control->section, $blocked_sections ) )
|
480 |
+
$this->json['controls'][] = $control->get_json();
|
481 |
+
}
|
482 |
+
}
|
483 |
+
|
484 |
+
/**
|
485 |
+
* Saves each of the settings for the manager.
|
486 |
+
*
|
487 |
+
* @since 1.0.0
|
488 |
+
* @access public
|
489 |
+
* @return void
|
490 |
+
*/
|
491 |
+
public function save( $post_id ) {
|
492 |
+
|
493 |
+
if ( ! $this->post_id )
|
494 |
+
$this->post_id = $post_id;
|
495 |
+
|
496 |
+
// Verify the nonce for this manager.
|
497 |
+
if ( ! isset( $_POST["butterbean_{$this->name}"] ) || ! wp_verify_nonce( $_POST["butterbean_{$this->name}"], "butterbean_{$this->name}_nonce" ) )
|
498 |
+
return;
|
499 |
+
|
500 |
+
// Loop through each setting and save it.
|
501 |
+
foreach ( $this->settings as $setting )
|
502 |
+
$setting->save();
|
503 |
+
}
|
504 |
+
|
505 |
+
/**
|
506 |
+
* Checks if the control should be allowed at all.
|
507 |
+
*
|
508 |
+
* @since 1.0.0
|
509 |
+
* @access public
|
510 |
+
* @return bool
|
511 |
+
*/
|
512 |
+
public function check_capabilities() {
|
513 |
+
|
514 |
+
if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
|
515 |
+
return false;
|
516 |
+
|
517 |
+
if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) )
|
518 |
+
return false;
|
519 |
+
|
520 |
+
if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) )
|
521 |
+
return false;
|
522 |
+
|
523 |
+
return true;
|
524 |
+
}
|
525 |
+
|
526 |
+
/**
|
527 |
+
* Prints Underscore.js template.
|
528 |
+
*
|
529 |
+
* @since 1.0.0
|
530 |
+
* @access public
|
531 |
+
* @return void
|
532 |
+
*/
|
533 |
+
public function print_template() { ?>
|
534 |
+
|
535 |
+
<script type="text/html" id="tmpl-butterbean-manager-<?php echo esc_attr( $this->type ); ?>">
|
536 |
+
<?php $this->get_template(); ?>
|
537 |
+
</script>
|
538 |
+
<?php }
|
539 |
+
|
540 |
+
/**
|
541 |
+
* Gets the Underscore.js template.
|
542 |
+
*
|
543 |
+
* @since 1.0.0
|
544 |
+
* @access public
|
545 |
+
* @return void
|
546 |
+
*/
|
547 |
+
public function get_template() {
|
548 |
+
butterbean_get_manager_template( $this->type );
|
549 |
+
}
|
550 |
+
}
|
includes/lib/butterbean/inc/class-section.php
ADDED
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base class for handling sections. Sections house groups of controls. Multiple sections can
|
4 |
+
* be added to a manager.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @subpackage Admin
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Base section class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Section {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Stores the project details manager object.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var object
|
28 |
+
*/
|
29 |
+
public $manager;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Name/ID of the section.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
public $name = '';
|
39 |
+
|
40 |
+
/**
|
41 |
+
* The type of section.
|
42 |
+
*
|
43 |
+
* @since 1.0.0
|
44 |
+
* @access public
|
45 |
+
* @var string
|
46 |
+
*/
|
47 |
+
public $type = 'default';
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Dashicons icon for the section.
|
51 |
+
*
|
52 |
+
* @since 1.0.0
|
53 |
+
* @access public
|
54 |
+
* @var string
|
55 |
+
*/
|
56 |
+
public $icon = 'dashicons-admin-generic';
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Label for the section.
|
60 |
+
*
|
61 |
+
* @since 1.0.0
|
62 |
+
* @access public
|
63 |
+
* @var string
|
64 |
+
*/
|
65 |
+
public $label = '';
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Description for the section.
|
69 |
+
*
|
70 |
+
* @since 1.0.0
|
71 |
+
* @access public
|
72 |
+
* @var string
|
73 |
+
*/
|
74 |
+
public $description = '';
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Priority (order) the section should be output.
|
78 |
+
*
|
79 |
+
* @since 1.0.0
|
80 |
+
* @access public
|
81 |
+
* @var int
|
82 |
+
*/
|
83 |
+
public $priority = 10;
|
84 |
+
|
85 |
+
/**
|
86 |
+
* The number of instances created.
|
87 |
+
*
|
88 |
+
* @since 1.0.0
|
89 |
+
* @access protected
|
90 |
+
* @var int
|
91 |
+
*/
|
92 |
+
protected static $instance_count = 0;
|
93 |
+
|
94 |
+
/**
|
95 |
+
* The instance of the current section.
|
96 |
+
*
|
97 |
+
* @since 1.0.0
|
98 |
+
* @access public
|
99 |
+
* @var int
|
100 |
+
*/
|
101 |
+
public $instance_number;
|
102 |
+
|
103 |
+
/**
|
104 |
+
* A callback function for deciding if a section is active.
|
105 |
+
*
|
106 |
+
* @since 1.0.0
|
107 |
+
* @access public
|
108 |
+
* @var callable
|
109 |
+
*/
|
110 |
+
public $active_callback = '';
|
111 |
+
|
112 |
+
/**
|
113 |
+
* A user role capability required to show the section.
|
114 |
+
*
|
115 |
+
* @since 1.0.0
|
116 |
+
* @access public
|
117 |
+
* @var string|array
|
118 |
+
*/
|
119 |
+
public $capability = '';
|
120 |
+
|
121 |
+
/**
|
122 |
+
* A feature that the current post type must support to show the section.
|
123 |
+
*
|
124 |
+
* @since 1.0.0
|
125 |
+
* @access public
|
126 |
+
* @var string
|
127 |
+
*/
|
128 |
+
public $post_type_supports = '';
|
129 |
+
|
130 |
+
/**
|
131 |
+
* A feature that the current theme must support to show the section.
|
132 |
+
*
|
133 |
+
* @since 1.0.0
|
134 |
+
* @access public
|
135 |
+
* @var string|array
|
136 |
+
*/
|
137 |
+
public $theme_supports = '';
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Stores the JSON data for the manager.
|
141 |
+
*
|
142 |
+
* @since 1.0.0
|
143 |
+
* @access public
|
144 |
+
* @var array()
|
145 |
+
*/
|
146 |
+
public $json = array();
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Creates a new section object.
|
150 |
+
*
|
151 |
+
* @since 1.0.0
|
152 |
+
* @access public
|
153 |
+
* @param object $manager
|
154 |
+
* @param string $section
|
155 |
+
* @param array $args
|
156 |
+
* @return void
|
157 |
+
*/
|
158 |
+
public function __construct( $manager, $name, $args = array() ) {
|
159 |
+
|
160 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
161 |
+
|
162 |
+
if ( isset( $args[ $key ] ) )
|
163 |
+
$this->$key = $args[ $key ];
|
164 |
+
}
|
165 |
+
|
166 |
+
$this->manager = $manager;
|
167 |
+
$this->name = $name;
|
168 |
+
|
169 |
+
// Increment the instance count and set the instance number.
|
170 |
+
self::$instance_count += 1;
|
171 |
+
$this->instance_number = self::$instance_count;
|
172 |
+
|
173 |
+
// Set the active callback function if not set.
|
174 |
+
if ( ! $this->active_callback )
|
175 |
+
$this->active_callback = array( $this, 'active_callback' );
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Enqueue scripts/styles for the section.
|
180 |
+
*
|
181 |
+
* @since 1.0.0
|
182 |
+
* @access public
|
183 |
+
* @return void
|
184 |
+
*/
|
185 |
+
public function enqueue() {}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Returns the json array.
|
189 |
+
*
|
190 |
+
* @since 1.0.0
|
191 |
+
* @access public
|
192 |
+
* @return array
|
193 |
+
*/
|
194 |
+
public function get_json() {
|
195 |
+
$this->to_json();
|
196 |
+
|
197 |
+
return $this->json;
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
202 |
+
*
|
203 |
+
* @since 1.0.0
|
204 |
+
* @access public
|
205 |
+
* @return void
|
206 |
+
*/
|
207 |
+
public function to_json() {
|
208 |
+
|
209 |
+
$this->json['manager'] = $this->manager->name;
|
210 |
+
$this->json['name'] = $this->name;
|
211 |
+
$this->json['type'] = $this->type;
|
212 |
+
$this->json['icon'] = preg_match( '/dashicons-/', $this->icon ) ? sprintf( 'dashicons %s', sanitize_html_class( $this->icon ) ) : esc_attr( $this->icon );
|
213 |
+
$this->json['label'] = $this->label;
|
214 |
+
$this->json['description'] = $this->description;
|
215 |
+
$this->json['active'] = $this->is_active();
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Returns whether the section is active.
|
220 |
+
*
|
221 |
+
* @since 1.0.0
|
222 |
+
* @access public
|
223 |
+
* @return bool
|
224 |
+
*/
|
225 |
+
public function is_active() {
|
226 |
+
|
227 |
+
$is_active = call_user_func( $this->active_callback, $this );
|
228 |
+
|
229 |
+
if ( $is_active )
|
230 |
+
$is_active = $this->check_capabilities();
|
231 |
+
|
232 |
+
return apply_filters( 'butterbean_is_section_active', $is_active, $this );
|
233 |
+
}
|
234 |
+
|
235 |
+
/**
|
236 |
+
* Default active callback.
|
237 |
+
*
|
238 |
+
* @since 1.0.0
|
239 |
+
* @access public
|
240 |
+
* @return bool
|
241 |
+
*/
|
242 |
+
public function active_callback() {
|
243 |
+
return true;
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Checks if the section should be allowed at all.
|
248 |
+
*
|
249 |
+
* @since 1.0.0
|
250 |
+
* @access public
|
251 |
+
* @return bool
|
252 |
+
*/
|
253 |
+
public function check_capabilities() {
|
254 |
+
|
255 |
+
if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
|
256 |
+
return false;
|
257 |
+
|
258 |
+
if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) )
|
259 |
+
return false;
|
260 |
+
|
261 |
+
if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) )
|
262 |
+
return false;
|
263 |
+
|
264 |
+
return true;
|
265 |
+
}
|
266 |
+
|
267 |
+
/**
|
268 |
+
* Prints Underscore.js template.
|
269 |
+
*
|
270 |
+
* @since 1.0.0
|
271 |
+
* @access public
|
272 |
+
* @return void
|
273 |
+
*/
|
274 |
+
public function print_template() { ?>
|
275 |
+
|
276 |
+
<script type="text/html" id="tmpl-butterbean-section-<?php echo esc_attr( $this->type ); ?>">
|
277 |
+
<?php $this->get_template(); ?>
|
278 |
+
</script>
|
279 |
+
<?php }
|
280 |
+
|
281 |
+
/**
|
282 |
+
* Gets the Underscore.js template.
|
283 |
+
*
|
284 |
+
* @since 1.0.0
|
285 |
+
* @access public
|
286 |
+
* @return void
|
287 |
+
*/
|
288 |
+
public function get_template() {
|
289 |
+
butterbean_get_section_template( $this->type );
|
290 |
+
}
|
291 |
+
}
|
includes/lib/butterbean/inc/class-setting.php
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Base setting class for the fields manager.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @subpackage Admin
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Base setting class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Setting {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* The type of setting.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
public $type = 'default';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Stores the manager object.
|
32 |
+
*
|
33 |
+
* @since 1.0.0
|
34 |
+
* @access public
|
35 |
+
* @var object
|
36 |
+
*/
|
37 |
+
public $manager;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Name/ID of the setting.
|
41 |
+
*
|
42 |
+
* @since 1.0.0
|
43 |
+
* @access public
|
44 |
+
* @var string
|
45 |
+
*/
|
46 |
+
public $name = '';
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Value of the setting.
|
50 |
+
*
|
51 |
+
* @since 1.0.0
|
52 |
+
* @access public
|
53 |
+
* @var string
|
54 |
+
*/
|
55 |
+
public $value = '';
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Default value of the setting.
|
59 |
+
*
|
60 |
+
* @since 1.0.0
|
61 |
+
* @access public
|
62 |
+
* @var string
|
63 |
+
*/
|
64 |
+
public $default = '';
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Sanitization/Validation callback function.
|
68 |
+
*
|
69 |
+
* @since 1.0.0
|
70 |
+
* @access public
|
71 |
+
* @var string
|
72 |
+
*/
|
73 |
+
public $sanitize_callback = '';
|
74 |
+
|
75 |
+
/**
|
76 |
+
* A user role capability required to save the setting.
|
77 |
+
*
|
78 |
+
* @since 1.0.0
|
79 |
+
* @access public
|
80 |
+
* @var string|array
|
81 |
+
*/
|
82 |
+
public $capability = '';
|
83 |
+
|
84 |
+
/**
|
85 |
+
* A feature that the current post type must support to save the setting.
|
86 |
+
*
|
87 |
+
* @since 1.0.0
|
88 |
+
* @access public
|
89 |
+
* @var string
|
90 |
+
*/
|
91 |
+
public $post_type_supports = '';
|
92 |
+
|
93 |
+
/**
|
94 |
+
* A feature that the current theme must support to save the setting.
|
95 |
+
*
|
96 |
+
* @since 1.0.0
|
97 |
+
* @access public
|
98 |
+
* @var string|array
|
99 |
+
*/
|
100 |
+
public $theme_supports = '';
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Creates a new setting object.
|
104 |
+
*
|
105 |
+
* @since 1.0.0
|
106 |
+
* @access public
|
107 |
+
* @param object $manager
|
108 |
+
* @param string $cap
|
109 |
+
* @param array $args
|
110 |
+
* @return void
|
111 |
+
*/
|
112 |
+
public function __construct( $manager, $name, $args = array() ) {
|
113 |
+
|
114 |
+
foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
|
115 |
+
|
116 |
+
if ( isset( $args[ $key ] ) )
|
117 |
+
$this->$key = $args[ $key ];
|
118 |
+
}
|
119 |
+
|
120 |
+
$this->manager = $manager;
|
121 |
+
$this->name = $name;
|
122 |
+
|
123 |
+
if ( $this->sanitize_callback )
|
124 |
+
add_filter( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $this->sanitize_callback, 10, 2 );
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Gets the value of the setting.
|
129 |
+
*
|
130 |
+
* @since 1.0.0
|
131 |
+
* @access public
|
132 |
+
* @return mixed
|
133 |
+
*/
|
134 |
+
public function get_value() {
|
135 |
+
|
136 |
+
$value = get_post_meta( $this->manager->post_id, $this->name, true );
|
137 |
+
|
138 |
+
return ! $value && butterbean()->is_new_post ? $this->default : $value;
|
139 |
+
}
|
140 |
+
|
141 |
+
/**
|
142 |
+
* Gets the posted value of the setting.
|
143 |
+
*
|
144 |
+
* @since 1.0.0
|
145 |
+
* @access public
|
146 |
+
* @return mixed
|
147 |
+
*/
|
148 |
+
public function get_posted_value() {
|
149 |
+
|
150 |
+
$value = '';
|
151 |
+
|
152 |
+
if ( isset( $_POST[ $this->get_field_name() ] ) )
|
153 |
+
$value = $_POST[ $this->get_field_name() ];
|
154 |
+
|
155 |
+
return $this->sanitize( $value );
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Retuns the correct field name for the setting.
|
160 |
+
*
|
161 |
+
* @since 1.0.0
|
162 |
+
* @access public
|
163 |
+
* @return string
|
164 |
+
*/
|
165 |
+
public function get_field_name() {
|
166 |
+
|
167 |
+
return "butterbean_{$this->manager->name}_setting_{$this->name}";
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Sanitizes the value of the setting.
|
172 |
+
*
|
173 |
+
* @since 1.0.0
|
174 |
+
* @access public
|
175 |
+
* @return mixed
|
176 |
+
*/
|
177 |
+
public function sanitize( $value ) {
|
178 |
+
|
179 |
+
return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this );
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Saves the value of the setting.
|
184 |
+
*
|
185 |
+
* @since 1.0.0
|
186 |
+
* @access public
|
187 |
+
* @return void
|
188 |
+
*/
|
189 |
+
public function save() {
|
190 |
+
|
191 |
+
if ( ! $this->check_capabilities() )
|
192 |
+
return;
|
193 |
+
|
194 |
+
$old_value = $this->get_value();
|
195 |
+
$new_value = $this->get_posted_value();
|
196 |
+
|
197 |
+
// If we have don't have a new value but do have an old one, delete it.
|
198 |
+
if ( ! $new_value && $old_value )
|
199 |
+
delete_post_meta( $this->manager->post_id, $this->name );
|
200 |
+
|
201 |
+
// If the new value doesn't match the old value, set it.
|
202 |
+
else if ( $new_value !== $old_value )
|
203 |
+
update_post_meta( $this->manager->post_id, $this->name, $new_value );
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Checks if the setting should be saved at all.
|
208 |
+
*
|
209 |
+
* @since 1.0.0
|
210 |
+
* @access public
|
211 |
+
* @return bool
|
212 |
+
*/
|
213 |
+
public function check_capabilities() {
|
214 |
+
|
215 |
+
if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
|
216 |
+
return false;
|
217 |
+
|
218 |
+
if ( $this->post_type_supports && ! call_user_func_array( 'post_type_supports', array( get_post_type( $this->manager->post_id ), $this->post_type_supports ) ) )
|
219 |
+
return false;
|
220 |
+
|
221 |
+
if ( $this->theme_supports && ! call_user_func_array( 'theme_supports', (array) $this->theme_supports ) )
|
222 |
+
return false;
|
223 |
+
|
224 |
+
return true;
|
225 |
+
}
|
226 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-checkboxes.php
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Multiple checkbox control class. This is for array-type settings, so you'll need
|
4 |
+
* to utilize a setting type that handles arrays. Both the `array` and `multiple`
|
5 |
+
* setting types will do this.
|
6 |
+
*
|
7 |
+
* @package ButterBean
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Multiple checkboxes control class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Control_CheckBoxes extends ButterBean_Control {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of control.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'checkboxes';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @return void
|
37 |
+
*/
|
38 |
+
public function to_json() {
|
39 |
+
parent::to_json();
|
40 |
+
|
41 |
+
$this->json['value'] = (array) $this->get_value();
|
42 |
+
}
|
43 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-color.php
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Color control class. This class uses the core WordPress color picker. Expected
|
4 |
+
* values are hex colors. This class also attempts to strip `#` from the hex color.
|
5 |
+
* By design, it's recommended to add the `#` on output.
|
6 |
+
*
|
7 |
+
* @package ButterBean
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Color control class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Control_Color extends ButterBean_Control {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of control.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'color';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Custom options to pass to the color picker. Mostly, this is a wrapper for
|
33 |
+
* `iris()`, which is bundled with core WP. However, if they change pickers
|
34 |
+
* in the future, it may correspond to a different script.
|
35 |
+
*
|
36 |
+
* @link http://automattic.github.io/Iris/#options
|
37 |
+
* @link https://make.wordpress.org/core/2012/11/30/new-color-picker-in-wp-3-5/
|
38 |
+
* @since 1.0.0
|
39 |
+
* @access public
|
40 |
+
* @var array
|
41 |
+
*/
|
42 |
+
public $options = array();
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Enqueue scripts/styles for the control.
|
46 |
+
*
|
47 |
+
* @since 1.0.0
|
48 |
+
* @access public
|
49 |
+
* @return void
|
50 |
+
*/
|
51 |
+
public function enqueue() {
|
52 |
+
|
53 |
+
wp_enqueue_script( 'wp-color-picker' );
|
54 |
+
wp_enqueue_style( 'wp-color-picker' );
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Gets the attributes for the control.
|
59 |
+
*
|
60 |
+
* @since 1.0.0
|
61 |
+
* @access public
|
62 |
+
* @return array
|
63 |
+
*/
|
64 |
+
public function get_attr() {
|
65 |
+
$attr = parent::get_attr();
|
66 |
+
|
67 |
+
$setting = $this->get_setting();
|
68 |
+
|
69 |
+
$attr['class'] = 'butterbean-color-picker';
|
70 |
+
$attr['type'] = 'text';
|
71 |
+
$attr['maxlength'] = 7;
|
72 |
+
$attr['data-default-color'] = $setting ? $setting->default : '';
|
73 |
+
|
74 |
+
return $attr;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Get the value for the setting.
|
79 |
+
*
|
80 |
+
* @since 1.0.0
|
81 |
+
* @access public
|
82 |
+
* @param string $setting
|
83 |
+
* @return mixed
|
84 |
+
*/
|
85 |
+
public function get_value( $setting = 'default' ) {
|
86 |
+
|
87 |
+
$value = parent::get_value( $setting );
|
88 |
+
|
89 |
+
return ltrim( $value, '#' );
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
94 |
+
*
|
95 |
+
* @since 1.0.0
|
96 |
+
* @access public
|
97 |
+
* @return void
|
98 |
+
*/
|
99 |
+
public function to_json() {
|
100 |
+
parent::to_json();
|
101 |
+
|
102 |
+
$this->json['options'] = $this->options;
|
103 |
+
}
|
104 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-datetime.php
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Datetime control class. This class is meant for storing a datetime in the format
|
4 |
+
* of `YYYY-MM-DD HH:MM:SS` or `0000-00-00 00:00:00`. You can set the `$show_time`
|
5 |
+
* property to `false`.
|
6 |
+
*
|
7 |
+
* Note that this control should be used in conjunction with the `datetime` setting
|
8 |
+
* type or another custom class that can handle the datetime.
|
9 |
+
*
|
10 |
+
* @package ButterBean
|
11 |
+
* @subpackage Admin
|
12 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
13 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
14 |
+
* @link https://github.com/justintadlock/butterbean
|
15 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
16 |
+
*/
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Datetime control class.
|
20 |
+
*
|
21 |
+
* @since 1.0.0
|
22 |
+
* @access public
|
23 |
+
*/
|
24 |
+
class ButterBean_Control_Datetime extends ButterBean_Control {
|
25 |
+
|
26 |
+
/**
|
27 |
+
* The type of control.
|
28 |
+
*
|
29 |
+
* @since 1.0.0
|
30 |
+
* @access public
|
31 |
+
* @var string
|
32 |
+
*/
|
33 |
+
public $type = 'datetime';
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Whether to show the time. Note that settings, particularly the
|
37 |
+
* `ButterBean_Setting_Date` class will store the time as `00:00:00` if
|
38 |
+
* no time is provided.
|
39 |
+
*
|
40 |
+
* @since 1.0.0
|
41 |
+
* @access public
|
42 |
+
* @var bool
|
43 |
+
*/
|
44 |
+
public $show_time = true;
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
48 |
+
*
|
49 |
+
* @since 1.0.0
|
50 |
+
* @access public
|
51 |
+
* @globl object $wp_locale
|
52 |
+
* @return void
|
53 |
+
*/
|
54 |
+
public function to_json() {
|
55 |
+
global $wp_locale;
|
56 |
+
|
57 |
+
parent::to_json();
|
58 |
+
|
59 |
+
$this->json['show_time'] = $this->show_time;
|
60 |
+
|
61 |
+
$field_name = $this->get_field_name();
|
62 |
+
|
63 |
+
// Get project start/end dates.
|
64 |
+
$date = $this->get_value();
|
65 |
+
|
66 |
+
// Get the year, month, and day.
|
67 |
+
$year = $date ? mysql2date( 'Y', $date, false ) : '';
|
68 |
+
$month = $date ? mysql2date( 'm', $date, false ) : '';
|
69 |
+
$day = $date ? mysql2date( 'd', $date, false ) : '';
|
70 |
+
|
71 |
+
// Get the hour, minute, and second.
|
72 |
+
$hour = $date ? mysql2date( 'H', $date, false ) : '';
|
73 |
+
$minute = $date ? mysql2date( 'i', $date, false ) : '';
|
74 |
+
$second = $date ? mysql2date( 's', $date, false ) : '';
|
75 |
+
|
76 |
+
// Year
|
77 |
+
$this->json['year'] = array(
|
78 |
+
'value' => esc_attr( $year ),
|
79 |
+
'label' => esc_html__( 'Year', 'butterbean' ),
|
80 |
+
'name' => esc_attr( "{$field_name}_year" ),
|
81 |
+
'attr' => sprintf( 'placeholder="%s" size="4" maxlength="4" autocomplete="off"', esc_attr( date_i18n( 'Y' ) ) )
|
82 |
+
);
|
83 |
+
|
84 |
+
// Month
|
85 |
+
$this->json['month'] = array(
|
86 |
+
'value' => esc_attr( $month ),
|
87 |
+
'name' => esc_attr( "{$field_name}_month" ),
|
88 |
+
'label' => esc_html__( 'Month', 'butterbean' ),
|
89 |
+
'choices' => array(
|
90 |
+
array(
|
91 |
+
'num' => '',
|
92 |
+
'label' => ''
|
93 |
+
)
|
94 |
+
)
|
95 |
+
);
|
96 |
+
|
97 |
+
for ( $i = 1; $i < 13; $i = $i +1 ) {
|
98 |
+
|
99 |
+
$monthnum = zeroise( $i, 2 );
|
100 |
+
$monthtext = $wp_locale->get_month_abbrev( $wp_locale->get_month( $i ) );
|
101 |
+
|
102 |
+
$this->json['month']['choices'][] = array(
|
103 |
+
'num' => $monthnum,
|
104 |
+
'label' => $monthtext
|
105 |
+
);
|
106 |
+
}
|
107 |
+
|
108 |
+
// Day
|
109 |
+
$this->json['day'] = array(
|
110 |
+
'value' => esc_attr( $day ),
|
111 |
+
'name' => esc_attr( "{$field_name}_day" ),
|
112 |
+
'label' => esc_html__( 'Day', 'butterbean' ),
|
113 |
+
'attr' => sprintf( 'placeholder="%s" size="2" maxlength="2" autocomplete="off"', esc_attr( date_i18n( 'd' ) ) )
|
114 |
+
);
|
115 |
+
|
116 |
+
// Hour
|
117 |
+
$this->json['hour'] = array(
|
118 |
+
'value' => esc_attr( $hour ),
|
119 |
+
'name' => esc_attr( "{$field_name}_hour" ),
|
120 |
+
'label' => esc_html__( 'Hour', 'butterbean' ),
|
121 |
+
'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"'
|
122 |
+
);
|
123 |
+
|
124 |
+
// Minute
|
125 |
+
$this->json['minute'] = array(
|
126 |
+
'value' => esc_attr( $minute ),
|
127 |
+
'name' => esc_attr( "{$field_name}_minute" ),
|
128 |
+
'label' => esc_html__( 'Minute', 'butterbean' ),
|
129 |
+
'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"'
|
130 |
+
);
|
131 |
+
|
132 |
+
// Second
|
133 |
+
$this->json['second'] = array(
|
134 |
+
'value' => esc_attr( $second ),
|
135 |
+
'name' => esc_attr( "{$field_name}_second" ),
|
136 |
+
'label' => esc_html__( 'Second', 'butterbean' ),
|
137 |
+
'attr' => 'placeholder="00" size="2" maxlength="2" autocomplete="off"'
|
138 |
+
);
|
139 |
+
}
|
140 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-excerpt.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Excerpt control class. Note that this control isn't meant to be tied to a setting. Core
|
4 |
+
* WP will save the excerpt. Also, make sure to disable the core excerpt metabox if using
|
5 |
+
* this control.
|
6 |
+
*
|
7 |
+
* @package ButterBean
|
8 |
+
* @subpackage Admin
|
9 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
10 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
11 |
+
* @link https://github.com/justintadlock/butterbean
|
12 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
13 |
+
*/
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Excerpt control class.
|
17 |
+
*
|
18 |
+
* @since 1.0.0
|
19 |
+
* @access public
|
20 |
+
*/
|
21 |
+
class ButterBean_Control_Excerpt extends ButterBean_Control_Textarea {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* The type of control.
|
25 |
+
*
|
26 |
+
* @since 1.0.0
|
27 |
+
* @access public
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
public $type = 'excerpt';
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Gets the attributes for the control.
|
34 |
+
*
|
35 |
+
* @since 1.0.0
|
36 |
+
* @access public
|
37 |
+
* @return array
|
38 |
+
*/
|
39 |
+
public function get_attr() {
|
40 |
+
$attr = parent::get_attr();
|
41 |
+
|
42 |
+
$attr['id'] = 'post_excerpt';
|
43 |
+
|
44 |
+
return $attr;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Returns the HTML field name for the control.
|
49 |
+
*
|
50 |
+
* @since 1.0.0
|
51 |
+
* @access public
|
52 |
+
* @param string $setting
|
53 |
+
* @return string
|
54 |
+
*/
|
55 |
+
public function get_field_name( $setting = 'default' ) {
|
56 |
+
return 'post_excerpt';
|
57 |
+
}
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Get the value for the setting.
|
61 |
+
*
|
62 |
+
* @since 1.0.0
|
63 |
+
* @access public
|
64 |
+
* @param string $setting
|
65 |
+
* @return mixed
|
66 |
+
*/
|
67 |
+
public function get_value( $setting = 'default' ) {
|
68 |
+
|
69 |
+
return get_post( $this->manager->post_id )->post_excerpt;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Gets the Underscore.js template.
|
74 |
+
*
|
75 |
+
* @since 1.0.0
|
76 |
+
* @access public
|
77 |
+
* @return void
|
78 |
+
*/
|
79 |
+
public function get_template() {
|
80 |
+
butterbean_get_control_template( 'textarea' );
|
81 |
+
}
|
82 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-image.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Image control class. This control allows users to set an image. It passes the attachment
|
4 |
+
* ID the setting, so you'll need a custom control class if you want to store anything else,
|
5 |
+
* such as the URL or other data.
|
6 |
+
*
|
7 |
+
* @package ButterBean
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Image control class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Control_Image extends ButterBean_Control {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of control.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'image';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Array of text labels to use for the media upload frame.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @var string
|
37 |
+
*/
|
38 |
+
public $l10n = array();
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Image size to display. If the size isn't found for the image,
|
42 |
+
* the full size of the image will be output.
|
43 |
+
*
|
44 |
+
* @since 1.0.0
|
45 |
+
* @access public
|
46 |
+
* @var string
|
47 |
+
*/
|
48 |
+
public $size = 'large';
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Creates a new control object.
|
52 |
+
*
|
53 |
+
* @since 1.0.0
|
54 |
+
* @access public
|
55 |
+
* @param object $manager
|
56 |
+
* @param string $name
|
57 |
+
* @param array $args
|
58 |
+
* @return void
|
59 |
+
*/
|
60 |
+
public function __construct( $manager, $name, $args = array() ) {
|
61 |
+
parent::__construct( $manager, $name, $args );
|
62 |
+
|
63 |
+
$this->l10n = wp_parse_args(
|
64 |
+
$this->l10n,
|
65 |
+
array(
|
66 |
+
'upload' => esc_html__( 'Add image', 'butterbean' ),
|
67 |
+
'set' => esc_html__( 'Set as image', 'butterbean' ),
|
68 |
+
'choose' => esc_html__( 'Choose image', 'butterbean' ),
|
69 |
+
'change' => esc_html__( 'Change image', 'butterbean' ),
|
70 |
+
'remove' => esc_html__( 'Remove image', 'butterbean' ),
|
71 |
+
'placeholder' => esc_html__( 'No image selected', 'butterbean' )
|
72 |
+
)
|
73 |
+
);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Enqueue scripts/styles for the control.
|
78 |
+
*
|
79 |
+
* @since 1.0.0
|
80 |
+
* @access public
|
81 |
+
* @return void
|
82 |
+
*/
|
83 |
+
public function enqueue() {
|
84 |
+
|
85 |
+
wp_enqueue_media();
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Adds custom data to the json array.
|
90 |
+
*
|
91 |
+
* @since 1.0.0
|
92 |
+
* @access public
|
93 |
+
* @return void
|
94 |
+
*/
|
95 |
+
public function to_json() {
|
96 |
+
parent::to_json();
|
97 |
+
|
98 |
+
$this->json['l10n'] = $this->l10n;
|
99 |
+
$this->json['size'] = $this->size;
|
100 |
+
|
101 |
+
$value = $this->get_value();
|
102 |
+
$image = $alt = '';
|
103 |
+
|
104 |
+
if ( $value ) {
|
105 |
+
$image = wp_get_attachment_image_src( absint( $value ), $this->size );
|
106 |
+
$alt = get_post_meta( absint( $value ), '_wp_attachment_image_alt', true );
|
107 |
+
}
|
108 |
+
|
109 |
+
$this->json['src'] = $image ? esc_url( $image[0] ) : '';
|
110 |
+
$this->json['alt'] = $alt ? esc_attr( $alt ) : '';
|
111 |
+
}
|
112 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-multi-avatars.php
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Multi-avatars control. This control is for outputting multiple users who can create,
|
4 |
+
* edit, or publish posts of the given post type. Multiple users can be selected. The
|
5 |
+
* data is expected to be an array. This control should be used with a setting type that
|
6 |
+
* handles arrays, such as the built-in `array` or `multiple` types.
|
7 |
+
*
|
8 |
+
* @package ButterBean
|
9 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
10 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
11 |
+
* @link https://github.com/justintadlock/butterbean
|
12 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
13 |
+
*/
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Multi-avatars control class.
|
17 |
+
*
|
18 |
+
* @since 1.0.0
|
19 |
+
* @access public
|
20 |
+
*/
|
21 |
+
class ButterBean_Control_Multi_Avatars extends ButterBean_Control {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* The type of control.
|
25 |
+
*
|
26 |
+
* @since 1.0.0
|
27 |
+
* @access public
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
public $type = 'multi-avatars';
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
34 |
+
*
|
35 |
+
* @since 1.0.0
|
36 |
+
* @access public
|
37 |
+
* @return void
|
38 |
+
*/
|
39 |
+
public function to_json() {
|
40 |
+
parent::to_json();
|
41 |
+
|
42 |
+
$this->json['value'] = is_array( $this->get_value() ) ? array_map( 'absint', $this->get_value() ) : array();
|
43 |
+
$this->json['choices'] = array();
|
44 |
+
|
45 |
+
$users = get_users( array( 'role__in' => $this->get_roles() ) );
|
46 |
+
|
47 |
+
foreach ( $users as $user ) {
|
48 |
+
$this->json['choices'][] = array(
|
49 |
+
'id' => $user->ID,
|
50 |
+
'name' => $user->display_name,
|
51 |
+
'avatar' => get_avatar( $user->ID, 70 )
|
52 |
+
);
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Returns an array of user roles that are allowed to edit, publish, or create
|
58 |
+
* posts of the given post type.
|
59 |
+
*
|
60 |
+
* @since 1.0.0
|
61 |
+
* @access public
|
62 |
+
* @global object $wp_roles
|
63 |
+
* @return array
|
64 |
+
*/
|
65 |
+
public function get_roles() {
|
66 |
+
global $wp_roles;
|
67 |
+
|
68 |
+
$roles = array();
|
69 |
+
$type = get_post_type_object( get_post_type( $this->manager->post_id ) );
|
70 |
+
|
71 |
+
// Get the post type object caps.
|
72 |
+
$caps = array( $type->cap->edit_posts, $type->cap->publish_posts, $type->cap->create_posts );
|
73 |
+
$caps = array_unique( $caps );
|
74 |
+
|
75 |
+
// Loop through the available roles.
|
76 |
+
foreach ( $wp_roles->roles as $name => $role ) {
|
77 |
+
|
78 |
+
foreach ( $caps as $cap ) {
|
79 |
+
|
80 |
+
// If the role is granted the cap, add it.
|
81 |
+
if ( isset( $role['capabilities'][ $cap ] ) && true === $role['capabilities'][ $cap ] ) {
|
82 |
+
$roles[] = $name;
|
83 |
+
break;
|
84 |
+
}
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
return $roles;
|
89 |
+
}
|
90 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-palette.php
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Color palette control class. The purpose of this class is to give users a choice
|
4 |
+
* of color palettes. The actual data that is stored is a key of your choosing.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Color palette control class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Control_Palette extends ButterBean_Control {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* The type of control.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
public $type = 'palette';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
32 |
+
*
|
33 |
+
* @since 1.0.0
|
34 |
+
* @access public
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
public function to_json() {
|
38 |
+
parent::to_json();
|
39 |
+
|
40 |
+
$value = $this->get_value();
|
41 |
+
|
42 |
+
// Make sure the colors have a hash.
|
43 |
+
foreach ( $this->choices as $choice => $palette ) {
|
44 |
+
$this->choices[ $choice ]['colors'] = array_map( 'butterbean_maybe_hash_hex_color', $palette['colors'] );
|
45 |
+
|
46 |
+
$this->choices[ $choice ]['selected'] = $value && $choice === $value;
|
47 |
+
}
|
48 |
+
|
49 |
+
$this->json['choices'] = $this->choices;
|
50 |
+
}
|
51 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-parent.php
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Post parent control class. This class is a specialty class meant for use in unique
|
4 |
+
* scenarios where you're not using the core post parent drop-down. This is often the
|
5 |
+
* case with flat post types that have a parent post. This control is not meant to be
|
6 |
+
* used with a setting. Core WP will store the data in the `post.post_parent` field.
|
7 |
+
*
|
8 |
+
* @package ButterBean
|
9 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
10 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
11 |
+
* @link https://github.com/justintadlock/butterbean
|
12 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
13 |
+
*/
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Post parent control class.
|
17 |
+
*
|
18 |
+
* @since 1.0.0
|
19 |
+
* @access public
|
20 |
+
*/
|
21 |
+
class ButterBean_Control_Parent extends ButterBean_Control {
|
22 |
+
|
23 |
+
/**
|
24 |
+
* The type of control.
|
25 |
+
*
|
26 |
+
* @since 1.0.0
|
27 |
+
* @access public
|
28 |
+
* @var string
|
29 |
+
*/
|
30 |
+
public $type = 'parent';
|
31 |
+
|
32 |
+
/**
|
33 |
+
* The post type to select posts from.
|
34 |
+
*
|
35 |
+
* @since 1.0.0
|
36 |
+
* @access public
|
37 |
+
* @var string
|
38 |
+
*/
|
39 |
+
public $post_type = '';
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Returns the HTML field name for the control.
|
43 |
+
*
|
44 |
+
* @since 1.0.0
|
45 |
+
* @access public
|
46 |
+
* @param string $setting
|
47 |
+
* @return array
|
48 |
+
*/
|
49 |
+
public function get_field_name( $setting = 'default' ) {
|
50 |
+
|
51 |
+
return 'post_parent';
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Get the value for the setting.
|
56 |
+
*
|
57 |
+
* @since 1.0.0
|
58 |
+
* @access public
|
59 |
+
* @param string $setting
|
60 |
+
* @return mixed
|
61 |
+
*/
|
62 |
+
public function get_value( $setting = 'default' ) {
|
63 |
+
|
64 |
+
return get_post( $this->manager->post_id )->post_parent;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
69 |
+
*
|
70 |
+
* @since 1.0.0
|
71 |
+
* @access public
|
72 |
+
* @return void
|
73 |
+
*/
|
74 |
+
public function to_json() {
|
75 |
+
parent::to_json();
|
76 |
+
|
77 |
+
$posts = get_posts(
|
78 |
+
array(
|
79 |
+
'post_type' => $this->post_type ? $this->post_type : get_post_type( $this->manager->post_id ),
|
80 |
+
'post_status' => 'any',
|
81 |
+
'post__not_in' => array( $this->manager->post_id ),
|
82 |
+
'posts_per_page' => -1,
|
83 |
+
'post_parent' => 0,
|
84 |
+
'orderby' => 'title',
|
85 |
+
'order' => 'ASC',
|
86 |
+
'fields' => array( 'ID', 'post_title' )
|
87 |
+
)
|
88 |
+
);
|
89 |
+
|
90 |
+
$this->json['choices'] = array( array( 'value' => 0, 'label' => '' ) );
|
91 |
+
|
92 |
+
foreach ( $posts as $post )
|
93 |
+
$this->json['choices'][] = array( 'value' => $post->ID, 'label' => $post->post_title );
|
94 |
+
}
|
95 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-radio-image.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Radio image control class extends the built-in radio control. This control is
|
4 |
+
* meant for displaying an image instead of the radio fields.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @subpackage Admin
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Radio image control class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Control_Radio_Image extends ButterBean_Control_Radio {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of control.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'radio-image';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @return void
|
37 |
+
*/
|
38 |
+
public function to_json() {
|
39 |
+
parent::to_json();
|
40 |
+
|
41 |
+
foreach ( $this->choices as $value => $args )
|
42 |
+
$this->choices[ $value ]['url'] = esc_url( sprintf( $args['url'], get_template_directory_uri(), get_stylesheet_directory_uri() ) );
|
43 |
+
|
44 |
+
$this->json['choices'] = $this->choices;
|
45 |
+
}
|
46 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-radio.php
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Radio control class that creates a list of radio inputs to choose from.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @subpackage Admin
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Radio control class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Control_Radio extends ButterBean_Control {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* The type of control.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
public $type = 'radio';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Radio controls imply that a value should be set. Therefore, we will return
|
32 |
+
* the default if there is no value.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @param string $setting
|
37 |
+
* @return mixed
|
38 |
+
*/
|
39 |
+
public function get_value( $setting = 'default' ) {
|
40 |
+
|
41 |
+
$value = parent::get_value( $setting );
|
42 |
+
$object = $this->get_setting( $setting );
|
43 |
+
|
44 |
+
return ! $value && $object ? $object->default : $value;
|
45 |
+
}
|
46 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-select-group.php
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Select group control class. This works just like a normal select. However, it
|
4 |
+
* allows for `<optgroup>` to be added.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Select group control class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Control_Select_Group extends ButterBean_Control {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* The type of control.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
public $type = 'select-group';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Adds custom data to the json array.
|
32 |
+
*
|
33 |
+
* @since 1.0.0
|
34 |
+
* @access public
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
public function to_json() {
|
38 |
+
parent::to_json();
|
39 |
+
|
40 |
+
$choices = $group = array();
|
41 |
+
|
42 |
+
foreach ( $this->choices as $choice => $maybe_group ) {
|
43 |
+
|
44 |
+
if ( is_array( $maybe_group ) )
|
45 |
+
$group[ $choice ] = $maybe_group;
|
46 |
+
else
|
47 |
+
$choices[ $choice ] = $maybe_group;
|
48 |
+
}
|
49 |
+
|
50 |
+
$this->json['choices'] = $choices;
|
51 |
+
$this->json['group'] = $group;
|
52 |
+
}
|
53 |
+
}
|
includes/lib/butterbean/inc/controls/class-control-textarea.php
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Textarea control class.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @subpackage Admin
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Textarea control class.
|
15 |
+
*
|
16 |
+
* @since 1.0.0
|
17 |
+
* @access public
|
18 |
+
*/
|
19 |
+
class ButterBean_Control_Textarea extends ButterBean_Control {
|
20 |
+
|
21 |
+
/**
|
22 |
+
* The type of control.
|
23 |
+
*
|
24 |
+
* @since 1.0.0
|
25 |
+
* @access public
|
26 |
+
* @var string
|
27 |
+
*/
|
28 |
+
public $type = 'textarea';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Adds custom data to the json array. This data is passed to the Underscore template.
|
32 |
+
*
|
33 |
+
* @since 1.0.0
|
34 |
+
* @access public
|
35 |
+
* @return void
|
36 |
+
*/
|
37 |
+
public function to_json() {
|
38 |
+
parent::to_json();
|
39 |
+
|
40 |
+
$this->json['value'] = esc_textarea( $this->get_value() );
|
41 |
+
}
|
42 |
+
}
|
includes/lib/butterbean/inc/functions-core.php
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Helper functions.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @subpackage Admin
|
7 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
8 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
9 |
+
* @link https://github.com/justintadlock/butterbean
|
10 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Function for validating booleans before saving them as metadata. If the value is
|
15 |
+
* `true`, we'll return a `1` to be stored as the meta value. Else, we return `false`.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
* @param mixed
|
20 |
+
* @return bool|int
|
21 |
+
*/
|
22 |
+
function butterbean_validate_boolean( $value ) {
|
23 |
+
|
24 |
+
return wp_validate_boolean( $value ) ? 1 : false;
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Pre-WP 4.6 function for sanitizing hex colors.
|
29 |
+
*
|
30 |
+
* @since 1.0.0
|
31 |
+
* @access public
|
32 |
+
* @param string $color
|
33 |
+
* @return string
|
34 |
+
*/
|
35 |
+
function butterbean_sanitize_hex_color( $color ) {
|
36 |
+
|
37 |
+
if ( function_exists( 'sanitize_hex_color' ) )
|
38 |
+
return sanitize_hex_color( $color );
|
39 |
+
|
40 |
+
return $color && preg_match('|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ? $color : '';
|
41 |
+
}
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Pre-WP 4.6 function for sanitizing hex colors without a hash.
|
45 |
+
*
|
46 |
+
* @since 1.0.0
|
47 |
+
* @access public
|
48 |
+
* @param string $color
|
49 |
+
* @return string
|
50 |
+
*/
|
51 |
+
function butterbean_sanitize_hex_color_no_hash( $color ) {
|
52 |
+
|
53 |
+
if ( function_exists( 'sanitize_hex_color_no_hash' ) )
|
54 |
+
return sanitize_hex_color_no_hash( $color );
|
55 |
+
|
56 |
+
$color = ltrim( $color, '#' );
|
57 |
+
|
58 |
+
if ( '' === $color )
|
59 |
+
return '';
|
60 |
+
|
61 |
+
return butterbean_sanitize_hex_color( '#' . $color ) ? $color : null;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Pre-WP 4.6 function for sanitizing a color and adding a hash.
|
66 |
+
*
|
67 |
+
* @since 1.0.0
|
68 |
+
* @access public
|
69 |
+
* @param string $color
|
70 |
+
* @return string
|
71 |
+
*/
|
72 |
+
function butterbean_maybe_hash_hex_color( $color ) {
|
73 |
+
|
74 |
+
if ( function_exists( 'maybe_hash_hex_color' ) )
|
75 |
+
return maybe_hash_hex_color( $color );
|
76 |
+
|
77 |
+
if ( $unhashed = butterbean_sanitize_hex_color_no_hash( $color ) )
|
78 |
+
return '#' . $unhashed;
|
79 |
+
|
80 |
+
return $color;
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Gets Underscore.js templates for managers.
|
85 |
+
*
|
86 |
+
* @since 1.0.0
|
87 |
+
* @param string $slug
|
88 |
+
* @return void
|
89 |
+
*/
|
90 |
+
function butterbean_get_manager_template( $slug = '' ) {
|
91 |
+
butterbean_get_template( 'manager', $slug );
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Gets Underscore.js templates for navs.
|
96 |
+
*
|
97 |
+
* @since 1.0.0
|
98 |
+
* @param string $slug
|
99 |
+
* @return void
|
100 |
+
*/
|
101 |
+
function butterbean_get_nav_template( $slug = '' ) {
|
102 |
+
butterbean_get_template( 'nav', $slug );
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Gets Underscore.js templates for sections.
|
107 |
+
*
|
108 |
+
* @since 1.0.0
|
109 |
+
* @param string $slug
|
110 |
+
* @return void
|
111 |
+
*/
|
112 |
+
function butterbean_get_section_template( $slug = '' ) {
|
113 |
+
butterbean_get_template( 'section', $slug );
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Gets Underscore.js templates for controls.
|
118 |
+
*
|
119 |
+
* @since 1.0.0
|
120 |
+
* @param string $slug
|
121 |
+
* @return void
|
122 |
+
*/
|
123 |
+
function butterbean_get_control_template( $slug = '' ) {
|
124 |
+
butterbean_get_template( 'control', $slug );
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Helper function for getting Underscore.js templates.
|
129 |
+
*
|
130 |
+
* @since 1.0.0
|
131 |
+
* @param string $name
|
132 |
+
* @param string $slug
|
133 |
+
* @return void
|
134 |
+
*/
|
135 |
+
function butterbean_get_template( $name, $slug = '' ) {
|
136 |
+
|
137 |
+
// Allow devs to hook in early to bypass template checking.
|
138 |
+
$located = apply_filters( "butterbean_pre_{$name}_template", '', $slug );
|
139 |
+
|
140 |
+
// If there's no template, let's try to find one.
|
141 |
+
if ( ! $located ) {
|
142 |
+
|
143 |
+
$templates = array();
|
144 |
+
|
145 |
+
if ( $slug )
|
146 |
+
$templates[] = "{$name}-{$slug}.php";
|
147 |
+
|
148 |
+
$templates[] = "{$name}.php";
|
149 |
+
|
150 |
+
// Allow devs to filter the template hierarchy.
|
151 |
+
$templates = apply_filters( "butterbean_{$name}_template_hierarchy", $templates, $slug );
|
152 |
+
|
153 |
+
// Loop through the templates and locate one.
|
154 |
+
foreach ( $templates as $template ) {
|
155 |
+
|
156 |
+
if ( file_exists( butterbean()->tmpl_path . $template ) ) {
|
157 |
+
$located = butterbean()->tmpl_path . $template;
|
158 |
+
break;
|
159 |
+
}
|
160 |
+
}
|
161 |
+
}
|
162 |
+
|
163 |
+
// Allow devs to filter the final template.
|
164 |
+
$located = apply_filters( "butterbean_{$name}_template", $located, $slug );
|
165 |
+
|
166 |
+
// Load the template.
|
167 |
+
if ( $located )
|
168 |
+
require( $located );
|
169 |
+
}
|
includes/lib/butterbean/inc/settings/class-setting-array.php
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Setting class for storing a single meta value as an array.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
7 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
8 |
+
* @link https://github.com/justintadlock/butterbean
|
9 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Array setting class.
|
14 |
+
*
|
15 |
+
* @since 1.0.0
|
16 |
+
* @access public
|
17 |
+
*/
|
18 |
+
class ButterBean_Setting_Array extends ButterBean_Setting {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* The type of setting.
|
22 |
+
*
|
23 |
+
* @since 1.0.0
|
24 |
+
* @access public
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
public $type = 'array';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Sanitizes the value of the setting.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access public
|
34 |
+
* @param array $value
|
35 |
+
* @return array
|
36 |
+
*/
|
37 |
+
public function sanitize( $values ) {
|
38 |
+
|
39 |
+
$multi_values = $values && ! is_array( $values ) ? explode( ',', $values ) : $values;
|
40 |
+
|
41 |
+
return $multi_values ? array_map( array( $this, 'map' ), $multi_values ) : array();
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Helper function for sanitizing each value of the array.
|
46 |
+
*
|
47 |
+
* @since 1.0.0
|
48 |
+
* @access public
|
49 |
+
* @param mixed $value
|
50 |
+
* @return mixed
|
51 |
+
*/
|
52 |
+
public function map( $value ) {
|
53 |
+
|
54 |
+
return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this );
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Saves the value of the setting.
|
59 |
+
*
|
60 |
+
* @since 1.0.0
|
61 |
+
* @access public
|
62 |
+
* @return void
|
63 |
+
*/
|
64 |
+
public function save() {
|
65 |
+
|
66 |
+
if ( ! $this->check_capabilities() )
|
67 |
+
return;
|
68 |
+
|
69 |
+
$old_values = $this->get_value();
|
70 |
+
$new_values = $this->get_posted_value();
|
71 |
+
|
72 |
+
// If there's an array of posted values, set them.
|
73 |
+
if ( $new_values && is_array( $new_values ) && $new_values !== $old_values )
|
74 |
+
return update_post_meta( $this->manager->post_id, $this->name, $new_values );
|
75 |
+
|
76 |
+
// If no array of posted values but we have old values, delete them.
|
77 |
+
else if ( $old_values && ! $new_values )
|
78 |
+
return delete_post_meta( $this->manager->post_id, $this->name );
|
79 |
+
}
|
80 |
+
}
|
includes/lib/butterbean/inc/settings/class-setting-datetime.php
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Datetime setting class. This is meant to be used in conjunction with the built-in
|
4 |
+
* `ButterBean_Datetime_Control` or a sub-class that passes the appropriate values.
|
5 |
+
*
|
6 |
+
* @package ButterBean
|
7 |
+
* @subpackage Admin
|
8 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
9 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
10 |
+
* @link https://github.com/justintadlock/butterbean
|
11 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
12 |
+
*/
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Date setting class.
|
16 |
+
*
|
17 |
+
* @since 1.0.0
|
18 |
+
* @access public
|
19 |
+
*/
|
20 |
+
class ButterBean_Setting_Datetime extends ButterBean_Setting {
|
21 |
+
|
22 |
+
/**
|
23 |
+
* The type of setting.
|
24 |
+
*
|
25 |
+
* @since 1.0.0
|
26 |
+
* @access public
|
27 |
+
* @var string
|
28 |
+
*/
|
29 |
+
public $type = 'datetime';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Gets the posted value of the setting.
|
33 |
+
*
|
34 |
+
* @since 1.0.0
|
35 |
+
* @access public
|
36 |
+
* @return mixed
|
37 |
+
*/
|
38 |
+
public function get_posted_value() {
|
39 |
+
|
40 |
+
$field_name = $this->get_field_name();
|
41 |
+
|
42 |
+
// Get the posted date.
|
43 |
+
$year = ! empty( $_POST[ "{$field_name}_year" ] ) ? zeroise( absint( $_POST[ "{$field_name}_year" ] ), 4 ) : '';
|
44 |
+
$month = ! empty( $_POST[ "{$field_name}_month" ] ) ? zeroise( absint( $_POST[ "{$field_name}_month" ] ), 2 ) : '';
|
45 |
+
$day = ! empty( $_POST[ "{$field_name}_day" ] ) ? zeroise( absint( $_POST[ "{$field_name}_day" ] ), 2 ) : '';
|
46 |
+
|
47 |
+
// Get the posted time.
|
48 |
+
$hour = ! empty( $_POST[ "{$field_name}_hour" ] ) ? $this->validate_hour( $_POST[ "{$field_name}_hour" ] ) : '00';
|
49 |
+
$minute = ! empty( $_POST[ "{$field_name}_minute" ] ) ? $this->validate_minute( $_POST[ "{$field_name}_minute" ] ) : '00';
|
50 |
+
$second = ! empty( $_POST[ "{$field_name}_second" ] ) ? $this->validate_second( $_POST[ "{$field_name}_second" ] ) : '00';
|
51 |
+
|
52 |
+
$date = "{$year}-{$month}-{$day}";
|
53 |
+
$time = "{$hour}:{$minute}:{$second}";
|
54 |
+
|
55 |
+
if ( $year && $month && $day && wp_checkdate( absint( $month ), absint( $day ), absint( $year ), $date ) )
|
56 |
+
return "{$date} {$time}";
|
57 |
+
|
58 |
+
return '';
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Validates the hour.
|
63 |
+
*
|
64 |
+
* @since 1.0.0
|
65 |
+
* @access public
|
66 |
+
* @param int|string $hour
|
67 |
+
* @return string
|
68 |
+
*/
|
69 |
+
public function validate_hour( $hour ) {
|
70 |
+
|
71 |
+
$hour = absint( $hour );
|
72 |
+
|
73 |
+
return 0 < $hour && 24 >= $hour ? zeroise( $hour, 2 ) : '00';
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Validates the minute.
|
78 |
+
*
|
79 |
+
* @since 1.0.0
|
80 |
+
* @access public
|
81 |
+
* @param int|string $minute
|
82 |
+
* @return string
|
83 |
+
*/
|
84 |
+
public function validate_minute( $minute ) {
|
85 |
+
|
86 |
+
$minute = absint( $minute );
|
87 |
+
|
88 |
+
return 0 < $minute && 60 >= $minute ? zeroise( $minute, 2 ) : '00';
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Validates the second.
|
93 |
+
*
|
94 |
+
* @since 1.0.0
|
95 |
+
* @access public
|
96 |
+
* @param int|string $second
|
97 |
+
* @return string
|
98 |
+
*/
|
99 |
+
public function validate_second( $second ) {
|
100 |
+
|
101 |
+
$second = absint( $second );
|
102 |
+
|
103 |
+
return 0 < $second && 60 >= $second ? zeroise( $second, 2 ) : '00';
|
104 |
+
}
|
105 |
+
}
|
includes/lib/butterbean/inc/settings/class-setting-multiple.php
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Setting class for storing multiple post meta values for a single key.
|
4 |
+
*
|
5 |
+
* @package ButterBean
|
6 |
+
* @author Justin Tadlock <justin@justintadlock.com>
|
7 |
+
* @copyright Copyright (c) 2015-2016, Justin Tadlock
|
8 |
+
* @link https://github.com/justintadlock/butterbean
|
9 |
+
* @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
10 |
+
*/
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Multiple setting class.
|
14 |
+
*
|
15 |
+
* @since 1.0.0
|
16 |
+
* @access public
|
17 |
+
*/
|
18 |
+
class ButterBean_Setting_Multiple extends ButterBean_Setting {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* The type of setting.
|
22 |
+
*
|
23 |
+
* @since 1.0.0
|
24 |
+
* @access public
|
25 |
+
* @var string
|
26 |
+
*/
|
27 |
+
public $type = 'multiple';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Gets the value of the setting.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access public
|
34 |
+
* @return mixed
|
35 |
+
*/
|
36 |
+
public function get_value() {
|
37 |
+
|
38 |
+
return get_post_meta( $this->manager->post_id, $this->name );
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Sanitizes the value of the setting.
|
43 |
+
*
|
44 |
+
* @since 1.0.0
|
45 |
+
* @access public
|
46 |
+
* @param array $value
|
47 |
+
* @return array
|
48 |
+
*/
|
49 |
+
public function sanitize( $values ) {
|
50 |
+
|
51 |
+
$multi_values = $values && ! is_array( $values ) ? explode( ',', $values ) : $values;
|
52 |
+
|
53 |
+
return $multi_values ? array_map( array( $this, 'map' ), $multi_values ) : array();
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Helper function for sanitizing each value of the array.
|
58 |
+
*
|
59 |
+
* @since 1.0.0
|
60 |
+
* @access public
|
61 |
+
* @param mixed $value
|
62 |
+
* @return mixed
|
63 |
+
*/
|
64 |
+
public function map( $value ) {
|
65 |
+
|
66 |
+
return apply_filters( "butterbean_{$this->manager->name}_sanitize_{$this->name}", $value, $this );
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Saves the value of the setting.
|
71 |
+
*
|
72 |
+
* @since 1.0.0
|
73 |
+
* @access public
|
74 |
+
* @return void
|
75 |
+
*/
|
76 |
+
public function save() {
|
77 |
+
|
78 |
+
if ( ! $this->check_capabilities() )
|
79 |
+
return;
|
80 |
+
|
81 |
+
$old_values = $this->get_value();
|
82 |
+
$new_values = $this->get_posted_value();
|
83 |
+
|
84 |
+
// If there's an array of posted values, set them.
|
85 |
+
if ( is_array( $new_values ) )
|
86 |
+
$this->set_values( $new_values, $old_values );
|
87 |
+
|
88 |
+
// If no array of posted values but we have old values, delete them.
|
89 |
+
else if ( $old_values )
|
90 |
+
$this->delete_values();
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Loops through new and old meta values and updates.
|
95 |
+
*
|
96 |
+
* @since 1.0.0
|
97 |
+
* @access public
|
98 |
+
* @param array $new_values
|
99 |
+
* @param array $old_values
|
100 |
+
* @return void
|
101 |
+
*/
|
102 |
+
public function set_values( $new_values, $old_values ) {
|
103 |
+
|
104 |
+
foreach ( $new_values as $new ) {
|
105 |
+
|
106 |
+
if ( ! in_array( $new, $old_values ) )
|
107 |
+
$this->add_value( $new );
|
108 |
+
}
|
109 |
+
|
110 |
+
foreach ( $old_values as $old ) {
|
111 |
+
|
112 |
+
if ( ! in_array( $old, $new_values ) )
|
113 |
+
$this->remove_value( $old );
|
114 |
+
}
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Deletes old meta values.
|
119 |
+
*
|
120 |
+
* @since 1.0.0
|
121 |
+
* @access public
|
122 |
+
* @return void
|
123 |
+
*/
|
124 |
+
public function delete_values() {
|
125 |
+
|
126 |
+
return delete_post_meta( $this->manager->post_id, $this->name );
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
* Adds a single meta value.
|
131 |
+
*
|
132 |
+
* @since 1.0.0
|
133 |
+
* @access public
|
134 |
+
* @param mixed $value
|
135 |
+
* @return bool
|
136 |
+
*/
|
137 |
+
public function add_value( $value ) {
|
138 |
+
|
139 |
+
return add_post_meta( $this->manager->post_id, $this->name, $value, false );
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Deletes a single meta value.
|
144 |
+
*
|
145 |
+
* @since 1.0.0
|
146 |
+
* @access public
|
147 |
+
* @param mixed $value
|
148 |
+
* @return bool
|
149 |
+
*/
|
150 |
+
public function remove_value( $value ) {
|
151 |
+
|
152 |
+
return delete_post_meta( $this->manager->post_id, $this->name, $value );
|
153 |
+
}
|
154 |
+
}
|
includes/lib/butterbean/js/butterbean.js
ADDED
@@ -0,0 +1,1062 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
window.butterbean = window.butterbean || {};
|
2 |
+
|
3 |
+
( function() {
|
4 |
+
|
5 |
+
// Bail if we don't have the JSON, which is passed in via `wp_localize_script()`.
|
6 |
+
if ( _.isUndefined( butterbean_data ) ) {
|
7 |
+
return;
|
8 |
+
}
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Our global object. The `butterbean` object is just a wrapper to house everything
|
12 |
+
* in a single namespace.
|
13 |
+
*
|
14 |
+
* @since 1.0.0
|
15 |
+
* @access public
|
16 |
+
* @var object
|
17 |
+
*/
|
18 |
+
var api = butterbean = {
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Houses the manager, section, and control models based on `name`.
|
22 |
+
*
|
23 |
+
* @since 1.1.0
|
24 |
+
* @access public
|
25 |
+
* @var object
|
26 |
+
*/
|
27 |
+
models : { managers : {}, sections : {}, controls : {} },
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Houses the manager, section, and control views based on the `type`.
|
31 |
+
*
|
32 |
+
* @since 1.0.0
|
33 |
+
* @access public
|
34 |
+
* @var object
|
35 |
+
*/
|
36 |
+
views : { managers : {}, sections : {}, controls : {} },
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Houses the manager, section, and control templates based on the `type`.
|
40 |
+
*
|
41 |
+
* @since 1.0.0
|
42 |
+
* @access public
|
43 |
+
* @var object
|
44 |
+
*/
|
45 |
+
templates : { managers : {}, sections : {}, controls : {} }
|
46 |
+
};
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Creates a new manager model.
|
50 |
+
*
|
51 |
+
* @since 1.0.0
|
52 |
+
* @access public
|
53 |
+
* @param string $name
|
54 |
+
* @param object $data
|
55 |
+
* @return void
|
56 |
+
*/
|
57 |
+
api.models.register_manager = function( name, data ) {
|
58 |
+
|
59 |
+
this.managers[ name ] = new Manager( data );
|
60 |
+
};
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Gets a manager model.
|
64 |
+
*
|
65 |
+
* @since 1.0.0
|
66 |
+
* @access public
|
67 |
+
* @param string $name
|
68 |
+
* @return object
|
69 |
+
*/
|
70 |
+
api.models.get_manager = function( name ) {
|
71 |
+
|
72 |
+
return this.manager_exists( name ) ? this.managers[ name ] : false;
|
73 |
+
};
|
74 |
+
|
75 |
+
/**
|
76 |
+
* Unregisters a manager model.
|
77 |
+
*
|
78 |
+
* @since 1.0.0
|
79 |
+
* @access public
|
80 |
+
* @param string $name
|
81 |
+
* @param object $data
|
82 |
+
* @return void
|
83 |
+
*/
|
84 |
+
api.models.unregister_manager = function( name ) {
|
85 |
+
|
86 |
+
if ( this.manager_exists( name ) )
|
87 |
+
delete this.managers[ name ];
|
88 |
+
};
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Checks if a manager model exists.
|
92 |
+
*
|
93 |
+
* @since 1.0.0
|
94 |
+
* @access public
|
95 |
+
* @param string $name
|
96 |
+
* @param object $data
|
97 |
+
* @return void
|
98 |
+
*/
|
99 |
+
api.models.manager_exists = function( name ) {
|
100 |
+
|
101 |
+
return this.managers.hasOwnProperty( name );
|
102 |
+
};
|
103 |
+
|
104 |
+
/**
|
105 |
+
* Creates a new section model.
|
106 |
+
*
|
107 |
+
* @since 1.0.0
|
108 |
+
* @access public
|
109 |
+
* @param string $name
|
110 |
+
* @param object $data
|
111 |
+
* @return void
|
112 |
+
*/
|
113 |
+
api.models.register_section = function( name, data ) {
|
114 |
+
|
115 |
+
this.sections[ name ] = new Section( data );
|
116 |
+
};
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Gets a section model.
|
120 |
+
*
|
121 |
+
* @since 1.0.0
|
122 |
+
* @access public
|
123 |
+
* @param string $name
|
124 |
+
* @return object
|
125 |
+
*/
|
126 |
+
api.models.get_section = function( name ) {
|
127 |
+
|
128 |
+
return this.section_exists( name ) ? this.sections[ name ] : false;
|
129 |
+
};
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Unregisters a section model.
|
133 |
+
*
|
134 |
+
* @since 1.0.0
|
135 |
+
* @access public
|
136 |
+
* @param string $name
|
137 |
+
* @param object $data
|
138 |
+
* @return void
|
139 |
+
*/
|
140 |
+
api.models.unregister_section = function( name ) {
|
141 |
+
|
142 |
+
if ( this.section_exists( name ) )
|
143 |
+
delete this.sections[ name ];
|
144 |
+
};
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Checks if a section model exists.
|
148 |
+
*
|
149 |
+
* @since 1.0.0
|
150 |
+
* @access public
|
151 |
+
* @param string $name
|
152 |
+
* @param object $data
|
153 |
+
* @return void
|
154 |
+
*/
|
155 |
+
api.models.section_exists = function( name ) {
|
156 |
+
|
157 |
+
return this.sections.hasOwnProperty( name );
|
158 |
+
};
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Creates a new control model.
|
162 |
+
*
|
163 |
+
* @since 1.0.0
|
164 |
+
* @access public
|
165 |
+
* @param string $name
|
166 |
+
* @param object $data
|
167 |
+
* @return void
|
168 |
+
*/
|
169 |
+
api.models.register_control = function( name, data ) {
|
170 |
+
|
171 |
+
this.controls[ name ] = new Control( data );
|
172 |
+
};
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Gets a control model.
|
176 |
+
*
|
177 |
+
* @since 1.0.0
|
178 |
+
* @access public
|
179 |
+
* @param string $name
|
180 |
+
* @return object
|
181 |
+
*/
|
182 |
+
api.models.get_control = function( name ) {
|
183 |
+
|
184 |
+
return this.control_exists( name ) ? this.controls[ name ] : false;
|
185 |
+
};
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Unregisters a control model.
|
189 |
+
*
|
190 |
+
* @since 1.0.0
|
191 |
+
* @access public
|
192 |
+
* @param string $name
|
193 |
+
* @param object $data
|
194 |
+
* @return void
|
195 |
+
*/
|
196 |
+
api.models.unregister_control = function( name ) {
|
197 |
+
|
198 |
+
if ( this.control_exists( name ) )
|
199 |
+
delete this.controls[ name ];
|
200 |
+
};
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Checks if a control model exists.
|
204 |
+
*
|
205 |
+
* @since 1.0.0
|
206 |
+
* @access public
|
207 |
+
* @param string $name
|
208 |
+
* @param object $data
|
209 |
+
* @return void
|
210 |
+
*/
|
211 |
+
api.models.control_exists = function( name ) {
|
212 |
+
|
213 |
+
return this.controls.hasOwnProperty( name );
|
214 |
+
};
|
215 |
+
|
216 |
+
/**
|
217 |
+
* Creates a new manager view.
|
218 |
+
*
|
219 |
+
* @since 1.0.0
|
220 |
+
* @access public
|
221 |
+
* @param string $type
|
222 |
+
* @param object $args
|
223 |
+
* @return void
|
224 |
+
*/
|
225 |
+
api.views.register_manager = function( type, args ) {
|
226 |
+
|
227 |
+
if ( 'default' !== type )
|
228 |
+
this.managers[ type ] = this.managers.default.extend( args );
|
229 |
+
};
|
230 |
+
|
231 |
+
/**
|
232 |
+
* Returns a manager view.
|
233 |
+
*
|
234 |
+
* @since 1.0.0
|
235 |
+
* @access public
|
236 |
+
* @param string $type
|
237 |
+
* @return object
|
238 |
+
*/
|
239 |
+
api.views.get_manager = function( type ) {
|
240 |
+
|
241 |
+
if ( this.manager_exists( type ) )
|
242 |
+
return this.managers[ type ];
|
243 |
+
|
244 |
+
return this.managers.default;
|
245 |
+
};
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Removes a manager view.
|
249 |
+
*
|
250 |
+
* @since 1.0.0
|
251 |
+
* @access public
|
252 |
+
* @param string $type
|
253 |
+
* @return void
|
254 |
+
*/
|
255 |
+
api.views.unregister_manager = function( type ) {
|
256 |
+
|
257 |
+
if ( 'default' !== type && this.manager_exists( type ) )
|
258 |
+
delete this.managers[ type ];
|
259 |
+
};
|
260 |
+
|
261 |
+
/**
|
262 |
+
* Checks if a manager view exists.
|
263 |
+
*
|
264 |
+
* @since 1.0.0
|
265 |
+
* @access public
|
266 |
+
* @param string $type
|
267 |
+
* @return bool
|
268 |
+
*/
|
269 |
+
api.views.manager_exists = function( type ) {
|
270 |
+
|
271 |
+
return this.managers.hasOwnProperty( type );
|
272 |
+
};
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Creates a new section view.
|
276 |
+
*
|
277 |
+
* @since 1.0.0
|
278 |
+
* @access public
|
279 |
+
* @param string $type
|
280 |
+
* @param object $args
|
281 |
+
* @return void
|
282 |
+
*/
|
283 |
+
api.views.register_section = function( type, args ) {
|
284 |
+
|
285 |
+
if ( 'default' !== type )
|
286 |
+
this.sections[ type ] = this.sections.default.extend( args );
|
287 |
+
};
|
288 |
+
|
289 |
+
/**
|
290 |
+
* Returns a section view.
|
291 |
+
*
|
292 |
+
* @since 1.0.0
|
293 |
+
* @access public
|
294 |
+
* @param string $type
|
295 |
+
* @return object
|
296 |
+
*/
|
297 |
+
api.views.get_section = function( type ) {
|
298 |
+
|
299 |
+
if ( this.section_exists( type ) )
|
300 |
+
return this.sections[ type ];
|
301 |
+
|
302 |
+
return this.sections.default;
|
303 |
+
};
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Removes a section view.
|
307 |
+
*
|
308 |
+
* @since 1.0.0
|
309 |
+
* @access public
|
310 |
+
* @param string $type
|
311 |
+
* @return void
|
312 |
+
*/
|
313 |
+
api.views.unregister_section = function( type ) {
|
314 |
+
|
315 |
+
if ( 'default' !== type && this.section_exists( type ) )
|
316 |
+
delete this.sections[ type ];
|
317 |
+
};
|
318 |
+
|
319 |
+
/**
|
320 |
+
* Checks if a section view exists.
|
321 |
+
*
|
322 |
+
* @since 1.0.0
|
323 |
+
* @access public
|
324 |
+
* @param string $type
|
325 |
+
* @return bool
|
326 |
+
*/
|
327 |
+
api.views.section_exists = function( type ) {
|
328 |
+
|
329 |
+
return this.sections.hasOwnProperty( type );
|
330 |
+
};
|
331 |
+
|
332 |
+
/**
|
333 |
+
* Creates a new control view.
|
334 |
+
*
|
335 |
+
* @since 1.0.0
|
336 |
+
* @access public
|
337 |
+
* @param string $type
|
338 |
+
* @param object $args
|
339 |
+
* @return void
|
340 |
+
*/
|
341 |
+
api.views.register_control = function( type, args ) {
|
342 |
+
|
343 |
+
if ( 'default' !== type )
|
344 |
+
this.controls[ type ] = this.controls.default.extend( args );
|
345 |
+
};
|
346 |
+
|
347 |
+
/**
|
348 |
+
* Returns a control view.
|
349 |
+
*
|
350 |
+
* @since 1.0.0
|
351 |
+
* @access public
|
352 |
+
* @param string $type
|
353 |
+
* @return object
|
354 |
+
*/
|
355 |
+
api.views.get_control = function( type ) {
|
356 |
+
|
357 |
+
if ( this.control_exists( type ) )
|
358 |
+
return this.controls[ type ];
|
359 |
+
|
360 |
+
return this.controls.default;
|
361 |
+
};
|
362 |
+
|
363 |
+
/**
|
364 |
+
* Removes a control view.
|
365 |
+
*
|
366 |
+
* @since 1.0.0
|
367 |
+
* @access public
|
368 |
+
* @param string $type
|
369 |
+
* @return void
|
370 |
+
*/
|
371 |
+
api.views.unregister_control = function( type ) {
|
372 |
+
|
373 |
+
if ( 'default' !== type && this.control_exists( type ) )
|
374 |
+
delete this.controls[ type ];
|
375 |
+
};
|
376 |
+
|
377 |
+
/**
|
378 |
+
* Checks if a control view exists.
|
379 |
+
*
|
380 |
+
* @since 1.0.0
|
381 |
+
* @access public
|
382 |
+
* @param string $type
|
383 |
+
* @return bool
|
384 |
+
*/
|
385 |
+
api.views.control_exists = function( type ) {
|
386 |
+
|
387 |
+
return this.controls.hasOwnProperty( type );
|
388 |
+
};
|
389 |
+
|
390 |
+
/**
|
391 |
+
* Creates a new manager template.
|
392 |
+
*
|
393 |
+
* @since 1.0.0
|
394 |
+
* @access public
|
395 |
+
* @param string $type
|
396 |
+
* @param object $args
|
397 |
+
* @return void
|
398 |
+
*/
|
399 |
+
api.templates.register_manager = function( type ) {
|
400 |
+
|
401 |
+
this.managers[ type ] = wp.template( 'butterbean-manager-' + type );
|
402 |
+
};
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Returns a manager template.
|
406 |
+
*
|
407 |
+
* @since 1.0.0
|
408 |
+
* @access public
|
409 |
+
* @param string $type
|
410 |
+
* @return function
|
411 |
+
*/
|
412 |
+
api.templates.get_manager = function( type ) {
|
413 |
+
|
414 |
+
return this.manager_exists( type ) ? this.managers[ type ] : false;
|
415 |
+
};
|
416 |
+
|
417 |
+
/**
|
418 |
+
* Removes a manager template.
|
419 |
+
*
|
420 |
+
* @since 1.0.0
|
421 |
+
* @access public
|
422 |
+
* @param string $type
|
423 |
+
* @return void
|
424 |
+
*/
|
425 |
+
api.templates.unregister_manager = function( type ) {
|
426 |
+
|
427 |
+
if ( this.manager_exists( type ) )
|
428 |
+
delete this.managers[ type ];
|
429 |
+
};
|
430 |
+
|
431 |
+
/**
|
432 |
+
* Checks if a manager template exists.
|
433 |
+
*
|
434 |
+
* @since 1.0.0
|
435 |
+
* @access public
|
436 |
+
* @param string $type
|
437 |
+
* @return bool
|
438 |
+
*/
|
439 |
+
api.templates.manager_exists = function( type ) {
|
440 |
+
|
441 |
+
return this.managers.hasOwnProperty( type );
|
442 |
+
};
|
443 |
+
|
444 |
+
/**
|
445 |
+
* Creates a new section template.
|
446 |
+
*
|
447 |
+
* @since 1.0.0
|
448 |
+
* @access public
|
449 |
+
* @param string $type
|
450 |
+
* @param object $args
|
451 |
+
* @return void
|
452 |
+
*/
|
453 |
+
api.templates.register_section = function( type ) {
|
454 |
+
|
455 |
+
this.sections[ type ] = wp.template( 'butterbean-section-' + type );
|
456 |
+
};
|
457 |
+
|
458 |
+
/**
|
459 |
+
* Returns a section template.
|
460 |
+
*
|
461 |
+
* @since 1.0.0
|
462 |
+
* @access public
|
463 |
+
* @param string $type
|
464 |
+
* @return function
|
465 |
+
*/
|
466 |
+
api.templates.get_section = function( type ) {
|
467 |
+
|
468 |
+
return this.section_exists( type ) ? this.sections[ type ] : false;
|
469 |
+
};
|
470 |
+
|
471 |
+
/**
|
472 |
+
* Removes a section template.
|
473 |
+
*
|
474 |
+
* @since 1.0.0
|
475 |
+
* @access public
|
476 |
+
* @param string $type
|
477 |
+
* @return void
|
478 |
+
*/
|
479 |
+
api.templates.unregister_section = function( type ) {
|
480 |
+
|
481 |
+
if ( this.section_exists( type ) )
|
482 |
+
delete this.sections[ type ];
|
483 |
+
};
|
484 |
+
|
485 |
+
/**
|
486 |
+
* Checks if a section template exists.
|
487 |
+
*
|
488 |
+
* @since 1.0.0
|
489 |
+
* @access public
|
490 |
+
* @param string $type
|
491 |
+
* @return bool
|
492 |
+
*/
|
493 |
+
api.templates.section_exists = function( type ) {
|
494 |
+
|
495 |
+
return this.sections.hasOwnProperty( type );
|
496 |
+
};
|
497 |
+
|
498 |
+
/**
|
499 |
+
* Creates a new control template.
|
500 |
+
*
|
501 |
+
* @since 1.0.0
|
502 |
+
* @access public
|
503 |
+
* @param string $type
|
504 |
+
* @param object $args
|
505 |
+
* @return void
|
506 |
+
*/
|
507 |
+
api.templates.register_control = function( type ) {
|
508 |
+
|
509 |
+
this.controls[ type ] = wp.template( 'butterbean-control-' + type );
|
510 |
+
};
|
511 |
+
|
512 |
+
/**
|
513 |
+
* Returns a control template.
|
514 |
+
*
|
515 |
+
* @since 1.0.0
|
516 |
+
* @access public
|
517 |
+
* @param string $type
|
518 |
+
* @return function
|
519 |
+
*/
|
520 |
+
api.templates.get_control = function( type ) {
|
521 |
+
|
522 |
+
return this.control_exists( type ) ? this.controls[ type ] : false;
|
523 |
+
};
|
524 |
+
|
525 |
+
/**
|
526 |
+
* Removes a control template.
|
527 |
+
*
|
528 |
+
* @since 1.0.0
|
529 |
+
* @access public
|
530 |
+
* @param string $type
|
531 |
+
* @return void
|
532 |
+
*/
|
533 |
+
api.templates.unregister_control = function( type ) {
|
534 |
+
|
535 |
+
if ( this.control_exists( type ) )
|
536 |
+
delete this.controls[ type ];
|
537 |
+
};
|
538 |
+
|
539 |
+
/**
|
540 |
+
* Checks if a control template exists.
|
541 |
+
*
|
542 |
+
* @since 1.0.0
|
543 |
+
* @access public
|
544 |
+
* @param string $type
|
545 |
+
* @return bool
|
546 |
+
*/
|
547 |
+
api.templates.control_exists = function( type ) {
|
548 |
+
|
549 |
+
return this.controls.hasOwnProperty( type );
|
550 |
+
};
|
551 |
+
|
552 |
+
/**
|
553 |
+
* Renders our managers, sections, and controls.
|
554 |
+
*
|
555 |
+
* @since 1.0.0
|
556 |
+
* @access private
|
557 |
+
* @return void
|
558 |
+
*/
|
559 |
+
api.render = function() {
|
560 |
+
|
561 |
+
// Loop through each of the managers and render their api.views.
|
562 |
+
_.each( butterbean_data.managers, function( data ) {
|
563 |
+
|
564 |
+
// Create a new manager model with the JSON data for the manager.
|
565 |
+
api.models.register_manager( data.name, data );
|
566 |
+
|
567 |
+
// Get the model.
|
568 |
+
var manager = api.models.get_manager( data.name );
|
569 |
+
|
570 |
+
// Get the manager view callback.
|
571 |
+
var callback = api.views.get_manager( manager.get( 'type' ) );
|
572 |
+
|
573 |
+
// Create a new manager view.
|
574 |
+
var view = new callback( { model : manager } );
|
575 |
+
|
576 |
+
// Get the meta box element.
|
577 |
+
var metabox = document.getElementById( 'butterbean-ui-' + manager.get( 'name' ) );
|
578 |
+
|
579 |
+
// Add the `.butterbean-ui` class to the meta box.
|
580 |
+
metabox.className += ' butterbean-ui';
|
581 |
+
|
582 |
+
// Render the manager view.
|
583 |
+
metabox.querySelector( '.inside' ).appendChild( view.render().el );
|
584 |
+
|
585 |
+
// Render the manager subviews.
|
586 |
+
view.subview_render();
|
587 |
+
|
588 |
+
// Call the view's ready method.
|
589 |
+
view.ready();
|
590 |
+
} );
|
591 |
+
};
|
592 |
+
|
593 |
+
/* === Templates === */
|
594 |
+
|
595 |
+
// Nav template.
|
596 |
+
var nav_template = wp.template( 'butterbean-nav' );
|
597 |
+
|
598 |
+
/* === Models === */
|
599 |
+
|
600 |
+
// Manager model (each manager is housed within a meta box).
|
601 |
+
var Manager = Backbone.Model.extend( {
|
602 |
+
defaults : {
|
603 |
+
name : '',
|
604 |
+
type : '',
|
605 |
+
sections : {},
|
606 |
+
controls : {}
|
607 |
+
}
|
608 |
+
} );
|
609 |
+
|
610 |
+
// Section model (each section belongs to a manager).
|
611 |
+
var Section = Backbone.Model.extend( {
|
612 |
+
defaults : {
|
613 |
+
name : '',
|
614 |
+
type : '',
|
615 |
+
label : '',
|
616 |
+
description : '',
|
617 |
+
icon : '',
|
618 |
+
manager : '',
|
619 |
+
active : '',
|
620 |
+
selected : false
|
621 |
+
}
|
622 |
+
} );
|
623 |
+
|
624 |
+
// Control model (each control belongs to a manager and section).
|
625 |
+
var Control = Backbone.Model.extend( {
|
626 |
+
defaults : {
|
627 |
+
name : '',
|
628 |
+
type : '',
|
629 |
+
label : '',
|
630 |
+
description : '',
|
631 |
+
icon : '',
|
632 |
+
value : '',
|
633 |
+
choices : {},
|
634 |
+
attr : '',
|
635 |
+
active : '',
|
636 |
+
manager : '',
|
637 |
+
section : '',
|
638 |
+
setting : ''
|
639 |
+
}
|
640 |
+
} );
|
641 |
+
|
642 |
+
/* === Collections === */
|
643 |
+
|
644 |
+
/**
|
645 |
+
* Stores our collection of section models.
|
646 |
+
*
|
647 |
+
* @since 1.0.0
|
648 |
+
* @access private
|
649 |
+
* @var object
|
650 |
+
*/
|
651 |
+
var Sections = Backbone.Collection.extend( {
|
652 |
+
model : Section
|
653 |
+
} );
|
654 |
+
|
655 |
+
/* === Views === */
|
656 |
+
|
657 |
+
/**
|
658 |
+
* The default manager view. Other views can extend this using the
|
659 |
+
* `butterbean.views.register_manager()` function.
|
660 |
+
*
|
661 |
+
* @since 1.0.0
|
662 |
+
* @access public
|
663 |
+
* @var object
|
664 |
+
*/
|
665 |
+
api.views.managers[ 'default' ] = Backbone.View.extend( {
|
666 |
+
|
667 |
+
// Wrapper element for the manager view.
|
668 |
+
tagName : 'div',
|
669 |
+
|
670 |
+
// Adds some custom attributes to the wrapper.
|
671 |
+
attributes : function() {
|
672 |
+
return {
|
673 |
+
'id' : 'butterbean-manager-' + this.model.get( 'name' ),
|
674 |
+
'class' : 'butterbean-manager butterbean-manager-' + this.model.get( 'type' )
|
675 |
+
};
|
676 |
+
},
|
677 |
+
|
678 |
+
// Initializes the view.
|
679 |
+
initialize : function() {
|
680 |
+
|
681 |
+
var type = this.model.get( 'type' );
|
682 |
+
|
683 |
+
// If there's not yet a template for this manager type, create it.
|
684 |
+
if ( ! api.templates.manager_exists( type ) )
|
685 |
+
api.templates.register_manager( type );
|
686 |
+
|
687 |
+
// Get the manager template.
|
688 |
+
this.template = api.templates.get_manager( type );
|
689 |
+
},
|
690 |
+
|
691 |
+
// Renders the manager.
|
692 |
+
render : function() {
|
693 |
+
this.el.innerHTML = this.template( this.model.toJSON() );
|
694 |
+
return this;
|
695 |
+
},
|
696 |
+
|
697 |
+
// Renders the manager's sections and controls.
|
698 |
+
// Important! This may change drastically in the future, possibly even
|
699 |
+
// taken out of the manager view altogether. It's for this reason that
|
700 |
+
// it's not recommended to create custom views for managers right now.
|
701 |
+
subview_render : function() {
|
702 |
+
|
703 |
+
// Create a new section collection.
|
704 |
+
var sections = new Sections();
|
705 |
+
|
706 |
+
// Loop through each section and add it to the collection.
|
707 |
+
_.each( this.model.get( 'sections' ), function( data ) {
|
708 |
+
|
709 |
+
// Create a new section model.
|
710 |
+
api.models.register_section( data.name, data );
|
711 |
+
|
712 |
+
// Add the section model to the collection.
|
713 |
+
sections.add( api.models.get_section( data.name ) );
|
714 |
+
} );
|
715 |
+
|
716 |
+
// Loop through each section in the collection and render its view.
|
717 |
+
sections.forEach( function( section, i ) {
|
718 |
+
|
719 |
+
// Create a new nav item view for the section.
|
720 |
+
var nav_view = new Nav_View( { model : section } );
|
721 |
+
|
722 |
+
// Render the nav item view.
|
723 |
+
document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-nav' ).appendChild( nav_view.render().el );
|
724 |
+
|
725 |
+
// Get the section view callback.
|
726 |
+
var callback = api.views.get_section( section.get( 'type' ) );
|
727 |
+
|
728 |
+
// Create a new section view.
|
729 |
+
var view = new callback( { model : section } );
|
730 |
+
|
731 |
+
// Render the section view.
|
732 |
+
document.querySelector( '#butterbean-ui-' + section.get( 'manager' ) + ' .butterbean-content' ).appendChild( view.render().el );
|
733 |
+
|
734 |
+
// Call the section view's ready method.
|
735 |
+
view.ready();
|
736 |
+
|
737 |
+
// If the first model, set it to selected.
|
738 |
+
section.set( 'selected', 0 === i );
|
739 |
+
}, this );
|
740 |
+
|
741 |
+
// Loop through each control for the manager and render its view.
|
742 |
+
_.each( this.model.get( 'controls' ), function( data ) {
|
743 |
+
|
744 |
+
// Create a new control model.
|
745 |
+
api.models.register_control( data.name, data );
|
746 |
+
|
747 |
+
// Get the control model
|
748 |
+
var control = api.models.get_control( data.name );
|
749 |
+
|
750 |
+
// Get the control view callback.
|
751 |
+
var callback = api.views.get_control( control.get( 'type' ) );
|
752 |
+
|
753 |
+
// Create a new control view.
|
754 |
+
var view = new callback( { model : control } );
|
755 |
+
|
756 |
+
// Render the view.
|
757 |
+
document.getElementById( 'butterbean-' + control.get( 'manager' ) + '-section-' + control.get( 'section' ) ).appendChild( view.render().el );
|
758 |
+
|
759 |
+
// Call the view's ready method.
|
760 |
+
view.ready();
|
761 |
+
} );
|
762 |
+
|
763 |
+
return this;
|
764 |
+
},
|
765 |
+
|
766 |
+
// Function that is executed *after* the view has been rendered.
|
767 |
+
// This is meant to be overwritten in sub-views.
|
768 |
+
ready : function() {}
|
769 |
+
} );
|
770 |
+
|
771 |
+
/**
|
772 |
+
* The default section view. Other views can extend this using the
|
773 |
+
* `butterbean.views.register_section()` function.
|
774 |
+
*
|
775 |
+
* @since 1.0.0
|
776 |
+
* @access public
|
777 |
+
* @var object
|
778 |
+
*/
|
779 |
+
api.views.sections[ 'default' ] = Backbone.View.extend( {
|
780 |
+
|
781 |
+
// Wrapper element for the section.
|
782 |
+
tagName : 'div',
|
783 |
+
|
784 |
+
// Adds custom attributes for the section wrapper.
|
785 |
+
attributes : function() {
|
786 |
+
return {
|
787 |
+
'id' : 'butterbean-' + this.model.get( 'manager' ) + '-section-' + this.model.get( 'name' ),
|
788 |
+
'class' : 'butterbean-section butterbean-section-' + this.model.get( 'type' ),
|
789 |
+
'aria-hidden' : ! this.model.get( 'selected' )
|
790 |
+
};
|
791 |
+
},
|
792 |
+
|
793 |
+
// Initializes the view.
|
794 |
+
initialize : function() {
|
795 |
+
|
796 |
+
// Add an event for when the model changes.
|
797 |
+
this.model.on( 'change', this.onchange, this );
|
798 |
+
|
799 |
+
// Get the section type.
|
800 |
+
var type = this.model.get( 'type' );
|
801 |
+
|
802 |
+
// If there's no template for this section type, create it.
|
803 |
+
if ( ! api.templates.section_exists( type ) )
|
804 |
+
api.templates.register_section( type );
|
805 |
+
|
806 |
+
// Gets the section template.
|
807 |
+
this.template = api.templates.get_section( type );
|
808 |
+
},
|
809 |
+
|
810 |
+
// Renders the section.
|
811 |
+
render : function() {
|
812 |
+
|
813 |
+
// Only render template if model is active.
|
814 |
+
if ( this.model.get( 'active' ) )
|
815 |
+
this.el.innerHTML = this.template( this.model.toJSON() );
|
816 |
+
|
817 |
+
return this;
|
818 |
+
},
|
819 |
+
|
820 |
+
// Executed when the model changes.
|
821 |
+
onchange : function() {
|
822 |
+
|
823 |
+
// Set the view's `aria-hidden` attribute based on whether the model is selected.
|
824 |
+
this.el.setAttribute( 'aria-hidden', ! this.model.get( 'selected' ) );
|
825 |
+
},
|
826 |
+
|
827 |
+
// Function that is executed *after* the view has been rendered.
|
828 |
+
// This is meant to be overwritten in sub-views.
|
829 |
+
ready : function() {}
|
830 |
+
} );
|
831 |
+
|
832 |
+
/**
|
833 |
+
* The nav item view for each section.
|
834 |
+
*
|
835 |
+
* @since 1.0.0
|
836 |
+
* @access public
|
837 |
+
* @var object
|
838 |
+
*/
|
839 |
+
var Nav_View = Backbone.View.extend( {
|
840 |
+
|
841 |
+
// Sets the template used.
|
842 |
+
template : nav_template,
|
843 |
+
|
844 |
+
// Wrapper element for the nav item.
|
845 |
+
tagName : 'li',
|
846 |
+
|
847 |
+
// Sets some custom attributes for the nav item wrapper.
|
848 |
+
attributes : function() {
|
849 |
+
return {
|
850 |
+
'aria-selected' : this.model.get( 'selected' )
|
851 |
+
};
|
852 |
+
},
|
853 |
+
|
854 |
+
// Initializes the nav item view.
|
855 |
+
initialize : function() {
|
856 |
+
this.model.on( 'change', this.render, this );
|
857 |
+
this.model.on( 'change', this.onchange, this );
|
858 |
+
},
|
859 |
+
|
860 |
+
// Renders the nav item.
|
861 |
+
render : function() {
|
862 |
+
|
863 |
+
// Only render template if model is active.
|
864 |
+
if ( this.model.get( 'active' ) )
|
865 |
+
this.el.innerHTML = this.template( this.model.toJSON() );
|
866 |
+
|
867 |
+
return this;
|
868 |
+
},
|
869 |
+
|
870 |
+
// Custom events.
|
871 |
+
events : {
|
872 |
+
'click a' : 'onselect'
|
873 |
+
},
|
874 |
+
|
875 |
+
// Executed when the section model changes.
|
876 |
+
onchange : function() {
|
877 |
+
|
878 |
+
// Set the `aria-selected` attibute based on the model selected state.
|
879 |
+
this.el.setAttribute( 'aria-selected', this.model.get( 'selected' ) );
|
880 |
+
},
|
881 |
+
|
882 |
+
// Executed when the link for the nav item is clicked.
|
883 |
+
onselect : function( event ) {
|
884 |
+
event.preventDefault();
|
885 |
+
|
886 |
+
// Loop through each of the models in the collection and set them to inactive.
|
887 |
+
_.each( this.model.collection.models, function( m ) {
|
888 |
+
|
889 |
+
m.set( 'selected', false );
|
890 |
+
}, this );
|
891 |
+
|
892 |
+
// Set this view's model to selected.
|
893 |
+
this.model.set( 'selected', true );
|
894 |
+
}
|
895 |
+
} );
|
896 |
+
|
897 |
+
/**
|
898 |
+
* The default control view. Other views can extend this using the
|
899 |
+
* `butterbean.views.register_control()` function.
|
900 |
+
*
|
901 |
+
* @since 1.0.0
|
902 |
+
* @access public
|
903 |
+
* @var object
|
904 |
+
*/
|
905 |
+
api.views.controls[ 'default' ] = Backbone.View.extend( {
|
906 |
+
|
907 |
+
// Wrapper element for the control.
|
908 |
+
tagName : 'div',
|
909 |
+
|
910 |
+
// Custom attributes for the control wrapper.
|
911 |
+
attributes : function() {
|
912 |
+
return {
|
913 |
+
'id' : 'butterbean-control-' + this.model.get( 'name' ),
|
914 |
+
'class' : 'butterbean-control butterbean-control-' + this.model.get( 'type' )
|
915 |
+
};
|
916 |
+
},
|
917 |
+
|
918 |
+
// Initiazlies the control view.
|
919 |
+
initialize : function() {
|
920 |
+
var type = this.model.get( 'type' );
|
921 |
+
|
922 |
+
// Only add a new control template if we have a different control type.
|
923 |
+
if ( ! api.templates.control_exists( type ) )
|
924 |
+
api.templates.register_control( type );
|
925 |
+
|
926 |
+
// Get the control template.
|
927 |
+
this.template = api.templates.get_control( type );
|
928 |
+
|
929 |
+
// Bind changes so that the view is re-rendered when the model changes.
|
930 |
+
_.bindAll( this, 'render' );
|
931 |
+
this.model.bind( 'change', this.render );
|
932 |
+
},
|
933 |
+
|
934 |
+
// Renders the control template.
|
935 |
+
render : function() {
|
936 |
+
|
937 |
+
// Only render template if model is active.
|
938 |
+
if ( this.model.get( 'active' ) )
|
939 |
+
this.el.innerHTML = this.template( this.model.toJSON() );
|
940 |
+
|
941 |
+
return this;
|
942 |
+
},
|
943 |
+
|
944 |
+
// Function that is executed *after* the view has been rendered.
|
945 |
+
// This is meant to be overwritten in sub-views.
|
946 |
+
ready : function() {}
|
947 |
+
} );
|
948 |
+
|
949 |
+
/**
|
950 |
+
* Adds the color control view.
|
951 |
+
*
|
952 |
+
* @since 1.0.0
|
953 |
+
*/
|
954 |
+
api.views.register_control( 'color', {
|
955 |
+
|
956 |
+
// Calls the core WP color picker for the control's input.
|
957 |
+
ready : function() {
|
958 |
+
|
959 |
+
var options = this.model.get( 'options' );
|
960 |
+
|
961 |
+
jQuery( this.$el ).find( '.butterbean-color-picker' ).wpColorPicker( options );
|
962 |
+
}
|
963 |
+
} );
|
964 |
+
|
965 |
+
/**
|
966 |
+
* Adds the color palette view.
|
967 |
+
*
|
968 |
+
* @since 1.0.0
|
969 |
+
*/
|
970 |
+
api.views.register_control( 'palette', {
|
971 |
+
|
972 |
+
// Adds custom events.
|
973 |
+
events : {
|
974 |
+
'change input' : 'onselect'
|
975 |
+
},
|
976 |
+
|
977 |
+
// Executed when one of the color palette's value has changed.
|
978 |
+
// These are radio inputs.
|
979 |
+
onselect : function() {
|
980 |
+
|
981 |
+
// Get the value of the input.
|
982 |
+
var value = document.querySelector( '#' + this.el.id + ' input:checked' ).getAttribute( 'value' );
|
983 |
+
|
984 |
+
// Get all choices.
|
985 |
+
var choices = this.model.get( 'choices' );
|
986 |
+
|
987 |
+
// Loop through choices and change the selected value.
|
988 |
+
_.each( choices, function( choice, key ) {
|
989 |
+
choice.selected = key === value;
|
990 |
+
} );
|
991 |
+
|
992 |
+
// Because `choices` is an array, it's not recognized as a change. So, we
|
993 |
+
// have to manually trigger a change here so that the view gets re-rendered.
|
994 |
+
this.model.set( 'choices', choices ).trigger( 'change', this.model );
|
995 |
+
}
|
996 |
+
} );
|
997 |
+
|
998 |
+
/**
|
999 |
+
* Adds the image control view.
|
1000 |
+
*
|
1001 |
+
* @since 1.0.0
|
1002 |
+
*/
|
1003 |
+
api.views.register_control( 'image', {
|
1004 |
+
|
1005 |
+
// Adds custom events.
|
1006 |
+
events : {
|
1007 |
+
'click .butterbean-add-media' : 'showmodal',
|
1008 |
+
'click .butterbean-change-media' : 'showmodal',
|
1009 |
+
'click .butterbean-remove-media' : 'removemedia'
|
1010 |
+
},
|
1011 |
+
|
1012 |
+
// Executed when the show modal button is clicked.
|
1013 |
+
showmodal : function() {
|
1014 |
+
|
1015 |
+
|
1016 |
+
// If we already have a media modal, open it.
|
1017 |
+
if ( ! _.isUndefined( this.media_modal ) ) {
|
1018 |
+
|
1019 |
+
this.media_modal.open();
|
1020 |
+
return;
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
// Create a new media modal.
|
1024 |
+
this.media_modal = wp.media( {
|
1025 |
+
frame : 'select',
|
1026 |
+
multiple : false,
|
1027 |
+
editing : true,
|
1028 |
+
title : this.model.get( 'l10n' ).choose,
|
1029 |
+
library : { type : 'image' },
|
1030 |
+
button : { text: this.model.get( 'l10n' ).set }
|
1031 |
+
} );
|
1032 |
+
|
1033 |
+
// Runs when an image is selected in the media modal.
|
1034 |
+
this.media_modal.on( 'select', function() {
|
1035 |
+
|
1036 |
+
// Gets the JSON data for the first selection.
|
1037 |
+
var media = this.media_modal.state().get( 'selection' ).first().toJSON();
|
1038 |
+
|
1039 |
+
// Size of image to display.
|
1040 |
+
var size = this.model.get( 'size' );
|
1041 |
+
|
1042 |
+
// Updates the model for the view.
|
1043 |
+
this.model.set( {
|
1044 |
+
src : media.sizes[ size ] ? media.sizes[ size ]['url'] : media.url,
|
1045 |
+
alt : media.alt,
|
1046 |
+
value : media.id
|
1047 |
+
} );
|
1048 |
+
}, this );
|
1049 |
+
|
1050 |
+
// Opens the media modal.
|
1051 |
+
this.media_modal.open();
|
1052 |
+
},
|
1053 |
+
|
1054 |
+
// Executed when the remove media button is clicked.
|
1055 |
+
removemedia : function() {
|
1056 |
+
|
1057 |
+
// Updates the model for the view.
|
1058 |
+
this.model.set( { src : '', alt : '', value : '' } );
|
1059 |
+
}
|
1060 |
+
} );
|
1061 |
+
|
1062 |
+
}() );
|
includes/lib/butterbean/js/butterbean.min.js
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
window.butterbean=window.butterbean||{},function(){if(!_.isUndefined(butterbean_data)){var e=butterbean={models:{managers:{},sections:{},controls:{}},views:{managers:{},sections:{},controls:{}},templates:{managers:{},sections:{},controls:{}}};e.models.register_manager=function(e,t){this.managers[e]=new n(t)},e.models.get_manager=function(e){return this.manager_exists(e)?this.managers[e]:!1},e.models.unregister_manager=function(e){this.manager_exists(e)&&delete this.managers[e]},e.models.manager_exists=function(e){return this.managers.hasOwnProperty(e)},e.models.register_section=function(e,t){this.sections[e]=new i(t)},e.models.get_section=function(e){return this.section_exists(e)?this.sections[e]:!1},e.models.unregister_section=function(e){this.section_exists(e)&&delete this.sections[e]},e.models.section_exists=function(e){return this.sections.hasOwnProperty(e)},e.models.register_control=function(e,t){this.controls[e]=new s(t)},e.models.get_control=function(e){return this.control_exists(e)?this.controls[e]:!1},e.models.unregister_control=function(e){this.control_exists(e)&&delete this.controls[e]},e.models.control_exists=function(e){return this.controls.hasOwnProperty(e)},e.views.register_manager=function(e,t){"default"!==e&&(this.managers[e]=this.managers.default.extend(t))},e.views.get_manager=function(e){return this.manager_exists(e)?this.managers[e]:this.managers.default},e.views.unregister_manager=function(e){"default"!==e&&this.manager_exists(e)&&delete this.managers[e]},e.views.manager_exists=function(e){return this.managers.hasOwnProperty(e)},e.views.register_section=function(e,t){"default"!==e&&(this.sections[e]=this.sections.default.extend(t))},e.views.get_section=function(e){return this.section_exists(e)?this.sections[e]:this.sections.default},e.views.unregister_section=function(e){"default"!==e&&this.section_exists(e)&&delete this.sections[e]},e.views.section_exists=function(e){return this.sections.hasOwnProperty(e)},e.views.register_control=function(e,t){"default"!==e&&(this.controls[e]=this.controls.default.extend(t))},e.views.get_control=function(e){return this.control_exists(e)?this.controls[e]:this.controls.default},e.views.unregister_control=function(e){"default"!==e&&this.control_exists(e)&&delete this.controls[e]},e.views.control_exists=function(e){return this.controls.hasOwnProperty(e)},e.templates.register_manager=function(e){this.managers[e]=wp.template("butterbean-manager-"+e)},e.templates.get_manager=function(e){return this.manager_exists(e)?this.managers[e]:!1},e.templates.unregister_manager=function(e){this.manager_exists(e)&&delete this.managers[e]},e.templates.manager_exists=function(e){return this.managers.hasOwnProperty(e)},e.templates.register_section=function(e){this.sections[e]=wp.template("butterbean-section-"+e)},e.templates.get_section=function(e){return this.section_exists(e)?this.sections[e]:!1},e.templates.unregister_section=function(e){this.section_exists(e)&&delete this.sections[e]},e.templates.section_exists=function(e){return this.sections.hasOwnProperty(e)},e.templates.register_control=function(e){this.controls[e]=wp.template("butterbean-control-"+e)},e.templates.get_control=function(e){return this.control_exists(e)?this.controls[e]:!1},e.templates.unregister_control=function(e){this.control_exists(e)&&delete this.controls[e]},e.templates.control_exists=function(e){return this.controls.hasOwnProperty(e)},e.render=function(){_.each(butterbean_data.managers,function(t){e.models.register_manager(t.name,t);var n=e.models.get_manager(t.name),i=e.views.get_manager(n.get("type")),s=new i({model:n}),o=document.getElementById("butterbean-ui-"+n.get("name"));o.className+=" butterbean-ui",o.querySelector(".inside").appendChild(s.render().el),s.subview_render(),s.ready()})};var t=wp.template("butterbean-nav"),n=Backbone.Model.extend({defaults:{name:"",type:"",sections:{},controls:{}}}),i=Backbone.Model.extend({defaults:{name:"",type:"",label:"",description:"",icon:"",manager:"",active:"",selected:!1}}),s=Backbone.Model.extend({defaults:{name:"",type:"",label:"",description:"",icon:"",value:"",choices:{},attr:"",active:"",manager:"",section:"",setting:""}}),o=Backbone.Collection.extend({model:i});e.views.managers["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-manager-"+this.model.get("name"),"class":"butterbean-manager butterbean-manager-"+this.model.get("type")}},initialize:function(){var t=this.model.get("type");e.templates.manager_exists(t)||e.templates.register_manager(t),this.template=e.templates.get_manager(t)},render:function(){return this.el.innerHTML=this.template(this.model.toJSON()),this},subview_render:function(){var t=new o;return _.each(this.model.get("sections"),function(n){e.models.register_section(n.name,n),t.add(e.models.get_section(n.name))}),t.forEach(function(t,n){var i=new a({model:t});document.querySelector("#butterbean-ui-"+t.get("manager")+" .butterbean-nav").appendChild(i.render().el);var s=e.views.get_section(t.get("type")),o=new s({model:t});document.querySelector("#butterbean-ui-"+t.get("manager")+" .butterbean-content").appendChild(o.render().el),o.ready(),t.set("selected",0===n)},this),_.each(this.model.get("controls"),function(t){e.models.register_control(t.name,t);var n=e.models.get_control(t.name),i=e.views.get_control(n.get("type")),s=new i({model:n});document.getElementById("butterbean-"+n.get("manager")+"-section-"+n.get("section")).appendChild(s.render().el),s.ready()}),this},ready:function(){}}),e.views.sections["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-"+this.model.get("manager")+"-section-"+this.model.get("name"),"class":"butterbean-section butterbean-section-"+this.model.get("type"),"aria-hidden":!this.model.get("selected")}},initialize:function(){this.model.on("change",this.onchange,this);var t=this.model.get("type");e.templates.section_exists(t)||e.templates.register_section(t),this.template=e.templates.get_section(t)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},onchange:function(){this.el.setAttribute("aria-hidden",!this.model.get("selected"))},ready:function(){}});var a=Backbone.View.extend({template:t,tagName:"li",attributes:function(){return{"aria-selected":this.model.get("selected")}},initialize:function(){this.model.on("change",this.render,this),this.model.on("change",this.onchange,this)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},events:{"click a":"onselect"},onchange:function(){this.el.setAttribute("aria-selected",this.model.get("selected"))},onselect:function(e){e.preventDefault(),_.each(this.model.collection.models,function(e){e.set("selected",!1)},this),this.model.set("selected",!0)}});e.views.controls["default"]=Backbone.View.extend({tagName:"div",attributes:function(){return{id:"butterbean-control-"+this.model.get("name"),"class":"butterbean-control butterbean-control-"+this.model.get("type")}},initialize:function(){var t=this.model.get("type");e.templates.control_exists(t)||e.templates.register_control(t),this.template=e.templates.get_control(t),_.bindAll(this,"render"),this.model.bind("change",this.render)},render:function(){return this.model.get("active")&&(this.el.innerHTML=this.template(this.model.toJSON())),this},ready:function(){}}),e.views.register_control("color",{ready:function(){var e=this.model.get("options");jQuery(this.$el).find(".butterbean-color-picker").wpColorPicker(e)}}),e.views.register_control("palette",{events:{"change input":"onselect"},onselect:function(){var e=document.querySelector("#"+this.el.id+" input:checked").getAttribute("value"),t=this.model.get("choices");_.each(t,function(t,n){t.selected=n===e}),this.model.set("choices",t).trigger("change",this.model)}}),e.views.register_control("image",{events:{"click .butterbean-add-media":"showmodal","click .butterbean-change-media":"showmodal","click .butterbean-remove-media":"removemedia"},showmodal:function(){return _.isUndefined(this.media_modal)?(this.media_modal=wp.media({frame:"select",multiple:!1,editing:!0,title:this.model.get("l10n").choose,library:{type:"image"},button:{text:this.model.get("l10n").set}}),this.media_modal.on("select",function(){var e=this.media_modal.state().get("selection").first().toJSON(),t=this.model.get("size");this.model.set({src:e.sizes[t]?e.sizes[t].url:e.url,alt:e.alt,value:e.id})},this),this.media_modal.open(),void 0):(this.media_modal.open(),void 0)},removemedia:function(){this.model.set({src:"",alt:"",value:""})}})}}();
|
includes/lib/butterbean/license.md
ADDED
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 2, June 1991
|
3 |
+
|
4 |
+
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
5 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
6 |
+
Everyone is permitted to copy and distribute verbatim copies
|
7 |
+
of this license document, but changing it is not allowed.
|
8 |
+
|
9 |
+
Preamble
|
10 |
+
|
11 |
+
The licenses for most software are designed to take away your
|
12 |
+
freedom to share and change it. By contrast, the GNU General Public
|
13 |
+
License is intended to guarantee your freedom to share and change free
|
14 |
+
software--to make sure the software is free for all its users. This
|
15 |
+
General Public License applies to most of the Free Software
|
16 |
+
Foundation's software and to any other program whose authors commit to
|
17 |
+
using it. (Some other Free Software Foundation software is covered by
|
18 |
+
the GNU Lesser General Public License instead.) You can apply it to
|
19 |
+
your programs, too.
|
20 |
+
|
21 |
+
When we speak of free software, we are referring to freedom, not
|
22 |
+
price. Our General Public Licenses are designed to make sure that you
|
23 |
+
have the freedom to distribute copies of free software (and charge for
|
24 |
+
this service if you wish), that you receive source code or can get it
|
25 |
+
if you want it, that you can change the software or use pieces of it
|
26 |
+
in new free programs; and that you know you can do these things.
|
27 |
+
|
28 |
+
To protect your rights, we need to make restrictions that forbid
|
29 |
+
anyone to deny you these rights or to ask you to surrender the rights.
|
30 |
+
These restrictions translate to certain responsibilities for you if you
|
31 |
+
distribute copies of the software, or if you modify it.
|
32 |
+
|
33 |
+
For example, if you distribute copies of such a program, whether
|
34 |
+
gratis or for a fee, you must give the recipients all the rights that
|
35 |
+
you have. You must make sure that they, too, receive or can get the
|
36 |
+
source code. And you must show them these terms so they know their
|
37 |
+
rights.
|
38 |
+
|
39 |
+
We protect your rights with two steps: (1) copyright the software, and
|
40 |
+
(2) offer you this license which gives you legal permission to copy,
|
41 |
+
distribute and/or modify the software.
|
42 |
+
|
43 |
+
Also, for each author's protection and ours, we want to make certain
|
44 |
+
that everyone understands that there is no warranty for this free
|
45 |
+
software. If the software is modified by someone else and passed on, we
|
46 |
+
want its recipients to know that what they have is not the original, so
|
47 |
+
that any problems introduced by others will not reflect on the original
|
48 |
+
authors' reputations.
|
49 |
+
|
50 |
+
Finally, any free program is threatened constantly by software
|
51 |
+
patents. We wish to avoid the danger that redistributors of a free
|
52 |
+
program will individually obtain patent licenses, in effect making the
|
53 |
+
program proprietary. To prevent this, we have made it clear that any
|
54 |
+
patent must be licensed for everyone's free use or not licensed at all.
|
55 |
+
|
56 |
+
The precise terms and conditions for copying, distribution and
|
57 |
+
modification follow.
|
58 |
+
|
59 |
+
GNU GENERAL PUBLIC LICENSE
|
60 |
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
61 |
+
|
62 |
+
0. This License applies to any program or other work which contains
|
63 |
+
a notice placed by the copyright holder saying it may be distributed
|
64 |
+
under the terms of this General Public License. The "Program", below,
|
65 |
+
refers to any such program or work, and a "work based on the Program"
|
66 |
+
means either the Program or any derivative work under copyright law:
|
67 |
+
that is to say, a work containing the Program or a portion of it,
|
68 |
+
either verbatim or with modifications and/or translated into another
|
69 |
+
language. (Hereinafter, translation is included without limitation in
|
70 |
+
the term "modification".) Each licensee is addressed as "you".
|
71 |
+
|
72 |
+
Activities other than copying, distribution and modification are not
|
73 |
+
covered by this License; they are outside its scope. The act of
|
74 |
+
running the Program is not restricted, and the output from the Program
|
75 |
+
is covered only if its contents constitute a work based on the
|
76 |
+
Program (independent of having been made by running the Program).
|
77 |
+
Whether that is true depends on what the Program does.
|
78 |
+
|
79 |
+
1. You may copy and distribute verbatim copies of the Program's
|
80 |
+
source code as you receive it, in any medium, provided that you
|
81 |
+
conspicuously and appropriately publish on each copy an appropriate
|
82 |
+
copyright notice and disclaimer of warranty; keep intact all the
|
83 |
+
notices that refer to this License and to the absence of any warranty;
|
84 |
+
and give any other recipients of the Program a copy of this License
|
85 |
+
along with the Program.
|
86 |
+
|
87 |
+
You may charge a fee for the physical act of transferring a copy, and
|
88 |
+
you may at your option offer warranty protection in exchange for a fee.
|
89 |
+
|
90 |
+
2. You may modify your copy or copies of the Program or any portion
|
91 |
+
of it, thus forming a work based on the Program, and copy and
|
92 |
+
distribute such modifications or work under the terms of Section 1
|
93 |
+
above, provided that you also meet all of these conditions:
|
94 |
+
|
95 |
+
a) You must cause the modified files to carry prominent notices
|
96 |
+
stating that you changed the files and the date of any change.
|
97 |
+
|
98 |
+
b) You must cause any work that you distribute or publish, that in
|
99 |
+
whole or in part contains or is derived from the Program or any
|
100 |
+
part thereof, to be licensed as a whole at no charge to all third
|
101 |
+
parties under the terms of this License.
|
102 |
+
|
103 |
+
c) If the modified program normally reads commands interactively
|
104 |
+
when run, you must cause it, when started running for such
|
105 |
+
interactive use in the most ordinary way, to print or display an
|
106 |
+
announcement including an appropriate copyright notice and a
|
107 |
+
notice that there is no warranty (or else, saying that you provide
|
108 |
+
a warranty) and that users may redistribute the program under
|
109 |
+
these conditions, and telling the user how to view a copy of this
|
110 |
+
License. (Exception: if the Program itself is interactive but
|
111 |
+
does not normally print such an announcement, your work based on
|
112 |
+
the Program is not required to print an announcement.)
|
113 |
+
|
114 |
+
These requirements apply to the modified work as a whole. If
|
115 |
+
identifiable sections of that work are not derived from the Program,
|
116 |
+
and can be reasonably considered independent and separate works in
|
117 |
+
themselves, then this License, and its terms, do not apply to those
|
118 |
+
sections when you distribute them as separate works. But when you
|
119 |
+
distribute the same sections as part of a whole which is a work based
|
120 |
+
on the Program, the distribution of the whole must be on the terms of
|
121 |
+
this License, whose permissions for other licensees extend to the
|
122 |
+
entire whole, and thus to each and every part regardless of who wrote it.
|
123 |
+
|
124 |
+
Thus, it is not the intent of this section to claim rights or contest
|
125 |
+
your rights to work written entirely by you; rather, the intent is to
|
126 |
+
exercise the right to control the distribution of derivative or
|
127 |
+
collective works based on the Program.
|
128 |
+
|
129 |
+
In addition, mere aggregation of another work not based on the Program
|
130 |
+
with the Program (or with a work based on the Program) on a volume of
|
131 |
+
a storage or distribution medium does not bring the other work under
|
132 |
+
the scope of this License.
|
133 |
+
|
134 |
+
3. You may copy and distribute the Program (or a work based on it,
|
135 |
+
under Section 2) in object code or executable form under the terms of
|
136 |
+
Sections 1 and 2 above provided that you also do one of the following:
|
137 |
+
|
138 |
+
a) Accompany it with the complete corresponding machine-readable
|
139 |
+
source code, which must be distributed under the terms of Sections
|
140 |
+
1 and 2 above on a medium customarily used for software interchange; or,
|
141 |
+
|
142 |
+
b) Accompany it with a written offer, valid for at least three
|
143 |
+
years, to give any third party, for a charge no more than your
|
144 |
+
cost of physically performing source distribution, a complete
|
145 |
+
machine-readable copy of the corresponding source code, to be
|
146 |
+
distributed under the terms of Sections 1 and 2 above on a medium
|
147 |
+
customarily used for software interchange; or,
|
148 |
+
|
149 |
+
c) Accompany it with the information you received as to the offer
|
150 |
+
to distribute corresponding source code. (This alternative is
|
151 |
+
allowed only for noncommercial distribution and only if you
|
152 |
+
received the program in object code or executable form with such
|
153 |
+
an offer, in accord with Subsection b above.)
|
154 |
+
|
155 |
+
The source code for a work means the preferred form of the work for
|
156 |
+
making modifications to it. For an executable work, complete source
|
157 |
+
code means all the source code for all modules it contains, plus any
|
158 |
+
associated interface definition files, plus the scripts used to
|
159 |
+
control compilation and installation of the executable. However, as a
|
160 |
+
special exception, the source code distributed need not include
|
161 |
+
anything that is normally distributed (in either source or binary
|
162 |
+
form) with the major components (compiler, kernel, and so on) of the
|
163 |
+
operating system on which the executable runs, unless that component
|
164 |
+
itself accompanies the executable.
|
165 |
+
|
166 |
+
If distribution of executable or object code is made by offering
|
167 |
+
access to copy from a designated place, then offering equivalent
|
168 |
+
access to copy the source code from the same place counts as
|
169 |
+
distribution of the source code, even though third parties are not
|
170 |
+
compelled to copy the source along with the object code.
|
171 |
+
|
172 |
+
4. You may not copy, modify, sublicense, or distribute the Program
|
173 |
+
except as expressly provided under this License. Any attempt
|
174 |
+
otherwise to copy, modify, sublicense or distribute the Program is
|
175 |
+
void, and will automatically terminate your rights under this License.
|
176 |
+
However, parties who have received copies, or rights, from you under
|
177 |
+
this License will not have their licenses terminated so long as such
|
178 |
+
parties remain in full compliance.
|
179 |
+
|
180 |
+
5. You are not required to accept this License, since you have not
|
181 |
+
signed it. However, nothing else grants you permission to modify or
|
182 |
+
distribute the Program or its derivative works. These actions are
|
183 |
+
prohibited by law if you do not accept this License. Therefore, by
|
184 |
+
modifying or distributing the Program (or any work based on the
|
185 |
+
Program), you indicate your acceptance of this License to do so, and
|
186 |
+
all its terms and conditions for copying, distributing or modifying
|
187 |
+
the Program or works based on it.
|
188 |
+
|
189 |
+
6. Each time you redistribute the Program (or any work based on the
|
190 |
+
Program), the recipient automatically receives a license from the
|
191 |
+
original licensor to copy, distribute or modify the Program subject to
|
192 |
+
these terms and conditions. You may not impose any further
|
193 |
+
restrictions on the recipients' exercise of the rights granted herein.
|
194 |
+
You are not responsible for enforcing compliance by third parties to
|
195 |
+
this License.
|
196 |
+
|
197 |
+
7. If, as a consequence of a court judgment or allegation of patent
|
198 |
+
infringement or for any other reason (not limited to patent issues),
|
199 |
+
conditions are imposed on you (whether by court order, agreement or
|
200 |
+
otherwise) that contradict the conditions of this License, they do not
|
201 |
+
excuse you from the conditions of this License. If you cannot
|
202 |
+
distribute so as to satisfy simultaneously your obligations under this
|
203 |
+
License and any other pertinent obligations, then as a consequence you
|
204 |
+
may not distribute the Program at all. For example, if a patent
|
205 |
+
license would not permit royalty-free redistribution of the Program by
|
206 |
+
all those who receive copies directly or indirectly through you, then
|
207 |
+
the only way you could satisfy both it and this License would be to
|
208 |
+
refrain entirely from distribution of the Program.
|
209 |
+
|
210 |
+
If any portion of this section is held invalid or unenforceable under
|
211 |
+
any particular circumstance, the balance of the section is intended to
|
212 |
+
apply and the section as a whole is intended to apply in other
|
213 |
+
circumstances.
|
214 |
+
|
215 |
+
It is not the purpose of this section to induce you to infringe any
|
216 |
+
patents or other property right claims or to contest validity of any
|
217 |
+
such claims; this section has the sole purpose of protecting the
|
218 |
+
integrity of the free software distribution system, which is
|
219 |
+
implemented by public license practices. Many people have made
|
220 |
+
generous contributions to the wide range of software distributed
|
221 |
+
through that system in reliance on consistent application of that
|
222 |
+
system; it is up to the author/donor to decide if he or she is willing
|
223 |
+
to distribute software through any other system and a licensee cannot
|
224 |
+
impose that choice.
|
225 |
+
|
226 |
+
This section is intended to make thoroughly clear what is believed to
|
227 |
+
be a consequence of the rest of this License.
|
228 |
+
|
229 |
+
8. If the distribution and/or use of the Program is restricted in
|
230 |
+
certain countries either by patents or by copyrighted interfaces, the
|
231 |
+
original copyright holder who places the Program under this License
|
232 |
+
may add an explicit geographical distribution limitation excluding
|
233 |
+
those countries, so that distribution is permitted only in or among
|
234 |
+
countries not thus excluded. In such case, this License incorporates
|
235 |
+
the limitation as if written in the body of this License.
|
236 |
+
|
237 |
+
9. The Free Software Foundation may publish revised and/or new versions
|
238 |
+
of the General Public License from time to time. Such new versions will
|
239 |
+
be similar in spirit to the present version, but may differ in detail to
|
240 |
+
address new problems or concerns.
|
241 |
+
|
242 |
+
Each version is given a distinguishing version number. If the Program
|
243 |
+
specifies a version number of this License which applies to it and "any
|
244 |
+
later version", you have the option of following the terms and conditions
|
245 |
+
either of that version or of any later version published by the Free
|
246 |
+
Software Foundation. If the Program does not specify a version number of
|
247 |
+
this License, you may choose any version ever published by the Free Software
|
248 |
+
Foundation.
|
249 |
+
|
250 |
+
10. If you wish to incorporate parts of the Program into other free
|
251 |
+
programs whose distribution conditions are different, write to the author
|
252 |
+
to ask for permission. For software which is copyrighted by the Free
|
253 |
+
Software Foundation, write to the Free Software Foundation; we sometimes
|
254 |
+
make exceptions for this. Our decision will be guided by the two goals
|
255 |
+
of preserving the free status of all derivatives of our free software and
|
256 |
+
of promoting the sharing and reuse of software generally.
|
257 |
+
|
258 |
+
NO WARRANTY
|
259 |
+
|
260 |
+
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
261 |
+
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
262 |
+
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
263 |
+
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
264 |
+
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
265 |
+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
266 |
+
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
267 |
+
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
268 |
+
REPAIR OR CORRECTION.
|
269 |
+
|
270 |
+
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
271 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
272 |
+
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
273 |
+
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
274 |
+
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
275 |
+
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
276 |
+
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
277 |
+
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
278 |
+
POSSIBILITY OF SUCH DAMAGES.
|
279 |
+
|
280 |
+
END OF TERMS AND CONDITIONS
|
281 |
+
|
282 |
+
How to Apply These Terms to Your New Programs
|
283 |
+
|
284 |
+
If you develop a new program, and you want it to be of the greatest
|
285 |
+
possible use to the public, the best way to achieve this is to make it
|
286 |
+
free software which everyone can redistribute and change under these terms.
|
287 |
+
|
288 |
+
To do so, attach the following notices to the program. It is safest
|
289 |
+
to attach them to the start of each source file to most effectively
|
290 |
+
convey the exclusion of warranty; and each file should have at least
|
291 |
+
the "copyright" line and a pointer to where the full notice is found.
|
292 |
+
|
293 |
+
<one line to give the program's name and a brief idea of what it does.>
|
294 |
+
Copyright (C) <year> <name of author>
|
295 |
+
|
296 |
+
This program is free software; you can redistribute it and/or modify
|
297 |
+
it under the terms of the GNU General Public License as published by
|
298 |
+
the Free Software Foundation; either version 2 of the License, or
|
299 |
+
(at your option) any later version.
|
300 |
+
|
301 |
+
This program is distributed in the hope that it will be useful,
|
302 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
303 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
304 |
+
GNU General Public License for more details.
|
305 |
+
|
306 |
+
You should have received a copy of the GNU General Public License along
|
307 |
+
with this program; if not, write to the Free Software Foundation, Inc.,
|
308 |
+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
309 |
+
|
310 |
+
Also add information on how to contact you by electronic and paper mail.
|
311 |
+
|
312 |
+
If the program is interactive, make it output a short notice like this
|
313 |
+
when it starts in an interactive mode:
|
314 |
+
|
315 |
+
Gnomovision version 69, Copyright (C) year name of author
|
316 |
+
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
317 |
+
This is free software, and you are welcome to redistribute it
|
318 |
+
under certain conditions; type `show c' for details.
|
319 |
+
|
320 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
321 |
+
parts of the General Public License. Of course, the commands you use may
|
322 |
+
be called something other than `show w' and `show c'; they could even be
|
323 |
+
mouse-clicks or menu items--whatever suits your program.
|
324 |
+
|
325 |
+
You should also get your employer (if you work as a programmer) or your
|
326 |
+
school, if any, to sign a "copyright disclaimer" for the program, if
|
327 |
+
necessary. Here is a sample; alter the names:
|
328 |
+
|
329 |
+
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
330 |
+
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
331 |
+
|
332 |
+
<signature of Ty Coon>, 1 April 1989
|
333 |
+
Ty Coon, President of Vice
|
334 |
+
|
335 |
+
This General Public License does not permit incorporating your program into
|
336 |
+
proprietary programs. If your program is a subroutine library, you may
|
337 |
+
consider it more useful to permit linking proprietary applications with the
|
338 |
+
library. If this is what you want to do, use the GNU Lesser General
|
339 |
+
Public License instead of this License.
|
includes/lib/butterbean/readme.md
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ButterBean
|
2 |
+
|
3 |
+
ButterBean is a neat little post meta box framework built on [Backbone.js](http://backbonejs.org) and [Underscore.js](http://underscorejs.org). You can run it as a standalone plugin or drop it into your own plugins.
|
4 |
+
|
5 |
+
The idea behind ButterBean came about because I often build custom post types that need quite a bit of custom metadata attached to the posts. Separating this into multiple meta boxes wasn't fun or user friendly. So, I decided to go with a single tabbed meta box instead.
|
6 |
+
|
7 |
+
And, that's what ButterBean is. It's essentially a meta box with tabs for lots of content.
|
8 |
+
|
9 |
+
## Just the interface
|
10 |
+
|
11 |
+
A lot of meta box frameworks try to do everything. They handle backend output, frontend output, and everything else you can think of. ButterBean is meant to be an interface only. Because every project's needs are vastly different, it doesn't make sense to stick you with a bunch of things you don't need. This means that the code can stay relatively lean and flexible, which makes it perfect for bundling in your plugins.
|
12 |
+
|
13 |
+
So, don't go looking for functions for outputting metadata on the front end from ButterBean. It doesn't have any. Use the core WordPress functionality or build your own wrapper functions.
|
14 |
+
|
15 |
+
## Documentation
|
16 |
+
|
17 |
+
This is a quick guide. If you're familiar with the WordPress Customization API, you should probably pick this up fairly quickly. A lot of the same concepts are used here.
|
18 |
+
|
19 |
+
### Installation
|
20 |
+
|
21 |
+
Drop the `butterbean` folder into your plugin. That's the simple part.
|
22 |
+
|
23 |
+
The script will auto-load itself on the correct admin hooks. You just need to load it like so:
|
24 |
+
|
25 |
+
```
|
26 |
+
add_action( 'plugins_loaded', 'th_load' );
|
27 |
+
|
28 |
+
function th_load() {
|
29 |
+
|
30 |
+
require_once( 'path/to/butterbean/butterbean.php' );
|
31 |
+
}
|
32 |
+
```
|
33 |
+
|
34 |
+
### Registration
|
35 |
+
|
36 |
+
There's a built-in action hook called `butterbean_register`. You're going to use that to register everything. So, you need to set up a callback function for that.
|
37 |
+
|
38 |
+
```
|
39 |
+
add_action( 'butterbean_register', 'th_register', 10, 2 );
|
40 |
+
|
41 |
+
function th_register( $butterbean, $post_type ) {
|
42 |
+
|
43 |
+
// Register managers, sections, controls, and settings here.
|
44 |
+
}
|
45 |
+
```
|
46 |
+
|
47 |
+
#### Registering a manager
|
48 |
+
|
49 |
+
A **manager** is a group of sections, controls, and settings. It's displayed as a single meta box. There can be multiple managers per screen (don't try multiples yet).
|
50 |
+
|
51 |
+
```
|
52 |
+
$butterbean->register_manager(
|
53 |
+
'example',
|
54 |
+
array(
|
55 |
+
'label' => esc_html__( 'Example', 'your-textdomain' ),
|
56 |
+
'post_type' => 'your_post_type',
|
57 |
+
'context' => 'normal',
|
58 |
+
'priority' => 'high'
|
59 |
+
)
|
60 |
+
);
|
61 |
+
|
62 |
+
$manager = $butterbean->get_manager( 'example' );
|
63 |
+
```
|
64 |
+
|
65 |
+
#### Registering a section
|
66 |
+
|
67 |
+
A **section** is a group of controls within a manager. They are presented as "tabbed" sections in the UI.
|
68 |
+
|
69 |
+
```
|
70 |
+
$manager->register_section(
|
71 |
+
'section_1',
|
72 |
+
array(
|
73 |
+
'label' => esc_html__( 'Section 1', 'your-textdomain' ),
|
74 |
+
'icon' => 'dashicons-admin-generic'
|
75 |
+
)
|
76 |
+
);
|
77 |
+
```
|
78 |
+
|
79 |
+
#### Registering a control
|
80 |
+
|
81 |
+
A **control** is essentially a form field. It's the field(s) that a user enters data into. Each control belongs to a section. Each control should also be tied to a setting (below).
|
82 |
+
|
83 |
+
```
|
84 |
+
$manager->register_control(
|
85 |
+
'abc_xyz', // Same as setting name.
|
86 |
+
array(
|
87 |
+
'type' => 'text',
|
88 |
+
'section' => 'section_1',
|
89 |
+
'label' => esc_html__( 'Control ABC', 'your-textdomain' ),
|
90 |
+
'attr' => array( 'class' => 'widefat' )
|
91 |
+
)
|
92 |
+
);
|
93 |
+
```
|
94 |
+
|
95 |
+
#### Registering a setting
|
96 |
+
|
97 |
+
A **setting** is nothing more than some post metadata and how it gets stored. A setting belongs to a specific control.
|
98 |
+
|
99 |
+
```
|
100 |
+
$manager->register_setting(
|
101 |
+
'abc_xyz', // Same as control name.
|
102 |
+
array(
|
103 |
+
'sanitize_callback' => 'wp_filter_nohtml_kses'
|
104 |
+
)
|
105 |
+
);
|
106 |
+
```
|
107 |
+
|
108 |
+
### JavaScript API
|
109 |
+
|
110 |
+
ButterBean was built using [Backbone](http://backbonejs.org) for handling models, collections, and views. It uses [Underscore](http://underscorejs.org) for rendering templates for the views. All output is handled via JavaScript rather than PHP so that we can do cool stuff on the fly without having to reload the page. This is particularly useful when you start building more complex controls.
|
111 |
+
|
112 |
+
You'll never need to touch JavaScript until you need to build a control that relies on JavaScript.
|
113 |
+
|
114 |
+
#### The butterbean object
|
115 |
+
|
116 |
+
`butterbean` is the global object that houses everything you ever want to touch on the JavaScript side of things. It's located in the `js/butterbean.js` file. This file is well-documented, so you'll want to dive into it for doing more advanced stuff.
|
117 |
+
|
118 |
+
`butterbean.views.register_control()` is what most people will end up using. It's a function for registering a custom control view. New views can be created for each `type` of control.
|
119 |
+
|
120 |
+
Here's a quick example of registering a view for a color control where we need to call the core WordPress `wpColorPicker()` function. It uses the `ready()` function, which is fired after the HTML has been rendered for the view.
|
121 |
+
|
122 |
+
```
|
123 |
+
( function() {
|
124 |
+
|
125 |
+
butterbean.views.register_control( 'color', {
|
126 |
+
|
127 |
+
// Calls the core WP color picker for the control's input.
|
128 |
+
ready : function() {
|
129 |
+
|
130 |
+
var options = this.model.attributes.options;
|
131 |
+
|
132 |
+
jQuery( this.$el ).find( '.butterbean-color-picker' ).wpColorPicker( options );
|
133 |
+
}
|
134 |
+
} );
|
135 |
+
}() );
|
136 |
+
```
|
137 |
+
|
138 |
+
## Professional Support
|
139 |
+
|
140 |
+
If you need professional plugin support from me, the plugin author, you can access the support forums at [Theme Hybrid](http://themehybrid.com/board/topics), which is a professional WordPress help/support site where I handle support for all my plugins and themes for a community of 70,000+ users (and growing).
|
141 |
+
|
142 |
+
## Copyright and License
|
143 |
+
|
144 |
+
Various ideas from different projects have made their way into ButterBean. A few of the projects that had an important impact on the direction of this project are:
|
145 |
+
|
146 |
+
* Architecturally, the PHP code was modeled after the core WordPress Customization API. - [GPL 2+](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
|
147 |
+
* The design concept of the default tabbed interface was taken from [WooCommerce](http://www.woothemes.com/woocommerce/). © WooThemes - [GPL 3+](http://www.gnu.org/licenses/gpl.html)
|
148 |
+
* Code ideas for the media frame were borrowed from [WP Term Images](https://wordpress.org/plugins/wp-term-images/). © John James Jacoby - [GPL 2+](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
|
149 |
+
|
150 |
+
This project is licensed under the [GNU GPL](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html), version 2 or later.
|
151 |
+
|
152 |
+
2015-2016 © [Justin Tadlock](http://justintadlock.com).
|
includes/lib/butterbean/tmpl/control-checkbox.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<input type="checkbox" value="true" {{{ data.attr }}} <# if ( data.value ) { #> checked="checked" <# } #> />
|
3 |
+
|
4 |
+
<# if ( data.label ) { #>
|
5 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
6 |
+
<# } #>
|
7 |
+
|
8 |
+
<# if ( data.description ) { #>
|
9 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
10 |
+
<# } #>
|
11 |
+
</label>
|
includes/lib/butterbean/tmpl/control-checkboxes.php
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<ul class="butterbean-checkbox-list">
|
10 |
+
|
11 |
+
<# _.each( data.choices, function( label, choice ) { #>
|
12 |
+
|
13 |
+
<li>
|
14 |
+
<label>
|
15 |
+
<input type="checkbox" value="{{ choice }}" name="{{ data.field_name }}[]" <# if ( -1 !== _.indexOf( data.value, choice ) ) { #> checked="checked" <# } #> />
|
16 |
+
{{ label }}
|
17 |
+
</label>
|
18 |
+
</li>
|
19 |
+
|
20 |
+
<# } ) #>
|
21 |
+
|
22 |
+
</ul>
|
includes/lib/butterbean/tmpl/control-color.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<# if ( data.label ) { #>
|
3 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
4 |
+
<# } #>
|
5 |
+
|
6 |
+
<# if ( data.description ) { #>
|
7 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
8 |
+
<# } #>
|
9 |
+
|
10 |
+
<input {{{ data.attr }}} value="<# if ( data.value ) { #>#{{ data.value }}<# } #>" />
|
11 |
+
</label>
|
includes/lib/butterbean/tmpl/control-datetime.php
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<?php $month = '<label>
|
10 |
+
<span class="screen-reader-text">{{ data.month.label }}</span>
|
11 |
+
<select name="{{ data.month.name }}">
|
12 |
+
<# _.each( data.month.choices, function( choice ) { #>
|
13 |
+
<option value="{{ choice.num }}" <# if ( choice.num === data.month.value ) { #> selected="selected" <# } #>>{{ choice.label }}</option>
|
14 |
+
<# } ) #>
|
15 |
+
</select>
|
16 |
+
</label>';
|
17 |
+
|
18 |
+
$day = '<label>
|
19 |
+
<span class="screen-reader-text">{{ data.day.label }}</span>
|
20 |
+
<input type="text" name="{{ data.day.name }}" value="{{ data.day.value }}" {{{ data.day.attr }}} />
|
21 |
+
</label>';
|
22 |
+
|
23 |
+
$year = '<label>
|
24 |
+
<span class="screen-reader-text">{{ data.year.label }}</span>
|
25 |
+
<input type="text" name="{{ data.year.name }}" value="{{ data.year.value }}" {{{ data.year.attr }}} />
|
26 |
+
</label>';
|
27 |
+
|
28 |
+
$hour = '<label>
|
29 |
+
<span class="screen-reader-text">{{ data.hour.label }}</span>
|
30 |
+
<input type="text" name="{{ data.hour.name }}" value="{{ data.hour.value }}" {{{ data.hour.attr }}} />
|
31 |
+
</label>';
|
32 |
+
|
33 |
+
$minute = '<label>
|
34 |
+
<span class="screen-reader-text">{{ data.minute.label }}</span>
|
35 |
+
<input type="text" name="{{ data.minute.name }}" value="{{ data.minute.value }}" {{{ data.minute.attr }}} />
|
36 |
+
</label>';
|
37 |
+
|
38 |
+
$second = '<label>
|
39 |
+
<span class="screen-reader-text">{{ data.second.label }}</span>
|
40 |
+
<input type="text" name="{{ data.second.name }}" value="{{ data.second.value }}" {{{ data.second.attr }}} />
|
41 |
+
</label>'; ?>
|
42 |
+
|
43 |
+
<# if ( data.show_time ) { #>
|
44 |
+
|
45 |
+
<?php // Translators: 1: month, 2: day, 3: year, 4: hour, 5: minute, 6: second.
|
46 |
+
printf( __( '%1$s %2$s, %3$s @ %4$s:%5$s:%6$s', 'butterbean' ), $month, $day, $year, $hour, $minute, $second );
|
47 |
+
?>
|
48 |
+
|
49 |
+
<# } else { #>
|
50 |
+
|
51 |
+
<?php // Translators: 1: month, 2: day, 3: year.
|
52 |
+
printf( __( '%1$s %2$s, %3$s', 'butterbean' ), $month, $day, $year );
|
53 |
+
?>
|
54 |
+
|
55 |
+
<# } #>
|
includes/lib/butterbean/tmpl/control-image.php
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<input type="hidden" class="butterbean-attachment-id" name="{{ data.field_name }}" value="{{ data.value }}" />
|
10 |
+
|
11 |
+
<# if ( data.src ) { #>
|
12 |
+
<img class="butterbean-img" src="{{ data.src }}" alt="{{ data.alt }}" />
|
13 |
+
<# } else { #>
|
14 |
+
<div class="butterbean-placeholder">{{ data.l10n.placeholder }}</div>
|
15 |
+
<# } #>
|
16 |
+
|
17 |
+
<p>
|
18 |
+
<# if ( data.src ) { #>
|
19 |
+
<button type="button" class="button button-secondary butterbean-change-media">{{ data.l10n.change }}</button>
|
20 |
+
<button type="button" class="button button-secondary butterbean-remove-media">{{ data.l10n.remove }}</button>
|
21 |
+
<# } else { #>
|
22 |
+
<button type="button" class="button button-secondary butterbean-add-media">{{ data.l10n.upload }}</button>
|
23 |
+
<# } #>
|
24 |
+
</p>
|
includes/lib/butterbean/tmpl/control-multi-avatars.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<div class="butterbean-multi-avatars-wrap">
|
10 |
+
|
11 |
+
<# _.each( data.choices, function( user ) { #>
|
12 |
+
|
13 |
+
<label>
|
14 |
+
<input type="checkbox" value="{{ user.id }}" name="{{ data.field_name }}[]" <# if ( -1 !== _.indexOf( data.value, user.id ) ) { #> checked="checked" <# } #> />
|
15 |
+
|
16 |
+
<span class="screen-reader-text">{{ user.name }}</span>
|
17 |
+
|
18 |
+
{{{ user.avatar }}}
|
19 |
+
</label>
|
20 |
+
|
21 |
+
<# } ) #>
|
22 |
+
|
23 |
+
</div><!-- .butterbean-multi-avatars-wrap -->
|
includes/lib/butterbean/tmpl/control-palette.php
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<# _.each( data.choices, function( palette, choice ) { #>
|
10 |
+
<label aria-selected="{{ palette.selected }}">
|
11 |
+
<input type="radio" value="{{ choice }}" name="{{ data.field_name }}" <# if ( palette.selected ) { #> checked="checked" <# } #> />
|
12 |
+
|
13 |
+
<span class="butterbean-palette-label">{{ palette.label }}</span>
|
14 |
+
|
15 |
+
<div class="butterbean-palette-block">
|
16 |
+
|
17 |
+
<# _.each( palette.colors, function( color ) { #>
|
18 |
+
<span class="butterbean-palette-color" style="background-color: {{ color }}"> </span>
|
19 |
+
<# } ) #>
|
20 |
+
|
21 |
+
</div>
|
22 |
+
</label>
|
23 |
+
<# } ) #>
|
includes/lib/butterbean/tmpl/control-parent.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<# if ( data.label ) { #>
|
3 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
4 |
+
<# } #>
|
5 |
+
|
6 |
+
<select name="{{ data.field_name }}" id="{{ data.field_name }}">
|
7 |
+
|
8 |
+
<# _.each( data.choices, function( choice ) { #>
|
9 |
+
<option value="{{ choice.value }}" <# if ( choice.value === data.value ) { #> selected="selected" <# } #>>{{ choice.label }}</option>
|
10 |
+
<# } ) #>
|
11 |
+
|
12 |
+
</select>
|
13 |
+
|
14 |
+
<# if ( data.description ) { #>
|
15 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
16 |
+
<# } #>
|
17 |
+
</label>
|
includes/lib/butterbean/tmpl/control-radio-image.php
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<# _.each( data.choices, function( args, choice ) { #>
|
10 |
+
|
11 |
+
<label>
|
12 |
+
<input type="radio" value="{{ choice }}" name="{{ data.field_name }}" <# if ( data.value === choice ) { #> checked="checked" <# } #> />
|
13 |
+
<span class="screen-reader-text">{{ args.label }}</span>
|
14 |
+
<img src="{{ args.url }}" alt="{{ args.label }}" />
|
15 |
+
</label>
|
16 |
+
|
17 |
+
<# } ) #>
|
includes/lib/butterbean/tmpl/control-radio.php
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<# if ( data.label ) { #>
|
2 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
3 |
+
<# } #>
|
4 |
+
|
5 |
+
<# if ( data.description ) { #>
|
6 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
7 |
+
<# } #>
|
8 |
+
|
9 |
+
<ul class="butterbean-radio-list">
|
10 |
+
|
11 |
+
<# _.each( data.choices, function( label, choice ) { #>
|
12 |
+
|
13 |
+
<li>
|
14 |
+
<label>
|
15 |
+
<input type="radio" value="{{ choice }}" name="{{ data.field_name }}" <# if ( data.value === choice ) { #> checked="checked" <# } #> />
|
16 |
+
{{ label }}
|
17 |
+
</label>
|
18 |
+
</li>
|
19 |
+
|
20 |
+
<# } ) #>
|
21 |
+
|
22 |
+
</ul>
|
includes/lib/butterbean/tmpl/control-select-group.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
|
3 |
+
<# if ( data.label ) { #>
|
4 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
5 |
+
<# } #>
|
6 |
+
|
7 |
+
<# if ( data.description ) { #>
|
8 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
9 |
+
<# } #>
|
10 |
+
|
11 |
+
<select {{{ data.attr }}}>
|
12 |
+
|
13 |
+
<# _.each( data.choices, function( label, choice ) { #>
|
14 |
+
|
15 |
+
<option value="{{ choice }}" <# if ( choice === data.value ) { #> selected="selected" <# } #>>{{ label }}</option>
|
16 |
+
|
17 |
+
<# } ) #>
|
18 |
+
|
19 |
+
<# _.each( data.group, function( group ) { #>
|
20 |
+
|
21 |
+
<optgroup label="{{ group.label }}">
|
22 |
+
|
23 |
+
<# _.each( group.choices, function( label, choice ) { #>
|
24 |
+
|
25 |
+
<option value="{{ choice }}" <# if ( choice === data.value ) { #> selected="selected" <# } #>>{{ label }}</option>
|
26 |
+
|
27 |
+
<# } ) #>
|
28 |
+
|
29 |
+
</optgroup>
|
30 |
+
<# } ) #>
|
31 |
+
|
32 |
+
</select>
|
33 |
+
</label>
|
includes/lib/butterbean/tmpl/control-select.php
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
|
3 |
+
<# if ( data.label ) { #>
|
4 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
5 |
+
<# } #>
|
6 |
+
|
7 |
+
<# if ( data.description ) { #>
|
8 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
9 |
+
<# } #>
|
10 |
+
|
11 |
+
<select {{{ data.attr }}}>
|
12 |
+
|
13 |
+
<# _.each( data.choices, function( label, choice ) { #>
|
14 |
+
|
15 |
+
<option value="{{ choice }}" <# if ( data.value === choice ) { #> selected="selected" <# } #>>{{ label }}</option>
|
16 |
+
|
17 |
+
<# } ) #>
|
18 |
+
|
19 |
+
</select>
|
20 |
+
</label>
|
includes/lib/butterbean/tmpl/control-textarea.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<# if ( data.label ) { #>
|
3 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
4 |
+
<# } #>
|
5 |
+
|
6 |
+
<textarea {{{ data.attr }}}>{{{ data.value }}}</textarea>
|
7 |
+
|
8 |
+
<# if ( data.description ) { #>
|
9 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
10 |
+
<# } #>
|
11 |
+
</label>
|
includes/lib/butterbean/tmpl/control.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<label>
|
2 |
+
<# if ( data.label ) { #>
|
3 |
+
<span class="butterbean-label">{{ data.label }}</span>
|
4 |
+
<# } #>
|
5 |
+
|
6 |
+
<# if ( data.description ) { #>
|
7 |
+
<span class="butterbean-description">{{{ data.description }}}</span>
|
8 |
+
<# } #>
|
9 |
+
|
10 |
+
<input type="{{ data.type }}" value="{{ data.value }}" {{{ data.attr }}} />
|
11 |
+
</label>
|
includes/lib/butterbean/tmpl/manager.php
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
<ul class="butterbean-nav"></ul>
|
2 |
+
<div class="butterbean-content"></div>
|
includes/lib/butterbean/tmpl/nav.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<a href="#butterbean-{{ data.manager }}-section-{{ data.name }}"><i class="{{ data.icon }}" aria-hidden="true"></i><span class="label">{{ data.label }}</span></a>
|
includes/lib/butterbean/tmpl/section.php
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
1 |
+
<# if ( data.description ) { #>
|
2 |
+
<span class="butterbean-description description">{{{ data.description }}}</span>
|
3 |
+
<# } #>
|
index.php
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
<?php // Silence is golden
|
languages/bgseo.pot
ADDED
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (C) 2017 bgseo
|
2 |
+
# This file is distributed under the same license as the bgseo package.
|
3 |
+
msgid ""
|
4 |
+
msgstr ""
|
5 |
+
"Project-Id-Version: bgseo\n"
|
6 |
+
"Report-Msgid-Bugs-To: https://boldgrid.com\n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"PO-Revision-Date: 2017-MO-DA HO:MI+ZONE\n"
|
11 |
+
"Language-Team: The BoldGrid Team <support@boldgrid.com>\n"
|
12 |
+
"X-Poedit-Basepath: ..\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
|
15 |
+
"X-Poedit-SearchPath-0: .\n"
|
16 |
+
"X-Poedit-SearchPathExcluded-0: *.js\n"
|
17 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
18 |
+
|
19 |
+
#: boldgrid-easy-seo.php:76
|
20 |
+
msgid "Easy SEO Error: Easy SEO Supports WordPress version 4.0+, and PHP version 5.3+"
|
21 |
+
msgstr ""
|
22 |
+
|
23 |
+
#: boldgrid-easy-seo.php:83
|
24 |
+
msgid "Easy SEO Error: You must have PHP 5.3 or higher and WordPress 4.0 or higher to use this plugin."
|
25 |
+
msgstr ""
|
26 |
+
|
27 |
+
#: includes/configs/i18n/content.config.php:5
|
28 |
+
msgid "Word Count: %s."
|
29 |
+
msgstr ""
|
30 |
+
|
31 |
+
#: includes/configs/i18n/content.config.php:7
|
32 |
+
msgid "You haven't entered any %1$scontent%2$s yet! As you start writing your content, we'll make recommendations for better SEO!"
|
33 |
+
msgstr ""
|
34 |
+
|
35 |
+
#: includes/configs/i18n/content.config.php:12
|
36 |
+
msgid "The content should be longer, we recommend %1$sat least 300 words%2$s. Try writing more about the focus keyword phrase of this page."
|
37 |
+
msgstr ""
|
38 |
+
|
39 |
+
#: includes/configs/i18n/content.config.php:17
|
40 |
+
msgid "We recommend a %1$sminimum of 300 words%2$s for the best SEO results."
|
41 |
+
msgstr ""
|
42 |
+
|
43 |
+
#: includes/configs/i18n/content.config.php:22
|
44 |
+
msgid "Your content is over the recommended %1$sminimum of 300 words%2$s, good job!"
|
45 |
+
msgstr ""
|
46 |
+
|
47 |
+
#: includes/configs/i18n/content.config.php:34
|
48 |
+
msgid "You haven't used your %1$skeyword phrase in your content%2$s at all. Try adding it naturally by talking about the subject more."
|
49 |
+
msgstr ""
|
50 |
+
|
51 |
+
#: includes/configs/i18n/content.config.php:39
|
52 |
+
msgid "Your %1$skeyword phrase is being used in your content%2$s, but we recommend using it at least 1 time."
|
53 |
+
msgstr ""
|
54 |
+
|
55 |
+
#: includes/configs/i18n/content.config.php:45
|
56 |
+
msgid "Your %1$skeyword phrase is being used in your content%2$s, but we recommend using it at least %%s times."
|
57 |
+
msgstr ""
|
58 |
+
|
59 |
+
#: includes/configs/i18n/content.config.php:51
|
60 |
+
msgid "Your %1$skeyword phrase appears excessively in your content%2$s. Try to only use it 1 time and use other words and variations that are related to it."
|
61 |
+
msgstr ""
|
62 |
+
|
63 |
+
#: includes/configs/i18n/content.config.php:56
|
64 |
+
msgid "Your %1$skeyword phrase appears excessively in your content%2$s. Try to only use it %%s times and use other words and variations that are related to it."
|
65 |
+
msgstr ""
|
66 |
+
|
67 |
+
#: includes/configs/i18n/content.config.php:61
|
68 |
+
msgid "Great, you have included the %1$skeyword in your content%2$s at least 1 time. This helps get you a better SEO score!"
|
69 |
+
msgstr ""
|
70 |
+
|
71 |
+
#: includes/configs/i18n/content.config.php:66
|
72 |
+
msgid "Great, you have included the %1$skeyword phrase in your content%2$s at least %%s times. This helps get you a better SEO score!"
|
73 |
+
msgstr ""
|
74 |
+
|
75 |
+
#: includes/configs/i18n/headings.config.php:5
|
76 |
+
msgid "It looks like this post is using %1$sonly one H1 tag%2$s! Good job!"
|
77 |
+
msgstr ""
|
78 |
+
|
79 |
+
#: includes/configs/i18n/headings.config.php:10
|
80 |
+
msgid "This post has %1$smore than one H1 tag%2$s which can negatively impact your SEO. You should try to only have one H1 on your page."
|
81 |
+
msgstr ""
|
82 |
+
|
83 |
+
#: includes/configs/i18n/headings.config.php:14
|
84 |
+
msgid "This post has %1$smore than one H1 tag%2$s. Unchecking the %3$s\"Display page title\"%4$s box at the top of this page will remove an H1 from your page."
|
85 |
+
msgstr ""
|
86 |
+
|
87 |
+
#: includes/configs/i18n/headings.config.php:21
|
88 |
+
msgid "Your page %1$sdoesn't have any H1 tags%2$s on it, you should considering adding one that includes your target keyword!"
|
89 |
+
msgstr ""
|
90 |
+
|
91 |
+
#: includes/configs/i18n/headings.config.php:28
|
92 |
+
msgid "Your %1$skeyword appears in your H1 and H2 tags%2$s, which is good for your search engine optimization!"
|
93 |
+
msgstr ""
|
94 |
+
|
95 |
+
#: includes/configs/i18n/headings.config.php:33
|
96 |
+
msgid "You have not %1$sused your keyword in any H1 or H2 tags%2$s. You should try to include this at least once."
|
97 |
+
msgstr ""
|
98 |
+
|
99 |
+
#: includes/configs/i18n/headings.config.php:38
|
100 |
+
msgid "The %1$skeyword appears too much in your H1 and H2 tags%2$s."
|
101 |
+
msgstr ""
|
102 |
+
|
103 |
+
#: includes/configs/i18n/image.config.php:5
|
104 |
+
msgid "Your article %1$scontains at least one image%2$s, which is great for SEO, awesome!"
|
105 |
+
msgstr ""
|
106 |
+
|
107 |
+
#: includes/configs/i18n/image.config.php:10
|
108 |
+
msgid "Try adding %1$sat least one image%2$s that's relevant to your content's topic to further optimize your page."
|
109 |
+
msgstr ""
|
110 |
+
|
111 |
+
#: includes/configs/i18n/keywords.config.php:5
|
112 |
+
msgid "Based on your content and frequency, search engines will likely think your content is about"
|
113 |
+
msgstr ""
|
114 |
+
|
115 |
+
#: includes/configs/i18n/keywords.config.php:6
|
116 |
+
msgid "Set a new target keyword below, and the dashboard will be updated with new stats!"
|
117 |
+
msgstr ""
|
118 |
+
|
119 |
+
#: includes/configs/i18n/keywords.config.php:15
|
120 |
+
msgid "Great, you've entered a %1$skeyword phrase%2$s for the focus of your content!"
|
121 |
+
msgstr ""
|
122 |
+
|
123 |
+
#: includes/configs/i18n/keywords.config.php:20
|
124 |
+
msgid "It looks like you have entered a single word for the keyword. We recommend adding a %1$skeyword phrase%2$s instead of a single word for better results."
|
125 |
+
msgstr ""
|
126 |
+
|
127 |
+
#: includes/configs/i18n/keywords.config.php:25
|
128 |
+
msgid "You haven't entered a %1$skeyword phrase%2$s for the focus of this content. This helps guide you in writing better optimized content!"
|
129 |
+
msgstr ""
|
130 |
+
|
131 |
+
#: includes/configs/i18n/noFollow.config.php:4
|
132 |
+
msgid "Great, your links are set to %1$sfollow%2$s for search engines!"
|
133 |
+
msgstr ""
|
134 |
+
|
135 |
+
#: includes/configs/i18n/noFollow.config.php:9
|
136 |
+
msgid "Your links are set to %1$snofollow%2$s for search engines!"
|
137 |
+
msgstr ""
|
138 |
+
|
139 |
+
#: includes/configs/i18n/noIndex.config.php:4
|
140 |
+
msgid "This article is set to %1$sindex%2$s, so it is being indexed by search engines!"
|
141 |
+
msgstr ""
|
142 |
+
|
143 |
+
#: includes/configs/i18n/noIndex.config.php:9
|
144 |
+
msgid "This page is set to %1$snoindex%2$s, so it is being blocked from search engine indexing!"
|
145 |
+
msgstr ""
|
146 |
+
|
147 |
+
#: includes/configs/i18n/readingEase.config.php:3
|
148 |
+
msgid "Score: %s%."
|
149 |
+
msgstr ""
|
150 |
+
|
151 |
+
#: includes/configs/i18n/readingEase.config.php:4
|
152 |
+
msgid "Your content's readability is looking great! It's very easy to understand by the majority of readers!"
|
153 |
+
msgstr ""
|
154 |
+
|
155 |
+
#: includes/configs/i18n/readingEase.config.php:5
|
156 |
+
msgid "The readability of your content is easy to understand by most readers! Awesome!"
|
157 |
+
msgstr ""
|
158 |
+
|
159 |
+
#: includes/configs/i18n/readingEase.config.php:6
|
160 |
+
msgid "Your content is pretty easy to understand by most readers."
|
161 |
+
msgstr ""
|
162 |
+
|
163 |
+
#: includes/configs/i18n/readingEase.config.php:7
|
164 |
+
msgid "The readability of this content is okay, but could use some improvements to reach a wider audience."
|
165 |
+
msgstr ""
|
166 |
+
|
167 |
+
#: includes/configs/i18n/readingEase.config.php:8
|
168 |
+
msgid "Your content is pretty hard to read, so you should try to simplify it."
|
169 |
+
msgstr ""
|
170 |
+
|
171 |
+
#: includes/configs/i18n/readingEase.config.php:9
|
172 |
+
msgid "The content is hard to read. Try shortening some sentences and using less complex wording to improve this score."
|
173 |
+
msgstr ""
|
174 |
+
|
175 |
+
#: includes/configs/i18n/readingEase.config.php:10
|
176 |
+
msgid "The text here is very hard to read, and is best understood by university graduates. You should consider making your sentences shorter and using easier words for people to understand."
|
177 |
+
msgstr ""
|
178 |
+
|
179 |
+
#: includes/configs/i18n/seoDescription.config.php:5
|
180 |
+
msgid "Your custom %1$sSEO Description%2$s is empty! Try adding a description with your focus keyword phrase."
|
181 |
+
msgstr ""
|
182 |
+
|
183 |
+
#: includes/configs/i18n/seoDescription.config.php:10
|
184 |
+
msgid "Your custom %1$sSEO Description%2$s is over the 156 character recommended length, you should consider making it shorter."
|
185 |
+
msgstr ""
|
186 |
+
|
187 |
+
#: includes/configs/i18n/seoDescription.config.php:15
|
188 |
+
msgid "You should make your %1$sSEO Description%2$s longer! We recommend 125-156 characters for the best results."
|
189 |
+
msgstr ""
|
190 |
+
|
191 |
+
#: includes/configs/i18n/seoDescription.config.php:20
|
192 |
+
msgid "Your %1$sSEO Description%2$s looks great, and is optimized for search engines!"
|
193 |
+
msgstr ""
|
194 |
+
|
195 |
+
#: includes/configs/i18n/seoDescription.config.php:31
|
196 |
+
msgid "Try incorporating your focus keyword phrase to your custom %1$sSEO Description%2$s for better optimization!"
|
197 |
+
msgstr ""
|
198 |
+
|
199 |
+
#: includes/configs/i18n/seoDescription.config.php:36
|
200 |
+
msgid "Your focus keyword phrase is used too frequently in your %1$sSEO Description%2$s. You should try removing some of the references."
|
201 |
+
msgstr ""
|
202 |
+
|
203 |
+
#: includes/configs/i18n/seoDescription.config.php:41
|
204 |
+
msgid "The %1$sSEO Description%2$s is properly optimized by using your focus keyword phrase! Good job!"
|
205 |
+
msgstr ""
|
206 |
+
|
207 |
+
#: includes/configs/i18n/seoDescription.config.php:47, includes/configs/i18n/seoTitle.config.php:47
|
208 |
+
msgid "Your title makes use of a stop word. We don't recommend using these as they can negatively imapct your SEO efforts"
|
209 |
+
msgstr ""
|
210 |
+
|
211 |
+
#: includes/configs/i18n/seoDescription.config.php:48, includes/configs/i18n/seoTitle.config.php:48
|
212 |
+
msgid "Your title doesn't use any stop words that will negatively impact your SEO ranking! Good Job!"
|
213 |
+
msgstr ""
|
214 |
+
|
215 |
+
#: includes/configs/i18n/seoTitle.config.php:5
|
216 |
+
msgid "You haven't entered a custom %1$sSEO Title%2$s to your page, you should consider adding one."
|
217 |
+
msgstr ""
|
218 |
+
|
219 |
+
#: includes/configs/i18n/seoTitle.config.php:10
|
220 |
+
msgid "Your custom %1$sSEO Title%2$s is longer than the recommended 70 characters, you should consider making it shorter."
|
221 |
+
msgstr ""
|
222 |
+
|
223 |
+
#: includes/configs/i18n/seoTitle.config.php:15
|
224 |
+
msgid "We suggest making your %1$sSEO Title%2$s at least 30 characters."
|
225 |
+
msgstr ""
|
226 |
+
|
227 |
+
#: includes/configs/i18n/seoTitle.config.php:20
|
228 |
+
msgid "Your %1$sSEO Title%2$s is a good length, and optimized for search engines!"
|
229 |
+
msgstr ""
|
230 |
+
|
231 |
+
#: includes/configs/i18n/seoTitle.config.php:31
|
232 |
+
msgid "You should try to use your focus keyword phrase at least one time in your %1$sSEO Title%2$s."
|
233 |
+
msgstr ""
|
234 |
+
|
235 |
+
#: includes/configs/i18n/seoTitle.config.php:36
|
236 |
+
msgid "It’s great you’ve used the focus keyword phrase in your %1$sSEO Title%2$s, but you should try to only use that one time."
|
237 |
+
msgstr ""
|
238 |
+
|
239 |
+
#: includes/configs/i18n/seoTitle.config.php:41
|
240 |
+
msgid "Your %1$sSEO Title%2$s is optimized by using your focus keyword phrase!"
|
241 |
+
msgstr ""
|
242 |
+
|
243 |
+
#: includes/configs/i18n/stopWords.config.php:2
|
244 |
+
msgid "a,about,above,after,again,against,all,am,an,and,any,are,aren't,as,at,be,because,been,before,being,below,between,both,but,by,can,can't,cannot,could,couldn't,did,didn't,do,does,doesn't,doing,don't,down,during,each,every,few,for,from,further,had,hadn't,has,hasn't,have,haven't,having,he,he'd,he'll,he's,her,here,here's,hers,herself,him,himself,his,how,how's,i,i'd,i'll,i'm,i've,if,in,into,is,isn't,it,it's,its,itself,let's,me,more,most,mustn't,my,myself,no,nor,not,of,off,on,once,only,or,other,ought,our,ours,ourselves,out,over,own,same,shan't,she,she'd,she'll,she's,should,shouldn't,so,some,such,than,that,that's,the,their,theirs,them,themselves,then,there,there's,these,they,they'd,they'll,they're,they've,this,those,through,to,too,under,until,up,very,was,wasn't,we,we'd,we'll,we're,we've,were,weren't,what,what's,when,when's,where,where's,which,while,who,who's,whom,why,why's,with,won't,would,wouldn't,you,you'd,you'll,you're,you've,your,yours,yourself,yourselves"
|
245 |
+
msgstr ""
|
246 |
+
|
247 |
+
#: includes/configs/meta-box.config.php:12
|
248 |
+
msgid "Easy SEO"
|
249 |
+
msgstr ""
|
250 |
+
|
251 |
+
#: includes/configs/meta-box.config.php:19
|
252 |
+
msgid "Keyword Phrase"
|
253 |
+
msgstr ""
|
254 |
+
|
255 |
+
#: includes/configs/meta-box.config.php:23
|
256 |
+
msgid "Title & Description"
|
257 |
+
msgstr ""
|
258 |
+
|
259 |
+
#: includes/configs/meta-box.config.php:27
|
260 |
+
msgid "Search Visibility"
|
261 |
+
msgstr ""
|
262 |
+
|
263 |
+
#: includes/configs/meta-box.config.php:45
|
264 |
+
msgid "SEO Title"
|
265 |
+
msgstr ""
|
266 |
+
|
267 |
+
#: includes/configs/meta-box.config.php:46
|
268 |
+
msgid "This is very important for search engines. The SEO Title is what usually shows as the link to your page in a Search Engine Results Page (SERP)."
|
269 |
+
msgstr ""
|
270 |
+
|
271 |
+
#: includes/configs/meta-box.config.php:57
|
272 |
+
msgid "SEO Description"
|
273 |
+
msgstr ""
|
274 |
+
|
275 |
+
#: includes/configs/meta-box.config.php:58
|
276 |
+
msgid "Typically what will show in a Search Engine Results Page (SERP). This is important, but secondary to your SEO Title."
|
277 |
+
msgstr ""
|
278 |
+
|
279 |
+
#: includes/configs/meta-box.config.php:67
|
280 |
+
msgid "Tell search engines to read and index this page"
|
281 |
+
msgstr ""
|
282 |
+
|
283 |
+
#: includes/configs/meta-box.config.php:69
|
284 |
+
msgid "Yes ( index )"
|
285 |
+
msgstr ""
|
286 |
+
|
287 |
+
#: includes/configs/meta-box.config.php:70
|
288 |
+
msgid "No ( noindex )"
|
289 |
+
msgstr ""
|
290 |
+
|
291 |
+
#: includes/configs/meta-box.config.php:72
|
292 |
+
msgid "Setting this to index means that search engines are encouraged to show your website in their search results."
|
293 |
+
msgstr ""
|
294 |
+
|
295 |
+
#: includes/configs/meta-box.config.php:77
|
296 |
+
msgid "Tell search engines to follow links in this page"
|
297 |
+
msgstr ""
|
298 |
+
|
299 |
+
#: includes/configs/meta-box.config.php:79
|
300 |
+
msgid "Yes ( follow )"
|
301 |
+
msgstr ""
|
302 |
+
|
303 |
+
#: includes/configs/meta-box.config.php:80
|
304 |
+
msgid "No ( nofollow )"
|
305 |
+
msgstr ""
|
306 |
+
|
307 |
+
#: includes/configs/meta-box.config.php:82
|
308 |
+
msgid "Having this set to follow means that search engines are able to count and follow where your links go to."
|
309 |
+
msgstr ""
|
310 |
+
|
311 |
+
#: includes/configs/meta-box.config.php:90
|
312 |
+
msgid "Tell search engines that another page should be read/indexed in place of this page"
|
313 |
+
msgstr ""
|
314 |
+
|
315 |
+
#: includes/configs/meta-box.config.php:91
|
316 |
+
msgid "This is called the canonical URL. We recommend that you leave this field empty, so it will use the default permalink."
|
317 |
+
msgstr ""
|
318 |
+
|
319 |
+
#: includes/configs/meta-box.config.php:105
|
320 |
+
msgid "Target Keyword or Phrase"
|
321 |
+
msgstr ""
|
322 |
+
|
323 |
+
#: includes/configs/meta-box.config.php:106
|
324 |
+
msgid "This should be what the main focus of this page or post is about."
|
325 |
+
msgstr ""
|
readme.txt
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== BoldGrid Easy SEO - Simple and Effective SEO ===
|
2 |
+
Contributors: boldgrid, timph, rramo012, imh_brad, joemoto
|
3 |
+
Tags: seo, search engine optimization, content analysis, readability, boldgrid
|
4 |
+
Requires at least: 4.4
|
5 |
+
Tested up to: 4.9.1
|
6 |
+
Requires PHP: 5.3
|
7 |
+
Stable tag: 1.5.1
|
8 |
+
License: GPLv2 or later
|
9 |
+
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
10 |
+
|
11 |
+
Easy SEO helps you easily create keyword rich content and rank higher in the search engines.
|
12 |
+
|
13 |
+
== Description ==
|
14 |
+
|
15 |
+
= Automatically Optimize Your SEO with BoldGrid Easy SEO and Rank Higher in the Search Engines =
|
16 |
+
BoldGrid Easy SEO will help you create better content and rank higher in the search engines. It analyzes your page content in real-time and makes recommendations that will help you maintain best SEO practices. Simply set a target keyword or phrase and the BoldGrid Easy SEO dashboard will automatically give you easy-to-follow suggestions on improving your on-page SEO. Regardless of your SEO skill level or knowledge, BoldGrid Easy SEO provides simple, yet powerful tools for website search engine optimization. Get automatic assistance with:
|
17 |
+
|
18 |
+
= Optimal Keyword Density =
|
19 |
+
BoldGrid Easy SEO will analyze your content in real-time to ensure your targeted keyword appears at the optimal frequency, as well as in the right places on the page.
|
20 |
+
|
21 |
+
= Page Titles and Descriptions =
|
22 |
+
Control and optimize how your pages appear in Google rankings pages with the title and description editor. BoldGrid Easy SEO will also make sure your target keyword is included.
|
23 |
+
|
24 |
+
= Header Tags and Content =
|
25 |
+
BoldGrid Easy SEO will automatically analyze your header tags and give you recommendations on their proper usage, as well as assisting you with including relevant content.
|
26 |
+
|
27 |
+
= Image Alt Tags =
|
28 |
+
BoldGrid Easy SEO automatically checks your images for alt tags to help you identify areas for SEO improvement.
|
29 |
+
|
30 |
+
= Content Length =
|
31 |
+
Keyword rich content is a prime factor when search engines determine rankings. BoldGrid Easy SEO will assist you in writing better content by tracking your progress and alerting you when you've reached the sweet spot.
|
32 |
+
|
33 |
+
= Search Engine Indexing =
|
34 |
+
Allow (or disallow) search engines from indexing pages with a single click.
|
35 |
+
|
36 |
+
= Follow/No Follow Links =
|
37 |
+
Choose whether your links are visible to search engines and count toward your SEO, or hide them if necessary.
|
38 |
+
|
39 |
+
= Canonical URLs =
|
40 |
+
Avoid duplicate content penalties by indicating a canonical URL where applicable.
|
41 |
+
|
42 |
+
== Frequently Asked Questions ==
|
43 |
+
|
44 |
+
= Where can I find more help? =
|
45 |
+
For information on using the BoldGrid Easy SEO or other BoldGrid plugins, please visit our [Support Center](https://www.boldgrid.com/support/).
|
46 |
+
|
47 |
+
For general questions check out our [Frequently Asked Questions](https://www.boldgrid.com/faqs/) page.
|
48 |
+
|
49 |
+
= Where can I report issues or ask questions? =
|
50 |
+
Bugs can be reported either in the [WordPress support forum](https://wordpress.org/support/plugin/boldgrid-easy-seo) or in the [BoldGrid Support Center](https://www.boldgrid.com/support/questions/).
|
51 |
+
|
52 |
+
= How can I contribute? =
|
53 |
+
|
54 |
+
The BoldGrid Easy SEO plugin is open source software. Join in on our [GitHub repository](https://github.com/BoldGrid/boldgrid-seo/).
|
55 |
+
|
56 |
+
== Installation ==
|
57 |
+
|
58 |
+
= Minimum Requirements =
|
59 |
+
|
60 |
+
* PHP version 5.3 or greater
|
61 |
+
* WordPress 4.4 or greater
|
62 |
+
|
63 |
+
= From within WordPress =
|
64 |
+
1. Visit 'Plugins > Add New'
|
65 |
+
1. Search for 'BoldGrid Easy SEO'
|
66 |
+
1. Activate BoldGrid Easy SEO from your Plugins page.
|
67 |
+
|
68 |
+
= Manually =
|
69 |
+
1. Upload the entire boldgrid-easy-seo folder to the /wp-content/plugins/ directory.
|
70 |
+
1. Activate the plugin through the Plugins menu in WordPress.
|
71 |
+
|
72 |
+
== Screenshots ==
|
73 |
+
|
74 |
+
1. Post and Page analysis.
|
75 |
+
2. Page titles and descriptions.
|
76 |
+
3. Search Visibility.
|
77 |
+
|
78 |
+
== Changelog ==
|
79 |
+
|
80 |
+
= 1.5.1 =
|
81 |
+
|
82 |
+
Release Date: November 14th, 2017
|
83 |
+
|
84 |
+
* Initial Release.
|
uninstall.php
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Fired when the plugin is uninstalled.
|
5 |
+
*
|
6 |
+
* When populating this file, consider the following flow
|
7 |
+
* of control:
|
8 |
+
*
|
9 |
+
* - This method should be static
|
10 |
+
* - Check if the $_REQUEST content actually is the plugin name
|
11 |
+
* - Run an admin referrer check to make sure it goes through authentication
|
12 |
+
* - Verify the output of $_GET makes sense
|
13 |
+
* - Repeat with other user roles. Best directly by using the links/query string parameters.
|
14 |
+
* - Repeat things for multisite. Once for a single site in the network, once sitewide.
|
15 |
+
*
|
16 |
+
* This file may be updated more in future version of the Boilerplate; however, this is the
|
17 |
+
* general skeleton and outline for how the file should work.
|
18 |
+
*
|
19 |
+
* For more information, see the following discussion:
|
20 |
+
* https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913
|
21 |
+
*
|
22 |
+
* @link https://boldgrid.com
|
23 |
+
* @since 1.0.0
|
24 |
+
*
|
25 |
+
* @package Boldgrid_Seo
|
26 |
+
*/
|
27 |
+
|
28 |
+
// If uninstall not called from WordPress, then exit.
|
29 |
+
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
30 |
+
exit;
|
31 |
+
}
|