Version Description
April 16, 2015 =
New: Add reset button to reset search filters (#144)
Tweak: WP-CLI command output improvements via
--format
option for table view, JSON and CSV (#705)Tweak: Add link to https://wp-stream.com in README (#709)
Tweak: Better highlighting on multiple live update rows
Tweak: Limit custom range datepickers based on the Stream plan type
Tweak: Limit legacy record migrations based on the Stream plan type
Fix: Allow properties with values of zero to be included in queries (#698)
Fix: Properly return record success/failure in log and store methods (#711)
Props @fjarrett, @szepeviktor
Download this release
Release Info
Developer | lukecarbis |
Plugin | Stream |
Version | 2.0.4 |
Comparing to | |
See all releases |
Version 2.0.4
- .ci-env.sh +2 -0
- .jscsrc +12 -0
- LICENSE +339 -0
- classes/class-wp-stream-admin.php +1279 -0
- classes/class-wp-stream-api.php +468 -0
- classes/class-wp-stream-author.php +288 -0
- classes/class-wp-stream-connector.php +236 -0
- classes/class-wp-stream-connectors.php +177 -0
- classes/class-wp-stream-dashboard-widget.php +272 -0
- classes/class-wp-stream-date-interval.php +112 -0
- classes/class-wp-stream-db.php +168 -0
- classes/class-wp-stream-feeds.php +264 -0
- classes/class-wp-stream-filter-input.php +115 -0
- classes/class-wp-stream-list-table.php +923 -0
- classes/class-wp-stream-live-update.php +222 -0
- classes/class-wp-stream-log.php +304 -0
- classes/class-wp-stream-migrate.php +593 -0
- classes/class-wp-stream-pointers.php +174 -0
- classes/class-wp-stream-query.php +279 -0
- classes/class-wp-stream-record.php +69 -0
- classes/class-wp-stream-settings.php +927 -0
- connectors/class-wp-stream-connector-acf.php +542 -0
- connectors/class-wp-stream-connector-bbpress.php +232 -0
- connectors/class-wp-stream-connector-buddypress.php +814 -0
- connectors/class-wp-stream-connector-comments.php +594 -0
- connectors/class-wp-stream-connector-edd.php +490 -0
- connectors/class-wp-stream-connector-editor.php +311 -0
- connectors/class-wp-stream-connector-gravityforms.php +729 -0
- connectors/class-wp-stream-connector-installer.php +374 -0
- connectors/class-wp-stream-connector-jetpack.php +672 -0
- connectors/class-wp-stream-connector-media.php +215 -0
- connectors/class-wp-stream-connector-menus.php +217 -0
- connectors/class-wp-stream-connector-posts.php +350 -0
- connectors/class-wp-stream-connector-settings.php +702 -0
- connectors/class-wp-stream-connector-stream.php +98 -0
- connectors/class-wp-stream-connector-taxonomies.php +225 -0
- connectors/class-wp-stream-connector-users.php +340 -0
- connectors/class-wp-stream-connector-widgets.php +805 -0
- connectors/class-wp-stream-connector-woocommerce.php +776 -0
- connectors/class-wp-stream-connector-wordpress-seo.php +487 -0
- contributing.md +106 -0
- extensions/notifications/class-wp-stream-notifications.php +273 -0
- extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-email.php +63 -0
- extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-push.php +190 -0
- extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-sms.php +98 -0
- extensions/notifications/includes/class-wp-stream-notifications-adapter.php +119 -0
- extensions/notifications/includes/class-wp-stream-notifications-list-table.php +246 -0
- extensions/notifications/includes/class-wp-stream-notifications-matcher.php +511 -0
- extensions/notifications/includes/class-wp-stream-notifications-post-type.php +850 -0
- extensions/notifications/includes/class-wp-stream-notifications-settings.php +107 -0
- extensions/notifications/ui/css/form.css +353 -0
- extensions/notifications/ui/images/stream-notifications-example.jpg +0 -0
- extensions/notifications/ui/js/form.js +571 -0
- extensions/notifications/ui/js/list.js +14 -0
- extensions/notifications/views/form-templates.php +132 -0
- extensions/reports/class-wp-stream-reports.php +331 -0
- extensions/reports/includes/class-wp-stream-reports-charts.php +292 -0
- extensions/reports/includes/class-wp-stream-reports-date-interval.php +67 -0
- extensions/reports/includes/class-wp-stream-reports-meta-boxes.php +895 -0
- extensions/reports/includes/class-wp-stream-reports-settings.php +233 -0
- extensions/reports/includes/template-tags.php +116 -0
- extensions/reports/ui/css/stream-reports.css +288 -0
- extensions/reports/ui/images/stream-reports-example.jpg +0 -0
- extensions/reports/ui/js/stream-reports.js +778 -0
- extensions/reports/ui/lib/d3/d3.min.js +5 -0
- extensions/reports/ui/lib/nvd3/nv.d3.min.css +1 -0
- extensions/reports/ui/lib/nvd3/nv.d3.min.js +6 -0
.ci-env.sh
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
1 |
+
export WPCS_STANDARD=WordPress-Extra
|
2 |
+
export PHPCS_IGNORE='tests/*,includes/lib/*,dev-lib/*'
|
.jscsrc
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"preset": "wordpress",
|
3 |
+
"excludeFiles": [
|
4 |
+
"**/vendor/**",
|
5 |
+
"**.min.js",
|
6 |
+
"**/node_modules/**",
|
7 |
+
"ui/js/**",
|
8 |
+
"ui/lib/**",
|
9 |
+
"extensions/*/ui/js/**",
|
10 |
+
"extensions/*/ui/lib/**"
|
11 |
+
]
|
12 |
+
}
|
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.
|
classes/class-wp-stream-admin.php
ADDED
@@ -0,0 +1,1279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Admin {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Menu page screen id
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $screen_id = array();
|
11 |
+
|
12 |
+
/**
|
13 |
+
* List table object
|
14 |
+
*
|
15 |
+
* @var WP_Stream_List_Table
|
16 |
+
*/
|
17 |
+
public static $list_table = null;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Option to disable access to Stream
|
21 |
+
*
|
22 |
+
* @var bool
|
23 |
+
*/
|
24 |
+
public static $disable_access = false;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* URL used for authenticating with Stream
|
28 |
+
*
|
29 |
+
* @var string
|
30 |
+
*/
|
31 |
+
public static $connect_url;
|
32 |
+
|
33 |
+
const ADMIN_BODY_CLASS = 'wp_stream_screen';
|
34 |
+
const RECORDS_PAGE_SLUG = 'wp_stream';
|
35 |
+
const SETTINGS_PAGE_SLUG = 'wp_stream_settings';
|
36 |
+
const ACCOUNT_PAGE_SLUG = 'wp_stream_account';
|
37 |
+
const ADMIN_PARENT_PAGE = 'admin.php';
|
38 |
+
const VIEW_CAP = 'view_stream';
|
39 |
+
const SETTINGS_CAP = 'manage_options';
|
40 |
+
const UNREAD_COUNT_OPTION_KEY = 'stream_unread_count';
|
41 |
+
const LAST_READ_OPTION_KEY = 'stream_last_read';
|
42 |
+
const PRELOAD_AUTHORS_MAX = 50;
|
43 |
+
const PUBLIC_URL = 'https://wp-stream.com';
|
44 |
+
|
45 |
+
public static function load() {
|
46 |
+
// User and role caps
|
47 |
+
add_filter( 'user_has_cap', array( __CLASS__, '_filter_user_caps' ), 10, 4 );
|
48 |
+
add_filter( 'role_has_cap', array( __CLASS__, '_filter_role_caps' ), 10, 3 );
|
49 |
+
|
50 |
+
$home_url = str_ireplace( array( 'http://', 'https://' ), '', home_url() );
|
51 |
+
$connect_nonce = wp_create_nonce( 'stream_connect_site-' . sanitize_key( $home_url ) );
|
52 |
+
|
53 |
+
self::$connect_url = add_query_arg(
|
54 |
+
array(
|
55 |
+
'auth' => 'true',
|
56 |
+
'action' => 'connect',
|
57 |
+
'home_url' => urlencode( $home_url ),
|
58 |
+
'plugin_url' => urlencode(
|
59 |
+
add_query_arg(
|
60 |
+
array(
|
61 |
+
'page' => self::RECORDS_PAGE_SLUG,
|
62 |
+
'nonce' => $connect_nonce,
|
63 |
+
),
|
64 |
+
admin_url( self::ADMIN_PARENT_PAGE )
|
65 |
+
)
|
66 |
+
),
|
67 |
+
),
|
68 |
+
esc_url_raw( untrailingslashit( self::PUBLIC_URL ) . '/pricing/' )
|
69 |
+
);
|
70 |
+
|
71 |
+
$api_key = wp_stream_filter_input( INPUT_GET, 'api_key' );
|
72 |
+
$site_uuid = wp_stream_filter_input( INPUT_GET, 'site_uuid' );
|
73 |
+
|
74 |
+
// Connect
|
75 |
+
if ( ! empty( $api_key ) && ! empty( $site_uuid ) ) {
|
76 |
+
add_action( 'admin_init', array( __CLASS__, 'save_api_authentication' ) );
|
77 |
+
}
|
78 |
+
|
79 |
+
// Disconnect
|
80 |
+
if ( self::ACCOUNT_PAGE_SLUG === wp_stream_filter_input( INPUT_GET, 'page' ) && '1' === wp_stream_filter_input( INPUT_GET, 'disconnect' ) ) {
|
81 |
+
add_action( 'admin_init', array( __CLASS__, 'remove_api_authentication' ) );
|
82 |
+
}
|
83 |
+
|
84 |
+
self::$disable_access = apply_filters( 'wp_stream_disable_admin_access', false );
|
85 |
+
|
86 |
+
// Register settings page
|
87 |
+
if ( ! self::$disable_access ) {
|
88 |
+
add_action( 'admin_menu', array( __CLASS__, 'register_menu' ) );
|
89 |
+
}
|
90 |
+
|
91 |
+
// Admin notices
|
92 |
+
add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
|
93 |
+
|
94 |
+
// Show connect notice on dashboard and plugins pages
|
95 |
+
add_action( 'load-index.php', array( __CLASS__, 'prepare_connect_notice' ) );
|
96 |
+
add_action( 'load-plugins.php', array( __CLASS__, 'prepare_connect_notice' ) );
|
97 |
+
|
98 |
+
// Add admin body class
|
99 |
+
add_filter( 'admin_body_class', array( __CLASS__, 'admin_body_class' ) );
|
100 |
+
|
101 |
+
// Plugin action links
|
102 |
+
add_filter( 'plugin_action_links', array( __CLASS__, 'plugin_action_links' ), 10, 2 );
|
103 |
+
|
104 |
+
// Load admin scripts and styles
|
105 |
+
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
|
106 |
+
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_menu_css' ) );
|
107 |
+
|
108 |
+
// Catch list table results
|
109 |
+
add_filter( 'wp_stream_query_results', array( __CLASS__, 'mark_as_read' ), 10, 3 );
|
110 |
+
|
111 |
+
// Add user option for enabling unread counts
|
112 |
+
add_action( 'show_user_profile', array( __CLASS__, 'unread_count_user_option' ) );
|
113 |
+
add_action( 'edit_user_profile', array( __CLASS__, 'unread_count_user_option' ) );
|
114 |
+
|
115 |
+
// Save unread counts user option
|
116 |
+
add_action( 'personal_options_update', array( __CLASS__, 'save_unread_count_user_option' ) );
|
117 |
+
add_action( 'edit_user_profile_update', array( __CLASS__, 'save_unread_count_user_option' ) );
|
118 |
+
|
119 |
+
// Delete user-specific transient when user is deleted
|
120 |
+
add_action( 'delete_user', array( __CLASS__, 'delete_unread_count_transient' ), 10, 1 );
|
121 |
+
|
122 |
+
// Reset Streams settings
|
123 |
+
add_action( 'wp_ajax_wp_stream_defaults', array( __CLASS__, 'wp_ajax_defaults' ) );
|
124 |
+
|
125 |
+
// Ajax authors list
|
126 |
+
add_action( 'wp_ajax_wp_stream_filters', array( __CLASS__, 'ajax_filters' ) );
|
127 |
+
|
128 |
+
// Ajax author's name by ID
|
129 |
+
add_action( 'wp_ajax_wp_stream_get_filter_value_by_id', array( __CLASS__, 'get_filter_value_by_id' ) );
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Prepare the Connect to Stream prompt
|
134 |
+
*
|
135 |
+
* @return void
|
136 |
+
*/
|
137 |
+
public static function prepare_connect_notice() {
|
138 |
+
if ( ! WP_Stream::is_connected() && ! WP_Stream::is_development_mode() ) {
|
139 |
+
wp_enqueue_style( 'wp-stream-connect', WP_STREAM_URL . 'ui/css/connect.css', array(), WP_Stream::VERSION );
|
140 |
+
wp_enqueue_script( 'wp-stream-connect', WP_STREAM_URL . 'ui/js/connect.js', array(), WP_Stream::VERSION );
|
141 |
+
add_action( 'admin_notices', array( __CLASS__, 'admin_connect_notice' ) );
|
142 |
+
}
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Prompt the user to connect to Stream
|
147 |
+
*
|
148 |
+
* @return void
|
149 |
+
*/
|
150 |
+
public static function admin_connect_notice() {
|
151 |
+
if ( ! current_user_can( self::SETTINGS_CAP ) ) {
|
152 |
+
return;
|
153 |
+
}
|
154 |
+
|
155 |
+
$dismiss_and_deactivate_url = wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . WP_STREAM_PLUGIN, 'deactivate-plugin_' . WP_STREAM_PLUGIN );
|
156 |
+
?>
|
157 |
+
<div id="stream-message" class="updated stream-connect" style="display:block !important;">
|
158 |
+
|
159 |
+
<div class="stream-message-container">
|
160 |
+
|
161 |
+
<div class="stream-button-container">
|
162 |
+
<a href="<?php echo esc_url( self::$connect_url ) ?>" class="stream-button"><i class="stream-icon"></i><?php _e( 'Connect to Stream', 'stream' ) ?></a>
|
163 |
+
</div>
|
164 |
+
|
165 |
+
<div class="stream-message-text">
|
166 |
+
<h4><?php esc_html_e( 'Stream is almost ready!', 'stream' ) ?></h4>
|
167 |
+
<p>
|
168 |
+
<?php
|
169 |
+
$tooltip = sprintf(
|
170 |
+
esc_html__( 'Stream only uses your WordPress.com ID during sign up to authorize your account. You can sign up for free at %swordpress.com/signup%s.', 'stream' ),
|
171 |
+
'<a href="https://signup.wordpress.com/signup/?user=1" target="_blank">',
|
172 |
+
'</a>'
|
173 |
+
);
|
174 |
+
echo wp_kses_post(
|
175 |
+
sprintf(
|
176 |
+
esc_html__( 'Connect to Stream with your %sWordPress.com ID%s to see every change made to your site in beautifully organized detail.', 'stream' ),
|
177 |
+
'<span class="wp-stream-tooltip-text">',
|
178 |
+
'</span><span class="wp-stream-tooltip">' . $tooltip . '</span>' // xss ok
|
179 |
+
)
|
180 |
+
);
|
181 |
+
?>
|
182 |
+
</p>
|
183 |
+
</div>
|
184 |
+
|
185 |
+
<div class="clear"></div>
|
186 |
+
|
187 |
+
</div>
|
188 |
+
|
189 |
+
</div>
|
190 |
+
<?php
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Output specific update
|
195 |
+
*
|
196 |
+
* @action admin_notices
|
197 |
+
* @return string
|
198 |
+
*/
|
199 |
+
public static function admin_notices() {
|
200 |
+
$message = wp_stream_filter_input( INPUT_GET, 'message' );
|
201 |
+
$notice = false;
|
202 |
+
|
203 |
+
switch ( $message ) {
|
204 |
+
case 'settings_reset':
|
205 |
+
$notice = esc_html__( 'All site settings have been successfully reset.', 'stream' );
|
206 |
+
break;
|
207 |
+
case 'connected':
|
208 |
+
if ( ! WP_Stream_Migrate::show_migrate_notice() ) {
|
209 |
+
$notice = sprintf(
|
210 |
+
'<strong>%s</strong></p><p>%s',
|
211 |
+
esc_html__( 'You have successfully connected to Stream!', 'stream' ),
|
212 |
+
esc_html__( 'Check back here regularly to see a history of the changes being made to this site.', 'stream' )
|
213 |
+
);
|
214 |
+
}
|
215 |
+
break;
|
216 |
+
}
|
217 |
+
|
218 |
+
if ( $notice ) {
|
219 |
+
WP_Stream::notice( $notice, false );
|
220 |
+
}
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* Return URL used for account level actions
|
225 |
+
*
|
226 |
+
* @return string
|
227 |
+
*/
|
228 |
+
public static function account_url( $path = '' ) {
|
229 |
+
$account_url = add_query_arg(
|
230 |
+
array(
|
231 |
+
'auth' => 'true',
|
232 |
+
'plugin_url' => urlencode(
|
233 |
+
add_query_arg(
|
234 |
+
array(
|
235 |
+
'page' => self::RECORDS_PAGE_SLUG,
|
236 |
+
),
|
237 |
+
admin_url( self::ADMIN_PARENT_PAGE )
|
238 |
+
)
|
239 |
+
),
|
240 |
+
),
|
241 |
+
esc_url_raw( sprintf( '%s/dashboard/%s', untrailingslashit( self::PUBLIC_URL ), untrailingslashit( $path ) ) )
|
242 |
+
);
|
243 |
+
|
244 |
+
return $account_url;
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Register menu page
|
249 |
+
*
|
250 |
+
* @action admin_menu
|
251 |
+
* @return bool|void
|
252 |
+
*/
|
253 |
+
public static function register_menu() {
|
254 |
+
if ( WP_Stream::is_connected() || WP_Stream::is_development_mode() ) {
|
255 |
+
$unread_count = self::get_unread_count();
|
256 |
+
$menu_title = __( 'Stream', 'stream' );
|
257 |
+
|
258 |
+
if ( self::unread_enabled_for_user() && ! empty( $unread_count ) ) {
|
259 |
+
$formatted_count = ( $unread_count > 99 ) ? __( '99 +', 'stream' ) : absint( $unread_count );
|
260 |
+
$menu_title = sprintf( '%s <span class="update-plugins count-%d"><span class="plugin-count">%s</span></span>', esc_html( $menu_title ), absint( $unread_count ), esc_html( $formatted_count ) );
|
261 |
+
}
|
262 |
+
|
263 |
+
self::$screen_id['main'] = add_menu_page(
|
264 |
+
__( 'Stream', 'stream' ),
|
265 |
+
$menu_title,
|
266 |
+
self::VIEW_CAP,
|
267 |
+
self::RECORDS_PAGE_SLUG,
|
268 |
+
array( __CLASS__, 'render_stream_page' ),
|
269 |
+
'div',
|
270 |
+
apply_filters( 'wp_stream_menu_position', '2.999999' ) // Using longtail decimal string to reduce the chance of position conflicts, see Codex
|
271 |
+
);
|
272 |
+
|
273 |
+
self::$screen_id['settings'] = add_submenu_page(
|
274 |
+
self::RECORDS_PAGE_SLUG,
|
275 |
+
__( 'Stream Settings', 'stream' ),
|
276 |
+
__( 'Settings', 'stream' ),
|
277 |
+
self::SETTINGS_CAP,
|
278 |
+
self::SETTINGS_PAGE_SLUG,
|
279 |
+
array( __CLASS__, 'render_settings_page' )
|
280 |
+
);
|
281 |
+
|
282 |
+
self::$screen_id['account'] = add_submenu_page(
|
283 |
+
self::RECORDS_PAGE_SLUG,
|
284 |
+
__( 'Stream Account', 'stream' ),
|
285 |
+
__( 'Account', 'stream' ),
|
286 |
+
self::SETTINGS_CAP,
|
287 |
+
self::ACCOUNT_PAGE_SLUG,
|
288 |
+
array( __CLASS__, 'render_account_page' )
|
289 |
+
);
|
290 |
+
} else {
|
291 |
+
self::$screen_id['connect'] = add_menu_page(
|
292 |
+
__( 'Connect to Stream', 'stream' ),
|
293 |
+
__( 'Stream', 'stream' ),
|
294 |
+
self::SETTINGS_CAP,
|
295 |
+
self::RECORDS_PAGE_SLUG,
|
296 |
+
array( __CLASS__, 'render_connect_page' ),
|
297 |
+
'div',
|
298 |
+
apply_filters( 'wp_stream_menu_position', '2.999999' ) // Using longtail decimal string to reduce the chance of position conflicts, see Codex
|
299 |
+
);
|
300 |
+
}
|
301 |
+
|
302 |
+
if ( isset( self::$screen_id['main'] ) ) {
|
303 |
+
/**
|
304 |
+
* Fires just before the Stream list table is registered.
|
305 |
+
*
|
306 |
+
* @since 1.4.0
|
307 |
+
*
|
308 |
+
* @return void
|
309 |
+
*/
|
310 |
+
do_action( 'wp_stream_admin_menu_screens' );
|
311 |
+
|
312 |
+
// Register the list table early, so it associates the column headers with 'Screen settings'
|
313 |
+
add_action( 'load-' . self::$screen_id['main'], array( __CLASS__, 'register_list_table' ) );
|
314 |
+
}
|
315 |
+
}
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Enqueue scripts/styles for admin screen
|
319 |
+
*
|
320 |
+
* @action admin_enqueue_scripts
|
321 |
+
*
|
322 |
+
* @param $hook
|
323 |
+
*
|
324 |
+
* @return void
|
325 |
+
*/
|
326 |
+
public static function admin_enqueue_scripts( $hook ) {
|
327 |
+
wp_register_script( 'select2', WP_STREAM_URL . 'ui/lib/select2/select2.js', array( 'jquery' ), '3.5.2', true );
|
328 |
+
wp_register_style( 'select2', WP_STREAM_URL . 'ui/lib/select2/select2.css', array(), '3.5.2' );
|
329 |
+
wp_register_script( 'timeago', WP_STREAM_URL . 'ui/lib/timeago/jquery.timeago.js', array(), '1.4.1', true );
|
330 |
+
|
331 |
+
$locale = strtolower( substr( get_locale(), 0, 2 ) );
|
332 |
+
$file_tmpl = 'ui/lib/timeago/locales/jquery.timeago.%s.js';
|
333 |
+
|
334 |
+
if ( file_exists( WP_STREAM_DIR . sprintf( $file_tmpl, $locale ) ) ) {
|
335 |
+
wp_register_script( 'timeago-locale', WP_STREAM_URL . sprintf( $file_tmpl, $locale ), array( 'timeago' ), '1' );
|
336 |
+
} else {
|
337 |
+
wp_register_script( 'timeago-locale', WP_STREAM_URL . sprintf( $file_tmpl, 'en' ), array( 'timeago' ), '1' );
|
338 |
+
}
|
339 |
+
|
340 |
+
wp_enqueue_style( 'wp-stream-admin', WP_STREAM_URL . 'ui/css/admin.css', array(), WP_Stream::VERSION );
|
341 |
+
|
342 |
+
$script_screens = array( 'plugins.php', 'user-edit.php', 'user-new.php', 'profile.php' );
|
343 |
+
|
344 |
+
if ( 'index.php' === $hook ) {
|
345 |
+
wp_enqueue_script( 'wp-stream-dashboard', WP_STREAM_URL . 'ui/js/dashboard.js', array( 'jquery' ), WP_Stream::VERSION );
|
346 |
+
wp_enqueue_script( 'wp-stream-live-updates', WP_STREAM_URL . 'ui/js/live-updates.js', array( 'jquery', 'heartbeat' ), WP_Stream::VERSION );
|
347 |
+
} elseif ( in_array( $hook, self::$screen_id ) || in_array( $hook, $script_screens ) ) {
|
348 |
+
wp_enqueue_script( 'select2' );
|
349 |
+
wp_enqueue_style( 'select2' );
|
350 |
+
|
351 |
+
wp_enqueue_script( 'timeago' );
|
352 |
+
wp_enqueue_script( 'timeago-locale' );
|
353 |
+
|
354 |
+
wp_enqueue_script( 'wp-stream-admin', WP_STREAM_URL . 'ui/js/admin.js', array( 'jquery', 'select2' ), WP_Stream::VERSION );
|
355 |
+
wp_enqueue_script( 'wp-stream-live-updates', WP_STREAM_URL . 'ui/js/live-updates.js', array( 'jquery', 'heartbeat' ), WP_Stream::VERSION );
|
356 |
+
|
357 |
+
wp_localize_script(
|
358 |
+
'wp-stream-admin',
|
359 |
+
'wp_stream',
|
360 |
+
array(
|
361 |
+
'i18n' => array(
|
362 |
+
'confirm_defaults' => __( 'Are you sure you want to reset all site settings to default? This cannot be undone.', 'stream' ),
|
363 |
+
),
|
364 |
+
'locale' => esc_js( $locale ),
|
365 |
+
'gmt_offset' => get_option( 'gmt_offset' ),
|
366 |
+
'plan' => array(
|
367 |
+
'type' => esc_js( WP_Stream::$api->get_plan_type() ),
|
368 |
+
'retention' => esc_js( WP_Stream::$api->get_plan_retention() ),
|
369 |
+
),
|
370 |
+
)
|
371 |
+
);
|
372 |
+
}
|
373 |
+
|
374 |
+
wp_localize_script(
|
375 |
+
'wp-stream-live-updates',
|
376 |
+
'wp_stream_live_updates',
|
377 |
+
array(
|
378 |
+
'current_screen' => $hook,
|
379 |
+
'current_page' => isset( $_GET['paged'] ) ? esc_js( $_GET['paged'] ) : '1',
|
380 |
+
'current_order' => isset( $_GET['order'] ) ? esc_js( $_GET['order'] ) : 'desc',
|
381 |
+
'current_query' => json_encode( $_GET ),
|
382 |
+
'current_query_count' => count( $_GET ),
|
383 |
+
)
|
384 |
+
);
|
385 |
+
|
386 |
+
if ( WP_Stream_Migrate::show_migrate_notice() ) {
|
387 |
+
$limit = absint( WP_Stream_Migrate::$limit );
|
388 |
+
$record_count = absint( WP_Stream_Migrate::$record_count );
|
389 |
+
$estimated_time = ( $limit < $record_count ) ? round( ( ( $record_count / $limit ) * ( 0.04 * $limit ) ) / 60 ) : 0;
|
390 |
+
$migrate_time_message = ( $estimated_time > 1 ) ? sprintf( __( 'This will take about %d minutes.', 'stream' ), absint( $estimated_time ) ) : __( 'This could take a few minutes.', 'stream' );
|
391 |
+
$delete_time_message = ( $estimated_time > 1 && is_multisite() ) ? sprintf( __( 'This will take about %d minutes.', 'stream' ), absint( $estimated_time ) ) : __( 'This could take a few minutes.', 'stream' );
|
392 |
+
|
393 |
+
wp_enqueue_script( 'wp-stream-migrate', WP_STREAM_URL . 'ui/js/migrate.js', array( 'jquery' ), WP_Stream::VERSION );
|
394 |
+
wp_localize_script(
|
395 |
+
'wp-stream-migrate',
|
396 |
+
'wp_stream_migrate',
|
397 |
+
array(
|
398 |
+
'i18n' => array(
|
399 |
+
'migrate_process_title' => __( 'Migrating Stream Records', 'stream' ),
|
400 |
+
'delete_process_title' => __( 'Deleting Stream Records', 'stream' ),
|
401 |
+
'error_message' => __( 'An unknown error occurred during migration. Please try again later or contact support.', 'stream' ),
|
402 |
+
'migrate_process_message' => __( 'Please do not exit this page until the process has completed.', 'stream' ) . ' ' . esc_html( $migrate_time_message ),
|
403 |
+
'delete_process_message' => __( 'Please do not exit this page until the process has completed.', 'stream' ) . ' ' . esc_html( $delete_time_message ),
|
404 |
+
'confirm_start_migrate' => ( $estimated_time > 1 ) ? sprintf( __( 'Please note: This process will take about %d minutes to complete.', 'stream' ), absint( $estimated_time ) ) : __( 'Please note: This process could take a few minutes to complete.', 'stream' ),
|
405 |
+
'confirm_migrate_reminder' => __( 'Please note: Your existing records will not appear in Stream until you have migrated them to your account.', 'stream' ),
|
406 |
+
'confirm_delete_records' => sprintf( __( 'Are you sure you want to delete all %s existing Stream records without migrating? This will take %s minutes and cannot be undone.', 'stream' ), number_format( WP_Stream_Migrate::$record_count ), ( $estimated_time > 1 && is_multisite() ) ? sprintf( __( 'about %d', 'stream' ), absint( $estimated_time ) ) : __( 'a few', 'stream' ) ),
|
407 |
+
),
|
408 |
+
'chunk_size' => absint( $limit ),
|
409 |
+
'record_count' => absint( $record_count ),
|
410 |
+
'nonce' => wp_create_nonce( 'wp_stream_migrate-' . absint( get_current_blog_id() ) . absint( get_current_user_id() ) ),
|
411 |
+
)
|
412 |
+
);
|
413 |
+
}
|
414 |
+
|
415 |
+
/**
|
416 |
+
* The maximum number of items that can be updated in bulk without receiving a warning.
|
417 |
+
*
|
418 |
+
* Stream watches for bulk actions performed in the WordPress Admin (such as updating
|
419 |
+
* many posts at once) and warns the user before proceeding if the number of items they
|
420 |
+
* are attempting to update exceeds this threshold value. Since Stream will try to save
|
421 |
+
* a log for each item, it will take longer than usual to complete the operation.
|
422 |
+
*
|
423 |
+
* The default threshold is 100 items.
|
424 |
+
*
|
425 |
+
* @return int
|
426 |
+
*/
|
427 |
+
$bulk_actions_threshold = apply_filters( 'wp_stream_bulk_actions_threshold', 100 );
|
428 |
+
|
429 |
+
wp_enqueue_script( 'wp-stream-global', WP_STREAM_URL . 'ui/js/global.js', array( 'jquery' ), WP_Stream::VERSION );
|
430 |
+
wp_localize_script(
|
431 |
+
'wp-stream-global',
|
432 |
+
'wp_stream_global',
|
433 |
+
array(
|
434 |
+
'bulk_actions' => array(
|
435 |
+
'i18n' => array(
|
436 |
+
'confirm_action' => sprintf( __( 'Are you sure you want to perform bulk actions on over %s items? This process could take a while to complete.', 'stream' ), number_format( absint( $bulk_actions_threshold ) ) ),
|
437 |
+
),
|
438 |
+
'threshold' => absint( $bulk_actions_threshold ),
|
439 |
+
),
|
440 |
+
'plugins_screen_url' => self_admin_url( 'plugins.php#stream' ),
|
441 |
+
)
|
442 |
+
);
|
443 |
+
}
|
444 |
+
|
445 |
+
/**
|
446 |
+
* Check whether or not the current admin screen belongs to Stream
|
447 |
+
*
|
448 |
+
* @return bool
|
449 |
+
*/
|
450 |
+
public static function is_stream_screen() {
|
451 |
+
global $typenow;
|
452 |
+
|
453 |
+
if ( is_admin() && ( false !== strpos( wp_stream_filter_input( INPUT_GET, 'page' ), self::RECORDS_PAGE_SLUG ) || WP_Stream_Notifications_Post_Type::POSTTYPE === $typenow ) ) {
|
454 |
+
return true;
|
455 |
+
}
|
456 |
+
|
457 |
+
return false;
|
458 |
+
}
|
459 |
+
|
460 |
+
/**
|
461 |
+
* Add a specific body class to all Stream admin screens
|
462 |
+
*
|
463 |
+
* @filter admin_body_class
|
464 |
+
*
|
465 |
+
* @param string $classes
|
466 |
+
*
|
467 |
+
* @return string $classes
|
468 |
+
*/
|
469 |
+
public static function admin_body_class( $classes ) {
|
470 |
+
if ( self::is_stream_screen() ) {
|
471 |
+
$classes .= sprintf( ' %s ', self::ADMIN_BODY_CLASS );
|
472 |
+
|
473 |
+
if ( WP_Stream::is_connected() || WP_Stream::is_development_mode() ) {
|
474 |
+
$classes .= ' wp_stream_connected ';
|
475 |
+
} else {
|
476 |
+
$classes .= ' wp_stream_disconnected ';
|
477 |
+
}
|
478 |
+
|
479 |
+
if ( WP_Stream::$api->is_restricted() ) {
|
480 |
+
$classes .= ' wp_stream_restricted ';
|
481 |
+
}
|
482 |
+
}
|
483 |
+
|
484 |
+
$settings_pages = array( self::SETTINGS_PAGE_SLUG );
|
485 |
+
|
486 |
+
if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $settings_pages ) ) {
|
487 |
+
$classes .= sprintf( ' %s ', self::SETTINGS_PAGE_SLUG );
|
488 |
+
}
|
489 |
+
|
490 |
+
return $classes;
|
491 |
+
}
|
492 |
+
|
493 |
+
/**
|
494 |
+
* Add menu styles for various WP Admin skins
|
495 |
+
*
|
496 |
+
* @uses wp_add_inline_style()
|
497 |
+
* @action admin_enqueue_scripts
|
498 |
+
* @return bool true on success false on failure
|
499 |
+
*/
|
500 |
+
public static function admin_menu_css() {
|
501 |
+
wp_register_style( 'jquery-ui', '//ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css', array(), '1.10.1' );
|
502 |
+
wp_register_style( 'wp-stream-datepicker', WP_STREAM_URL . 'ui/css/datepicker.css', array( 'jquery-ui' ), WP_Stream::VERSION );
|
503 |
+
wp_register_style( 'wp-stream-icons', WP_STREAM_URL . 'ui/stream-icons/style.css', array(), WP_Stream::VERSION );
|
504 |
+
|
505 |
+
// Make sure we're working off a clean version
|
506 |
+
include( ABSPATH . WPINC . '/version.php' );
|
507 |
+
|
508 |
+
$body_class = self::ADMIN_BODY_CLASS;
|
509 |
+
$records_page = self::RECORDS_PAGE_SLUG;
|
510 |
+
$stream_url = WP_STREAM_URL;
|
511 |
+
|
512 |
+
if ( version_compare( $wp_version, '3.8-alpha', '>=' ) ) {
|
513 |
+
wp_enqueue_style( 'wp-stream-icons' );
|
514 |
+
$css = "
|
515 |
+
#toplevel_page_{$records_page} .wp-menu-image:before {
|
516 |
+
font-family: 'WP Stream' !important;
|
517 |
+
content: '\\73' !important;
|
518 |
+
}
|
519 |
+
#toplevel_page_{$records_page} .wp-menu-image {
|
520 |
+
background-repeat: no-repeat;
|
521 |
+
}
|
522 |
+
#menu-posts-feedback .wp-menu-image:before {
|
523 |
+
font-family: dashicons !important;
|
524 |
+
content: '\\f175';
|
525 |
+
}
|
526 |
+
#adminmenu #menu-posts-feedback div.wp-menu-image {
|
527 |
+
background: none !important;
|
528 |
+
background-repeat: no-repeat;
|
529 |
+
}
|
530 |
+
body.{$body_class} #wpbody-content .wrap h2:nth-child(1):before {
|
531 |
+
font-family: 'WP Stream' !important;
|
532 |
+
content: '\\73';
|
533 |
+
padding: 0 8px 0 0;
|
534 |
+
}
|
535 |
+
";
|
536 |
+
} else {
|
537 |
+
$css = "
|
538 |
+
#toplevel_page_{$records_page} .wp-menu-image {
|
539 |
+
background: url( {$stream_url}ui/stream-icons/menuicon-sprite.png ) 0 90% no-repeat;
|
540 |
+
}
|
541 |
+
/* Retina Stream Menu Icon */
|
542 |
+
@media only screen and (-moz-min-device-pixel-ratio: 1.5),
|
543 |
+
only screen and (-o-min-device-pixel-ratio: 3/2),
|
544 |
+
only screen and (-webkit-min-device-pixel-ratio: 1.5),
|
545 |
+
only screen and (min-device-pixel-ratio: 1.5) {
|
546 |
+
#toplevel_page_{$records_page} .wp-menu-image {
|
547 |
+
background: url( {$stream_url}ui/stream-icons/menuicon-sprite-2x.png ) 0 90% no-repeat;
|
548 |
+
background-size:30px 64px;
|
549 |
+
}
|
550 |
+
}
|
551 |
+
#toplevel_page_{$records_page}.current .wp-menu-image,
|
552 |
+
#toplevel_page_{$records_page}.wp-has-current-submenu .wp-menu-image,
|
553 |
+
#toplevel_page_{$records_page}:hover .wp-menu-image {
|
554 |
+
background-position: top left;
|
555 |
+
}
|
556 |
+
";
|
557 |
+
}
|
558 |
+
|
559 |
+
wp_add_inline_style( 'wp-admin', $css );
|
560 |
+
}
|
561 |
+
|
562 |
+
/**
|
563 |
+
* Check whether or not the current user should see the unread counter.
|
564 |
+
*
|
565 |
+
* Defaults to TRUE if user option does not exist.
|
566 |
+
*
|
567 |
+
* @param int $user_id (optional)
|
568 |
+
*
|
569 |
+
* @return bool
|
570 |
+
*/
|
571 |
+
public static function unread_enabled_for_user( $user_id = 0 ) {
|
572 |
+
$user_id = empty( $user_id ) ? get_current_user_id() : $user_id;
|
573 |
+
$enabled = get_user_meta( $user_id, self::UNREAD_COUNT_OPTION_KEY, true );
|
574 |
+
$enabled = ( 'off' !== $enabled );
|
575 |
+
|
576 |
+
/**
|
577 |
+
* Enable or disable the unread count functionality
|
578 |
+
*
|
579 |
+
* @since 2.0.4
|
580 |
+
*
|
581 |
+
* @param int $user_id
|
582 |
+
*
|
583 |
+
* @return bool
|
584 |
+
*/
|
585 |
+
return (bool) apply_filters( 'wp_stream_unread_enabled', $enabled, $user_id );
|
586 |
+
}
|
587 |
+
|
588 |
+
/**
|
589 |
+
* Get the unread count for the current user.
|
590 |
+
*
|
591 |
+
* Results are cached in transient with a 5 min TTL.
|
592 |
+
*
|
593 |
+
* @return int
|
594 |
+
*/
|
595 |
+
public static function get_unread_count() {
|
596 |
+
if ( ! self::unread_enabled_for_user() ) {
|
597 |
+
return false;
|
598 |
+
}
|
599 |
+
|
600 |
+
$user_id = get_current_user_id();
|
601 |
+
$cache_key = sprintf( '%s_%d', self::UNREAD_COUNT_OPTION_KEY, $user_id );
|
602 |
+
|
603 |
+
if ( false === ( $count = get_transient( $cache_key ) ) ) {
|
604 |
+
$count = 0;
|
605 |
+
$last_read = get_user_meta( $user_id, self::LAST_READ_OPTION_KEY, true );
|
606 |
+
|
607 |
+
if ( ! empty( $last_read ) ) {
|
608 |
+
$args = array(
|
609 |
+
'records_per_page' => 101,
|
610 |
+
'author__not_in' => array( $user_id ), // Ignore changes authored by the current user
|
611 |
+
'date_after' => date( 'c', strtotime( $last_read . ' + 1 second' ) ), // Bump time to bypass gte issue
|
612 |
+
'fields' => array( 'created' ), // We don't need the entire record
|
613 |
+
);
|
614 |
+
|
615 |
+
$unread_records = wp_stream_query( $args );
|
616 |
+
|
617 |
+
$count = empty( $unread_records ) ? 0 : count( $unread_records );
|
618 |
+
}
|
619 |
+
|
620 |
+
set_transient( $cache_key, $count, 5 * 60 ); // TTL 5 min
|
621 |
+
}
|
622 |
+
|
623 |
+
return absint( $count );
|
624 |
+
}
|
625 |
+
|
626 |
+
/**
|
627 |
+
* Mark records as read based on Records screen results
|
628 |
+
*
|
629 |
+
* @filter wp_stream_query_results
|
630 |
+
*
|
631 |
+
* @param array $results
|
632 |
+
* @param array $query
|
633 |
+
* @param array $fields
|
634 |
+
*
|
635 |
+
* @return array
|
636 |
+
*/
|
637 |
+
public static function mark_as_read( $results, $query, $fields ) {
|
638 |
+
if ( ! is_admin() ) {
|
639 |
+
return $results;
|
640 |
+
}
|
641 |
+
|
642 |
+
$screen = get_current_screen();
|
643 |
+
$is_list_table = ( isset( $screen->id ) && sprintf( 'toplevel_page_%s', self::RECORDS_PAGE_SLUG ) === $screen->id );
|
644 |
+
$is_first_page = empty( $query['from'] );
|
645 |
+
$is_date_desc = ( isset( $query['sort'][0]['created']['order'] ) && 'desc' === $query['sort'][0]['created']['order'] );
|
646 |
+
|
647 |
+
if ( $is_list_table && $is_first_page && $is_date_desc ) {
|
648 |
+
$user_id = get_current_user_id();
|
649 |
+
$cache_key = sprintf( '%s_%d', self::UNREAD_COUNT_OPTION_KEY, $user_id );
|
650 |
+
|
651 |
+
if ( self::unread_enabled_for_user() && isset( $results[0]->created ) ) {
|
652 |
+
update_user_meta( $user_id, self::LAST_READ_OPTION_KEY, date( 'c', strtotime( $results[0]->created ) ) );
|
653 |
+
}
|
654 |
+
|
655 |
+
set_transient( $cache_key, 0 ); // No expiration
|
656 |
+
}
|
657 |
+
|
658 |
+
return $results;
|
659 |
+
}
|
660 |
+
|
661 |
+
/**
|
662 |
+
* Output for Stream Unread Count field in user profiles.
|
663 |
+
*
|
664 |
+
* @action show_user_profile
|
665 |
+
* @action edit_user_profile
|
666 |
+
*
|
667 |
+
* @param WP_User $user
|
668 |
+
*
|
669 |
+
* @return void
|
670 |
+
*/
|
671 |
+
public static function unread_count_user_option( $user ) {
|
672 |
+
if ( ! array_intersect( $user->roles, WP_Stream_Settings::$options['general_role_access'] ) ) {
|
673 |
+
return;
|
674 |
+
}
|
675 |
+
|
676 |
+
$unread_enabled = self::unread_enabled_for_user();
|
677 |
+
?>
|
678 |
+
<table class="form-table">
|
679 |
+
<tr>
|
680 |
+
<th scope="row">
|
681 |
+
<label for="<?php echo esc_attr( self::UNREAD_COUNT_OPTION_KEY ) ?>">
|
682 |
+
<?php esc_html_e( 'Stream Unread Count', 'stream' ) ?>
|
683 |
+
</label>
|
684 |
+
</th>
|
685 |
+
<td>
|
686 |
+
<label for="<?php echo esc_attr( self::UNREAD_COUNT_OPTION_KEY ) ?>">
|
687 |
+
<input type="checkbox" name="<?php echo esc_attr( self::UNREAD_COUNT_OPTION_KEY ) ?>" id="<?php echo esc_attr( self::UNREAD_COUNT_OPTION_KEY ) ?>" value="1" <?php checked( $unread_enabled ) ?>>
|
688 |
+
<?php esc_html_e( 'Enabled', 'stream' ) ?>
|
689 |
+
</label>
|
690 |
+
</td>
|
691 |
+
</tr>
|
692 |
+
</table>
|
693 |
+
<?php
|
694 |
+
}
|
695 |
+
|
696 |
+
/**
|
697 |
+
* Saves unread count user meta option in profiles.
|
698 |
+
*
|
699 |
+
* @action personal_options_update
|
700 |
+
* @action edit_user_profile_update
|
701 |
+
*
|
702 |
+
* @param $user_id
|
703 |
+
*
|
704 |
+
* @return void
|
705 |
+
*/
|
706 |
+
public static function save_unread_count_user_option( $user_id ) {
|
707 |
+
$enabled = wp_stream_filter_input( INPUT_POST, self::UNREAD_COUNT_OPTION_KEY );
|
708 |
+
$enabled = ( '1' === $enabled ) ? 'on' : 'off';
|
709 |
+
|
710 |
+
update_user_meta( $user_id, self::UNREAD_COUNT_OPTION_KEY, $enabled );
|
711 |
+
}
|
712 |
+
|
713 |
+
/**
|
714 |
+
* Delete user-specific transient when a user is deleted
|
715 |
+
*
|
716 |
+
* @action delete_user
|
717 |
+
*
|
718 |
+
* @param int $user_id
|
719 |
+
*
|
720 |
+
* @return void
|
721 |
+
*/
|
722 |
+
public static function delete_unread_count_transient( $user_id ) {
|
723 |
+
$cache_key = sprintf( '%s_%d', self::UNREAD_COUNT_OPTION_KEY, $user_id );
|
724 |
+
|
725 |
+
delete_transient( $cache_key );
|
726 |
+
}
|
727 |
+
|
728 |
+
/**
|
729 |
+
* @filter plugin_action_links
|
730 |
+
*/
|
731 |
+
public static function plugin_action_links( $links, $file ) {
|
732 |
+
if ( plugin_basename( WP_STREAM_DIR . 'stream.php' ) === $file ) {
|
733 |
+
$admin_page_url = add_query_arg( array( 'page' => self::SETTINGS_PAGE_SLUG ), admin_url( self::ADMIN_PARENT_PAGE ) );
|
734 |
+
|
735 |
+
$links[] = sprintf( '<a href="%s">%s</a>', esc_url( $admin_page_url ), esc_html__( 'Settings', 'stream' ) );
|
736 |
+
}
|
737 |
+
|
738 |
+
return $links;
|
739 |
+
}
|
740 |
+
|
741 |
+
public static function save_api_authentication() {
|
742 |
+
$home_url = str_ireplace( array( 'http://', 'https://' ), '', home_url() );
|
743 |
+
$connect_nonce_name = 'stream_connect_site-' . sanitize_key( $home_url );
|
744 |
+
|
745 |
+
if ( ! isset( $_GET['api_key'] ) || ! isset( $_GET['site_uuid'] ) ) {
|
746 |
+
wp_die( 'There was a problem connecting to Stream. Please try again later.', 'stream' );
|
747 |
+
}
|
748 |
+
|
749 |
+
if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], $connect_nonce_name ) ) {
|
750 |
+
wp_die( 'Doing it wrong.', 'stream' );
|
751 |
+
}
|
752 |
+
|
753 |
+
WP_Stream::$api->site_uuid = wp_stream_filter_input( INPUT_GET, 'site_uuid' );
|
754 |
+
WP_Stream::$api->api_key = wp_stream_filter_input( INPUT_GET, 'api_key' );
|
755 |
+
|
756 |
+
// Verify the API Key and Site UUID
|
757 |
+
$site = WP_Stream::$api->get_site();
|
758 |
+
$plan_type = WP_Stream::$api->get_plan_type();
|
759 |
+
|
760 |
+
WP_Stream_API::$restricted = ( 'free' === $plan_type ) ? 1 : 0;
|
761 |
+
|
762 |
+
if ( ! isset( $site->site_id ) ) {
|
763 |
+
wp_die( 'There was a problem verifying your site with Stream. Please try again later.', 'stream' );
|
764 |
+
}
|
765 |
+
|
766 |
+
if ( ! WP_Stream_API::$restricted ) {
|
767 |
+
WP_Stream_Notifications::$instance->on_activation();
|
768 |
+
}
|
769 |
+
|
770 |
+
update_option( WP_Stream_API::SITE_UUID_OPTION_KEY, WP_Stream::$api->site_uuid );
|
771 |
+
update_option( WP_Stream_API::API_KEY_OPTION_KEY, WP_Stream::$api->api_key );
|
772 |
+
update_option( WP_Stream_API::RESTRICTED_OPTION_KEY, WP_Stream_API::$restricted );
|
773 |
+
|
774 |
+
do_action( 'wp_stream_site_connected', WP_Stream::$api->site_uuid, WP_Stream::$api->api_key, get_current_blog_id() );
|
775 |
+
|
776 |
+
$redirect_url = add_query_arg(
|
777 |
+
array(
|
778 |
+
'page' => self::RECORDS_PAGE_SLUG,
|
779 |
+
'message' => 'connected',
|
780 |
+
),
|
781 |
+
admin_url( self::ADMIN_PARENT_PAGE )
|
782 |
+
);
|
783 |
+
|
784 |
+
wp_safe_redirect( $redirect_url );
|
785 |
+
|
786 |
+
exit;
|
787 |
+
}
|
788 |
+
|
789 |
+
public static function remove_api_authentication() {
|
790 |
+
delete_option( WP_Stream_API::SITE_UUID_OPTION_KEY );
|
791 |
+
delete_option( WP_Stream_API::API_KEY_OPTION_KEY );
|
792 |
+
|
793 |
+
do_action( 'wp_stream_site_disconnected', WP_Stream::$api->site_uuid, WP_Stream::$api->api_key, get_current_blog_id() );
|
794 |
+
|
795 |
+
WP_Stream::$api->site_uuid = false;
|
796 |
+
WP_Stream::$api->api_key = false;
|
797 |
+
|
798 |
+
if ( '1' !== wp_stream_filter_input( INPUT_GET, 'disconnect' ) ) {
|
799 |
+
return;
|
800 |
+
}
|
801 |
+
|
802 |
+
$redirect_url = add_query_arg(
|
803 |
+
array(
|
804 |
+
'page' => self::RECORDS_PAGE_SLUG,
|
805 |
+
),
|
806 |
+
admin_url( self::ADMIN_PARENT_PAGE )
|
807 |
+
);
|
808 |
+
|
809 |
+
wp_safe_redirect( $redirect_url );
|
810 |
+
|
811 |
+
exit;
|
812 |
+
}
|
813 |
+
|
814 |
+
public static function get_testimonials() {
|
815 |
+
$testimonials = get_site_transient( 'wp_stream_testimonials' );
|
816 |
+
|
817 |
+
if ( false !== $testimonials ) {
|
818 |
+
return $testimonials;
|
819 |
+
}
|
820 |
+
|
821 |
+
$url = sprintf( '%s/wp-content/themes/wp-stream.com/assets/testimonials.json', untrailingslashit( self::PUBLIC_URL ) );
|
822 |
+
$request = wp_remote_request( esc_url_raw( $url ), array( 'sslverify' => false ) );
|
823 |
+
|
824 |
+
if ( ! is_wp_error( $request ) && $request['response']['code'] === 200 ) {
|
825 |
+
$testimonials = json_decode( $request['body'], true );
|
826 |
+
} else {
|
827 |
+
$testimonials = false;
|
828 |
+
}
|
829 |
+
|
830 |
+
// Cache failed attempts as zero, to distinguish them from expired transients
|
831 |
+
if ( ! $testimonials ) {
|
832 |
+
$testimonials = 0;
|
833 |
+
}
|
834 |
+
|
835 |
+
set_site_transient( 'wp_stream_testimonials', $testimonials, WEEK_IN_SECONDS );
|
836 |
+
|
837 |
+
return $testimonials;
|
838 |
+
}
|
839 |
+
|
840 |
+
/**
|
841 |
+
* Render settings page
|
842 |
+
*
|
843 |
+
* @return void
|
844 |
+
*/
|
845 |
+
public static function render_settings_page() {
|
846 |
+
|
847 |
+
$option_key = WP_Stream_Settings::$option_key;
|
848 |
+
$form_action = apply_filters( 'wp_stream_settings_form_action', admin_url( 'options.php' ) );
|
849 |
+
|
850 |
+
$page_title = apply_filters( 'wp_stream_settings_form_title', get_admin_page_title() );
|
851 |
+
$page_description = apply_filters( 'wp_stream_settings_form_description', '' );
|
852 |
+
|
853 |
+
$sections = WP_Stream_Settings::get_fields();
|
854 |
+
$active_tab = wp_stream_filter_input( INPUT_GET, 'tab' );
|
855 |
+
|
856 |
+
wp_enqueue_script(
|
857 |
+
'stream-settings',
|
858 |
+
plugins_url( '../ui/js/settings.js', __FILE__ ),
|
859 |
+
array( 'jquery' ),
|
860 |
+
WP_Stream::VERSION,
|
861 |
+
true
|
862 |
+
);
|
863 |
+
?>
|
864 |
+
<div class="wrap">
|
865 |
+
<h2><?php echo esc_html( $page_title ) ?></h2>
|
866 |
+
|
867 |
+
<?php if ( ! empty( $page_description ) ) : ?>
|
868 |
+
<p><?php echo esc_html( $page_description ) ?></p>
|
869 |
+
<?php endif; ?>
|
870 |
+
|
871 |
+
<?php settings_errors() ?>
|
872 |
+
|
873 |
+
<?php if ( count( $sections ) > 1 ) : ?>
|
874 |
+
<h2 class="nav-tab-wrapper">
|
875 |
+
<?php $i = 0 ?>
|
876 |
+
<?php foreach ( $sections as $section => $data ) : ?>
|
877 |
+
<?php $i ++ ?>
|
878 |
+
<?php $is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section ) ?>
|
879 |
+
<a href="<?php echo esc_url( add_query_arg( 'tab', $section ) ) ?>" class="nav-tab<?php if ( $is_active ) { echo esc_attr( ' nav-tab-active' ); } ?>">
|
880 |
+
<?php echo esc_html( $data['title'] ) ?>
|
881 |
+
</a>
|
882 |
+
<?php endforeach; ?>
|
883 |
+
</h2>
|
884 |
+
<?php endif; ?>
|
885 |
+
|
886 |
+
<div class="nav-tab-content" id="tab-content-settings">
|
887 |
+
<form method="post" action="<?php echo esc_attr( $form_action ) ?>" enctype="multipart/form-data">
|
888 |
+
<div class="settings-sections">
|
889 |
+
<?php
|
890 |
+
$i = 0;
|
891 |
+
foreach ( $sections as $section => $data ) {
|
892 |
+
$i++;
|
893 |
+
$is_active = ( ( 1 === $i && ! $active_tab ) || $active_tab === $section );
|
894 |
+
if ( $is_active ) {
|
895 |
+
settings_fields( $option_key );
|
896 |
+
do_settings_sections( $option_key );
|
897 |
+
}
|
898 |
+
}
|
899 |
+
?>
|
900 |
+
</div>
|
901 |
+
<?php submit_button() ?>
|
902 |
+
</form>
|
903 |
+
</div>
|
904 |
+
</div>
|
905 |
+
<?php
|
906 |
+
}
|
907 |
+
|
908 |
+
/**
|
909 |
+
* Render account page
|
910 |
+
*
|
911 |
+
* @return void
|
912 |
+
*/
|
913 |
+
public static function render_account_page() {
|
914 |
+
$page_title = apply_filters( 'wp_stream_account_page_title', get_admin_page_title() );
|
915 |
+
$date_format = get_option( 'date_format' );
|
916 |
+
$site = WP_Stream::$api->get_site();
|
917 |
+
$plan_type = WP_Stream::$api->get_plan_type();
|
918 |
+
$plan_type_label = WP_Stream::$api->get_plan_type_label();
|
919 |
+
$plan_retention = WP_Stream::$api->get_plan_retention();
|
920 |
+
$plan_retention_label = WP_Stream::$api->get_plan_retention_label();
|
921 |
+
$plan_amount = WP_Stream::$api->get_plan_amount();
|
922 |
+
$expiry_date = WP_Stream::$api->get_expiry_date();
|
923 |
+
$created_date = WP_Stream::$api->get_created_date();
|
924 |
+
?>
|
925 |
+
<div class="wrap">
|
926 |
+
<h2><?php echo esc_html( $page_title ) ?></h2>
|
927 |
+
<div class="postbox">
|
928 |
+
<?php
|
929 |
+
if ( ! $site ) {
|
930 |
+
?>
|
931 |
+
<h3><?php _e( 'Error retrieving account details.' ) ?></h3>
|
932 |
+
<div class="plan-details">
|
933 |
+
<p><?php _e( 'If this problem persists, please disconnect from Stream and try connecting again.', 'stream' ) ?></p>
|
934 |
+
</div>
|
935 |
+
<div class="plan-actions submitbox">
|
936 |
+
<a class="submitdelete disconnect" href="<?php echo esc_url( add_query_arg( 'disconnect', '1' ) ) ?>"><?php _e( 'Disconnect', 'stream' ) ?></a>
|
937 |
+
</div>
|
938 |
+
<?php
|
939 |
+
} else {
|
940 |
+
?>
|
941 |
+
<h3><?php echo esc_html( $site->site_url ) ?></h3>
|
942 |
+
<div class="plan-details">
|
943 |
+
<table class="form-table">
|
944 |
+
<tbody>
|
945 |
+
<tr>
|
946 |
+
<th><?php _e( 'Plan', 'stream' ) ?></th>
|
947 |
+
<td><?php echo esc_html( $plan_type_label ) ?></td>
|
948 |
+
</tr>
|
949 |
+
<tr>
|
950 |
+
<th><?php _e( 'Activity History', 'stream' ) ?></th>
|
951 |
+
<td><?php echo esc_html( $plan_retention_label ) ?></td>
|
952 |
+
</tr>
|
953 |
+
<?php if ( 'free' !== $plan_type ) : ?>
|
954 |
+
<?php
|
955 |
+
$next_billing_label = sprintf(
|
956 |
+
_x( '$%1$s on %2$s', '1: Price, 2: Renewal date', 'stream' ),
|
957 |
+
esc_html( $plan_amount ),
|
958 |
+
esc_html( $expiry_date )
|
959 |
+
);
|
960 |
+
?>
|
961 |
+
<tr>
|
962 |
+
<th><?php _e( 'Next Billing', 'stream' ) ?></th>
|
963 |
+
<td><?php echo esc_html( $next_billing_label ) ?></td>
|
964 |
+
</tr>
|
965 |
+
<?php endif; ?>
|
966 |
+
<tr>
|
967 |
+
<th><?php _e( 'Created', 'stream' ) ?></th>
|
968 |
+
<td><?php echo esc_html( $created_date ) ?></td>
|
969 |
+
</tr>
|
970 |
+
<tr>
|
971 |
+
<th><?php _e( 'Site ID', 'stream' ) ?></th>
|
972 |
+
<td>
|
973 |
+
<code class="site-uuid"><?php echo esc_html( WP_Stream::$api->site_uuid ) ?></code>
|
974 |
+
</td>
|
975 |
+
</tr>
|
976 |
+
<tr>
|
977 |
+
<th><?php _e( 'API Key', 'stream' ) ?></th>
|
978 |
+
<td>
|
979 |
+
<code class="api-key"><?php echo esc_html( WP_Stream::$api->api_key ) ?></code>
|
980 |
+
</td>
|
981 |
+
</tr>
|
982 |
+
</tbody>
|
983 |
+
</table>
|
984 |
+
</div>
|
985 |
+
<div class="plan-actions submitbox">
|
986 |
+
<?php if ( 'free' === $plan_type ) : ?>
|
987 |
+
<a href="<?php echo esc_url( WP_Stream_Admin::account_url( sprintf( 'upgrade/?site_uuid=%s', WP_Stream::$api->site_uuid ) ) ) ?>" class="button button-primary button-large"><?php _e( 'Upgrade to Pro', 'stream' ) ?></a>
|
988 |
+
<?php else : ?>
|
989 |
+
<a href="<?php echo esc_url( sprintf( '%s/dashboard/?site_uuid=%s', self::PUBLIC_URL, WP_Stream::$api->site_uuid ) ) ?>" class="button button-primary button-large" target="_blank"><?php _e( 'Modify This Plan', 'stream' ) ?></a>
|
990 |
+
<?php endif; ?>
|
991 |
+
<a class="submitdelete disconnect" href="<?php echo esc_url( add_query_arg( 'disconnect', '1' ) ) ?>"><?php _e( 'Disconnect', 'stream' ) ?></a>
|
992 |
+
</div>
|
993 |
+
<?php
|
994 |
+
}
|
995 |
+
?>
|
996 |
+
</div>
|
997 |
+
</div>
|
998 |
+
<?php
|
999 |
+
}
|
1000 |
+
|
1001 |
+
/**
|
1002 |
+
* Render connect page
|
1003 |
+
*
|
1004 |
+
* @return void
|
1005 |
+
*/
|
1006 |
+
public static function render_connect_page() {
|
1007 |
+
$page_title = apply_filters( 'wp_stream_connect_page_title', get_admin_page_title() );
|
1008 |
+
|
1009 |
+
if ( $testimonials = self::get_testimonials() ) {
|
1010 |
+
$testimonial = $testimonials[ array_rand( $testimonials ) ];
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
wp_enqueue_style( 'wp-stream-connect', WP_STREAM_URL . 'ui/css/connect.css', array(), WP_Stream::VERSION );
|
1014 |
+
wp_enqueue_script( 'wp-stream-connect', WP_STREAM_URL . 'ui/js/connect.js', array(), WP_Stream::VERSION );
|
1015 |
+
?>
|
1016 |
+
<div id="wp-stream-connect">
|
1017 |
+
|
1018 |
+
<div class="wrap">
|
1019 |
+
|
1020 |
+
<div class="stream-connect-container">
|
1021 |
+
<a href="<?php echo esc_url( self::$connect_url ) ?>" class="stream-button"><i class="stream-icon"></i><?php _e( 'Connect to Stream', 'stream' ) ?></a>
|
1022 |
+
<p>
|
1023 |
+
<?php
|
1024 |
+
$tooltip = sprintf(
|
1025 |
+
esc_html__( 'Stream only uses your WordPress.com ID during sign up to authorize your account. You can sign up for free at %swordpress.com/signup%s.', 'stream' ),
|
1026 |
+
'<a href="https://signup.wordpress.com/signup/?user=1" target="_blank">',
|
1027 |
+
'</a>'
|
1028 |
+
);
|
1029 |
+
wp_kses_post(
|
1030 |
+
printf(
|
1031 |
+
esc_html__( 'with your %sWordPress.com ID%s', 'stream' ),
|
1032 |
+
'<span class="wp-stream-tooltip-text">',
|
1033 |
+
'</span><span class="wp-stream-tooltip">' . $tooltip . '</span>' // xss ok
|
1034 |
+
)
|
1035 |
+
);
|
1036 |
+
?>
|
1037 |
+
</p>
|
1038 |
+
</div>
|
1039 |
+
|
1040 |
+
<?php if ( isset( $testimonial ) ) : ?>
|
1041 |
+
<div class="stream-quotes-container">
|
1042 |
+
<p class="stream-quote">“<?php echo esc_html( $testimonial['quote'] ) ?>”</p>
|
1043 |
+
<p class="stream-quote-author">‐ <?php echo esc_html( $testimonial['author'] ) ?>, <a class="stream-quote-organization" href="<?php echo esc_url( $testimonial['link'] ) ?>"><?php echo esc_html( $testimonial['organization'] ) ?></a></p>
|
1044 |
+
</div>
|
1045 |
+
<?php endif; ?>
|
1046 |
+
|
1047 |
+
</div>
|
1048 |
+
|
1049 |
+
</div>
|
1050 |
+
<?php
|
1051 |
+
}
|
1052 |
+
|
1053 |
+
public static function register_list_table() {
|
1054 |
+
self::$list_table = new WP_Stream_List_Table( array( 'screen' => self::$screen_id['main'] ) );
|
1055 |
+
}
|
1056 |
+
|
1057 |
+
public static function render_stream_page() {
|
1058 |
+
$page_title = __( 'Stream Records', 'stream' );
|
1059 |
+
|
1060 |
+
self::$list_table->prepare_items();
|
1061 |
+
|
1062 |
+
echo '<div class="wrap">';
|
1063 |
+
|
1064 |
+
printf( '<h2>%s</h2>', __( 'Stream Records', 'stream' ) ); // xss ok
|
1065 |
+
|
1066 |
+
self::$list_table->display();
|
1067 |
+
|
1068 |
+
echo '</div>';
|
1069 |
+
}
|
1070 |
+
|
1071 |
+
public static function wp_ajax_defaults() {
|
1072 |
+
check_ajax_referer( 'stream_nonce', 'wp_stream_nonce' );
|
1073 |
+
|
1074 |
+
if ( current_user_can( self::SETTINGS_CAP ) ) {
|
1075 |
+
self::reset_stream_settings();
|
1076 |
+
|
1077 |
+
wp_safe_redirect(
|
1078 |
+
add_query_arg(
|
1079 |
+
array(
|
1080 |
+
'page' => 'wp_stream_settings',
|
1081 |
+
'message' => 'settings_reset',
|
1082 |
+
),
|
1083 |
+
admin_url( self::ADMIN_PARENT_PAGE )
|
1084 |
+
)
|
1085 |
+
);
|
1086 |
+
|
1087 |
+
exit;
|
1088 |
+
} else {
|
1089 |
+
wp_die( "You don't have sufficient privileges to do this action." );
|
1090 |
+
}
|
1091 |
+
}
|
1092 |
+
|
1093 |
+
private static function reset_stream_settings() {
|
1094 |
+
global $wpdb;
|
1095 |
+
|
1096 |
+
$blogs = wp_get_sites();
|
1097 |
+
|
1098 |
+
if ( $blogs ) {
|
1099 |
+
foreach ( $blogs as $blog ) {
|
1100 |
+
switch_to_blog( $blog['blog_id'] );
|
1101 |
+
delete_option( WP_Stream_Settings::OPTION_KEY );
|
1102 |
+
}
|
1103 |
+
restore_current_blog();
|
1104 |
+
}
|
1105 |
+
}
|
1106 |
+
|
1107 |
+
private static function _role_can_view_stream( $role ) {
|
1108 |
+
if ( in_array( $role, WP_Stream_Settings::$options['general_role_access'] ) ) {
|
1109 |
+
return true;
|
1110 |
+
}
|
1111 |
+
|
1112 |
+
return false;
|
1113 |
+
}
|
1114 |
+
|
1115 |
+
/**
|
1116 |
+
* Filter user caps to dynamically grant our view cap based on allowed roles
|
1117 |
+
*
|
1118 |
+
* @filter user_has_cap
|
1119 |
+
*
|
1120 |
+
* @param $allcaps
|
1121 |
+
* @param $caps
|
1122 |
+
* @param $args
|
1123 |
+
* @param $user
|
1124 |
+
*
|
1125 |
+
* @return array
|
1126 |
+
*/
|
1127 |
+
public static function _filter_user_caps( $allcaps, $caps, $args, $user = null ) {
|
1128 |
+
global $wp_roles;
|
1129 |
+
|
1130 |
+
$_wp_roles = isset( $wp_roles ) ? $wp_roles : new WP_Roles();
|
1131 |
+
|
1132 |
+
$user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
|
1133 |
+
|
1134 |
+
// @see
|
1135 |
+
// https://github.com/WordPress/WordPress/blob/c67c9565f1495255807069fdb39dac914046b1a0/wp-includes/capabilities.php#L758
|
1136 |
+
$roles = array_unique(
|
1137 |
+
array_merge(
|
1138 |
+
$user->roles,
|
1139 |
+
array_filter(
|
1140 |
+
array_keys( $user->caps ),
|
1141 |
+
array( $_wp_roles, 'is_role' )
|
1142 |
+
)
|
1143 |
+
)
|
1144 |
+
);
|
1145 |
+
|
1146 |
+
$stream_view_caps = array(
|
1147 |
+
self::VIEW_CAP,
|
1148 |
+
WP_Stream_Notifications::VIEW_CAP,
|
1149 |
+
WP_Stream_Reports::VIEW_CAP,
|
1150 |
+
);
|
1151 |
+
|
1152 |
+
foreach ( $caps as $cap ) {
|
1153 |
+
if ( in_array( $cap, $stream_view_caps ) ) {
|
1154 |
+
foreach ( $roles as $role ) {
|
1155 |
+
if ( self::_role_can_view_stream( $role ) ) {
|
1156 |
+
$allcaps[ $cap ] = true;
|
1157 |
+
break 2;
|
1158 |
+
}
|
1159 |
+
}
|
1160 |
+
}
|
1161 |
+
}
|
1162 |
+
|
1163 |
+
return $allcaps;
|
1164 |
+
}
|
1165 |
+
|
1166 |
+
/**
|
1167 |
+
* Filter role caps to dynamically grant our view cap based on allowed roles
|
1168 |
+
*
|
1169 |
+
* @filter role_has_cap
|
1170 |
+
*
|
1171 |
+
* @param $allcaps
|
1172 |
+
* @param $cap
|
1173 |
+
* @param $role
|
1174 |
+
*
|
1175 |
+
* @return array
|
1176 |
+
*/
|
1177 |
+
public static function _filter_role_caps( $allcaps, $cap, $role ) {
|
1178 |
+
$stream_view_caps = array(
|
1179 |
+
self::VIEW_CAP,
|
1180 |
+
WP_Stream_Notifications::VIEW_CAP,
|
1181 |
+
WP_Stream_Reports::VIEW_CAP,
|
1182 |
+
);
|
1183 |
+
|
1184 |
+
if ( in_array( $cap, $stream_view_caps ) && self::_role_can_view_stream( $role ) ) {
|
1185 |
+
$allcaps[ $cap ] = true;
|
1186 |
+
}
|
1187 |
+
|
1188 |
+
return $allcaps;
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
/**
|
1192 |
+
* @action wp_ajax_wp_stream_filters
|
1193 |
+
*/
|
1194 |
+
public static function ajax_filters() {
|
1195 |
+
switch ( wp_stream_filter_input( INPUT_GET, 'filter' ) ) {
|
1196 |
+
case 'author':
|
1197 |
+
$users = array_merge(
|
1198 |
+
array( 0 => (object) array( 'display_name' => 'WP-CLI' ) ),
|
1199 |
+
get_users()
|
1200 |
+
);
|
1201 |
+
|
1202 |
+
// `search` arg for get_users() is not enough
|
1203 |
+
$users = array_filter(
|
1204 |
+
$users,
|
1205 |
+
function ( $user ) {
|
1206 |
+
return false !== mb_strpos( mb_strtolower( $user->display_name ), mb_strtolower( wp_stream_filter_input( INPUT_GET, 'q' ) ) );
|
1207 |
+
}
|
1208 |
+
);
|
1209 |
+
|
1210 |
+
if ( count( $users ) > self::PRELOAD_AUTHORS_MAX ) {
|
1211 |
+
$users = array_slice( $users, 0, self::PRELOAD_AUTHORS_MAX );
|
1212 |
+
// @todo $extra is not used
|
1213 |
+
$extra = array(
|
1214 |
+
'id' => 0,
|
1215 |
+
'disabled' => true,
|
1216 |
+
'text' => sprintf( _n( 'One more result...', '%d more results...', $results_count - self::PRELOAD_AUTHORS_MAX, 'stream' ), $results_count - self::PRELOAD_AUTHORS_MAX ),
|
1217 |
+
);
|
1218 |
+
}
|
1219 |
+
|
1220 |
+
// Get gravatar / roles for final result set
|
1221 |
+
$results = self::get_authors_record_meta( $users );
|
1222 |
+
|
1223 |
+
break;
|
1224 |
+
}
|
1225 |
+
if ( isset( $results ) ) {
|
1226 |
+
echo json_encode( array_values( $results ) );
|
1227 |
+
}
|
1228 |
+
die();
|
1229 |
+
}
|
1230 |
+
|
1231 |
+
/**
|
1232 |
+
* @action wp_ajax_wp_stream_get_filter_value_by_id
|
1233 |
+
*/
|
1234 |
+
public static function get_filter_value_by_id() {
|
1235 |
+
$filter = wp_stream_filter_input( INPUT_POST, 'filter' );
|
1236 |
+
|
1237 |
+
switch ( $filter ) {
|
1238 |
+
case 'author':
|
1239 |
+
$id = wp_stream_filter_input( INPUT_POST, 'id' );
|
1240 |
+
if ( '0' === $id ) {
|
1241 |
+
$value = 'WP-CLI';
|
1242 |
+
break;
|
1243 |
+
}
|
1244 |
+
$user = get_userdata( $id );
|
1245 |
+
if ( ! $user || is_wp_error( $user ) ) {
|
1246 |
+
$value = '';
|
1247 |
+
} else {
|
1248 |
+
$value = $user->display_name;
|
1249 |
+
}
|
1250 |
+
break;
|
1251 |
+
default:
|
1252 |
+
$value = '';
|
1253 |
+
}
|
1254 |
+
|
1255 |
+
echo json_encode( $value );
|
1256 |
+
|
1257 |
+
wp_die();
|
1258 |
+
}
|
1259 |
+
|
1260 |
+
public static function get_authors_record_meta( $authors ) {
|
1261 |
+
$authors_records = array();
|
1262 |
+
|
1263 |
+
foreach ( $authors as $user_id => $args ) {
|
1264 |
+
$author = new WP_Stream_Author( $user_id );
|
1265 |
+
$disabled = isset( $args['disabled'] ) ? $args['disabled'] : null;
|
1266 |
+
|
1267 |
+
$authors_records[ $user_id ] = array(
|
1268 |
+
'text' => $author->get_display_name(),
|
1269 |
+
'id' => $user_id,
|
1270 |
+
'label' => $author->get_display_name(),
|
1271 |
+
'icon' => $author->get_avatar_src( 32 ),
|
1272 |
+
'title' => '',
|
1273 |
+
'disabled' => $disabled,
|
1274 |
+
);
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
return $authors_records;
|
1278 |
+
}
|
1279 |
+
}
|
classes/class-wp-stream-api.php
ADDED
@@ -0,0 +1,468 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_API {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* API Key key/identifier
|
7 |
+
*/
|
8 |
+
const API_KEY_OPTION_KEY = 'wp_stream_site_api_key';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Site UUID key/identifier
|
12 |
+
*/
|
13 |
+
const SITE_UUID_OPTION_KEY = 'wp_stream_site_uuid';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Site Retricted key/identifier
|
17 |
+
*/
|
18 |
+
const RESTRICTED_OPTION_KEY = 'wp_stream_site_restricted';
|
19 |
+
|
20 |
+
/**
|
21 |
+
* The site's API Key
|
22 |
+
*
|
23 |
+
* @var string
|
24 |
+
*/
|
25 |
+
public $api_key = false;
|
26 |
+
|
27 |
+
/**
|
28 |
+
* The site's unique identifier
|
29 |
+
*
|
30 |
+
* @var string
|
31 |
+
*/
|
32 |
+
public $site_uuid = false;
|
33 |
+
|
34 |
+
/**
|
35 |
+
* The site's restriction status
|
36 |
+
*
|
37 |
+
* @var bool
|
38 |
+
*/
|
39 |
+
public static $restricted = true;
|
40 |
+
|
41 |
+
/**
|
42 |
+
* The API URL
|
43 |
+
*
|
44 |
+
* @var string
|
45 |
+
*/
|
46 |
+
public $api_url = 'https://api.wp-stream.com';
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Error messages
|
50 |
+
*
|
51 |
+
* @var array
|
52 |
+
*/
|
53 |
+
public $errors = array();
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Total API calls made per page load
|
57 |
+
* Used for debugging and optimization
|
58 |
+
*
|
59 |
+
* @var array
|
60 |
+
*/
|
61 |
+
public $count = 0;
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Public constructor
|
65 |
+
*
|
66 |
+
* @return void
|
67 |
+
*/
|
68 |
+
public function __construct() {
|
69 |
+
$this->api_key = get_option( self::API_KEY_OPTION_KEY, 0 );
|
70 |
+
$this->site_uuid = get_option( self::SITE_UUID_OPTION_KEY, 0 );
|
71 |
+
self::$restricted = get_option( self::RESTRICTED_OPTION_KEY, 1 );
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Check if the current site is restricted
|
76 |
+
*
|
77 |
+
* @param bool Force the API to send a request to check the site's plan type
|
78 |
+
*
|
79 |
+
* @return bool
|
80 |
+
*/
|
81 |
+
public static function is_restricted( $force_check = false ) {
|
82 |
+
if ( $force_check ) {
|
83 |
+
$site = WP_Stream::$api->get_site();
|
84 |
+
|
85 |
+
self::$restricted = ( ! isset( $site->plan->type ) || 'free' === $site->plan->type );
|
86 |
+
}
|
87 |
+
|
88 |
+
return self::$restricted;
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Used to prioritise the streams transport which support non-blocking
|
93 |
+
*
|
94 |
+
* @filter http_api_transports
|
95 |
+
*
|
96 |
+
* @return bool
|
97 |
+
*/
|
98 |
+
public static function http_api_transport_priority( $request_order, $args, $url ) {
|
99 |
+
if ( isset( $args['blocking'] ) && false === $args['blocking'] ) {
|
100 |
+
$request_order = array( 'streams', 'curl' );
|
101 |
+
}
|
102 |
+
|
103 |
+
return $request_order;
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Get the details for a specific site.
|
108 |
+
*
|
109 |
+
* @param array Returns specified fields only.
|
110 |
+
* @param bool Allow API calls to be cached.
|
111 |
+
* @param int Set transient expiration in seconds.
|
112 |
+
*
|
113 |
+
* @return mixed
|
114 |
+
*/
|
115 |
+
public function get_site( $fields = array(), $allow_cache = true, $expiration = 30 ) {
|
116 |
+
if ( ! $this->site_uuid ) {
|
117 |
+
return false;
|
118 |
+
}
|
119 |
+
|
120 |
+
$params = array();
|
121 |
+
|
122 |
+
if ( ! empty( $fields ) ) {
|
123 |
+
$params['fields'] = implode( ',', $fields );
|
124 |
+
}
|
125 |
+
|
126 |
+
$url = $this->request_url( sprintf( '/sites/%s', urlencode( $this->site_uuid ) ), $params );
|
127 |
+
$args = array( 'method' => 'GET' );
|
128 |
+
$site = $this->remote_request( $url, $args, $allow_cache, $expiration );
|
129 |
+
|
130 |
+
if ( $site && ! is_wp_error( $site ) ) {
|
131 |
+
$is_restricted = ( ! isset( $site->plan->type ) || 'free' === $site->plan->type ) ? 1 : 0;
|
132 |
+
|
133 |
+
if ( self::$restricted !== (bool) $is_restricted ) {
|
134 |
+
self::$restricted = $is_restricted;
|
135 |
+
|
136 |
+
update_option( self::RESTRICTED_OPTION_KEY, $is_restricted );
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
return $site;
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Return this site's plan type
|
145 |
+
*
|
146 |
+
* @return string
|
147 |
+
*/
|
148 |
+
public function get_plan_type() {
|
149 |
+
$site = WP_Stream::$api->get_site();
|
150 |
+
|
151 |
+
return isset( $site->plan->type ) ? esc_html( $site->plan->type ) : 'free';
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Return this site's plan type label
|
156 |
+
*
|
157 |
+
* @return string
|
158 |
+
*/
|
159 |
+
public function get_plan_type_label() {
|
160 |
+
$type = WP_Stream::$api->get_plan_type();
|
161 |
+
|
162 |
+
// Only check the beginning of these type strings
|
163 |
+
if ( 0 === strpos( $type, 'pro' ) ) {
|
164 |
+
$label = __( 'Pro', 'stream' );
|
165 |
+
} else {
|
166 |
+
$label = __( 'Free', 'stream' );
|
167 |
+
}
|
168 |
+
|
169 |
+
return $label;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* Return this site's plan retention length
|
174 |
+
*
|
175 |
+
* @return int
|
176 |
+
*/
|
177 |
+
public function get_plan_retention() {
|
178 |
+
$site = WP_Stream::$api->get_site();
|
179 |
+
|
180 |
+
return isset( $site->plan->retention ) ? absint( $site->plan->retention ) : 30;
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* Return this site's plan retention label
|
185 |
+
*
|
186 |
+
* @return string
|
187 |
+
*/
|
188 |
+
public function get_plan_retention_label() {
|
189 |
+
$retention = WP_Stream::$api->get_plan_retention();
|
190 |
+
|
191 |
+
if ( 0 === $retention ) {
|
192 |
+
$label = __( '1 Year', 'stream' );
|
193 |
+
} else {
|
194 |
+
$label = sprintf(
|
195 |
+
_n( '1 Day', '%s Days', $retention, 'stream' ),
|
196 |
+
$retention
|
197 |
+
);
|
198 |
+
}
|
199 |
+
|
200 |
+
return $label;
|
201 |
+
}
|
202 |
+
|
203 |
+
/**
|
204 |
+
* Return the oldest record date (GMT) allowed for this site's plan
|
205 |
+
*
|
206 |
+
* @return string
|
207 |
+
*/
|
208 |
+
public function get_plan_retention_max_date( $format = 'Y-m-d H:i:s' ) {
|
209 |
+
$retention = WP_Stream::$api->get_plan_retention();
|
210 |
+
|
211 |
+
return empty( $retention ) ? gmdate( $format, strtotime( '1 year ago' ) ) : gmdate( $format, strtotime( sprintf( '%d days ago', $retention ) ) );
|
212 |
+
}
|
213 |
+
|
214 |
+
/**
|
215 |
+
* Return this site's plan amount
|
216 |
+
*
|
217 |
+
* @return string
|
218 |
+
*/
|
219 |
+
public function get_plan_amount() {
|
220 |
+
$site = WP_Stream::$api->get_site();
|
221 |
+
|
222 |
+
return isset( $site->plan->amount ) ? esc_html( $site->plan->amount ) : 0;
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Return the account creation date for this site
|
227 |
+
*
|
228 |
+
* @return string
|
229 |
+
*/
|
230 |
+
public function get_created_date() {
|
231 |
+
$site = WP_Stream::$api->get_site();
|
232 |
+
$date_format = get_option( 'date_format' );
|
233 |
+
|
234 |
+
return isset( $site->created ) ? date_i18n( $date_format, strtotime( $site->created ) ) : __( 'N/A', 'stream' );
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Return the expiration date for this site's plan
|
239 |
+
*
|
240 |
+
* @return string
|
241 |
+
*/
|
242 |
+
public function get_expiry_date() {
|
243 |
+
$site = WP_Stream::$api->get_site();
|
244 |
+
$date_format = get_option( 'date_format' );
|
245 |
+
|
246 |
+
return isset( $site->expiry->date ) ? date_i18n( $date_format, strtotime( $site->expiry->date ) ) : __( 'N/A', 'stream' );
|
247 |
+
}
|
248 |
+
|
249 |
+
/**
|
250 |
+
* Get a specific record.
|
251 |
+
*
|
252 |
+
* @param string A record ID.
|
253 |
+
* @param array Returns specified fields only.
|
254 |
+
* @param bool Allow API calls to be cached.
|
255 |
+
* @param int Set transient expiration in seconds.
|
256 |
+
*
|
257 |
+
* @return mixed
|
258 |
+
*/
|
259 |
+
public function get_record( $record_id = false, $fields = array(), $allow_cache = true, $expiration = 30 ) {
|
260 |
+
if ( false === $record_id ) {
|
261 |
+
return false;
|
262 |
+
}
|
263 |
+
|
264 |
+
if ( ! $this->site_uuid ) {
|
265 |
+
return false;
|
266 |
+
}
|
267 |
+
|
268 |
+
$params = array();
|
269 |
+
|
270 |
+
if ( ! empty( $fields ) ) {
|
271 |
+
$params['fields'] = implode( ',', $fields );
|
272 |
+
}
|
273 |
+
|
274 |
+
$url = $this->request_url( sprintf( '/sites/%s/records/%s', urlencode( $this->site_uuid ), urlencode( $record_id ) ), $params );
|
275 |
+
$args = array( 'method' => 'GET' );
|
276 |
+
|
277 |
+
return $this->remote_request( $url, $args, $allow_cache, $expiration );
|
278 |
+
}
|
279 |
+
|
280 |
+
/**
|
281 |
+
* Get all records.
|
282 |
+
*
|
283 |
+
* @param array Returns specified fields only.
|
284 |
+
* @param bool Allow API calls to be cached.
|
285 |
+
* @param int Set transient expiration in seconds.
|
286 |
+
*
|
287 |
+
* @return mixed
|
288 |
+
*/
|
289 |
+
public function get_records( $fields = array(), $allow_cache = true, $expiration = 30 ) {
|
290 |
+
if ( ! $this->site_uuid ) {
|
291 |
+
return false;
|
292 |
+
}
|
293 |
+
|
294 |
+
$params = array();
|
295 |
+
|
296 |
+
if ( ! empty( $fields ) ) {
|
297 |
+
$params['fields'] = implode( ',', $fields );
|
298 |
+
}
|
299 |
+
|
300 |
+
$url = $this->request_url( sprintf( '/sites/%s/records', urlencode( $this->site_uuid ) ), $params );
|
301 |
+
$args = array( 'method' => 'GET' );
|
302 |
+
|
303 |
+
return $this->remote_request( $url, $args, $allow_cache, $expiration );
|
304 |
+
}
|
305 |
+
|
306 |
+
/**
|
307 |
+
* Create new records.
|
308 |
+
*
|
309 |
+
* @param array $records
|
310 |
+
* @param bool $blocking
|
311 |
+
*
|
312 |
+
* @return mixed
|
313 |
+
*/
|
314 |
+
public function new_records( $records, $blocking = false ) {
|
315 |
+
if ( ! $this->site_uuid ) {
|
316 |
+
return false;
|
317 |
+
}
|
318 |
+
|
319 |
+
$url = $this->request_url( sprintf( '/sites/%s/records', urlencode( $this->site_uuid ) ) );
|
320 |
+
$args = array( 'method' => 'POST', 'body' => json_encode( array( 'records' => $records ) ), 'blocking' => (bool) $blocking );
|
321 |
+
|
322 |
+
return $this->remote_request( $url, $args );
|
323 |
+
}
|
324 |
+
|
325 |
+
/**
|
326 |
+
* Search all records.
|
327 |
+
*
|
328 |
+
* @param array Elasticsearch's Query DSL query object.
|
329 |
+
* @param array Returns specified fields only.
|
330 |
+
* @param bool Allow API calls to be cached.
|
331 |
+
* @param int Set transient expiration in seconds.
|
332 |
+
*
|
333 |
+
* @return mixed
|
334 |
+
*/
|
335 |
+
public function search( $query = array(), $fields = array(), $sites = array(), $search_type = '', $allow_cache = false, $expiration = 120 ) {
|
336 |
+
if ( ! $this->site_uuid ) {
|
337 |
+
return false;
|
338 |
+
}
|
339 |
+
|
340 |
+
$body = array();
|
341 |
+
|
342 |
+
$body['query'] = ! empty( $query ) ? $query : array();
|
343 |
+
$body['fields'] = ! empty( $fields ) ? $fields : array();
|
344 |
+
$body['sites'] = ! empty( $sites ) ? $sites : array( $this->site_uuid );
|
345 |
+
$body['search_type'] = ! empty( $search_type ) ? $search_type : '';
|
346 |
+
|
347 |
+
$url = $this->request_url( '/search' );
|
348 |
+
$args = array( 'method' => 'POST', 'body' => json_encode( (object) $body ) );
|
349 |
+
|
350 |
+
return $this->remote_request( $url, $args, $allow_cache, $expiration );
|
351 |
+
}
|
352 |
+
|
353 |
+
/**
|
354 |
+
* Helper function to create and escape a URL for an API request.
|
355 |
+
*
|
356 |
+
* @param string The endpoint path, with a starting slash.
|
357 |
+
* @param array The $_GET parameters.
|
358 |
+
*
|
359 |
+
* @return string A properly escaped URL.
|
360 |
+
*/
|
361 |
+
public function request_url( $path, $params = array() ) {
|
362 |
+
return esc_url_raw(
|
363 |
+
add_query_arg(
|
364 |
+
$params,
|
365 |
+
untrailingslashit( $this->api_url ) . $path
|
366 |
+
)
|
367 |
+
);
|
368 |
+
}
|
369 |
+
|
370 |
+
/**
|
371 |
+
* Helper function to query the marketplace API via wp_remote_request.
|
372 |
+
*
|
373 |
+
* @param string The url to access.
|
374 |
+
* @param string The method of the request.
|
375 |
+
* @param array The headers sent during the request.
|
376 |
+
* @param bool Allow API calls to be cached.
|
377 |
+
* @param int Set transient expiration in seconds.
|
378 |
+
*
|
379 |
+
* @return object The results of the wp_remote_request request.
|
380 |
+
*/
|
381 |
+
protected function remote_request( $url = '', $args = array(), $allow_cache = true, $expiration = 300 ) {
|
382 |
+
if ( empty( $url ) || empty( $this->api_key ) ) {
|
383 |
+
return false;
|
384 |
+
}
|
385 |
+
|
386 |
+
$defaults = array(
|
387 |
+
'headers' => array(),
|
388 |
+
'method' => 'GET',
|
389 |
+
'body' => '',
|
390 |
+
'sslverify' => true,
|
391 |
+
);
|
392 |
+
|
393 |
+
$this->count++;
|
394 |
+
|
395 |
+
$args = wp_parse_args( $args, $defaults );
|
396 |
+
|
397 |
+
$args['headers']['Stream-Site-API-Key'] = $this->api_key;
|
398 |
+
$args['headers']['Content-Type'] = 'application/json';
|
399 |
+
|
400 |
+
if ( WP_Stream::is_development_mode() ) {
|
401 |
+
$args['blocking'] = true;
|
402 |
+
}
|
403 |
+
|
404 |
+
add_filter( 'http_api_transports', array( __CLASS__, 'http_api_transport_priority' ), 10, 3 );
|
405 |
+
|
406 |
+
$transient = 'wp_stream_' . md5( $url );
|
407 |
+
|
408 |
+
if ( 'GET' === $args['method'] && $allow_cache ) {
|
409 |
+
if ( false === ( $request = get_transient( $transient ) ) ) {
|
410 |
+
$request = wp_remote_request( $url, $args );
|
411 |
+
|
412 |
+
set_transient( $transient, $request, $expiration );
|
413 |
+
}
|
414 |
+
} else {
|
415 |
+
$request = wp_remote_request( $url, $args );
|
416 |
+
}
|
417 |
+
|
418 |
+
remove_filter( 'http_api_transports', array( __CLASS__, 'http_api_transport_priority' ), 10 );
|
419 |
+
|
420 |
+
// Return early if the request is non blocking
|
421 |
+
if ( isset( $args['blocking'] ) && false === $args['blocking'] ) {
|
422 |
+
return true;
|
423 |
+
}
|
424 |
+
|
425 |
+
if ( ! is_wp_error( $request ) ) {
|
426 |
+
/**
|
427 |
+
* Filter the request data of the API response.
|
428 |
+
*
|
429 |
+
* Does not fire on non-blocking requests.
|
430 |
+
*
|
431 |
+
* @since 2.0.0
|
432 |
+
*
|
433 |
+
* @param string $url
|
434 |
+
* @param array $args
|
435 |
+
*
|
436 |
+
* @return array
|
437 |
+
*/
|
438 |
+
$data = apply_filters( 'wp_stream_api_request_data', json_decode( $request['body'] ), $url, $args );
|
439 |
+
|
440 |
+
// Loose comparison needed
|
441 |
+
if ( 200 == $request['response']['code'] || 201 == $request['response']['code'] ) {
|
442 |
+
return $data;
|
443 |
+
} else {
|
444 |
+
// Disconnect if unauthorized or no longer exists, loose comparison needed
|
445 |
+
if ( 403 == $request['response']['code'] || 410 == $request['response']['code'] ) {
|
446 |
+
WP_Stream_Admin::remove_api_authentication();
|
447 |
+
}
|
448 |
+
|
449 |
+
$this->errors['errors']['http_code'] = $request['response']['code'];
|
450 |
+
}
|
451 |
+
|
452 |
+
if ( isset( $data->error ) ) {
|
453 |
+
$this->errors['errors']['api_error'] = $data->error;
|
454 |
+
}
|
455 |
+
} else {
|
456 |
+
$this->errors['errors']['remote_request_error'] = $request->get_error_message();
|
457 |
+
|
458 |
+
WP_Stream::notice( sprintf( '<strong>%s</strong> %s.', __( 'Stream API Error.', 'stream' ), $this->errors['errors']['remote_request_error'] ) );
|
459 |
+
}
|
460 |
+
|
461 |
+
if ( ! empty( $this->errors ) ) {
|
462 |
+
delete_transient( $transient );
|
463 |
+
}
|
464 |
+
|
465 |
+
return false;
|
466 |
+
}
|
467 |
+
|
468 |
+
}
|
classes/class-wp-stream-author.php
ADDED
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Author {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var int
|
7 |
+
*/
|
8 |
+
public $id;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var array
|
12 |
+
*/
|
13 |
+
public $meta = array();
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var WP_User
|
17 |
+
*/
|
18 |
+
protected $user;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* @param int $user_id
|
22 |
+
* @param array $author_meta
|
23 |
+
*
|
24 |
+
* @return void
|
25 |
+
*/
|
26 |
+
function __construct( $user_id, $author_meta = array() ) {
|
27 |
+
$this->id = $user_id;
|
28 |
+
$this->meta = $author_meta;
|
29 |
+
|
30 |
+
if ( $this->id ) {
|
31 |
+
$this->user = new WP_User( $this->id );
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Get various user meta data
|
37 |
+
*
|
38 |
+
* @param string $name
|
39 |
+
*
|
40 |
+
* @throws Exception
|
41 |
+
*
|
42 |
+
* @return string
|
43 |
+
*/
|
44 |
+
function __get( $name ) {
|
45 |
+
if ( 'display_name' === $name ) {
|
46 |
+
return $this->get_display_name();
|
47 |
+
} elseif ( 'avatar_img' === $name ) {
|
48 |
+
return $this->get_avatar_img();
|
49 |
+
} elseif ( 'avatar_src' === $name ) {
|
50 |
+
return $this->get_avatar_src();
|
51 |
+
} elseif ( 'role' === $name ) {
|
52 |
+
return $this->get_role();
|
53 |
+
} elseif ( 'agent' === $name ) {
|
54 |
+
return $this->get_agent();
|
55 |
+
} elseif ( ! empty( $this->user ) && 0 !== $this->user->ID ) {
|
56 |
+
return $this->user->$name;
|
57 |
+
} else {
|
58 |
+
throw new Exception( "Unrecognized magic '$name'" );
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Get the display name of the user
|
64 |
+
*
|
65 |
+
* @return string
|
66 |
+
*/
|
67 |
+
function get_display_name() {
|
68 |
+
if ( 0 === $this->id ) {
|
69 |
+
if ( isset( $this->meta['system_user_name'] ) ) {
|
70 |
+
return esc_html( $this->meta['system_user_name'] );
|
71 |
+
} elseif ( 'wp_cli' === $this->get_current_agent() ) {
|
72 |
+
return 'WP-CLI'; // No translation needed
|
73 |
+
}
|
74 |
+
return esc_html__( 'N/A', 'stream' );
|
75 |
+
} else {
|
76 |
+
if ( $this->is_deleted() ) {
|
77 |
+
if ( ! empty( $this->meta['display_name'] ) ) {
|
78 |
+
return $this->meta['display_name'];
|
79 |
+
} elseif ( ! empty( $this->meta['user_login'] ) ) {
|
80 |
+
return $this->meta['user_login'];
|
81 |
+
} else {
|
82 |
+
return esc_html__( 'N/A', 'stream' );
|
83 |
+
}
|
84 |
+
} elseif ( ! empty( $this->user->display_name ) ) {
|
85 |
+
return $this->user->display_name;
|
86 |
+
} else {
|
87 |
+
return $this->user->user_login;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Get the agent of the user
|
94 |
+
*
|
95 |
+
* @return string
|
96 |
+
*/
|
97 |
+
function get_agent() {
|
98 |
+
$agent = '';
|
99 |
+
|
100 |
+
if ( ! empty( $this->meta['agent'] ) ) {
|
101 |
+
$agent = $this->meta['agent'];
|
102 |
+
} elseif ( ! empty( $this->meta['is_wp_cli'] ) ) {
|
103 |
+
$agent = 'wp_cli'; // legacy
|
104 |
+
}
|
105 |
+
|
106 |
+
return $agent;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Return a Gravatar image as an HTML element.
|
111 |
+
*
|
112 |
+
* This function will not return an avatar if "Show Avatars" is unchecked in Settings > Discussion.
|
113 |
+
*
|
114 |
+
* @param int $size (optional) Size of Gravatar to return (in pixels), max is 512, default is 80
|
115 |
+
*
|
116 |
+
* @return string|bool An img HTML element, or false if avatars are disabled
|
117 |
+
*/
|
118 |
+
function get_avatar_img( $size = 80 ) {
|
119 |
+
if ( ! get_option( 'show_avatars' ) ) {
|
120 |
+
return false;
|
121 |
+
}
|
122 |
+
|
123 |
+
if ( 0 === $this->id ) {
|
124 |
+
$url = WP_STREAM_URL . 'ui/stream-icons/wp-cli.png';
|
125 |
+
$avatar = sprintf( '<img alt="%1$s" src="%2$s" class="avatar avatar-%3$s photo" height="%3$s" width="%3$s">', esc_attr( $this->get_display_name() ), esc_url( $url ), esc_attr( $size ) );
|
126 |
+
} else {
|
127 |
+
if ( $this->is_deleted() ) {
|
128 |
+
$email = $this->meta['user_email'];
|
129 |
+
$avatar = get_avatar( $email, $size );
|
130 |
+
} else {
|
131 |
+
$avatar = get_avatar( $this->id, $size );
|
132 |
+
}
|
133 |
+
}
|
134 |
+
|
135 |
+
return $avatar;
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Return the URL of a Gravatar image.
|
140 |
+
*
|
141 |
+
* @param int $size (optional) Size of Gravatar to return (in pixels), max is 512, default is 80
|
142 |
+
*
|
143 |
+
* @return string|bool Gravatar image URL, or false on failure
|
144 |
+
*/
|
145 |
+
function get_avatar_src( $size = 80 ) {
|
146 |
+
$img = $this->get_avatar_img( $size );
|
147 |
+
|
148 |
+
if ( ! $img ) {
|
149 |
+
return false;
|
150 |
+
}
|
151 |
+
|
152 |
+
if ( 1 === preg_match( '/src=([\'"])(.*?)\1/', $img, $matches ) ) {
|
153 |
+
$src = html_entity_decode( $matches[2] );
|
154 |
+
} else {
|
155 |
+
return false;
|
156 |
+
}
|
157 |
+
|
158 |
+
return $src;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Tries to find a label for the record's author_role.
|
163 |
+
*
|
164 |
+
* If the author_role exists, use the label associated with it.
|
165 |
+
*
|
166 |
+
* Otherwise, if there is a user role label stored as Stream meta then use that.
|
167 |
+
*
|
168 |
+
* Otherwise, if the user exists, use the label associated with their current role.
|
169 |
+
*
|
170 |
+
* Otherwise, use the role slug as the label.
|
171 |
+
*
|
172 |
+
* @return string
|
173 |
+
*/
|
174 |
+
function get_role() {
|
175 |
+
global $wp_roles;
|
176 |
+
|
177 |
+
if ( ! empty( $this->meta['author_role'] ) && isset( $wp_roles->role_names[ $this->meta['author_role'] ] ) ) {
|
178 |
+
$author_role = $wp_roles->role_names[ $this->meta['author_role'] ];
|
179 |
+
} elseif ( ! empty( $this->meta['user_role_label'] ) ) {
|
180 |
+
$author_role = $this->meta['user_role_label'];
|
181 |
+
} elseif ( isset( $this->user->roles[0] ) && isset( $wp_roles->role_names[ $this->user->roles[0] ] ) ) {
|
182 |
+
$author_role = $wp_roles->role_names[ $this->user->roles[0] ];
|
183 |
+
} else {
|
184 |
+
$author_role = '';
|
185 |
+
}
|
186 |
+
|
187 |
+
return $author_role;
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Construct a URL for viewing user-specific records
|
192 |
+
*
|
193 |
+
* @return string
|
194 |
+
*/
|
195 |
+
function get_records_page_url() {
|
196 |
+
$url = add_query_arg(
|
197 |
+
array(
|
198 |
+
'page' => WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
199 |
+
'author' => absint( $this->id ),
|
200 |
+
),
|
201 |
+
self_admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE )
|
202 |
+
);
|
203 |
+
|
204 |
+
return $url;
|
205 |
+
}
|
206 |
+
|
207 |
+
/**
|
208 |
+
* True if user no longer exists, otherwise false
|
209 |
+
*
|
210 |
+
* @return bool
|
211 |
+
*/
|
212 |
+
function is_deleted() {
|
213 |
+
return ( 0 !== $this->id && 0 === $this->user->ID );
|
214 |
+
}
|
215 |
+
|
216 |
+
/**
|
217 |
+
* True if user is WP-CLI, otherwise false
|
218 |
+
*
|
219 |
+
* @return bool
|
220 |
+
*/
|
221 |
+
function is_wp_cli() {
|
222 |
+
return ( 'wp_cli' === $this->get_agent() );
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* @return string
|
227 |
+
*/
|
228 |
+
function __toString() {
|
229 |
+
return $this->get_display_name();
|
230 |
+
}
|
231 |
+
|
232 |
+
/**
|
233 |
+
* Look at the environment to detect if an agent is being used
|
234 |
+
*
|
235 |
+
* @return string
|
236 |
+
*/
|
237 |
+
static function get_current_agent() {
|
238 |
+
$agent = '';
|
239 |
+
|
240 |
+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
241 |
+
$agent = 'wp_cli';
|
242 |
+
} elseif ( defined( 'DOING_CRON' ) && DOING_CRON ) {
|
243 |
+
$agent = 'wp_cron';
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Filter the current agent string
|
248 |
+
*
|
249 |
+
* @since 1.4.4
|
250 |
+
*
|
251 |
+
* @return string
|
252 |
+
*/
|
253 |
+
$agent = apply_filters( 'wp_stream_current_agent', $agent );
|
254 |
+
|
255 |
+
return $agent;
|
256 |
+
}
|
257 |
+
|
258 |
+
/**
|
259 |
+
* Get the agent label
|
260 |
+
*
|
261 |
+
* @param string $agent
|
262 |
+
*
|
263 |
+
* @return string
|
264 |
+
*/
|
265 |
+
static function get_agent_label( $agent ) {
|
266 |
+
if ( 'wp_cli' === $agent ) {
|
267 |
+
$label = esc_html__( 'via WP-CLI', 'stream' );
|
268 |
+
} elseif ( 'wp_cron' === $agent ) {
|
269 |
+
$label = esc_html__( 'during WP Cron', 'stream' );
|
270 |
+
} else {
|
271 |
+
$label = '';
|
272 |
+
}
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Filter agent labels
|
276 |
+
*
|
277 |
+
* @since 1.4.4
|
278 |
+
*
|
279 |
+
* @param string $agent
|
280 |
+
*
|
281 |
+
* @return string
|
282 |
+
*/
|
283 |
+
$label = apply_filters( 'wp_stream_agent_label', $label, $agent );
|
284 |
+
|
285 |
+
return $label;
|
286 |
+
}
|
287 |
+
|
288 |
+
}
|
classes/class-wp-stream-connector.php
ADDED
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
abstract class WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = null;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Previous Stream entry in same request
|
21 |
+
*
|
22 |
+
* @var int
|
23 |
+
*/
|
24 |
+
public static $prev_stream = null;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Register all context hooks
|
28 |
+
*
|
29 |
+
* @return void
|
30 |
+
*/
|
31 |
+
public static function register() {
|
32 |
+
$class = get_called_class();
|
33 |
+
|
34 |
+
foreach ( $class::$actions as $action ) {
|
35 |
+
add_action( $action, array( $class, 'callback' ), null, 5 );
|
36 |
+
}
|
37 |
+
|
38 |
+
add_filter( 'wp_stream_action_links_' . $class::$name, array( $class, 'action_links' ), 10, 2 );
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Callback for all registered hooks throughout Stream
|
43 |
+
* Looks for a class method with the convention: "callback_{action name}"
|
44 |
+
*
|
45 |
+
* @return void
|
46 |
+
*/
|
47 |
+
public static function callback() {
|
48 |
+
$action = current_filter();
|
49 |
+
$class = get_called_class();
|
50 |
+
$callback = array( $class, 'callback_' . preg_replace( '/[^a-z0-9_\-]/', '_', $action ) );
|
51 |
+
|
52 |
+
// For the sake of testing, trigger an action with the name of the callback
|
53 |
+
if ( defined( 'STREAM_TESTS' ) ) {
|
54 |
+
/**
|
55 |
+
* Action fires during testing to test the current callback
|
56 |
+
*
|
57 |
+
* @param array $callback Callback name
|
58 |
+
*/
|
59 |
+
do_action( 'wp_stream_test_' . $callback[1] );
|
60 |
+
}
|
61 |
+
|
62 |
+
// Call the real function
|
63 |
+
if ( is_callable( $callback ) ) {
|
64 |
+
return call_user_func_array( $callback, func_get_args() );
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Add action links to Stream drop row in admin list screen
|
70 |
+
*
|
71 |
+
* @filter wp_stream_action_links_{connector}
|
72 |
+
* @param array $links Previous links registered
|
73 |
+
* @param object $record Stream record
|
74 |
+
* @return array Action links
|
75 |
+
*/
|
76 |
+
public static function action_links( $links, $record ) {
|
77 |
+
return $links;
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Log handler
|
82 |
+
*
|
83 |
+
* @param string $message sprintf-ready error message string
|
84 |
+
* @param array $args sprintf (and extra) arguments to use
|
85 |
+
* @param int $object_id Target object id
|
86 |
+
* @param string $context Context of the event
|
87 |
+
* @param string $action Action of the event
|
88 |
+
* @param int $user_id User responsible for the event
|
89 |
+
*
|
90 |
+
* @internal param string $action Action performed (stream_action)
|
91 |
+
* @return bool
|
92 |
+
*/
|
93 |
+
public static function log( $message, $args, $object_id, $context, $action, $user_id = null ) {
|
94 |
+
$class = get_called_class();
|
95 |
+
$connector = $class::$name;
|
96 |
+
|
97 |
+
$data = apply_filters(
|
98 |
+
'wp_stream_log_data',
|
99 |
+
compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id' )
|
100 |
+
);
|
101 |
+
|
102 |
+
if ( ! $data ) {
|
103 |
+
return false;
|
104 |
+
} else {
|
105 |
+
$connector = $data['connector'];
|
106 |
+
$message = $data['message'];
|
107 |
+
$args = $data['args'];
|
108 |
+
$object_id = $data['object_id'];
|
109 |
+
$context = $data['context'];
|
110 |
+
$action = $data['action'];
|
111 |
+
$user_id = $data['user_id'];
|
112 |
+
}
|
113 |
+
|
114 |
+
return call_user_func_array( array( WP_Stream_Log::get_instance(), 'log' ), compact( 'connector', 'message', 'args', 'object_id', 'context', 'action', 'user_id' ) );
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Save log data till shutdown, so other callbacks would be able to override
|
119 |
+
*
|
120 |
+
* @param string $handle Special slug to be shared with other actions
|
121 |
+
*
|
122 |
+
* @internal param mixed $arg1 Extra arguments to sent to log()
|
123 |
+
* @internal param mixed $arg2 , etc..
|
124 |
+
* @return void
|
125 |
+
*/
|
126 |
+
public static function delayed_log( $handle ) {
|
127 |
+
$args = func_get_args();
|
128 |
+
|
129 |
+
array_shift( $args );
|
130 |
+
|
131 |
+
self::$delayed[ $handle ] = $args;
|
132 |
+
|
133 |
+
add_action( 'shutdown', array( __CLASS__, 'delayed_log_commit' ) );
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Commit delayed logs saved by @delayed_log
|
138 |
+
*
|
139 |
+
* @return void
|
140 |
+
*/
|
141 |
+
public static function delayed_log_commit() {
|
142 |
+
foreach ( self::$delayed as $handle => $args ) {
|
143 |
+
call_user_func_array( array( __CLASS__, 'log' ) , $args );
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Compare two values and return changed keys if they are arrays
|
149 |
+
*
|
150 |
+
* @param mixed $old_value Value before change
|
151 |
+
* @param mixed $new_value Value after change
|
152 |
+
* @param bool|int $deep Get array children changes keys as well, not just parents
|
153 |
+
*
|
154 |
+
* @return array
|
155 |
+
*/
|
156 |
+
public static function get_changed_keys( $old_value, $new_value, $deep = false ) {
|
157 |
+
if ( ! is_array( $old_value ) && ! is_array( $new_value ) ) {
|
158 |
+
return array();
|
159 |
+
}
|
160 |
+
|
161 |
+
if ( ! is_array( $old_value ) ) {
|
162 |
+
return array_keys( $new_value );
|
163 |
+
}
|
164 |
+
|
165 |
+
if ( ! is_array( $new_value ) ) {
|
166 |
+
return array_keys( $old_value );
|
167 |
+
}
|
168 |
+
|
169 |
+
$diff = array_udiff_assoc(
|
170 |
+
$old_value,
|
171 |
+
$new_value,
|
172 |
+
function( $value1, $value2 ) {
|
173 |
+
return maybe_serialize( $value1 ) !== maybe_serialize( $value2 );
|
174 |
+
}
|
175 |
+
);
|
176 |
+
|
177 |
+
$result = array_keys( $diff );
|
178 |
+
|
179 |
+
// find unexisting keys in old or new value
|
180 |
+
$common_keys = array_keys( array_intersect_key( $old_value, $new_value ) );
|
181 |
+
$unique_keys_old = array_values( array_diff( array_keys( $old_value ), $common_keys ) );
|
182 |
+
$unique_keys_new = array_values( array_diff( array_keys( $new_value ), $common_keys ) );
|
183 |
+
$result = array_merge( $result, $unique_keys_old, $unique_keys_new );
|
184 |
+
|
185 |
+
// remove numeric indexes
|
186 |
+
$result = array_filter(
|
187 |
+
$result,
|
188 |
+
function( $value ) {
|
189 |
+
// check if is not valid number (is_int, is_numeric and ctype_digit are not enough)
|
190 |
+
return (string) (int) $value !== (string) $value;
|
191 |
+
}
|
192 |
+
);
|
193 |
+
|
194 |
+
$result = array_values( array_unique( $result ) );
|
195 |
+
|
196 |
+
if ( false === $deep ) {
|
197 |
+
return $result; // Return an numerical based array with changed TOP PARENT keys only
|
198 |
+
}
|
199 |
+
|
200 |
+
$result = array_fill_keys( $result, null );
|
201 |
+
|
202 |
+
foreach ( $result as $key => $val ) {
|
203 |
+
if ( in_array( $key, $unique_keys_old ) ) {
|
204 |
+
$result[ $key ] = false; // Removed
|
205 |
+
}
|
206 |
+
elseif ( in_array( $key, $unique_keys_new ) ) {
|
207 |
+
$result[ $key ] = true; // Added
|
208 |
+
}
|
209 |
+
elseif ( $deep ) { // Changed, find what changed, only if we're allowed to explore a new level
|
210 |
+
if ( is_array( $old_value[ $key ] ) && is_array( $new_value[ $key ] ) ) {
|
211 |
+
$inner = array();
|
212 |
+
$parent = $key;
|
213 |
+
$deep--;
|
214 |
+
$changed = self::get_changed_keys( $old_value[ $key ], $new_value[ $key ], $deep );
|
215 |
+
foreach ( $changed as $child => $change ) {
|
216 |
+
$inner[ $parent . '::' . $child ] = $change;
|
217 |
+
}
|
218 |
+
$result[ $key ] = 0; // Changed parent which has a changed children
|
219 |
+
$result = array_merge( $result, $inner );
|
220 |
+
}
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
return $result;
|
225 |
+
}
|
226 |
+
|
227 |
+
/**
|
228 |
+
* Allow connectors to determine if their dependencies is satisfied or not
|
229 |
+
*
|
230 |
+
* @return bool
|
231 |
+
*/
|
232 |
+
public static function is_dependency_satisfied() {
|
233 |
+
return true;
|
234 |
+
}
|
235 |
+
|
236 |
+
}
|
classes/class-wp-stream-connectors.php
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connectors {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connectors registered
|
7 |
+
*
|
8 |
+
* @var array
|
9 |
+
*/
|
10 |
+
public static $connectors = array();
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Contexts registered to Connectors
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $contexts = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Action taxonomy terms
|
21 |
+
* Holds slug to localized label association
|
22 |
+
*
|
23 |
+
* @var array
|
24 |
+
*/
|
25 |
+
public static $term_labels = array(
|
26 |
+
'stream_connector' => array(),
|
27 |
+
'stream_context' => array(),
|
28 |
+
'stream_action' => array(),
|
29 |
+
);
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Admin notice messages
|
33 |
+
*
|
34 |
+
* @since 1.2.3
|
35 |
+
* @var array
|
36 |
+
*/
|
37 |
+
protected static $admin_notices = array();
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Load built-in connectors
|
41 |
+
*/
|
42 |
+
public static function load() {
|
43 |
+
add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
|
44 |
+
|
45 |
+
$connectors = array(
|
46 |
+
// Core
|
47 |
+
'comments',
|
48 |
+
'editor',
|
49 |
+
'installer',
|
50 |
+
'media',
|
51 |
+
'menus',
|
52 |
+
'posts',
|
53 |
+
'settings',
|
54 |
+
'taxonomies',
|
55 |
+
'users',
|
56 |
+
'widgets',
|
57 |
+
|
58 |
+
// Extras
|
59 |
+
'acf',
|
60 |
+
'bbpress',
|
61 |
+
'buddypress',
|
62 |
+
'edd',
|
63 |
+
'gravityforms',
|
64 |
+
'jetpack',
|
65 |
+
'stream',
|
66 |
+
'woocommerce',
|
67 |
+
'wordpress-seo',
|
68 |
+
);
|
69 |
+
|
70 |
+
$classes = array();
|
71 |
+
foreach ( $connectors as $connector ) {
|
72 |
+
include_once WP_STREAM_DIR . '/connectors/class-wp-stream-connector-' . $connector .'.php';
|
73 |
+
$class = sprintf( 'WP_Stream_Connector_%s', str_replace( '-', '_', $connector ) );
|
74 |
+
if ( $class::is_dependency_satisfied() ) {
|
75 |
+
$classes[] = $class;
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Allows for adding additional connectors via classes that extend WP_Stream_Connector.
|
81 |
+
*
|
82 |
+
* @since 0.0.2
|
83 |
+
*
|
84 |
+
* @param array $classes An array of connector class names.
|
85 |
+
*/
|
86 |
+
self::$connectors = apply_filters( 'wp_stream_connectors', $classes );
|
87 |
+
|
88 |
+
foreach ( self::$connectors as $connector ) {
|
89 |
+
self::$term_labels['stream_connector'][ $connector::$name ] = $connector::get_label();
|
90 |
+
}
|
91 |
+
|
92 |
+
// Get excluded connectors
|
93 |
+
$excluded_connectors = array();
|
94 |
+
|
95 |
+
foreach ( self::$connectors as $connector ) {
|
96 |
+
// Check if the connectors extends the WP_Stream_Connector class, if not skip it
|
97 |
+
if ( ! is_subclass_of( $connector, 'WP_Stream_Connector' ) ) {
|
98 |
+
self::$admin_notices[] = sprintf(
|
99 |
+
__( "%s class wasn't loaded because it doesn't extends the %s class.", 'stream' ),
|
100 |
+
$connector,
|
101 |
+
'WP_Stream_Connector'
|
102 |
+
);
|
103 |
+
|
104 |
+
continue;
|
105 |
+
}
|
106 |
+
|
107 |
+
// Store connector label
|
108 |
+
if ( ! in_array( $connector::$name, self::$term_labels['stream_connector'] ) ) {
|
109 |
+
self::$term_labels['stream_connector'][ $connector::$name ] = $connector::get_label();
|
110 |
+
}
|
111 |
+
|
112 |
+
$connector_name = $connector::$name;
|
113 |
+
$is_excluded = in_array( $connector_name, $excluded_connectors );
|
114 |
+
|
115 |
+
/**
|
116 |
+
* Allows excluded connectors to be overridden and registered.
|
117 |
+
*
|
118 |
+
* @since 1.3.0
|
119 |
+
*
|
120 |
+
* @param bool $is_excluded True if excluded, otherwise false.
|
121 |
+
* @param string $connector The current connector's slug.
|
122 |
+
* @param array $excluded_connectors An array of all excluded connector slugs.
|
123 |
+
*/
|
124 |
+
$is_excluded_connector = apply_filters( 'wp_stream_check_connector_is_excluded', $is_excluded, $connector_name, $excluded_connectors );
|
125 |
+
|
126 |
+
if ( $is_excluded_connector ) {
|
127 |
+
continue;
|
128 |
+
}
|
129 |
+
|
130 |
+
$connector::register();
|
131 |
+
|
132 |
+
// Link context labels to their connector
|
133 |
+
self::$contexts[ $connector::$name ] = $connector::get_context_labels();
|
134 |
+
|
135 |
+
// Add new terms to our label lookup array
|
136 |
+
self::$term_labels['stream_action'] = array_merge(
|
137 |
+
self::$term_labels['stream_action'],
|
138 |
+
$connector::get_action_labels()
|
139 |
+
);
|
140 |
+
self::$term_labels['stream_context'] = array_merge(
|
141 |
+
self::$term_labels['stream_context'],
|
142 |
+
$connector::get_context_labels()
|
143 |
+
);
|
144 |
+
}
|
145 |
+
|
146 |
+
$connectors = self::$term_labels['stream_connector'];
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Fires after all connectors have been registered.
|
150 |
+
*
|
151 |
+
* @since 1.3.0
|
152 |
+
*
|
153 |
+
* @param array all register connectors labels array
|
154 |
+
*/
|
155 |
+
do_action( 'wp_stream_after_connectors_registration', $connectors );
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Print admin notices
|
160 |
+
*
|
161 |
+
* @since 1.2.3
|
162 |
+
*
|
163 |
+
* @return void
|
164 |
+
*/
|
165 |
+
public static function admin_notices() {
|
166 |
+
if ( ! empty( self::$admin_notices ) ) :
|
167 |
+
?>
|
168 |
+
<div class="error">
|
169 |
+
<?php foreach ( self::$admin_notices as $message ) : ?>
|
170 |
+
<?php echo wpautop( esc_html( $message ) ) // xss ok ?>
|
171 |
+
<?php endforeach; ?>
|
172 |
+
</div>
|
173 |
+
<?php
|
174 |
+
endif;
|
175 |
+
}
|
176 |
+
|
177 |
+
}
|
classes/class-wp-stream-dashboard-widget.php
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Dashboard_Widget {
|
4 |
+
|
5 |
+
public static function load() {
|
6 |
+
// Load Dashboard widget
|
7 |
+
add_action( 'wp_dashboard_setup', array( __CLASS__, 'stream_activity' ) );
|
8 |
+
|
9 |
+
// Dashboard AJAX pagination
|
10 |
+
add_action( 'wp_ajax_stream_activity_dashboard_update', array( __CLASS__, 'stream_activity_update_contents' ) );
|
11 |
+
}
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Add Stream Activity widget to the dashboard
|
15 |
+
*
|
16 |
+
* @action wp_dashboard_setup
|
17 |
+
*/
|
18 |
+
public static function stream_activity() {
|
19 |
+
if ( ! current_user_can( WP_Stream_Admin::VIEW_CAP ) ) {
|
20 |
+
return;
|
21 |
+
}
|
22 |
+
|
23 |
+
wp_add_dashboard_widget(
|
24 |
+
'dashboard_stream_activity',
|
25 |
+
esc_html__( 'Stream Activity', 'stream' ),
|
26 |
+
array( __CLASS__, 'stream_activity_initial_contents' ),
|
27 |
+
array( __CLASS__, 'stream_activity_options' )
|
28 |
+
);
|
29 |
+
}
|
30 |
+
|
31 |
+
public static function stream_activity_initial_contents() {
|
32 |
+
self::stream_activity_contents();
|
33 |
+
}
|
34 |
+
|
35 |
+
public static function stream_activity_update_contents() {
|
36 |
+
$paged = ! empty( $_POST['stream-paged'] ) ? absint( $_POST['stream-paged'] ) : 1;
|
37 |
+
self::stream_activity_contents( $paged );
|
38 |
+
die;
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Contents of the Stream Activity dashboard widget
|
43 |
+
*/
|
44 |
+
public static function stream_activity_contents( $paged = 1 ) {
|
45 |
+
$options = get_option( 'dashboard_stream_activity_options', array() );
|
46 |
+
$records_per_page = isset( $options['records_per_page'] ) ? absint( $options['records_per_page'] ) : 5;
|
47 |
+
$args = array(
|
48 |
+
'records_per_page' => $records_per_page,
|
49 |
+
'paged' => $paged,
|
50 |
+
);
|
51 |
+
|
52 |
+
$records = wp_stream_query( $args );
|
53 |
+
$total_items = WP_Stream::$db->get_found_rows();
|
54 |
+
|
55 |
+
if ( ! $records ) {
|
56 |
+
?>
|
57 |
+
<p class="no-records"><?php esc_html_e( 'Sorry, no activity records were found.', 'stream' ) ?></p>
|
58 |
+
<?php
|
59 |
+
return;
|
60 |
+
}
|
61 |
+
|
62 |
+
printf(
|
63 |
+
'<ul>%s</ul>',
|
64 |
+
implode( '', array_map( array( __CLASS__, 'widget_row' ), $records ) )
|
65 |
+
);
|
66 |
+
|
67 |
+
$args = array(
|
68 |
+
'current' => $paged,
|
69 |
+
'total_pages' => absint( ceil( $total_items / $records_per_page ) ), // Cast as an integer, not a float
|
70 |
+
);
|
71 |
+
|
72 |
+
self::pagination( $args );
|
73 |
+
}
|
74 |
+
|
75 |
+
/*
|
76 |
+
* Display pagination links for Dashboard Widget
|
77 |
+
* Copied from private class WP_List_Table::pagination()
|
78 |
+
*/
|
79 |
+
public static function pagination( $args = array() ) {
|
80 |
+
$args = wp_parse_args(
|
81 |
+
$args,
|
82 |
+
array(
|
83 |
+
'current' => 1,
|
84 |
+
'total_pages' => 1,
|
85 |
+
)
|
86 |
+
);
|
87 |
+
|
88 |
+
$current = $args['current'];
|
89 |
+
$total_pages = $args['total_pages'];
|
90 |
+
|
91 |
+
$records_link = add_query_arg(
|
92 |
+
array( 'page' => WP_Stream_Admin::RECORDS_PAGE_SLUG ),
|
93 |
+
self_admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE )
|
94 |
+
);
|
95 |
+
|
96 |
+
$html_view_all = sprintf(
|
97 |
+
'<a class="%s" title="%s" href="%s">%s</a>',
|
98 |
+
'view-all',
|
99 |
+
esc_attr__( 'View all records', 'stream' ),
|
100 |
+
esc_url( $records_link ),
|
101 |
+
esc_html__( 'View All', 'stream' )
|
102 |
+
);
|
103 |
+
|
104 |
+
$page_links = array();
|
105 |
+
$disable_first = '';
|
106 |
+
$disable_last = '';
|
107 |
+
|
108 |
+
if ( 1 === $current ) {
|
109 |
+
$disable_first = ' disabled';
|
110 |
+
}
|
111 |
+
|
112 |
+
if ( $current === $total_pages ) {
|
113 |
+
$disable_last = ' disabled';
|
114 |
+
}
|
115 |
+
|
116 |
+
$page_links[] = sprintf(
|
117 |
+
'<a class="%s" title="%s" href="%s" data-page="1">%s</a>',
|
118 |
+
'first-page' . $disable_first,
|
119 |
+
esc_attr__( 'Go to the first page', 'stream' ),
|
120 |
+
esc_url( remove_query_arg( 'paged', $records_link ) ),
|
121 |
+
'«'
|
122 |
+
);
|
123 |
+
|
124 |
+
$page_links[] = sprintf(
|
125 |
+
'<a class="%s" title="%s" href="%s" data-page="%s">%s</a>',
|
126 |
+
'prev-page' . $disable_first,
|
127 |
+
esc_attr__( 'Go to the previous page', 'stream' ),
|
128 |
+
esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $records_link ) ),
|
129 |
+
max( 1, $current - 1 ),
|
130 |
+
'‹'
|
131 |
+
);
|
132 |
+
|
133 |
+
$html_total_pages = sprintf( '<span class="total-pages">%s</span>', number_format_i18n( $total_pages ) );
|
134 |
+
$page_links[] = '<span class="paging-input">' . sprintf( _x( '%1$s of %2$s', 'paging', 'stream' ), number_format_i18n( $current ), $html_total_pages ) . '</span>';
|
135 |
+
|
136 |
+
$page_links[] = sprintf(
|
137 |
+
'<a class="%s" title="%s" href="%s" data-page="%s">%s</a>',
|
138 |
+
'next-page' . $disable_last,
|
139 |
+
esc_attr__( 'Go to the next page', 'stream' ),
|
140 |
+
esc_url( add_query_arg( 'paged', min( $total_pages, $current + 1 ), $records_link ) ),
|
141 |
+
min( $total_pages, $current + 1 ),
|
142 |
+
'›'
|
143 |
+
);
|
144 |
+
|
145 |
+
$page_links[] = sprintf(
|
146 |
+
'<a class="%s" title="%s" href="%s" data-page="%s">%s</a>',
|
147 |
+
'last-page' . $disable_last,
|
148 |
+
esc_attr__( 'Go to the last page', 'stream' ),
|
149 |
+
esc_url( add_query_arg( 'paged', $total_pages, $records_link ) ),
|
150 |
+
$total_pages,
|
151 |
+
'»'
|
152 |
+
);
|
153 |
+
|
154 |
+
$html_pagination_links = '
|
155 |
+
<div class="tablenav">
|
156 |
+
<div class="tablenav-pages">
|
157 |
+
<span class="pagination-links">' . join( "\n", $page_links ) . '</span>
|
158 |
+
</div>
|
159 |
+
<div class="clear"></div>
|
160 |
+
</div>';
|
161 |
+
|
162 |
+
echo '<div>' . $html_view_all . $html_pagination_links . '</div>'; // xss ok
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Configurable options for the Stream Activity dashboard widget
|
167 |
+
*/
|
168 |
+
public static function stream_activity_options() {
|
169 |
+
$options = get_option( 'dashboard_stream_activity_options', array() );
|
170 |
+
|
171 |
+
if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['dashboard_stream_activity_options'] ) ) {
|
172 |
+
$options['records_per_page'] = absint( $_POST['dashboard_stream_activity_options']['records_per_page'] );
|
173 |
+
$options['live_update'] = isset( $_POST['dashboard_stream_activity_options']['live_update'] ) ? 'on' : 'off';;
|
174 |
+
update_option( 'dashboard_stream_activity_options', $options );
|
175 |
+
}
|
176 |
+
|
177 |
+
if ( ! isset( $options['records_per_page'] ) ) {
|
178 |
+
$options['records_per_page'] = 5;
|
179 |
+
}
|
180 |
+
|
181 |
+
?>
|
182 |
+
<div id="dashboard-stream-activity-options">
|
183 |
+
<p>
|
184 |
+
<input type="number" step="1" min="1" max="999" class="screen-per-page" name="dashboard_stream_activity_options[records_per_page]" id="dashboard_stream_activity_options[records_per_page]" value="<?php echo absint( $options['records_per_page'] ) ?>">
|
185 |
+
<label for="dashboard_stream_activity_options[records_per_page]"><?php esc_html_e( 'Records per page', 'stream' ) ?></label>
|
186 |
+
</p>
|
187 |
+
<?php $value = isset( $options['live_update'] ) ? $options['live_update'] : 'on'; ?>
|
188 |
+
<p>
|
189 |
+
<input type="checkbox" name="dashboard_stream_activity_options[live_update]" id="dashboard_stream_activity_options[live_update]" value='on' <?php checked( $value, 'on' ) ?> />
|
190 |
+
<label for="dashboard_stream_activity_options[live_update]"><?php esc_html_e( 'Enable live updates', 'stream' ) ?></label>
|
191 |
+
</p>
|
192 |
+
</div>
|
193 |
+
<?php
|
194 |
+
}
|
195 |
+
|
196 |
+
/**
|
197 |
+
* Renders rows for Stream Activity Dashboard Widget
|
198 |
+
*
|
199 |
+
* @param obj Record to be inserted
|
200 |
+
* @param int Row number
|
201 |
+
*
|
202 |
+
* @return string Contents of new row
|
203 |
+
*/
|
204 |
+
public static function widget_row( $item ) {
|
205 |
+
$author = new WP_Stream_Author( (int) $item->author, (array) $item->author_meta );
|
206 |
+
|
207 |
+
$time_author = sprintf(
|
208 |
+
_x(
|
209 |
+
'%1$s ago by <a href="%2$s">%3$s</a>',
|
210 |
+
'1: Time, 2: User profile URL, 3: User display name',
|
211 |
+
'stream'
|
212 |
+
),
|
213 |
+
human_time_diff( strtotime( $item->created ) ),
|
214 |
+
esc_url( $author->get_records_page_url() ),
|
215 |
+
esc_html( $author->get_display_name() )
|
216 |
+
);
|
217 |
+
|
218 |
+
if ( $author->get_agent() ) {
|
219 |
+
$time_author .= sprintf( ' %s', WP_Stream_Author::get_agent_label( $author->get_agent() ) );
|
220 |
+
}
|
221 |
+
|
222 |
+
ob_start()
|
223 |
+
?>
|
224 |
+
<li data-datetime="<?php echo wp_stream_get_iso_8601_extended_date( strtotime( $item->created ) ) ?>">
|
225 |
+
<div class="record-avatar">
|
226 |
+
<a href="<?php echo esc_url( $author->get_records_page_url() ) ?>">
|
227 |
+
<?php echo $author->get_avatar_img( 72 ); // xss ok ?>
|
228 |
+
</a>
|
229 |
+
</div>
|
230 |
+
<span class="record-meta"><?php echo $time_author; // xss ok ?></span>
|
231 |
+
<br/>
|
232 |
+
<?php echo esc_html( $item->summary ) ?>
|
233 |
+
</li>
|
234 |
+
<?php
|
235 |
+
|
236 |
+
return ob_get_clean();
|
237 |
+
}
|
238 |
+
|
239 |
+
/**
|
240 |
+
* Handles Live Updates for Stream Activity Dashboard Widget.
|
241 |
+
*
|
242 |
+
* @uses gather_updated_items
|
243 |
+
*
|
244 |
+
* @param array Response to heartbeat
|
245 |
+
* @param array Response from heartbeat
|
246 |
+
*
|
247 |
+
* @return array Data sent to heartbeat
|
248 |
+
*/
|
249 |
+
public static function live_update( $response, $data ) {
|
250 |
+
if ( ! isset( $data['wp-stream-heartbeat-last-time'] ) ) {
|
251 |
+
return;
|
252 |
+
}
|
253 |
+
|
254 |
+
$send = array();
|
255 |
+
|
256 |
+
$last_time = $data['wp-stream-heartbeat-last-time'];
|
257 |
+
|
258 |
+
$updated_items = WP_Stream_Live_Update::gather_updated_items( $last_time );
|
259 |
+
|
260 |
+
if ( ! empty( $updated_items ) ) {
|
261 |
+
ob_start();
|
262 |
+
foreach ( $updated_items as $item ) {
|
263 |
+
echo self::widget_row( $item ); //xss okay
|
264 |
+
}
|
265 |
+
|
266 |
+
$send = ob_get_clean();
|
267 |
+
}
|
268 |
+
|
269 |
+
return $send;
|
270 |
+
}
|
271 |
+
|
272 |
+
}
|
classes/class-wp-stream-date-interval.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
// Load Carbon to Handle dates much easier
|
4 |
+
if ( ! class_exists( 'Carbon\Carbon' ) ) {
|
5 |
+
require_once WP_STREAM_INC_DIR . 'lib/Carbon.php';
|
6 |
+
}
|
7 |
+
|
8 |
+
use Carbon\Carbon;
|
9 |
+
|
10 |
+
class WP_Stream_Date_Interval {
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Contains an array of all available intervals
|
14 |
+
*
|
15 |
+
* @var array $intervals
|
16 |
+
*/
|
17 |
+
public $intervals;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Class constructor
|
21 |
+
*/
|
22 |
+
public function __construct() {
|
23 |
+
// Get all default intervals
|
24 |
+
$this->intervals = $this->get_predefined_intervals();
|
25 |
+
}
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @return mixed|void
|
29 |
+
*/
|
30 |
+
public function get_predefined_intervals() {
|
31 |
+
$timezone = get_option( 'timezone_string' );
|
32 |
+
|
33 |
+
if ( empty( $timezone ) ) {
|
34 |
+
$gmt_offset = (int) get_option( 'gmt_offset' );
|
35 |
+
$timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, true );
|
36 |
+
if ( false === $timezone ) {
|
37 |
+
$timezone = timezone_name_from_abbr( null, $gmt_offset * 3600, false );
|
38 |
+
}
|
39 |
+
if ( false === $timezone ) {
|
40 |
+
$timezone = null;
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
return apply_filters(
|
45 |
+
'wp_stream_predefined_date_intervals',
|
46 |
+
array(
|
47 |
+
'today' => array(
|
48 |
+
'label' => esc_html__( 'Today', 'stream' ),
|
49 |
+
'start' => Carbon::today( $timezone )->startOfDay(),
|
50 |
+
'end' => Carbon::today( $timezone )->endOfDay(),
|
51 |
+
),
|
52 |
+
'yesterday' => array(
|
53 |
+
'label' => esc_html__( 'Yesterday', 'stream' ),
|
54 |
+
'start' => Carbon::today( $timezone )->startOfDay()->subDay(),
|
55 |
+
'end' => Carbon::today( $timezone )->startOfDay()->subSecond(),
|
56 |
+
),
|
57 |
+
'last-7-days' => array(
|
58 |
+
'label' => sprintf( esc_html__( 'Last %d Days', 'stream' ), 7 ),
|
59 |
+
'start' => Carbon::today( $timezone )->subDays( 7 ),
|
60 |
+
'end' => Carbon::today( $timezone ),
|
61 |
+
),
|
62 |
+
'last-14-days' => array(
|
63 |
+
'label' => sprintf( esc_html__( 'Last %d Days', 'stream' ), 14 ),
|
64 |
+
'start' => Carbon::today( $timezone )->subDays( 14 ),
|
65 |
+
'end' => Carbon::today( $timezone ),
|
66 |
+
),
|
67 |
+
'last-30-days' => array(
|
68 |
+
'label' => sprintf( esc_html__( 'Last %d Days', 'stream' ), 30 ),
|
69 |
+
'start' => Carbon::today( $timezone )->subDays( 30 ),
|
70 |
+
'end' => Carbon::today( $timezone ),
|
71 |
+
),
|
72 |
+
'this-month' => array(
|
73 |
+
'label' => esc_html__( 'This Month', 'stream' ),
|
74 |
+
'start' => Carbon::today( $timezone )->startOfMonth(),
|
75 |
+
'end' => Carbon::today( $timezone )->endOfMonth(),
|
76 |
+
),
|
77 |
+
'last-month' => array(
|
78 |
+
'label' => esc_html__( 'Last Month', 'stream' ),
|
79 |
+
'start' => Carbon::today( $timezone )->startOfMonth()->subMonth(),
|
80 |
+
'end' => Carbon::today( $timezone )->startOfMonth()->subSecond(),
|
81 |
+
),
|
82 |
+
'last-3-months' => array(
|
83 |
+
'label' => sprintf( esc_html__( 'Last %d Months', 'stream' ), 3 ),
|
84 |
+
'start' => Carbon::today( $timezone )->subMonths( 3 ),
|
85 |
+
'end' => Carbon::today( $timezone ),
|
86 |
+
),
|
87 |
+
'last-6-months' => array(
|
88 |
+
'label' => sprintf( esc_html__( 'Last %d Months', 'stream' ), 6 ),
|
89 |
+
'start' => Carbon::today( $timezone )->subMonths( 6 ),
|
90 |
+
'end' => Carbon::today( $timezone ),
|
91 |
+
),
|
92 |
+
'last-12-months' => array(
|
93 |
+
'label' => sprintf( esc_html__( 'Last %d Months', 'stream' ), 12 ),
|
94 |
+
'start' => Carbon::today( $timezone )->subMonths( 12 ),
|
95 |
+
'end' => Carbon::today( $timezone ),
|
96 |
+
),
|
97 |
+
'this-year' => array(
|
98 |
+
'label' => esc_html__( 'This Year', 'stream' ),
|
99 |
+
'start' => Carbon::today( $timezone )->startOfYear(),
|
100 |
+
'end' => Carbon::today( $timezone )->endOfYear(),
|
101 |
+
),
|
102 |
+
'last-year' => array(
|
103 |
+
'label' => esc_html__( 'Last Year', 'stream' ),
|
104 |
+
'start' => Carbon::today( $timezone )->startOfYear()->subYear(),
|
105 |
+
'end' => Carbon::today( $timezone )->startOfYear()->subSecond(),
|
106 |
+
),
|
107 |
+
),
|
108 |
+
$timezone
|
109 |
+
);
|
110 |
+
}
|
111 |
+
|
112 |
+
}
|
classes/class-wp-stream-db.php
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_DB {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Meta information returned in the last query
|
7 |
+
*
|
8 |
+
* @var mixed
|
9 |
+
*/
|
10 |
+
public $query_meta = false;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Store records
|
14 |
+
*
|
15 |
+
* @param array $records
|
16 |
+
*
|
17 |
+
* @return mixed True if updated, otherwise false|WP_Error
|
18 |
+
*/
|
19 |
+
public function store( $records ) {
|
20 |
+
// Take only what's ours!
|
21 |
+
$valid_keys = get_class_vars( 'WP_Stream_Record' );
|
22 |
+
|
23 |
+
// Fill in defaults
|
24 |
+
$defaults = array(
|
25 |
+
'type' => 'stream',
|
26 |
+
'site_id' => 1,
|
27 |
+
'blog_id' => 0,
|
28 |
+
'object_id' => 0,
|
29 |
+
'author' => 0,
|
30 |
+
'author_role' => '',
|
31 |
+
'visibility' => 'publish',
|
32 |
+
'ip' => '',
|
33 |
+
);
|
34 |
+
|
35 |
+
foreach ( $records as $key => $record ) {
|
36 |
+
$records[ $key ] = array_intersect_key( $record, $valid_keys );
|
37 |
+
$records[ $key ] = array_filter( $record );
|
38 |
+
$records[ $key ] = wp_parse_args( $record, $defaults );
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Allows modification of record information just before logging occurs.
|
43 |
+
*
|
44 |
+
* @since 0.2.0
|
45 |
+
*
|
46 |
+
* @param array $records An array of record data.
|
47 |
+
*/
|
48 |
+
$records = apply_filters( 'wp_stream_record_array', $records );
|
49 |
+
|
50 |
+
// Allow extensions to handle the saving process
|
51 |
+
if ( empty( $records ) ) {
|
52 |
+
return false;
|
53 |
+
}
|
54 |
+
|
55 |
+
// TODO: Check/Validate *required* fields
|
56 |
+
|
57 |
+
$result = $this->insert( $records );
|
58 |
+
|
59 |
+
if ( $result && ! is_wp_error( $result ) ) {
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Fires when A Post is inserted
|
63 |
+
*
|
64 |
+
* @since 2.0.0
|
65 |
+
*
|
66 |
+
* @param int $record_id Inserted record ID
|
67 |
+
* @param array $recordarr Array of information on this record
|
68 |
+
*/
|
69 |
+
do_action( 'wp_stream_records_inserted', $records );
|
70 |
+
|
71 |
+
return true;
|
72 |
+
}
|
73 |
+
|
74 |
+
return is_wp_error( $result ) ? $result : false;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Insert a new record
|
79 |
+
*
|
80 |
+
* @internal Used by store()
|
81 |
+
*
|
82 |
+
* @param array $records
|
83 |
+
*
|
84 |
+
* @return object $response The inserted records
|
85 |
+
*/
|
86 |
+
private function insert( array $records ) {
|
87 |
+
return WP_Stream::$api->new_records( $records );
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Query records
|
92 |
+
*
|
93 |
+
* @internal Used by WP_Stream_Query, and is not designed to be called explicitly
|
94 |
+
*
|
95 |
+
* @param array $query Query body.
|
96 |
+
* @param array $fields Returns specified fields only.
|
97 |
+
*
|
98 |
+
* @return array List of records that match query
|
99 |
+
*/
|
100 |
+
public function query( $query, $fields ) {
|
101 |
+
$response = WP_Stream::$api->search( $query, $fields );
|
102 |
+
|
103 |
+
if ( empty( $response ) || ! isset( $response->meta ) || ! isset( $response->records ) ) {
|
104 |
+
return false;
|
105 |
+
}
|
106 |
+
|
107 |
+
$this->query_meta = $response->meta;
|
108 |
+
|
109 |
+
$results = (array) $response->records;
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Allows developers to change the final result set of records
|
113 |
+
*
|
114 |
+
* @since 2.0.0
|
115 |
+
*
|
116 |
+
* @param array $results
|
117 |
+
* @param array $query
|
118 |
+
* @param array $fields
|
119 |
+
*
|
120 |
+
* @return array Filtered array of record results
|
121 |
+
*/
|
122 |
+
return apply_filters( 'wp_stream_query_results', $results, $query, $fields );
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Get total count of the last query using query() method
|
127 |
+
*
|
128 |
+
* @return integer Total item count
|
129 |
+
*/
|
130 |
+
public function get_found_rows() {
|
131 |
+
if ( ! isset( $this->query_meta->total ) ) {
|
132 |
+
return 0;
|
133 |
+
}
|
134 |
+
return $this->query_meta->total;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Get meta data for last query using query() method
|
139 |
+
*
|
140 |
+
* @return array Meta data for query
|
141 |
+
*/
|
142 |
+
public function get_query_meta() {
|
143 |
+
return $this->query_meta;
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Returns array of existing values for requested field.
|
148 |
+
* Used to fill search filters with only used items, instead of all items.
|
149 |
+
*
|
150 |
+
* @param string Requested field (i.e., 'context')
|
151 |
+
*
|
152 |
+
* @return array Array of distinct values
|
153 |
+
*/
|
154 |
+
public function get_distinct_field_values( $field ) {
|
155 |
+
$query['aggregations']['fields']['terms']['field'] = $field;
|
156 |
+
|
157 |
+
$values = array();
|
158 |
+
$response = WP_Stream::$api->search( $query, array( $field ) );
|
159 |
+
|
160 |
+
if ( isset( $response->meta->aggregations->fields->buckets ) ) {
|
161 |
+
foreach ( $response->meta->aggregations->fields->buckets as $field ) {
|
162 |
+
$values[] = $field->key;
|
163 |
+
}
|
164 |
+
}
|
165 |
+
|
166 |
+
return $values;
|
167 |
+
}
|
168 |
+
}
|
classes/class-wp-stream-feeds.php
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Feeds {
|
4 |
+
|
5 |
+
const FEED_QUERY_VAR = 'stream';
|
6 |
+
const FEED_KEY_QUERY_VAR = 'key';
|
7 |
+
const FEED_TYPE_QUERY_VAR = 'type';
|
8 |
+
const USER_FEED_OPTION_KEY = 'stream_user_feed_key';
|
9 |
+
const GENERATE_KEY_QUERY_VAR = 'stream_new_user_feed_key';
|
10 |
+
|
11 |
+
public static function load() {
|
12 |
+
if ( ! is_admin() ) {
|
13 |
+
$feed_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
|
14 |
+
}
|
15 |
+
|
16 |
+
if ( ! isset( WP_Stream_Settings::$options['general_private_feeds'] ) || ! WP_Stream_Settings::$options['general_private_feeds'] ) {
|
17 |
+
return;
|
18 |
+
}
|
19 |
+
|
20 |
+
add_action( 'show_user_profile', array( __CLASS__, 'save_user_feed_key' ) );
|
21 |
+
add_action( 'edit_user_profile', array( __CLASS__, 'save_user_feed_key' ) );
|
22 |
+
|
23 |
+
add_action( 'show_user_profile', array( __CLASS__, 'user_feed_key' ) );
|
24 |
+
add_action( 'edit_user_profile', array( __CLASS__, 'user_feed_key' ) );
|
25 |
+
|
26 |
+
// Generate new Stream Feed Key
|
27 |
+
add_action( 'wp_ajax_wp_stream_feed_key_generate', array( __CLASS__, 'generate_user_feed_key' ) );
|
28 |
+
|
29 |
+
add_feed( self::FEED_QUERY_VAR, array( __CLASS__, 'feed_template' ) );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Sends a new user key when the
|
34 |
+
*
|
35 |
+
* @return void/json
|
36 |
+
*/
|
37 |
+
public static function generate_user_feed_key() {
|
38 |
+
check_ajax_referer( 'wp_stream_generate_key', 'nonce' );
|
39 |
+
|
40 |
+
$user_id = wp_stream_filter_input( INPUT_POST, 'user', FILTER_SANITIZE_NUMBER_INT );
|
41 |
+
|
42 |
+
if ( $user_id ) {
|
43 |
+
$feed_key = wp_generate_password( 32, false );
|
44 |
+
update_user_meta( $user_id, self::USER_FEED_OPTION_KEY, $feed_key );
|
45 |
+
|
46 |
+
$link = self::get_user_feed_url( $feed_key );
|
47 |
+
$xml_feed = add_query_arg( array( 'type' => 'json' ), $link );
|
48 |
+
$json_feed = add_query_arg( array( 'type' => 'json' ), $link );
|
49 |
+
|
50 |
+
wp_send_json_success(
|
51 |
+
array(
|
52 |
+
'message' => 'User feed key successfully generated.',
|
53 |
+
'feed_key' => $feed_key,
|
54 |
+
'xml_feed' => $xml_feed,
|
55 |
+
'json_feed' => $json_feed,
|
56 |
+
)
|
57 |
+
);
|
58 |
+
} else {
|
59 |
+
wp_send_json_error( 'User ID error' );
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Generates and saves a unique key as user meta if the user does not
|
65 |
+
* already have a key, or has requested a new one.
|
66 |
+
*
|
67 |
+
* @action show_user_profile
|
68 |
+
* @action edit_user_profile
|
69 |
+
* @param WP_User $user
|
70 |
+
* @return void
|
71 |
+
*/
|
72 |
+
public static function save_user_feed_key( $user ) {
|
73 |
+
$generate_key = wp_stream_filter_input( INPUT_GET, self::GENERATE_KEY_QUERY_VAR );
|
74 |
+
$nonce = wp_stream_filter_input( INPUT_GET, 'wp_stream_nonce' );
|
75 |
+
|
76 |
+
if ( ! $generate_key && get_user_meta( $user->ID, self::USER_FEED_OPTION_KEY, true ) ) {
|
77 |
+
return;
|
78 |
+
}
|
79 |
+
|
80 |
+
if ( $generate_key && ! wp_verify_nonce( $nonce, 'wp_stream_generate_key' ) ) {
|
81 |
+
return;
|
82 |
+
}
|
83 |
+
|
84 |
+
$feed_key = wp_generate_password( 32, false );
|
85 |
+
|
86 |
+
update_user_meta( $user->ID, self::USER_FEED_OPTION_KEY, $feed_key );
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Output for Stream Feed URL field in user profiles.
|
91 |
+
*
|
92 |
+
* @action show_user_profile
|
93 |
+
* @action edit_user_profile
|
94 |
+
* @param WP_User $user
|
95 |
+
* @return string
|
96 |
+
*/
|
97 |
+
public static function user_feed_key( $user ) {
|
98 |
+
if ( ! array_intersect( $user->roles, WP_Stream_Settings::$options['general_role_access'] ) ) {
|
99 |
+
return;
|
100 |
+
}
|
101 |
+
|
102 |
+
$key = get_user_meta( $user->ID, self::USER_FEED_OPTION_KEY, true );
|
103 |
+
$link = self::get_user_feed_url( $key );
|
104 |
+
|
105 |
+
$nonce = wp_create_nonce( 'wp_stream_generate_key' );
|
106 |
+
?>
|
107 |
+
<table class="form-table">
|
108 |
+
<tr>
|
109 |
+
<th><label for="<?php echo esc_attr( self::USER_FEED_OPTION_KEY ) ?>"><?php esc_html_e( 'Stream Feeds Key', 'stream' ) ?></label></th>
|
110 |
+
<td>
|
111 |
+
<p class="wp-stream-feeds-key">
|
112 |
+
<?php wp_nonce_field( 'wp_stream_generate_key', 'wp_stream_generate_key_nonce' ) ?>
|
113 |
+
<input type="text" name="<?php echo esc_attr( self::USER_FEED_OPTION_KEY ) ?>" id="<?php echo esc_attr( self::USER_FEED_OPTION_KEY ) ?>" class="regular-text code" value="<?php echo esc_attr( $key ) ?>" readonly>
|
114 |
+
<small><a href="<?php echo esc_url( add_query_arg( array( self::GENERATE_KEY_QUERY_VAR => true, 'wp_stream_nonce' => $nonce ) ) ) ?>" id="<?php echo esc_attr( self::USER_FEED_OPTION_KEY ) ?>_generate"><?php esc_html_e( 'Generate new key', 'stream' ) ?></a></small>
|
115 |
+
<span class="spinner" style="display: none;"></span>
|
116 |
+
</p>
|
117 |
+
<p class="description"><?php esc_html_e( 'This is your private key used for accessing feeds of Stream Records securely. You can change your key at any time by generating a new one using the link above.', 'stream' ) ?></p>
|
118 |
+
<p class="wp-stream-feeds-links">
|
119 |
+
<a href="<?php echo esc_url( add_query_arg( array( 'type' => 'rss' ), $link ) ) ?>" class="rss-feed" target="_blank"><?php echo esc_html_e( 'RSS Feed', 'stream' ) ?></a>
|
120 |
+
|
|
121 |
+
<a href="<?php echo esc_url( add_query_arg( array( 'type' => 'atom' ), $link ) ) ?>" class="atom-feed" target="_blank"><?php echo esc_html_e( 'ATOM Feed', 'stream' ) ?></a>
|
122 |
+
|
|
123 |
+
<a href="<?php echo esc_url( add_query_arg( array( 'type' => 'json' ), $link ) ) ?>" class="json-feed" target="_blank"><?php echo esc_html_e( 'JSON Feed', 'stream' ) ?></a>
|
124 |
+
</p>
|
125 |
+
</td>
|
126 |
+
</tr>
|
127 |
+
</table>
|
128 |
+
<?php
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Return Stream Feed URL
|
133 |
+
*
|
134 |
+
* @return string
|
135 |
+
*/
|
136 |
+
public static function get_user_feed_url( $key ) {
|
137 |
+
$pretty_permalinks = get_option( 'permalink_structure' );
|
138 |
+
$query_var = self::FEED_QUERY_VAR;
|
139 |
+
|
140 |
+
if ( empty( $pretty_permalinks ) ) {
|
141 |
+
$link = add_query_arg(
|
142 |
+
array(
|
143 |
+
'feed' => $query_var,
|
144 |
+
self::FEED_KEY_QUERY_VAR => $key,
|
145 |
+
),
|
146 |
+
home_url( '/' )
|
147 |
+
);
|
148 |
+
} else {
|
149 |
+
$link = add_query_arg(
|
150 |
+
array(
|
151 |
+
self::FEED_KEY_QUERY_VAR => $key,
|
152 |
+
),
|
153 |
+
home_url(
|
154 |
+
sprintf(
|
155 |
+
'/feed/%s/',
|
156 |
+
$query_var
|
157 |
+
)
|
158 |
+
)
|
159 |
+
);
|
160 |
+
}
|
161 |
+
|
162 |
+
return $link;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Output for Stream Records as a feed.
|
167 |
+
*
|
168 |
+
* @return xml
|
169 |
+
*/
|
170 |
+
public static function feed_template() {
|
171 |
+
$die_title = esc_html__( 'Access Denied', 'stream' );
|
172 |
+
$die_message = sprintf( '<h1>%s</h1><p>%s</p>', $die_title, esc_html__( "You don't have permission to view this feed, please contact your site Administrator.", 'stream' ) );
|
173 |
+
$query_var = self::FEED_QUERY_VAR;
|
174 |
+
|
175 |
+
$args = array(
|
176 |
+
'meta_key' => self::USER_FEED_OPTION_KEY,
|
177 |
+
'meta_value' => wp_stream_filter_input( INPUT_GET, self::FEED_KEY_QUERY_VAR ),
|
178 |
+
'number' => 1,
|
179 |
+
);
|
180 |
+
$user = get_users( $args );
|
181 |
+
|
182 |
+
if ( empty( $user ) ) {
|
183 |
+
wp_die( $die_message, $die_title );
|
184 |
+
}
|
185 |
+
|
186 |
+
if ( ! is_super_admin( $user[0]->ID ) ) {
|
187 |
+
$roles = isset( $user[0]->roles ) ? (array) $user[0]->roles : array();
|
188 |
+
|
189 |
+
if ( ! $roles || ! array_intersect( $roles, WP_Stream_Settings::$options['general_role_access'] ) ) {
|
190 |
+
wp_die( $die_message, $die_title );
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
$args = array(
|
195 |
+
'search' => wp_stream_filter_input( INPUT_GET, 'search' ),
|
196 |
+
'record_after' => wp_stream_filter_input( INPUT_GET, 'record_after' ), // Deprecated, use date_after instead
|
197 |
+
'date' => wp_stream_filter_input( INPUT_GET, 'date' ),
|
198 |
+
'date_from' => wp_stream_filter_input( INPUT_GET, 'date_from' ),
|
199 |
+
'date_to' => wp_stream_filter_input( INPUT_GET, 'date_to' ),
|
200 |
+
'date_after' => wp_stream_filter_input( INPUT_GET, 'date_after' ),
|
201 |
+
'date_before' => wp_stream_filter_input( INPUT_GET, 'date_before' ),
|
202 |
+
'record' => wp_stream_filter_input( INPUT_GET, 'record' ),
|
203 |
+
'record__in' => wp_stream_filter_input( INPUT_GET, 'record__in' ),
|
204 |
+
'record__not_in' => wp_stream_filter_input( INPUT_GET, 'record__not_in' ),
|
205 |
+
'records_per_page' => wp_stream_filter_input( INPUT_GET, 'records_per_page', FILTER_SANITIZE_NUMBER_INT ),
|
206 |
+
'order' => wp_stream_filter_input( INPUT_GET, 'order' ),
|
207 |
+
'orderby' => wp_stream_filter_input( INPUT_GET, 'orderby' ),
|
208 |
+
'meta' => wp_stream_filter_input( INPUT_GET, 'meta' ),
|
209 |
+
'fields' => wp_stream_filter_input( INPUT_GET, 'fields' ),
|
210 |
+
);
|
211 |
+
|
212 |
+
$properties = array(
|
213 |
+
'author',
|
214 |
+
'author_role',
|
215 |
+
'ip',
|
216 |
+
'object_id',
|
217 |
+
'connector',
|
218 |
+
'context',
|
219 |
+
'action',
|
220 |
+
);
|
221 |
+
|
222 |
+
foreach ( $properties as $property ) {
|
223 |
+
$args[ $property ] = wp_stream_filter_input( INPUT_GET, $property );
|
224 |
+
$args[ "{$property}__in" ] = wp_stream_filter_input( INPUT_GET, "{$property}__in" );
|
225 |
+
$args[ "{$property}__not_in" ] = wp_stream_filter_input( INPUT_GET, "{$property}__not_in" );
|
226 |
+
}
|
227 |
+
|
228 |
+
$records = wp_stream_query( $args );
|
229 |
+
|
230 |
+
$latest_record = isset( $records[0]->created ) ? $records[0]->created : null;
|
231 |
+
|
232 |
+
$records_admin_url = add_query_arg(
|
233 |
+
array(
|
234 |
+
'page' => WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
235 |
+
),
|
236 |
+
admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE )
|
237 |
+
);
|
238 |
+
|
239 |
+
$latest_link = null;
|
240 |
+
|
241 |
+
if ( isset( $records[0]->ID ) ) {
|
242 |
+
$latest_link = add_query_arg(
|
243 |
+
array(
|
244 |
+
'record__in' => $records[0]->ID,
|
245 |
+
),
|
246 |
+
$records_admin_url
|
247 |
+
);
|
248 |
+
}
|
249 |
+
|
250 |
+
$domain = parse_url( $records_admin_url, PHP_URL_HOST );
|
251 |
+
$format = wp_stream_filter_input( INPUT_GET, self::FEED_TYPE_QUERY_VAR );
|
252 |
+
|
253 |
+
if ( 'atom' === $format ) {
|
254 |
+
require_once WP_STREAM_INC_DIR . 'feeds/atom.php';
|
255 |
+
} elseif ( 'json' === $format ) {
|
256 |
+
require_once WP_STREAM_INC_DIR . 'feeds/json.php';
|
257 |
+
} else {
|
258 |
+
require_once WP_STREAM_INC_DIR . 'feeds/rss-2.0.php';
|
259 |
+
}
|
260 |
+
|
261 |
+
exit;
|
262 |
+
}
|
263 |
+
|
264 |
+
}
|
classes/class-wp-stream-filter-input.php
ADDED
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Filter_Input {
|
4 |
+
|
5 |
+
public static $filter_callbacks = array(
|
6 |
+
FILTER_DEFAULT => null,
|
7 |
+
// Validate
|
8 |
+
FILTER_VALIDATE_BOOLEAN => 'is_bool',
|
9 |
+
FILTER_VALIDATE_EMAIL => 'is_email',
|
10 |
+
FILTER_VALIDATE_FLOAT => 'is_float',
|
11 |
+
FILTER_VALIDATE_INT => 'is_int',
|
12 |
+
FILTER_VALIDATE_IP => array( 'WP_Stream_Filter_Input', 'is_ip_address' ),
|
13 |
+
FILTER_VALIDATE_REGEXP => array( 'WP_Stream_Filter_Input', 'is_regex' ),
|
14 |
+
FILTER_VALIDATE_URL => 'wp_http_validate_url',
|
15 |
+
// Sanitize
|
16 |
+
FILTER_SANITIZE_EMAIL => 'sanitize_email',
|
17 |
+
FILTER_SANITIZE_ENCODED => 'esc_url_raw',
|
18 |
+
FILTER_SANITIZE_NUMBER_FLOAT => 'floatval',
|
19 |
+
FILTER_SANITIZE_NUMBER_INT => 'intval',
|
20 |
+
FILTER_SANITIZE_SPECIAL_CHARS => 'htmlspecialchars',
|
21 |
+
FILTER_SANITIZE_STRING => 'sanitize_text_field',
|
22 |
+
FILTER_SANITIZE_URL => 'esc_url_raw',
|
23 |
+
// Other
|
24 |
+
FILTER_UNSAFE_RAW => null,
|
25 |
+
);
|
26 |
+
|
27 |
+
public static function super( $type, $variable_name, $filter = null, $options = array() ) {
|
28 |
+
$super = null;
|
29 |
+
|
30 |
+
switch ( $type ) {
|
31 |
+
case INPUT_POST :
|
32 |
+
$super = $_POST;
|
33 |
+
break;
|
34 |
+
case INPUT_GET :
|
35 |
+
$super = $_GET;
|
36 |
+
break;
|
37 |
+
case INPUT_COOKIE :
|
38 |
+
$super = $_COOKIE;
|
39 |
+
break;
|
40 |
+
case INPUT_ENV :
|
41 |
+
$super = $_ENV;
|
42 |
+
break;
|
43 |
+
case INPUT_SERVER :
|
44 |
+
$super = $_SERVER;
|
45 |
+
break;
|
46 |
+
}
|
47 |
+
|
48 |
+
if ( is_null( $super ) ) {
|
49 |
+
throw new Exception( __( 'Invalid use, type must be one of INPUT_* family.', 'stream' ) );
|
50 |
+
}
|
51 |
+
|
52 |
+
$var = isset( $super[ $variable_name ] ) ? $super[ $variable_name ] : null;
|
53 |
+
$var = self::filter( $var, $filter, $options );
|
54 |
+
|
55 |
+
return $var;
|
56 |
+
}
|
57 |
+
|
58 |
+
public static function filter( $var, $filter = null, $options = array() ) {
|
59 |
+
// Default filter is a sanitizer, not validator
|
60 |
+
$filter_type = 'sanitizer';
|
61 |
+
|
62 |
+
// Only filter value if it is not null
|
63 |
+
if ( isset( $var ) && $filter && FILTER_DEFAULT !== $filter ) {
|
64 |
+
if ( ! isset( self::$filter_callbacks[ $filter ] ) ) {
|
65 |
+
throw new Exception( __( 'Filter not supported.', 'stream' ) );
|
66 |
+
}
|
67 |
+
|
68 |
+
$filter_callback = self::$filter_callbacks[ $filter ];
|
69 |
+
$result = call_user_func( $filter_callback, $var );
|
70 |
+
|
71 |
+
// filter_var / filter_input treats validation/sanitization filters the same
|
72 |
+
// they both return output and change the var value, this shouldn't be the case here.
|
73 |
+
// We'll do a boolean check on validation function, and let sanitizers change the value
|
74 |
+
$filter_type = ( $filter < 500 ) ? 'validator' : 'sanitizer';
|
75 |
+
if ( 'validator' === $filter_type ) { // Validation functions
|
76 |
+
if ( ! $result ) {
|
77 |
+
$var = false;
|
78 |
+
}
|
79 |
+
} else { // Santization functions
|
80 |
+
$var = $result;
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
// Detect FILTER_REQUIRE_ARRAY flag
|
85 |
+
if ( isset( $var ) && is_int( $options ) && FILTER_REQUIRE_ARRAY === $options ) {
|
86 |
+
if ( ! is_array( $var ) ) {
|
87 |
+
$var = ( 'validator' === $filter_type ) ? false : null;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
// Polyfill the `default` attribute only, for now.
|
92 |
+
if ( is_array( $options ) && ! empty( $options['options']['default'] ) ) {
|
93 |
+
if ( 'validator' === $filter_type && false === $var ) {
|
94 |
+
$var = $options['options']['default'];
|
95 |
+
} elseif ( 'sanitizer' === $filter_type && null === $var ) {
|
96 |
+
$var = $options['options']['default'];
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
return $var;
|
101 |
+
}
|
102 |
+
|
103 |
+
public static function is_regex( $var ) {
|
104 |
+
// @codingStandardsIgnoreStart
|
105 |
+
$test = @preg_match( $var, '' );
|
106 |
+
// @codingStandardsIgnoreEnd
|
107 |
+
|
108 |
+
return $test !== false;
|
109 |
+
}
|
110 |
+
|
111 |
+
public static function is_ip_address( $var ) {
|
112 |
+
return false !== WP_Http::is_ip_address( $var );
|
113 |
+
}
|
114 |
+
|
115 |
+
}
|
classes/class-wp-stream-list-table.php
ADDED
@@ -0,0 +1,923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_List_Table extends WP_List_Table {
|
4 |
+
|
5 |
+
function __construct( $args = array() ) {
|
6 |
+
|
7 |
+
$screen_id = isset( $args['screen'] ) ? $args['screen'] : null;
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Filter the list table screen ID
|
11 |
+
*
|
12 |
+
* @since 1.4.0
|
13 |
+
*
|
14 |
+
* @return string
|
15 |
+
*/
|
16 |
+
$screen_id = apply_filters( 'wp_stream_list_table_screen_id', $screen_id );
|
17 |
+
|
18 |
+
parent::__construct(
|
19 |
+
array(
|
20 |
+
'post_type' => 'stream',
|
21 |
+
'plural' => 'records',
|
22 |
+
'screen' => $screen_id,
|
23 |
+
)
|
24 |
+
);
|
25 |
+
|
26 |
+
add_screen_option(
|
27 |
+
'per_page',
|
28 |
+
array(
|
29 |
+
'default' => 20,
|
30 |
+
'label' => __( 'Records per page', 'stream' ),
|
31 |
+
'option' => 'edit_stream_per_page',
|
32 |
+
)
|
33 |
+
);
|
34 |
+
|
35 |
+
// Check for default hidden columns
|
36 |
+
$this->get_hidden_columns();
|
37 |
+
|
38 |
+
add_filter( 'screen_settings', array( $this, 'screen_controls' ), 10, 2 );
|
39 |
+
add_filter( 'set-screen-option', array( __CLASS__, 'set_screen_option' ), 10, 3 );
|
40 |
+
|
41 |
+
set_screen_options();
|
42 |
+
}
|
43 |
+
|
44 |
+
function extra_tablenav( $which ) {
|
45 |
+
if ( 'top' === $which ) {
|
46 |
+
echo $this->filters_form(); //xss ok
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
function no_items() {
|
51 |
+
$site = WP_Stream::$api->get_site();
|
52 |
+
$plan_type = WP_Stream::$api->get_plan_type();
|
53 |
+
$plan_retention = WP_Stream::$api->get_plan_retention();
|
54 |
+
|
55 |
+
if ( 'free' === $plan_type && ! empty( $plan_retention ) && 0 !== $this->get_total_found_rows() ) {
|
56 |
+
?>
|
57 |
+
<div class="stream-list-table-upgrade">
|
58 |
+
<p><?php printf( _n( 'Your free account is limited to viewing <strong>1 day</strong> of activity history.', 'Your free account is limited to viewing <strong>%d days</strong> of activity history.', absint( $plan_retention ), 'stream' ), absint( $plan_retention ) ) ?></p>
|
59 |
+
<p><a href="<?php echo esc_url( WP_Stream_Admin::account_url( sprintf( 'upgrade?site_uuid=%s', WP_Stream::$api->site_uuid ) ) ); ?>" class="button button-primary button-large"><?php _e( 'Upgrade to Pro', 'stream' ) ?></a></p>
|
60 |
+
</div>
|
61 |
+
<?php
|
62 |
+
} else {
|
63 |
+
_e( 'Sorry, no activity records were found.', 'stream' );
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
function get_columns(){
|
68 |
+
/**
|
69 |
+
* Allows devs to add new columns to table
|
70 |
+
*
|
71 |
+
* @since 0.2.0
|
72 |
+
*
|
73 |
+
* @return array
|
74 |
+
*/
|
75 |
+
return apply_filters(
|
76 |
+
'wp_stream_list_table_columns',
|
77 |
+
array(
|
78 |
+
'date' => __( 'Date', 'stream' ),
|
79 |
+
'summary' => __( 'Summary', 'stream' ),
|
80 |
+
'author' => __( 'Author', 'stream' ),
|
81 |
+
'context' => __( 'Context', 'stream' ),
|
82 |
+
'action' => __( 'Action', 'stream' ),
|
83 |
+
'ip' => __( 'IP Address', 'stream' ),
|
84 |
+
)
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
function get_sortable_columns() {
|
89 |
+
return array(
|
90 |
+
'date' => array( 'date', false ),
|
91 |
+
);
|
92 |
+
}
|
93 |
+
|
94 |
+
function get_hidden_columns() {
|
95 |
+
if ( ! $user = wp_get_current_user() ) {
|
96 |
+
return array();
|
97 |
+
}
|
98 |
+
|
99 |
+
// Directly checking the user meta; to check whether user has changed screen option or not
|
100 |
+
$hidden = get_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', true );
|
101 |
+
|
102 |
+
// If user meta is not found; add the default hidden column 'id'
|
103 |
+
if ( ! $hidden ) {
|
104 |
+
$hidden = array( 'id' );
|
105 |
+
update_user_meta( $user->ID, 'manage' . $this->screen->id . 'columnshidden', $hidden );
|
106 |
+
}
|
107 |
+
|
108 |
+
return $hidden;
|
109 |
+
}
|
110 |
+
|
111 |
+
function prepare_items() {
|
112 |
+
$columns = $this->get_columns();
|
113 |
+
$sortable = $this->get_sortable_columns();
|
114 |
+
$hidden = $this->get_hidden_columns();
|
115 |
+
|
116 |
+
$this->_column_headers = array( $columns, $hidden, $sortable );
|
117 |
+
|
118 |
+
$this->items = $this->get_records();
|
119 |
+
|
120 |
+
$total_items = $this->get_total_found_rows();
|
121 |
+
|
122 |
+
$this->set_pagination_args(
|
123 |
+
array(
|
124 |
+
'total_items' => $total_items,
|
125 |
+
'per_page' => $this->get_items_per_page( 'edit_stream_per_page', 20 ),
|
126 |
+
)
|
127 |
+
);
|
128 |
+
}
|
129 |
+
|
130 |
+
/**
|
131 |
+
* Render the checkbox column
|
132 |
+
*
|
133 |
+
* @param array $item Contains all the data for the checkbox column
|
134 |
+
*
|
135 |
+
* @return string Displays a checkbox
|
136 |
+
*/
|
137 |
+
function column_cb( $item ) {
|
138 |
+
return sprintf(
|
139 |
+
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
|
140 |
+
/*$1%s*/
|
141 |
+
'wp_stream_checkbox',
|
142 |
+
/*$2%s*/
|
143 |
+
$item->ID
|
144 |
+
);
|
145 |
+
}
|
146 |
+
|
147 |
+
function get_records() {
|
148 |
+
$args = array();
|
149 |
+
|
150 |
+
// Parse sorting params
|
151 |
+
if ( $order = wp_stream_filter_input( INPUT_GET, 'order' ) ) {
|
152 |
+
$args['order'] = $order;
|
153 |
+
}
|
154 |
+
if ( $orderby = wp_stream_filter_input( INPUT_GET, 'orderby' ) ) {
|
155 |
+
$args['orderby'] = $orderby;
|
156 |
+
}
|
157 |
+
|
158 |
+
// Filters
|
159 |
+
$params = array(
|
160 |
+
'search',
|
161 |
+
'date',
|
162 |
+
'date_from',
|
163 |
+
'date_to',
|
164 |
+
'record_after', // Deprecated, use date_after instead
|
165 |
+
'date_after',
|
166 |
+
'date_before',
|
167 |
+
);
|
168 |
+
|
169 |
+
foreach ( $params as $param ) {
|
170 |
+
$value = wp_stream_filter_input( INPUT_GET, $param );
|
171 |
+
if ( $value ) {
|
172 |
+
$args[ $param ] = $value;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
|
176 |
+
// Additional filter properties
|
177 |
+
$properties = array(
|
178 |
+
'record',
|
179 |
+
'author',
|
180 |
+
'author_role',
|
181 |
+
'ip',
|
182 |
+
'object_id',
|
183 |
+
'site_id',
|
184 |
+
'blog_id',
|
185 |
+
'connector',
|
186 |
+
'context',
|
187 |
+
'action',
|
188 |
+
);
|
189 |
+
|
190 |
+
// Add property fields to defaults, including their __in/__not_in variations
|
191 |
+
foreach ( $properties as $property ) {
|
192 |
+
$value = wp_stream_filter_input( INPUT_GET, $property );
|
193 |
+
|
194 |
+
// Allow 0 values
|
195 |
+
if ( isset( $value ) && '' !== $value && false !== $value ) {
|
196 |
+
$args[ $property ] = $value;
|
197 |
+
}
|
198 |
+
|
199 |
+
$value_in = wp_stream_filter_input( INPUT_GET, $property . '__in' );
|
200 |
+
|
201 |
+
if ( $value_in ) {
|
202 |
+
$args[ $property . '__in' ] = explode( ',', $value_in );
|
203 |
+
}
|
204 |
+
|
205 |
+
$value_not_in = wp_stream_filter_input( INPUT_GET, $property . '__not_in' );
|
206 |
+
|
207 |
+
if ( $value_not_in ) {
|
208 |
+
$args[ $property . '__not_in' ] = explode( ',', $value_not_in );
|
209 |
+
}
|
210 |
+
}
|
211 |
+
|
212 |
+
$args['paged'] = $this->get_pagenum();
|
213 |
+
|
214 |
+
if ( isset( $args['context'] ) && 0 === strpos( $args['context'], 'group-' ) ) {
|
215 |
+
$args['connector'] = str_replace( 'group-', '', $args['context'] );
|
216 |
+
$args['context'] = '';
|
217 |
+
}
|
218 |
+
|
219 |
+
if ( ! isset( $args['records_per_page'] ) ) {
|
220 |
+
$args['records_per_page'] = $this->get_items_per_page( 'edit_stream_per_page', 20 );
|
221 |
+
}
|
222 |
+
|
223 |
+
$args['aggregations'] = array( 'author', 'connector', 'context', 'action' );
|
224 |
+
|
225 |
+
$items = wp_stream_query( $args );
|
226 |
+
|
227 |
+
return $items;
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Get last query found rows
|
232 |
+
*
|
233 |
+
* @return integer
|
234 |
+
*/
|
235 |
+
function get_total_found_rows() {
|
236 |
+
return WP_Stream::$db->get_found_rows();
|
237 |
+
}
|
238 |
+
|
239 |
+
function column_default( $item, $column_name ) {
|
240 |
+
switch ( $column_name ) {
|
241 |
+
case 'date' :
|
242 |
+
$created = date( 'Y-m-d H:i:s', strtotime( $item->created ) );
|
243 |
+
$date_string = sprintf(
|
244 |
+
'<time datetime="%s" class="relative-time record-created">%s</time>',
|
245 |
+
wp_stream_get_iso_8601_extended_date( strtotime( $item->created ) ),
|
246 |
+
get_date_from_gmt( $created, 'Y/m/d' )
|
247 |
+
);
|
248 |
+
$out = $this->column_link( $date_string, 'date', get_date_from_gmt( $created, 'Y/m/d' ) );
|
249 |
+
$out .= '<br />';
|
250 |
+
$out .= get_date_from_gmt( $created, 'h:i:s A' );
|
251 |
+
break;
|
252 |
+
|
253 |
+
case 'summary' :
|
254 |
+
$out = $item->summary;
|
255 |
+
$object_title = wp_stream_get_object_title( $item );
|
256 |
+
$view_all_text = $object_title ? sprintf( __( 'View all activity for "%s"', 'stream' ), esc_attr( $object_title ) ) : __( 'View all activity for this object', 'stream' );
|
257 |
+
if ( $item->object_id ) {
|
258 |
+
$out .= $this->column_link(
|
259 |
+
'<span class="dashicons dashicons-search stream-filter-object-id"></span>',
|
260 |
+
array(
|
261 |
+
'object_id' => $item->object_id,
|
262 |
+
'context' => $item->context,
|
263 |
+
),
|
264 |
+
null,
|
265 |
+
esc_attr( $view_all_text )
|
266 |
+
);
|
267 |
+
}
|
268 |
+
$out .= $this->get_action_links( $item );
|
269 |
+
break;
|
270 |
+
|
271 |
+
case 'author' :
|
272 |
+
$author = new WP_Stream_Author( (int) $item->author, (array) $item->author_meta );
|
273 |
+
|
274 |
+
$out = sprintf(
|
275 |
+
'<a href="%s">%s <span>%s</span></a>%s%s%s',
|
276 |
+
$author->get_records_page_url(),
|
277 |
+
$author->get_avatar_img( 80 ),
|
278 |
+
$author->get_display_name(),
|
279 |
+
$author->is_deleted() ? sprintf( '<br /><small class="deleted">%s</small>', esc_html__( 'Deleted User', 'stream' ) ) : '',
|
280 |
+
$author->get_role() ? sprintf( '<br /><small>%s</small>', $author->get_role() ) : '',
|
281 |
+
$author->get_agent() ? sprintf( '<br /><small>%s</small>', WP_Stream_Author::get_agent_label( $author->get_agent() ) ) : ''
|
282 |
+
);
|
283 |
+
break;
|
284 |
+
|
285 |
+
case 'context':
|
286 |
+
$connector_title = $this->get_term_title( $item->{'connector'}, 'connector' );
|
287 |
+
$context_title = $this->get_term_title( $item->{'context'}, 'context' );
|
288 |
+
|
289 |
+
$out = $this->column_link( $connector_title, 'connector', $item->{'connector'} );
|
290 |
+
$out .= '<br />↳ ';
|
291 |
+
$out .= $this->column_link(
|
292 |
+
$context_title,
|
293 |
+
array(
|
294 |
+
'connector' => $item->{'connector'},
|
295 |
+
'context' => $item->{'context'},
|
296 |
+
)
|
297 |
+
);
|
298 |
+
break;
|
299 |
+
|
300 |
+
case 'action':
|
301 |
+
$out = $this->column_link( $this->get_term_title( $item->{$column_name}, $column_name ), $column_name, $item->{$column_name} );
|
302 |
+
break;
|
303 |
+
|
304 |
+
case 'ip' :
|
305 |
+
$out = $this->column_link( $item->{$column_name}, 'ip', $item->{$column_name} );
|
306 |
+
break;
|
307 |
+
|
308 |
+
default :
|
309 |
+
/**
|
310 |
+
* Registers new Columns to be inserted into the table. The cell contents of this column is set
|
311 |
+
* below with 'wp_stream_inster_column_default-'
|
312 |
+
*
|
313 |
+
* @since 1.0.0
|
314 |
+
*
|
315 |
+
* @return array
|
316 |
+
*/
|
317 |
+
$inserted_columns = apply_filters( 'wp_stream_register_column_defaults', $new_columns = array() );
|
318 |
+
|
319 |
+
if ( ! empty( $inserted_columns ) && is_array( $inserted_columns ) ) {
|
320 |
+
foreach ( $inserted_columns as $column_title ) {
|
321 |
+
/**
|
322 |
+
* If column title inserted via wp_stream_register_column_defaults ($column_title) exists
|
323 |
+
* among columns registered with get_columns ($column_name) and there is an action associated
|
324 |
+
* with this column, do the action
|
325 |
+
*
|
326 |
+
* Also, note that the action name must include the $column_title registered
|
327 |
+
* with wp_stream_register_column_defaults
|
328 |
+
*/
|
329 |
+
if ( $column_title == $column_name && has_filter( "wp_stream_insert_column_default-{$column_title}" ) ) {
|
330 |
+
/**
|
331 |
+
* Allows for the addition of content under a specified column.
|
332 |
+
*
|
333 |
+
* @since 2.0.4
|
334 |
+
*
|
335 |
+
* @param object $item Contents of the row
|
336 |
+
*
|
337 |
+
* @return string
|
338 |
+
*/
|
339 |
+
$out = apply_filters( "wp_stream_insert_column_default-{$column_title}", $column_name, $item );
|
340 |
+
} else {
|
341 |
+
$out = $column_name;
|
342 |
+
}
|
343 |
+
}
|
344 |
+
} else {
|
345 |
+
$out = $column_name;
|
346 |
+
}
|
347 |
+
}
|
348 |
+
|
349 |
+
echo $out; // xss ok
|
350 |
+
}
|
351 |
+
|
352 |
+
public static function get_action_links( $record ) {
|
353 |
+
$out = '';
|
354 |
+
|
355 |
+
/**
|
356 |
+
* Filter allows modification of action links for a specific connector
|
357 |
+
*
|
358 |
+
* @since 0.5.0
|
359 |
+
*
|
360 |
+
* @param string connector
|
361 |
+
* @param array array of action links for this connector
|
362 |
+
* @param obj record
|
363 |
+
*
|
364 |
+
* @return arrray action links for this connector
|
365 |
+
*/
|
366 |
+
$action_links = apply_filters( 'wp_stream_action_links_' . $record->connector, array(), $record );
|
367 |
+
|
368 |
+
/**
|
369 |
+
* Filter allows addition of custom links for a specific connector
|
370 |
+
*
|
371 |
+
* @since 1.0.0
|
372 |
+
*
|
373 |
+
* @param string connector
|
374 |
+
* @param array array of custom links for this connector
|
375 |
+
* @param obj record
|
376 |
+
*
|
377 |
+
* @return arrray custom links for this connector
|
378 |
+
*/
|
379 |
+
$custom_links = apply_filters( 'wp_stream_custom_action_links_' . $record->connector, array(), $record );
|
380 |
+
|
381 |
+
if ( $action_links || $custom_links ) {
|
382 |
+
$out .= '<div class="row-actions">';
|
383 |
+
}
|
384 |
+
|
385 |
+
$links = array();
|
386 |
+
if ( $action_links && is_array( $action_links ) ) {
|
387 |
+
foreach ( $action_links as $al_title => $al_href ) {
|
388 |
+
$links[] = sprintf(
|
389 |
+
'<span><a href="%s" class="action-link">%s</a></span>',
|
390 |
+
$al_href,
|
391 |
+
$al_title
|
392 |
+
);
|
393 |
+
}
|
394 |
+
}
|
395 |
+
|
396 |
+
if ( $custom_links && is_array( $custom_links ) ) {
|
397 |
+
foreach ( $custom_links as $key => $link ) {
|
398 |
+
$links[] = $link;
|
399 |
+
}
|
400 |
+
}
|
401 |
+
|
402 |
+
$out .= implode( ' | ', $links );
|
403 |
+
|
404 |
+
if ( $action_links || $custom_links ) {
|
405 |
+
$out .= '</div>';
|
406 |
+
}
|
407 |
+
|
408 |
+
return $out;
|
409 |
+
}
|
410 |
+
|
411 |
+
function column_link( $display, $key, $value = null, $title = null ) {
|
412 |
+
$url = add_query_arg(
|
413 |
+
array(
|
414 |
+
'page' => WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
415 |
+
),
|
416 |
+
self_admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE )
|
417 |
+
);
|
418 |
+
|
419 |
+
$args = ! is_array( $key ) ? array( $key => $value ) : $key;
|
420 |
+
|
421 |
+
foreach ( $args as $k => $v ) {
|
422 |
+
$url = add_query_arg( $k, $v, $url );
|
423 |
+
}
|
424 |
+
|
425 |
+
return sprintf(
|
426 |
+
'<a href="%s" title="%s">%s</a>',
|
427 |
+
esc_url( $url ),
|
428 |
+
esc_attr( $title ),
|
429 |
+
$display
|
430 |
+
);
|
431 |
+
}
|
432 |
+
|
433 |
+
public function get_term_title( $term, $type ) {
|
434 |
+
if ( isset( WP_Stream_Connectors::$term_labels[ "stream_$type" ][ $term ] ) ) {
|
435 |
+
return WP_Stream_Connectors::$term_labels[ "stream_$type" ][ $term ];
|
436 |
+
} else {
|
437 |
+
return $term;
|
438 |
+
}
|
439 |
+
}
|
440 |
+
|
441 |
+
/**
|
442 |
+
* Assembles records for display in search filters
|
443 |
+
*
|
444 |
+
* Gathers list of all authors/connectors, then compares it to
|
445 |
+
* results of existing records. All items that do not exist in records
|
446 |
+
* get assigned a disabled value of "true".
|
447 |
+
*
|
448 |
+
* @uses wp_stream_existing_records (see class-wp-stream-query.php)
|
449 |
+
* @since 1.0.4
|
450 |
+
*
|
451 |
+
* @param string Column requested
|
452 |
+
* @param string Table to be queried
|
453 |
+
*
|
454 |
+
* @return array options to be displayed in search filters
|
455 |
+
*/
|
456 |
+
function assemble_records( $column ) {
|
457 |
+
$setting_key = self::get_column_excluded_setting_key( $column );
|
458 |
+
|
459 |
+
// @todo eliminate special condition for authors, especially using a WP_User object as the value; should use string or stringifiable object
|
460 |
+
if ( 'author' === $column ) {
|
461 |
+
$all_records = array();
|
462 |
+
|
463 |
+
// If the number of users exceeds the max authors constant value then return an empty array and use AJAX instead
|
464 |
+
$user_count = count_users();
|
465 |
+
$total_users = $user_count['total_users'];
|
466 |
+
|
467 |
+
if ( $total_users > WP_Stream_Admin::PRELOAD_AUTHORS_MAX ) {
|
468 |
+
return array();
|
469 |
+
}
|
470 |
+
|
471 |
+
$authors = array_map(
|
472 |
+
function ( $user_id ) {
|
473 |
+
return new WP_Stream_Author( $user_id );
|
474 |
+
},
|
475 |
+
get_users( array( 'fields' => 'ID' ) )
|
476 |
+
);
|
477 |
+
|
478 |
+
$authors[] = new WP_Stream_Author( 0, array( 'is_wp_cli' => true ) );
|
479 |
+
|
480 |
+
foreach ( $authors as $author ) {
|
481 |
+
$all_records[ $author->id ] = $author->get_display_name();
|
482 |
+
}
|
483 |
+
} else {
|
484 |
+
$prefixed_column = sprintf( 'stream_%s', $column );
|
485 |
+
$all_records = WP_Stream_Connectors::$term_labels[ $prefixed_column ];
|
486 |
+
}
|
487 |
+
|
488 |
+
$query_meta = WP_Stream::$db->get_query_meta();
|
489 |
+
|
490 |
+
$values = array();
|
491 |
+
$existing_records = array();
|
492 |
+
|
493 |
+
if ( isset( $query_meta->aggregations->$column->buckets ) ) {
|
494 |
+
foreach ( $query_meta->aggregations->$column->buckets as $field ) {
|
495 |
+
$values[ $field->key ] = $field->key;
|
496 |
+
}
|
497 |
+
|
498 |
+
if ( ! empty( $values ) ) {
|
499 |
+
$existing_records = array_combine( $values, $values );
|
500 |
+
}
|
501 |
+
}
|
502 |
+
|
503 |
+
$active_records = array();
|
504 |
+
$disabled_records = array();
|
505 |
+
|
506 |
+
foreach ( $all_records as $record => $label ) {
|
507 |
+
if ( array_key_exists( $record, $existing_records ) ) {
|
508 |
+
$active_records[ $record ] = array( 'label' => $label, 'disabled' => '' );
|
509 |
+
} else {
|
510 |
+
$disabled_records[ $record ] = array( 'label' => $label, 'disabled' => 'disabled="disabled"' );
|
511 |
+
}
|
512 |
+
}
|
513 |
+
|
514 |
+
// Remove WP-CLI pseudo user if no records with user=0 exist
|
515 |
+
if ( isset( $disabled_records[0] ) ) {
|
516 |
+
unset( $disabled_records[0] );
|
517 |
+
}
|
518 |
+
|
519 |
+
$sort = function ( $a, $b ) use ( $column ) {
|
520 |
+
$label_a = (string) $a['label'];
|
521 |
+
$label_b = (string) $b['label'];
|
522 |
+
|
523 |
+
if ( $label_a === $label_b ) {
|
524 |
+
return 0;
|
525 |
+
}
|
526 |
+
|
527 |
+
return ( strtolower( $label_a ) < strtolower( $label_b ) ) ? -1 : 1;
|
528 |
+
};
|
529 |
+
|
530 |
+
uasort( $active_records, $sort );
|
531 |
+
uasort( $disabled_records, $sort );
|
532 |
+
|
533 |
+
// Not using array_merge() in order to preserve the array index for the Authors dropdown which uses the user_id as the key
|
534 |
+
$all_records = $active_records + $disabled_records;
|
535 |
+
|
536 |
+
return $all_records;
|
537 |
+
}
|
538 |
+
|
539 |
+
public function get_filters() {
|
540 |
+
$filters = array();
|
541 |
+
|
542 |
+
$date_interval = new WP_Stream_Date_Interval();
|
543 |
+
|
544 |
+
$filters['date'] = array(
|
545 |
+
'title' => __( 'dates', 'stream' ),
|
546 |
+
'items' => $date_interval->intervals,
|
547 |
+
);
|
548 |
+
|
549 |
+
$authors_records = WP_Stream_Admin::get_authors_record_meta(
|
550 |
+
$this->assemble_records( 'author' )
|
551 |
+
);
|
552 |
+
|
553 |
+
$filters['author'] = array(
|
554 |
+
'title' => __( 'authors', 'stream' ),
|
555 |
+
'items' => $authors_records,
|
556 |
+
'ajax' => count( $authors_records ) <= 0,
|
557 |
+
);
|
558 |
+
|
559 |
+
$filters['context'] = array(
|
560 |
+
'title' => __( 'contexts', 'stream' ),
|
561 |
+
'items' => $this->assemble_records( 'context' ),
|
562 |
+
);
|
563 |
+
|
564 |
+
$filters['action'] = array(
|
565 |
+
'title' => __( 'actions', 'stream' ),
|
566 |
+
'items' => $this->assemble_records( 'action' ),
|
567 |
+
);
|
568 |
+
|
569 |
+
/**
|
570 |
+
* Filter allows additional filters in the list table dropdowns
|
571 |
+
* Note the format of the filters above, with they key and array
|
572 |
+
* containing a title and array of items.
|
573 |
+
*
|
574 |
+
* @since 0.2.0
|
575 |
+
*
|
576 |
+
* @return array
|
577 |
+
*/
|
578 |
+
return apply_filters( 'wp_stream_list_table_filters', $filters );
|
579 |
+
}
|
580 |
+
|
581 |
+
function filters_form() {
|
582 |
+
$user_id = get_current_user_id();
|
583 |
+
$filters = $this->get_filters();
|
584 |
+
|
585 |
+
$filters_string = sprintf( '<input type="hidden" name="page" value="%s" />', 'wp_stream' );
|
586 |
+
$filters_string .= sprintf( '<span class="filter_info hidden">%s</span>', esc_html__( 'Show filter controls via the screen options tab above.', 'stream' ) );
|
587 |
+
|
588 |
+
foreach ( $filters as $name => $data ) {
|
589 |
+
if ( 'date' === $name ) {
|
590 |
+
$filters_string .= $this->filter_date( $data['items'] );
|
591 |
+
} else {
|
592 |
+
if ( 'context' === $name ) {
|
593 |
+
// Add Connectors as parents, and apply the Contexts as children
|
594 |
+
$connectors = $this->assemble_records( 'connector' );
|
595 |
+
|
596 |
+
foreach ( $connectors as $connector => $item ) {
|
597 |
+
$context_items[ $connector ]['label'] = $item['label'];
|
598 |
+
|
599 |
+
foreach ( $data['items'] as $context_value => $context_item ) {
|
600 |
+
if ( isset( WP_Stream_Connectors::$contexts[ $connector ] ) && array_key_exists( $context_value, WP_Stream_Connectors::$contexts[ $connector ] ) ) {
|
601 |
+
$context_items[ $connector ]['children'][ $context_value ] = $context_item;
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
if ( isset( $context_items[ $connector ]['children'] ) ) {
|
606 |
+
$labels = wp_list_pluck( $context_items[ $connector ]['children'], 'label' );
|
607 |
+
|
608 |
+
// Sort child items by label
|
609 |
+
array_multisort( $labels, SORT_ASC, $context_items[ $connector ]['children'] );
|
610 |
+
}
|
611 |
+
}
|
612 |
+
|
613 |
+
foreach ( $context_items as $context_value => $context_item ) {
|
614 |
+
if ( ! isset( $context_item['children'] ) || empty( $context_item['children'] ) ) {
|
615 |
+
unset( $context_items[ $context_value ] );
|
616 |
+
}
|
617 |
+
}
|
618 |
+
|
619 |
+
$data['items'] = $context_items;
|
620 |
+
|
621 |
+
$labels = wp_list_pluck( $data['items'], 'label' );
|
622 |
+
|
623 |
+
// Sort top-level items by label
|
624 |
+
array_multisort( $labels, SORT_ASC, $data['items'] );
|
625 |
+
|
626 |
+
// Ouput a hidden input to handle the connector value
|
627 |
+
$filters_string .= '<input type="hidden" name="connector" class="record-filter-connector" />';
|
628 |
+
}
|
629 |
+
$filters_string .= $this->filter_select( $name, $data['title'], $data['items'] );
|
630 |
+
}
|
631 |
+
}
|
632 |
+
|
633 |
+
$filters_string .= sprintf( '<input type="submit" id="record-query-submit" class="button" value="%s" />', __( 'Filter', 'stream' ) );
|
634 |
+
|
635 |
+
// Parse all query vars into an array
|
636 |
+
parse_str( $_SERVER['QUERY_STRING'], $query_vars );
|
637 |
+
|
638 |
+
// Ignore certain query vars and query vars that are empty
|
639 |
+
foreach ( $query_vars as $query_var => $value ) {
|
640 |
+
if ( '' === $value || 'page' === $query_var || 'paged' === $query_var ) {
|
641 |
+
unset( $query_vars[ $query_var ] );
|
642 |
+
}
|
643 |
+
}
|
644 |
+
|
645 |
+
$url = add_query_arg(
|
646 |
+
array(
|
647 |
+
'page' => WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
648 |
+
),
|
649 |
+
self_admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE )
|
650 |
+
);
|
651 |
+
|
652 |
+
// Display reset action if records are being filtered
|
653 |
+
if ( ! empty( $query_vars ) ) {
|
654 |
+
$filters_string .= sprintf( '<a href="%s" id="record-query-reset"><span class="dashicons dashicons-dismiss"></span> <span class="record-query-reset-text">%s</span></a>', esc_url( $url ), __( 'Reset filters', 'stream' ) );
|
655 |
+
}
|
656 |
+
|
657 |
+
return sprintf( '<div class="alignleft actions">%s</div>', $filters_string ); // xss ok
|
658 |
+
}
|
659 |
+
|
660 |
+
function filter_select( $name, $title, $items, $ajax = false ) {
|
661 |
+
if ( $ajax ) {
|
662 |
+
$out = sprintf(
|
663 |
+
'<input type="hidden" name="%s" class="chosen-select" value="%s" data-placeholder="%s" />',
|
664 |
+
esc_attr( $name ),
|
665 |
+
esc_attr( wp_stream_filter_input( INPUT_GET, $name ) ),
|
666 |
+
esc_attr( $title )
|
667 |
+
);
|
668 |
+
} else {
|
669 |
+
$options = array( '<option value=""></option>' );
|
670 |
+
$selected = wp_stream_filter_input( INPUT_GET, $name );
|
671 |
+
|
672 |
+
foreach ( $items as $key => $item ) {
|
673 |
+
$value = isset( $item['children'] ) ? 'group-' . $key : $key;
|
674 |
+
$option_args = array(
|
675 |
+
'value' => $value,
|
676 |
+
'selected' => selected( $value, $selected, false ),
|
677 |
+
'disabled' => isset( $item['disabled'] ) ? $item['disabled'] : null,
|
678 |
+
'icon' => isset( $item['icon'] ) ? $item['icon'] : null,
|
679 |
+
'group' => isset( $item['children'] ) ? $key : null,
|
680 |
+
'tooltip' => isset( $item['tooltip'] ) ? $item['tooltip'] : null,
|
681 |
+
'class' => isset( $item['children'] ) ? 'level-1' : null,
|
682 |
+
'label' => isset( $item['label'] ) ? $item['label'] : null,
|
683 |
+
);
|
684 |
+
$options[] = $this->filter_option( $option_args );
|
685 |
+
|
686 |
+
if ( isset( $item['children'] ) ) {
|
687 |
+
foreach ( $item['children'] as $child_value => $child_item ) {
|
688 |
+
$option_args = array(
|
689 |
+
'value' => $child_value,
|
690 |
+
'selected' => selected( $child_value, $selected, false ),
|
691 |
+
'disabled' => isset( $child_item['disabled'] ) ? $child_item['disabled'] : null,
|
692 |
+
'icon' => isset( $child_item['icon'] ) ? $child_item['icon'] : null,
|
693 |
+
'group' => $key,
|
694 |
+
'tooltip' => isset( $child_item['tooltip'] ) ? $child_item['tooltip'] : null,
|
695 |
+
'class' => 'level-2',
|
696 |
+
'label' => isset( $child_item['label'] ) ? '- ' . $child_item['label'] : null,
|
697 |
+
);
|
698 |
+
$options[] = $this->filter_option( $option_args );
|
699 |
+
}
|
700 |
+
}
|
701 |
+
}
|
702 |
+
$out = sprintf(
|
703 |
+
'<select name="%s" class="chosen-select" data-placeholder="%s">%s</select>',
|
704 |
+
esc_attr( $name ),
|
705 |
+
sprintf( esc_attr__( 'Show all %s', 'stream' ), $title ),
|
706 |
+
implode( '', $options )
|
707 |
+
);
|
708 |
+
}
|
709 |
+
|
710 |
+
return $out;
|
711 |
+
}
|
712 |
+
|
713 |
+
function filter_option( $args ) {
|
714 |
+
$defaults = array(
|
715 |
+
'value' => null,
|
716 |
+
'selected' => null,
|
717 |
+
'disabled' => null,
|
718 |
+
'icon' => null,
|
719 |
+
'group' => null,
|
720 |
+
'tooltip' => null,
|
721 |
+
'class' => null,
|
722 |
+
'label' => null,
|
723 |
+
);
|
724 |
+
wp_parse_args( $args, $defaults );
|
725 |
+
|
726 |
+
return sprintf(
|
727 |
+
'<option value="%s" %s %s %s %s %s class="%s">%s</option>',
|
728 |
+
esc_attr( $args['value'] ),
|
729 |
+
$args['selected'],
|
730 |
+
$args['disabled'],
|
731 |
+
$args['icon'] ? sprintf( 'data-icon="%s"', esc_attr( $args['icon'] ) ) : null,
|
732 |
+
$args['group'] ? sprintf( 'data-group="%s"', esc_attr( $args['group'] ) ) : null,
|
733 |
+
$args['tooltip'] ? sprintf( 'title="%s"', esc_attr( $args['tooltip'] ) ) : null,
|
734 |
+
$args['class'] ? esc_attr( $args['class'] ) : null,
|
735 |
+
esc_html( $args['label'] )
|
736 |
+
);
|
737 |
+
}
|
738 |
+
|
739 |
+
function filter_search() {
|
740 |
+
$out = sprintf(
|
741 |
+
'<p class="search-box">
|
742 |
+
<label class="screen-reader-text" for="record-search-input">%1$s:</label>
|
743 |
+
<input type="search" id="record-search-input" name="search" value="%2$s" />
|
744 |
+
<input type="submit" name="" id="search-submit" class="button" value="%1$s" />
|
745 |
+
</p>',
|
746 |
+
esc_attr__( 'Search Records', 'stream' ),
|
747 |
+
isset( $_GET['search'] ) ? esc_attr( wp_unslash( $_GET['search'] ) ) : null
|
748 |
+
);
|
749 |
+
|
750 |
+
return $out;
|
751 |
+
}
|
752 |
+
|
753 |
+
function filter_date( $items ) {
|
754 |
+
wp_enqueue_style( 'jquery-ui' );
|
755 |
+
wp_enqueue_style( 'wp-stream-datepicker' );
|
756 |
+
wp_enqueue_script( 'jquery-ui-datepicker' );
|
757 |
+
|
758 |
+
$date_predefined = wp_stream_filter_input( INPUT_GET, 'date_predefined' );
|
759 |
+
$date_from = wp_stream_filter_input( INPUT_GET, 'date_from' );
|
760 |
+
$date_to = wp_stream_filter_input( INPUT_GET, 'date_to' );
|
761 |
+
|
762 |
+
ob_start();
|
763 |
+
?>
|
764 |
+
<div class="date-interval">
|
765 |
+
|
766 |
+
<select class="field-predefined hide-if-no-js" name="date_predefined" data-placeholder="<?php esc_attr_e( 'All Time', 'stream' ) ?>">
|
767 |
+
<option></option>
|
768 |
+
<option value="custom" <?php selected( 'custom' === $date_predefined ); ?>><?php esc_attr_e( 'Custom', 'stream' ) ?></option>
|
769 |
+
<?php
|
770 |
+
foreach ( $items as $key => $interval ) {
|
771 |
+
printf(
|
772 |
+
'<option value="%s" data-from="%s" data-to="%s" %s>%s</option>',
|
773 |
+
esc_attr( $key ),
|
774 |
+
esc_attr( $interval['start']->format( 'Y/m/d' ) ),
|
775 |
+
isset( $interval['end'] ) ? esc_attr( $interval['end']->format( 'Y/m/d' ) ) : '',
|
776 |
+
selected( $key === $date_predefined ),
|
777 |
+
esc_html( $interval['label'] )
|
778 |
+
); // xss ok
|
779 |
+
}
|
780 |
+
?>
|
781 |
+
</select>
|
782 |
+
|
783 |
+
<div class="date-inputs">
|
784 |
+
<div class="box">
|
785 |
+
<i class="date-remove dashicons"></i>
|
786 |
+
<input type="text" name="date_from" class="date-picker field-from" placeholder="<?php esc_attr_e( 'Start Date', 'stream' ) ?>" value="<?php echo esc_attr( $date_from ) ?>" />
|
787 |
+
</div>
|
788 |
+
<span class="connector dashicons"></span>
|
789 |
+
|
790 |
+
<div class="box">
|
791 |
+
<i class="date-remove dashicons"></i>
|
792 |
+
<input type="text" name="date_to" class="date-picker field-to" placeholder="<?php esc_attr_e( 'End Date', 'stream' ) ?>" value="<?php echo esc_attr( $date_to ) ?>" />
|
793 |
+
</div>
|
794 |
+
</div>
|
795 |
+
|
796 |
+
</div>
|
797 |
+
<?php
|
798 |
+
|
799 |
+
return ob_get_clean();
|
800 |
+
}
|
801 |
+
|
802 |
+
function display() {
|
803 |
+
$url = self_admin_url( WP_Stream_Admin::ADMIN_PARENT_PAGE );
|
804 |
+
|
805 |
+
echo '<form method="get" action="' . esc_url( $url ) . '" id="record-filter-form">';
|
806 |
+
echo $this->filter_search(); // xss ok
|
807 |
+
|
808 |
+
parent::display();
|
809 |
+
echo '</form>';
|
810 |
+
}
|
811 |
+
|
812 |
+
function display_tablenav( $which ) {
|
813 |
+
if ( 'top' === $which ) : ?>
|
814 |
+
<div class="tablenav <?php echo esc_attr( $which ); ?>">
|
815 |
+
<?php
|
816 |
+
$this->pagination( $which );
|
817 |
+
$this->extra_tablenav( $which );
|
818 |
+
?>
|
819 |
+
|
820 |
+
<br class="clear" />
|
821 |
+
</div>
|
822 |
+
<?php else : ?>
|
823 |
+
<div class="tablenav <?php echo esc_attr( $which ); ?>">
|
824 |
+
<?php
|
825 |
+
/**
|
826 |
+
* Fires after the list table is displayed.
|
827 |
+
*
|
828 |
+
* @since 1.0.0
|
829 |
+
*/
|
830 |
+
do_action( 'wp_stream_after_list_table' );
|
831 |
+
$this->pagination( $which );
|
832 |
+
$this->extra_tablenav( $which );
|
833 |
+
?>
|
834 |
+
|
835 |
+
<br class="clear" />
|
836 |
+
</div>
|
837 |
+
<?php
|
838 |
+
endif;
|
839 |
+
}
|
840 |
+
|
841 |
+
static function set_screen_option( $dummy, $option, $value ) {
|
842 |
+
if ( 'edit_stream_per_page' === $option ) {
|
843 |
+
return $value;
|
844 |
+
} else {
|
845 |
+
return $dummy;
|
846 |
+
}
|
847 |
+
}
|
848 |
+
|
849 |
+
static function set_live_update_option( $dummy, $option, $value ) {
|
850 |
+
if ( WP_Stream_Live_Update::USER_META_KEY === $option ) {
|
851 |
+
$value = $_POST[ WP_Stream_Live_Update::USER_META_KEY ];
|
852 |
+
return $value;
|
853 |
+
} else {
|
854 |
+
return $dummy;
|
855 |
+
}
|
856 |
+
}
|
857 |
+
|
858 |
+
public function screen_controls( $status, $args ) {
|
859 |
+
$user_id = get_current_user_id();
|
860 |
+
$option = get_user_meta( $user_id, WP_Stream_Live_Update::USER_META_KEY, true );
|
861 |
+
$heartbeat = wp_script_is( 'heartbeat', 'done' ) ? 'true' : 'false';
|
862 |
+
|
863 |
+
if ( 'on' === $option && 'false' === $heartbeat ) {
|
864 |
+
$option = 'off';
|
865 |
+
|
866 |
+
update_user_meta( $user_id, WP_Stream_Live_Update::USER_META_KEY, 'off' );
|
867 |
+
}
|
868 |
+
|
869 |
+
$nonce = wp_create_nonce( WP_Stream_Live_Update::USER_META_KEY . '_nonce' );
|
870 |
+
|
871 |
+
ob_start();
|
872 |
+
?>
|
873 |
+
<fieldset>
|
874 |
+
<h5><?php esc_html_e( 'Live updates', 'stream' ) ?></h5>
|
875 |
+
|
876 |
+
<div>
|
877 |
+
<input type="hidden" name="stream_live_update_nonce" id="stream_live_update_nonce" value="<?php echo esc_attr( $nonce ) ?>" />
|
878 |
+
</div>
|
879 |
+
<div>
|
880 |
+
<input type="hidden" name="enable_live_update_user" id="enable_live_update_user" value="<?php echo absint( $user_id ) ?>" />
|
881 |
+
</div>
|
882 |
+
<div class="metabox-prefs stream-live-update-checkbox">
|
883 |
+
<label for="enable_live_update">
|
884 |
+
<input type="checkbox" value="on" name="enable_live_update" id="enable_live_update" data-heartbeat="<?php echo esc_attr( $heartbeat ) ?>" <?php checked( $option, 'on' ) ?> />
|
885 |
+
<?php esc_html_e( 'Enabled', 'stream' ) ?><span class="spinner"></span>
|
886 |
+
</label>
|
887 |
+
</div>
|
888 |
+
</fieldset>
|
889 |
+
<?php
|
890 |
+
return ob_get_clean();
|
891 |
+
}
|
892 |
+
|
893 |
+
/**
|
894 |
+
* This function is use to map List table column name with excluded setting keys
|
895 |
+
*
|
896 |
+
* @param $column string list table column name
|
897 |
+
*
|
898 |
+
* @return string setting name for that column
|
899 |
+
*/
|
900 |
+
function get_column_excluded_setting_key( $column ) {
|
901 |
+
switch ( $column ) {
|
902 |
+
case 'connector':
|
903 |
+
$output = 'connectors';
|
904 |
+
break;
|
905 |
+
case 'context':
|
906 |
+
$output = 'contexts';
|
907 |
+
break;
|
908 |
+
case 'action':
|
909 |
+
$output = 'action';
|
910 |
+
break;
|
911 |
+
case 'ip':
|
912 |
+
$output = 'ip_addresses';
|
913 |
+
break;
|
914 |
+
case 'author':
|
915 |
+
$output = 'authors';
|
916 |
+
break;
|
917 |
+
default:
|
918 |
+
$output = false;
|
919 |
+
}
|
920 |
+
|
921 |
+
return $output;
|
922 |
+
}
|
923 |
+
}
|
classes/class-wp-stream-live-update.php
ADDED
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Live_Update {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* User meta key/identifier
|
7 |
+
*
|
8 |
+
* @const string
|
9 |
+
*/
|
10 |
+
const USER_META_KEY = 'stream_live_update_records';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* List table object instance
|
14 |
+
*
|
15 |
+
* @var object
|
16 |
+
*/
|
17 |
+
public static $list_table = null;
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Load live updates methods
|
21 |
+
*/
|
22 |
+
public static function load() {
|
23 |
+
// Heartbeat live update
|
24 |
+
add_filter( 'heartbeat_received', array( __CLASS__, 'heartbeat_received' ), 10, 2 );
|
25 |
+
|
26 |
+
// Enable/Disable live update per user
|
27 |
+
add_action( 'wp_ajax_stream_enable_live_update', array( __CLASS__, 'enable_live_update' ) );
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Ajax function to enable/disable live update
|
32 |
+
*
|
33 |
+
* @return string Ajax respsonse back in JSON format
|
34 |
+
*/
|
35 |
+
public static function enable_live_update() {
|
36 |
+
check_ajax_referer( self::USER_META_KEY . '_nonce', 'nonce' );
|
37 |
+
|
38 |
+
$input = array(
|
39 |
+
'checked' => FILTER_SANITIZE_STRING,
|
40 |
+
'user' => FILTER_SANITIZE_STRING,
|
41 |
+
'heartbeat' => FILTER_SANITIZE_STRING,
|
42 |
+
);
|
43 |
+
|
44 |
+
$input = filter_input_array( INPUT_POST, $input );
|
45 |
+
|
46 |
+
if ( false === $input ) {
|
47 |
+
wp_send_json_error( 'Error in live update checkbox' );
|
48 |
+
}
|
49 |
+
|
50 |
+
$checked = ( 'checked' === $input['checked'] ) ? 'on' : 'off';
|
51 |
+
|
52 |
+
$user = (int) $input['user'];
|
53 |
+
|
54 |
+
if ( 'false' === $input['heartbeat'] ) {
|
55 |
+
update_user_meta( $user, self::USER_META_KEY, 'off' );
|
56 |
+
|
57 |
+
wp_send_json_error( esc_html__( "Live updates could not be enabled because Heartbeat is not loaded.\n\nYour hosting provider or another plugin may have disabled it for performance reasons.", 'stream' ) );
|
58 |
+
|
59 |
+
return;
|
60 |
+
}
|
61 |
+
|
62 |
+
$success = update_user_meta( $user, self::USER_META_KEY, $checked );
|
63 |
+
|
64 |
+
if ( $success ) {
|
65 |
+
wp_send_json_success( ( 'on' === $checked ) ? 'Live Updates enabled' : 'Live Updates disabled' );
|
66 |
+
} else {
|
67 |
+
wp_send_json_error( 'Live Updates checkbox error' );
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Sends updated actions to the list table view
|
73 |
+
*
|
74 |
+
* @todo Fix reliability issues with sidebar widgets
|
75 |
+
*
|
76 |
+
* @uses gather_updated_items
|
77 |
+
* @uses generate_row
|
78 |
+
*
|
79 |
+
* @param array Response to heartbeat
|
80 |
+
* @param array Response from heartbeat
|
81 |
+
*
|
82 |
+
* @return array Data sent to heartbeat
|
83 |
+
*/
|
84 |
+
public static function live_update( $response, $data ) {
|
85 |
+
if ( ! isset( $data['wp-stream-heartbeat-last-time'] ) ) {
|
86 |
+
return;
|
87 |
+
}
|
88 |
+
|
89 |
+
$last_time = $data['wp-stream-heartbeat-last-time'];
|
90 |
+
$query = $data['wp-stream-heartbeat-query'];
|
91 |
+
|
92 |
+
if ( empty( $query ) ) {
|
93 |
+
$query = array();
|
94 |
+
}
|
95 |
+
|
96 |
+
// Decode the query
|
97 |
+
$query = json_decode( wp_kses_stripslashes( $query ) );
|
98 |
+
|
99 |
+
$updated_items = self::gather_updated_items( $last_time, (array) $query );
|
100 |
+
|
101 |
+
if ( ! empty( $updated_items ) ) {
|
102 |
+
ob_start();
|
103 |
+
|
104 |
+
foreach ( $updated_items as $item ) {
|
105 |
+
self::$list_table->single_row( $item );
|
106 |
+
}
|
107 |
+
|
108 |
+
$send = ob_get_clean();
|
109 |
+
} else {
|
110 |
+
$send = '';
|
111 |
+
}
|
112 |
+
|
113 |
+
return $send;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Sends Updated Actions to the List Table View
|
118 |
+
*
|
119 |
+
* @param int Timestamp of last update
|
120 |
+
* @param array Query args
|
121 |
+
*
|
122 |
+
* @return array Array of recently updated items
|
123 |
+
*/
|
124 |
+
public static function gather_updated_items( $last_time, $args = array() ) {
|
125 |
+
if ( false === $last_time ) {
|
126 |
+
return '';
|
127 |
+
}
|
128 |
+
|
129 |
+
if ( empty( self::$list_table->items ) ) {
|
130 |
+
return '';
|
131 |
+
}
|
132 |
+
|
133 |
+
$items = array();
|
134 |
+
|
135 |
+
foreach ( self::$list_table->items as $item ) {
|
136 |
+
if ( strtotime( $item->created ) > strtotime( $last_time ) ) {
|
137 |
+
$items[] = $item;
|
138 |
+
} else {
|
139 |
+
break;
|
140 |
+
}
|
141 |
+
}
|
142 |
+
|
143 |
+
return $items;
|
144 |
+
}
|
145 |
+
|
146 |
+
/**
|
147 |
+
* Handles live updates for both dashboard widget and Stream Post List
|
148 |
+
*
|
149 |
+
* @action heartbeat_recieved
|
150 |
+
*
|
151 |
+
* @param array Response to be sent to heartbeat tick
|
152 |
+
* @param array Data from heartbeat send
|
153 |
+
*
|
154 |
+
* @return array Data sent to heartbeat tick
|
155 |
+
*/
|
156 |
+
public static function heartbeat_received( $response, $data ) {
|
157 |
+
// Only fire when Stream is requesting a live update
|
158 |
+
if ( ! isset( $data['wp-stream-heartbeat'] ) ) {
|
159 |
+
return $response;
|
160 |
+
}
|
161 |
+
|
162 |
+
$option = get_option( 'dashboard_stream_activity_options' );
|
163 |
+
$enable_stream_update = ( 'off' !== get_user_meta( get_current_user_id(), self::USER_META_KEY, true ) );
|
164 |
+
$enable_dashboard_update = ( 'off' !== ( $option['live_update'] ) );
|
165 |
+
|
166 |
+
// Register list table
|
167 |
+
self::$list_table = new WP_Stream_List_Table( array( 'screen' => 'toplevel_page_' . WP_Stream_Admin::RECORDS_PAGE_SLUG ) );
|
168 |
+
self::$list_table->prepare_items();
|
169 |
+
|
170 |
+
$total_items = isset( self::$list_table->_pagination_args['total_items'] ) ? self::$list_table->_pagination_args['total_items'] : null;
|
171 |
+
$total_pages = isset( self::$list_table->_pagination_args['total_pages'] ) ? self::$list_table->_pagination_args['total_pages'] : null;
|
172 |
+
$per_page = isset( self::$list_table->_pagination_args['per_page'] ) ? self::$list_table->_pagination_args['per_page'] : null;
|
173 |
+
|
174 |
+
if ( isset( $data['wp-stream-heartbeat'] ) && isset( $total_items ) ) {
|
175 |
+
$response['total_items'] = $total_items;
|
176 |
+
$response['total_items_i18n'] = sprintf( _n( '1 item', '%s items', $total_items ), number_format_i18n( $total_items ) );
|
177 |
+
}
|
178 |
+
|
179 |
+
if ( isset( $data['wp-stream-heartbeat'] ) && 'live-update' === $data['wp-stream-heartbeat'] && $enable_stream_update ) {
|
180 |
+
|
181 |
+
if ( ! empty( $data['wp-stream-heartbeat'] ) ) {
|
182 |
+
if ( isset( $total_pages ) ) {
|
183 |
+
$response['total_pages'] = $total_pages;
|
184 |
+
$response['total_pages_i18n'] = number_format_i18n( $total_pages );
|
185 |
+
|
186 |
+
$query_args = json_decode( $data['wp-stream-heartbeat-query'], true );
|
187 |
+
$query_args['paged'] = $total_pages;
|
188 |
+
|
189 |
+
$response['last_page_link'] = add_query_arg( $query_args, admin_url( 'admin.php' ) );
|
190 |
+
} else {
|
191 |
+
$response['total_pages'] = 0;
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
$response['wp-stream-heartbeat'] = self::live_update( $response, $data );
|
196 |
+
|
197 |
+
} elseif ( isset( $data['wp-stream-heartbeat'] ) && 'dashboard-update' === $data['wp-stream-heartbeat'] && $enable_dashboard_update ) {
|
198 |
+
|
199 |
+
$per_page = isset( $option['records_per_page'] ) ? absint( $option['records_per_page'] ) : 5;
|
200 |
+
|
201 |
+
if ( isset( $total_items ) ) {
|
202 |
+
$total_pages = ceil( $total_items / $per_page );
|
203 |
+
$response['total_pages'] = $total_pages;
|
204 |
+
$response['total_pages_i18n'] = number_format_i18n( $total_pages );
|
205 |
+
|
206 |
+
$query_args['page'] = WP_Stream_Admin::RECORDS_PAGE_SLUG;
|
207 |
+
$query_args['paged'] = $total_pages;
|
208 |
+
|
209 |
+
$response['last_page_link'] = add_query_arg( $query_args, admin_url( 'admin.php' ) );
|
210 |
+
}
|
211 |
+
|
212 |
+
$response['per_page'] = $per_page;
|
213 |
+
$response['wp-stream-heartbeat'] = WP_Stream_Dashboard_Widget::live_update( $response, $data );
|
214 |
+
|
215 |
+
} else {
|
216 |
+
$response['log'] = 'fail';
|
217 |
+
}
|
218 |
+
|
219 |
+
return $response;
|
220 |
+
}
|
221 |
+
|
222 |
+
}
|
classes/class-wp-stream-log.php
ADDED
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Log {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Log handler
|
7 |
+
*
|
8 |
+
* @var \WP_Stream_Log
|
9 |
+
*/
|
10 |
+
public static $instance = null;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Load log handler class, filterable by extensions
|
14 |
+
*
|
15 |
+
* @return void
|
16 |
+
*/
|
17 |
+
public static function load() {
|
18 |
+
/**
|
19 |
+
* Filter allows developers to change log handler class
|
20 |
+
*
|
21 |
+
* @since 0.2.0
|
22 |
+
*
|
23 |
+
* @return string Class name to use for log handling
|
24 |
+
*/
|
25 |
+
$log_handler = apply_filters( 'wp_stream_log_handler', __CLASS__ );
|
26 |
+
|
27 |
+
self::$instance = new $log_handler;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Return active instance of this class
|
32 |
+
*
|
33 |
+
* @return WP_Stream_Log
|
34 |
+
*/
|
35 |
+
public static function get_instance() {
|
36 |
+
if ( ! self::$instance ) {
|
37 |
+
$class = __CLASS__;
|
38 |
+
self::$instance = new $class;
|
39 |
+
}
|
40 |
+
|
41 |
+
return self::$instance;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Log handler
|
46 |
+
*
|
47 |
+
* @param $connector
|
48 |
+
* @param string $message sprintf-ready error message string
|
49 |
+
* @param array $args sprintf (and extra) arguments to use
|
50 |
+
* @param int $object_id Target object id
|
51 |
+
* @param string $context Context of the event
|
52 |
+
* @param string $action Action of the event
|
53 |
+
* @param int $user_id User responsible for the event
|
54 |
+
*
|
55 |
+
* @return mixed True if updated, otherwise false|WP_Error
|
56 |
+
*/
|
57 |
+
public function log( $connector, $message, $args, $object_id, $context, $action, $user_id = null ) {
|
58 |
+
global $wpdb;
|
59 |
+
|
60 |
+
if ( is_null( $user_id ) ) {
|
61 |
+
$user_id = get_current_user_id();
|
62 |
+
}
|
63 |
+
|
64 |
+
if ( is_null( $object_id ) ) {
|
65 |
+
$object_id = 0;
|
66 |
+
}
|
67 |
+
|
68 |
+
$wp_cron_tracking = isset( WP_Stream_Settings::$options['advanced_wp_cron_tracking'] ) ? WP_Stream_Settings::$options['advanced_wp_cron_tracking'] : false;
|
69 |
+
$author = new WP_Stream_Author( $user_id );
|
70 |
+
$agent = $author->get_current_agent();
|
71 |
+
|
72 |
+
// WP cron tracking requires opt-in
|
73 |
+
if ( ! $wp_cron_tracking && 'wp_cron' === $agent ) {
|
74 |
+
return false;
|
75 |
+
}
|
76 |
+
|
77 |
+
$user = new WP_User( $user_id );
|
78 |
+
$roles = get_option( $wpdb->get_blog_prefix() . 'user_roles' );
|
79 |
+
$visibility = 'publish';
|
80 |
+
|
81 |
+
if ( self::is_record_excluded( $connector, $context, $action, $user ) ) {
|
82 |
+
$visibility = 'private';
|
83 |
+
}
|
84 |
+
|
85 |
+
$author_meta = array(
|
86 |
+
'user_email' => (string) ! empty( $user->user_email ) ? $user->user_email : '',
|
87 |
+
'display_name' => (string) $author->get_display_name(),
|
88 |
+
'user_login' => (string) ! empty( $user->user_login ) ? $user->user_login : '',
|
89 |
+
'user_role_label' => (string) $author->get_role(),
|
90 |
+
'agent' => (string) $agent,
|
91 |
+
);
|
92 |
+
|
93 |
+
if ( 'wp_cli' === $agent && function_exists( 'posix_getuid' ) ) {
|
94 |
+
$uid = posix_getuid();
|
95 |
+
$user_info = posix_getpwuid( $uid );
|
96 |
+
|
97 |
+
$author_meta['system_user_id'] = (int) $uid;
|
98 |
+
$author_meta['system_user_name'] = (string) $user_info['name'];
|
99 |
+
}
|
100 |
+
|
101 |
+
// Prevent any meta with null values from being logged
|
102 |
+
$stream_meta = array_filter(
|
103 |
+
$args,
|
104 |
+
function ( $var ) {
|
105 |
+
return ! is_null( $var );
|
106 |
+
}
|
107 |
+
);
|
108 |
+
|
109 |
+
// All meta must be strings, so we will serialize any array meta values
|
110 |
+
array_walk(
|
111 |
+
$stream_meta,
|
112 |
+
function( &$v ) {
|
113 |
+
$v = (string) maybe_serialize( $v );
|
114 |
+
}
|
115 |
+
);
|
116 |
+
|
117 |
+
// Get the current time in milliseconds
|
118 |
+
$iso_8601_extended_date = wp_stream_get_iso_8601_extended_date();
|
119 |
+
|
120 |
+
$recordarr = array(
|
121 |
+
'object_id' => (int) $object_id,
|
122 |
+
'site_id' => (int) is_multisite() ? get_current_site()->id : 1,
|
123 |
+
'blog_id' => (int) apply_filters( 'wp_stream_blog_id_logged', get_current_blog_id() ),
|
124 |
+
'author' => (int) $user_id,
|
125 |
+
'author_role' => (string) ! empty( $user->roles ) ? $user->roles[0] : '',
|
126 |
+
'author_meta' => (array) $author_meta,
|
127 |
+
'created' => (string) $iso_8601_extended_date,
|
128 |
+
'visibility' => (string) $visibility,
|
129 |
+
'type' => 'stream',
|
130 |
+
'summary' => (string) vsprintf( $message, $args ),
|
131 |
+
'connector' => (string) $connector,
|
132 |
+
'context' => (string) $context,
|
133 |
+
'action' => (string) $action,
|
134 |
+
'stream_meta' => (array) $stream_meta,
|
135 |
+
'ip' => (string) wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP ),
|
136 |
+
);
|
137 |
+
|
138 |
+
$result = WP_Stream::$db->store( array( $recordarr ) );
|
139 |
+
|
140 |
+
self::debug_backtrace( $recordarr );
|
141 |
+
|
142 |
+
return $result;
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* This function is use to check whether or not a record should be excluded from the log
|
147 |
+
*
|
148 |
+
* @param $connector string name of the connector being logged
|
149 |
+
* @param $context string name of the context being logged
|
150 |
+
* @param $action string name of the action being logged
|
151 |
+
* @param $user_id int id of the user being logged
|
152 |
+
* @param $ip string ip address being logged
|
153 |
+
* @return bool
|
154 |
+
*/
|
155 |
+
public function is_record_excluded( $connector, $context, $action, $user = null, $ip = null ) {
|
156 |
+
if ( is_null( $user ) ) {
|
157 |
+
$user = wp_get_current_user();
|
158 |
+
}
|
159 |
+
|
160 |
+
if ( is_null( $ip ) ) {
|
161 |
+
$ip = wp_stream_filter_input( INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP );
|
162 |
+
} else {
|
163 |
+
$ip = wp_stream_filter_var( $ip, FILTER_VALIDATE_IP );
|
164 |
+
}
|
165 |
+
|
166 |
+
$user_role = isset( $user->roles[0] ) ? $user->roles[0] : null;
|
167 |
+
|
168 |
+
$record = array(
|
169 |
+
'connector' => $connector,
|
170 |
+
'context' => $context,
|
171 |
+
'action' => $action,
|
172 |
+
'author' => $user->ID,
|
173 |
+
'role' => $user_role,
|
174 |
+
'ip_address' => $ip,
|
175 |
+
);
|
176 |
+
|
177 |
+
$exclude_settings = isset( WP_Stream_Settings::$options['exclude_rules'] ) ? WP_Stream_Settings::$options['exclude_rules'] : array();
|
178 |
+
|
179 |
+
if ( isset( $exclude_settings['exclude_row'] ) && ! empty( $exclude_settings['exclude_row'] ) ) {
|
180 |
+
foreach ( $exclude_settings['exclude_row'] as $key => $value ) {
|
181 |
+
// Prepare values
|
182 |
+
$author_or_role = isset( $exclude_settings['author_or_role'][ $key ] ) ? $exclude_settings['author_or_role'][ $key ] : '';
|
183 |
+
$connector = isset( $exclude_settings['connector'][ $key ] ) ? $exclude_settings['connector'][ $key ] : '';
|
184 |
+
$context = isset( $exclude_settings['context'][ $key ] ) ? $exclude_settings['context'][ $key ] : '';
|
185 |
+
$action = isset( $exclude_settings['action'][ $key ] ) ? $exclude_settings['action'][ $key ] : '';
|
186 |
+
$ip_address = isset( $exclude_settings['ip_address'][ $key ] ) ? $exclude_settings['ip_address'][ $key ] : '';
|
187 |
+
|
188 |
+
$exclude = array(
|
189 |
+
'connector' => ! empty( $connector ) ? $connector : null,
|
190 |
+
'context' => ! empty( $context ) ? $context : null,
|
191 |
+
'action' => ! empty( $action ) ? $action : null,
|
192 |
+
'ip_address' => ! empty( $ip_address ) ? $ip_address : null,
|
193 |
+
'author' => is_numeric( $author_or_role ) ? absint( $author_or_role ) : null,
|
194 |
+
'role' => ( ! empty( $author_or_role ) && ! is_numeric( $author_or_role ) ) ? $author_or_role : null,
|
195 |
+
);
|
196 |
+
|
197 |
+
$exclude_rules = array_filter( $exclude, 'strlen' );
|
198 |
+
|
199 |
+
if ( ! empty( $exclude_rules ) ) {
|
200 |
+
$excluded = true;
|
201 |
+
|
202 |
+
foreach ( $exclude_rules as $exclude_key => $exclude_value ) {
|
203 |
+
if ( $record[ $exclude_key ] !== $exclude_value ) {
|
204 |
+
$excluded = false;
|
205 |
+
break;
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
if ( $excluded ) {
|
210 |
+
return true;
|
211 |
+
}
|
212 |
+
}
|
213 |
+
}
|
214 |
+
}
|
215 |
+
|
216 |
+
return false;
|
217 |
+
}
|
218 |
+
|
219 |
+
/**
|
220 |
+
* Send a full backtrace of calls to the PHP error log for debugging
|
221 |
+
*
|
222 |
+
* @param array $recordarr
|
223 |
+
*
|
224 |
+
* @return void
|
225 |
+
*/
|
226 |
+
public static function debug_backtrace( $recordarr ) {
|
227 |
+
/**
|
228 |
+
* Enable debug backtrace on records.
|
229 |
+
*
|
230 |
+
* This filter is for developer use only. When enabled, Stream will send
|
231 |
+
* a full debug backtrace of PHP calls for each record. Optionally, you may
|
232 |
+
* use the available $recordarr parameter to specify what types of records to
|
233 |
+
* create backtrace logs for.
|
234 |
+
*
|
235 |
+
* @since 2.0.2
|
236 |
+
*
|
237 |
+
* @param array $recordarr
|
238 |
+
*
|
239 |
+
* @return bool Set to FALSE by default (backtrace disabled)
|
240 |
+
*/
|
241 |
+
$enabled = apply_filters( 'wp_stream_debug_backtrace', false, $recordarr );
|
242 |
+
|
243 |
+
if ( ! $enabled ) {
|
244 |
+
return;
|
245 |
+
}
|
246 |
+
|
247 |
+
if ( version_compare( PHP_VERSION, '5.3.6', '<' ) ) {
|
248 |
+
error_log( 'WP Stream debug backtrace requires at least PHP 5.3.6' );
|
249 |
+
return;
|
250 |
+
}
|
251 |
+
|
252 |
+
// Record details
|
253 |
+
$summary = isset( $recordarr['summary'] ) ? $recordarr['summary'] : null;
|
254 |
+
$author = isset( $recordarr['author'] ) ? $recordarr['author'] : null;
|
255 |
+
$connector = isset( $recordarr['connector'] ) ? $recordarr['connector'] : null;
|
256 |
+
$context = isset( $recordarr['context'] ) ? $recordarr['context'] : null;
|
257 |
+
$action = isset( $recordarr['action'] ) ? $recordarr['action'] : null;
|
258 |
+
|
259 |
+
// Stream meta
|
260 |
+
$stream_meta = isset( $recordarr['stream_meta'] ) ? $recordarr['stream_meta'] : null;
|
261 |
+
|
262 |
+
if ( $stream_meta ) {
|
263 |
+
array_walk( $stream_meta, function( &$value, $key ) {
|
264 |
+
$value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
|
265 |
+
});
|
266 |
+
|
267 |
+
$stream_meta = implode( ', ', $stream_meta );
|
268 |
+
}
|
269 |
+
|
270 |
+
// Author meta
|
271 |
+
$author_meta = isset( $recordarr['author_meta'] ) ? $recordarr['author_meta'] : null;
|
272 |
+
|
273 |
+
if ( $author_meta ) {
|
274 |
+
array_walk( $author_meta, function( &$value, $key ) {
|
275 |
+
$value = sprintf( '%s: %s', $key, ( '' === $value ) ? 'null' : $value );
|
276 |
+
});
|
277 |
+
|
278 |
+
$author_meta = implode( ', ', $author_meta );
|
279 |
+
}
|
280 |
+
|
281 |
+
// Debug backtrace
|
282 |
+
ob_start();
|
283 |
+
|
284 |
+
debug_print_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // Option to ignore args requires PHP 5.3.6
|
285 |
+
|
286 |
+
$backtrace = ob_get_clean();
|
287 |
+
$backtrace = array_values( array_filter( explode( "\n", $backtrace ) ) );
|
288 |
+
|
289 |
+
$output = sprintf(
|
290 |
+
"WP Stream Debug Backtrace\n\n Summary | %s\n Author | %s\n Connector | %s\n Context | %s\n Action | %s\nStream Meta | %s\nAuthor Meta | %s\n\n%s\n",
|
291 |
+
$summary,
|
292 |
+
$author,
|
293 |
+
$connector,
|
294 |
+
$context,
|
295 |
+
$action,
|
296 |
+
$stream_meta,
|
297 |
+
$author_meta,
|
298 |
+
implode( "\n", $backtrace )
|
299 |
+
);
|
300 |
+
|
301 |
+
error_log( $output );
|
302 |
+
}
|
303 |
+
|
304 |
+
}
|
classes/class-wp-stream-migrate.php
ADDED
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Migrate {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Migrate delay transient name/identifier used when user wants to be reminded to migrate later
|
7 |
+
*/
|
8 |
+
const MIGRATE_DELAY_TRANSIENT = 'wp_stream_migrate_delayed';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Hold the current site ID
|
12 |
+
*
|
13 |
+
* @var int
|
14 |
+
*/
|
15 |
+
public static $site_id = 1;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Hold the current blog ID
|
19 |
+
*
|
20 |
+
* @var int
|
21 |
+
*/
|
22 |
+
public static $blog_id = 1;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Hold the total number of legacy records found in the DB
|
26 |
+
*
|
27 |
+
* @var int
|
28 |
+
*/
|
29 |
+
public static $record_count = 0;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Limit payload chunks to a certain number of records
|
33 |
+
*
|
34 |
+
* @var int
|
35 |
+
*/
|
36 |
+
public static $limit = 0;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Hold unformatted records temporarily for deletion
|
40 |
+
*
|
41 |
+
* @var array
|
42 |
+
*/
|
43 |
+
private static $_records = array();
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Check that legacy data exists before doing anything
|
47 |
+
*
|
48 |
+
* @return void
|
49 |
+
*/
|
50 |
+
public static function load() {
|
51 |
+
// Exit early if there is no option holding the DB version
|
52 |
+
if ( false === get_site_option( 'wp_stream_db' ) ) {
|
53 |
+
return;
|
54 |
+
}
|
55 |
+
|
56 |
+
global $wpdb;
|
57 |
+
|
58 |
+
// If there are no legacy tables found, then attempt to clear all legacy data and exit early
|
59 |
+
if ( null === $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->base_prefix}stream'" ) ) {
|
60 |
+
self::drop_legacy_data( false );
|
61 |
+
return;
|
62 |
+
}
|
63 |
+
|
64 |
+
self::$site_id = is_multisite() ? get_current_site()->id : 1;
|
65 |
+
self::$blog_id = get_current_blog_id();
|
66 |
+
|
67 |
+
$since = WP_Stream::$api->get_plan_retention_max_date();
|
68 |
+
|
69 |
+
self::$record_count = $wpdb->get_var(
|
70 |
+
$wpdb->prepare( "
|
71 |
+
SELECT COUNT(*)
|
72 |
+
FROM {$wpdb->base_prefix}stream AS s, {$wpdb->base_prefix}stream_context AS sc
|
73 |
+
WHERE s.site_id = %d
|
74 |
+
AND s.blog_id = %d
|
75 |
+
AND s.type = 'stream'
|
76 |
+
AND s.created > %s
|
77 |
+
AND sc.record_id = s.ID
|
78 |
+
",
|
79 |
+
self::$site_id,
|
80 |
+
self::$blog_id,
|
81 |
+
$since
|
82 |
+
)
|
83 |
+
);
|
84 |
+
|
85 |
+
// If there are no legacy records for this site/blog, then attempt to clear all legacy data and exit early
|
86 |
+
if ( 0 === self::$record_count ) {
|
87 |
+
self::drop_legacy_data();
|
88 |
+
return;
|
89 |
+
}
|
90 |
+
|
91 |
+
self::$limit = apply_filters( 'wp_stream_migrate_chunk_size', 100 );
|
92 |
+
|
93 |
+
add_action( 'admin_notices', array( __CLASS__, 'migrate_notice' ), 9 );
|
94 |
+
|
95 |
+
add_action( 'wp_ajax_wp_stream_migrate_action', array( __CLASS__, 'process_migrate_action' ) );
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* Give the user options for how to handle their legacy Stream records
|
100 |
+
*
|
101 |
+
* @action admin_notices
|
102 |
+
* @return void
|
103 |
+
*/
|
104 |
+
public static function show_migrate_notice() {
|
105 |
+
if ( ! isset( $_GET['migrate_action'] ) && WP_Stream::is_connected() && WP_Stream_Admin::is_stream_screen() && ! empty( self::$record_count ) && false === get_transient( self::MIGRATE_DELAY_TRANSIENT ) ) {
|
106 |
+
return true;
|
107 |
+
}
|
108 |
+
|
109 |
+
return false;
|
110 |
+
}
|
111 |
+
|
112 |
+
/**
|
113 |
+
* Give the user options for how to handle their legacy Stream records
|
114 |
+
*
|
115 |
+
* @action admin_notices
|
116 |
+
* @return void
|
117 |
+
*/
|
118 |
+
public static function migrate_notice() {
|
119 |
+
if ( ! self::show_migrate_notice() ) {
|
120 |
+
return;
|
121 |
+
}
|
122 |
+
|
123 |
+
$notice = sprintf(
|
124 |
+
'<strong id="stream-migrate-title">%s</strong></p><p id="stream-migrate-message">%s</p><div id="stream-migrate-progress"><progress value="0" max="100"></progress> <strong>0%</strong> <em></em> <button id="stream-migrate-actions-close" class="button button-secondary">%s</button><div class="clear"></div></div><p id="stream-migrate-actions"><button id="stream-start-migrate" class="button button-primary">%s</button> <button id="stream-migrate-reminder" class="button button-secondary">%s</button> <a href="#" id="stream-delete-records" class="delete">%s</a>',
|
125 |
+
__( 'Migrate Stream Records', 'stream' ),
|
126 |
+
sprintf( __( 'We found %s existing Stream records that need to be migrated to your Stream account.', 'stream' ), number_format( self::$record_count ) ),
|
127 |
+
__( 'Close', 'stream' ),
|
128 |
+
__( 'Start Migration Now', 'stream' ),
|
129 |
+
__( 'Remind Me Later', 'stream' ),
|
130 |
+
__( 'Delete Existing Records', 'stream' )
|
131 |
+
);
|
132 |
+
|
133 |
+
WP_Stream::notice( $notice, false );
|
134 |
+
}
|
135 |
+
|
136 |
+
/**
|
137 |
+
* Ajax callback for processing migrate actions
|
138 |
+
*
|
139 |
+
* Break down the total number of records found into reasonably-sized chunks
|
140 |
+
* and send each of those chunks to the Stream API
|
141 |
+
*
|
142 |
+
* Drops the legacy Stream data from the DB once the API has consumed everything
|
143 |
+
*
|
144 |
+
* @action wp_ajax_wp_stream_migrate_action
|
145 |
+
* @return void
|
146 |
+
*/
|
147 |
+
public static function process_migrate_action() {
|
148 |
+
$action = wp_stream_filter_input( INPUT_POST, 'migrate_action' );
|
149 |
+
$nonce = wp_stream_filter_input( INPUT_POST, 'nonce' );
|
150 |
+
|
151 |
+
if ( ! wp_verify_nonce( $nonce, 'wp_stream_migrate-' . absint( get_current_blog_id() ) . absint( get_current_user_id() ) ) ) {
|
152 |
+
return;
|
153 |
+
}
|
154 |
+
|
155 |
+
set_time_limit( 0 ); // Just in case, this could take a while for some
|
156 |
+
|
157 |
+
if ( 'migrate' === $action ) {
|
158 |
+
self::migrate_notification_rules();
|
159 |
+
|
160 |
+
$records = self::get_records( self::$limit );
|
161 |
+
|
162 |
+
if ( ! $records ) {
|
163 |
+
// If all the records are gone, clean everything up
|
164 |
+
self::drop_legacy_data();
|
165 |
+
|
166 |
+
wp_send_json_success( __( 'Migration complete!', 'stream' ) );
|
167 |
+
}
|
168 |
+
|
169 |
+
$response = self::send_records( $records );
|
170 |
+
|
171 |
+
if ( true === $response ) {
|
172 |
+
// Delete the records that were just sent to the API successfully
|
173 |
+
self::delete_records( self::$_records );
|
174 |
+
|
175 |
+
wp_send_json_success( 'migrate' );
|
176 |
+
} else {
|
177 |
+
if ( isset( $response['body']['message'] ) && ! empty( $response['body']['message'] ) ) {
|
178 |
+
$body = json_decode( $response['body'], true );
|
179 |
+
$message = $body['message'];
|
180 |
+
} elseif ( isset( $response['response']['message'] ) && ! empty( $response['response']['message'] ) ) {
|
181 |
+
$message = $response['response']['message'];
|
182 |
+
} else {
|
183 |
+
$message = __( 'An unknown error occurred during migration.', 'stream' );
|
184 |
+
}
|
185 |
+
|
186 |
+
wp_send_json_error( sprintf( __( '%s Please try again later or contact support.', 'stream' ), esc_html( $message ) ) );
|
187 |
+
}
|
188 |
+
}
|
189 |
+
|
190 |
+
if ( 'delay' === $action ) {
|
191 |
+
set_transient( self::MIGRATE_DELAY_TRANSIENT, "Don't nag me, bro", HOUR_IN_SECONDS * 3 );
|
192 |
+
|
193 |
+
wp_send_json_success( __( "OK, we'll remind you again in a few hours.", 'stream' ) );
|
194 |
+
}
|
195 |
+
|
196 |
+
if ( 'delete' === $action ) {
|
197 |
+
$success_message = __( 'All existing records have been deleted from the database.', 'stream' );
|
198 |
+
|
199 |
+
if ( ! is_multisite() ) {
|
200 |
+
// If this is a single-site install, force delete everything
|
201 |
+
self::drop_legacy_data( true, true );
|
202 |
+
|
203 |
+
wp_send_json_success( $success_message );
|
204 |
+
} else {
|
205 |
+
// If multisite, only delete records for this site - this will take longer
|
206 |
+
$records = self::get_record_ids( self::$limit );
|
207 |
+
|
208 |
+
if ( ! $records ) {
|
209 |
+
// If all the records are gone, clean everything up
|
210 |
+
self::drop_legacy_data();
|
211 |
+
|
212 |
+
wp_send_json_success( $success_message );
|
213 |
+
} else {
|
214 |
+
self::delete_records( $records );
|
215 |
+
|
216 |
+
wp_send_json_success( 'delete' );
|
217 |
+
}
|
218 |
+
}
|
219 |
+
}
|
220 |
+
|
221 |
+
die();
|
222 |
+
}
|
223 |
+
|
224 |
+
/**
|
225 |
+
* Migrate notification_rule records to the new custom post type
|
226 |
+
*
|
227 |
+
* @return void
|
228 |
+
*/
|
229 |
+
private static function migrate_notification_rules() {
|
230 |
+
global $wpdb;
|
231 |
+
|
232 |
+
// Blog ID is set to 0 on single site installs
|
233 |
+
$blog_id = is_multisite() ? self::$blog_id : 0;
|
234 |
+
|
235 |
+
$rules = $wpdb->get_results(
|
236 |
+
$wpdb->prepare( "
|
237 |
+
SELECT *
|
238 |
+
FROM {$wpdb->base_prefix}stream
|
239 |
+
WHERE site_id = %d
|
240 |
+
AND blog_id = %d
|
241 |
+
AND type = 'notification_rule'
|
242 |
+
ORDER BY created DESC
|
243 |
+
",
|
244 |
+
self::$site_id,
|
245 |
+
$blog_id
|
246 |
+
),
|
247 |
+
ARRAY_A
|
248 |
+
);
|
249 |
+
|
250 |
+
if ( empty( $rules ) ) {
|
251 |
+
return;
|
252 |
+
}
|
253 |
+
|
254 |
+
foreach ( $rules as $rule => $data ) {
|
255 |
+
$rule_post_args = array();
|
256 |
+
$rule_post_meta = array();
|
257 |
+
|
258 |
+
// Set args for the new rule post
|
259 |
+
$rule_post_args['post_title'] = $rules[ $rule ]['summary'];
|
260 |
+
$rule_post_args['post_type'] = WP_Stream_Notifications_Post_Type::POSTTYPE;
|
261 |
+
$rule_post_args['post_status'] = ( 'active' === $rules[ $rule ]['visibility'] ) ? 'publish' : 'draft';
|
262 |
+
$rule_post_args['post_date'] = get_date_from_gmt( $rules[ $rule ]['created'] );
|
263 |
+
$rule_post_args['post_date_gmt'] = $rules[ $rule ]['created']; // May not work, known bug in WP, see workaround below
|
264 |
+
$rule_post_args['comment_status'] = 'closed';
|
265 |
+
$rule_post_args['ping_status'] = 'closed';
|
266 |
+
|
267 |
+
// Get rule meta
|
268 |
+
$stream_rule_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->base_prefix}stream_meta WHERE record_id = %d", $rules[ $rule ]['ID'] ), ARRAY_A );
|
269 |
+
|
270 |
+
// Prepare meta values for rule post meta
|
271 |
+
foreach ( $stream_rule_meta as $meta => $value ) {
|
272 |
+
$rule_post_meta[ $value['meta_key'] ] = maybe_unserialize( $value['meta_value'] );
|
273 |
+
}
|
274 |
+
|
275 |
+
// Get rule option, which is automatically unserialized
|
276 |
+
$stream_rule_option = get_option( 'stream_notifications_' . absint( $rules[ $rule ]['ID'] ) );
|
277 |
+
|
278 |
+
// Prepare option values for rule post meta
|
279 |
+
$rule_post_meta['triggers'] = isset( $stream_rule_option['triggers'] ) ? $stream_rule_option['triggers'] : array();
|
280 |
+
$rule_post_meta['groups'] = isset( $stream_rule_option['groups'] ) ? $stream_rule_option['groups'] : array();
|
281 |
+
$rule_post_meta['alerts'] = isset( $stream_rule_option['alerts'] ) ? $stream_rule_option['alerts'] : array();
|
282 |
+
|
283 |
+
// Insert rule as a new post
|
284 |
+
$post_id = wp_insert_post( $rule_post_args );
|
285 |
+
|
286 |
+
// Workaround to fix bug in wp_insert_post() not honoring the `post_date_gmt` arg
|
287 |
+
// See: https://core.trac.wordpress.org/ticket/15946
|
288 |
+
$wpdb->update( $wpdb->prefix . 'posts', array( 'post_date_gmt' => $rules[ $rule ]['created'] ), array( 'ID' => $post_id ), array( '%s' ), array( '%d' ) );
|
289 |
+
|
290 |
+
// Save the rule post meta
|
291 |
+
foreach ( $rule_post_meta as $key => $value ) {
|
292 |
+
update_post_meta( $post_id, $key, $value );
|
293 |
+
}
|
294 |
+
|
295 |
+
// Delete the old option
|
296 |
+
delete_option( 'stream_notifications_' . absint( $rules[ $rule ]['ID'] ) );
|
297 |
+
}
|
298 |
+
|
299 |
+
// No need for chunks since there likely won't be more than a few dozen rules
|
300 |
+
self::delete_records( $rules );
|
301 |
+
}
|
302 |
+
|
303 |
+
/**
|
304 |
+
* Send records to the API
|
305 |
+
*
|
306 |
+
* @param array $records
|
307 |
+
*
|
308 |
+
* @return mixed True on success, the full response array on failure.
|
309 |
+
*/
|
310 |
+
private static function send_records( $records ) {
|
311 |
+
if ( empty( $records ) || ! WP_Stream::$api->site_uuid ) {
|
312 |
+
return false;
|
313 |
+
}
|
314 |
+
|
315 |
+
$url = WP_Stream::$api->request_url( sprintf( '/sites/%s/records', urlencode( WP_Stream::$api->site_uuid ) ) );
|
316 |
+
$args = array(
|
317 |
+
'method' => 'POST',
|
318 |
+
'body' => json_encode( array( 'records' => $records ) ),
|
319 |
+
'sslverify' => true,
|
320 |
+
'blocking' => true,
|
321 |
+
'headers' => array(
|
322 |
+
'Content-Type' => 'application/json',
|
323 |
+
'Stream-Site-API-Key' => WP_Stream::$api->api_key,
|
324 |
+
),
|
325 |
+
);
|
326 |
+
|
327 |
+
$response = wp_remote_request( $url, $args );
|
328 |
+
|
329 |
+
// Loose comparison needed
|
330 |
+
if ( ! is_wp_error( $response ) && isset( $response['response']['code'] ) && 201 == $response['response']['code'] ) {
|
331 |
+
return true;
|
332 |
+
} else {
|
333 |
+
return (array) $response;
|
334 |
+
}
|
335 |
+
}
|
336 |
+
|
337 |
+
/**
|
338 |
+
* Get a chunk of records formatted for Stream API ingestion
|
339 |
+
*
|
340 |
+
* @param int $limit The number of rows to query
|
341 |
+
*
|
342 |
+
* @return mixed An array of record arrays, or FALSE if no records were found
|
343 |
+
*/
|
344 |
+
private static function get_records( $limit = null ) {
|
345 |
+
$limit = is_int( $limit ) ? $limit : self::$limit;
|
346 |
+
$since = WP_Stream::$api->get_plan_retention_max_date( 'Y-m-d 00:00:00' );
|
347 |
+
|
348 |
+
global $wpdb;
|
349 |
+
|
350 |
+
$records = $wpdb->get_results(
|
351 |
+
$wpdb->prepare( "
|
352 |
+
SELECT s.*, sc.connector, sc.context, sc.action
|
353 |
+
FROM {$wpdb->base_prefix}stream AS s, {$wpdb->base_prefix}stream_context AS sc
|
354 |
+
WHERE s.site_id = %d
|
355 |
+
AND s.blog_id = %d
|
356 |
+
AND s.type = 'stream'
|
357 |
+
AND s.created > %s
|
358 |
+
AND sc.record_id = s.ID
|
359 |
+
ORDER BY s.created DESC
|
360 |
+
LIMIT %d
|
361 |
+
",
|
362 |
+
self::$site_id,
|
363 |
+
self::$blog_id,
|
364 |
+
$since,
|
365 |
+
$limit
|
366 |
+
),
|
367 |
+
ARRAY_A
|
368 |
+
);
|
369 |
+
|
370 |
+
if ( empty( $records ) ) {
|
371 |
+
return false;
|
372 |
+
}
|
373 |
+
|
374 |
+
self::$_records = array();
|
375 |
+
|
376 |
+
foreach ( $records as $record => $data ) {
|
377 |
+
$stream_meta = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->base_prefix}stream_meta WHERE record_id = %d", $records[ $record ]['ID'] ), ARRAY_A );
|
378 |
+
$stream_meta_output = array();
|
379 |
+
$author_meta_output = array();
|
380 |
+
|
381 |
+
foreach ( $stream_meta as $key => $meta ) {
|
382 |
+
|
383 |
+
if ( 'author_meta' === $meta['meta_key'] && ! empty( $meta['meta_value'] ) ) {
|
384 |
+
$author_meta_output = maybe_unserialize( $meta['meta_value'] );
|
385 |
+
|
386 |
+
unset( $stream_meta[ $key ] );
|
387 |
+
|
388 |
+
continue;
|
389 |
+
}
|
390 |
+
|
391 |
+
// Unserialize meta first so we can then check for malformed serialized strings
|
392 |
+
$stream_meta_output[ $meta['meta_key'] ] = maybe_unserialize( $meta['meta_value'] );
|
393 |
+
|
394 |
+
// If any serialized data is still lingering in the meta value that means it's malformed and should be removed
|
395 |
+
if (
|
396 |
+
is_string( $stream_meta_output[ $meta['meta_key'] ] )
|
397 |
+
&&
|
398 |
+
1 === preg_match( '/(a|O) ?\x3a ?[0-9]+ ?\x3a ?\x7b/', $stream_meta_output[ $meta['meta_key'] ] )
|
399 |
+
) {
|
400 |
+
unset( $stream_meta_output[ $meta['meta_key'] ] );
|
401 |
+
|
402 |
+
continue;
|
403 |
+
}
|
404 |
+
|
405 |
+
// All meta must be strings, so serialize any array meta values again
|
406 |
+
$stream_meta_output[ $meta['meta_key'] ] = (string) maybe_serialize( $stream_meta_output[ $meta['meta_key'] ] );
|
407 |
+
}
|
408 |
+
|
409 |
+
// All author meta must be strings
|
410 |
+
array_walk(
|
411 |
+
$author_meta_output,
|
412 |
+
function( &$v ) {
|
413 |
+
$v = (string) $v;
|
414 |
+
}
|
415 |
+
);
|
416 |
+
|
417 |
+
$records[ $record ]['stream_meta'] = $stream_meta_output;
|
418 |
+
$records[ $record ]['author_meta'] = $author_meta_output;
|
419 |
+
|
420 |
+
self::$_records[] = $records[ $record ];
|
421 |
+
|
422 |
+
$records[ $record ]['created'] = wp_stream_get_iso_8601_extended_date( strtotime( $records[ $record ]['created'] ) );
|
423 |
+
|
424 |
+
unset( $records[ $record ]['ID'] );
|
425 |
+
unset( $records[ $record ]['parent'] );
|
426 |
+
|
427 |
+
// Ensure required fields always exist
|
428 |
+
$records[ $record ]['site_id'] = ! empty( $records[ $record ]['site_id'] ) ? $records[ $record ]['site_id'] : 1;
|
429 |
+
$records[ $record ]['blog_id'] = ! empty( $records[ $record ]['blog_id'] ) ? $records[ $record ]['blog_id'] : 1;
|
430 |
+
$records[ $record ]['object_id'] = ! empty( $records[ $record ]['object_id'] ) ? $records[ $record ]['object_id'] : 0;
|
431 |
+
$records[ $record ]['author'] = ! empty( $records[ $record ]['author'] ) ? $records[ $record ]['author'] : 0;
|
432 |
+
$records[ $record ]['author_role'] = ! empty( $records[ $record ]['author_role'] ) ? $records[ $record ]['author_role'] : '';
|
433 |
+
$records[ $record ]['ip'] = ! empty( $records[ $record ]['ip'] ) ? $records[ $record ]['ip'] : '';
|
434 |
+
}
|
435 |
+
|
436 |
+
return $records;
|
437 |
+
}
|
438 |
+
|
439 |
+
/**
|
440 |
+
* Get a chunk of record IDs
|
441 |
+
*
|
442 |
+
* @param int $limit The number of rows to query
|
443 |
+
*
|
444 |
+
* @return mixed An array of record IDs, or FALSE if no records were found
|
445 |
+
*/
|
446 |
+
private static function get_record_ids( $limit = null ) {
|
447 |
+
$limit = is_int( $limit ) ? $limit : self::$limit;
|
448 |
+
$since = WP_Stream::$api->get_plan_retention_max_date( 'Y-m-d 00:00:00' );
|
449 |
+
|
450 |
+
global $wpdb;
|
451 |
+
|
452 |
+
$records = $wpdb->get_col(
|
453 |
+
$wpdb->prepare( "
|
454 |
+
SELECT s.ID
|
455 |
+
FROM {$wpdb->base_prefix}stream AS s
|
456 |
+
WHERE s.site_id = %d
|
457 |
+
AND s.blog_id = %d
|
458 |
+
AND s.type = 'stream'
|
459 |
+
AND s.created > %s
|
460 |
+
ORDER BY s.created DESC
|
461 |
+
LIMIT %d
|
462 |
+
",
|
463 |
+
self::$site_id,
|
464 |
+
self::$blog_id,
|
465 |
+
$since,
|
466 |
+
$limit
|
467 |
+
)
|
468 |
+
);
|
469 |
+
|
470 |
+
if ( empty( $records ) ) {
|
471 |
+
return false;
|
472 |
+
}
|
473 |
+
|
474 |
+
return $records;
|
475 |
+
}
|
476 |
+
|
477 |
+
/**
|
478 |
+
* Drop the legacy Stream records from the database for the current site/blog
|
479 |
+
*
|
480 |
+
* @param array $records An array of record arrays.
|
481 |
+
*
|
482 |
+
* @return void
|
483 |
+
*/
|
484 |
+
private static function delete_records( $records ) {
|
485 |
+
if ( empty( $records ) ) {
|
486 |
+
return;
|
487 |
+
}
|
488 |
+
|
489 |
+
global $wpdb;
|
490 |
+
|
491 |
+
// Delete legacy rows from each Stream table for these records only
|
492 |
+
foreach ( $records as $record ) {
|
493 |
+
// Get the record ID from an array of records, or from an array of IDs
|
494 |
+
if ( isset( $record['ID'] ) ) {
|
495 |
+
$record_id = $record['ID'];
|
496 |
+
} elseif ( is_numeric( $record ) ) {
|
497 |
+
$record_id = $record;
|
498 |
+
} else {
|
499 |
+
$record_id = false;
|
500 |
+
}
|
501 |
+
|
502 |
+
if ( empty( $record_id ) ) {
|
503 |
+
continue;
|
504 |
+
}
|
505 |
+
|
506 |
+
$wpdb->delete( $wpdb->base_prefix . 'stream', array( 'ID' => $record_id ), array( '%d' ) );
|
507 |
+
$wpdb->delete( $wpdb->base_prefix . 'stream_context', array( 'record_id' => $record_id ), array( '%d' ) );
|
508 |
+
$wpdb->delete( $wpdb->base_prefix . 'stream_meta', array( 'record_id' => $record_id ), array( '%d' ) );
|
509 |
+
}
|
510 |
+
}
|
511 |
+
|
512 |
+
/**
|
513 |
+
* Drop the legacy Stream tables and options from the database
|
514 |
+
*
|
515 |
+
* @param bool $drop_tables If true, attempt to drop the legacy Stream tables
|
516 |
+
* @param bool $force If true, delete tables even if records still exist
|
517 |
+
*
|
518 |
+
* @return void
|
519 |
+
*/
|
520 |
+
private static function drop_legacy_data( $drop_tables = true, $force = false ) {
|
521 |
+
global $wpdb;
|
522 |
+
|
523 |
+
if ( $drop_tables ) {
|
524 |
+
if ( is_multisite() ) {
|
525 |
+
$stream_site_blog_pairs = $wpdb->get_results( "SELECT site_id, blog_id FROM {$wpdb->base_prefix}stream WHERE type = 'stream'", ARRAY_A );
|
526 |
+
$stream_site_blog_pairs = array_unique( array_map( 'self::implode_key_value', $stream_site_blog_pairs ) );
|
527 |
+
$wp_site_blog_pairs = $wpdb->get_results( "SELECT site_id, blog_id FROM {$wpdb->base_prefix}blogs", ARRAY_A );
|
528 |
+
$wp_site_blog_pairs = array_unique( array_map( 'self::implode_key_value', $wp_site_blog_pairs ) );
|
529 |
+
$records_exist = ( array_intersect( $stream_site_blog_pairs, $wp_site_blog_pairs ) ) ? true : false;
|
530 |
+
} else {
|
531 |
+
$records_exist = $wpdb->get_var( "SELECT * FROM `{$wpdb->prefix}stream` LIMIT 1" );
|
532 |
+
}
|
533 |
+
|
534 |
+
// If records exist for other sites/blogs then don't proceed, unless we're force deleting or those sites/blogs have been deleted
|
535 |
+
if ( $records_exist && ! $force ) {
|
536 |
+
return;
|
537 |
+
}
|
538 |
+
|
539 |
+
// Drop legacy tables
|
540 |
+
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->base_prefix}stream, {$wpdb->base_prefix}stream_context, {$wpdb->base_prefix}stream_meta" );
|
541 |
+
}
|
542 |
+
|
543 |
+
// Delete legacy multisite options
|
544 |
+
if ( is_multisite() ) {
|
545 |
+
$blogs = wp_get_sites();
|
546 |
+
|
547 |
+
foreach ( $blogs as $blog ) {
|
548 |
+
switch_to_blog( $blog['blog_id'] );
|
549 |
+
delete_option( plugin_basename( WP_STREAM_DIR ) . '_db' ); // Deprecated option key
|
550 |
+
delete_option( 'wp_stream_db' );
|
551 |
+
delete_option( 'wp_stream_license' );
|
552 |
+
delete_option( 'wp_stream_licensee' );
|
553 |
+
}
|
554 |
+
|
555 |
+
restore_current_blog();
|
556 |
+
}
|
557 |
+
|
558 |
+
// Delete legacy options
|
559 |
+
delete_site_option( plugin_basename( WP_STREAM_DIR ) . '_db' ); // Deprecated option key
|
560 |
+
delete_site_option( 'wp_stream_db' );
|
561 |
+
delete_site_option( 'wp_stream_license' );
|
562 |
+
delete_site_option( 'wp_stream_licensee' );
|
563 |
+
|
564 |
+
// Delete legacy transients
|
565 |
+
delete_transient( 'wp_stream_extensions_' );
|
566 |
+
|
567 |
+
// Delete legacy cron event hooks
|
568 |
+
wp_clear_scheduled_hook( 'stream_auto_purge' ); // Deprecated hook
|
569 |
+
wp_clear_scheduled_hook( 'wp_stream_auto_purge' );
|
570 |
+
}
|
571 |
+
|
572 |
+
/**
|
573 |
+
* Callback to impode key/value pairs from an associative array into a specially-formatted string
|
574 |
+
*
|
575 |
+
* @param array $array An associate array
|
576 |
+
*
|
577 |
+
* @return string $output
|
578 |
+
*/
|
579 |
+
public static function implode_key_value( $array ) {
|
580 |
+
$output = implode( ', ',
|
581 |
+
array_map(
|
582 |
+
function ( $v, $k ) {
|
583 |
+
return sprintf( '%s:%s', $k, $v );
|
584 |
+
},
|
585 |
+
$array,
|
586 |
+
array_keys( $array )
|
587 |
+
)
|
588 |
+
);
|
589 |
+
|
590 |
+
return $output;
|
591 |
+
}
|
592 |
+
|
593 |
+
}
|
classes/class-wp-stream-pointers.php
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Stream pointers. Based on WP core internal pointers.
|
4 |
+
*
|
5 |
+
* @since 1.4.4
|
6 |
+
*/
|
7 |
+
class WP_Stream_Pointers {
|
8 |
+
|
9 |
+
public static $pointers = array();
|
10 |
+
public static $caps = array();
|
11 |
+
|
12 |
+
public static function load() {
|
13 |
+
add_action( 'admin_enqueue_scripts', array( 'WP_Stream_Pointers', 'enqueue_scripts' ) );
|
14 |
+
add_action( 'user_register', array( 'WP_Stream_Pointers', 'dismiss_pointers_for_new_users' ) );
|
15 |
+
}
|
16 |
+
|
17 |
+
public static function init_core_pointers() {
|
18 |
+
self::$pointers = array(
|
19 |
+
'WP_Stream_Pointers' => array(
|
20 |
+
'index.php' => 'wpstream143_extensions',
|
21 |
+
'toplevel_page_' . WP_Stream_Admin::RECORDS_PAGE_SLUG => 'wpstream143_extensions',
|
22 |
+
'stream_page_' . WP_Stream_Admin::SETTINGS_PAGE_SLUG => 'wpstream143_extensions',
|
23 |
+
)
|
24 |
+
);
|
25 |
+
|
26 |
+
self::$caps = array(
|
27 |
+
'WP_Stream_Pointers' => array(
|
28 |
+
'wpstream143_extensions' => array( 'install_plugins' ),
|
29 |
+
)
|
30 |
+
);
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Initializes the pointers.
|
35 |
+
*
|
36 |
+
* @since 1.4.4
|
37 |
+
*
|
38 |
+
* All pointers can be disabled using the following:
|
39 |
+
* remove_action( 'admin_enqueue_scripts', array( 'WP_Stream_Pointers', 'enqueue_scripts' ) );
|
40 |
+
*
|
41 |
+
* Individual pointers (e.g. wpstream143_extensions) can be disabled using the following:
|
42 |
+
* remove_action( 'admin_print_footer_scripts', array( 'WP_Stream_Pointers', 'pointer_wpstream143_extensions' ) );
|
43 |
+
*/
|
44 |
+
public static function enqueue_scripts( $hook_suffix ) {
|
45 |
+
/*
|
46 |
+
* Register feature pointers
|
47 |
+
* Format: array( hook_suffix => pointer_id )
|
48 |
+
*/
|
49 |
+
self::init_core_pointers();
|
50 |
+
|
51 |
+
$get_pointers = array_merge( self::$pointers, apply_filters( 'wp_stream_pointers', array() ) );
|
52 |
+
$caps = array_merge( self::$caps, apply_filters( 'wp_stream_pointer_caps', array() ) );
|
53 |
+
|
54 |
+
foreach ( $get_pointers as $context => $registered_pointers ) {
|
55 |
+
|
56 |
+
// Check if screen related pointer is registered
|
57 |
+
if ( empty( $registered_pointers[ $hook_suffix ] ) ) {
|
58 |
+
return;
|
59 |
+
}
|
60 |
+
|
61 |
+
$pointers = (array) $registered_pointers[ $hook_suffix ];
|
62 |
+
|
63 |
+
$caps_required = $caps[ $context ];
|
64 |
+
|
65 |
+
// Get dismissed pointers
|
66 |
+
$dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) );
|
67 |
+
|
68 |
+
$got_pointers = false;
|
69 |
+
foreach ( array_diff( $pointers, $dismissed ) as $pointer ) {
|
70 |
+
if ( isset( $caps_required[ $pointer ] ) ) {
|
71 |
+
foreach ( $caps_required[ $pointer ] as $cap ) {
|
72 |
+
if ( ! current_user_can( $cap ) ) {
|
73 |
+
continue 2;
|
74 |
+
}
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
// Bind pointer print function
|
79 |
+
add_action( 'admin_print_footer_scripts', array( $context, 'pointer_' . $pointer ) );
|
80 |
+
|
81 |
+
$got_pointers = true;
|
82 |
+
}
|
83 |
+
}
|
84 |
+
|
85 |
+
if ( ! $got_pointers ) {
|
86 |
+
return;
|
87 |
+
}
|
88 |
+
|
89 |
+
// Add pointers script and style to queue
|
90 |
+
wp_enqueue_style( 'wp-pointer' );
|
91 |
+
wp_enqueue_script( 'wp-pointer' );
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Print the pointer javascript data.
|
96 |
+
*
|
97 |
+
* @since 1.4.4
|
98 |
+
*
|
99 |
+
* @param string $pointer_id The pointer ID.
|
100 |
+
* @param string $selector The HTML elements, on which the pointer should be attached.
|
101 |
+
* @param array $args Arguments to be passed to the pointer JS (see wp-pointer.js).
|
102 |
+
*/
|
103 |
+
public static function print_js( $pointer_id, $selector, $args ) {
|
104 |
+
if ( empty( $pointer_id ) || empty( $selector ) || empty( $args ) || empty( $args['content'] ) ) {
|
105 |
+
return;
|
106 |
+
}
|
107 |
+
|
108 |
+
?>
|
109 |
+
<script type="text/javascript">
|
110 |
+
//<![CDATA[
|
111 |
+
(function($){
|
112 |
+
var options = <?php echo json_encode( $args ) ?>, setup;
|
113 |
+
|
114 |
+
if ( ! options ) {
|
115 |
+
return;
|
116 |
+
}
|
117 |
+
|
118 |
+
options = $.extend( options, {
|
119 |
+
close: function() {
|
120 |
+
$.post( ajaxurl, {
|
121 |
+
pointer: <?php echo json_encode( $pointer_id ) ?>,
|
122 |
+
action: 'dismiss-wp-pointer'
|
123 |
+
});
|
124 |
+
}
|
125 |
+
});
|
126 |
+
|
127 |
+
setup = function() {
|
128 |
+
$(<?php echo json_encode( $selector ) ?>).first().pointer( options ).pointer('open');
|
129 |
+
};
|
130 |
+
|
131 |
+
if ( options.position && options.position.defer_loading ) {
|
132 |
+
$(window).bind( 'load.wp-pointers', setup );
|
133 |
+
} else {
|
134 |
+
$(document).ready( setup );
|
135 |
+
}
|
136 |
+
|
137 |
+
})( jQuery );
|
138 |
+
//]]>
|
139 |
+
</script>
|
140 |
+
<?php
|
141 |
+
}
|
142 |
+
|
143 |
+
public static function pointer_wpstream143_extensions() {
|
144 |
+
$content = '<h3>' . esc_html__( 'Stream Extensions', 'stream' ) . '</h3>';
|
145 |
+
$content .= '<p>' . esc_html__( 'Extension plugins are now available for Stream!', 'stream' ) . '</p>';
|
146 |
+
|
147 |
+
if ( 'dashboard' === get_current_screen()->id ) {
|
148 |
+
$selector = sprintf( '#toplevel_page_%s', WP_Stream_Admin::RECORDS_PAGE_SLUG );
|
149 |
+
$position = array( 'edge' => is_rtl() ? 'right' : 'left', 'align' => 'center' );
|
150 |
+
} else {
|
151 |
+
$selector = sprintf( 'a[href="%s?page=%s"]', WP_Stream_Admin::ADMIN_PARENT_PAGE, WP_Stream_Admin::ADMIN_PARENT_PAGE );
|
152 |
+
$position = array( 'edge' => is_rtl() ? 'right' : 'left', 'align' => 'center' );
|
153 |
+
}
|
154 |
+
|
155 |
+
self::print_js(
|
156 |
+
'wpstream143_extensions',
|
157 |
+
$selector,
|
158 |
+
array(
|
159 |
+
'content' => $content,
|
160 |
+
'position' => $position,
|
161 |
+
)
|
162 |
+
);
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Prevents new users from seeing existing 'new feature' pointers.
|
167 |
+
*
|
168 |
+
* @since 1.4.4
|
169 |
+
*/
|
170 |
+
public static function dismiss_pointers_for_new_users( $user_id ) {
|
171 |
+
add_user_meta( $user_id, 'dismissed_wp_pointers', '' );
|
172 |
+
}
|
173 |
+
|
174 |
+
}
|
classes/class-wp-stream-query.php
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Query {
|
4 |
+
|
5 |
+
public static $instance;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* @return WP_Stream_Query
|
9 |
+
*/
|
10 |
+
public static function instance() {
|
11 |
+
if ( ! self::$instance ) {
|
12 |
+
$class = __CLASS__;
|
13 |
+
self::$instance = new $class;
|
14 |
+
}
|
15 |
+
|
16 |
+
return self::$instance;
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Query Stream records
|
21 |
+
*
|
22 |
+
* @param array Query args
|
23 |
+
*
|
24 |
+
* @return array Stream Records
|
25 |
+
*/
|
26 |
+
public function query( $args ) {
|
27 |
+
global $wpdb;
|
28 |
+
|
29 |
+
$defaults = array(
|
30 |
+
// Search param
|
31 |
+
'search' => null,
|
32 |
+
'search_field' => 'summary',
|
33 |
+
'record_after' => null, // Deprecated, use date_after instead
|
34 |
+
// Date-based filters
|
35 |
+
'date' => null, // Ex: 2014-02-17
|
36 |
+
'date_from' => null, // Ex: 2014-02-17
|
37 |
+
'date_to' => null, // Ex: 2014-02-17
|
38 |
+
'date_after' => null, // Ex: 2014-02-17T15:19:21+00:00
|
39 |
+
'date_before' => null, // Ex: 2014-02-17T15:19:21+00:00
|
40 |
+
// Record ID filters
|
41 |
+
'record' => null,
|
42 |
+
'record__in' => array(),
|
43 |
+
'record__not_in' => array(),
|
44 |
+
// Pagination params
|
45 |
+
'records_per_page' => get_option( 'posts_per_page', 20 ),
|
46 |
+
'paged' => 1,
|
47 |
+
// Order
|
48 |
+
'order' => 'desc',
|
49 |
+
'orderby' => 'date',
|
50 |
+
// Meta/Taxonomy sub queries
|
51 |
+
'meta' => array(),
|
52 |
+
// Data aggregations
|
53 |
+
'aggregations' => array(),
|
54 |
+
// Fields selection
|
55 |
+
'fields' => null,
|
56 |
+
);
|
57 |
+
|
58 |
+
// Additional property fields
|
59 |
+
$properties = array(
|
60 |
+
'type' => 'stream',
|
61 |
+
'author' => null,
|
62 |
+
'author_role' => null,
|
63 |
+
'ip' => null,
|
64 |
+
'object_id' => null,
|
65 |
+
'site_id' => null,
|
66 |
+
'blog_id' => null,
|
67 |
+
'visibility' => 'publish',
|
68 |
+
'connector' => null,
|
69 |
+
'context' => null,
|
70 |
+
'action' => null,
|
71 |
+
);
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Filter allows additional query properties to be added
|
75 |
+
*
|
76 |
+
* @since 2.0.0
|
77 |
+
*
|
78 |
+
* @return array Array of query properties
|
79 |
+
*/
|
80 |
+
$properties = apply_filters( 'wp_stream_query_properties', $properties );
|
81 |
+
|
82 |
+
// Add property fields to defaults, including their __in/__not_in variations
|
83 |
+
foreach ( $properties as $property => $default ) {
|
84 |
+
if ( ! isset( $defaults[ $property ] ) ) {
|
85 |
+
$defaults[ $property ] = $default;
|
86 |
+
}
|
87 |
+
$defaults[ "{$property}__in" ] = array();
|
88 |
+
$defaults[ "{$property}__not_in" ] = array();
|
89 |
+
}
|
90 |
+
|
91 |
+
$args = wp_parse_args( $args, $defaults );
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Filter allows additional arguments to query $args
|
95 |
+
*
|
96 |
+
* @since 1.4.0
|
97 |
+
*
|
98 |
+
* @return array Array of query arguments
|
99 |
+
*/
|
100 |
+
$args = apply_filters( 'wp_stream_query_args', $args );
|
101 |
+
|
102 |
+
$query = array();
|
103 |
+
$filters = array();
|
104 |
+
$fields = array();
|
105 |
+
|
106 |
+
// PARSE SEARCH
|
107 |
+
if ( $args['search'] ) {
|
108 |
+
if ( $args['search_field'] ) {
|
109 |
+
$search_field = $args['search_field'];
|
110 |
+
$query['query']['match'][ $search_field ] = $args['search'];
|
111 |
+
} else {
|
112 |
+
$query['query']['match']['summary'] = $args['search'];
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
// PARSE FIELDS
|
117 |
+
if ( $args['fields'] ) {
|
118 |
+
$fields = is_array( $args['fields'] ) ? $args['fields'] : explode( ',', $args['fields'] );
|
119 |
+
}
|
120 |
+
|
121 |
+
// PARSE DATE
|
122 |
+
if ( $args['date_from'] ) {
|
123 |
+
$filters[]['range']['created']['gte'] = wp_stream_get_iso_8601_extended_date( strtotime( $args['date_from'] . ' 00:00:00' ), get_option( 'gmt_offset' ) );
|
124 |
+
}
|
125 |
+
|
126 |
+
if ( $args['date_to'] ) {
|
127 |
+
$filters[]['range']['created']['lte'] = wp_stream_get_iso_8601_extended_date( strtotime( $args['date_to'] . ' 23:59:59' ), get_option( 'gmt_offset' ) );
|
128 |
+
}
|
129 |
+
|
130 |
+
// Support deprecated argument replaced by date_after
|
131 |
+
if ( $args['record_after'] && ! $args['date_after'] ) {
|
132 |
+
$args['date_after'] = $args['record_after'];
|
133 |
+
}
|
134 |
+
|
135 |
+
if ( $args['date_after'] ) {
|
136 |
+
$filters[]['range']['created']['gt'] = wp_stream_get_iso_8601_extended_date( strtotime( $args['date_after'] ) );
|
137 |
+
}
|
138 |
+
|
139 |
+
if ( $args['date_before'] ) {
|
140 |
+
$filters[]['range']['created']['lt'] = wp_stream_get_iso_8601_extended_date( strtotime( $args['date_before'] ) );
|
141 |
+
}
|
142 |
+
|
143 |
+
if ( $args['date'] ) {
|
144 |
+
$filters[]['range']['created'] = array(
|
145 |
+
'gte' => wp_stream_get_iso_8601_extended_date( strtotime( $args['date'] . ' 00:00:00' ), get_option( 'gmt_offset' ) ),
|
146 |
+
'lte' => wp_stream_get_iso_8601_extended_date( strtotime( $args['date'] . ' 23:59:59' ), get_option( 'gmt_offset' ) ),
|
147 |
+
);
|
148 |
+
}
|
149 |
+
|
150 |
+
// PARSE RECORD
|
151 |
+
if ( $args['record'] ) {
|
152 |
+
$filters[]['ids']['values'] = array( $args['record'] );
|
153 |
+
}
|
154 |
+
|
155 |
+
if ( $args['record__in'] && is_array( $args['record__in'] ) ) {
|
156 |
+
$values = is_array( $args['record__in'] ) ? $args['record__in'] : array_map( 'trim', explode( ',', $args['record__in'] ) );
|
157 |
+
|
158 |
+
$filters[]['ids']['values'] = $values;
|
159 |
+
}
|
160 |
+
|
161 |
+
if ( $args['record__not_in'] && is_array( $args['record__not_in'] ) ) {
|
162 |
+
$values = is_array( $args['record__not_in'] ) ? $args['record__not_in'] : array_map( 'trim', explode( ',', $args['record__not_in'] ) );
|
163 |
+
|
164 |
+
$filters[]['not']['ids']['values'] = $values;
|
165 |
+
}
|
166 |
+
|
167 |
+
// PARSE PROPERTIES
|
168 |
+
foreach ( $properties as $property => $default ) {
|
169 |
+
// Allow 0 values
|
170 |
+
if ( isset( $args[ $property ] ) && '' !== $args[ $property ] && false !== $args[ $property ] && ! is_array( $args[ $property ] ) ) {
|
171 |
+
$filters[]['term'][ $property ] = $args[ $property ];
|
172 |
+
}
|
173 |
+
|
174 |
+
if ( $args[ "{$property}__in" ] ) {
|
175 |
+
$values = is_array( $args[ "{$property}__in" ] ) ? $args[ "{$property}__in" ] : array_map( 'trim', explode( ',', $args[ "{$property}__in" ] ) );
|
176 |
+
$property_in = array();
|
177 |
+
foreach ( $values as $value ) {
|
178 |
+
$property_in[]['term'][ $property ] = $value;
|
179 |
+
}
|
180 |
+
$filters[]['or'] = $property_in;
|
181 |
+
}
|
182 |
+
|
183 |
+
if ( $args[ "{$property}__not_in" ] ) {
|
184 |
+
$values = is_array( $args[ "{$property}__not_in" ] ) ? $args[ "{$property}__not_in" ] : array_map( 'trim', explode( ',', $args[ "{$property}__not_in" ] ) );
|
185 |
+
$property_not_in = array();
|
186 |
+
foreach ( $values as $value ) {
|
187 |
+
$property_not_in[]['not']['term'][ $property ] = $value;
|
188 |
+
}
|
189 |
+
$filters[]['or'] = $property_not_in;
|
190 |
+
}
|
191 |
+
}
|
192 |
+
|
193 |
+
// PARSE PAGINATION
|
194 |
+
if ( $args['records_per_page'] ) {
|
195 |
+
if ( $args['records_per_page'] >= 0 ) {
|
196 |
+
$query['size'] = (int) $args['records_per_page'];
|
197 |
+
} else {
|
198 |
+
$query['size'] = 999999; // Actual limit placed on "unlimited" results
|
199 |
+
}
|
200 |
+
} else {
|
201 |
+
$query['size'] = get_option( 'posts_per_page', 20 );
|
202 |
+
}
|
203 |
+
|
204 |
+
if ( $args['paged'] ) {
|
205 |
+
$query['from'] = ( (int) $args['paged'] - 1 ) * $query['size'];
|
206 |
+
}
|
207 |
+
|
208 |
+
// PARSE ORDER
|
209 |
+
$query['sort'] = array();
|
210 |
+
|
211 |
+
$orderby = ! empty( $args['orderby'] ) ? $args['orderby'] : 'created';
|
212 |
+
$order = ! empty( $args['order'] ) ? $args['order'] : 'desc';
|
213 |
+
|
214 |
+
if ( 'date' === $orderby ) {
|
215 |
+
$orderby = 'created';
|
216 |
+
}
|
217 |
+
|
218 |
+
$query['sort'][][ $orderby ]['order'] = $order;
|
219 |
+
|
220 |
+
// PARSE META
|
221 |
+
if ( $args['meta'] ) {
|
222 |
+
$meta = (array) $args['meta'];
|
223 |
+
foreach ( $meta as $key => $values ) {
|
224 |
+
if ( ! is_array( $values ) ) {
|
225 |
+
$values = (array) $values;
|
226 |
+
}
|
227 |
+
$filters[]['nested'] = array(
|
228 |
+
'path' => 'stream_meta',
|
229 |
+
'filter' => array(
|
230 |
+
'terms' => array(
|
231 |
+
$key => $values,
|
232 |
+
),
|
233 |
+
),
|
234 |
+
);
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
// PARSE AGGREGATIONS
|
239 |
+
if ( ! empty( $args['aggregations'] ) ) {
|
240 |
+
foreach ( $args['aggregations'] as $aggregation_term ) {
|
241 |
+
$query['aggregations'][ $aggregation_term ]['terms']['field'] = $aggregation_term;
|
242 |
+
}
|
243 |
+
}
|
244 |
+
|
245 |
+
// Add filters to query
|
246 |
+
if ( ! empty( $filters ) ) {
|
247 |
+
if ( count( $filters ) > 1 ) {
|
248 |
+
$query['filter']['and'] = $filters;
|
249 |
+
} else {
|
250 |
+
$query['filter'] = current( $filters );
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
/**
|
255 |
+
* Filter allows the final query args to be modified
|
256 |
+
*
|
257 |
+
* @since 2.0.0
|
258 |
+
*
|
259 |
+
* @return array Array of query arguments
|
260 |
+
*/
|
261 |
+
$query = apply_filters( 'wp_stream_db_query', $query );
|
262 |
+
|
263 |
+
/**
|
264 |
+
* Filter allows the final query fields to be modified
|
265 |
+
*
|
266 |
+
* @since 2.0.0
|
267 |
+
*
|
268 |
+
* @return array Array of query fields
|
269 |
+
*/
|
270 |
+
$fields = apply_filters( 'wp_stream_db_fields', $fields );
|
271 |
+
|
272 |
+
/**
|
273 |
+
* Query results
|
274 |
+
*
|
275 |
+
* @var array
|
276 |
+
*/
|
277 |
+
return WP_Stream::$db->query( $query, $fields );
|
278 |
+
}
|
279 |
+
}
|
classes/class-wp-stream-record.php
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Record {
|
4 |
+
|
5 |
+
public $ID;
|
6 |
+
public $site_id;
|
7 |
+
public $blog_id;
|
8 |
+
public $object_id;
|
9 |
+
public $author;
|
10 |
+
public $author_role;
|
11 |
+
public $summary;
|
12 |
+
public $visibility;
|
13 |
+
public $type;
|
14 |
+
public $connector;
|
15 |
+
public $context;
|
16 |
+
public $action;
|
17 |
+
public $created;
|
18 |
+
public $ip;
|
19 |
+
|
20 |
+
public $stream_meta;
|
21 |
+
|
22 |
+
// Deprecated
|
23 |
+
public $contexts;
|
24 |
+
|
25 |
+
public function __construct( $id = null ) {
|
26 |
+
if ( $id ) {
|
27 |
+
$this->load( $id );
|
28 |
+
}
|
29 |
+
}
|
30 |
+
|
31 |
+
public function load( $id ) {
|
32 |
+
$records = WP_Stream::$db->query( array( 'id' => $id ) );
|
33 |
+
|
34 |
+
if ( $record ) {
|
35 |
+
$this->populate( $record );
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
public function save() {
|
40 |
+
if ( ! $this->validate() ) {
|
41 |
+
return new WP_Error( 'validation-error', __( 'Could not validate record data.', 'stream' ) );
|
42 |
+
}
|
43 |
+
|
44 |
+
return WP_Stream::$db->store( (array) $this );
|
45 |
+
}
|
46 |
+
|
47 |
+
public function populate( array $raw ) {
|
48 |
+
$keys = get_class_vars( __CLASS__ );
|
49 |
+
$data = array_intersect_key( $raw, $keys );
|
50 |
+
if ( ! empty( $data['contexts'] ) ) {
|
51 |
+
$data['context'] = key( $data['contexts'] );
|
52 |
+
$data['action'] = current( $data['contexts'] );
|
53 |
+
}
|
54 |
+
foreach ( $data as $key => $val ) {
|
55 |
+
$this->{$key} = $val;
|
56 |
+
}
|
57 |
+
}
|
58 |
+
|
59 |
+
public function validate() {
|
60 |
+
return true;
|
61 |
+
}
|
62 |
+
|
63 |
+
public static function instance( array $data ) {
|
64 |
+
$object = new self();
|
65 |
+
$object->populate( $data );
|
66 |
+
return $object;
|
67 |
+
}
|
68 |
+
|
69 |
+
}
|
classes/class-wp-stream-settings.php
ADDED
@@ -0,0 +1,927 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Settings class
|
4 |
+
*
|
5 |
+
* @author X-Team <x-team.com>
|
6 |
+
* @author Shady Sharaf <shady@x-team.com>
|
7 |
+
*/
|
8 |
+
class WP_Stream_Settings {
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Settings key/identifier
|
12 |
+
*/
|
13 |
+
const OPTION_KEY = 'wp_stream';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Plugin settings
|
17 |
+
*
|
18 |
+
* @var array
|
19 |
+
*/
|
20 |
+
public static $options = array();
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Option key for current screen
|
24 |
+
*
|
25 |
+
* @var array
|
26 |
+
*/
|
27 |
+
public static $option_key = '';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Settings fields
|
31 |
+
*
|
32 |
+
* @var array
|
33 |
+
*/
|
34 |
+
public static $fields = array();
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Public constructor
|
38 |
+
*
|
39 |
+
* @return void
|
40 |
+
*/
|
41 |
+
public static function load() {
|
42 |
+
self::$option_key = self::get_option_key();
|
43 |
+
self::$options = self::get_options();
|
44 |
+
|
45 |
+
// Register settings, and fields
|
46 |
+
add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
|
47 |
+
|
48 |
+
// Check if we need to flush rewrites rules
|
49 |
+
add_action( 'update_option_' . self::OPTION_KEY, array( __CLASS__, 'updated_option_trigger_flush_rules' ), 10, 2 );
|
50 |
+
|
51 |
+
add_filter( 'wp_stream_serialized_labels', array( __CLASS__, 'get_settings_translations' ) );
|
52 |
+
|
53 |
+
// Ajax callback function to search users
|
54 |
+
add_action( 'wp_ajax_stream_get_users', array( __CLASS__, 'get_users' ) );
|
55 |
+
|
56 |
+
// Ajax callback function to search IPs
|
57 |
+
add_action( 'wp_ajax_stream_get_ips', array( __CLASS__, 'get_ips' ) );
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Ajax callback function to search users, used on exclude setting page
|
62 |
+
*
|
63 |
+
* @uses WP_User_Query WordPress User Query class.
|
64 |
+
* @return void
|
65 |
+
*/
|
66 |
+
public static function get_users(){
|
67 |
+
if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( WP_Stream_Admin::SETTINGS_CAP ) ) {
|
68 |
+
return;
|
69 |
+
}
|
70 |
+
|
71 |
+
check_ajax_referer( 'stream_get_users', 'nonce' );
|
72 |
+
|
73 |
+
$response = (object) array(
|
74 |
+
'status' => false,
|
75 |
+
'message' => esc_html__( 'There was an error in the request', 'stream' ),
|
76 |
+
);
|
77 |
+
|
78 |
+
$search = ( isset( $_POST['find'] )? wp_unslash( trim( $_POST['find'] ) ) : '' );
|
79 |
+
$request = (object) array(
|
80 |
+
'find' => $search,
|
81 |
+
);
|
82 |
+
|
83 |
+
add_filter( 'user_search_columns', array( __CLASS__, 'add_display_name_search_columns' ), 10, 3 );
|
84 |
+
|
85 |
+
$users = new WP_User_Query(
|
86 |
+
array(
|
87 |
+
'search' => "*{$request->find}*",
|
88 |
+
'search_columns' => array(
|
89 |
+
'user_login',
|
90 |
+
'user_nicename',
|
91 |
+
'user_email',
|
92 |
+
'user_url',
|
93 |
+
),
|
94 |
+
'orderby' => 'display_name',
|
95 |
+
'number' => WP_Stream_Admin::PRELOAD_AUTHORS_MAX,
|
96 |
+
)
|
97 |
+
);
|
98 |
+
|
99 |
+
remove_filter( 'user_search_columns', array( __CLASS__, 'add_display_name_search_columns' ), 10 );
|
100 |
+
|
101 |
+
if ( 0 === $users->get_total() ) {
|
102 |
+
wp_send_json_error( $response );
|
103 |
+
}
|
104 |
+
|
105 |
+
$response->status = true;
|
106 |
+
$response->message = '';
|
107 |
+
$response->users = array();
|
108 |
+
|
109 |
+
foreach ( $users->results as $key => $user ) {
|
110 |
+
$author = new WP_Stream_Author( $user->ID );
|
111 |
+
|
112 |
+
$args = array(
|
113 |
+
'id' => $author->ID,
|
114 |
+
'text' => $author->display_name,
|
115 |
+
);
|
116 |
+
|
117 |
+
$args['tooltip'] = esc_attr(
|
118 |
+
sprintf(
|
119 |
+
__( "ID: %d\nUser: %s\nEmail: %s\nRole: %s", 'stream' ),
|
120 |
+
$author->id,
|
121 |
+
$author->user_login,
|
122 |
+
$author->user_email,
|
123 |
+
ucwords( $author->get_role() )
|
124 |
+
)
|
125 |
+
);
|
126 |
+
|
127 |
+
$args['icon'] = $author->get_avatar_src( 32 );
|
128 |
+
|
129 |
+
$response->users[] = $args;
|
130 |
+
}
|
131 |
+
|
132 |
+
if ( empty( $search ) || preg_match( '/wp|cli|system|unknown/i', $search ) ) {
|
133 |
+
$author = new WP_Stream_Author( 0 );
|
134 |
+
$response->users[] = array(
|
135 |
+
'id' => $author->id,
|
136 |
+
'text' => $author->get_display_name(),
|
137 |
+
'icon' => $author->get_avatar_src( 32 ),
|
138 |
+
'tooltip' => esc_html__( 'Actions performed by the system when a user is not logged in (e.g. auto site upgrader, or invoking WP-CLI without --user)', 'stream' ),
|
139 |
+
);
|
140 |
+
}
|
141 |
+
|
142 |
+
wp_send_json_success( $response );
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Ajax callback function to search IP addresses, used on exclude setting page
|
147 |
+
*
|
148 |
+
* @uses WP_User_Query WordPress User Query class.
|
149 |
+
* @return void
|
150 |
+
*/
|
151 |
+
public static function get_ips(){
|
152 |
+
if ( ! defined( 'DOING_AJAX' ) || ! current_user_can( WP_Stream_Admin::SETTINGS_CAP ) ) {
|
153 |
+
return;
|
154 |
+
}
|
155 |
+
|
156 |
+
check_ajax_referer( 'stream_get_ips', 'nonce' );
|
157 |
+
|
158 |
+
$ips = wp_stream_existing_records( 'ip' );
|
159 |
+
|
160 |
+
if ( $ips ) {
|
161 |
+
wp_send_json_success( $ips );
|
162 |
+
} else {
|
163 |
+
wp_send_json_error();
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
167 |
+
/**
|
168 |
+
* Filter the columns to search in a WP_User_Query search.
|
169 |
+
*
|
170 |
+
* @param array $search_columns Array of column names to be searched.
|
171 |
+
* @param string $search Text being searched.
|
172 |
+
* @param WP_User_Query $query current WP_User_Query instance.
|
173 |
+
*
|
174 |
+
* @return array
|
175 |
+
*/
|
176 |
+
public static function add_display_name_search_columns( $search_columns, $search, $query ){
|
177 |
+
$search_columns[] = 'display_name';
|
178 |
+
|
179 |
+
return $search_columns;
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Returns the option key depending on which settings page is being viewed
|
184 |
+
*
|
185 |
+
* @return string Option key for this page
|
186 |
+
*/
|
187 |
+
public static function get_option_key() {
|
188 |
+
$option_key = self::OPTION_KEY;
|
189 |
+
|
190 |
+
$current_page = wp_stream_filter_input( INPUT_GET, 'page' );
|
191 |
+
|
192 |
+
if ( ! $current_page ) {
|
193 |
+
$current_page = wp_stream_filter_input( INPUT_GET, 'action' );
|
194 |
+
}
|
195 |
+
|
196 |
+
return apply_filters( 'wp_stream_settings_option_key', $option_key );
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Return settings fields
|
201 |
+
*
|
202 |
+
* @return array Multidimensional array of fields
|
203 |
+
*/
|
204 |
+
public static function get_fields() {
|
205 |
+
$fields = array(
|
206 |
+
'general' => array(
|
207 |
+
'title' => esc_html__( 'General', 'stream' ),
|
208 |
+
'fields' => array(
|
209 |
+
array(
|
210 |
+
'name' => 'role_access',
|
211 |
+
'title' => esc_html__( 'Role Access', 'stream' ),
|
212 |
+
'type' => 'multi_checkbox',
|
213 |
+
'desc' => esc_html__( 'Users from the selected roles above will have permission to view Stream Records. However, only site Administrators can access Stream Settings.', 'stream' ),
|
214 |
+
'choices' => self::get_roles(),
|
215 |
+
'default' => array( 'administrator' ),
|
216 |
+
),
|
217 |
+
array(
|
218 |
+
'name' => 'private_feeds',
|
219 |
+
'title' => esc_html__( 'Private Feeds', 'stream' ),
|
220 |
+
'type' => 'checkbox',
|
221 |
+
'desc' => sprintf(
|
222 |
+
__( 'Users from the selected roles above will be given a private key found in their %suser profile%s to access feeds of Stream Records securely. Please %sflush rewrite rules%s on your site after changing this setting.', 'stream' ),
|
223 |
+
sprintf(
|
224 |
+
'<a href="%s" title="%s">',
|
225 |
+
admin_url( sprintf( 'profile.php#wp-stream-highlight:%s', WP_Stream_Feeds::USER_FEED_OPTION_KEY ) ),
|
226 |
+
esc_attr__( 'View Profile', 'stream' )
|
227 |
+
),
|
228 |
+
'</a>',
|
229 |
+
sprintf(
|
230 |
+
'<a href="%s" title="%s" target="_blank">',
|
231 |
+
esc_url( 'http://codex.wordpress.org/Rewrite_API/flush_rules#What_it_does' ),
|
232 |
+
esc_attr__( 'View Codex', 'stream' )
|
233 |
+
),
|
234 |
+
'</a>'
|
235 |
+
),
|
236 |
+
'after_field' => esc_html__( 'Enabled', 'stream' ),
|
237 |
+
'default' => 0,
|
238 |
+
),
|
239 |
+
),
|
240 |
+
),
|
241 |
+
'exclude' => array(
|
242 |
+
'title' => esc_html__( 'Exclude', 'stream' ),
|
243 |
+
'fields' => array(
|
244 |
+
array(
|
245 |
+
'name' => 'rules',
|
246 |
+
'title' => esc_html__( 'Exclude Rules', 'stream' ),
|
247 |
+
'type' => 'rule_list',
|
248 |
+
'desc' => esc_html__( 'Create rules for excluding certain kinds of records from appearing in Stream.', 'stream' ),
|
249 |
+
'default' => array(),
|
250 |
+
'nonce' => 'stream_get_ips',
|
251 |
+
),
|
252 |
+
),
|
253 |
+
),
|
254 |
+
'advanced' => array(
|
255 |
+
'title' => esc_html__( 'Advanced', 'stream' ),
|
256 |
+
'fields' => array(
|
257 |
+
array(
|
258 |
+
'name' => 'comment_flood_tracking',
|
259 |
+
'title' => esc_html__( 'Comment Flood Tracking', 'stream' ),
|
260 |
+
'type' => 'checkbox',
|
261 |
+
'desc' => __( 'WordPress will automatically prevent duplicate comments from flooding the database. By default, Stream does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
|
262 |
+
'after_field' => esc_html__( 'Enabled', 'stream' ),
|
263 |
+
'default' => 0,
|
264 |
+
),
|
265 |
+
array(
|
266 |
+
'name' => 'wp_cron_tracking',
|
267 |
+
'title' => esc_html__( 'WP Cron Tracking', 'stream' ),
|
268 |
+
'type' => 'checkbox',
|
269 |
+
'desc' => __( 'By default, Stream does not track activity performed by WordPress cron events unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
|
270 |
+
'after_field' => esc_html__( 'Enabled', 'stream' ),
|
271 |
+
'default' => 0,
|
272 |
+
),
|
273 |
+
),
|
274 |
+
),
|
275 |
+
);
|
276 |
+
|
277 |
+
// If Akismet is active, allow Admins to opt-in to Akismet tracking
|
278 |
+
if ( class_exists( 'Akismet' ) ) {
|
279 |
+
$akismet_tracking = array(
|
280 |
+
'name' => 'akismet_tracking',
|
281 |
+
'title' => esc_html__( 'Akismet Tracking', 'stream' ),
|
282 |
+
'type' => 'checkbox',
|
283 |
+
'desc' => __( 'Akismet already keeps statistics for comment attempts that it blocks as SPAM. By default, Stream does not track these attempts unless you opt-in here. Enabling this is not necessary or recommended for most sites.', 'stream' ),
|
284 |
+
'after_field' => esc_html__( 'Enabled', 'stream' ),
|
285 |
+
'default' => 0,
|
286 |
+
);
|
287 |
+
|
288 |
+
array_push( $fields['advanced']['fields'], $akismet_tracking );
|
289 |
+
}
|
290 |
+
|
291 |
+
/**
|
292 |
+
* Filter allows for modification of options fields
|
293 |
+
*
|
294 |
+
* @return array Array of option fields
|
295 |
+
*/
|
296 |
+
self::$fields = apply_filters( 'wp_stream_settings_option_fields', $fields );
|
297 |
+
|
298 |
+
// Sort option fields in each tab by title ASC
|
299 |
+
foreach ( self::$fields as $tab => $options ) {
|
300 |
+
$titles = wp_list_pluck( self::$fields[ $tab ]['fields'], 'title' );
|
301 |
+
|
302 |
+
array_multisort( $titles, SORT_ASC, self::$fields[ $tab ]['fields'] );
|
303 |
+
}
|
304 |
+
|
305 |
+
return self::$fields;
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Returns a list of options based on the current screen.
|
310 |
+
*
|
311 |
+
* @return array Options
|
312 |
+
*/
|
313 |
+
public static function get_options() {
|
314 |
+
$option_key = self::$option_key;
|
315 |
+
$defaults = self::get_defaults( $option_key );
|
316 |
+
|
317 |
+
/**
|
318 |
+
* Filter allows for modification of options
|
319 |
+
*
|
320 |
+
* @param array array of options
|
321 |
+
* @return array updated array of options
|
322 |
+
*/
|
323 |
+
return apply_filters(
|
324 |
+
'wp_stream_settings_options',
|
325 |
+
wp_parse_args(
|
326 |
+
(array) get_option( $option_key, array() ),
|
327 |
+
$defaults
|
328 |
+
),
|
329 |
+
$option_key
|
330 |
+
);
|
331 |
+
}
|
332 |
+
|
333 |
+
/**
|
334 |
+
* Iterate through registered fields and extract default values
|
335 |
+
*
|
336 |
+
* @return array Default option values
|
337 |
+
*/
|
338 |
+
public static function get_defaults() {
|
339 |
+
$fields = self::get_fields();
|
340 |
+
$defaults = array();
|
341 |
+
|
342 |
+
foreach ( $fields as $section_name => $section ) {
|
343 |
+
foreach ( $section['fields'] as $field ) {
|
344 |
+
$defaults[ $section_name.'_'.$field['name'] ] = isset( $field['default'] )
|
345 |
+
? $field['default']
|
346 |
+
: null;
|
347 |
+
}
|
348 |
+
}
|
349 |
+
|
350 |
+
/**
|
351 |
+
* Filter allows for modification of options defaults
|
352 |
+
*
|
353 |
+
* @param array array of options
|
354 |
+
* @return array updated array of option defaults
|
355 |
+
*/
|
356 |
+
return apply_filters(
|
357 |
+
'wp_stream_settings_option_defaults',
|
358 |
+
$defaults
|
359 |
+
);
|
360 |
+
}
|
361 |
+
|
362 |
+
/**
|
363 |
+
* Registers settings fields and sections
|
364 |
+
*
|
365 |
+
* @return void
|
366 |
+
*/
|
367 |
+
public static function register_settings() {
|
368 |
+
$sections = self::get_fields();
|
369 |
+
|
370 |
+
register_setting( self::$option_key, self::$option_key );
|
371 |
+
|
372 |
+
foreach ( $sections as $section_name => $section ) {
|
373 |
+
add_settings_section(
|
374 |
+
$section_name,
|
375 |
+
null,
|
376 |
+
'__return_false',
|
377 |
+
self::$option_key
|
378 |
+
);
|
379 |
+
|
380 |
+
foreach ( $section['fields'] as $field_idx => $field ) {
|
381 |
+
if ( ! isset( $field['type'] ) ) { // No field type associated, skip, no GUI
|
382 |
+
continue;
|
383 |
+
}
|
384 |
+
add_settings_field(
|
385 |
+
$field['name'],
|
386 |
+
$field['title'],
|
387 |
+
( isset( $field['callback'] ) ? $field['callback'] : array( __CLASS__, 'output_field' ) ),
|
388 |
+
self::$option_key,
|
389 |
+
$section_name,
|
390 |
+
$field + array(
|
391 |
+
'section' => $section_name,
|
392 |
+
'label_for' => sprintf( '%s_%s_%s', self::$option_key, $section_name, $field['name'] ), // xss ok
|
393 |
+
)
|
394 |
+
);
|
395 |
+
}
|
396 |
+
}
|
397 |
+
}
|
398 |
+
|
399 |
+
/**
|
400 |
+
* Check if we have updated a settings that requires rewrite rules to be flushed
|
401 |
+
*
|
402 |
+
* @param array $old_value
|
403 |
+
* @param array $new_value
|
404 |
+
*
|
405 |
+
* @internal param $option
|
406 |
+
* @internal param string $option
|
407 |
+
* @action updated_option
|
408 |
+
* @return void
|
409 |
+
*/
|
410 |
+
public static function updated_option_trigger_flush_rules( $old_value, $new_value ) {
|
411 |
+
if ( is_array( $new_value ) && is_array( $old_value ) ) {
|
412 |
+
$new_value = ( array_key_exists( 'general_private_feeds', $new_value ) ) ? $new_value['general_private_feeds'] : 0;
|
413 |
+
$old_value = ( array_key_exists( 'general_private_feeds', $old_value ) ) ? $old_value['general_private_feeds'] : 0;
|
414 |
+
|
415 |
+
if ( $new_value !== $old_value ) {
|
416 |
+
delete_option( 'rewrite_rules' );
|
417 |
+
}
|
418 |
+
}
|
419 |
+
}
|
420 |
+
|
421 |
+
/**
|
422 |
+
* Compile HTML needed for displaying the field
|
423 |
+
*
|
424 |
+
* @param array $field Field settings
|
425 |
+
* @return string HTML to be displayed
|
426 |
+
*/
|
427 |
+
public static function render_field( $field ) {
|
428 |
+
$output = null;
|
429 |
+
$type = isset( $field['type'] ) ? $field['type'] : null;
|
430 |
+
$section = isset( $field['section'] ) ? $field['section'] : null;
|
431 |
+
$name = isset( $field['name'] ) ? $field['name'] : null;
|
432 |
+
$class = isset( $field['class'] ) ? $field['class'] : null;
|
433 |
+
$placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : null;
|
434 |
+
$description = isset( $field['desc'] ) ? $field['desc'] : null;
|
435 |
+
$href = isset( $field['href'] ) ? $field['href'] : null;
|
436 |
+
$rows = isset( $field['rows'] ) ? $field['rows'] : 10;
|
437 |
+
$cols = isset( $field['cols'] ) ? $field['cols'] : 50;
|
438 |
+
$after_field = isset( $field['after_field'] ) ? $field['after_field'] : null;
|
439 |
+
$default = isset( $field['default'] ) ? $field['default'] : null;
|
440 |
+
$title = isset( $field['title'] ) ? $field['title'] : null;
|
441 |
+
$nonce = isset( $field['nonce'] ) ? $field['nonce'] : null;
|
442 |
+
|
443 |
+
if ( isset( $field['value'] ) ) {
|
444 |
+
$current_value = $field['value'];
|
445 |
+
} else {
|
446 |
+
$current_value = self::$options[ $section . '_' . $name ];
|
447 |
+
}
|
448 |
+
|
449 |
+
$option_key = self::$option_key;
|
450 |
+
|
451 |
+
if ( is_callable( $current_value ) ) {
|
452 |
+
$current_value = call_user_func( $current_value );
|
453 |
+
}
|
454 |
+
|
455 |
+
if ( ! $type || ! $section || ! $name ) {
|
456 |
+
return;
|
457 |
+
}
|
458 |
+
|
459 |
+
if ( 'multi_checkbox' === $type
|
460 |
+
&& ( empty( $field['choices'] ) || ! is_array( $field['choices'] ) )
|
461 |
+
) {
|
462 |
+
return;
|
463 |
+
}
|
464 |
+
|
465 |
+
switch ( $type ) {
|
466 |
+
case 'text':
|
467 |
+
case 'number':
|
468 |
+
$output = sprintf(
|
469 |
+
'<input type="%1$s" name="%2$s[%3$s_%4$s]" id="%2$s_%3$s_%4$s" class="%5$s" placeholder="%6$s" value="%7$s" /> %8$s',
|
470 |
+
esc_attr( $type ),
|
471 |
+
esc_attr( $option_key ),
|
472 |
+
esc_attr( $section ),
|
473 |
+
esc_attr( $name ),
|
474 |
+
esc_attr( $class ),
|
475 |
+
esc_attr( $placeholder ),
|
476 |
+
esc_attr( $current_value ),
|
477 |
+
$after_field // xss ok
|
478 |
+
);
|
479 |
+
break;
|
480 |
+
case 'textarea':
|
481 |
+
$output = sprintf(
|
482 |
+
'<textarea name="%1$s[%2$s_%3$s]" id="%1$s_%2$s_%3$s" class="%4$s" placeholder="%5$s" rows="%6$d" cols="%7$d">%8$s</textarea> %9$s',
|
483 |
+
esc_attr( $option_key ),
|
484 |
+
esc_attr( $section ),
|
485 |
+
esc_attr( $name ),
|
486 |
+
esc_attr( $class ),
|
487 |
+
esc_attr( $placeholder ),
|
488 |
+
absint( $rows ),
|
489 |
+
absint( $cols ),
|
490 |
+
esc_textarea( $current_value ),
|
491 |
+
$after_field // xss ok
|
492 |
+
);
|
493 |
+
break;
|
494 |
+
case 'checkbox':
|
495 |
+
$output = sprintf(
|
496 |
+
'<label><input type="checkbox" name="%1$s[%2$s_%3$s]" id="%1$s[%2$s_%3$s]" value="1" %4$s /> %5$s</label>',
|
497 |
+
esc_attr( $option_key ),
|
498 |
+
esc_attr( $section ),
|
499 |
+
esc_attr( $name ),
|
500 |
+
checked( $current_value, 1, false ),
|
501 |
+
$after_field // xss ok
|
502 |
+
);
|
503 |
+
break;
|
504 |
+
case 'multi_checkbox':
|
505 |
+
$output = sprintf(
|
506 |
+
'<div id="%1$s[%2$s_%3$s]"><fieldset>',
|
507 |
+
esc_attr( $option_key ),
|
508 |
+
esc_attr( $section ),
|
509 |
+
esc_attr( $name )
|
510 |
+
);
|
511 |
+
// Fallback if nothing is selected
|
512 |
+
$output .= sprintf(
|
513 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][]" value="__placeholder__" />',
|
514 |
+
esc_attr( $option_key ),
|
515 |
+
esc_attr( $section ),
|
516 |
+
esc_attr( $name )
|
517 |
+
);
|
518 |
+
$current_value = (array) $current_value;
|
519 |
+
$choices = $field['choices'];
|
520 |
+
if ( is_callable( $choices ) ) {
|
521 |
+
$choices = call_user_func( $choices );
|
522 |
+
}
|
523 |
+
foreach ( $choices as $value => $label ) {
|
524 |
+
$output .= sprintf(
|
525 |
+
'<label>%1$s <span>%2$s</span></label><br />',
|
526 |
+
sprintf(
|
527 |
+
'<input type="checkbox" name="%1$s[%2$s_%3$s][]" value="%4$s" %5$s />',
|
528 |
+
esc_attr( $option_key ),
|
529 |
+
esc_attr( $section ),
|
530 |
+
esc_attr( $name ),
|
531 |
+
esc_attr( $value ),
|
532 |
+
checked( in_array( $value, $current_value ), true, false )
|
533 |
+
),
|
534 |
+
esc_html( $label )
|
535 |
+
);
|
536 |
+
}
|
537 |
+
$output .= '</fieldset></div>';
|
538 |
+
break;
|
539 |
+
case 'select':
|
540 |
+
$current_value = self::$options[ $section . '_' . $name ];
|
541 |
+
$default_value = isset( $default['value'] ) ? $default['value'] : '-1';
|
542 |
+
$default_name = isset( $default['name'] ) ? $default['name'] : 'Choose Setting';
|
543 |
+
|
544 |
+
$output = sprintf(
|
545 |
+
'<select name="%1$s[%2$s_%3$s]" class="%1$s_%2$s_%3$s">',
|
546 |
+
esc_attr( $option_key ),
|
547 |
+
esc_attr( $section ),
|
548 |
+
esc_attr( $name )
|
549 |
+
);
|
550 |
+
$output .= sprintf(
|
551 |
+
'<option value="%1$s" %2$s>%3$s</option>',
|
552 |
+
esc_attr( $default_value ),
|
553 |
+
checked( $default_value === $current_value, true, false ),
|
554 |
+
esc_html( $default_name )
|
555 |
+
);
|
556 |
+
foreach ( $field['choices'] as $value => $label ) {
|
557 |
+
$output .= sprintf(
|
558 |
+
'<option value="%1$s" %2$s>%3$s</option>',
|
559 |
+
esc_attr( $value ),
|
560 |
+
checked( $value === $current_value, true, false ),
|
561 |
+
esc_html( $label )
|
562 |
+
);
|
563 |
+
}
|
564 |
+
$output .= '</select>';
|
565 |
+
break;
|
566 |
+
case 'file':
|
567 |
+
$output = sprintf(
|
568 |
+
'<input type="file" name="%1$s[%2$s_%3$s]" class="%4$s">',
|
569 |
+
esc_attr( $option_key ),
|
570 |
+
esc_attr( $section ),
|
571 |
+
esc_attr( $name ),
|
572 |
+
esc_attr( $class )
|
573 |
+
);
|
574 |
+
break;
|
575 |
+
case 'link':
|
576 |
+
$output = sprintf(
|
577 |
+
'<a id="%1$s_%2$s_%3$s" class="%4$s" href="%5$s">%6$s</a>',
|
578 |
+
esc_attr( $option_key ),
|
579 |
+
esc_attr( $section ),
|
580 |
+
esc_attr( $name ),
|
581 |
+
esc_attr( $class ),
|
582 |
+
esc_attr( $href ),
|
583 |
+
esc_attr( $title )
|
584 |
+
);
|
585 |
+
break;
|
586 |
+
case 'select2' :
|
587 |
+
if ( ! isset( $current_value ) ) {
|
588 |
+
$current_value = '';
|
589 |
+
}
|
590 |
+
|
591 |
+
$data_values = array();
|
592 |
+
|
593 |
+
if ( isset( $field['choices'] ) ) {
|
594 |
+
$choices = $field['choices'];
|
595 |
+
if ( is_callable( $choices ) ) {
|
596 |
+
$param = ( isset( $field['param'] ) ) ? $field['param'] : null;
|
597 |
+
$choices = call_user_func( $choices, $param );
|
598 |
+
}
|
599 |
+
foreach ( $choices as $key => $value ) {
|
600 |
+
if ( is_array( $value ) ) {
|
601 |
+
$child_values = array();
|
602 |
+
if ( isset( $value['children'] ) ) {
|
603 |
+
$child_values = array();
|
604 |
+
foreach ( $value['children'] as $child_key => $child_value ) {
|
605 |
+
$child_values[] = array( 'id' => $child_key, 'text' => $child_value );
|
606 |
+
}
|
607 |
+
}
|
608 |
+
if ( isset( $value['label'] ) ) {
|
609 |
+
$data_values[] = array( 'id' => $key, 'text' => $value['label'], 'children' => $child_values );
|
610 |
+
}
|
611 |
+
} else {
|
612 |
+
$data_values[] = array( 'id' => $key, 'text' => $value );
|
613 |
+
}
|
614 |
+
}
|
615 |
+
$class .= ' with-source';
|
616 |
+
}
|
617 |
+
|
618 |
+
$input_html = sprintf(
|
619 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s]" data-values=\'%4$s\' value="%5$s" class="select2-select %6$s" data-placeholder="%7$s" />',
|
620 |
+
esc_attr( $option_key ),
|
621 |
+
esc_attr( $section ),
|
622 |
+
esc_attr( $name ),
|
623 |
+
esc_attr( json_encode( $data_values ) ),
|
624 |
+
esc_attr( $current_value ),
|
625 |
+
$class,
|
626 |
+
sprintf( esc_html__( 'Any %s', 'stream' ), $title )
|
627 |
+
);
|
628 |
+
|
629 |
+
$output = sprintf(
|
630 |
+
'<div class="%1$s_%2$s_%3$s">%4$s</div>',
|
631 |
+
esc_attr( $option_key ),
|
632 |
+
esc_attr( $section ),
|
633 |
+
esc_attr( $name ),
|
634 |
+
$input_html
|
635 |
+
);
|
636 |
+
|
637 |
+
break;
|
638 |
+
case 'rule_list' :
|
639 |
+
$output = '<p class="description">' . esc_html( $description ) . '</p>';
|
640 |
+
|
641 |
+
$actions_top = sprintf( '<input type="button" class="button" id="%1$s_new_rule" value="+ %2$s" />', esc_attr( $section . '_' . $name ), __( 'Add New Rule', 'stream' ) );
|
642 |
+
$actions_bottom = sprintf( '<input type="button" class="button" id="%1$s_remove_rules" value="%2$s" />', esc_attr( $section . '_' . $name ), __( 'Delete Selected Rules', 'stream' ) );
|
643 |
+
|
644 |
+
$output .= sprintf( '<div class="tablenav top">%1$s</div>', $actions_top );
|
645 |
+
$output .= '<table class="wp-list-table widefat fixed stream-exclude-list">';
|
646 |
+
|
647 |
+
unset( $description );
|
648 |
+
|
649 |
+
$heading_row = sprintf(
|
650 |
+
'<tr>
|
651 |
+
<th scope="col" class="check-column manage-column">%1$s</th>
|
652 |
+
<th scope="col" class="manage-column">%2$s</th>
|
653 |
+
<th scope="col" class="manage-column">%3$s</th>
|
654 |
+
<th scope="col" class="manage-column">%4$s</th>
|
655 |
+
<th scope="col" class="manage-column">%5$s</th>
|
656 |
+
<th scope="col" class="actions-column manage-column"><span class="hidden">%6$s</span></th>
|
657 |
+
</tr>',
|
658 |
+
'<input class="cb-select" type="checkbox" />',
|
659 |
+
esc_html__( 'Author or Role', 'stream' ),
|
660 |
+
esc_html__( 'Context', 'stream' ),
|
661 |
+
esc_html__( 'Action', 'stream' ),
|
662 |
+
esc_html__( 'IP Address', 'stream' ),
|
663 |
+
esc_html__( 'Filters', 'stream' )
|
664 |
+
);
|
665 |
+
|
666 |
+
$exclude_rows = array();
|
667 |
+
|
668 |
+
// Prepend an empty row
|
669 |
+
$current_value['exclude_row'] = array( 'helper' => '' ) + ( isset( $current_value['exclude_row'] ) ? $current_value['exclude_row'] : array() );
|
670 |
+
|
671 |
+
foreach ( $current_value['exclude_row'] as $key => $value ) {
|
672 |
+
// Prepare values
|
673 |
+
$author_or_role = isset( $current_value['author_or_role'][ $key ] ) ? $current_value['author_or_role'][ $key ] : '';
|
674 |
+
$connector = isset( $current_value['connector'][ $key ] ) ? $current_value['connector'][ $key ] : '';
|
675 |
+
$context = isset( $current_value['context'][ $key ] ) ? $current_value['context'][ $key ] : '';
|
676 |
+
$action = isset( $current_value['action'][ $key ] ) ? $current_value['action'][ $key ] : '';
|
677 |
+
$ip_address = isset( $current_value['ip_address'][ $key ] ) ? $current_value['ip_address'][ $key ] : '';
|
678 |
+
|
679 |
+
// Author or Role dropdown menu
|
680 |
+
$author_or_role_values = array();
|
681 |
+
$author_or_role_selected = array();
|
682 |
+
|
683 |
+
foreach ( self::get_roles() as $role_id => $role ) {
|
684 |
+
$args = array( 'id' => $role_id, 'text' => $role );
|
685 |
+
$users = get_users( array( 'role' => $role_id ) );
|
686 |
+
|
687 |
+
if ( count( $users ) ) {
|
688 |
+
$args['user_count'] = sprintf( _n( '1 user', '%s users', count( $users ), 'stream' ), count( $users ) );
|
689 |
+
}
|
690 |
+
|
691 |
+
if ( $role_id === $author_or_role ) {
|
692 |
+
$author_or_role_selected['id'] = $role_id;
|
693 |
+
$author_or_role_selected['text'] = $role;
|
694 |
+
}
|
695 |
+
|
696 |
+
$author_or_role_values[] = $args;
|
697 |
+
}
|
698 |
+
|
699 |
+
if ( empty( $author_or_role_selected ) && is_numeric( $author_or_role ) ) {
|
700 |
+
$user = new WP_User( $author_or_role );
|
701 |
+
$display_name = ( 0 === $user->ID ) ? __( 'N/A', 'stream' ) : $user->display_name;
|
702 |
+
$author_or_role_selected = array( 'id' => $user->ID, 'text' => $display_name );
|
703 |
+
}
|
704 |
+
|
705 |
+
$author_or_role_input = sprintf(
|
706 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" data-values=\'%5$s\' data-selected-id=\'%6$s\' data-selected-text=\'%7$s\' value="%6$s" class="select2-select %4$s" data-placeholder="%8$s" data-nonce="%9$s" />',
|
707 |
+
esc_attr( $option_key ),
|
708 |
+
esc_attr( $section ),
|
709 |
+
esc_attr( $name ),
|
710 |
+
'author_or_role',
|
711 |
+
esc_attr( json_encode( $author_or_role_values ) ),
|
712 |
+
isset( $author_or_role_selected['id'] ) ? esc_attr( $author_or_role_selected['id'] ) : '',
|
713 |
+
isset( $author_or_role_selected['text'] ) ? esc_attr( $author_or_role_selected['text'] ) : '',
|
714 |
+
esc_html__( 'Any Author or Role', 'stream' ),
|
715 |
+
esc_attr( wp_create_nonce( 'stream_get_users' ) )
|
716 |
+
);
|
717 |
+
|
718 |
+
// Context dropdown menu
|
719 |
+
$context_values = array();
|
720 |
+
|
721 |
+
foreach ( self::get_terms_labels( 'context' ) as $context_id => $context_data ) {
|
722 |
+
if ( is_array( $context_data ) ) {
|
723 |
+
$child_values = array();
|
724 |
+
if ( isset( $context_data['children'] ) ) {
|
725 |
+
$child_values = array();
|
726 |
+
foreach ( $context_data['children'] as $child_id => $child_value ) {
|
727 |
+
$child_values[] = array( 'id' => $child_id, 'text' => $child_value, 'parent' => $context_id );
|
728 |
+
}
|
729 |
+
}
|
730 |
+
if ( isset( $context_data['label'] ) ) {
|
731 |
+
$context_values[] = array( 'id' => $context_id, 'text' => $context_data['label'], 'children' => $child_values );
|
732 |
+
}
|
733 |
+
} else {
|
734 |
+
$context_values[] = array( 'id' => $context_id, 'text' => $context_data );
|
735 |
+
}
|
736 |
+
}
|
737 |
+
|
738 |
+
$connector_input = sprintf(
|
739 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" class="%4$s" value="%5$s">',
|
740 |
+
esc_attr( $option_key ),
|
741 |
+
esc_attr( $section ),
|
742 |
+
esc_attr( $name ),
|
743 |
+
esc_attr( 'connector' ),
|
744 |
+
esc_attr( $connector )
|
745 |
+
);
|
746 |
+
|
747 |
+
$context_input = sprintf(
|
748 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" data-values=\'%5$s\' value="%6$s" class="select2-select with-source %4$s" data-placeholder="%7$s" data-group="%8$s" />',
|
749 |
+
esc_attr( $option_key ),
|
750 |
+
esc_attr( $section ),
|
751 |
+
esc_attr( $name ),
|
752 |
+
'context',
|
753 |
+
esc_attr( json_encode( $context_values ) ),
|
754 |
+
esc_attr( $context ),
|
755 |
+
esc_html__( 'Any Context', 'stream' ),
|
756 |
+
'connector'
|
757 |
+
);
|
758 |
+
|
759 |
+
// Action dropdown menu
|
760 |
+
$action_values = array();
|
761 |
+
|
762 |
+
foreach ( self::get_terms_labels( 'action' ) as $action_id => $action_data ) {
|
763 |
+
$action_values[] = array( 'id' => $action_id, 'text' => $action_data );
|
764 |
+
}
|
765 |
+
|
766 |
+
$action_input = sprintf(
|
767 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" data-values=\'%5$s\' value="%6$s" class="select2-select with-source %4$s" data-placeholder="%7$s" />',
|
768 |
+
esc_attr( $option_key ),
|
769 |
+
esc_attr( $section ),
|
770 |
+
esc_attr( $name ),
|
771 |
+
'action',
|
772 |
+
esc_attr( json_encode( $action_values ) ),
|
773 |
+
esc_attr( $action ),
|
774 |
+
esc_html__( 'Any Action', 'stream' )
|
775 |
+
);
|
776 |
+
|
777 |
+
// IP Address input
|
778 |
+
$ip_address_input = sprintf(
|
779 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="%5$s" class="select2-select %4$s" data-placeholder="%6$s" data-nonce="%7$s" />',
|
780 |
+
esc_attr( $option_key ),
|
781 |
+
esc_attr( $section ),
|
782 |
+
esc_attr( $name ),
|
783 |
+
'ip_address',
|
784 |
+
esc_attr( $ip_address ),
|
785 |
+
esc_html__( 'Any IP Address', 'stream' ),
|
786 |
+
esc_attr( wp_create_nonce( 'stream_get_ips' ) )
|
787 |
+
);
|
788 |
+
|
789 |
+
// Hidden helper input
|
790 |
+
$helper_input = sprintf(
|
791 |
+
'<input type="hidden" name="%1$s[%2$s_%3$s][%4$s][]" value="" />',
|
792 |
+
esc_attr( $option_key ),
|
793 |
+
esc_attr( $section ),
|
794 |
+
esc_attr( $name ),
|
795 |
+
'exclude_row'
|
796 |
+
);
|
797 |
+
|
798 |
+
$exclude_rows[] = sprintf(
|
799 |
+
'<tr class="%1$s %2$s">
|
800 |
+
<th scope="row" class="check-column">%3$s %4$s</th>
|
801 |
+
<td>%5$s</td>
|
802 |
+
<td>%6$s %7$s</td>
|
803 |
+
<td>%8$s</td>
|
804 |
+
<td>%9$s</td>
|
805 |
+
<th scope="row" class="actions-column">%10$s</th>
|
806 |
+
</tr>',
|
807 |
+
( 0 !== $key % 2 ) ? 'alternate' : '',
|
808 |
+
( 'helper' === $key ) ? 'hidden helper' : '',
|
809 |
+
'<input class="cb-select" type="checkbox" />',
|
810 |
+
$helper_input,
|
811 |
+
$author_or_role_input,
|
812 |
+
$connector_input,
|
813 |
+
$context_input,
|
814 |
+
$action_input,
|
815 |
+
$ip_address_input,
|
816 |
+
'<a href="#" class="exclude_rules_remove_rule_row">Delete</a>'
|
817 |
+
);
|
818 |
+
}
|
819 |
+
|
820 |
+
$no_rules_found_row = sprintf(
|
821 |
+
'<tr class="no-items hidden"><td class="colspanchange" colspan="5">%1$s</td></tr>',
|
822 |
+
esc_html__( 'No rules found.', 'stream' )
|
823 |
+
);
|
824 |
+
|
825 |
+
$output .= '<thead>' . $heading_row . '</thead>';
|
826 |
+
$output .= '<tfoot>' . $heading_row . '</tfoot>';
|
827 |
+
$output .= '<tbody>' . $no_rules_found_row . implode( '', $exclude_rows ) . '</tbody>';
|
828 |
+
|
829 |
+
$output .= '</table>';
|
830 |
+
|
831 |
+
$output .= sprintf( '<div class="tablenav bottom">%1$s</div>', $actions_bottom );
|
832 |
+
|
833 |
+
break;
|
834 |
+
}
|
835 |
+
$output .= ! empty( $description ) ? sprintf( '<p class="description">%s</p>', $description /* xss ok */ ) : null;
|
836 |
+
|
837 |
+
return $output;
|
838 |
+
}
|
839 |
+
|
840 |
+
/**
|
841 |
+
* Render Callback for post_types field
|
842 |
+
*
|
843 |
+
* @param array $field
|
844 |
+
*
|
845 |
+
* @internal param $args
|
846 |
+
* @return void
|
847 |
+
*/
|
848 |
+
public static function output_field( $field ) {
|
849 |
+
$method = 'output_' . $field['name'];
|
850 |
+
|
851 |
+
if ( method_exists( __CLASS__, $method ) ) {
|
852 |
+
return call_user_func( array( __CLASS__, $method ), $field );
|
853 |
+
}
|
854 |
+
|
855 |
+
$output = self::render_field( $field );
|
856 |
+
|
857 |
+
echo $output; // xss okay
|
858 |
+
}
|
859 |
+
|
860 |
+
/**
|
861 |
+
* Get an array of user roles
|
862 |
+
*
|
863 |
+
* @return array
|
864 |
+
*/
|
865 |
+
public static function get_roles() {
|
866 |
+
$wp_roles = new WP_Roles();
|
867 |
+
$roles = array();
|
868 |
+
|
869 |
+
foreach ( $wp_roles->get_names() as $role => $label ) {
|
870 |
+
$roles[ $role ] = translate_user_role( $label );
|
871 |
+
}
|
872 |
+
|
873 |
+
return $roles;
|
874 |
+
}
|
875 |
+
|
876 |
+
/**
|
877 |
+
* Function will return all terms labels of given column
|
878 |
+
*
|
879 |
+
* @param $column string Name of the column
|
880 |
+
* @return array
|
881 |
+
*/
|
882 |
+
public static function get_terms_labels( $column ) {
|
883 |
+
$return_labels = array();
|
884 |
+
|
885 |
+
if ( isset( WP_Stream_Connectors::$term_labels[ 'stream_' . $column ] ) ) {
|
886 |
+
if ( 'context' === $column && isset( WP_Stream_Connectors::$term_labels['stream_connector'] ) ) {
|
887 |
+
$connectors = WP_Stream_Connectors::$term_labels['stream_connector'];
|
888 |
+
$contexts = WP_Stream_Connectors::$term_labels['stream_context'];
|
889 |
+
|
890 |
+
foreach ( $connectors as $connector => $connector_label ) {
|
891 |
+
$return_labels[ $connector ]['label'] = $connector_label;
|
892 |
+
foreach ( $contexts as $context => $context_label ) {
|
893 |
+
if ( isset( WP_Stream_Connectors::$contexts[ $connector ] ) && array_key_exists( $context, WP_Stream_Connectors::$contexts[ $connector ] ) ) {
|
894 |
+
$return_labels[ $connector ]['children'][ $context ] = $context_label;
|
895 |
+
}
|
896 |
+
}
|
897 |
+
}
|
898 |
+
} else {
|
899 |
+
$return_labels = WP_Stream_Connectors::$term_labels[ 'stream_' . $column ];
|
900 |
+
}
|
901 |
+
|
902 |
+
ksort( $return_labels );
|
903 |
+
}
|
904 |
+
|
905 |
+
return $return_labels;
|
906 |
+
}
|
907 |
+
|
908 |
+
/**
|
909 |
+
* Get translations of serialized Stream settings
|
910 |
+
*
|
911 |
+
* @filter wp_stream_serialized_labels
|
912 |
+
* @return array Multidimensional array of fields
|
913 |
+
*/
|
914 |
+
public static function get_settings_translations( $labels ) {
|
915 |
+
if ( ! isset( $labels[ self::OPTION_KEY ] ) ) {
|
916 |
+
$labels[ self::OPTION_KEY ] = array();
|
917 |
+
}
|
918 |
+
|
919 |
+
foreach ( self::get_fields() as $section_slug => $section ) {
|
920 |
+
foreach ( $section['fields'] as $field ) {
|
921 |
+
$labels[ self::OPTION_KEY ][ sprintf( '%s_%s', $section_slug, $field['name'] ) ] = $field['title'];
|
922 |
+
}
|
923 |
+
}
|
924 |
+
|
925 |
+
return $labels;
|
926 |
+
}
|
927 |
+
}
|
connectors/class-wp-stream-connector-acf.php
ADDED
@@ -0,0 +1,542 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_ACF extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'acf';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '4.3.8';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'added_post_meta',
|
26 |
+
'updated_post_meta',
|
27 |
+
'delete_post_meta',
|
28 |
+
'added_user_meta',
|
29 |
+
'updated_user_meta',
|
30 |
+
'delete_user_meta',
|
31 |
+
'added_option',
|
32 |
+
'updated_option',
|
33 |
+
'deleted_option',
|
34 |
+
'pre_post_update',
|
35 |
+
);
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Cached location rules, used in shutdown callback to verify changes in meta
|
39 |
+
*
|
40 |
+
* @var array
|
41 |
+
*/
|
42 |
+
public static $cached_location_rules = array();
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Cached field values updates, used by shutdown callback to verify actual changes
|
46 |
+
*
|
47 |
+
* @var array
|
48 |
+
*/
|
49 |
+
public static $cached_field_values_updates = array();
|
50 |
+
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
54 |
+
*
|
55 |
+
* @return bool
|
56 |
+
*/
|
57 |
+
public static function is_dependency_satisfied() {
|
58 |
+
if ( class_exists( 'acf' ) && version_compare( acf()->settings['version'], self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
59 |
+
return true;
|
60 |
+
}
|
61 |
+
|
62 |
+
return false;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Return translated connector label
|
67 |
+
*
|
68 |
+
* @return string Translated connector label
|
69 |
+
*/
|
70 |
+
public static function get_label() {
|
71 |
+
return _x( 'ACF', 'acf', 'stream' );
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Return translated action labels
|
76 |
+
*
|
77 |
+
* @return array Action label translations
|
78 |
+
*/
|
79 |
+
public static function get_action_labels() {
|
80 |
+
return array(
|
81 |
+
'created' => __( 'Created', 'acf', 'stream' ),
|
82 |
+
'updated' => __( 'Updated', 'acf', 'stream' ),
|
83 |
+
'added' => __( 'Added', 'acf', 'stream' ),
|
84 |
+
'deleted' => __( 'Deleted', 'acf', 'stream' ),
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Return translated context labels
|
90 |
+
*
|
91 |
+
* @return array Context label translations
|
92 |
+
*/
|
93 |
+
public static function get_context_labels() {
|
94 |
+
return array(
|
95 |
+
'field_groups' => _x( 'Field Groups', 'acf', 'stream' ),
|
96 |
+
'fields' => _x( 'Fields', 'acf', 'stream' ),
|
97 |
+
'rules' => _x( 'Rules', 'acf', 'stream' ),
|
98 |
+
'options' => _x( 'Options', 'acf', 'stream' ),
|
99 |
+
'values' => _x( 'Values', 'acf', 'stream' ),
|
100 |
+
);
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Register the connector
|
105 |
+
*
|
106 |
+
* @return void
|
107 |
+
*/
|
108 |
+
public static function register() {
|
109 |
+
add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Allow devs to disable logging values of rendered forms
|
113 |
+
*
|
114 |
+
* @return bool
|
115 |
+
*/
|
116 |
+
if ( apply_filters( 'wp_stream_acf_enable_value_logging', true ) ) {
|
117 |
+
self::$actions[] = 'acf/update_value';
|
118 |
+
}
|
119 |
+
|
120 |
+
parent::register();
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Add action links to Stream drop row in admin list screen
|
125 |
+
*
|
126 |
+
* @filter wp_stream_action_links_{connector}
|
127 |
+
*
|
128 |
+
* @param array $links Previous links registered
|
129 |
+
* @param object $record Stream record
|
130 |
+
*
|
131 |
+
* @return array Action links
|
132 |
+
*/
|
133 |
+
public static function action_links( $links, $record ) {
|
134 |
+
$links = WP_Stream_Connector_Posts::action_links( $links, $record );
|
135 |
+
|
136 |
+
return $links;
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Track addition of post meta
|
141 |
+
*
|
142 |
+
* @action added_post_meta
|
143 |
+
*/
|
144 |
+
public static function callback_added_post_meta() {
|
145 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'added' ), func_get_args() ) );
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Track updating post meta
|
150 |
+
*
|
151 |
+
* @action updated_post_meta
|
152 |
+
*/
|
153 |
+
public static function callback_updated_post_meta() {
|
154 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'updated' ), func_get_args() ) );
|
155 |
+
}
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Track deletion of post meta
|
159 |
+
*
|
160 |
+
* Note: Using delete_post_meta instead of deleted_post_meta to be able to
|
161 |
+
* capture old field value
|
162 |
+
*
|
163 |
+
* @action delete_post_meta
|
164 |
+
*/
|
165 |
+
public static function callback_delete_post_meta() {
|
166 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'post', 'deleted' ), func_get_args() ) );
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Track addition of user meta
|
171 |
+
*
|
172 |
+
* @action added_user_meta
|
173 |
+
*/
|
174 |
+
public static function callback_added_user_meta() {
|
175 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'added' ), func_get_args() ) );
|
176 |
+
}
|
177 |
+
|
178 |
+
/**
|
179 |
+
* Track updating user meta
|
180 |
+
*
|
181 |
+
* @action updated_user_meta
|
182 |
+
*/
|
183 |
+
public static function callback_updated_user_meta() {
|
184 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'updated' ), func_get_args() ) );
|
185 |
+
}
|
186 |
+
|
187 |
+
/**
|
188 |
+
* Track deletion of user meta
|
189 |
+
*
|
190 |
+
* Note: Using delete_user_meta instead of deleted_user_meta to be able to
|
191 |
+
* capture old field value
|
192 |
+
*
|
193 |
+
* @action delete_user_meta
|
194 |
+
*/
|
195 |
+
public static function callback_delete_user_meta() {
|
196 |
+
call_user_func_array( array( __CLASS__, 'check_meta' ), array_merge( array( 'user', 'deleted' ), func_get_args() ) );
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Track addition of post/user meta
|
201 |
+
*
|
202 |
+
* @param string $type Type of object, post or user
|
203 |
+
* @param string $action Added, updated, deleted
|
204 |
+
* @param integer $meta_id
|
205 |
+
* @param integer $object_id
|
206 |
+
* @param string $meta_key
|
207 |
+
* @param mixed|null $meta_value
|
208 |
+
*/
|
209 |
+
public static function check_meta( $type, $action, $meta_id, $object_id, $meta_key, $meta_value = null ) {
|
210 |
+
if ( 'post' !== $type || ! ( $post = get_post( $object_id ) ) || 'acf' !== $post->post_type ) {
|
211 |
+
self::check_meta_values( $type, $action, $meta_id, $object_id, $meta_key, $meta_value = null );
|
212 |
+
return;
|
213 |
+
}
|
214 |
+
|
215 |
+
$action_labels = self::get_action_labels();
|
216 |
+
|
217 |
+
// Fields
|
218 |
+
if ( 0 === strpos( $meta_key, 'field_' ) ) {
|
219 |
+
if ( 'deleted' === $action ) {
|
220 |
+
$meta_value = get_post_meta( $object_id, $meta_key, true );
|
221 |
+
}
|
222 |
+
|
223 |
+
self::log(
|
224 |
+
_x( '"%1$s" field in "%2$s" %3$s', 'acf', 'stream' ),
|
225 |
+
array(
|
226 |
+
'label' => $meta_value['label'],
|
227 |
+
'title' => $post->post_title,
|
228 |
+
'action' => strtolower( $action_labels[ $action ] ),
|
229 |
+
'key' => $meta_value['key'],
|
230 |
+
'name' => $meta_value['name'],
|
231 |
+
),
|
232 |
+
$object_id,
|
233 |
+
'fields',
|
234 |
+
$action
|
235 |
+
);
|
236 |
+
}
|
237 |
+
// Location rules
|
238 |
+
elseif ( 'rule' === $meta_key ) {
|
239 |
+
if ( 'deleted' === $action ) {
|
240 |
+
self::$cached_location_rules[ $object_id ] = get_post_meta( $object_id, 'rule' );
|
241 |
+
|
242 |
+
add_action( 'shutdown', array( __CLASS__, 'check_location_rules' ), 9 );
|
243 |
+
}
|
244 |
+
}
|
245 |
+
// Position option
|
246 |
+
elseif ( 'position' === $meta_key ) {
|
247 |
+
if ( 'deleted' === $action ) {
|
248 |
+
return;
|
249 |
+
}
|
250 |
+
|
251 |
+
$options = array(
|
252 |
+
'acf_after_title' => _x( 'High (after title)', 'acf', 'stream' ),
|
253 |
+
'normal' => _x( 'Normal (after content)', 'acf', 'stream' ),
|
254 |
+
'side' => _x( 'Side', 'acf', 'stream' ),
|
255 |
+
);
|
256 |
+
|
257 |
+
self::log(
|
258 |
+
_x( 'Position of "%1$s" updated to "%2$s"', 'acf', 'stream' ),
|
259 |
+
array(
|
260 |
+
'title' => $post->post_title,
|
261 |
+
'option_label' => $options[ $meta_value ],
|
262 |
+
'option' => $meta_key,
|
263 |
+
'option_value' => $meta_value,
|
264 |
+
),
|
265 |
+
$object_id,
|
266 |
+
'options',
|
267 |
+
'updated'
|
268 |
+
);
|
269 |
+
}
|
270 |
+
// Layout option
|
271 |
+
elseif ( 'layout' === $meta_key ) {
|
272 |
+
if ( 'deleted' === $action ) {
|
273 |
+
return;
|
274 |
+
}
|
275 |
+
|
276 |
+
$options = array(
|
277 |
+
'no_box' => _x( 'Seamless (no metabox)', 'acf', 'stream' ),
|
278 |
+
'default' => _x( 'Standard (WP metabox)', 'acf', 'stream' ),
|
279 |
+
);
|
280 |
+
|
281 |
+
self::log(
|
282 |
+
_x( 'Style of "%1$s" updated to "%2$s"', 'acf', 'stream' ),
|
283 |
+
array(
|
284 |
+
'title' => $post->post_title,
|
285 |
+
'option_label' => $options[ $meta_value ],
|
286 |
+
'option' => $meta_key,
|
287 |
+
'option_value' => $meta_value,
|
288 |
+
),
|
289 |
+
$object_id,
|
290 |
+
'options',
|
291 |
+
'updated'
|
292 |
+
);
|
293 |
+
}
|
294 |
+
// Screen exclusion option
|
295 |
+
elseif ( 'hide_on_screen' === $meta_key ) {
|
296 |
+
if ( 'deleted' === $action ) {
|
297 |
+
return;
|
298 |
+
}
|
299 |
+
|
300 |
+
$options = array(
|
301 |
+
'permalink' => _x( 'Permalink', 'acf', 'stream' ),
|
302 |
+
'the_content' => _x( 'Content Editor', 'acf', 'stream' ),
|
303 |
+
'excerpt' => _x( 'Excerpt', 'acf', 'stream' ),
|
304 |
+
'custom_fields' => _x( 'Custom Fields', 'acf', 'stream' ),
|
305 |
+
'discussion' => _x( 'Discussion', 'acf', 'stream' ),
|
306 |
+
'comments' => _x( 'Comments', 'acf', 'stream' ),
|
307 |
+
'revisions' => _x( 'Revisions', 'acf', 'stream' ),
|
308 |
+
'slug' => _x( 'Slug', 'acf', 'stream' ),
|
309 |
+
'author' => _x( 'Author', 'acf', 'stream' ),
|
310 |
+
'format' => _x( 'Format', 'acf', 'stream' ),
|
311 |
+
'featured_image' => _x( 'Featured Image', 'acf', 'stream' ),
|
312 |
+
'categories' => _x( 'Categories', 'acf', 'stream' ),
|
313 |
+
'tags' => _x( 'Tags', 'acf', 'stream' ),
|
314 |
+
'send-trackbacks' => _x( 'Send Trackbacks', 'acf', 'stream' ),
|
315 |
+
);
|
316 |
+
|
317 |
+
if ( count( $options ) === count( $meta_value ) ) {
|
318 |
+
$options_label = _x( 'All screens', 'acf', 'stream' );
|
319 |
+
} elseif ( empty( $meta_value ) ) {
|
320 |
+
$options_label = _x( 'No screens', 'acf', 'stream' );
|
321 |
+
} else {
|
322 |
+
$options_label = implode( ', ', array_intersect_key( $options, array_flip( $meta_value ) ) );
|
323 |
+
}
|
324 |
+
|
325 |
+
self::log(
|
326 |
+
_x( '"%1$s" set to display on "%2$s"', 'acf', 'stream' ),
|
327 |
+
array(
|
328 |
+
'title' => $post->post_title,
|
329 |
+
'option_label' => $options_label,
|
330 |
+
'option' => $meta_key,
|
331 |
+
'option_value' => $meta_value,
|
332 |
+
),
|
333 |
+
$object_id,
|
334 |
+
'options',
|
335 |
+
'updated'
|
336 |
+
);
|
337 |
+
}
|
338 |
+
}
|
339 |
+
|
340 |
+
/**
|
341 |
+
* Track changes to ACF values within rendered post meta forms
|
342 |
+
*
|
343 |
+
* @param string $type Type of object, post or user
|
344 |
+
* @param string $action Added, updated, deleted
|
345 |
+
* @param integer $meta_id
|
346 |
+
* @param integer $object_id
|
347 |
+
* @param string $key
|
348 |
+
* @param mixed|null $value
|
349 |
+
*
|
350 |
+
* @return bool
|
351 |
+
*/
|
352 |
+
public static function check_meta_values( $type, $action, $meta_id, $object_id, $key, $value = null ) {
|
353 |
+
if ( empty( self::$cached_field_values_updates ) ) {
|
354 |
+
return false;
|
355 |
+
}
|
356 |
+
|
357 |
+
$object_key = $object_id;
|
358 |
+
|
359 |
+
if ( 'user' === $type ) {
|
360 |
+
$object_key = 'user_' . $object_id;
|
361 |
+
} elseif ( 'taxonomy' === $type ) {
|
362 |
+
if ( 0 === strpos( $key, '_' ) ) { // Ignore the 'revision' stuff!
|
363 |
+
return false;
|
364 |
+
}
|
365 |
+
|
366 |
+
if ( 1 !== preg_match( '#([a-z0-9_-]+)_([\d]+)_([a-z0-9_-]+)#', $key, $matches ) ) {
|
367 |
+
return false;
|
368 |
+
}
|
369 |
+
|
370 |
+
list( , $taxonomy, $term_id, $key ) = $matches; // Skips 0 index
|
371 |
+
|
372 |
+
$object_key = $taxonomy . '_' . $term_id;
|
373 |
+
}
|
374 |
+
|
375 |
+
if ( isset( self::$cached_field_values_updates[ $object_key ][ $key ] ) ) {
|
376 |
+
if ( 'post' === $type ) {
|
377 |
+
$post = get_post( $object_id );
|
378 |
+
$title = $post->post_title;
|
379 |
+
$type_name = strtolower( WP_Stream_Connector_Posts::get_post_type_name( $post->post_type ) );
|
380 |
+
} elseif ( 'user' === $type ) {
|
381 |
+
$user = new WP_User( $object_id );
|
382 |
+
$title = $user->get( 'display_name' );
|
383 |
+
$type_name = __( 'user', 'stream' );
|
384 |
+
} elseif ( 'taxonomy' === $type ) {
|
385 |
+
$term = get_term( $term_id, $taxonomy );
|
386 |
+
$title = $term->name;
|
387 |
+
$tax_obj = get_taxonomy( $taxonomy );
|
388 |
+
$type_name = strtolower( get_taxonomy_labels( $tax_obj )->singular_name );
|
389 |
+
} else {
|
390 |
+
return false;
|
391 |
+
}
|
392 |
+
|
393 |
+
$cache = self::$cached_field_values_updates[ $object_key ][ $key ];
|
394 |
+
|
395 |
+
self::log(
|
396 |
+
_x( '"%1$s" of "%2$s" %3$s updated', 'acf', 'stream' ),
|
397 |
+
array(
|
398 |
+
'field_label' => $cache['field']['label'],
|
399 |
+
'title' => $title,
|
400 |
+
'singular_name' => $type_name,
|
401 |
+
'meta_value' => $value,
|
402 |
+
'meta_key' => $key,
|
403 |
+
'meta_type' => $type,
|
404 |
+
),
|
405 |
+
$object_id,
|
406 |
+
'values',
|
407 |
+
'updated'
|
408 |
+
);
|
409 |
+
}
|
410 |
+
|
411 |
+
return true;
|
412 |
+
}
|
413 |
+
|
414 |
+
/**
|
415 |
+
* Track changes to rules, complements post-meta updates
|
416 |
+
*
|
417 |
+
* @action shutdown
|
418 |
+
*/
|
419 |
+
public static function check_location_rules() {
|
420 |
+
foreach ( self::$cached_location_rules as $post_id => $old ) {
|
421 |
+
$new = get_post_meta( $post_id, 'rule' );
|
422 |
+
$post = get_post( $post_id );
|
423 |
+
|
424 |
+
if ( $old === $new ) {
|
425 |
+
continue;
|
426 |
+
}
|
427 |
+
|
428 |
+
$new = array_map( 'json_encode', $new );
|
429 |
+
$old = array_map( 'json_encode', $old );
|
430 |
+
$added = array_diff( $new, $old );
|
431 |
+
$deleted = array_diff( $old, $new );
|
432 |
+
|
433 |
+
self::log(
|
434 |
+
_x( 'Updated rules of "%1$s" (%2$d added, %3$d deleted)', 'acf', 'stream' ),
|
435 |
+
array(
|
436 |
+
'title' => $post->post_title,
|
437 |
+
'no_added' => count( $added ),
|
438 |
+
'no_deleted' => count( $deleted ),
|
439 |
+
'added' => $added,
|
440 |
+
'deleted' => $deleted,
|
441 |
+
),
|
442 |
+
$post_id,
|
443 |
+
'rules',
|
444 |
+
'updated'
|
445 |
+
);
|
446 |
+
}
|
447 |
+
}
|
448 |
+
|
449 |
+
/**
|
450 |
+
* Override connector log for our own Settings / Actions
|
451 |
+
*
|
452 |
+
* @param array $data
|
453 |
+
*
|
454 |
+
* @return array|bool
|
455 |
+
*/
|
456 |
+
public static function log_override( $data ) {
|
457 |
+
if ( ! is_array( $data ) ) {
|
458 |
+
return $data;
|
459 |
+
}
|
460 |
+
|
461 |
+
if ( 'posts' === $data['connector'] && 'acf' === $data['context'] ) {
|
462 |
+
$data['context'] = 'field_groups';
|
463 |
+
$data['connector'] = self::$name;
|
464 |
+
$data['args']['singular_name'] = __( 'field group', 'stream' );
|
465 |
+
}
|
466 |
+
|
467 |
+
return $data;
|
468 |
+
}
|
469 |
+
|
470 |
+
/**
|
471 |
+
* Track changes to custom field values updates, saves filtered values to be
|
472 |
+
* processed by callback_updated_post_meta
|
473 |
+
*
|
474 |
+
* @param $value
|
475 |
+
* @param $post_id
|
476 |
+
* @param $field
|
477 |
+
*/
|
478 |
+
public static function callback_acf_update_value( $value, $post_id, $field ) {
|
479 |
+
self::$cached_field_values_updates[ $post_id ][ $field['name'] ] = compact( 'field', 'value', 'post_id' );
|
480 |
+
return $value;
|
481 |
+
}
|
482 |
+
|
483 |
+
/**
|
484 |
+
* Track changes to post main attributes, ie: Order No.
|
485 |
+
*
|
486 |
+
* @param $post_id
|
487 |
+
* @param $data Array with the updated post data
|
488 |
+
*/
|
489 |
+
public static function callback_pre_post_update( $post_id, $data ) {
|
490 |
+
$post = get_post( $post_id );
|
491 |
+
|
492 |
+
if ( 'acf' !== $post->post_type ) {
|
493 |
+
return;
|
494 |
+
}
|
495 |
+
|
496 |
+
// menu_order, aka Order No.
|
497 |
+
if ( $data['menu_order'] !== $post->menu_order ) {
|
498 |
+
self::log(
|
499 |
+
_x( 'Updated Order of "%1$s" from %2$d to %3$d', 'acf', 'stream' ),
|
500 |
+
array(
|
501 |
+
'title' => $post->post_title,
|
502 |
+
'old_menu_order' => $post->menu_order,
|
503 |
+
'menu_order' => $data['menu_order'],
|
504 |
+
),
|
505 |
+
$post_id,
|
506 |
+
'field_groups',
|
507 |
+
'updated'
|
508 |
+
);
|
509 |
+
}
|
510 |
+
}
|
511 |
+
|
512 |
+
/**
|
513 |
+
* Track addition of new options
|
514 |
+
*
|
515 |
+
* @param $key Option name
|
516 |
+
* @param $value Option value
|
517 |
+
*/
|
518 |
+
public static function callback_added_option( $key, $value ) {
|
519 |
+
self::check_meta_values( 'taxonomy', 'added', null, null, $key, $value );
|
520 |
+
}
|
521 |
+
|
522 |
+
/**
|
523 |
+
* Track addition of new options
|
524 |
+
*
|
525 |
+
* @param $key
|
526 |
+
* @param $old
|
527 |
+
* @param $value
|
528 |
+
*/
|
529 |
+
public static function callback_updated_option( $key, $old, $value ) {
|
530 |
+
self::check_meta_values( 'taxonomy', 'updated', null, null, $key, $value );
|
531 |
+
}
|
532 |
+
|
533 |
+
/**
|
534 |
+
* Track addition of new options
|
535 |
+
*
|
536 |
+
* @param $key
|
537 |
+
*/
|
538 |
+
public static function callback_deleted_option( $key ) {
|
539 |
+
self::check_meta_values( 'taxonomy', 'deleted', null, null, $key, null );
|
540 |
+
}
|
541 |
+
|
542 |
+
}
|
connectors/class-wp-stream-connector-bbpress.php
ADDED
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_bbPress extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'bbpress';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '2.5.4';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'bbp_toggle_topic_admin',
|
26 |
+
);
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Tracked option keys
|
30 |
+
*
|
31 |
+
* @var array
|
32 |
+
*/
|
33 |
+
public static $options = array(
|
34 |
+
'bbpress' => null,
|
35 |
+
);
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Flag to stop logging update logic twice
|
39 |
+
*
|
40 |
+
* @var bool
|
41 |
+
*/
|
42 |
+
public static $is_update = false;
|
43 |
+
|
44 |
+
/**
|
45 |
+
* @var bool
|
46 |
+
*/
|
47 |
+
public static $_deleted_activity = false;
|
48 |
+
|
49 |
+
/**
|
50 |
+
* @var array
|
51 |
+
*/
|
52 |
+
public static $_delete_activity_args = array();
|
53 |
+
|
54 |
+
/**
|
55 |
+
* @var bool
|
56 |
+
*/
|
57 |
+
public static $ignore_activity_bulk_deletion = false;
|
58 |
+
|
59 |
+
/**
|
60 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
61 |
+
*
|
62 |
+
* @return bool
|
63 |
+
*/
|
64 |
+
public static function is_dependency_satisfied() {
|
65 |
+
if ( class_exists( 'bbPress' ) && function_exists( 'bbp_get_version' ) && version_compare( bbp_get_version(), self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
66 |
+
return true;
|
67 |
+
}
|
68 |
+
|
69 |
+
return false;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Return translated connector label
|
74 |
+
*
|
75 |
+
* @return string Translated connector label
|
76 |
+
*/
|
77 |
+
public static function get_label() {
|
78 |
+
return _x( 'bbPress', 'bbpress', 'stream' );
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Return translated action labels
|
83 |
+
*
|
84 |
+
* @return array Action label translations
|
85 |
+
*/
|
86 |
+
public static function get_action_labels() {
|
87 |
+
return array(
|
88 |
+
'created' => _x( 'Created', 'bbpress', 'stream' ),
|
89 |
+
'updated' => _x( 'Updated', 'bbpress', 'stream' ),
|
90 |
+
'activated' => _x( 'Activated', 'bbpress', 'stream' ),
|
91 |
+
'deactivated' => _x( 'Deactivated', 'bbpress', 'stream' ),
|
92 |
+
'deleted' => _x( 'Deleted', 'bbpress', 'stream' ),
|
93 |
+
'trashed' => _x( 'Trashed', 'bbpress', 'stream' ),
|
94 |
+
'untrashed' => _x( 'Restored', 'bbpress', 'stream' ),
|
95 |
+
'generated' => _x( 'Generated', 'bbpress', 'stream' ),
|
96 |
+
'imported' => _x( 'Imported', 'bbpress', 'stream' ),
|
97 |
+
'exported' => _x( 'Exported', 'bbpress', 'stream' ),
|
98 |
+
'closed' => _x( 'Closed', 'bbpress', 'stream' ),
|
99 |
+
'opened' => _x( 'Opened', 'bbpress', 'stream' ),
|
100 |
+
'sticked' => _x( 'Sticked', 'bbpress', 'stream' ),
|
101 |
+
'unsticked' => _x( 'Unsticked', 'bbpress', 'stream' ),
|
102 |
+
'spammed' => _x( 'Marked as spam', 'bbpress', 'stream' ),
|
103 |
+
'unspammed' => _x( 'Unmarked as spam', 'bbpress', 'stream' ),
|
104 |
+
);
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Return translated context labels
|
109 |
+
*
|
110 |
+
* @return array Context label translations
|
111 |
+
*/
|
112 |
+
public static function get_context_labels() {
|
113 |
+
return array(
|
114 |
+
'settings' => _x( 'Settings', 'bbpress', 'stream' ),
|
115 |
+
);
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Add action links to Stream drop row in admin list screen
|
120 |
+
*
|
121 |
+
* @filter wp_stream_action_links_{connector}
|
122 |
+
*
|
123 |
+
* @param array $links Previous links registered
|
124 |
+
* @param object $record Stream record
|
125 |
+
*
|
126 |
+
* @return array Action links
|
127 |
+
*/
|
128 |
+
public static function action_links( $links, $record ) {
|
129 |
+
if ( 'settings' === $record->context ) {
|
130 |
+
$option = wp_stream_get_meta( $record, 'option', true );
|
131 |
+
$links[ __( 'Edit', 'stream' ) ] = esc_url( add_query_arg(
|
132 |
+
array(
|
133 |
+
'page' => 'bbpress',
|
134 |
+
),
|
135 |
+
admin_url( 'options-general.php' )
|
136 |
+
) . esc_url_raw( '#' . $option ) );
|
137 |
+
}
|
138 |
+
return $links;
|
139 |
+
}
|
140 |
+
|
141 |
+
public static function register() {
|
142 |
+
parent::register();
|
143 |
+
|
144 |
+
add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Override connector log for our own Settings / Actions
|
149 |
+
*
|
150 |
+
* @param array $data
|
151 |
+
*
|
152 |
+
* @return array|bool
|
153 |
+
*/
|
154 |
+
public static function log_override( $data ) {
|
155 |
+
if ( ! is_array( $data ) ) {
|
156 |
+
return $data;
|
157 |
+
}
|
158 |
+
|
159 |
+
if ( 'settings' === $data['connector'] && 'bbpress' === $data['args']['context'] ) {
|
160 |
+
$settings = bbp_admin_get_settings_fields();
|
161 |
+
|
162 |
+
/* fix for missing title for this single field */
|
163 |
+
$settings['bbp_settings_features']['_bbp_allow_threaded_replies']['title'] = __( 'Reply Threading', 'stream' );
|
164 |
+
|
165 |
+
$option = $data['args']['option'];
|
166 |
+
foreach ( $settings as $section => $fields ) {
|
167 |
+
if ( isset( $fields[ $option ] ) ) {
|
168 |
+
$field = $fields[ $option ];
|
169 |
+
break;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
|
173 |
+
if ( ! isset( $field ) ) {
|
174 |
+
return $data;
|
175 |
+
}
|
176 |
+
|
177 |
+
$data['args']['label'] = $field['title'];
|
178 |
+
$data['connector'] = self::$name;
|
179 |
+
$data['context'] = 'settings';
|
180 |
+
$data['action'] = 'updated';
|
181 |
+
}
|
182 |
+
elseif ( 'posts' === $data['connector'] && in_array( $data['context'], array( 'forum', 'topic', 'reply' ) ) ) {
|
183 |
+
if ( 'reply' === $data['context'] ) {
|
184 |
+
if ( 'updated' === $data['action'] ) {
|
185 |
+
$data['message'] = __( 'Replied on "%1$s"', 'stream' );
|
186 |
+
$data['args']['post_title'] = get_post( wp_get_post_parent_id( $data['object_id'] ) )->post_title;
|
187 |
+
}
|
188 |
+
$data['args']['post_title'] = sprintf(
|
189 |
+
__( 'Reply to: %s', 'stream' ),
|
190 |
+
get_post( wp_get_post_parent_id( $data['object_id'] ) )->post_title
|
191 |
+
);
|
192 |
+
}
|
193 |
+
|
194 |
+
$data['connector'] = self::$name;
|
195 |
+
}
|
196 |
+
elseif ( 'taxonomies' === $data['connector'] && in_array( $data['context'], array( 'topic-tag' ) ) ) {
|
197 |
+
$data['connector'] = self::$name;
|
198 |
+
}
|
199 |
+
|
200 |
+
return $data;
|
201 |
+
}
|
202 |
+
|
203 |
+
public static function callback_bbp_toggle_topic_admin( $success, $post_data, $action, $message ) {
|
204 |
+
|
205 |
+
if ( ! empty( $message['failed'] ) ) {
|
206 |
+
return;
|
207 |
+
}
|
208 |
+
|
209 |
+
$action = $message['bbp_topic_toggle_notice'];
|
210 |
+
$actions = self::get_action_labels();
|
211 |
+
|
212 |
+
if ( ! isset( $actions[ $action ] ) ) {
|
213 |
+
return;
|
214 |
+
}
|
215 |
+
|
216 |
+
$label = $actions[ $action ];
|
217 |
+
$topic = get_post( $message['topic_id'] );
|
218 |
+
|
219 |
+
self::log(
|
220 |
+
_x( '%1$s "%2$s" topic', '1: Action, 2: Topic title', 'stream' ),
|
221 |
+
array(
|
222 |
+
'action_title' => $actions[ $action ],
|
223 |
+
'topic_title' => $topic->post_title,
|
224 |
+
'action' => $action,
|
225 |
+
),
|
226 |
+
$topic->ID,
|
227 |
+
'topic',
|
228 |
+
$action
|
229 |
+
);
|
230 |
+
}
|
231 |
+
|
232 |
+
}
|
connectors/class-wp-stream-connector-buddypress.php
ADDED
@@ -0,0 +1,814 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_BuddyPress extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'buddypress';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '2.0.1';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'update_option',
|
26 |
+
'add_option',
|
27 |
+
'delete_option',
|
28 |
+
'update_site_option',
|
29 |
+
'add_site_option',
|
30 |
+
'delete_site_option',
|
31 |
+
|
32 |
+
'bp_before_activity_delete',
|
33 |
+
'bp_activity_deleted_activities',
|
34 |
+
|
35 |
+
'bp_activity_mark_as_spam',
|
36 |
+
'bp_activity_mark_as_ham',
|
37 |
+
'bp_activity_admin_edit_after',
|
38 |
+
|
39 |
+
'groups_create_group',
|
40 |
+
'groups_update_group',
|
41 |
+
'groups_before_delete_group',
|
42 |
+
'groups_details_updated',
|
43 |
+
'groups_settings_updated',
|
44 |
+
|
45 |
+
'groups_leave_group',
|
46 |
+
'groups_join_group',
|
47 |
+
|
48 |
+
'groups_promote_member',
|
49 |
+
'groups_demote_member',
|
50 |
+
'groups_ban_member',
|
51 |
+
'groups_unban_member',
|
52 |
+
'groups_remove_member',
|
53 |
+
|
54 |
+
'xprofile_field_after_save',
|
55 |
+
'xprofile_fields_deleted_field',
|
56 |
+
|
57 |
+
'xprofile_group_after_save',
|
58 |
+
'xprofile_groups_deleted_group',
|
59 |
+
);
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Tracked option keys
|
63 |
+
*
|
64 |
+
* @var array
|
65 |
+
*/
|
66 |
+
public static $options = array(
|
67 |
+
'bp-active-components' => null,
|
68 |
+
'bp-pages' => null,
|
69 |
+
'buddypress' => null,
|
70 |
+
);
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Flag to stop logging update logic twice
|
74 |
+
*
|
75 |
+
* @var bool
|
76 |
+
*/
|
77 |
+
public static $is_update = false;
|
78 |
+
|
79 |
+
/**
|
80 |
+
* @var bool
|
81 |
+
*/
|
82 |
+
public static $_deleted_activity = false;
|
83 |
+
|
84 |
+
/**
|
85 |
+
* @var array
|
86 |
+
*/
|
87 |
+
public static $_delete_activity_args = array();
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @var bool
|
91 |
+
*/
|
92 |
+
public static $ignore_activity_bulk_deletion = false;
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
96 |
+
*
|
97 |
+
* @return bool
|
98 |
+
*/
|
99 |
+
public static function is_dependency_satisfied() {
|
100 |
+
if ( class_exists( 'BuddyPress' ) && version_compare( BuddyPress::instance()->version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
101 |
+
return true;
|
102 |
+
}
|
103 |
+
|
104 |
+
return false;
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Return translated connector label
|
109 |
+
*
|
110 |
+
* @return string Translated connector label
|
111 |
+
*/
|
112 |
+
public static function get_label() {
|
113 |
+
return _x( 'BuddyPress', 'buddypress', 'stream' );
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Return translated action labels
|
118 |
+
*
|
119 |
+
* @return array Action label translations
|
120 |
+
*/
|
121 |
+
public static function get_action_labels() {
|
122 |
+
return array(
|
123 |
+
'created' => _x( 'Created', 'buddypress', 'stream' ),
|
124 |
+
'updated' => _x( 'Updated', 'buddypress', 'stream' ),
|
125 |
+
'activated' => _x( 'Activated', 'buddypress', 'stream' ),
|
126 |
+
'deactivated' => _x( 'Deactivated', 'buddypress', 'stream' ),
|
127 |
+
'deleted' => _x( 'Deleted', 'buddypress', 'stream' ),
|
128 |
+
'spammed' => _x( 'Marked as spam', 'buddypress', 'stream' ),
|
129 |
+
'unspammed' => _x( 'Unmarked as spam', 'buddypress', 'stream' ),
|
130 |
+
'promoted' => _x( 'Promoted', 'buddypress', 'stream' ),
|
131 |
+
'demoted' => _x( 'Demoted', 'buddypress', 'stream' ),
|
132 |
+
);
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Return translated context labels
|
137 |
+
*
|
138 |
+
* @return array Context label translations
|
139 |
+
*/
|
140 |
+
public static function get_context_labels() {
|
141 |
+
return array(
|
142 |
+
'components' => _x( 'Components', 'buddypress', 'stream' ),
|
143 |
+
'groups' => _x( 'Groups', 'buddypress', 'stream' ),
|
144 |
+
'activity' => _x( 'Activity', 'buddypress', 'stream' ),
|
145 |
+
'profile_fields' => _x( 'Profile fields', 'buddypress', 'stream' ),
|
146 |
+
);
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* Add action links to Stream drop row in admin list screen
|
151 |
+
*
|
152 |
+
* @filter wp_stream_action_links_{connector}
|
153 |
+
*
|
154 |
+
* @param array $links Previous links registered
|
155 |
+
* @param object $record Stream record
|
156 |
+
*
|
157 |
+
* @return array Action links
|
158 |
+
*/
|
159 |
+
public static function action_links( $links, $record ) {
|
160 |
+
if ( in_array( $record->context, array( 'components' ) ) ) {
|
161 |
+
$option_key = wp_stream_get_meta( $record, 'option_key', true );
|
162 |
+
|
163 |
+
if ( 'bp-active-components' === $option_key ) {
|
164 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
165 |
+
array(
|
166 |
+
'page' => 'bp-components',
|
167 |
+
),
|
168 |
+
admin_url( 'admin.php' )
|
169 |
+
);
|
170 |
+
} elseif ( 'bp-pages' === $option_key ) {
|
171 |
+
$page_id = wp_stream_get_meta( $record, 'page_id', true );
|
172 |
+
|
173 |
+
$links[ __( 'Edit setting', 'stream' ) ] = add_query_arg(
|
174 |
+
array(
|
175 |
+
'page' => 'bp-page-settings',
|
176 |
+
),
|
177 |
+
admin_url( 'admin.php' )
|
178 |
+
);
|
179 |
+
|
180 |
+
if ( $page_id ) {
|
181 |
+
$links[ __( 'Edit Page', 'stream' ) ] = get_edit_post_link( $page_id );
|
182 |
+
$links[ __( 'View', 'stream' ) ] = get_permalink( $page_id );
|
183 |
+
}
|
184 |
+
}
|
185 |
+
} elseif ( in_array( $record->context, array( 'settings' ) ) ) {
|
186 |
+
$links[ __( 'Edit setting', 'stream' ) ] = add_query_arg(
|
187 |
+
array(
|
188 |
+
'page' => wp_stream_get_meta( $record, 'page', true ),
|
189 |
+
),
|
190 |
+
admin_url( 'admin.php' )
|
191 |
+
);
|
192 |
+
} elseif ( in_array( $record->context, array( 'groups' ) ) ) {
|
193 |
+
$group_id = wp_stream_get_meta( $record, 'id', true );
|
194 |
+
$group = groups_get_group( array( 'group_id' => $group_id ) );
|
195 |
+
|
196 |
+
if ( $group ) {
|
197 |
+
// Build actions URLs
|
198 |
+
$base_url = bp_get_admin_url( 'admin.php?page=bp-groups&gid=' . $group_id );
|
199 |
+
$delete_url = wp_nonce_url( $base_url . '&action=delete', 'bp-groups-delete' );
|
200 |
+
$edit_url = $base_url . '&action=edit';
|
201 |
+
$visit_url = bp_get_group_permalink( $group );
|
202 |
+
|
203 |
+
$links[ __( 'Edit group', 'stream' ) ] = $edit_url;
|
204 |
+
$links[ __( 'View group', 'stream' ) ] = $visit_url;
|
205 |
+
$links[ __( 'Delete group', 'stream' ) ] = $delete_url;
|
206 |
+
}
|
207 |
+
} elseif ( in_array( $record->context, array( 'activity' ) ) ) {
|
208 |
+
$activity_id = wp_stream_get_meta( $record, 'id', true );
|
209 |
+
$activities = bp_activity_get( array( 'in' => $activity_id, 'spam' => 'all' ) );
|
210 |
+
if ( ! empty( $activities['activities'] ) ) {
|
211 |
+
$activity = reset( $activities['activities'] );
|
212 |
+
|
213 |
+
$base_url = bp_get_admin_url( 'admin.php?page=bp-activity&aid=' . $activity->id );
|
214 |
+
$spam_nonce = esc_html( '_wpnonce=' . wp_create_nonce( 'spam-activity_' . $activity->id ) );
|
215 |
+
$delete_url = $base_url . "&action=delete&$spam_nonce";
|
216 |
+
$edit_url = $base_url . '&action=edit';
|
217 |
+
$ham_url = $base_url . "&action=ham&$spam_nonce";
|
218 |
+
$spam_url = $base_url . "&action=spam&$spam_nonce";
|
219 |
+
|
220 |
+
if ( $activity->is_spam ) {
|
221 |
+
$links[ __( 'Ham', 'stream' ) ] = $ham_url;
|
222 |
+
} else {
|
223 |
+
$links[ __( 'Edit', 'stream' ) ] = $edit_url;
|
224 |
+
$links[ __( 'Spam', 'stream' ) ] = $spam_url;
|
225 |
+
}
|
226 |
+
$links[ __( 'Delete', 'stream' ) ] = $delete_url;
|
227 |
+
}
|
228 |
+
} elseif ( in_array( $record->context, array( 'profile_fields' ) ) ) {
|
229 |
+
$field_id = wp_stream_get_meta( $record, 'field_id', true );
|
230 |
+
$group_id = wp_stream_get_meta( $record, 'group_id', true );
|
231 |
+
|
232 |
+
if ( empty( $field_id ) ) { // is a group action
|
233 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
234 |
+
array(
|
235 |
+
'page' => 'bp-profile-setup',
|
236 |
+
'mode' => 'edit_group',
|
237 |
+
'group_id' => $group_id,
|
238 |
+
),
|
239 |
+
admin_url( 'users.php' )
|
240 |
+
);
|
241 |
+
$links[ __( 'Delete', 'stream' ) ] = add_query_arg(
|
242 |
+
array(
|
243 |
+
'page' => 'bp-profile-setup',
|
244 |
+
'mode' => 'delete_group',
|
245 |
+
'group_id' => $group_id,
|
246 |
+
),
|
247 |
+
admin_url( 'users.php' )
|
248 |
+
);
|
249 |
+
} else {
|
250 |
+
$field = new BP_XProfile_Field( $field_id );
|
251 |
+
if ( empty( $field->type ) ) {
|
252 |
+
return $links;
|
253 |
+
}
|
254 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
255 |
+
array(
|
256 |
+
'page' => 'bp-profile-setup',
|
257 |
+
'mode' => 'edit_field',
|
258 |
+
'group_id' => $group_id,
|
259 |
+
'field_id' => $field_id,
|
260 |
+
),
|
261 |
+
admin_url( 'users.php' )
|
262 |
+
);
|
263 |
+
$links[ __( 'Delete', 'stream' ) ] = add_query_arg(
|
264 |
+
array(
|
265 |
+
'page' => 'bp-profile-setup',
|
266 |
+
'mode' => 'delete_field',
|
267 |
+
'field_id' => $field_id,
|
268 |
+
),
|
269 |
+
admin_url( 'users.php' )
|
270 |
+
);
|
271 |
+
}
|
272 |
+
}
|
273 |
+
|
274 |
+
return $links;
|
275 |
+
}
|
276 |
+
|
277 |
+
public static function register() {
|
278 |
+
parent::register();
|
279 |
+
|
280 |
+
self::$options = array_merge(
|
281 |
+
self::$options,
|
282 |
+
array(
|
283 |
+
'hide-loggedout-adminbar' => array(
|
284 |
+
'label' => _x( 'Toolbar', 'buddypress', 'stream' ),
|
285 |
+
'page' => 'bp-settings',
|
286 |
+
),
|
287 |
+
'_bp_force_buddybar' => array(
|
288 |
+
'label' => _x( 'Toolbar', 'buddypress', 'stream' ),
|
289 |
+
'page' => 'bp-settings',
|
290 |
+
),
|
291 |
+
'bp-disable-account-deletion' => array(
|
292 |
+
'label' => _x( 'Account Deletion', 'buddypress', 'stream' ),
|
293 |
+
'page' => 'bp-settings',
|
294 |
+
),
|
295 |
+
'bp-disable-profile-sync' => array(
|
296 |
+
'label' => _x( 'Profile Syncing', 'buddypress', 'stream' ),
|
297 |
+
'page' => 'bp-settings',
|
298 |
+
),
|
299 |
+
'bp_restrict_group_creation' => array(
|
300 |
+
'label' => _x( 'Group Creation', 'buddypress', 'stream' ),
|
301 |
+
'page' => 'bp-settings',
|
302 |
+
),
|
303 |
+
'bb-config-location' => array(
|
304 |
+
'label' => _x( 'bbPress Configuration', 'buddypress', 'stream' ),
|
305 |
+
'page' => 'bp-settings',
|
306 |
+
),
|
307 |
+
'bp-disable-blogforum-comments' => array(
|
308 |
+
'label' => _x( 'Blog & Forum Comments', 'buddypress', 'stream' ),
|
309 |
+
'page' => 'bp-settings',
|
310 |
+
),
|
311 |
+
'_bp_enable_heartbeat_refresh' => array(
|
312 |
+
'label' => _x( 'Activity auto-refresh', 'buddypress', 'stream' ),
|
313 |
+
'page' => 'bp-settings',
|
314 |
+
),
|
315 |
+
'_bp_enable_akismet' => array(
|
316 |
+
'label' => _x( 'Akismet', 'buddypress', 'stream' ),
|
317 |
+
'page' => 'bp-settings',
|
318 |
+
),
|
319 |
+
'bp-disable-avatar-uploads' => array(
|
320 |
+
'label' => _x( 'Avatar Uploads', 'buddypress', 'stream' ),
|
321 |
+
'page' => 'bp-settings',
|
322 |
+
),
|
323 |
+
)
|
324 |
+
);
|
325 |
+
}
|
326 |
+
|
327 |
+
public static function callback_update_option( $option, $old, $new ) {
|
328 |
+
self::check( $option, $old, $new );
|
329 |
+
}
|
330 |
+
|
331 |
+
public static function callback_add_option( $option, $val ) {
|
332 |
+
self::check( $option, null, $val );
|
333 |
+
}
|
334 |
+
|
335 |
+
public static function callback_delete_option( $option ) {
|
336 |
+
self::check( $option, null, null );
|
337 |
+
}
|
338 |
+
|
339 |
+
public static function callback_update_site_option( $option, $old, $new ) {
|
340 |
+
self::check( $option, $old, $new );
|
341 |
+
}
|
342 |
+
|
343 |
+
public static function callback_add_site_option( $option, $val ) {
|
344 |
+
self::check( $option, null, $val );
|
345 |
+
}
|
346 |
+
|
347 |
+
public static function callback_delete_site_option( $option ) {
|
348 |
+
self::check( $option, null, null );
|
349 |
+
}
|
350 |
+
|
351 |
+
public static function check( $option, $old_value, $new_value ) {
|
352 |
+
if ( ! array_key_exists( $option, self::$options ) ) {
|
353 |
+
return;
|
354 |
+
}
|
355 |
+
|
356 |
+
$replacement = str_replace( '-', '_', $option );
|
357 |
+
|
358 |
+
if ( method_exists( __CLASS__, 'check_' . $replacement ) ) {
|
359 |
+
call_user_func( array( __CLASS__, 'check_' . $replacement ), $old_value, $new_value );
|
360 |
+
} else {
|
361 |
+
$data = self::$options[ $option ];
|
362 |
+
$option_title = $data['label'];
|
363 |
+
$context = isset( $data['context'] ) ? $data['context'] : 'settings';
|
364 |
+
$page = isset( $data['page'] ) ? $data['page'] : null;
|
365 |
+
|
366 |
+
self::log(
|
367 |
+
__( '"%s" setting updated', 'stream' ),
|
368 |
+
compact( 'option_title', 'option', 'old_value', 'new_value', 'page' ),
|
369 |
+
null,
|
370 |
+
$context,
|
371 |
+
isset( $data['action'] ) ? $data['action'] : 'updated'
|
372 |
+
);
|
373 |
+
}
|
374 |
+
}
|
375 |
+
|
376 |
+
public static function check_bp_active_components( $old_value, $new_value ) {
|
377 |
+
$options = array();
|
378 |
+
|
379 |
+
if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
|
380 |
+
return;
|
381 |
+
}
|
382 |
+
|
383 |
+
foreach ( self::get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
|
384 |
+
$options[ $field_key ] = $field_value;
|
385 |
+
}
|
386 |
+
|
387 |
+
$components = bp_core_admin_get_components();
|
388 |
+
|
389 |
+
$actions = array(
|
390 |
+
true => __( 'activated', 'stream' ),
|
391 |
+
false => __( 'deactivated', 'stream' ),
|
392 |
+
);
|
393 |
+
|
394 |
+
foreach ( $options as $option => $option_value ) {
|
395 |
+
if ( ! isset( $components[ $option ], $actions[ $option_value ] ) ) {
|
396 |
+
continue;
|
397 |
+
}
|
398 |
+
|
399 |
+
self::log(
|
400 |
+
sprintf(
|
401 |
+
__( '"%1$s" component %2$s', 'stream' ),
|
402 |
+
$components[ $option ]['title'],
|
403 |
+
$actions[ $option_value ]
|
404 |
+
),
|
405 |
+
array(
|
406 |
+
'option' => $option,
|
407 |
+
'option_key' => 'bp-active-components',
|
408 |
+
'old_value' => maybe_serialize( $old_value ),
|
409 |
+
'value' => maybe_serialize( $new_value ),
|
410 |
+
),
|
411 |
+
null,
|
412 |
+
'components',
|
413 |
+
$option_value ? 'activated' : 'deactivated'
|
414 |
+
);
|
415 |
+
}
|
416 |
+
}
|
417 |
+
|
418 |
+
public static function check_bp_pages( $old_value, $new_value ) {
|
419 |
+
$options = array();
|
420 |
+
|
421 |
+
if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
|
422 |
+
return;
|
423 |
+
}
|
424 |
+
|
425 |
+
foreach ( self::get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
|
426 |
+
$options[ $field_key ] = $field_value;
|
427 |
+
}
|
428 |
+
|
429 |
+
$pages = array_merge(
|
430 |
+
self::bp_get_directory_pages(),
|
431 |
+
array(
|
432 |
+
'register' => _x( 'Register', 'buddypress', 'stream' ),
|
433 |
+
'activate' => _x( 'Activate', 'buddypress', 'stream' ),
|
434 |
+
)
|
435 |
+
);
|
436 |
+
|
437 |
+
foreach ( $options as $option => $option_value ) {
|
438 |
+
if ( ! isset( $pages[ $option ] ) ) {
|
439 |
+
continue;
|
440 |
+
}
|
441 |
+
|
442 |
+
$page = ! empty( $new_value[ $option ] ) ? get_post( $new_value[ $option ] )->post_title : __( 'No page', 'stream' );
|
443 |
+
|
444 |
+
self::log(
|
445 |
+
sprintf(
|
446 |
+
__( '"%1$s" page set to "%2$s"', 'stream' ),
|
447 |
+
$pages[ $option ],
|
448 |
+
$page
|
449 |
+
),
|
450 |
+
array(
|
451 |
+
'option' => $option,
|
452 |
+
'option_key' => 'bp-pages',
|
453 |
+
'old_value' => maybe_serialize( $old_value ),
|
454 |
+
'value' => maybe_serialize( $new_value ),
|
455 |
+
'page_id' => empty( $new_value[ $option ] ) ? 0 : $new_value[ $option ],
|
456 |
+
),
|
457 |
+
null,
|
458 |
+
'components',
|
459 |
+
'updated'
|
460 |
+
);
|
461 |
+
}
|
462 |
+
}
|
463 |
+
|
464 |
+
public static function callback_bp_before_activity_delete( $args ) {
|
465 |
+
if ( empty( $args['id'] ) ) { // Bail if we're deleting in bulk
|
466 |
+
self::$_delete_activity_args = $args;
|
467 |
+
return;
|
468 |
+
}
|
469 |
+
|
470 |
+
$activity = new BP_Activity_Activity( $args['id'] );
|
471 |
+
|
472 |
+
self::$_deleted_activity = $activity;
|
473 |
+
}
|
474 |
+
|
475 |
+
public static function callback_bp_activity_deleted_activities( $activities_ids ) {
|
476 |
+
if ( 1 === count( $activities_ids ) && isset( self::$_deleted_activity ) ) { // Single activity deletion
|
477 |
+
$activity = self::$_deleted_activity;
|
478 |
+
self::log(
|
479 |
+
sprintf(
|
480 |
+
__( '"%s" activity deleted', 'stream' ),
|
481 |
+
strip_tags( $activity->action )
|
482 |
+
),
|
483 |
+
array(
|
484 |
+
'id' => $activity->id,
|
485 |
+
'item_id' => $activity->item_id,
|
486 |
+
'type' => $activity->type,
|
487 |
+
'author' => $activity->user_id,
|
488 |
+
),
|
489 |
+
$activity->id,
|
490 |
+
$activity->component,
|
491 |
+
'deleted'
|
492 |
+
);
|
493 |
+
} else { // Bulk deletion
|
494 |
+
// Sometimes some objects removal are followed by deleting relevant
|
495 |
+
// activities, so we probably don't need to track those
|
496 |
+
if ( self::$ignore_activity_bulk_deletion ) {
|
497 |
+
self::$ignore_activity_bulk_deletion = false;
|
498 |
+
return;
|
499 |
+
}
|
500 |
+
self::log(
|
501 |
+
sprintf(
|
502 |
+
__( '"%s" activities were deleted', 'stream' ),
|
503 |
+
count( $activities_ids )
|
504 |
+
),
|
505 |
+
array(
|
506 |
+
'count' => count( $activities_ids ),
|
507 |
+
'args' => self::$_delete_activity_args,
|
508 |
+
'ids' => $activities_ids,
|
509 |
+
),
|
510 |
+
null,
|
511 |
+
'activity',
|
512 |
+
'deleted'
|
513 |
+
);
|
514 |
+
}
|
515 |
+
}
|
516 |
+
|
517 |
+
public static function callback_bp_activity_mark_as_spam( $activity, $by ) {
|
518 |
+
self::log(
|
519 |
+
sprintf(
|
520 |
+
__( 'Marked activity "%s" as spam', 'stream' ),
|
521 |
+
strip_tags( $activity->action )
|
522 |
+
),
|
523 |
+
array(
|
524 |
+
'id' => $activity->id,
|
525 |
+
'item_id' => $activity->item_id,
|
526 |
+
'type' => $activity->type,
|
527 |
+
'author' => $activity->user_id,
|
528 |
+
),
|
529 |
+
$activity->id,
|
530 |
+
$activity->component,
|
531 |
+
'spammed'
|
532 |
+
);
|
533 |
+
}
|
534 |
+
|
535 |
+
public static function callback_bp_activity_mark_as_ham( $activity, $by ) {
|
536 |
+
self::log(
|
537 |
+
sprintf(
|
538 |
+
__( 'Unmarked activity "%s" as spam', 'stream' ),
|
539 |
+
strip_tags( $activity->action )
|
540 |
+
),
|
541 |
+
array(
|
542 |
+
'id' => $activity->id,
|
543 |
+
'item_id' => $activity->item_id,
|
544 |
+
'type' => $activity->type,
|
545 |
+
'author' => $activity->user_id,
|
546 |
+
),
|
547 |
+
$activity->id,
|
548 |
+
$activity->component,
|
549 |
+
'unspammed'
|
550 |
+
);
|
551 |
+
}
|
552 |
+
|
553 |
+
public static function callback_bp_activity_admin_edit_after( $activity, $error ) {
|
554 |
+
self::log(
|
555 |
+
sprintf(
|
556 |
+
__( '"%s" activity updated', 'stream' ),
|
557 |
+
strip_tags( $activity->action )
|
558 |
+
),
|
559 |
+
array(
|
560 |
+
'id' => $activity->id,
|
561 |
+
'item_id' => $activity->item_id,
|
562 |
+
'type' => $activity->type,
|
563 |
+
'author' => $activity->user_id,
|
564 |
+
),
|
565 |
+
$activity->id,
|
566 |
+
'activity',
|
567 |
+
'updated'
|
568 |
+
);
|
569 |
+
}
|
570 |
+
|
571 |
+
public static function group_action( $group, $action, $meta = array(), $message = null ) {
|
572 |
+
if ( is_numeric( $group ) ) {
|
573 |
+
$group = groups_get_group( array( 'group_id' => $group ) );
|
574 |
+
}
|
575 |
+
|
576 |
+
$replacements = array(
|
577 |
+
$group->name,
|
578 |
+
);
|
579 |
+
|
580 |
+
if ( $message ) {
|
581 |
+
// Do nothing
|
582 |
+
}
|
583 |
+
elseif ( 'created' === $action ) {
|
584 |
+
$message = __( '"%s" group created', 'stream' );
|
585 |
+
}
|
586 |
+
elseif ( 'updated' === $action ) {
|
587 |
+
$message = __( '"%s" group updated', 'stream' );
|
588 |
+
}
|
589 |
+
elseif ( 'deleted' === $action ) {
|
590 |
+
$message = __( '"%s" group deleted', 'stream' );
|
591 |
+
}
|
592 |
+
elseif ( 'joined' === $action ) {
|
593 |
+
$message = __( 'Joined group "%s"', 'stream' );
|
594 |
+
}
|
595 |
+
elseif ( 'left' === $action ) {
|
596 |
+
$message = __( 'Left group "%s"', 'stream' );
|
597 |
+
}
|
598 |
+
elseif ( 'banned' === $action ) {
|
599 |
+
$message = __( 'Banned "%2$s" from "%1$s"', 'stream' );
|
600 |
+
$replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
|
601 |
+
}
|
602 |
+
elseif ( 'unbanned' === $action ) {
|
603 |
+
$message = __( 'Unbanned "%2$s" from "%1$s"', 'stream' );
|
604 |
+
$replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
|
605 |
+
}
|
606 |
+
elseif ( 'removed' === $action ) {
|
607 |
+
$message = __( 'Removed "%2$s" from "%1$s"', 'stream' );
|
608 |
+
$replacements[] = get_user_by( 'id', $meta['user_id'] )->display_name;
|
609 |
+
}
|
610 |
+
else {
|
611 |
+
return;
|
612 |
+
}
|
613 |
+
|
614 |
+
self::log(
|
615 |
+
vsprintf(
|
616 |
+
$message,
|
617 |
+
$replacements
|
618 |
+
),
|
619 |
+
array_merge(
|
620 |
+
array(
|
621 |
+
'id' => $group->id,
|
622 |
+
'name' => $group->name,
|
623 |
+
'slug' => $group->slug,
|
624 |
+
),
|
625 |
+
$meta
|
626 |
+
),
|
627 |
+
$group->id,
|
628 |
+
'groups',
|
629 |
+
$action
|
630 |
+
);
|
631 |
+
}
|
632 |
+
|
633 |
+
public static function callback_groups_create_group( $group_id, $member, $group ) {
|
634 |
+
self::group_action( $group, 'created' );
|
635 |
+
}
|
636 |
+
public static function callback_groups_update_group( $group_id, $group ) {
|
637 |
+
self::group_action( $group, 'updated' );
|
638 |
+
}
|
639 |
+
public static function callback_groups_before_delete_group( $group_id ) {
|
640 |
+
self::$ignore_activity_bulk_deletion = true;
|
641 |
+
self::group_action( $group_id, 'deleted' );
|
642 |
+
}
|
643 |
+
public static function callback_groups_details_updated( $group_id ) {
|
644 |
+
self::$is_update = true;
|
645 |
+
self::group_action( $group_id, 'updated' );
|
646 |
+
}
|
647 |
+
public static function callback_groups_settings_updated( $group_id ) {
|
648 |
+
if ( self::$is_update ) {
|
649 |
+
return;
|
650 |
+
}
|
651 |
+
self::group_action( $group_id, 'updated' );
|
652 |
+
}
|
653 |
+
|
654 |
+
public static function callback_groups_leave_group( $group_id, $user_id ) {
|
655 |
+
self::group_action( $group_id, 'left', compact( 'user_id' ) );
|
656 |
+
}
|
657 |
+
public static function callback_groups_join_group( $group_id, $user_id ) {
|
658 |
+
self::group_action( $group_id, 'joined', compact( 'user_id' ) );
|
659 |
+
}
|
660 |
+
|
661 |
+
public static function callback_groups_promote_member( $group_id, $user_id, $status ) {
|
662 |
+
$group = groups_get_group( array( 'group_id' => $group_id ) );
|
663 |
+
$user = new WP_User( $user_id );
|
664 |
+
$roles = array(
|
665 |
+
'admin' => _x( 'Administrator', 'buddypress', 'stream' ),
|
666 |
+
'mod' => _x( 'Moderator', 'buddypress', 'stream' ),
|
667 |
+
);
|
668 |
+
$message = sprintf(
|
669 |
+
__( 'Promoted "%s" to "%s" in "%s"', 'stream' ),
|
670 |
+
$user->display_name,
|
671 |
+
$roles[ $status ],
|
672 |
+
$group->name
|
673 |
+
);
|
674 |
+
self::group_action( $group_id, 'promoted', compact( 'user_id', 'status' ), $message );
|
675 |
+
}
|
676 |
+
public static function callback_groups_demote_member( $group_id, $user_id ) {
|
677 |
+
$group = groups_get_group( array( 'group_id' => $group_id ) );
|
678 |
+
$user = new WP_User( $user_id );
|
679 |
+
$message = sprintf(
|
680 |
+
__( 'Demoted "%s" to "%s" in "%s"', 'stream' ),
|
681 |
+
$user->display_name,
|
682 |
+
_x( 'Member', 'buddypress', 'stream' ),
|
683 |
+
$group->name
|
684 |
+
);
|
685 |
+
self::group_action( $group_id, 'demoted', compact( 'user_id' ), $message );
|
686 |
+
}
|
687 |
+
public static function callback_groups_ban_member( $group_id, $user_id ) {
|
688 |
+
self::group_action( $group_id, 'banned', compact( 'user_id' ) );
|
689 |
+
}
|
690 |
+
public static function callback_groups_unban_member( $group_id, $user_id ) {
|
691 |
+
self::group_action( $group_id, 'unbanned', compact( 'user_id' ) );
|
692 |
+
}
|
693 |
+
public static function callback_groups_remove_member( $group_id, $user_id ) {
|
694 |
+
self::group_action( $group_id, 'removed', compact( 'user_id' ) );
|
695 |
+
}
|
696 |
+
|
697 |
+
public static function field_action( $field, $action, $meta = array(), $message = null ) {
|
698 |
+
$replacements = array(
|
699 |
+
$field->name,
|
700 |
+
);
|
701 |
+
|
702 |
+
if ( $message ) {
|
703 |
+
// Do nothing
|
704 |
+
}
|
705 |
+
elseif ( 'created' === $action ) {
|
706 |
+
$message = __( 'Created profile field "%s"', 'stream' );
|
707 |
+
}
|
708 |
+
elseif ( 'updated' === $action ) {
|
709 |
+
$message = __( 'Updated profile field "%s"', 'stream' );
|
710 |
+
}
|
711 |
+
elseif ( 'deleted' === $action ) {
|
712 |
+
$message = __( 'Deleted profile field "%s"', 'stream' );
|
713 |
+
}
|
714 |
+
else {
|
715 |
+
return;
|
716 |
+
}
|
717 |
+
|
718 |
+
self::log(
|
719 |
+
vsprintf(
|
720 |
+
$message,
|
721 |
+
$replacements
|
722 |
+
),
|
723 |
+
array_merge(
|
724 |
+
array(
|
725 |
+
'field_id' => $field->id,
|
726 |
+
'field_name' => $field->name,
|
727 |
+
'group_id' => $field->group_id,
|
728 |
+
),
|
729 |
+
$meta
|
730 |
+
),
|
731 |
+
$field->id,
|
732 |
+
'profile_fields',
|
733 |
+
$action
|
734 |
+
);
|
735 |
+
}
|
736 |
+
|
737 |
+
public static function callback_xprofile_field_after_save( $field ) {
|
738 |
+
$action = isset( $field->id ) ? 'updated' : 'created';
|
739 |
+
self::field_action( $field, $action );
|
740 |
+
}
|
741 |
+
|
742 |
+
public static function callback_xprofile_fields_deleted_field( $field ){
|
743 |
+
self::field_action( $field, 'deleted' );
|
744 |
+
}
|
745 |
+
|
746 |
+
public static function field_group_action( $group, $action, $meta = array(), $message = null ) {
|
747 |
+
$replacements = array(
|
748 |
+
$group->name,
|
749 |
+
);
|
750 |
+
|
751 |
+
if ( $message ) {
|
752 |
+
// Do nothing
|
753 |
+
}
|
754 |
+
elseif ( 'created' === $action ) {
|
755 |
+
$message = __( 'Created profile field group "%s"', 'stream' );
|
756 |
+
}
|
757 |
+
elseif ( 'updated' === $action ) {
|
758 |
+
$message = __( 'Updated profile field group "%s"', 'stream' );
|
759 |
+
}
|
760 |
+
elseif ( 'deleted' === $action ) {
|
761 |
+
$message = __( 'Deleted profile field group "%s"', 'stream' );
|
762 |
+
}
|
763 |
+
else {
|
764 |
+
return;
|
765 |
+
}
|
766 |
+
|
767 |
+
self::log(
|
768 |
+
vsprintf(
|
769 |
+
$message,
|
770 |
+
$replacements
|
771 |
+
),
|
772 |
+
array_merge(
|
773 |
+
array(
|
774 |
+
'group_id' => $group->id,
|
775 |
+
'group_name' => $group->name,
|
776 |
+
),
|
777 |
+
$meta
|
778 |
+
),
|
779 |
+
$group->id,
|
780 |
+
'profile_fields',
|
781 |
+
$action
|
782 |
+
);
|
783 |
+
}
|
784 |
+
|
785 |
+
public static function callback_xprofile_group_after_save( $group ) {
|
786 |
+
global $wpdb;
|
787 |
+
// a bit hacky, due to inconsistency with BP action scheme, see callback_xprofile_field_after_save for correct behavior
|
788 |
+
$action = ( $group->id === $wpdb->insert_id ) ? 'created' : 'updated';
|
789 |
+
self::field_group_action( $group, $action );
|
790 |
+
}
|
791 |
+
|
792 |
+
public static function callback_xprofile_groups_deleted_group( $group ){
|
793 |
+
self::field_group_action( $group, 'deleted' );
|
794 |
+
}
|
795 |
+
|
796 |
+
private static function bp_get_directory_pages() {
|
797 |
+
$bp = buddypress();
|
798 |
+
$directory_pages = array();
|
799 |
+
|
800 |
+
// Loop through loaded components and collect directories
|
801 |
+
if ( is_array( $bp->loaded_components ) ) {
|
802 |
+
foreach ( $bp->loaded_components as $component_slug => $component_id ) {
|
803 |
+
// Only components that need directories should be listed here
|
804 |
+
if ( isset( $bp->{$component_id} ) && ! empty( $bp->{$component_id}->has_directory ) ) {
|
805 |
+
// component->name was introduced in BP 1.5, so we must provide a fallback
|
806 |
+
$directory_pages[ $component_id ] = ! empty( $bp->{ $component_id }->name ) ? $bp->{ $component_id }->name : ucwords( $component_id );
|
807 |
+
}
|
808 |
+
}
|
809 |
+
}
|
810 |
+
|
811 |
+
return $directory_pages;
|
812 |
+
}
|
813 |
+
|
814 |
+
}
|
connectors/class-wp-stream-connector-comments.php
ADDED
@@ -0,0 +1,594 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Comments extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'comments';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'comment_flood_trigger',
|
19 |
+
'wp_insert_comment',
|
20 |
+
'edit_comment',
|
21 |
+
'before_delete_post',
|
22 |
+
'deleted_post',
|
23 |
+
'delete_comment',
|
24 |
+
'trash_comment',
|
25 |
+
'untrash_comment',
|
26 |
+
'spam_comment',
|
27 |
+
'unspam_comment',
|
28 |
+
'transition_comment_status',
|
29 |
+
'comment_duplicate_trigger',
|
30 |
+
);
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Catch and store the post ID during post deletion
|
34 |
+
*
|
35 |
+
* @var int
|
36 |
+
*/
|
37 |
+
protected static $delete_post = 0;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Return translated connector label
|
41 |
+
*
|
42 |
+
* @return string Translated connector label
|
43 |
+
*/
|
44 |
+
public static function get_label() {
|
45 |
+
return __( 'Comments', 'stream' );
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Return translated action labels
|
50 |
+
*
|
51 |
+
* @return array Action label translations
|
52 |
+
*/
|
53 |
+
public static function get_action_labels() {
|
54 |
+
return array(
|
55 |
+
'created' => __( 'Created', 'stream' ),
|
56 |
+
'edited' => __( 'Edited', 'stream' ),
|
57 |
+
'replied' => __( 'Replied', 'stream' ),
|
58 |
+
'approved' => __( 'Approved', 'stream' ),
|
59 |
+
'unapproved' => __( 'Unapproved', 'stream' ),
|
60 |
+
'trashed' => __( 'Trashed', 'stream' ),
|
61 |
+
'untrashed' => __( 'Restored', 'stream' ),
|
62 |
+
'spammed' => __( 'Marked as Spam', 'stream' ),
|
63 |
+
'unspammed' => __( 'Unmarked as Spam', 'stream' ),
|
64 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
65 |
+
'duplicate' => __( 'Duplicate', 'stream' ),
|
66 |
+
'flood' => __( 'Throttled', 'stream' ),
|
67 |
+
);
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Return translated context labels
|
72 |
+
*
|
73 |
+
* @return array Context label translations
|
74 |
+
*/
|
75 |
+
public static function get_context_labels() {
|
76 |
+
return array(
|
77 |
+
'comments' => __( 'Comments', 'stream' ),
|
78 |
+
);
|
79 |
+
}
|
80 |
+
|
81 |
+
/**
|
82 |
+
* Return translated comment type labels
|
83 |
+
*
|
84 |
+
* @return array Comment type label translations
|
85 |
+
*/
|
86 |
+
public static function get_comment_type_labels() {
|
87 |
+
return apply_filters(
|
88 |
+
'wp_stream_comments_comment_type_labels',
|
89 |
+
array(
|
90 |
+
'comment' => __( 'Comment', 'stream' ),
|
91 |
+
'trackback' => __( 'Trackback', 'stream' ),
|
92 |
+
'pingback' => __( 'Pingback', 'stream' ),
|
93 |
+
)
|
94 |
+
);
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Return the comment type label for a given comment ID
|
99 |
+
*
|
100 |
+
* @param int $comment_id ID of the comment
|
101 |
+
* @return string The comment type label
|
102 |
+
*/
|
103 |
+
public static function get_comment_type_label( $comment_id ) {
|
104 |
+
$comment_type = get_comment_type( $comment_id );
|
105 |
+
|
106 |
+
if ( empty( $comment_type ) ) {
|
107 |
+
$comment_type = 'comment';
|
108 |
+
}
|
109 |
+
|
110 |
+
$comment_type_labels = self::get_comment_type_labels();
|
111 |
+
|
112 |
+
$label = isset( $comment_type_labels[ $comment_type ] ) ? $comment_type_labels[ $comment_type ] : $comment_type;
|
113 |
+
|
114 |
+
return $label;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Add action links to Stream drop row in admin list screen
|
119 |
+
*
|
120 |
+
* @filter wp_stream_action_links_{connector}
|
121 |
+
*
|
122 |
+
* @param array $links Previous links registered
|
123 |
+
* @param object $record Stream record
|
124 |
+
*
|
125 |
+
* @return array Action links
|
126 |
+
*/
|
127 |
+
public static function action_links( $links, $record ) {
|
128 |
+
if ( $record->object_id ) {
|
129 |
+
if ( $comment = get_comment( $record->object_id ) ) {
|
130 |
+
$del_nonce = wp_create_nonce( "delete-comment_$comment->comment_ID" );
|
131 |
+
$approve_nonce = wp_create_nonce( "approve-comment_$comment->comment_ID" );
|
132 |
+
|
133 |
+
$links[ __( 'Edit', 'stream' ) ] = admin_url( "comment.php?action=editcomment&c=$comment->comment_ID" );
|
134 |
+
|
135 |
+
if ( 1 === $comment->comment_approved ) {
|
136 |
+
$links[ __( 'Unapprove', 'stream' ) ] = admin_url(
|
137 |
+
sprintf(
|
138 |
+
'comment.php?action=unapprovecomment&c=%s&_wpnonce=%s',
|
139 |
+
$record->object_id,
|
140 |
+
$approve_nonce
|
141 |
+
)
|
142 |
+
);
|
143 |
+
} elseif ( empty( $comment->comment_approved ) ) {
|
144 |
+
$links[ __( 'Approve', 'stream' ) ] = admin_url(
|
145 |
+
sprintf(
|
146 |
+
'comment.php?action=approvecomment&c=%s&_wpnonce=%s',
|
147 |
+
$record->object_id,
|
148 |
+
$approve_nonce
|
149 |
+
)
|
150 |
+
);
|
151 |
+
}
|
152 |
+
}
|
153 |
+
}
|
154 |
+
|
155 |
+
return $links;
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Fetches the comment author and returns the specified field.
|
160 |
+
*
|
161 |
+
* This also takes into consideration whether or not the blog requires only
|
162 |
+
* name and e-mail or that users be logged in to comment. In either case it
|
163 |
+
* will try to see if the e-mail provided does belong to a registered user.
|
164 |
+
*
|
165 |
+
* @param object|int $comment A comment object or comment ID
|
166 |
+
* @param string $field What field you want to return
|
167 |
+
* @return int|string $output User ID or user display name
|
168 |
+
*/
|
169 |
+
public static function get_comment_author( $comment, $field = 'id' ) {
|
170 |
+
$comment = is_object( $comment ) ? $comment : get_comment( absint( $comment ) );
|
171 |
+
|
172 |
+
$req_name_email = get_option( 'require_name_email' );
|
173 |
+
$req_user_login = get_option( 'comment_registration' );
|
174 |
+
|
175 |
+
$user_id = 0;
|
176 |
+
$user_name = __( 'Guest', 'stream' );
|
177 |
+
|
178 |
+
if ( $req_name_email && isset( $comment->comment_author_email ) && isset( $comment->comment_author ) ) {
|
179 |
+
$user = get_user_by( 'email', $comment->comment_author_email );
|
180 |
+
$user_id = isset( $user->ID ) ? $user->ID : 0;
|
181 |
+
$user_name = isset( $user->display_name ) ? $user->display_name : $comment->comment_author;
|
182 |
+
}
|
183 |
+
|
184 |
+
if ( $req_user_login ) {
|
185 |
+
$user = wp_get_current_user();
|
186 |
+
$user_id = $user->ID;
|
187 |
+
$user_name = $user->display_name;
|
188 |
+
}
|
189 |
+
|
190 |
+
if ( 'id' === $field ) {
|
191 |
+
$output = $user_id;
|
192 |
+
} elseif ( 'name' === $field ) {
|
193 |
+
$output = $user_name;
|
194 |
+
}
|
195 |
+
|
196 |
+
return $output;
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Tracks comment flood blocks
|
201 |
+
*
|
202 |
+
* @action comment_flood_trigger
|
203 |
+
*/
|
204 |
+
public static function callback_comment_flood_trigger( $time_lastcomment, $time_newcomment ) {
|
205 |
+
$flood_tracking = isset( WP_Stream_Settings::$options['advanced_comment_flood_tracking'] ) ? WP_Stream_Settings::$options['advanced_comment_flood_tracking'] : false;
|
206 |
+
|
207 |
+
if ( ! $flood_tracking ) {
|
208 |
+
return;
|
209 |
+
}
|
210 |
+
|
211 |
+
$req_user_login = get_option( 'comment_registration' );
|
212 |
+
|
213 |
+
if ( $req_user_login ) {
|
214 |
+
$user = wp_get_current_user();
|
215 |
+
$user_id = $user->ID;
|
216 |
+
$user_name = $user->display_name;
|
217 |
+
} else {
|
218 |
+
$user_name = __( 'a logged out user', 'stream' );
|
219 |
+
}
|
220 |
+
|
221 |
+
self::log(
|
222 |
+
__( 'Comment flooding by %s detected and prevented', 'stream' ),
|
223 |
+
compact( 'user_name', 'user_id', 'time_lastcomment', 'time_newcomment' ),
|
224 |
+
null,
|
225 |
+
'comments',
|
226 |
+
'flood'
|
227 |
+
);
|
228 |
+
}
|
229 |
+
|
230 |
+
/**
|
231 |
+
* Tracks comment creation
|
232 |
+
*
|
233 |
+
* @action wp_insert_comment
|
234 |
+
*/
|
235 |
+
public static function callback_wp_insert_comment( $comment_id, $comment ) {
|
236 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
237 |
+
return;
|
238 |
+
}
|
239 |
+
|
240 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
241 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
242 |
+
$post_id = $comment->comment_post_ID;
|
243 |
+
$post_type = get_post_type( $post_id );
|
244 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
245 |
+
$comment_status = ( 1 == $comment->comment_approved ) ? __( 'approved automatically', 'stream' ) : __( 'pending approval', 'stream' );
|
246 |
+
$is_spam = false;
|
247 |
+
|
248 |
+
// Auto-marked spam comments
|
249 |
+
$ak_tracking = isset( WP_Stream_Settings::$options['advanced_akismet_tracking'] ) ? WP_Stream_Settings::$options['advanced_akismet_tracking'] : false;
|
250 |
+
if ( class_exists( 'Akismet' ) && $ak_tracking && Akismet::matches_last_comment( $comment ) ) {
|
251 |
+
$ak_last_comment = Akismet::get_last_comment();
|
252 |
+
if ( 'true' == $ak_last_comment['akismet_result'] ) {
|
253 |
+
$is_spam = true;
|
254 |
+
$comment_status = __( 'automatically marked as spam by Akismet', 'stream' );
|
255 |
+
}
|
256 |
+
}
|
257 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
258 |
+
|
259 |
+
if ( $comment->comment_parent ) {
|
260 |
+
$parent_user_id = get_comment_author( $comment->comment_parent, 'id' );
|
261 |
+
$parent_user_name = get_comment_author( $comment->comment_parent, 'name' );
|
262 |
+
|
263 |
+
self::log(
|
264 |
+
_x(
|
265 |
+
'Reply to %1$s\'s %5$s by %2$s on %3$s %4$s',
|
266 |
+
"1: Parent comment's author, 2: Comment author, 3: Post title, 4: Comment status, 5: Comment type",
|
267 |
+
'stream'
|
268 |
+
),
|
269 |
+
compact( 'parent_user_name', 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id', 'parent_user_id' ),
|
270 |
+
$comment_id,
|
271 |
+
$post_type,
|
272 |
+
'replied',
|
273 |
+
$user_id
|
274 |
+
);
|
275 |
+
} else {
|
276 |
+
self::log(
|
277 |
+
_x(
|
278 |
+
'New %4$s by %1$s on %2$s %3$s',
|
279 |
+
'1: Comment author, 2: Post title 3: Comment status, 4: Comment type',
|
280 |
+
'stream'
|
281 |
+
),
|
282 |
+
compact( 'user_name', 'post_title', 'comment_status', 'comment_type', 'post_id', 'is_spam' ),
|
283 |
+
$comment_id,
|
284 |
+
$post_type,
|
285 |
+
$is_spam ? 'spammed' : 'created',
|
286 |
+
$user_id
|
287 |
+
);
|
288 |
+
}
|
289 |
+
}
|
290 |
+
|
291 |
+
/**
|
292 |
+
* Tracks comment updates
|
293 |
+
*
|
294 |
+
* @action edit_comment
|
295 |
+
*/
|
296 |
+
public static function callback_edit_comment( $comment_id ) {
|
297 |
+
$comment = get_comment( $comment_id );
|
298 |
+
|
299 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
300 |
+
return;
|
301 |
+
}
|
302 |
+
|
303 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
304 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
305 |
+
$post_id = $comment->comment_post_ID;
|
306 |
+
$post_type = get_post_type( $post_id );
|
307 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
308 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
309 |
+
|
310 |
+
self::log(
|
311 |
+
_x(
|
312 |
+
'%1$s\'s %3$s on %2$s edited',
|
313 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
314 |
+
'stream'
|
315 |
+
),
|
316 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
317 |
+
$comment_id,
|
318 |
+
$post_type,
|
319 |
+
'edited'
|
320 |
+
);
|
321 |
+
}
|
322 |
+
|
323 |
+
/**
|
324 |
+
* Catch the post ID during deletion
|
325 |
+
*
|
326 |
+
* @action before_delete_post
|
327 |
+
*/
|
328 |
+
public static function callback_before_delete_post( $post_id ) {
|
329 |
+
if ( wp_is_post_revision( $post_id ) ) {
|
330 |
+
return;
|
331 |
+
}
|
332 |
+
|
333 |
+
self::$delete_post = $post_id;
|
334 |
+
}
|
335 |
+
|
336 |
+
/**
|
337 |
+
* Reset the post ID after deletion
|
338 |
+
*
|
339 |
+
* @action deleted_post
|
340 |
+
*/
|
341 |
+
public static function callback_deleted_post( $post_id ) {
|
342 |
+
if ( wp_is_post_revision( $post_id ) ) {
|
343 |
+
return;
|
344 |
+
}
|
345 |
+
|
346 |
+
self::$delete_post = 0;
|
347 |
+
}
|
348 |
+
|
349 |
+
/**
|
350 |
+
* Tracks comment delete
|
351 |
+
*
|
352 |
+
* @action delete_comment
|
353 |
+
*/
|
354 |
+
public static function callback_delete_comment( $comment_id ) {
|
355 |
+
$comment = get_comment( $comment_id );
|
356 |
+
|
357 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
358 |
+
return;
|
359 |
+
}
|
360 |
+
|
361 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
362 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
363 |
+
$post_id = absint( $comment->comment_post_ID );
|
364 |
+
$post_type = get_post_type( $post_id );
|
365 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
366 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
367 |
+
|
368 |
+
if ( self::$delete_post === $post_id ) {
|
369 |
+
return;
|
370 |
+
}
|
371 |
+
|
372 |
+
self::log(
|
373 |
+
_x(
|
374 |
+
'%1$s\'s %3$s on %2$s deleted permanently',
|
375 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
376 |
+
'stream'
|
377 |
+
),
|
378 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
379 |
+
$comment_id,
|
380 |
+
$post_type,
|
381 |
+
'deleted'
|
382 |
+
);
|
383 |
+
}
|
384 |
+
|
385 |
+
/**
|
386 |
+
* Tracks comment trashing
|
387 |
+
*
|
388 |
+
* @action trash_comment
|
389 |
+
*/
|
390 |
+
public static function callback_trash_comment( $comment_id ) {
|
391 |
+
$comment = get_comment( $comment_id );
|
392 |
+
|
393 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
394 |
+
return;
|
395 |
+
}
|
396 |
+
|
397 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
398 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
399 |
+
$post_id = $comment->comment_post_ID;
|
400 |
+
$post_type = get_post_type( $post_id );
|
401 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
402 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
403 |
+
|
404 |
+
self::log(
|
405 |
+
_x(
|
406 |
+
'%1$s\'s %3$s on %2$s trashed',
|
407 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
408 |
+
'stream'
|
409 |
+
),
|
410 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
411 |
+
$comment_id,
|
412 |
+
$post_type,
|
413 |
+
'trashed'
|
414 |
+
);
|
415 |
+
}
|
416 |
+
|
417 |
+
/**
|
418 |
+
* Tracks comment trashing
|
419 |
+
*
|
420 |
+
* @action untrash_comment
|
421 |
+
*/
|
422 |
+
public static function callback_untrash_comment( $comment_id ) {
|
423 |
+
$comment = get_comment( $comment_id );
|
424 |
+
|
425 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
426 |
+
return;
|
427 |
+
}
|
428 |
+
|
429 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
430 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
431 |
+
$post_id = $comment->comment_post_ID;
|
432 |
+
$post_type = get_post_type( $post_id );
|
433 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
434 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
435 |
+
|
436 |
+
self::log(
|
437 |
+
_x(
|
438 |
+
'%1$s\'s %3$s on %2$s restored',
|
439 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
440 |
+
'stream'
|
441 |
+
),
|
442 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
443 |
+
$comment_id,
|
444 |
+
$post_type,
|
445 |
+
'untrashed'
|
446 |
+
);
|
447 |
+
}
|
448 |
+
|
449 |
+
/**
|
450 |
+
* Tracks comment marking as spam
|
451 |
+
*
|
452 |
+
* @action spam_comment
|
453 |
+
*/
|
454 |
+
public static function callback_spam_comment( $comment_id ) {
|
455 |
+
$comment = get_comment( $comment_id );
|
456 |
+
|
457 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
458 |
+
return;
|
459 |
+
}
|
460 |
+
|
461 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
462 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
463 |
+
$post_id = $comment->comment_post_ID;
|
464 |
+
$post_type = get_post_type( $post_id );
|
465 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
466 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
467 |
+
|
468 |
+
self::log(
|
469 |
+
_x(
|
470 |
+
'%1$s\'s %3$s on %2$s marked as spam',
|
471 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
472 |
+
'stream'
|
473 |
+
),
|
474 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
475 |
+
$comment_id,
|
476 |
+
$post_type,
|
477 |
+
'spammed'
|
478 |
+
);
|
479 |
+
}
|
480 |
+
|
481 |
+
/**
|
482 |
+
* Tracks comment unmarking as spam
|
483 |
+
*
|
484 |
+
* @action unspam_comment
|
485 |
+
*/
|
486 |
+
public static function callback_unspam_comment( $comment_id ) {
|
487 |
+
$comment = get_comment( $comment_id );
|
488 |
+
|
489 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
490 |
+
return;
|
491 |
+
}
|
492 |
+
|
493 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
494 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
495 |
+
$post_id = $comment->comment_post_ID;
|
496 |
+
$post_type = get_post_type( $post_id );
|
497 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
498 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
499 |
+
|
500 |
+
self::log(
|
501 |
+
_x(
|
502 |
+
'%1$s\'s %3$s on %2$s unmarked as spam',
|
503 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
504 |
+
'stream'
|
505 |
+
),
|
506 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
507 |
+
$comment_id,
|
508 |
+
$post_type,
|
509 |
+
'unspammed'
|
510 |
+
);
|
511 |
+
}
|
512 |
+
|
513 |
+
/**
|
514 |
+
* Track comment status transition
|
515 |
+
*
|
516 |
+
* @action transition_comment_status
|
517 |
+
*/
|
518 |
+
public static function callback_transition_comment_status( $new_status, $old_status, $comment ) {
|
519 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
520 |
+
return;
|
521 |
+
}
|
522 |
+
|
523 |
+
if ( 'approved' !== $new_status && 'unapproved' !== $new_status || 'trash' === $old_status || 'spam' === $old_status ) {
|
524 |
+
return;
|
525 |
+
}
|
526 |
+
|
527 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
528 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
529 |
+
$post_id = $comment->comment_post_ID;
|
530 |
+
$post_type = get_post_type( $post_id );
|
531 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
532 |
+
$comment_type = get_comment_type( $comment->comment_ID );
|
533 |
+
|
534 |
+
self::log(
|
535 |
+
_x(
|
536 |
+
'%1$s\'s %3$s %2$s',
|
537 |
+
'Comment status transition. 1: Comment author, 2: Post title, 3: Comment type',
|
538 |
+
'stream'
|
539 |
+
),
|
540 |
+
compact( 'user_name', 'new_status', 'comment_type', 'old_status', 'post_title', 'post_id', 'user_id' ),
|
541 |
+
$comment->comment_ID,
|
542 |
+
$post_type,
|
543 |
+
$new_status
|
544 |
+
);
|
545 |
+
}
|
546 |
+
|
547 |
+
/**
|
548 |
+
* Track attempts to add duplicate comments
|
549 |
+
*
|
550 |
+
* @action comment_duplicate_trigger
|
551 |
+
*/
|
552 |
+
public static function callback_comment_duplicate_trigger( $comment_data ) {
|
553 |
+
global $wpdb;
|
554 |
+
|
555 |
+
$comment_id = $wpdb->last_result[0]->comment_ID;
|
556 |
+
$comment = get_comment( $comment_id );
|
557 |
+
|
558 |
+
if ( in_array( $comment->comment_type, self::get_ignored_comment_types() ) ) {
|
559 |
+
return;
|
560 |
+
}
|
561 |
+
|
562 |
+
$user_id = self::get_comment_author( $comment, 'id' );
|
563 |
+
$user_name = self::get_comment_author( $comment, 'name' );
|
564 |
+
$post_id = $comment->comment_post_ID;
|
565 |
+
$post_type = get_post_type( $post_id );
|
566 |
+
$post_title = ( $post = get_post( $post_id ) ) ? "\"$post->post_title\"" : __( 'a post', 'stream' );
|
567 |
+
$comment_type = mb_strtolower( self::get_comment_type_label( $comment_id ) );
|
568 |
+
|
569 |
+
self::log(
|
570 |
+
_x(
|
571 |
+
'Duplicate %3$s by %1$s prevented on %2$s',
|
572 |
+
'1: Comment author, 2: Post title, 3: Comment type',
|
573 |
+
'stream'
|
574 |
+
),
|
575 |
+
compact( 'user_name', 'post_title', 'comment_type', 'post_id', 'user_id' ),
|
576 |
+
$comment_id,
|
577 |
+
$post_type,
|
578 |
+
'duplicate'
|
579 |
+
);
|
580 |
+
}
|
581 |
+
|
582 |
+
/**
|
583 |
+
* Constructs list of ignored comment types for the comments connector
|
584 |
+
*
|
585 |
+
* @return array List of ignored comment types
|
586 |
+
*/
|
587 |
+
public static function get_ignored_comment_types() {
|
588 |
+
return apply_filters(
|
589 |
+
'wp_stream_comments_exclude_comment_types',
|
590 |
+
array()
|
591 |
+
);
|
592 |
+
}
|
593 |
+
|
594 |
+
}
|
connectors/class-wp-stream-connector-edd.php
ADDED
@@ -0,0 +1,490 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_EDD extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'edd';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '1.8.8';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'update_option',
|
26 |
+
'add_option',
|
27 |
+
'delete_option',
|
28 |
+
'update_site_option',
|
29 |
+
'add_site_option',
|
30 |
+
'delete_site_option',
|
31 |
+
'edd_pre_update_discount_status',
|
32 |
+
'edd_generate_pdf',
|
33 |
+
'edd_earnings_export',
|
34 |
+
'edd_payment_export',
|
35 |
+
'edd_email_export',
|
36 |
+
'edd_downloads_history_export',
|
37 |
+
'edd_import_settings',
|
38 |
+
'edd_export_settings',
|
39 |
+
'add_user_meta',
|
40 |
+
'update_user_meta',
|
41 |
+
'delete_user_meta',
|
42 |
+
);
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Tracked option keys
|
46 |
+
*
|
47 |
+
* @var array
|
48 |
+
*/
|
49 |
+
public static $options = array();
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Tracking registered Settings, with overridden data
|
53 |
+
*
|
54 |
+
* @var array
|
55 |
+
*/
|
56 |
+
public static $options_override = array();
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Tracking user meta updates related to this connector
|
60 |
+
*
|
61 |
+
* @var array
|
62 |
+
*/
|
63 |
+
public static $user_meta = array(
|
64 |
+
'edd_user_public_key',
|
65 |
+
);
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Flag status changes to not create duplicate entries
|
69 |
+
* @var bool
|
70 |
+
*/
|
71 |
+
public static $is_discount_status_change = false;
|
72 |
+
|
73 |
+
/**
|
74 |
+
* Flag status changes to not create duplicate entries
|
75 |
+
* @var bool
|
76 |
+
*/
|
77 |
+
public static $is_payment_status_change = false;
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
81 |
+
*
|
82 |
+
* @return bool
|
83 |
+
*/
|
84 |
+
public static function is_dependency_satisfied() {
|
85 |
+
if ( class_exists( 'Easy_Digital_Downloads' ) && defined( 'EDD_VERSION' ) && version_compare( EDD_VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
86 |
+
return true;
|
87 |
+
}
|
88 |
+
|
89 |
+
return false;
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Return translated connector label
|
94 |
+
*
|
95 |
+
* @return string Translated connector label
|
96 |
+
*/
|
97 |
+
public static function get_label() {
|
98 |
+
return _x( 'Easy Digital Downloads', 'edd', 'stream' );
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Return translated action labels
|
103 |
+
*
|
104 |
+
* @return array Action label translations
|
105 |
+
*/
|
106 |
+
public static function get_action_labels() {
|
107 |
+
return array(
|
108 |
+
'created' => _x( 'Created', 'edd', 'stream' ),
|
109 |
+
'updated' => _x( 'Updated', 'edd', 'stream' ),
|
110 |
+
'added' => _x( 'Added', 'edd', 'stream' ),
|
111 |
+
'deleted' => _x( 'Deleted', 'edd', 'stream' ),
|
112 |
+
'trashed' => _x( 'Trashed', 'edd', 'stream' ),
|
113 |
+
'untrashed' => _x( 'Restored', 'edd', 'stream' ),
|
114 |
+
'generated' => _x( 'Generated', 'edd', 'stream' ),
|
115 |
+
'imported' => _x( 'Imported', 'edd', 'stream' ),
|
116 |
+
'exported' => _x( 'Exported', 'edd', 'stream' ),
|
117 |
+
'revoked' => _x( 'Revoked', 'edd', 'stream' ),
|
118 |
+
);
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Return translated context labels
|
123 |
+
*
|
124 |
+
* @return array Context label translations
|
125 |
+
*/
|
126 |
+
public static function get_context_labels() {
|
127 |
+
return array(
|
128 |
+
'downloads' => _x( 'Downloads', 'edd', 'stream' ),
|
129 |
+
'download_category' => _x( 'Categories', 'edd', 'stream' ),
|
130 |
+
'download_tag' => _x( 'Tags', 'edd', 'stream' ),
|
131 |
+
'discounts' => _x( 'Discounts', 'edd', 'stream' ),
|
132 |
+
'reports' => _x( 'Reports', 'edd', 'stream' ),
|
133 |
+
'api_keys' => _x( 'API Keys', 'edd', 'stream' ),
|
134 |
+
//'payments' => _x( 'Payments', 'edd', 'stream' ),
|
135 |
+
);
|
136 |
+
}
|
137 |
+
|
138 |
+
/**
|
139 |
+
* Add action links to Stream drop row in admin list screen
|
140 |
+
*
|
141 |
+
* @filter wp_stream_action_links_{connector}
|
142 |
+
*
|
143 |
+
* @param array $links Previous links registered
|
144 |
+
* @param object $record Stream record
|
145 |
+
*
|
146 |
+
* @return array Action links
|
147 |
+
*/
|
148 |
+
public static function action_links( $links, $record ) {
|
149 |
+
if ( in_array( $record->context, array( 'downloads' ) ) ) {
|
150 |
+
$links = WP_Stream_Connector_Posts::action_links( $links, $record );
|
151 |
+
} elseif ( in_array( $record->context, array( 'discounts' ) ) ) {
|
152 |
+
$post_type_label = get_post_type_labels( get_post_type_object( 'edd_discount' ) )->singular_name;
|
153 |
+
$base = admin_url( 'edit.php?post_type=download&page=edd-discounts' );
|
154 |
+
|
155 |
+
$links[ sprintf( __( 'Edit %s', 'stream' ), $post_type_label ) ] = add_query_arg(
|
156 |
+
array(
|
157 |
+
'edd-action' => 'edit_discount',
|
158 |
+
'discount' => $record->object_id,
|
159 |
+
),
|
160 |
+
$base
|
161 |
+
);
|
162 |
+
|
163 |
+
if ( 'active' === get_post( $record->object_id )->post_status ) {
|
164 |
+
$links[ sprintf( __( 'Deactivate %s', 'stream' ), $post_type_label ) ] = add_query_arg(
|
165 |
+
array(
|
166 |
+
'edd-action' => 'deactivate_discount',
|
167 |
+
'discount' => $record->object_id,
|
168 |
+
),
|
169 |
+
$base
|
170 |
+
);
|
171 |
+
} else {
|
172 |
+
$links[ sprintf( __( 'Activate %s', 'stream' ), $post_type_label ) ] = add_query_arg(
|
173 |
+
array(
|
174 |
+
'edd-action' => 'activate_discount',
|
175 |
+
'discount' => $record->object_id,
|
176 |
+
),
|
177 |
+
$base
|
178 |
+
);
|
179 |
+
}
|
180 |
+
} elseif ( in_array( $record->context, array( 'download_category', 'download_tag' ) ) ) {
|
181 |
+
$tax_label = get_taxonomy_labels( get_taxonomy( $record->context ) )->singular_name;
|
182 |
+
$links[ sprintf( __( 'Edit %s', 'stream' ), $tax_label ) ] = get_edit_term_link( $record->object_id, wp_stream_get_meta( $record, 'taxonomy', true ) );
|
183 |
+
} elseif ( 'api_keys' === $record->context ) {
|
184 |
+
$user = new WP_User( $record->object_id );
|
185 |
+
|
186 |
+
if ( apply_filters( 'edd_api_log_requests', true ) ) {
|
187 |
+
$links[ __( 'View API Log', 'stream' ) ] = add_query_arg( array( 'view' => 'api_requests', 'post_type' => 'download', 'page' => 'edd-reports', 'tab' => 'logs', 's' => $user->user_email ), 'edit.php' );
|
188 |
+
}
|
189 |
+
|
190 |
+
$links[ __( 'Revoke', 'stream' ) ] = add_query_arg( array( 'post_type' => 'download', 'user_id' => $record->object_id, 'edd_action' => 'process_api_key', 'edd_api_process' => 'revoke' ), 'edit.php' );
|
191 |
+
$links[ __( 'Reissue', 'stream' ) ] = add_query_arg( array( 'post_type' => 'download', 'user_id' => $record->object_id, 'edd_action' => 'process_api_key', 'edd_api_process' => 'regenerate' ), 'edit.php' );
|
192 |
+
}
|
193 |
+
|
194 |
+
return $links;
|
195 |
+
}
|
196 |
+
|
197 |
+
public static function register() {
|
198 |
+
parent::register();
|
199 |
+
|
200 |
+
add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
|
201 |
+
|
202 |
+
self::$options = array(
|
203 |
+
'edd_settings' => null,
|
204 |
+
);
|
205 |
+
}
|
206 |
+
|
207 |
+
public static function callback_update_option( $option, $old, $new ) {
|
208 |
+
self::check( $option, $old, $new );
|
209 |
+
}
|
210 |
+
|
211 |
+
public static function callback_add_option( $option, $val ) {
|
212 |
+
self::check( $option, null, $val );
|
213 |
+
}
|
214 |
+
|
215 |
+
public static function callback_delete_option( $option ) {
|
216 |
+
self::check( $option, null, null );
|
217 |
+
}
|
218 |
+
|
219 |
+
public static function callback_update_site_option( $option, $old, $new ) {
|
220 |
+
self::check( $option, $old, $new );
|
221 |
+
}
|
222 |
+
|
223 |
+
public static function callback_add_site_option( $option, $val ) {
|
224 |
+
self::check( $option, null, $val );
|
225 |
+
}
|
226 |
+
|
227 |
+
public static function callback_delete_site_option( $option ) {
|
228 |
+
self::check( $option, null, null );
|
229 |
+
}
|
230 |
+
|
231 |
+
public static function check( $option, $old_value, $new_value ) {
|
232 |
+
if ( ! array_key_exists( $option, self::$options ) ) {
|
233 |
+
return;
|
234 |
+
}
|
235 |
+
|
236 |
+
$replacement = str_replace( '-', '_', $option );
|
237 |
+
|
238 |
+
if ( method_exists( __CLASS__, 'check_' . $replacement ) ) {
|
239 |
+
call_user_func( array( __CLASS__, 'check_' . $replacement ), $old_value, $new_value );
|
240 |
+
} else {
|
241 |
+
$data = self::$options[ $option ];
|
242 |
+
$option_title = $data['label'];
|
243 |
+
$context = isset( $data['context'] ) ? $data['context'] : 'settings';
|
244 |
+
|
245 |
+
self::log(
|
246 |
+
__( '"%s" setting updated', 'stream' ),
|
247 |
+
compact( 'option_title', 'option', 'old_value', 'new_value' ),
|
248 |
+
null,
|
249 |
+
$context,
|
250 |
+
isset( $data['action'] ) ? $data['action'] : 'updated'
|
251 |
+
);
|
252 |
+
}
|
253 |
+
}
|
254 |
+
|
255 |
+
public static function check_edd_settings( $old_value, $new_value ) {
|
256 |
+
$options = array();
|
257 |
+
|
258 |
+
if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
|
259 |
+
return;
|
260 |
+
}
|
261 |
+
|
262 |
+
foreach ( self::get_changed_keys( $old_value, $new_value, 0 ) as $field_key => $field_value ) {
|
263 |
+
$options[ $field_key ] = $field_value;
|
264 |
+
}
|
265 |
+
|
266 |
+
$settings = edd_get_registered_settings();
|
267 |
+
|
268 |
+
foreach ( $options as $option => $option_value ) {
|
269 |
+
$field = null;
|
270 |
+
|
271 |
+
if ( 'banned_email' === $option ) {
|
272 |
+
$field = array(
|
273 |
+
'name' => _x( 'Banned emails', 'edd', 'stream' ),
|
274 |
+
);
|
275 |
+
$page = 'edd-tools';
|
276 |
+
$tab = 'general';
|
277 |
+
} else {
|
278 |
+
$page = 'edd-settings';
|
279 |
+
|
280 |
+
foreach ( $settings as $tab => $fields ) {
|
281 |
+
if ( isset( $fields[ $option ] ) ) {
|
282 |
+
$field = $fields[ $option ];
|
283 |
+
break;
|
284 |
+
}
|
285 |
+
}
|
286 |
+
}
|
287 |
+
|
288 |
+
if ( empty( $field ) ) {
|
289 |
+
continue;
|
290 |
+
}
|
291 |
+
|
292 |
+
self::log(
|
293 |
+
__( '"%s" setting updated', 'stream' ),
|
294 |
+
array(
|
295 |
+
'option_title' => $field['name'],
|
296 |
+
'option' => $option,
|
297 |
+
'old_value' => maybe_serialize( $old_value ),
|
298 |
+
'value' => maybe_serialize( $new_value ),
|
299 |
+
'tab' => $tab,
|
300 |
+
),
|
301 |
+
null,
|
302 |
+
'settings',
|
303 |
+
'updated'
|
304 |
+
);
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
/**
|
309 |
+
* Override connector log for our own Settings / Actions
|
310 |
+
*
|
311 |
+
* @param array $data
|
312 |
+
*
|
313 |
+
* @return array|bool
|
314 |
+
*/
|
315 |
+
public static function log_override( $data ) {
|
316 |
+
if ( ! is_array( $data ) ) {
|
317 |
+
return $data;
|
318 |
+
}
|
319 |
+
|
320 |
+
if ( 'posts' === $data['connector'] && 'download' === $data['context'] ) {
|
321 |
+
// Download posts operations
|
322 |
+
$data['context'] = 'downloads';
|
323 |
+
$data['connector'] = self::$name;
|
324 |
+
} elseif ( 'posts' === $data['connector'] && 'edd_discount' === $data['context'] ) {
|
325 |
+
// Discount posts operations
|
326 |
+
if ( self::$is_discount_status_change ) {
|
327 |
+
return false;
|
328 |
+
}
|
329 |
+
|
330 |
+
if ( 'deleted' === $data['action'] ) {
|
331 |
+
$data['message'] = __( '"%1s" discount deleted', 'stream' );
|
332 |
+
}
|
333 |
+
|
334 |
+
$data['context'] = 'discounts';
|
335 |
+
$data['connector'] = self::$name;
|
336 |
+
} elseif ( 'posts' === $data['connector'] && 'edd_payment' === $data['context'] ) {
|
337 |
+
// Payment posts operations
|
338 |
+
return false; // Do not track payments, they're well logged!
|
339 |
+
} elseif ( 'posts' === $data['connector'] && 'edd_log' === $data['context'] ) {
|
340 |
+
// Logging operations
|
341 |
+
return false; // Do not track notes, because they're basically logs
|
342 |
+
} elseif ( 'comments' === $data['connector'] && 'edd_payment' === $data['context'] ) {
|
343 |
+
// Payment notes ( comments ) operations
|
344 |
+
return false; // Do not track notes, because they're basically logs
|
345 |
+
} elseif ( 'taxonomies' === $data['connector'] && 'download_category' === $data['context'] ) {
|
346 |
+
$data['connector'] = self::$name;
|
347 |
+
} elseif ( 'taxonomies' === $data['connector'] && 'download_tag' === $data['contexts'] ) {
|
348 |
+
$data['connector'] = self::$name;
|
349 |
+
} elseif ( 'taxonomies' === $data['connector'] && 'edd_log_type' === $data['contexts'] ) {
|
350 |
+
return false;
|
351 |
+
} elseif ( 'settings' === $data['connector'] && 'edd_settings' === $data['args']['option'] ) {
|
352 |
+
return false;
|
353 |
+
}
|
354 |
+
|
355 |
+
return $data;
|
356 |
+
}
|
357 |
+
|
358 |
+
public static function callback_edd_pre_update_discount_status( $code_id, $new_status ) {
|
359 |
+
self::$is_discount_status_change = true;
|
360 |
+
|
361 |
+
self::log(
|
362 |
+
sprintf(
|
363 |
+
__( '"%1$s" discount %2$s', 'stream' ),
|
364 |
+
get_post( $code_id )->post_title,
|
365 |
+
'active' === $new_status ? __( 'activated', 'stream' ) : __( 'deactivated', 'stream' )
|
366 |
+
),
|
367 |
+
array(
|
368 |
+
'post_id' => $code_id,
|
369 |
+
'status' => $new_status,
|
370 |
+
),
|
371 |
+
$code_id,
|
372 |
+
'discounts',
|
373 |
+
'updated'
|
374 |
+
);
|
375 |
+
}
|
376 |
+
|
377 |
+
private static function callback_edd_generate_pdf() {
|
378 |
+
self::report_generated( 'pdf' );
|
379 |
+
}
|
380 |
+
public static function callback_edd_earnings_export() {
|
381 |
+
self::report_generated( 'earnings' );
|
382 |
+
}
|
383 |
+
public static function callback_edd_payment_export() {
|
384 |
+
self::report_generated( 'payments' );
|
385 |
+
}
|
386 |
+
public static function callback_edd_email_export() {
|
387 |
+
self::report_generated( 'emails' );
|
388 |
+
}
|
389 |
+
public static function callback_edd_downloads_history_export() {
|
390 |
+
self::report_generated( 'download-history' );
|
391 |
+
}
|
392 |
+
|
393 |
+
private static function report_generated( $type ) {
|
394 |
+
if ( 'pdf' === $type ) {
|
395 |
+
$label = __( 'Sales and Earnings', 'stream' );
|
396 |
+
} elseif ( 'earnings' ) {
|
397 |
+
$label = __( 'Earnings', 'stream' );
|
398 |
+
} elseif ( 'payments' ) {
|
399 |
+
$label = __( 'Payments', 'stream' );
|
400 |
+
} elseif ( 'emails' ) {
|
401 |
+
$label = __( 'Emails', 'stream' );
|
402 |
+
} elseif ( 'download-history' ) {
|
403 |
+
$label = __( 'Download History', 'stream' );
|
404 |
+
}
|
405 |
+
|
406 |
+
self::log(
|
407 |
+
sprintf(
|
408 |
+
__( 'Generated %s report', 'stream' ),
|
409 |
+
$label
|
410 |
+
),
|
411 |
+
array(
|
412 |
+
'type' => $type,
|
413 |
+
),
|
414 |
+
null,
|
415 |
+
'reports',
|
416 |
+
'generated'
|
417 |
+
);
|
418 |
+
}
|
419 |
+
|
420 |
+
public static function callback_edd_export_settings() {
|
421 |
+
self::log(
|
422 |
+
__( 'Exported Settings', 'stream' ),
|
423 |
+
array(),
|
424 |
+
null,
|
425 |
+
'settings',
|
426 |
+
'exported'
|
427 |
+
);
|
428 |
+
}
|
429 |
+
|
430 |
+
public static function callback_edd_import_settings() {
|
431 |
+
self::log(
|
432 |
+
__( 'Imported Settings', 'stream' ),
|
433 |
+
array(),
|
434 |
+
null,
|
435 |
+
'settings',
|
436 |
+
'imported'
|
437 |
+
);
|
438 |
+
}
|
439 |
+
|
440 |
+
public static function callback_update_user_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
|
441 |
+
self::meta( $object_id, $meta_key, $_meta_value );
|
442 |
+
}
|
443 |
+
|
444 |
+
public static function callback_add_user_meta( $object_id, $meta_key, $_meta_value ) {
|
445 |
+
self::meta( $object_id, $meta_key, $_meta_value, true );
|
446 |
+
}
|
447 |
+
|
448 |
+
public static function callback_delete_user_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
|
449 |
+
self::meta( $object_id, $meta_key, null );
|
450 |
+
}
|
451 |
+
|
452 |
+
public static function meta( $object_id, $key, $value, $is_add = false ) {
|
453 |
+
if ( ! in_array( $key, self::$user_meta ) ) {
|
454 |
+
return false;
|
455 |
+
}
|
456 |
+
|
457 |
+
$key = str_replace( '-', '_', $key );
|
458 |
+
|
459 |
+
if ( method_exists( __CLASS__, 'meta_' . $key ) ) {
|
460 |
+
return call_user_func( array( __CLASS__, 'meta_' . $key ), $object_id, $value, $is_add );
|
461 |
+
}
|
462 |
+
}
|
463 |
+
|
464 |
+
private static function meta_edd_user_public_key( $user_id, $value, $is_add = false ) {
|
465 |
+
if ( is_null( $value ) ) {
|
466 |
+
$action = 'revoked';
|
467 |
+
$action_title = __( 'revoked', 'stream' );
|
468 |
+
} elseif ( $is_add ) {
|
469 |
+
$action = 'created';
|
470 |
+
$action_title = __( 'created', 'stream' );
|
471 |
+
} else {
|
472 |
+
$action = 'updated';
|
473 |
+
$action_title = __( 'updated', 'stream' );
|
474 |
+
}
|
475 |
+
|
476 |
+
self::log(
|
477 |
+
sprintf(
|
478 |
+
__( 'User API Key %s', 'stream' ),
|
479 |
+
$action_title
|
480 |
+
),
|
481 |
+
array(
|
482 |
+
'meta_value' => $value,
|
483 |
+
),
|
484 |
+
$user_id,
|
485 |
+
'api_keys',
|
486 |
+
$action
|
487 |
+
);
|
488 |
+
}
|
489 |
+
|
490 |
+
}
|
connectors/class-wp-stream-connector-editor.php
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Editor extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'editor';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
private static $edited_file = array();
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Register all context hooks
|
28 |
+
*
|
29 |
+
* @return void
|
30 |
+
*/
|
31 |
+
public static function register() {
|
32 |
+
parent::register();
|
33 |
+
add_action( 'load-theme-editor.php', array( __CLASS__, 'get_edition_data' ) );
|
34 |
+
add_action( 'load-plugin-editor.php', array( __CLASS__, 'get_edition_data' ) );
|
35 |
+
add_filter( 'wp_redirect', array( __CLASS__, 'log_changes' ) );
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Return translated connector label
|
40 |
+
*
|
41 |
+
* @return string Translated connector label
|
42 |
+
*/
|
43 |
+
public static function get_label() {
|
44 |
+
return __( 'Editor', 'stream' );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Return translated action labels
|
49 |
+
*
|
50 |
+
* @return array Action label translations
|
51 |
+
*/
|
52 |
+
public static function get_action_labels() {
|
53 |
+
return array(
|
54 |
+
'updated' => __( 'Updated', 'stream' ),
|
55 |
+
);
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Return translated context labels
|
60 |
+
*
|
61 |
+
* @return array Context label translations
|
62 |
+
*/
|
63 |
+
public static function get_context_labels() {
|
64 |
+
/**
|
65 |
+
* Filter available context labels for the Editor connector
|
66 |
+
*
|
67 |
+
* @return array Array of context slugs and their translated labels
|
68 |
+
*/
|
69 |
+
return apply_filters(
|
70 |
+
'wp_stream_editor_context_labels',
|
71 |
+
array(
|
72 |
+
'themes' => __( 'Themes', 'stream' ),
|
73 |
+
'plugins' => __( 'Plugins', 'stream' ),
|
74 |
+
)
|
75 |
+
);
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Get the context based on wp_redirect location
|
80 |
+
*
|
81 |
+
* @param string $location The URL of the redirect
|
82 |
+
* @return string Context slug
|
83 |
+
*/
|
84 |
+
public static function get_context( $location ) {
|
85 |
+
$context = null;
|
86 |
+
|
87 |
+
if ( false !== strpos( $location, 'theme-editor.php' ) ) {
|
88 |
+
$context = 'themes';
|
89 |
+
}
|
90 |
+
|
91 |
+
if ( false !== strpos( $location, 'plugin-editor.php' ) ) {
|
92 |
+
$context = 'plugins';
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Filter available contexts for the Editor connector
|
97 |
+
*
|
98 |
+
* @param string $context Context slug
|
99 |
+
* @param string $location The URL of the redirect
|
100 |
+
* @return string Context slug
|
101 |
+
*/
|
102 |
+
return apply_filters( 'wp_stream_editor_context', $context, $location );
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* Get the message format for file updates
|
107 |
+
*
|
108 |
+
* @return string Translated string
|
109 |
+
*/
|
110 |
+
public static function get_message() {
|
111 |
+
return _x(
|
112 |
+
'"%1$s" in "%2$s" updated',
|
113 |
+
'1: File name, 2: Theme/plugin name',
|
114 |
+
'stream'
|
115 |
+
);
|
116 |
+
}
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Add action links to Stream drop row in admin list screen
|
120 |
+
*
|
121 |
+
* @filter wp_stream_action_links_{connector}
|
122 |
+
*
|
123 |
+
* @param array $links Previous links registered
|
124 |
+
* @param object $record Stream record
|
125 |
+
*
|
126 |
+
* @return array Action links
|
127 |
+
*/
|
128 |
+
public static function action_links( $links, $record ) {
|
129 |
+
if ( current_user_can( 'edit_theme_options' ) ) {
|
130 |
+
$file_name = wp_stream_get_meta( $record, 'file', true );
|
131 |
+
$file_path = wp_stream_get_meta( $record, 'file_path', true );
|
132 |
+
|
133 |
+
if ( ! empty( $file_name ) && ! empty( $file_path ) ) {
|
134 |
+
$theme_slug = wp_stream_get_meta( $record, 'theme_slug', true );
|
135 |
+
$plugin_slug = wp_stream_get_meta( $record, 'plugin_slug', true );
|
136 |
+
$theme_exists = ( ! empty( $theme_slug ) && file_exists( $file_path ) );
|
137 |
+
$plugin_exists = ( ! empty( $plugin_slug ) && file_exists( $file_path ) );
|
138 |
+
|
139 |
+
if ( $theme_exists ) {
|
140 |
+
$links[ __( 'Edit File', 'stream' ) ] = add_query_arg(
|
141 |
+
array(
|
142 |
+
'theme' => urlencode( $theme_slug ),
|
143 |
+
'file' => urlencode( $file_name ),
|
144 |
+
),
|
145 |
+
self_admin_url( 'theme-editor.php' )
|
146 |
+
);
|
147 |
+
|
148 |
+
$links[ __( 'Theme Details', 'stream' ) ] = add_query_arg(
|
149 |
+
array(
|
150 |
+
'theme' => urlencode( $theme_slug ),
|
151 |
+
),
|
152 |
+
self_admin_url( 'themes.php' )
|
153 |
+
);
|
154 |
+
}
|
155 |
+
|
156 |
+
if ( $plugin_exists ) {
|
157 |
+
$links[ __( 'Edit File', 'stream' ) ] = add_query_arg(
|
158 |
+
array(
|
159 |
+
'plugin' => urlencode( $plugin_slug ),
|
160 |
+
'file' => urlencode( str_ireplace( trailingslashit( WP_PLUGIN_DIR ), '', $file_path ) ),
|
161 |
+
),
|
162 |
+
self_admin_url( 'plugin-editor.php' )
|
163 |
+
);
|
164 |
+
}
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
return $links;
|
169 |
+
}
|
170 |
+
|
171 |
+
/**
|
172 |
+
* Retrieves data submitted on the screen, and prepares it for the appropriate context type
|
173 |
+
*
|
174 |
+
* @action load-theme-editor.php
|
175 |
+
* @action load-plugin-editor.php
|
176 |
+
* @return void
|
177 |
+
*/
|
178 |
+
public static function get_edition_data() {
|
179 |
+
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] || 'update' !== wp_stream_filter_input( INPUT_POST, 'action' ) ) {
|
180 |
+
return;
|
181 |
+
}
|
182 |
+
|
183 |
+
if ( $slug = wp_stream_filter_input( INPUT_POST, 'theme' ) ) {
|
184 |
+
self::$edited_file = self::get_theme_data( $slug );
|
185 |
+
}
|
186 |
+
|
187 |
+
if ( $slug = wp_stream_filter_input( INPUT_POST, 'plugin' ) ) {
|
188 |
+
self::$edited_file = self::get_plugin_data( $slug );
|
189 |
+
}
|
190 |
+
}
|
191 |
+
|
192 |
+
/**
|
193 |
+
* Retrieve theme data needed for the log message
|
194 |
+
*
|
195 |
+
* @param string $slug The theme slug (e.g. twentyfourteen)
|
196 |
+
* @return mixed $output Compacted variables
|
197 |
+
*/
|
198 |
+
public static function get_theme_data( $slug ) {
|
199 |
+
$theme = wp_get_theme( $slug );
|
200 |
+
|
201 |
+
if ( ! $theme->exists() || ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) ) {
|
202 |
+
return;
|
203 |
+
}
|
204 |
+
|
205 |
+
$allowed_files = $theme->get_files( 'php', 1 );
|
206 |
+
$style_files = $theme->get_files( 'css' );
|
207 |
+
$allowed_files['style.css'] = $style_files['style.css'];
|
208 |
+
$file = wp_stream_filter_input( INPUT_POST, 'file' );
|
209 |
+
|
210 |
+
if ( empty( $file ) ) {
|
211 |
+
$file_name = 'style.css';
|
212 |
+
$file_path = $allowed_files['style.css'];
|
213 |
+
} else {
|
214 |
+
$file_name = $file;
|
215 |
+
$file_path = sprintf( '%s/%s', $theme->get_stylesheet_directory(), $file_name );
|
216 |
+
}
|
217 |
+
|
218 |
+
$file_contents_before = file_get_contents( $file_path );
|
219 |
+
|
220 |
+
$name = $theme->get( 'Name' );
|
221 |
+
|
222 |
+
$output = compact(
|
223 |
+
'file_name',
|
224 |
+
'file_path',
|
225 |
+
'file_contents_before',
|
226 |
+
'slug',
|
227 |
+
'name'
|
228 |
+
);
|
229 |
+
|
230 |
+
return $output;
|
231 |
+
}
|
232 |
+
|
233 |
+
/**
|
234 |
+
* Retrieve plugin data needed for the log message
|
235 |
+
*
|
236 |
+
* @param string $slug The plugin file base name (e.g. akismet/akismet.php)
|
237 |
+
* @return mixed $output Compacted variables
|
238 |
+
*/
|
239 |
+
public static function get_plugin_data( $slug ) {
|
240 |
+
$base = null;
|
241 |
+
$name = null;
|
242 |
+
$slug = current( explode( '/', $slug ) );
|
243 |
+
$file_name = wp_stream_filter_input( INPUT_POST, 'file' );
|
244 |
+
$file_path = WP_PLUGIN_DIR . '/' . $file_name;
|
245 |
+
$file_contents_before = file_get_contents( $file_path );
|
246 |
+
|
247 |
+
$plugins = get_plugins();
|
248 |
+
|
249 |
+
foreach ( $plugins as $key => $plugin_data ) {
|
250 |
+
if ( 0 === strpos( $key, $slug ) ) {
|
251 |
+
$base = $key;
|
252 |
+
$name = $plugin_data['Name'];
|
253 |
+
break;
|
254 |
+
}
|
255 |
+
}
|
256 |
+
|
257 |
+
$file_name = str_ireplace( trailingslashit( $slug ), '', $file_name );
|
258 |
+
$slug = ! empty( $base ) ? $base : $slug;
|
259 |
+
|
260 |
+
$output = compact(
|
261 |
+
'file_name',
|
262 |
+
'file_path',
|
263 |
+
'file_contents_before',
|
264 |
+
'slug',
|
265 |
+
'name'
|
266 |
+
);
|
267 |
+
|
268 |
+
return $output;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* @filter wp_redirect
|
273 |
+
*/
|
274 |
+
public static function log_changes( $location ) {
|
275 |
+
if ( ! empty( self::$edited_file ) ) {
|
276 |
+
if ( file_get_contents( self::$edited_file['file_path'] ) !== self::$edited_file['file_contents_before'] ) {
|
277 |
+
$context = self::get_context( $location );
|
278 |
+
|
279 |
+
switch ( $context ) {
|
280 |
+
case 'themes':
|
281 |
+
$name_key = 'theme_name';
|
282 |
+
$slug_key = 'theme_slug';
|
283 |
+
break;
|
284 |
+
case 'plugins':
|
285 |
+
$name_key = 'plugin_name';
|
286 |
+
$slug_key = 'plugin_slug';
|
287 |
+
break;
|
288 |
+
default:
|
289 |
+
$name_key = 'name';
|
290 |
+
$slug_key = 'slug';
|
291 |
+
}
|
292 |
+
|
293 |
+
self::log(
|
294 |
+
self::get_message(),
|
295 |
+
array(
|
296 |
+
'file' => (string) self::$edited_file['file_name'],
|
297 |
+
$name_key => (string) self::$edited_file['name'],
|
298 |
+
$slug_key => (string) self::$edited_file['slug'],
|
299 |
+
'file_path' => (string) self::$edited_file['file_path'],
|
300 |
+
),
|
301 |
+
null,
|
302 |
+
$context,
|
303 |
+
'updated'
|
304 |
+
);
|
305 |
+
}
|
306 |
+
}
|
307 |
+
|
308 |
+
return $location;
|
309 |
+
}
|
310 |
+
|
311 |
+
}
|
connectors/class-wp-stream-connector-gravityforms.php
ADDED
@@ -0,0 +1,729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_GravityForms extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'gravityforms';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '1.8.8';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'gform_after_save_form',
|
26 |
+
'gform_pre_confirmation_save',
|
27 |
+
'gform_pre_notification_save',
|
28 |
+
'gform_notification_delete',
|
29 |
+
'gform_confirmation_delete',
|
30 |
+
'gform_notification_status',
|
31 |
+
'gform_confirmation_status',
|
32 |
+
'gform_form_status_change',
|
33 |
+
'gform_form_reset_views',
|
34 |
+
'gform_before_delete_form',
|
35 |
+
'gform_form_trash',
|
36 |
+
'gform_form_restore',
|
37 |
+
'gform_form_duplicate',
|
38 |
+
'gform_export_separator', // Export entries
|
39 |
+
'gform_export_options', // Export forms
|
40 |
+
'gform_import_form_xml_options', // Import
|
41 |
+
'gform_delete_lead',
|
42 |
+
'gform_insert_note',
|
43 |
+
'gform_delete_note',
|
44 |
+
'gform_update_status',
|
45 |
+
'gform_update_is_read',
|
46 |
+
'gform_update_is_starred',
|
47 |
+
'update_option',
|
48 |
+
'add_option',
|
49 |
+
'delete_option',
|
50 |
+
'update_site_option',
|
51 |
+
'add_site_option',
|
52 |
+
'delete_site_option',
|
53 |
+
);
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Tracked option keys
|
57 |
+
*
|
58 |
+
* @var array
|
59 |
+
*/
|
60 |
+
public static $options = array();
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Tracking registered Settings, with overridden data
|
64 |
+
*
|
65 |
+
* @var array
|
66 |
+
*/
|
67 |
+
public static $options_override = array();
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
71 |
+
*
|
72 |
+
* @return bool
|
73 |
+
*/
|
74 |
+
public static function is_dependency_satisfied() {
|
75 |
+
if ( class_exists( 'GFForms' ) && version_compare( GFCommon::$version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
76 |
+
return true;
|
77 |
+
}
|
78 |
+
|
79 |
+
return false;
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Return translated connector label
|
84 |
+
*
|
85 |
+
* @return string Translated connector label
|
86 |
+
*/
|
87 |
+
public static function get_label() {
|
88 |
+
return _x( 'Gravity Forms', 'gravityforms', 'stream' );
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Return translated action labels
|
93 |
+
*
|
94 |
+
* @return array Action label translations
|
95 |
+
*/
|
96 |
+
public static function get_action_labels() {
|
97 |
+
return array(
|
98 |
+
'created' => _x( 'Created', 'gravityforms', 'stream' ),
|
99 |
+
'updated' => _x( 'Updated', 'gravityforms', 'stream' ),
|
100 |
+
'exported' => _x( 'Exported', 'gravityforms', 'stream' ),
|
101 |
+
'imported' => _x( 'Imported', 'gravityforms', 'stream' ),
|
102 |
+
'added' => _x( 'Added', 'gravityforms', 'stream' ),
|
103 |
+
'deleted' => _x( 'Deleted', 'gravityforms', 'stream' ),
|
104 |
+
'trashed' => _x( 'Trashed', 'gravityforms', 'stream' ),
|
105 |
+
'untrashed' => _x( 'Restored', 'gravityforms', 'stream' ),
|
106 |
+
'duplicated' => _x( 'Duplicated', 'gravityforms', 'stream' ),
|
107 |
+
);
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Return translated context labels
|
112 |
+
*
|
113 |
+
* @return array Context label translations
|
114 |
+
*/
|
115 |
+
public static function get_context_labels() {
|
116 |
+
return array(
|
117 |
+
'forms' => _x( 'Forms', 'gravityforms', 'stream' ),
|
118 |
+
'settings' => _x( 'Settings', 'gravityforms', 'stream' ),
|
119 |
+
'export' => _x( 'Import/Export', 'gravityforms', 'stream' ),
|
120 |
+
'entries' => _x( 'Entries', 'gravityforms', 'stream' ),
|
121 |
+
'notes' => _x( 'Notes', 'gravityforms', 'stream' ),
|
122 |
+
);
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Add action links to Stream drop row in admin list screen
|
127 |
+
*
|
128 |
+
* @filter wp_stream_action_links_{connector}
|
129 |
+
*
|
130 |
+
* @param array $links Previous links registered
|
131 |
+
* @param object $record Stream record
|
132 |
+
*
|
133 |
+
* @return array Action links
|
134 |
+
*/
|
135 |
+
public static function action_links( $links, $record ) {
|
136 |
+
if ( 'forms' === $record->context ) {
|
137 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
138 |
+
array(
|
139 |
+
'page' => 'gf_edit_forms',
|
140 |
+
'id' => $record->object_id,
|
141 |
+
),
|
142 |
+
admin_url( 'admin.php' )
|
143 |
+
);
|
144 |
+
} elseif ( 'entries' === $record->context ) {
|
145 |
+
$links[ __( 'View', 'stream' ) ] = add_query_arg(
|
146 |
+
array(
|
147 |
+
'page' => 'gf_entries',
|
148 |
+
'view' => 'entry',
|
149 |
+
'lid' => $record->object_id,
|
150 |
+
'id' => wp_stream_get_meta( $record, 'form_id', true ),
|
151 |
+
),
|
152 |
+
admin_url( 'admin.php' )
|
153 |
+
);
|
154 |
+
} elseif ( 'notes' === $record->context ) {
|
155 |
+
$links[ __( 'View', 'stream' ) ] = add_query_arg(
|
156 |
+
array(
|
157 |
+
'page' => 'gf_entries',
|
158 |
+
'view' => 'entry',
|
159 |
+
'lid' => wp_stream_get_meta( $record, 'lead_id', true ),
|
160 |
+
'id' => wp_stream_get_meta( $record, 'form_id', true ),
|
161 |
+
),
|
162 |
+
admin_url( 'admin.php' )
|
163 |
+
);
|
164 |
+
} elseif ( 'settings' === $record->context ) {
|
165 |
+
$links[ __( 'Edit Settings', 'stream' ) ] = add_query_arg(
|
166 |
+
array(
|
167 |
+
'page' => 'gf_settings',
|
168 |
+
),
|
169 |
+
admin_url( 'admin.php' )
|
170 |
+
);
|
171 |
+
}
|
172 |
+
|
173 |
+
return $links;
|
174 |
+
}
|
175 |
+
|
176 |
+
public static function register() {
|
177 |
+
parent::register();
|
178 |
+
|
179 |
+
self::$options = array(
|
180 |
+
'rg_gforms_disable_css' => array(
|
181 |
+
'label' => _x( 'Output CSS', 'gravityforms', 'stream' ),
|
182 |
+
),
|
183 |
+
'rg_gforms_enable_html5' => array(
|
184 |
+
'label' => _x( 'Output HTML5', 'gravityforms', 'stream' ),
|
185 |
+
),
|
186 |
+
'gform_enable_noconflict' => array(
|
187 |
+
'label' => _x( 'No-Conflict Mode', 'gravityforms', 'stream' ),
|
188 |
+
),
|
189 |
+
'rg_gforms_currency' => array(
|
190 |
+
'label' => _x( 'Currency', 'gravityforms', 'stream' ),
|
191 |
+
),
|
192 |
+
'rg_gforms_captcha_public_key' => array(
|
193 |
+
'label' => _x( 'reCAPTCHA Public Key', 'gravityforms', 'stream' ),
|
194 |
+
),
|
195 |
+
'rg_gforms_captcha_private_key' => array(
|
196 |
+
'label' => _x( 'reCAPTCHA Private Key', 'gravityforms', 'stream' ),
|
197 |
+
),
|
198 |
+
'rg_gforms_key' => null,
|
199 |
+
);
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Track Create/Update actions on Forms
|
204 |
+
*
|
205 |
+
* @param $form
|
206 |
+
* @param $is_new
|
207 |
+
*/
|
208 |
+
public static function callback_gform_after_save_form( $form, $is_new ) {
|
209 |
+
$title = $form['title'];
|
210 |
+
$id = $form['id'];
|
211 |
+
|
212 |
+
self::log(
|
213 |
+
sprintf(
|
214 |
+
__( '"%1$s" form %2$s', 'stream' ),
|
215 |
+
$title,
|
216 |
+
$is_new ? __( 'created', 'stream' ) : __( 'updated', 'stream' )
|
217 |
+
),
|
218 |
+
array(
|
219 |
+
'action' => $is_new,
|
220 |
+
'id' => $id,
|
221 |
+
'title' => $title,
|
222 |
+
),
|
223 |
+
$id,
|
224 |
+
'forms',
|
225 |
+
$is_new ? 'created' : 'updated'
|
226 |
+
);
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Track saving form confirmations
|
231 |
+
*
|
232 |
+
* @param $confirmation
|
233 |
+
* @param $form
|
234 |
+
* @param bool $is_new
|
235 |
+
*
|
236 |
+
* @return mixed
|
237 |
+
*/
|
238 |
+
public static function callback_gform_pre_confirmation_save( $confirmation, $form, $is_new = true ) {
|
239 |
+
if ( ! isset( $is_new ) ) {
|
240 |
+
$is_new = false;
|
241 |
+
}
|
242 |
+
|
243 |
+
self::log(
|
244 |
+
sprintf(
|
245 |
+
__( '"%1$s" confirmation %2$s for "%3$s"', 'stream' ),
|
246 |
+
$confirmation['name'],
|
247 |
+
$is_new ? __( 'created', 'stream' ) : __( 'updated', 'stream' ),
|
248 |
+
$form['title']
|
249 |
+
),
|
250 |
+
array(
|
251 |
+
'is_new' => $is_new,
|
252 |
+
'form_id' => $form['id'],
|
253 |
+
),
|
254 |
+
$form['id'],
|
255 |
+
'forms',
|
256 |
+
'updated'
|
257 |
+
);
|
258 |
+
|
259 |
+
return $confirmation;
|
260 |
+
}
|
261 |
+
|
262 |
+
/**
|
263 |
+
* Track saving form notifications
|
264 |
+
*
|
265 |
+
* @param $notification
|
266 |
+
* @param $form
|
267 |
+
* @param bool $is_new
|
268 |
+
*
|
269 |
+
* @return mixed
|
270 |
+
*/
|
271 |
+
public static function callback_gform_pre_notification_save( $notification, $form, $is_new = true ) {
|
272 |
+
if ( ! isset( $is_new ) ) {
|
273 |
+
$is_new = false;
|
274 |
+
}
|
275 |
+
|
276 |
+
self::log(
|
277 |
+
sprintf(
|
278 |
+
__( '"%1$s" notification %2$s for "%3$s"', 'stream' ),
|
279 |
+
$notification['name'],
|
280 |
+
$is_new ? __( 'created', 'stream' ) : __( 'updated', 'stream' ),
|
281 |
+
$form['title']
|
282 |
+
),
|
283 |
+
array(
|
284 |
+
'is_update' => $is_new,
|
285 |
+
'form_id' => $form['id'],
|
286 |
+
),
|
287 |
+
$form['id'],
|
288 |
+
'forms',
|
289 |
+
'updated'
|
290 |
+
);
|
291 |
+
|
292 |
+
return $notification;
|
293 |
+
}
|
294 |
+
|
295 |
+
/**
|
296 |
+
* Track deletion of notifications
|
297 |
+
*
|
298 |
+
* @param $notification
|
299 |
+
* @param $form
|
300 |
+
*/
|
301 |
+
public static function callback_gform_notification_delete( $notification, $form ) {
|
302 |
+
self::log(
|
303 |
+
sprintf(
|
304 |
+
__( '"%1$s" notification deleted from "%2$s"', 'stream' ),
|
305 |
+
$notification['name'],
|
306 |
+
$form['title']
|
307 |
+
),
|
308 |
+
array(
|
309 |
+
'form_id' => $form['id'],
|
310 |
+
'notification' => $notification,
|
311 |
+
),
|
312 |
+
$form['id'],
|
313 |
+
'forms',
|
314 |
+
'updated'
|
315 |
+
);
|
316 |
+
}
|
317 |
+
|
318 |
+
/**
|
319 |
+
* Track deletion of confirmations
|
320 |
+
*
|
321 |
+
* @param $confirmation
|
322 |
+
* @param $form
|
323 |
+
*/
|
324 |
+
public static function callback_gform_confirmation_delete( $confirmation, $form ) {
|
325 |
+
self::log(
|
326 |
+
sprintf(
|
327 |
+
__( '"%1$s" confirmation deleted from "%2$s"', 'stream' ),
|
328 |
+
$confirmation['name'],
|
329 |
+
$form['title']
|
330 |
+
),
|
331 |
+
array(
|
332 |
+
'form_id' => $form['id'],
|
333 |
+
'confirmation' => $confirmation,
|
334 |
+
),
|
335 |
+
$form['id'],
|
336 |
+
'forms',
|
337 |
+
'updated'
|
338 |
+
);
|
339 |
+
}
|
340 |
+
|
341 |
+
/**
|
342 |
+
* Track status change of confirmations
|
343 |
+
*
|
344 |
+
* @param $confirmation
|
345 |
+
* @param $form
|
346 |
+
* @param $is_active
|
347 |
+
*/
|
348 |
+
public static function callback_gform_confirmation_status( $confirmation, $form, $is_active ) {
|
349 |
+
self::log(
|
350 |
+
sprintf(
|
351 |
+
__( '"%1$s" confirmation %2$s from "%3$s"', 'stream' ),
|
352 |
+
$confirmation['name'],
|
353 |
+
$is_active ? __( 'activated', 'stream' ) : __( 'deactivated', 'stream' ),
|
354 |
+
$form['title']
|
355 |
+
),
|
356 |
+
array(
|
357 |
+
'form_id' => $form['id'],
|
358 |
+
'confirmation' => $confirmation,
|
359 |
+
'is_active' => $is_active,
|
360 |
+
),
|
361 |
+
null,
|
362 |
+
'forms',
|
363 |
+
'updated'
|
364 |
+
);
|
365 |
+
}
|
366 |
+
|
367 |
+
/**
|
368 |
+
* Track status change of confirmations
|
369 |
+
*
|
370 |
+
* @param $id
|
371 |
+
*/
|
372 |
+
public static function callback_gform_form_reset_views( $id ) {
|
373 |
+
$form = self::get_form( $id );
|
374 |
+
|
375 |
+
self::log(
|
376 |
+
__( '"%s" form views reset', 'stream' ),
|
377 |
+
array(
|
378 |
+
'title' => $form['title'],
|
379 |
+
'form_id' => $form['id'],
|
380 |
+
),
|
381 |
+
$form['id'],
|
382 |
+
'forms',
|
383 |
+
'updated'
|
384 |
+
);
|
385 |
+
}
|
386 |
+
|
387 |
+
/**
|
388 |
+
* Track status change of notifications
|
389 |
+
*
|
390 |
+
* @param $notification
|
391 |
+
* @param $form
|
392 |
+
* @param $is_active
|
393 |
+
*/
|
394 |
+
public static function callback_gform_notification_status( $notification, $form, $is_active ) {
|
395 |
+
self::log(
|
396 |
+
sprintf(
|
397 |
+
__( '"%1$s" notification %2$s from "%3$s"', 'stream' ),
|
398 |
+
$notification['name'],
|
399 |
+
$is_active ? __( 'activated', 'stream' ) : __( 'deactivated', 'stream' ),
|
400 |
+
$form['title']
|
401 |
+
),
|
402 |
+
array(
|
403 |
+
'form_id' => $form['id'],
|
404 |
+
'notification' => $notification,
|
405 |
+
'is_active' => $is_active,
|
406 |
+
),
|
407 |
+
$form['id'],
|
408 |
+
'forms',
|
409 |
+
'updated'
|
410 |
+
);
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Track status change of forms
|
415 |
+
*
|
416 |
+
* @param $id
|
417 |
+
* @param $action
|
418 |
+
*/
|
419 |
+
public static function callback_gform_form_status_change( $id, $action ) {
|
420 |
+
$form = self::get_form( $id );
|
421 |
+
$actions = array(
|
422 |
+
'activated' => __( 'Activated', 'stream' ),
|
423 |
+
'deactivated' => __( 'Deactivated', 'stream' ),
|
424 |
+
'trashed' => __( 'Trashed', 'stream' ),
|
425 |
+
'untrashed' => __( 'Restored', 'stream' ),
|
426 |
+
);
|
427 |
+
|
428 |
+
self::log(
|
429 |
+
sprintf(
|
430 |
+
__( '"%1$s" form %2$s', 'stream' ),
|
431 |
+
$form['title'],
|
432 |
+
$actions[ $action ]
|
433 |
+
),
|
434 |
+
array(
|
435 |
+
'form_title' => $form['title'],
|
436 |
+
'form_id' => $id,
|
437 |
+
),
|
438 |
+
$form['id'],
|
439 |
+
'forms',
|
440 |
+
$action
|
441 |
+
);
|
442 |
+
}
|
443 |
+
|
444 |
+
public static function callback_update_option( $option, $old, $new ) {
|
445 |
+
self::check( $option, $old, $new );
|
446 |
+
}
|
447 |
+
|
448 |
+
public static function callback_add_option( $option, $val ) {
|
449 |
+
self::check( $option, null, $val );
|
450 |
+
}
|
451 |
+
|
452 |
+
public static function callback_delete_option( $option ) {
|
453 |
+
self::check( $option, null, null );
|
454 |
+
}
|
455 |
+
|
456 |
+
public static function callback_update_site_option( $option, $old, $new ) {
|
457 |
+
self::check( $option, $old, $new );
|
458 |
+
}
|
459 |
+
|
460 |
+
public static function callback_add_site_option( $option, $val ) {
|
461 |
+
self::check( $option, null, $val );
|
462 |
+
}
|
463 |
+
|
464 |
+
public static function callback_delete_site_option( $option ) {
|
465 |
+
self::check( $option, null, null );
|
466 |
+
}
|
467 |
+
|
468 |
+
public static function check( $option, $old_value, $new_value ) {
|
469 |
+
if ( ! array_key_exists( $option, self::$options ) ) {
|
470 |
+
return;
|
471 |
+
}
|
472 |
+
|
473 |
+
if ( is_null( self::$options[ $option ] ) ) {
|
474 |
+
call_user_func( array( __CLASS__, 'check_' . str_replace( '-', '_', $option ) ), $old_value, $new_value );
|
475 |
+
} else {
|
476 |
+
$data = self::$options[ $option ];
|
477 |
+
$option_title = $data['label'];
|
478 |
+
$context = isset( $data['context'] ) ? $data['context'] : 'settings';
|
479 |
+
|
480 |
+
self::log(
|
481 |
+
__( '"%s" setting updated', 'stream' ),
|
482 |
+
compact( 'option_title', 'option', 'old_value', 'new_value' ),
|
483 |
+
null,
|
484 |
+
$context,
|
485 |
+
isset( $data['action'] ) ? $data['action'] : 'updated'
|
486 |
+
);
|
487 |
+
}
|
488 |
+
}
|
489 |
+
|
490 |
+
public static function check_rg_gforms_key( $old_value, $new_value ) {
|
491 |
+
$is_update = ( $new_value && strlen( $new_value ) );
|
492 |
+
$option = 'rg_gforms_key';
|
493 |
+
|
494 |
+
self::log(
|
495 |
+
sprintf(
|
496 |
+
__( 'Gravity Forms license key %s', 'stream' ),
|
497 |
+
$is_update ? __( 'updated', 'stream' ) : __( 'deleted', 'stream' )
|
498 |
+
),
|
499 |
+
compact( 'option', 'old_value', 'new_value' ),
|
500 |
+
null,
|
501 |
+
'settings',
|
502 |
+
$is_update ? 'updated' : 'deleted'
|
503 |
+
);
|
504 |
+
}
|
505 |
+
|
506 |
+
public static function callback_gform_export_separator( $dummy, $form_id ) {
|
507 |
+
$form = self::get_form( $form_id );
|
508 |
+
|
509 |
+
self::log(
|
510 |
+
__( '"%s" form exported', 'stream' ),
|
511 |
+
array(
|
512 |
+
'form_title' => $form['title'],
|
513 |
+
'form_id' => $form_id,
|
514 |
+
),
|
515 |
+
$form_id,
|
516 |
+
'export',
|
517 |
+
'exported'
|
518 |
+
);
|
519 |
+
|
520 |
+
return $dummy;
|
521 |
+
}
|
522 |
+
|
523 |
+
public static function callback_gform_import_form_xml_options( $dummy ) {
|
524 |
+
self::log(
|
525 |
+
__( 'Import process started', 'stream' ),
|
526 |
+
array(),
|
527 |
+
null,
|
528 |
+
'export',
|
529 |
+
'imported'
|
530 |
+
);
|
531 |
+
|
532 |
+
return $dummy;
|
533 |
+
}
|
534 |
+
|
535 |
+
public static function callback_gform_export_options( $dummy, $forms ) {
|
536 |
+
$ids = wp_list_pluck( $forms, 'id' );
|
537 |
+
$titles = wp_list_pluck( $forms, 'title' );
|
538 |
+
|
539 |
+
self::log(
|
540 |
+
__( 'Export process started for %d forms', 'stream' ),
|
541 |
+
array(
|
542 |
+
'count' => count( $forms ),
|
543 |
+
'ids' => $ids,
|
544 |
+
'titles' => $titles,
|
545 |
+
),
|
546 |
+
null,
|
547 |
+
'export',
|
548 |
+
'imported'
|
549 |
+
);
|
550 |
+
|
551 |
+
return $dummy;
|
552 |
+
}
|
553 |
+
|
554 |
+
public static function callback_gform_before_delete_form( $id ) {
|
555 |
+
$form = self::get_form( $id );
|
556 |
+
|
557 |
+
self::log(
|
558 |
+
__( '"%s" form deleted', 'stream' ),
|
559 |
+
array(
|
560 |
+
'form_title' => $form['title'],
|
561 |
+
'form_id' => $id,
|
562 |
+
),
|
563 |
+
$form['id'],
|
564 |
+
'forms',
|
565 |
+
'deleted'
|
566 |
+
);
|
567 |
+
}
|
568 |
+
|
569 |
+
public static function callback_gform_form_duplicate( $id, $new_id ) {
|
570 |
+
$form = self::get_form( $id );
|
571 |
+
$new = self::get_form( $new_id );
|
572 |
+
|
573 |
+
self::log(
|
574 |
+
__( '"%1$s" form created as duplicate from "%2$s"', 'stream' ),
|
575 |
+
array(
|
576 |
+
'new_form_title' => $new['title'],
|
577 |
+
'form_title' => $form['title'],
|
578 |
+
'form_id' => $id,
|
579 |
+
'new_id' => $new_id,
|
580 |
+
),
|
581 |
+
$new_id,
|
582 |
+
'forms',
|
583 |
+
'duplicated'
|
584 |
+
);
|
585 |
+
}
|
586 |
+
|
587 |
+
public static function callback_gform_delete_lead( $lead_id ) {
|
588 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
589 |
+
$form = self::get_form( $lead['form_id'] );
|
590 |
+
|
591 |
+
self::log(
|
592 |
+
__( 'Lead #%1$d from "%2$s" deleted', 'stream' ),
|
593 |
+
array(
|
594 |
+
'lead_id' => $lead_id,
|
595 |
+
'form_title' => $form['title'],
|
596 |
+
'form_id' => $form['id'],
|
597 |
+
),
|
598 |
+
$lead_id,
|
599 |
+
'entries',
|
600 |
+
'deleted'
|
601 |
+
);
|
602 |
+
}
|
603 |
+
|
604 |
+
public static function callback_gform_insert_note( $note_id, $lead_id, $user_id, $user_name, $note, $note_type ) {
|
605 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
606 |
+
$form = self::get_form( $lead['form_id'] );
|
607 |
+
|
608 |
+
self::log(
|
609 |
+
__( 'Note #%1$d added to lead #%2$d on "%3$s" form', 'stream' ),
|
610 |
+
array(
|
611 |
+
'note_id' => $note_id,
|
612 |
+
'lead_id' => $lead_id,
|
613 |
+
'form_title' => $form['title'],
|
614 |
+
'form_id' => $form['id'],
|
615 |
+
),
|
616 |
+
$note_id,
|
617 |
+
'notes',
|
618 |
+
'added'
|
619 |
+
);
|
620 |
+
}
|
621 |
+
|
622 |
+
public static function callback_gform_delete_note( $note_id, $lead_id ) {
|
623 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
624 |
+
$form = self::get_form( $lead['form_id'] );
|
625 |
+
|
626 |
+
self::log(
|
627 |
+
__( 'Note #%1$d deleted from lead #%2$d on "%3$s" form', 'stream' ),
|
628 |
+
array(
|
629 |
+
'note_id' => $note_id,
|
630 |
+
'lead_id' => $lead_id,
|
631 |
+
'form_title' => $form['title'],
|
632 |
+
'form_id' => $form['id'],
|
633 |
+
),
|
634 |
+
$note_id,
|
635 |
+
'notes',
|
636 |
+
'deleted'
|
637 |
+
);
|
638 |
+
}
|
639 |
+
|
640 |
+
public static function callback_gform_update_status( $lead_id, $status, $prev = '' ) {
|
641 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
642 |
+
$form = self::get_form( $lead['form_id'] );
|
643 |
+
|
644 |
+
if ( 'active' === $status && 'trash' === $prev ) {
|
645 |
+
$status = 'restore';
|
646 |
+
}
|
647 |
+
|
648 |
+
$actions = array(
|
649 |
+
'active' => __( 'activated', 'stream' ),
|
650 |
+
'spam' => __( 'marked as spam', 'stream' ),
|
651 |
+
'trash' => __( 'trashed', 'stream' ),
|
652 |
+
'restore' => __( 'restored', 'stream' ),
|
653 |
+
);
|
654 |
+
|
655 |
+
if ( ! isset( $actions[ $status ] ) ) {
|
656 |
+
return;
|
657 |
+
}
|
658 |
+
|
659 |
+
self::log(
|
660 |
+
sprintf(
|
661 |
+
__( 'Lead #%1$d %2$s on "%3$s" form', 'stream' ),
|
662 |
+
$lead_id,
|
663 |
+
$actions[ $status ],
|
664 |
+
$form['title']
|
665 |
+
),
|
666 |
+
array(
|
667 |
+
'lead_id' => $lead_id,
|
668 |
+
'form_title' => $form['title'],
|
669 |
+
'form_id' => $form['id'],
|
670 |
+
'status' => $status,
|
671 |
+
'prev' => $prev,
|
672 |
+
),
|
673 |
+
$lead_id,
|
674 |
+
'entries',
|
675 |
+
$status
|
676 |
+
);
|
677 |
+
}
|
678 |
+
|
679 |
+
public static function callback_gform_update_is_read( $lead_id, $status ) {
|
680 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
681 |
+
$form = self::get_form( $lead['form_id'] );
|
682 |
+
|
683 |
+
self::log(
|
684 |
+
sprintf(
|
685 |
+
__( 'Lead #%1$d marked as %2$s on "%3$s" form', 'stream' ),
|
686 |
+
$lead_id,
|
687 |
+
$status ? __( 'read', 'stream' ) : __( 'unread', 'stream' ),
|
688 |
+
$form['title']
|
689 |
+
),
|
690 |
+
array(
|
691 |
+
'lead_id' => $lead_id,
|
692 |
+
'form_title' => $form['title'],
|
693 |
+
'form_id' => $form['id'],
|
694 |
+
'status' => $status,
|
695 |
+
),
|
696 |
+
$lead_id,
|
697 |
+
'entries',
|
698 |
+
'updated'
|
699 |
+
);
|
700 |
+
}
|
701 |
+
|
702 |
+
public static function callback_gform_update_is_starred( $lead_id, $status ) {
|
703 |
+
$lead = GFFormsModel::get_lead( $lead_id );
|
704 |
+
$form = self::get_form( $lead['form_id'] );
|
705 |
+
|
706 |
+
self::log(
|
707 |
+
sprintf(
|
708 |
+
__( 'Lead #%1$d %2$s on "%3$s" form', 'stream' ),
|
709 |
+
$lead_id,
|
710 |
+
$status ? __( 'starred', 'stream' ) : __( 'unstarred', 'stream' ),
|
711 |
+
$form['title']
|
712 |
+
),
|
713 |
+
array(
|
714 |
+
'lead_id' => $lead_id,
|
715 |
+
'form_title' => $form['title'],
|
716 |
+
'form_id' => $form['id'],
|
717 |
+
'status' => $status,
|
718 |
+
),
|
719 |
+
$lead_id,
|
720 |
+
'entries',
|
721 |
+
'updated'
|
722 |
+
);
|
723 |
+
}
|
724 |
+
|
725 |
+
private static function get_form( $form_id ) {
|
726 |
+
return reset( GFFormsModel::get_forms_by_id( $form_id ) );
|
727 |
+
}
|
728 |
+
|
729 |
+
}
|
connectors/class-wp-stream-connector-installer.php
ADDED
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Installer extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'installer';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'upgrader_process_complete', // plugins::installed | themes::installed
|
19 |
+
'activate_plugin', // plugins::activated
|
20 |
+
'deactivate_plugin', // plugins::deactivated
|
21 |
+
'switch_theme', // themes::activated
|
22 |
+
'delete_site_transient_update_themes', // themes::deleted
|
23 |
+
'pre_option_uninstall_plugins', // plugins::deleted
|
24 |
+
'pre_set_site_transient_update_plugins',
|
25 |
+
'_core_updated_successfully',
|
26 |
+
);
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Return translated connector label
|
30 |
+
*
|
31 |
+
* @return string Translated connector label
|
32 |
+
*/
|
33 |
+
public static function get_label() {
|
34 |
+
return __( 'Installer', 'stream' );
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Return translated action labels
|
39 |
+
*
|
40 |
+
* @return array Action label translations
|
41 |
+
*/
|
42 |
+
public static function get_action_labels() {
|
43 |
+
return array(
|
44 |
+
'installed' => __( 'Installed', 'stream' ),
|
45 |
+
'activated' => __( 'Activated', 'stream' ),
|
46 |
+
'deactivated' => __( 'Deactivated', 'stream' ),
|
47 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
48 |
+
'updated' => __( 'Updated', 'stream' ),
|
49 |
+
);
|
50 |
+
}
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Return translated context labels
|
54 |
+
*
|
55 |
+
* @return array Context label translations
|
56 |
+
*/
|
57 |
+
public static function get_context_labels() {
|
58 |
+
return array(
|
59 |
+
'plugins' => __( 'Plugins', 'stream' ),
|
60 |
+
'themes' => __( 'Themes', 'stream' ),
|
61 |
+
'wordpress' => __( 'WordPress', 'stream' ),
|
62 |
+
);
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* Add action links to Stream drop row in admin list screen
|
67 |
+
*
|
68 |
+
* @filter wp_stream_action_links_{connector}
|
69 |
+
*
|
70 |
+
* @param array $links Previous links registered
|
71 |
+
* @param object $record Stream record
|
72 |
+
*
|
73 |
+
* @return array Action links
|
74 |
+
*/
|
75 |
+
public static function action_links( $links, $record ) {
|
76 |
+
if ( 'wordpress' === $record->context && 'updated' === $record->action ) {
|
77 |
+
global $wp_version;
|
78 |
+
$version = wp_stream_get_meta( $record, 'new_version', true );
|
79 |
+
if ( $version === $wp_version ) {
|
80 |
+
$links[ __( 'About', 'stream' ) ] = admin_url( 'about.php?updated' );
|
81 |
+
}
|
82 |
+
$links[ __( 'View Release Notes', 'stream' ) ] = esc_url( sprintf( 'http://codex.wordpress.org/Version_%s', $version ) );
|
83 |
+
}
|
84 |
+
return $links;
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Wrapper method for calling get_plugins()
|
89 |
+
*
|
90 |
+
* @return array
|
91 |
+
*/
|
92 |
+
public static function get_plugins() {
|
93 |
+
if ( ! function_exists( 'get_plugins' ) ) {
|
94 |
+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
95 |
+
}
|
96 |
+
|
97 |
+
return get_plugins();
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Log plugin installations
|
102 |
+
*
|
103 |
+
* @action transition_post_status
|
104 |
+
*/
|
105 |
+
public static function callback_upgrader_process_complete( $upgrader, $extra ) {
|
106 |
+
$logs = array();
|
107 |
+
$success = ! is_wp_error( $upgrader->skin->result );
|
108 |
+
$error = null;
|
109 |
+
|
110 |
+
if ( ! $success ) {
|
111 |
+
$errors = $upgrader->skin->result->errors;
|
112 |
+
list( $error ) = reset( $errors );
|
113 |
+
}
|
114 |
+
|
115 |
+
// This would have failed down the road anyway
|
116 |
+
if ( ! isset( $extra['type'] ) ) {
|
117 |
+
return false;
|
118 |
+
}
|
119 |
+
|
120 |
+
$type = $extra['type'];
|
121 |
+
$action = $extra['action'];
|
122 |
+
|
123 |
+
if ( ! in_array( $type, array( 'plugin', 'theme' ) ) ) {
|
124 |
+
return;
|
125 |
+
}
|
126 |
+
|
127 |
+
if ( 'install' === $action ) {
|
128 |
+
if ( 'plugin' === $type ) {
|
129 |
+
$path = $upgrader->plugin_info();
|
130 |
+
if ( ! $path ) {
|
131 |
+
return;
|
132 |
+
}
|
133 |
+
$data = get_plugin_data( $upgrader->skin->result['local_destination'] . '/' . $path );
|
134 |
+
$slug = $upgrader->result['destination_name'];
|
135 |
+
$name = $data['Name'];
|
136 |
+
$version = $data['Version'];
|
137 |
+
} else { // theme
|
138 |
+
$slug = $upgrader->theme_info();
|
139 |
+
if ( ! $slug ) {
|
140 |
+
return;
|
141 |
+
}
|
142 |
+
wp_clean_themes_cache();
|
143 |
+
$theme = wp_get_theme( $slug );
|
144 |
+
$name = $theme->name;
|
145 |
+
$version = $theme->version;
|
146 |
+
}
|
147 |
+
$action = 'installed';
|
148 |
+
$message = _x(
|
149 |
+
'Installed %1$s: %2$s %3$s',
|
150 |
+
'Plugin/theme installation. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
|
151 |
+
'stream'
|
152 |
+
);
|
153 |
+
$logs[] = compact( 'slug', 'name', 'version', 'message', 'action' );
|
154 |
+
} elseif ( 'update' === $action ) {
|
155 |
+
$action = 'updated';
|
156 |
+
$message = _x(
|
157 |
+
'Updated %1$s: %2$s %3$s',
|
158 |
+
'Plugin/theme update. 1: Type (plugin/theme), 2: Plugin/theme name, 3: Plugin/theme version',
|
159 |
+
'stream'
|
160 |
+
);
|
161 |
+
if ( 'plugin' === $type ) {
|
162 |
+
if ( isset( $extra['bulk'] ) && true == $extra['bulk'] ) {
|
163 |
+
$slugs = $extra['plugins'];
|
164 |
+
} else {
|
165 |
+
$slugs = array( $upgrader->skin->plugin );
|
166 |
+
}
|
167 |
+
|
168 |
+
$_plugins = self::get_plugins();
|
169 |
+
|
170 |
+
foreach ( $slugs as $slug ) {
|
171 |
+
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $slug );
|
172 |
+
$name = $plugin_data['Name'];
|
173 |
+
$version = $plugin_data['Version'];
|
174 |
+
$old_version = $_plugins[ $slug ]['Version'];
|
175 |
+
|
176 |
+
$logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
|
177 |
+
}
|
178 |
+
} else { // theme
|
179 |
+
if ( isset( $extra['bulk'] ) && true == $extra['bulk'] ) {
|
180 |
+
$slugs = $extra['themes'];
|
181 |
+
} else {
|
182 |
+
$slugs = array( $upgrader->skin->theme );
|
183 |
+
}
|
184 |
+
foreach ( $slugs as $slug ) {
|
185 |
+
$theme = wp_get_theme( $slug );
|
186 |
+
$stylesheet = $theme['Stylesheet Dir'] . '/style.css';
|
187 |
+
$theme_data = get_file_data( $stylesheet, array( 'Version' => 'Version' ) );
|
188 |
+
$name = $theme['Name'];
|
189 |
+
$old_version = $theme['Version'];
|
190 |
+
$version = $theme_data['Version'];
|
191 |
+
|
192 |
+
$logs[] = compact( 'slug', 'name', 'old_version', 'version', 'message', 'action' );
|
193 |
+
}
|
194 |
+
}
|
195 |
+
} else {
|
196 |
+
return false;
|
197 |
+
}
|
198 |
+
|
199 |
+
$context = $type . 's';
|
200 |
+
|
201 |
+
foreach ( $logs as $log ) {
|
202 |
+
$name = isset( $log['name'] ) ? $log['name'] : null;
|
203 |
+
$version = isset( $log['version'] ) ? $log['version'] : null;
|
204 |
+
$slug = isset( $log['slug'] ) ? $log['slug'] : null;
|
205 |
+
$old_version = isset( $log['old_version'] ) ? $log['old_version'] : null;
|
206 |
+
$message = isset( $log['message'] ) ? $log['message'] : null;
|
207 |
+
$action = isset( $log['action'] ) ? $log['action'] : null;
|
208 |
+
self::log(
|
209 |
+
$message,
|
210 |
+
compact( 'type', 'name', 'version', 'slug', 'success', 'error', 'old_version' ),
|
211 |
+
null,
|
212 |
+
$context,
|
213 |
+
$action
|
214 |
+
);
|
215 |
+
}
|
216 |
+
}
|
217 |
+
|
218 |
+
public static function callback_activate_plugin( $slug, $network_wide ) {
|
219 |
+
$_plugins = self::get_plugins();
|
220 |
+
$name = $_plugins[ $slug ]['Name'];
|
221 |
+
$network_wide = $network_wide ? __( 'network wide', 'stream' ) : null;
|
222 |
+
|
223 |
+
self::log(
|
224 |
+
_x(
|
225 |
+
'"%1$s" plugin activated %2$s',
|
226 |
+
'1: Plugin name, 2: Single site or network wide',
|
227 |
+
'stream'
|
228 |
+
),
|
229 |
+
compact( 'name', 'network_wide' ),
|
230 |
+
null,
|
231 |
+
'plugins',
|
232 |
+
'activated'
|
233 |
+
);
|
234 |
+
}
|
235 |
+
|
236 |
+
public static function callback_deactivate_plugin( $slug, $network_wide ) {
|
237 |
+
$_plugins = self::get_plugins();
|
238 |
+
$name = $_plugins[ $slug ]['Name'];
|
239 |
+
$network_wide = $network_wide ? __( 'network wide', 'stream' ) : null;
|
240 |
+
|
241 |
+
self::log(
|
242 |
+
_x(
|
243 |
+
'"%1$s" plugin deactivated %2$s',
|
244 |
+
'1: Plugin name, 2: Single site or network wide',
|
245 |
+
'stream'
|
246 |
+
),
|
247 |
+
compact( 'name', 'network_wide' ),
|
248 |
+
null,
|
249 |
+
'plugins',
|
250 |
+
'deactivated'
|
251 |
+
);
|
252 |
+
}
|
253 |
+
|
254 |
+
public static function callback_switch_theme( $name, $theme ) {
|
255 |
+
$stylesheet = $theme->get_stylesheet();
|
256 |
+
|
257 |
+
self::log(
|
258 |
+
__( '"%s" theme activated', 'stream' ),
|
259 |
+
compact( 'name' ),
|
260 |
+
null,
|
261 |
+
'themes',
|
262 |
+
'activated'
|
263 |
+
);
|
264 |
+
}
|
265 |
+
|
266 |
+
/**
|
267 |
+
* @todo Core needs a delete_theme hook
|
268 |
+
*/
|
269 |
+
public static function callback_delete_site_transient_update_themes() {
|
270 |
+
|
271 |
+
$backtrace = debug_backtrace();
|
272 |
+
$delete_theme_call = null;
|
273 |
+
foreach ( $backtrace as $call ) {
|
274 |
+
if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
|
275 |
+
$delete_theme_call = $call;
|
276 |
+
break;
|
277 |
+
}
|
278 |
+
}
|
279 |
+
|
280 |
+
if ( empty( $delete_theme_call ) ) {
|
281 |
+
return;
|
282 |
+
}
|
283 |
+
|
284 |
+
$name = $delete_theme_call['args'][0];
|
285 |
+
// @todo Can we get the name of the theme? Or has it already been eliminated
|
286 |
+
|
287 |
+
self::log(
|
288 |
+
__( '"%s" theme deleted', 'stream' ),
|
289 |
+
compact( 'name' ),
|
290 |
+
null,
|
291 |
+
'themes',
|
292 |
+
'deleted'
|
293 |
+
);
|
294 |
+
}
|
295 |
+
|
296 |
+
/**
|
297 |
+
* @todo Core needs an uninstall_plugin hook
|
298 |
+
* @todo This does not work in WP-CLI
|
299 |
+
*/
|
300 |
+
public static function callback_pre_option_uninstall_plugins() {
|
301 |
+
if (
|
302 |
+
'delete-selected' !== wp_stream_filter_input( INPUT_GET, 'action' )
|
303 |
+
&&
|
304 |
+
'delete-selected' !== wp_stream_filter_input( INPUT_POST, 'action2' )
|
305 |
+
) {
|
306 |
+
return false;
|
307 |
+
}
|
308 |
+
|
309 |
+
$type = isset( $_POST['action2'] ) ? INPUT_POST : INPUT_GET;
|
310 |
+
$plugins = wp_stream_filter_input( $type, 'checked' );
|
311 |
+
$_plugins = self::get_plugins();
|
312 |
+
|
313 |
+
foreach ( (array) $plugins as $plugin ) {
|
314 |
+
$plugins_to_delete[ $plugin ] = $_plugins[ $plugin ];
|
315 |
+
}
|
316 |
+
|
317 |
+
update_option( 'wp_stream_plugins_to_delete', $plugins_to_delete );
|
318 |
+
|
319 |
+
return false;
|
320 |
+
}
|
321 |
+
|
322 |
+
/**
|
323 |
+
* @todo Core needs a delete_plugin hook
|
324 |
+
* @todo This does not work in WP-CLI
|
325 |
+
*/
|
326 |
+
public static function callback_pre_set_site_transient_update_plugins( $value ) {
|
327 |
+
if (
|
328 |
+
! wp_stream_filter_input( INPUT_POST, 'verify-delete' )
|
329 |
+
||
|
330 |
+
! ( $plugins_to_delete = get_option( 'wp_stream_plugins_to_delete' ) )
|
331 |
+
) {
|
332 |
+
return $value;
|
333 |
+
}
|
334 |
+
|
335 |
+
foreach ( $plugins_to_delete as $plugin => $data ) {
|
336 |
+
$name = $data['Name'];
|
337 |
+
$network_wide = $data['Network'] ? __( 'network wide', 'stream' ) : '';
|
338 |
+
|
339 |
+
self::log(
|
340 |
+
__( '"%s" plugin deleted', 'stream' ),
|
341 |
+
compact( 'name', 'plugin', 'network_wide' ),
|
342 |
+
null,
|
343 |
+
'plugins',
|
344 |
+
'deleted'
|
345 |
+
);
|
346 |
+
}
|
347 |
+
|
348 |
+
delete_option( 'wp_stream_plugins_to_delete' );
|
349 |
+
|
350 |
+
return $value;
|
351 |
+
}
|
352 |
+
|
353 |
+
public static function callback__core_updated_successfully( $new_version ) {
|
354 |
+
global $pagenow, $wp_version;
|
355 |
+
|
356 |
+
$old_version = $wp_version;
|
357 |
+
$auto_updated = ( 'update-core.php' !== $pagenow );
|
358 |
+
|
359 |
+
if ( $auto_updated ) {
|
360 |
+
$message = __( 'WordPress auto-updated to %s', 'stream' );
|
361 |
+
} else {
|
362 |
+
$message = __( 'WordPress updated to %s', 'stream' );
|
363 |
+
}
|
364 |
+
|
365 |
+
self::log(
|
366 |
+
$message,
|
367 |
+
compact( 'new_version', 'old_version', 'auto_updated' ),
|
368 |
+
null,
|
369 |
+
'wordpress',
|
370 |
+
'updated'
|
371 |
+
);
|
372 |
+
}
|
373 |
+
|
374 |
+
}
|
connectors/class-wp-stream-connector-jetpack.php
ADDED
@@ -0,0 +1,672 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Jetpack extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'jetpack';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '3.0.2';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'jetpack_log_entry',
|
26 |
+
'sharing_get_services_state',
|
27 |
+
'update_option',
|
28 |
+
'add_option',
|
29 |
+
'delete_option',
|
30 |
+
'jetpack_module_configuration_load_monitor',
|
31 |
+
'wp_ajax_jetpack_post_by_email_enable', // @todo These three actions do not verify whether the action has been done or if an error has been raised
|
32 |
+
'wp_ajax_jetpack_post_by_email_regenerate',
|
33 |
+
'wp_ajax_jetpack_post_by_email_disable',
|
34 |
+
);
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Tracked option keys
|
38 |
+
*
|
39 |
+
* @var array
|
40 |
+
*/
|
41 |
+
public static $options = array();
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Tracking registered Settings, with overridden data
|
45 |
+
*
|
46 |
+
* @var array
|
47 |
+
*/
|
48 |
+
public static $options_override = array();
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
52 |
+
*
|
53 |
+
* @return bool
|
54 |
+
*/
|
55 |
+
public static function is_dependency_satisfied() {
|
56 |
+
if ( class_exists( 'Jetpack' ) && defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
57 |
+
return true;
|
58 |
+
}
|
59 |
+
|
60 |
+
return false;
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Return translated connector label
|
65 |
+
*
|
66 |
+
* @return string Translated connector label
|
67 |
+
*/
|
68 |
+
public static function get_label() {
|
69 |
+
return _x( 'Jetpack', 'jetpack', 'stream' );
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Return translated action labels
|
74 |
+
*
|
75 |
+
* @return array Action label translations
|
76 |
+
*/
|
77 |
+
public static function get_action_labels() {
|
78 |
+
return array(
|
79 |
+
'activated' => _x( 'Activated', 'jetpack', 'stream' ),
|
80 |
+
'deactivated' => _x( 'Dectivated', 'jetpack', 'stream' ),
|
81 |
+
'register' => _x( 'Connected', 'jetpack', 'stream' ),
|
82 |
+
'disconnect' => _x( 'Disconnected', 'jetpack', 'stream' ),
|
83 |
+
'authorize' => _x( 'Link', 'jetpack', 'stream' ),
|
84 |
+
'unlink' => _x( 'Unlink', 'jetpack', 'stream' ),
|
85 |
+
'updated' => _x( 'Updated', 'jetpack', 'stream' ),
|
86 |
+
'added' => _x( 'Added', 'jetpack', 'stream' ),
|
87 |
+
'removed' => _x( 'Removed', 'jetpack', 'stream' ),
|
88 |
+
);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Return translated context labels
|
93 |
+
*
|
94 |
+
* @return array Context label translations
|
95 |
+
*/
|
96 |
+
public static function get_context_labels() {
|
97 |
+
return array(
|
98 |
+
'modules' => _x( 'Modules', 'jetpack', 'stream' ),
|
99 |
+
'blogs' => _x( 'Blogs', 'jetpack', 'stream' ),
|
100 |
+
'users' => _x( 'Users', 'jetpack', 'stream' ),
|
101 |
+
'options' => _x( 'Options', 'jetpack', 'stream' ),
|
102 |
+
'sharedaddy' => _x( 'Sharing', 'jetpack', 'stream' ),
|
103 |
+
'publicize' => _x( 'Publicize', 'jetpack', 'stream' ),
|
104 |
+
'gplus-authorship' => _x( 'Google+ Profile', 'jetpack', 'stream' ),
|
105 |
+
'stats' => _x( 'WordPress.com Stats', 'jetpack', 'stream' ),
|
106 |
+
'carousel' => _x( 'Carousel', 'jetpack', 'stream' ),
|
107 |
+
'custom-css' => _x( 'Custom CSS', 'jetpack', 'stream' ),
|
108 |
+
'subscriptions' => _x( 'Subscriptions', 'jetpack', 'stream' ),
|
109 |
+
'jetpack-comments' => _x( 'Comments', 'jetpack', 'stream' ),
|
110 |
+
'infinite-scroll' => _x( 'Infinite Scroll', 'jetpack', 'stream' ),
|
111 |
+
'sso' => _x( 'SSO', 'jetpack', 'stream' ),
|
112 |
+
'likes' => _x( 'Likes', 'jetpack', 'stream' ),
|
113 |
+
'minileven' => _x( 'Mobile', 'jetpack', 'stream' ),
|
114 |
+
'monitor' => _x( 'Monitor', 'jetpack', 'stream' ),
|
115 |
+
'post-by-email' => _x( 'Post by Email', 'jetpack', 'stream' ),
|
116 |
+
'related-posts' => _x( 'Related Posts', 'jetpack', 'stream' ),
|
117 |
+
'verification-tools' => _x( 'Site Verification', 'jetpack', 'stream' ),
|
118 |
+
'tiled-gallery' => _x( 'Tiled Galleries', 'jetpack', 'stream' ),
|
119 |
+
'videopress' => _x( 'VideoPress', 'jetpack', 'stream' ),
|
120 |
+
);
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Add action links to Stream drop row in admin list screen
|
125 |
+
*
|
126 |
+
* @filter wp_stream_action_links_{connector}
|
127 |
+
*
|
128 |
+
* @param array $links Previous links registered
|
129 |
+
* @param object $record Stream record
|
130 |
+
*
|
131 |
+
* @return array Action links
|
132 |
+
*/
|
133 |
+
public static function action_links( $links, $record ) {
|
134 |
+
// @todo provide proper action links
|
135 |
+
if ( 'jetpack' === $record->connector ) {
|
136 |
+
if ( 'modules' === $record->context ) {
|
137 |
+
$slug = wp_stream_get_meta( $record, 'module_slug', true );
|
138 |
+
|
139 |
+
if ( is_array( $slug ) ) {
|
140 |
+
$slug = current( $slug );
|
141 |
+
}
|
142 |
+
|
143 |
+
if ( Jetpack::is_module_active( $slug ) ) {
|
144 |
+
if ( apply_filters( 'jetpack_module_configurable_' . $slug, false ) ) {
|
145 |
+
$links[ __( 'Configure', 'stream' ) ] = Jetpack::module_configuration_url( $slug );;
|
146 |
+
}
|
147 |
+
|
148 |
+
$links[ __( 'Deactivate', 'stream' ) ] = wp_nonce_url(
|
149 |
+
add_query_arg(
|
150 |
+
array(
|
151 |
+
'action' => 'deactivate',
|
152 |
+
'module' => $slug,
|
153 |
+
),
|
154 |
+
Jetpack::admin_url()
|
155 |
+
),
|
156 |
+
'jetpack_deactivate-' . sanitize_title( $slug )
|
157 |
+
);
|
158 |
+
} else {
|
159 |
+
$links[ __( 'Activate', 'stream' ) ] = wp_nonce_url(
|
160 |
+
add_query_arg(
|
161 |
+
array(
|
162 |
+
'action' => 'activate',
|
163 |
+
'module' => $slug,
|
164 |
+
),
|
165 |
+
Jetpack::admin_url()
|
166 |
+
),
|
167 |
+
'jetpack_activate-' . sanitize_title( $slug )
|
168 |
+
);
|
169 |
+
}
|
170 |
+
} elseif ( Jetpack::is_module_active( str_replace( 'jetpack-', '', $record->context ) ) ) {
|
171 |
+
$slug = str_replace( 'jetpack-', '', $record->context ); // handling jetpack-comment anomaly
|
172 |
+
|
173 |
+
if ( apply_filters( 'jetpack_module_configurable_' . $slug, false ) ) {
|
174 |
+
$links[ __( 'Configure module', 'stream' ) ] = Jetpack::module_configuration_url( $slug );;
|
175 |
+
}
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
return $links;
|
180 |
+
}
|
181 |
+
|
182 |
+
public static function register() {
|
183 |
+
parent::register();
|
184 |
+
|
185 |
+
add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
|
186 |
+
|
187 |
+
self::$options = array(
|
188 |
+
'jetpack_options' => null,
|
189 |
+
// Sharing module
|
190 |
+
'hide_gplus' => null,
|
191 |
+
'gplus_authors' => null,
|
192 |
+
'sharing-options' => array(
|
193 |
+
'label' => __( 'Sharing options', 'stream' ),
|
194 |
+
'context' => 'sharedaddy',
|
195 |
+
),
|
196 |
+
'sharedaddy_disable_resources' => null,
|
197 |
+
'jetpack-twitter-cards-site-tag' => array(
|
198 |
+
'label' => __( 'Twitter site tag', 'stream' ),
|
199 |
+
'context' => 'sharedaddy',
|
200 |
+
),
|
201 |
+
// Stats module
|
202 |
+
'stats_options' => array(
|
203 |
+
'label' => __( 'WordPress.com Stats', 'stream' ),
|
204 |
+
'context' => 'stats',
|
205 |
+
),
|
206 |
+
// Comments
|
207 |
+
'jetpack_comment_form_color_scheme' => array(
|
208 |
+
'label' => __( 'Color Scheme', 'stream' ),
|
209 |
+
'context' => 'jetpack-comments',
|
210 |
+
),
|
211 |
+
// Likes
|
212 |
+
'disabled_likes' => array(
|
213 |
+
'label' => __( 'WP.com Site-wide Likes', 'stream' ),
|
214 |
+
'context' => 'likes',
|
215 |
+
),
|
216 |
+
// Mobile
|
217 |
+
'wp_mobile_excerpt' => array(
|
218 |
+
'label' => __( 'Excerpts appearance', 'stream' ),
|
219 |
+
'context' => 'minileven',
|
220 |
+
),
|
221 |
+
'wp_mobile_app_promos' => array(
|
222 |
+
'label' => __( 'App promos', 'stream' ),
|
223 |
+
'context' => 'minileven',
|
224 |
+
),
|
225 |
+
);
|
226 |
+
|
227 |
+
self::$options_override = array(
|
228 |
+
// Carousel Module
|
229 |
+
'carousel_background_color' => array(
|
230 |
+
'label' => __( 'Background color', 'stream' ),
|
231 |
+
'context' => 'carousel',
|
232 |
+
),
|
233 |
+
'carousel_display_exif' => array(
|
234 |
+
'label' => __( 'Metadata', 'stream' ),
|
235 |
+
'context' => 'carousel',
|
236 |
+
),
|
237 |
+
// Subscriptions
|
238 |
+
'stb_enabled' => array(
|
239 |
+
'label' => __( 'Follow blog comment form button', 'stream' ),
|
240 |
+
'context' => 'subscriptions',
|
241 |
+
),
|
242 |
+
'stc_enabled' => array(
|
243 |
+
'label' => __( 'Follow comments form button', 'stream' ),
|
244 |
+
'context' => 'subscriptions',
|
245 |
+
),
|
246 |
+
// Jetpack comments
|
247 |
+
'highlander_comment_form_prompt' => array(
|
248 |
+
'label' => __( 'Greeting Text', 'stream' ),
|
249 |
+
'context' => 'jetpack-comments',
|
250 |
+
),
|
251 |
+
// Infinite Scroll
|
252 |
+
'infinite_scroll_google_analytics' => array(
|
253 |
+
'label' => __( 'Infinite Scroll Google Analytics', 'stream' ),
|
254 |
+
'context' => 'infinite-scroll',
|
255 |
+
),
|
256 |
+
// SSO
|
257 |
+
'jetpack_sso_require_two_step' => array(
|
258 |
+
'label' => __( 'Require Two-Step Authentication', 'stream' ),
|
259 |
+
'context' => 'sso',
|
260 |
+
),
|
261 |
+
'jetpack_sso_match_by_email' => array(
|
262 |
+
'label' => __( 'Match by Email', 'stream' ),
|
263 |
+
'context' => 'sso',
|
264 |
+
),
|
265 |
+
// Related posts
|
266 |
+
'jetpack_relatedposts' => array(
|
267 |
+
'show_headline' => array(
|
268 |
+
'label' => __( 'Show Related Posts Headline', 'stream' ),
|
269 |
+
'context' => 'related-posts',
|
270 |
+
),
|
271 |
+
'show_thumbnails' => array(
|
272 |
+
'label' => __( 'Show Related Posts Thumbnails', 'stream' ),
|
273 |
+
'context' => 'related-posts',
|
274 |
+
),
|
275 |
+
),
|
276 |
+
// Site verification
|
277 |
+
'verification_services_codes' => array(
|
278 |
+
'google' => array(
|
279 |
+
'label' => __( 'Google Webmaster Tools Token', 'stream' ),
|
280 |
+
'context' => 'verification-tools',
|
281 |
+
),
|
282 |
+
'bing' => array(
|
283 |
+
'label' => __( 'Bing Webmaster Center Token', 'stream' ),
|
284 |
+
'context' => 'verification-tools',
|
285 |
+
),
|
286 |
+
'pinterest' => array(
|
287 |
+
'label' => __( 'Pinterest Site Verification Token', 'stream' ),
|
288 |
+
'context' => 'verification-tools',
|
289 |
+
),
|
290 |
+
),
|
291 |
+
// Tiled galleries
|
292 |
+
'tiled_galleries' => array(
|
293 |
+
'label' => __( 'Tiled Galleries', 'stream' ),
|
294 |
+
'context' => 'tiled-gallery',
|
295 |
+
),
|
296 |
+
);
|
297 |
+
}
|
298 |
+
|
299 |
+
/**
|
300 |
+
* Track Jetpack log entries
|
301 |
+
* Includes:
|
302 |
+
* - Activation/Deactivation of modules
|
303 |
+
* - Registration/Disconnection of blogs
|
304 |
+
* - Authorization/unlinking of users
|
305 |
+
*
|
306 |
+
* @param array $entry
|
307 |
+
*/
|
308 |
+
public static function callback_jetpack_log_entry( array $entry ) {
|
309 |
+
$method = $entry['code'];
|
310 |
+
$data = $entry['data'];
|
311 |
+
$context = null;
|
312 |
+
$action = null;
|
313 |
+
|
314 |
+
if ( in_array( $method, array( 'activate', 'deactivate' ) ) ) {
|
315 |
+
$module_slug = $data;
|
316 |
+
$module = Jetpack::get_module( $module_slug );
|
317 |
+
$module_name = $module['name'];
|
318 |
+
$context = 'modules';
|
319 |
+
$action = $method . 'd';
|
320 |
+
$meta = compact( 'module_slug' );
|
321 |
+
$message = sprintf(
|
322 |
+
__( '%1$s module %2$s', 'stream' ),
|
323 |
+
$module_name,
|
324 |
+
( 'activated' === $action ) ? __( 'activated', 'stream' ) : __( 'deactivated', 'stream' )
|
325 |
+
);
|
326 |
+
} elseif ( in_array( $method, array( 'authorize', 'unlink' ) ) ) {
|
327 |
+
$user_id = intval( $data );
|
328 |
+
|
329 |
+
if ( empty( $user_id ) ) {
|
330 |
+
$user_id = get_current_user_id();
|
331 |
+
}
|
332 |
+
|
333 |
+
$user = new WP_User( $user_id );
|
334 |
+
$user_email = $user->user_email;
|
335 |
+
$user_login = $user->user_login;
|
336 |
+
$context = 'users';
|
337 |
+
$action = $method;
|
338 |
+
$meta = compact( 'user_id', 'user_email', 'user_login' );
|
339 |
+
$message = sprintf(
|
340 |
+
__( '%1$s\'s account %2$s %3$s Jetpack', 'stream' ),
|
341 |
+
$user->display_name,
|
342 |
+
( 'unlink' === $action ) ? __( 'unlinked', 'stream' ) : __( 'linked', 'stream' ),
|
343 |
+
( 'unlink' === $action ) ? __( 'from', 'stream' ) : __( 'to', 'stream' )
|
344 |
+
);
|
345 |
+
} elseif ( in_array( $method, array( 'register', 'disconnect', 'subsiteregister', 'subsitedisconnect' ) ) ) {
|
346 |
+
$context = 'blogs';
|
347 |
+
$action = str_replace( 'subsite', '', $method );
|
348 |
+
$is_multisite = ( 0 === strpos( $method, 'subsite' ) );
|
349 |
+
$blog_id = $is_multisite ? ( isset( $_GET['site_id'] ) ? intval( $_GET['site_id'] ) : null ) : get_current_blog_id();
|
350 |
+
|
351 |
+
if ( empty( $blog_id ) ) {
|
352 |
+
return;
|
353 |
+
}
|
354 |
+
|
355 |
+
$meta = array();
|
356 |
+
|
357 |
+
if ( ! $is_multisite ) {
|
358 |
+
$message = sprintf(
|
359 |
+
__( 'Site %s Jetpack', 'stream' ),
|
360 |
+
( 'register' === $action ) ? __( 'connected to', 'stream' ) : __( 'disconnected from', 'stream' )
|
361 |
+
);
|
362 |
+
} else {
|
363 |
+
$blog_details = get_blog_details( array( 'blog_id' => $blog_id ) );
|
364 |
+
$blog_name = $blog_details->blogname;
|
365 |
+
$meta += compact( 'blog_id', 'blog_name' );
|
366 |
+
|
367 |
+
$message = sprintf(
|
368 |
+
__( '"%1$s" blog %2$s Jetpack', 'stream' ),
|
369 |
+
$blog_name,
|
370 |
+
( 'register' === $action ) ? __( 'connected to', 'stream' ) : __( 'disconnected from', 'stream' )
|
371 |
+
);
|
372 |
+
}
|
373 |
+
}
|
374 |
+
|
375 |
+
if ( empty( $message ) ) {
|
376 |
+
return;
|
377 |
+
}
|
378 |
+
|
379 |
+
self::log(
|
380 |
+
$message,
|
381 |
+
$meta,
|
382 |
+
null,
|
383 |
+
$context,
|
384 |
+
$action
|
385 |
+
);
|
386 |
+
}
|
387 |
+
|
388 |
+
/**
|
389 |
+
* Track visible/enabled sharing services ( buttons )
|
390 |
+
*
|
391 |
+
* @param $state
|
392 |
+
*/
|
393 |
+
public static function callback_sharing_get_services_state( $state ) {
|
394 |
+
self::log(
|
395 |
+
__( 'Sharing services updated', 'stream' ),
|
396 |
+
$state,
|
397 |
+
null,
|
398 |
+
'sharedaddy',
|
399 |
+
'updated'
|
400 |
+
);
|
401 |
+
}
|
402 |
+
|
403 |
+
public static function callback_update_option( $option, $old, $new ) {
|
404 |
+
self::check( $option, $old, $new );
|
405 |
+
}
|
406 |
+
|
407 |
+
public static function callback_add_option( $option, $val ) {
|
408 |
+
self::check( $option, null, $val );
|
409 |
+
}
|
410 |
+
|
411 |
+
public static function callback_delete_option( $option ) {
|
412 |
+
self::check( $option, null, null );
|
413 |
+
}
|
414 |
+
|
415 |
+
/**
|
416 |
+
* Track Monitor module notification status
|
417 |
+
*/
|
418 |
+
public static function callback_jetpack_module_configuration_load_monitor() {
|
419 |
+
if ( $_POST ) {
|
420 |
+
$active = wp_stream_filter_input( INPUT_POST, 'receive_jetpack_monitor_notification' );
|
421 |
+
|
422 |
+
self::log(
|
423 |
+
__( 'Monitor notifications %s', 'stream' ),
|
424 |
+
array(
|
425 |
+
'status' => $active ? __( 'activated', 'stream' ) : __( 'deactivated', 'stream' ),
|
426 |
+
'option' => 'receive_jetpack_monitor_notification',
|
427 |
+
'old_value' => ! $active,
|
428 |
+
'value' => $active,
|
429 |
+
),
|
430 |
+
null,
|
431 |
+
'monitor',
|
432 |
+
'updated'
|
433 |
+
);
|
434 |
+
}
|
435 |
+
}
|
436 |
+
|
437 |
+
public static function callback_wp_ajax_jetpack_post_by_email_enable() {
|
438 |
+
self::track_post_by_email( true );
|
439 |
+
}
|
440 |
+
|
441 |
+
public static function callback_wp_ajax_jetpack_post_by_email_regenerate() {
|
442 |
+
self::track_post_by_email( null );
|
443 |
+
}
|
444 |
+
|
445 |
+
public static function callback_wp_ajax_jetpack_post_by_email_disable() {
|
446 |
+
self::track_post_by_email( false );
|
447 |
+
}
|
448 |
+
|
449 |
+
public static function track_post_by_email( $status ) {
|
450 |
+
if ( true === $status ) {
|
451 |
+
$action = __( 'enabled', 'stream' );
|
452 |
+
} elseif ( false === $status ) {
|
453 |
+
$action = __( 'disabled', 'stream' );
|
454 |
+
} elseif ( null === $status ) {
|
455 |
+
$action = __( 'regenerated', 'stream' );
|
456 |
+
}
|
457 |
+
|
458 |
+
$user = wp_get_current_user();
|
459 |
+
|
460 |
+
self::log(
|
461 |
+
__( '%1$s %2$s Post by Email', 'stream' ),
|
462 |
+
array(
|
463 |
+
'user_displayname' => $user->display_name,
|
464 |
+
'action' => $action,
|
465 |
+
'status' => $status,
|
466 |
+
),
|
467 |
+
null,
|
468 |
+
'post-by-email',
|
469 |
+
'updated'
|
470 |
+
);
|
471 |
+
}
|
472 |
+
|
473 |
+
public static function check( $option, $old_value, $new_value ) {
|
474 |
+
if ( ! array_key_exists( $option, self::$options ) ) {
|
475 |
+
return;
|
476 |
+
}
|
477 |
+
|
478 |
+
if ( is_null( self::$options[ $option ] ) ) {
|
479 |
+
call_user_func( array( __CLASS__, 'check_' . str_replace( '-', '_', $option ) ), $old_value, $new_value );
|
480 |
+
} else {
|
481 |
+
$data = self::$options[ $option ];
|
482 |
+
$option_title = $data['label'];
|
483 |
+
|
484 |
+
self::log(
|
485 |
+
__( '"%s" setting updated', 'stream' ),
|
486 |
+
compact( 'option_title', 'option', 'old_value', 'new_value' ),
|
487 |
+
null,
|
488 |
+
$data['context'],
|
489 |
+
isset( $data['action'] ) ? $data['action'] : 'updated'
|
490 |
+
);
|
491 |
+
}
|
492 |
+
}
|
493 |
+
|
494 |
+
public static function check_jetpack_options( $old_value, $new_value ) {
|
495 |
+
$options = array();
|
496 |
+
|
497 |
+
if ( ! is_array( $old_value ) || ! is_array( $new_value ) ) {
|
498 |
+
return;
|
499 |
+
}
|
500 |
+
|
501 |
+
foreach ( self::get_changed_keys( $old_value, $new_value, 1 ) as $field_key => $field_value ) {
|
502 |
+
$options[ $field_key ] = $field_value;
|
503 |
+
}
|
504 |
+
|
505 |
+
foreach ( $options as $option => $option_value ) {
|
506 |
+
$settings = self::get_settings_def( $option, $option_value );
|
507 |
+
|
508 |
+
if ( ! $settings ) {
|
509 |
+
continue;
|
510 |
+
}
|
511 |
+
|
512 |
+
if ( 0 === $option_value ) { // Skip updated array with updated members, we'll be logging those instead
|
513 |
+
continue;
|
514 |
+
}
|
515 |
+
|
516 |
+
$settings['meta'] += array(
|
517 |
+
'option' => $option,
|
518 |
+
'old_value' => maybe_serialize( $old_value ),
|
519 |
+
'value' => maybe_serialize( $new_value ),
|
520 |
+
);
|
521 |
+
|
522 |
+
self::log(
|
523 |
+
$settings['message'],
|
524 |
+
$settings['meta'],
|
525 |
+
isset( $settings['object_id'] ) ? $settings['object_id'] : null,
|
526 |
+
$settings['context'],
|
527 |
+
$settings['action']
|
528 |
+
);
|
529 |
+
}
|
530 |
+
}
|
531 |
+
|
532 |
+
public static function check_hide_gplus( $old_value, $new_value ) {
|
533 |
+
$status = ! is_null( $new_value );
|
534 |
+
|
535 |
+
if ( $status && $old_value ) {
|
536 |
+
return false;
|
537 |
+
}
|
538 |
+
|
539 |
+
self::log(
|
540 |
+
__( 'G+ profile display %s', 'stream' ),
|
541 |
+
array(
|
542 |
+
'action' => $status ? __( 'enabled', 'stream' ) : __( 'disabled', 'stream' ),
|
543 |
+
),
|
544 |
+
null,
|
545 |
+
'gplus-authorship',
|
546 |
+
'updated'
|
547 |
+
);
|
548 |
+
}
|
549 |
+
|
550 |
+
public static function check_gplus_authors( $old_value, $new_value ) {
|
551 |
+
$user = wp_get_current_user();
|
552 |
+
$connected = is_array( $new_value ) && array_key_exists( $user->ID, $new_value );
|
553 |
+
|
554 |
+
self::log(
|
555 |
+
__( '%1$s\'s Google+ account %2$s', 'stream' ),
|
556 |
+
array(
|
557 |
+
'display_name' => $user->display_name,
|
558 |
+
'action' => $connected ? __( 'connected', 'stream' ) : __( 'disconnected', 'stream' ),
|
559 |
+
'user_id' => $user->ID,
|
560 |
+
),
|
561 |
+
$user->ID,
|
562 |
+
'gplus-authorship',
|
563 |
+
'updated'
|
564 |
+
);
|
565 |
+
}
|
566 |
+
|
567 |
+
public static function check_sharedaddy_disable_resources( $old_value, $new_value ) {
|
568 |
+
if ( $old_value == $new_value ) {
|
569 |
+
return;
|
570 |
+
}
|
571 |
+
|
572 |
+
$status = ! $new_value ? 'enabled' : 'disabled'; // disabled = 1
|
573 |
+
|
574 |
+
self::log(
|
575 |
+
__( 'Sharing CSS/JS %s', 'stream' ),
|
576 |
+
compact( 'status', 'old_value', 'new_value' ),
|
577 |
+
null,
|
578 |
+
'sharing',
|
579 |
+
'updated'
|
580 |
+
);
|
581 |
+
}
|
582 |
+
|
583 |
+
/**
|
584 |
+
* Override connector log for our own Settings / Actions
|
585 |
+
*
|
586 |
+
* @param array $data
|
587 |
+
*
|
588 |
+
* @return array|bool
|
589 |
+
*/
|
590 |
+
public static function log_override( $data ) {
|
591 |
+
if ( ! is_array( $data ) ) {
|
592 |
+
return $data;
|
593 |
+
}
|
594 |
+
|
595 |
+
// Handling our Settings
|
596 |
+
if ( 'settings' === $data['connector'] && isset( self::$options_override[ $data['args']['option'] ] ) ) {
|
597 |
+
if ( isset( $data['args']['option_key'] ) ) {
|
598 |
+
$overrides = self::$options_override[ $data['args']['option'] ][ $data['args']['option_key'] ];
|
599 |
+
} else {
|
600 |
+
$overrides = self::$options_override[ $data['args']['option'] ];
|
601 |
+
}
|
602 |
+
|
603 |
+
if ( isset( $overrides ) ) {
|
604 |
+
$data['args']['label'] = $overrides['label'];
|
605 |
+
$data['args']['context'] = $overrides['context'];
|
606 |
+
$data['context'] = $overrides['context'];
|
607 |
+
$data['connector'] = self::$name;
|
608 |
+
}
|
609 |
+
} elseif ( 'posts' === $data['connector'] && 'safecss' === $data['context'] ) {
|
610 |
+
$data = array_merge(
|
611 |
+
$data,
|
612 |
+
array(
|
613 |
+
'connector' => self::$name,
|
614 |
+
'message' => __( 'Custom CSS updated', 'stream' ),
|
615 |
+
'args' => array(),
|
616 |
+
'object_id' => null,
|
617 |
+
'context' => 'custom-css',
|
618 |
+
'action' => 'updated',
|
619 |
+
)
|
620 |
+
);
|
621 |
+
}
|
622 |
+
|
623 |
+
return $data;
|
624 |
+
}
|
625 |
+
|
626 |
+
private static function get_settings_def( $key, $value = null ) {
|
627 |
+
// Sharing
|
628 |
+
if ( 0 === strpos( $key, 'publicize_connections::' ) ) {
|
629 |
+
global $publicize_ui;
|
630 |
+
|
631 |
+
$name = str_replace( 'publicize_connections::', '', $key );
|
632 |
+
|
633 |
+
return array(
|
634 |
+
'message' => __( '%1$s connection %2$s', 'stream' ),
|
635 |
+
'meta' => array(
|
636 |
+
'connection' => $publicize_ui->publicize->get_service_label( $name ),
|
637 |
+
'action' => $value ? __( 'added', 'stream' ) : __( 'removed', 'stream' ),
|
638 |
+
'option' => 'jetpack_options',
|
639 |
+
'option_key' => $key,
|
640 |
+
),
|
641 |
+
'action' => $value ? 'added' : 'removed',
|
642 |
+
'context' => 'publicize',
|
643 |
+
);
|
644 |
+
} elseif ( 0 === strpos( $key, 'videopress::' ) ) {
|
645 |
+
$name = str_replace( 'videopress::', '', $key );
|
646 |
+
$options = array(
|
647 |
+
'access' => __( 'Video Library Access', 'stream' ),
|
648 |
+
'upload' => __( 'Allow users to upload videos', 'stream' ),
|
649 |
+
'freedom' => __( 'Free formats', 'stream' ),
|
650 |
+
'hd' => __( 'Default quality', 'stream' ),
|
651 |
+
);
|
652 |
+
|
653 |
+
if ( ! isset( $options[ $name ] ) ) {
|
654 |
+
return false;
|
655 |
+
}
|
656 |
+
|
657 |
+
return array(
|
658 |
+
'message' => __( '"%s" setting updated', 'stream' ),
|
659 |
+
'meta' => array(
|
660 |
+
'option_name' => $options[ $name ],
|
661 |
+
'option' => 'jetpack_options',
|
662 |
+
'option_key' => $key,
|
663 |
+
),
|
664 |
+
'action' => 'updated',
|
665 |
+
'context' => 'videopress',
|
666 |
+
);
|
667 |
+
}
|
668 |
+
|
669 |
+
return false;
|
670 |
+
}
|
671 |
+
|
672 |
+
}
|
connectors/class-wp-stream-connector-media.php
ADDED
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Media extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'media';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'add_attachment',
|
19 |
+
'edit_attachment',
|
20 |
+
'delete_attachment',
|
21 |
+
'wp_save_image_editor_file',
|
22 |
+
'wp_save_image_file',
|
23 |
+
);
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Return translated connector label
|
27 |
+
*
|
28 |
+
* @return string Translated connector label
|
29 |
+
*/
|
30 |
+
public static function get_label() {
|
31 |
+
return __( 'Media', 'stream' );
|
32 |
+
}
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Return translated action labels
|
36 |
+
*
|
37 |
+
* @return array Action label translations
|
38 |
+
*/
|
39 |
+
public static function get_action_labels() {
|
40 |
+
return array(
|
41 |
+
'attached' => __( 'Attached', 'stream' ),
|
42 |
+
'uploaded' => __( 'Uploaded', 'stream' ),
|
43 |
+
'updated' => __( 'Updated', 'stream' ),
|
44 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
45 |
+
'assigned' => __( 'Assigned', 'stream' ),
|
46 |
+
'unassigned' => __( 'Unassigned', 'stream' ),
|
47 |
+
);
|
48 |
+
}
|
49 |
+
|
50 |
+
/**
|
51 |
+
* Return translated context labels
|
52 |
+
*
|
53 |
+
* Based on extension types used by wp_ext2type() in wp-includes/functions.php.
|
54 |
+
*
|
55 |
+
* @return array Context label translations
|
56 |
+
*/
|
57 |
+
public static function get_context_labels() {
|
58 |
+
return array(
|
59 |
+
'image' => __( 'Image', 'stream' ),
|
60 |
+
'audio' => __( 'Audio', 'stream' ),
|
61 |
+
'video' => __( 'Video', 'stream' ),
|
62 |
+
'document' => __( 'Document', 'stream' ),
|
63 |
+
'spreadsheet' => __( 'Spreadsheet', 'stream' ),
|
64 |
+
'interactive' => __( 'Interactive', 'stream' ),
|
65 |
+
'text' => __( 'Text', 'stream' ),
|
66 |
+
'archive' => __( 'Archive', 'stream' ),
|
67 |
+
'code' => __( 'Code', 'stream' ),
|
68 |
+
);
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Return the file type for an attachment which corresponds with a context label
|
73 |
+
*
|
74 |
+
* @param object $file_uri URI of the attachment
|
75 |
+
* @return string A file type which corresponds with a context label
|
76 |
+
*/
|
77 |
+
public static function get_attachment_type( $file_uri ) {
|
78 |
+
$extension = pathinfo( $file_uri, PATHINFO_EXTENSION );
|
79 |
+
$extension_type = wp_ext2type( $extension );
|
80 |
+
|
81 |
+
if ( empty( $extension_type ) ) {
|
82 |
+
$extension_type = 'document';
|
83 |
+
}
|
84 |
+
|
85 |
+
$context_labels = self::get_context_labels();
|
86 |
+
|
87 |
+
if ( ! isset( $context_labels[ $extension_type ] ) ) {
|
88 |
+
$extension_type = 'document';
|
89 |
+
}
|
90 |
+
|
91 |
+
return $extension_type;
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Add action links to Stream drop row in admin list screen
|
96 |
+
*
|
97 |
+
* @filter wp_stream_action_links_{connector}
|
98 |
+
*
|
99 |
+
* @param array $links Previous links registered
|
100 |
+
* @param object $record Stream record
|
101 |
+
*
|
102 |
+
* @return array Action links
|
103 |
+
*/
|
104 |
+
public static function action_links( $links, $record ) {
|
105 |
+
if ( $record->object_id ) {
|
106 |
+
if ( $link = get_edit_post_link( $record->object_id ) ) {
|
107 |
+
$links[ __( 'Edit Media', 'stream' ) ] = $link;
|
108 |
+
}
|
109 |
+
if ( $link = get_permalink( $record->object_id ) ) {
|
110 |
+
$links[ __( 'View', 'stream' ) ] = $link;
|
111 |
+
}
|
112 |
+
}
|
113 |
+
|
114 |
+
return $links;
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Tracks creation of attachments
|
119 |
+
*
|
120 |
+
* @action add_attachment
|
121 |
+
*/
|
122 |
+
public static function callback_add_attachment( $post_id ) {
|
123 |
+
$post = get_post( $post_id );
|
124 |
+
if ( $post->post_parent ) {
|
125 |
+
$message = _x(
|
126 |
+
'Attached "%1$s" to "%2$s"',
|
127 |
+
'1: Attachment title, 2: Parent post title',
|
128 |
+
'stream'
|
129 |
+
);
|
130 |
+
} else {
|
131 |
+
$message = __( 'Added "%s" to Media library', 'stream' );
|
132 |
+
}
|
133 |
+
|
134 |
+
$name = $post->post_title;
|
135 |
+
$url = $post->guid;
|
136 |
+
$parent_id = $post->post_parent;
|
137 |
+
$parent = get_post( $parent_id );
|
138 |
+
$parent_title = $parent_id ? $parent->post_title : null;
|
139 |
+
$attachment_type = self::get_attachment_type( $post->guid );
|
140 |
+
|
141 |
+
self::log(
|
142 |
+
$message,
|
143 |
+
compact( 'name', 'parent_title', 'parent_id', 'url' ),
|
144 |
+
$post_id,
|
145 |
+
$attachment_type,
|
146 |
+
$post->post_parent ? 'attached' : 'uploaded'
|
147 |
+
);
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Tracks editing attachments
|
152 |
+
*
|
153 |
+
* @action edit_attachment
|
154 |
+
*/
|
155 |
+
public static function callback_edit_attachment( $post_id ) {
|
156 |
+
$post = get_post( $post_id );
|
157 |
+
$message = __( 'Updated "%s"', 'stream' );
|
158 |
+
$name = $post->post_title;
|
159 |
+
$attachment_type = self::get_attachment_type( $post->guid );
|
160 |
+
|
161 |
+
self::log(
|
162 |
+
$message,
|
163 |
+
compact( 'name' ),
|
164 |
+
$post_id,
|
165 |
+
$attachment_type,
|
166 |
+
'updated'
|
167 |
+
);
|
168 |
+
}
|
169 |
+
|
170 |
+
/**
|
171 |
+
* Tracks deletion of attachments
|
172 |
+
*
|
173 |
+
* @action delete_attachment
|
174 |
+
*/
|
175 |
+
public static function callback_delete_attachment( $post_id ) {
|
176 |
+
$post = get_post( $post_id );
|
177 |
+
$parent = $post->post_parent ? get_post( $post->post_parent ) : null;
|
178 |
+
$parent_id = $parent ? $parent->ID : null;
|
179 |
+
$message = __( 'Deleted "%s"', 'stream' );
|
180 |
+
$name = $post->post_title;
|
181 |
+
$url = $post->guid;
|
182 |
+
$attachment_type = self::get_attachment_type( $post->guid );
|
183 |
+
|
184 |
+
self::log(
|
185 |
+
$message,
|
186 |
+
compact( 'name', 'parent_id', 'url' ),
|
187 |
+
$post_id,
|
188 |
+
$attachment_type,
|
189 |
+
'deleted'
|
190 |
+
);
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Tracks changes made in the image editor
|
195 |
+
*
|
196 |
+
* @action delete_attachment
|
197 |
+
*/
|
198 |
+
public static function callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id ) {
|
199 |
+
$name = basename( $filename );
|
200 |
+
$attachment_type = self::get_attachment_type( $post->guid );
|
201 |
+
|
202 |
+
self::log(
|
203 |
+
__( 'Edited image "%s"', 'stream' ),
|
204 |
+
compact( 'name', 'filename', 'post_id' ),
|
205 |
+
$post_id,
|
206 |
+
$attachment_type,
|
207 |
+
'edited'
|
208 |
+
);
|
209 |
+
}
|
210 |
+
|
211 |
+
public static function callback_wp_save_image_file( $dummy, $filename, $image, $mime_type, $post_id ) {
|
212 |
+
return self::callback_wp_save_image_editor_file( $dummy, $filename, $image, $mime_type, $post_id );
|
213 |
+
}
|
214 |
+
|
215 |
+
}
|
connectors/class-wp-stream-connector-menus.php
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Menus extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'menus';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'wp_create_nav_menu',
|
19 |
+
'wp_update_nav_menu',
|
20 |
+
'delete_nav_menu',
|
21 |
+
);
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Return translated connector label
|
25 |
+
*
|
26 |
+
* @return string Translated connector label
|
27 |
+
*/
|
28 |
+
public static function get_label() {
|
29 |
+
return __( 'Menus', 'stream' );
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Return translated action labels
|
34 |
+
*
|
35 |
+
* @return array Action label translations
|
36 |
+
*/
|
37 |
+
public static function get_action_labels() {
|
38 |
+
return array(
|
39 |
+
'created' => __( 'Created', 'stream' ),
|
40 |
+
'updated' => __( 'Updated', 'stream' ),
|
41 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
42 |
+
'assigned' => __( 'Assigned', 'stream' ),
|
43 |
+
'unassigned' => __( 'Unassigned', 'stream' ),
|
44 |
+
);
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Return translated context labels
|
49 |
+
*
|
50 |
+
* @return array Context label translations
|
51 |
+
*/
|
52 |
+
public static function get_context_labels() {
|
53 |
+
$labels = array();
|
54 |
+
$menus = get_terms( 'nav_menu', array( 'hide_empty' => false ) );
|
55 |
+
|
56 |
+
foreach ( $menus as $menu ) {
|
57 |
+
$slug = sanitize_title( $menu->name );
|
58 |
+
$labels[ $slug ] = $menu->name;
|
59 |
+
}
|
60 |
+
|
61 |
+
return $labels;
|
62 |
+
}
|
63 |
+
|
64 |
+
public static function register() {
|
65 |
+
parent::register();
|
66 |
+
|
67 |
+
add_action( 'update_option_theme_mods_' . get_option( 'stylesheet' ), array( __CLASS__, 'callback_update_option_theme_mods' ), 10, 2 );
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Add action links to Stream drop row in admin list screen
|
72 |
+
*
|
73 |
+
* @filter wp_stream_action_links_{connector}
|
74 |
+
*
|
75 |
+
* @param array $links Previous links registered
|
76 |
+
* @param object $record Stream record
|
77 |
+
*
|
78 |
+
* @return array Action links
|
79 |
+
*/
|
80 |
+
public static function action_links( $links, $record ) {
|
81 |
+
if ( $record->object_id ) {
|
82 |
+
$menus = wp_get_nav_menus();
|
83 |
+
$menu_ids = wp_list_pluck( $menus, 'term_id' );
|
84 |
+
|
85 |
+
if ( in_array( $record->object_id, $menu_ids ) ) {
|
86 |
+
$links[ __( 'Edit Menu', 'stream' ) ] = admin_url( 'nav-menus.php?action=edit&menu=' . $record->object_id ); // xss ok (@todo fix WPCS rule)
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
return $links;
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* Tracks creation of menus
|
95 |
+
*
|
96 |
+
* @action wp_create_nav_menu
|
97 |
+
*/
|
98 |
+
public static function callback_wp_create_nav_menu( $menu_id, $menu_data ) {
|
99 |
+
$name = $menu_data['menu-name'];
|
100 |
+
|
101 |
+
self::log(
|
102 |
+
__( 'Created new menu "%s"', 'stream' ),
|
103 |
+
compact( 'name', 'menu_id' ),
|
104 |
+
$menu_id,
|
105 |
+
sanitize_title( $name ),
|
106 |
+
'created'
|
107 |
+
);
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Tracks menu updates
|
112 |
+
*
|
113 |
+
* @action wp_update_nav_menu
|
114 |
+
*/
|
115 |
+
public static function callback_wp_update_nav_menu( $menu_id, $menu_data = array() ) {
|
116 |
+
if ( empty( $menu_data ) ) {
|
117 |
+
return;
|
118 |
+
}
|
119 |
+
|
120 |
+
$name = $menu_data['menu-name'];
|
121 |
+
|
122 |
+
self::log(
|
123 |
+
_x( 'Updated menu "%s"', 'Menu name', 'stream' ),
|
124 |
+
compact( 'name', 'menu_id', 'menu_data' ),
|
125 |
+
$menu_id,
|
126 |
+
sanitize_title( $name ),
|
127 |
+
'updated'
|
128 |
+
);
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Tracks menu deletion
|
133 |
+
*
|
134 |
+
* @action delete_nav_menu
|
135 |
+
*/
|
136 |
+
public static function callback_delete_nav_menu( $term, $tt_id, $deleted_term ) {
|
137 |
+
$name = $deleted_term->name;
|
138 |
+
$menu_id = $term;
|
139 |
+
|
140 |
+
self::log(
|
141 |
+
_x( 'Deleted "%s"', 'Menu name', 'stream' ),
|
142 |
+
compact( 'name', 'menu_id' ),
|
143 |
+
$menu_id,
|
144 |
+
sanitize_title( $name ),
|
145 |
+
'deleted'
|
146 |
+
);
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* Track assignment to menu locations
|
151 |
+
*
|
152 |
+
* @action update_option_theme_mods_{$stylesheet}
|
153 |
+
*/
|
154 |
+
public static function callback_update_option_theme_mods( $old, $new ) {
|
155 |
+
// Disable if we're switching themes
|
156 |
+
if ( did_action( 'after_switch_theme' ) ) {
|
157 |
+
return;
|
158 |
+
}
|
159 |
+
|
160 |
+
$key = 'nav_menu_locations';
|
161 |
+
|
162 |
+
if ( ! isset( $new[ $key ] ) ) {
|
163 |
+
return; // Switching themes ?
|
164 |
+
}
|
165 |
+
|
166 |
+
if ( $old[ $key ] === $new[ $key ] ) {
|
167 |
+
return;
|
168 |
+
}
|
169 |
+
|
170 |
+
$locations = get_registered_nav_menus();
|
171 |
+
$old_value = (array) $old[ $key ];
|
172 |
+
$new_value = (array) $new[ $key ];
|
173 |
+
$changed = array_diff_assoc( $old_value, $new_value ) + array_diff_assoc( $new_value, $old_value );
|
174 |
+
|
175 |
+
if ( $changed ) {
|
176 |
+
foreach ( $changed as $location_id => $menu_id ) {
|
177 |
+
$location = $locations[ $location_id ];
|
178 |
+
|
179 |
+
if ( empty( $new[ $key ][ $location_id ] ) ) {
|
180 |
+
$action = 'unassigned';
|
181 |
+
$menu_id = isset( $old[ $key ][ $location_id ] ) ? $old[ $key ][ $location_id ] : 0;
|
182 |
+
$message = _x(
|
183 |
+
'"%1$s" has been unassigned from "%2$s"',
|
184 |
+
'1: Menu name, 2: Theme location',
|
185 |
+
'stream'
|
186 |
+
);
|
187 |
+
} else {
|
188 |
+
$action = 'assigned';
|
189 |
+
$menu_id = isset( $new[ $key ][ $location_id ] ) ? $new[ $key ][ $location_id ] : 0;
|
190 |
+
$message = _x(
|
191 |
+
'"%1$s" has been assigned to "%2$s"',
|
192 |
+
'1: Menu name, 2: Theme location',
|
193 |
+
'stream'
|
194 |
+
);
|
195 |
+
}
|
196 |
+
|
197 |
+
$menu = get_term( $menu_id, 'nav_menu' );
|
198 |
+
|
199 |
+
if ( ! $menu || is_wp_error( $menu ) ) {
|
200 |
+
continue; // This is a deleted menu
|
201 |
+
}
|
202 |
+
|
203 |
+
$name = $menu->name;
|
204 |
+
|
205 |
+
self::log(
|
206 |
+
$message,
|
207 |
+
compact( 'name', 'location', 'location_id', 'menu_id' ),
|
208 |
+
$menu_id,
|
209 |
+
sanitize_title( $name ),
|
210 |
+
$action
|
211 |
+
);
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
}
|
216 |
+
|
217 |
+
}
|
connectors/class-wp-stream-connector-posts.php
ADDED
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Posts extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'posts';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'transition_post_status',
|
19 |
+
'deleted_post',
|
20 |
+
);
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Return translated connector label
|
24 |
+
*
|
25 |
+
* @return string Translated connector label
|
26 |
+
*/
|
27 |
+
public static function get_label() {
|
28 |
+
return __( 'Posts', 'stream' );
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Return translated action labels
|
33 |
+
*
|
34 |
+
* @return array Action label translations
|
35 |
+
*/
|
36 |
+
public static function get_action_labels() {
|
37 |
+
return array(
|
38 |
+
'updated' => __( 'Updated', 'stream' ),
|
39 |
+
'created' => __( 'Created', 'stream' ),
|
40 |
+
'trashed' => __( 'Trashed', 'stream' ),
|
41 |
+
'untrashed' => __( 'Restored', 'stream' ),
|
42 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
43 |
+
);
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Return translated context labels
|
48 |
+
*
|
49 |
+
* @return array Context label translations
|
50 |
+
*/
|
51 |
+
public static function get_context_labels() {
|
52 |
+
global $wp_post_types;
|
53 |
+
|
54 |
+
$post_types = wp_filter_object_list( $wp_post_types, array(), null, 'label' );
|
55 |
+
$post_types = array_diff_key( $post_types, array_flip( self::get_excluded_post_types() ) );
|
56 |
+
|
57 |
+
add_action( 'registered_post_type', array( __CLASS__, '_registered_post_type' ), 10, 2 );
|
58 |
+
|
59 |
+
return $post_types;
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Add action links to Stream drop row in admin list screen
|
64 |
+
*
|
65 |
+
* @filter wp_stream_action_links_{connector}
|
66 |
+
*
|
67 |
+
* @param array $links Previous links registered
|
68 |
+
* @param object $record Stream record
|
69 |
+
*
|
70 |
+
* @return array Action links
|
71 |
+
*/
|
72 |
+
public static function action_links( $links, $record ) {
|
73 |
+
$post = get_post( $record->object_id );
|
74 |
+
|
75 |
+
if ( $post && $post->post_status === wp_stream_get_meta( $record, 'new_status', true ) ) {
|
76 |
+
$post_type_name = self::get_post_type_name( get_post_type( $post->ID ) );
|
77 |
+
|
78 |
+
if ( 'trash' === $post->post_status ) {
|
79 |
+
$untrash = wp_nonce_url(
|
80 |
+
add_query_arg(
|
81 |
+
array(
|
82 |
+
'action' => 'untrash',
|
83 |
+
'post' => $post->ID,
|
84 |
+
),
|
85 |
+
admin_url( 'post.php' )
|
86 |
+
),
|
87 |
+
sprintf( 'untrash-post_%d', $post->ID )
|
88 |
+
);
|
89 |
+
|
90 |
+
$delete = wp_nonce_url(
|
91 |
+
add_query_arg(
|
92 |
+
array(
|
93 |
+
'action' => 'delete',
|
94 |
+
'post' => $post->ID,
|
95 |
+
),
|
96 |
+
admin_url( 'post.php' )
|
97 |
+
),
|
98 |
+
sprintf( 'delete-post_%d', $post->ID )
|
99 |
+
);
|
100 |
+
|
101 |
+
$links[ sprintf( esc_html_x( 'Restore %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = $untrash;
|
102 |
+
$links[ sprintf( esc_html_x( 'Delete %s Permenantly', 'Post type singular name', 'stream' ), $post_type_name ) ] = $delete;
|
103 |
+
} else {
|
104 |
+
$links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = get_edit_post_link( $post->ID );
|
105 |
+
|
106 |
+
if ( $view_link = get_permalink( $post->ID ) ) {
|
107 |
+
$links[ esc_html__( 'View', 'stream' ) ] = $view_link;
|
108 |
+
}
|
109 |
+
|
110 |
+
$revision_id = absint( wp_stream_get_meta( $record, 'revision_id', true ) );
|
111 |
+
$revision_id = self::get_adjacent_post_revision( $revision_id, false );
|
112 |
+
|
113 |
+
if ( $revision_id ) {
|
114 |
+
$links[ esc_html__( 'Revision', 'stream' ) ] = get_edit_post_link( $revision_id );
|
115 |
+
}
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
return $links;
|
120 |
+
}
|
121 |
+
|
122 |
+
/**
|
123 |
+
* Catch registeration of post_types after initial loading, to cache its labels
|
124 |
+
*
|
125 |
+
* @action registered_post_type
|
126 |
+
*
|
127 |
+
* @param string $post_type Post type slug
|
128 |
+
* @param array $args Arguments used to register the post type
|
129 |
+
*/
|
130 |
+
public static function _registered_post_type( $post_type, $args ) {
|
131 |
+
$post_type_obj = get_post_type_object( $post_type );
|
132 |
+
$label = $post_type_obj->label;
|
133 |
+
|
134 |
+
WP_Stream_Connectors::$term_labels['stream_context'][ $post_type ] = $label;
|
135 |
+
}
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Log all post status changes ( creating / updating / trashing )
|
139 |
+
*
|
140 |
+
* @action transition_post_status
|
141 |
+
*/
|
142 |
+
public static function callback_transition_post_status( $new, $old, $post ) {
|
143 |
+
if ( in_array( $post->post_type, self::get_excluded_post_types() ) ) {
|
144 |
+
return;
|
145 |
+
}
|
146 |
+
|
147 |
+
if ( in_array( $new, array( 'auto-draft', 'inherit' ) ) ) {
|
148 |
+
return;
|
149 |
+
} elseif ( 'auto-draft' === $old && 'draft' === $new ) {
|
150 |
+
$summary = _x(
|
151 |
+
'"%1$s" %2$s drafted',
|
152 |
+
'1: Post title, 2: Post type singular name',
|
153 |
+
'stream'
|
154 |
+
);
|
155 |
+
$action = 'created';
|
156 |
+
} elseif ( 'auto-draft' === $old && ( in_array( $new, array( 'publish', 'private' ) ) ) ) {
|
157 |
+
$summary = _x(
|
158 |
+
'"%1$s" %2$s published',
|
159 |
+
'1: Post title, 2: Post type singular name',
|
160 |
+
'stream'
|
161 |
+
);
|
162 |
+
$action = 'created';
|
163 |
+
} elseif ( 'draft' === $old && ( in_array( $new, array( 'publish', 'private' ) ) ) ) {
|
164 |
+
$summary = _x(
|
165 |
+
'"%1$s" %2$s published',
|
166 |
+
'1: Post title, 2: Post type singular name',
|
167 |
+
'stream'
|
168 |
+
);
|
169 |
+
} elseif ( 'publish' === $old && ( in_array( $new, array( 'draft' ) ) ) ) {
|
170 |
+
$summary = _x(
|
171 |
+
'"%1$s" %2$s unpublished',
|
172 |
+
'1: Post title, 2: Post type singular name',
|
173 |
+
'stream'
|
174 |
+
);
|
175 |
+
} elseif ( 'trash' === $new ) {
|
176 |
+
$summary = _x(
|
177 |
+
'"%1$s" %2$s trashed',
|
178 |
+
'1: Post title, 2: Post type singular name',
|
179 |
+
'stream'
|
180 |
+
);
|
181 |
+
$action = 'trashed';
|
182 |
+
} elseif ( 'trash' === $old && 'trash' !== $new ) {
|
183 |
+
$summary = _x(
|
184 |
+
'"%1$s" %2$s restored from trash',
|
185 |
+
'1: Post title, 2: Post type singular name',
|
186 |
+
'stream'
|
187 |
+
);
|
188 |
+
$action = 'untrashed';
|
189 |
+
} else {
|
190 |
+
$summary = _x(
|
191 |
+
'"%1$s" %2$s updated',
|
192 |
+
'1: Post title, 2: Post type singular name',
|
193 |
+
'stream'
|
194 |
+
);
|
195 |
+
}
|
196 |
+
|
197 |
+
if ( empty( $action ) ) {
|
198 |
+
$action = 'updated';
|
199 |
+
}
|
200 |
+
|
201 |
+
$revision_id = null;
|
202 |
+
|
203 |
+
if ( wp_revisions_enabled( $post ) ) {
|
204 |
+
$revision = get_children(
|
205 |
+
array(
|
206 |
+
'post_type' => 'revision',
|
207 |
+
'post_status' => 'inherit',
|
208 |
+
'post_parent' => $post->ID,
|
209 |
+
'posts_per_page' => 1,
|
210 |
+
'orderby' => 'post_date',
|
211 |
+
'order' => 'DESC',
|
212 |
+
)
|
213 |
+
);
|
214 |
+
|
215 |
+
if ( $revision ) {
|
216 |
+
$revision = array_values( $revision );
|
217 |
+
$revision_id = $revision[0]->ID;
|
218 |
+
}
|
219 |
+
}
|
220 |
+
|
221 |
+
$post_type_name = strtolower( self::get_post_type_name( $post->post_type ) );
|
222 |
+
|
223 |
+
self::log(
|
224 |
+
$summary,
|
225 |
+
array(
|
226 |
+
'post_title' => $post->post_title,
|
227 |
+
'singular_name' => $post_type_name,
|
228 |
+
'new_status' => $new,
|
229 |
+
'old_status' => $old,
|
230 |
+
'revision_id' => $revision_id,
|
231 |
+
),
|
232 |
+
$post->ID,
|
233 |
+
$post->post_type,
|
234 |
+
$action
|
235 |
+
);
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Log post deletion
|
240 |
+
*
|
241 |
+
* @action deleted_post
|
242 |
+
*/
|
243 |
+
public static function callback_deleted_post( $post_id ) {
|
244 |
+
$post = get_post( $post_id );
|
245 |
+
|
246 |
+
// We check if post is an instance of WP_Post as it doesn't always resolve in unit testing
|
247 |
+
if ( ! ( $post instanceof WP_Post ) || in_array( $post->post_type, self::get_excluded_post_types() ) ) {
|
248 |
+
return;
|
249 |
+
}
|
250 |
+
|
251 |
+
// Ignore auto-drafts that are deleted by the system, see issue-293
|
252 |
+
if ( 'auto-draft' === $post->post_status ) {
|
253 |
+
return;
|
254 |
+
}
|
255 |
+
|
256 |
+
$post_type_name = strtolower( self::get_post_type_name( $post->post_type ) );
|
257 |
+
|
258 |
+
self::log(
|
259 |
+
_x(
|
260 |
+
'"%1$s" %2$s deleted from trash',
|
261 |
+
'1: Post title, 2: Post type singular name',
|
262 |
+
'stream'
|
263 |
+
),
|
264 |
+
array(
|
265 |
+
'post_title' => $post->post_title,
|
266 |
+
'singular_name' => $post_type_name,
|
267 |
+
),
|
268 |
+
$post->ID,
|
269 |
+
$post->post_type,
|
270 |
+
'deleted'
|
271 |
+
);
|
272 |
+
}
|
273 |
+
|
274 |
+
/**
|
275 |
+
* Constructs list of excluded post types for the Posts connector
|
276 |
+
*
|
277 |
+
* @return array List of excluded post types
|
278 |
+
*/
|
279 |
+
public static function get_excluded_post_types() {
|
280 |
+
return apply_filters(
|
281 |
+
'wp_stream_posts_exclude_post_types',
|
282 |
+
array(
|
283 |
+
'nav_menu_item',
|
284 |
+
'attachment',
|
285 |
+
'revision',
|
286 |
+
)
|
287 |
+
);
|
288 |
+
}
|
289 |
+
|
290 |
+
/**
|
291 |
+
* Gets the singular post type label
|
292 |
+
*
|
293 |
+
* @param string $post_type_slug
|
294 |
+
* @return string Post type label
|
295 |
+
*/
|
296 |
+
public static function get_post_type_name( $post_type_slug ) {
|
297 |
+
$name = __( 'Post', 'stream' ); // Default
|
298 |
+
|
299 |
+
if ( post_type_exists( $post_type_slug ) ) {
|
300 |
+
$post_type = get_post_type_object( $post_type_slug );
|
301 |
+
$name = $post_type->labels->singular_name;
|
302 |
+
}
|
303 |
+
|
304 |
+
return $name;
|
305 |
+
}
|
306 |
+
|
307 |
+
/**
|
308 |
+
* Get an adjacent post revision ID
|
309 |
+
*
|
310 |
+
* @param int $revision_id
|
311 |
+
* @param bool $previous
|
312 |
+
*
|
313 |
+
* @return int $revision_id
|
314 |
+
*/
|
315 |
+
public static function get_adjacent_post_revision( $revision_id, $previous = true ) {
|
316 |
+
if ( empty( $revision_id ) || ! wp_is_post_revision( $revision_id ) ) {
|
317 |
+
return false;
|
318 |
+
}
|
319 |
+
|
320 |
+
$revision = wp_get_post_revision( $revision_id );
|
321 |
+
$operator = ( $previous ) ? '<' : '>';
|
322 |
+
$order = ( $previous ) ? 'DESC' : 'ASC';
|
323 |
+
|
324 |
+
global $wpdb;
|
325 |
+
|
326 |
+
$revision_id = $wpdb->get_var(
|
327 |
+
$wpdb->prepare( "
|
328 |
+
SELECT p.ID
|
329 |
+
FROM $wpdb->posts AS p
|
330 |
+
WHERE p.post_date {$operator} %s
|
331 |
+
AND p.post_type = 'revision'
|
332 |
+
AND p.post_parent = %d
|
333 |
+
ORDER BY p.post_date {$order}
|
334 |
+
LIMIT 1
|
335 |
+
",
|
336 |
+
$revision->post_date,
|
337 |
+
$revision->post_parent
|
338 |
+
)
|
339 |
+
);
|
340 |
+
|
341 |
+
$revision_id = absint( $revision_id );
|
342 |
+
|
343 |
+
if ( ! wp_is_post_revision( $revision_id ) ) {
|
344 |
+
return false;
|
345 |
+
}
|
346 |
+
|
347 |
+
return $revision_id;
|
348 |
+
}
|
349 |
+
|
350 |
+
}
|
connectors/class-wp-stream-connector-settings.php
ADDED
@@ -0,0 +1,702 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Settings extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Prefix for the highlight URL hash
|
7 |
+
*
|
8 |
+
* @const string
|
9 |
+
*/
|
10 |
+
const HIGHLIGHT_FIELD_URL_HASH_PREFIX = 'wp-stream-highlight:';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Connector slug
|
14 |
+
*
|
15 |
+
* @var string
|
16 |
+
*/
|
17 |
+
public static $name = 'settings';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'whitelist_options',
|
26 |
+
'update_site_option',
|
27 |
+
'update_option_permalink_structure',
|
28 |
+
'update_option_category_base',
|
29 |
+
'update_option_tag_base',
|
30 |
+
);
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Option names used in options-permalink.php
|
34 |
+
*
|
35 |
+
* @var array
|
36 |
+
*/
|
37 |
+
public static $permalink_options = array(
|
38 |
+
'permalink_structure',
|
39 |
+
'category_base',
|
40 |
+
'tag_base',
|
41 |
+
);
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Option names used in network/settings.php
|
45 |
+
*
|
46 |
+
* @var array
|
47 |
+
*/
|
48 |
+
public static $network_options = array(
|
49 |
+
'registrationnotification',
|
50 |
+
'registration',
|
51 |
+
'add_new_users',
|
52 |
+
'menu_items',
|
53 |
+
'upload_space_check_disabled',
|
54 |
+
'blog_upload_space',
|
55 |
+
'upload_filetypes',
|
56 |
+
'site_name',
|
57 |
+
'first_post',
|
58 |
+
'first_page',
|
59 |
+
'first_comment',
|
60 |
+
'first_comment_url',
|
61 |
+
'first_comment_author',
|
62 |
+
'welcome_email',
|
63 |
+
'welcome_user_email',
|
64 |
+
'fileupload_maxk',
|
65 |
+
'global_terms_enabled',
|
66 |
+
'illegal_names',
|
67 |
+
'limited_email_domains',
|
68 |
+
'banned_email_domains',
|
69 |
+
'WPLANG',
|
70 |
+
'admin_email',
|
71 |
+
'user_count',
|
72 |
+
);
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Register all context hooks
|
76 |
+
*
|
77 |
+
* @return void
|
78 |
+
*/
|
79 |
+
public static function register() {
|
80 |
+
parent::register();
|
81 |
+
|
82 |
+
add_action( 'admin_head', array( __CLASS__, 'highlight_field' ) );
|
83 |
+
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_jquery_color' ) );
|
84 |
+
add_action( sprintf( 'update_option_theme_mods_%s', get_option( 'stylesheet' ) ), array( __CLASS__, 'log_theme_modification' ), 10, 2 );
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* @action update_option_theme_mods_{name}
|
89 |
+
*/
|
90 |
+
public static function log_theme_modification( $old_value, $new_value ) {
|
91 |
+
self::callback_updated_option( 'theme_mods', $old_value, $new_value );
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Return translated context label
|
96 |
+
*
|
97 |
+
* @return string Translated context label
|
98 |
+
*/
|
99 |
+
public static function get_label() {
|
100 |
+
return __( 'Settings', 'stream' );
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Return translated action labels
|
105 |
+
*
|
106 |
+
* @return array Action label translations
|
107 |
+
*/
|
108 |
+
public static function get_action_labels() {
|
109 |
+
return array(
|
110 |
+
'updated' => __( 'Updated', 'stream' ),
|
111 |
+
);
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Return translated context labels
|
116 |
+
*
|
117 |
+
* @return array Context label translations
|
118 |
+
*/
|
119 |
+
public static function get_context_labels() {
|
120 |
+
$context_labels = array(
|
121 |
+
'settings' => __( 'Settings', 'stream' ),
|
122 |
+
'general' => __( 'General', 'stream' ),
|
123 |
+
'writing' => __( 'Writing', 'stream' ),
|
124 |
+
'reading' => __( 'Reading', 'stream' ),
|
125 |
+
'discussion' => __( 'Discussion', 'stream' ),
|
126 |
+
'media' => __( 'Media', 'stream' ),
|
127 |
+
'permalink' => __( 'Permalinks', 'stream' ),
|
128 |
+
'network' => __( 'Network', 'stream' ),
|
129 |
+
'wp_stream' => __( 'Stream', 'stream' ),
|
130 |
+
'custom_background' => __( 'Custom Background', 'stream' ),
|
131 |
+
'custom_header' => __( 'Custom Header', 'stream' ),
|
132 |
+
);
|
133 |
+
|
134 |
+
if ( is_network_admin() ) {
|
135 |
+
$context_labels = array_merge(
|
136 |
+
$context_labels,
|
137 |
+
array(
|
138 |
+
'wp_stream_network' => __( 'Stream Network', 'stream' ),
|
139 |
+
'wp_stream_defaults' => __( 'Stream Defaults', 'stream' ),
|
140 |
+
)
|
141 |
+
);
|
142 |
+
}
|
143 |
+
|
144 |
+
return $context_labels;
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Return context by option name and key
|
149 |
+
*
|
150 |
+
* @return string Context slug
|
151 |
+
*/
|
152 |
+
public static function get_context_by_key( $option_name, $key ) {
|
153 |
+
$contexts = array(
|
154 |
+
'theme_mods' => array(
|
155 |
+
'custom_background' => array(
|
156 |
+
'background_image',
|
157 |
+
'background_position_x',
|
158 |
+
'background_repeat',
|
159 |
+
'background_attachment',
|
160 |
+
'background_color',
|
161 |
+
),
|
162 |
+
'custom_header' => array(
|
163 |
+
'header_image',
|
164 |
+
'header_textcolor',
|
165 |
+
),
|
166 |
+
),
|
167 |
+
);
|
168 |
+
|
169 |
+
if ( isset( $contexts[ $option_name ] ) ) {
|
170 |
+
foreach ( $contexts[ $option_name ] as $context => $keys ) {
|
171 |
+
if ( in_array( $key, $keys ) ) {
|
172 |
+
return $context;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
return false;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* Find out if the option key should be ignored and not logged
|
182 |
+
*
|
183 |
+
* @return bool Whether option key is ignored or not
|
184 |
+
*/
|
185 |
+
public static function is_key_ignored( $option_name, $key ) {
|
186 |
+
$ignored = array(
|
187 |
+
'theme_mods' => array(
|
188 |
+
'background_image_thumb',
|
189 |
+
'header_image_data',
|
190 |
+
),
|
191 |
+
);
|
192 |
+
|
193 |
+
if ( isset( $ignored[ $option_name ] ) ) {
|
194 |
+
return in_array( $key, $ignored[ $option_name ] );
|
195 |
+
}
|
196 |
+
|
197 |
+
return false;
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Find out if array keys in the option should be logged separately
|
202 |
+
*
|
203 |
+
* @return bool Whether the option should be treated as a group
|
204 |
+
*/
|
205 |
+
public static function is_key_option_group( $key, $old_value, $value ) {
|
206 |
+
$not_grouped = array(
|
207 |
+
'wp_stream_connected_sites'
|
208 |
+
);
|
209 |
+
|
210 |
+
if ( in_array( $key, $not_grouped ) ) {
|
211 |
+
return false;
|
212 |
+
}
|
213 |
+
|
214 |
+
if ( ! is_array( $old_value ) && ! is_array( $value ) ) {
|
215 |
+
return false;
|
216 |
+
}
|
217 |
+
|
218 |
+
if ( 0 === count( array_filter( array_keys( $value ), 'is_string' ) ) ) {
|
219 |
+
return false;
|
220 |
+
}
|
221 |
+
|
222 |
+
return true;
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Return translated labels for all default Settings fields found in WordPress.
|
227 |
+
*
|
228 |
+
* @return array Field label translations
|
229 |
+
*/
|
230 |
+
public static function get_field_label( $field_key ) {
|
231 |
+
$labels = array(
|
232 |
+
// General
|
233 |
+
'blogname' => __( 'Site Title', 'stream' ),
|
234 |
+
'blogdescription' => __( 'Tagline', 'stream' ),
|
235 |
+
'siteurl' => __( 'WordPress Address (URL)', 'stream' ),
|
236 |
+
'home' => __( 'Site Address (URL)', 'stream' ),
|
237 |
+
'admin_email' => __( 'E-mail Address', 'stream' ),
|
238 |
+
'users_can_register' => __( 'Membership', 'stream' ),
|
239 |
+
'default_role' => __( 'New User Default Role', 'stream' ),
|
240 |
+
'timezone_string' => __( 'Timezone', 'stream' ),
|
241 |
+
'date_format' => __( 'Date Format', 'stream' ),
|
242 |
+
'time_format' => __( 'Time Format', 'stream' ),
|
243 |
+
'start_of_week' => __( 'Week Starts On', 'stream' ),
|
244 |
+
// Writing
|
245 |
+
'use_smilies' => __( 'Formatting', 'stream' ),
|
246 |
+
'use_balanceTags' => __( 'Formatting', 'stream' ),
|
247 |
+
'default_category' => __( 'Default Post Category', 'stream' ),
|
248 |
+
'default_post_format' => __( 'Default Post Format', 'stream' ),
|
249 |
+
'mailserver_url' => __( 'Mail Server', 'stream' ),
|
250 |
+
'mailserver_login' => __( 'Login Name', 'stream' ),
|
251 |
+
'mailserver_pass' => __( 'Password', 'stream' ),
|
252 |
+
'default_email_category' => __( 'Default Mail Category', 'stream' ),
|
253 |
+
'ping_sites' => __( 'Update Services', 'stream' ),
|
254 |
+
// Reading
|
255 |
+
'show_on_front' => __( 'Front page displays', 'stream' ),
|
256 |
+
'page_on_front' => __( 'Front page displays', 'stream' ),
|
257 |
+
'page_for_posts' => __( 'Front page displays', 'stream' ),
|
258 |
+
'posts_per_page' => __( 'Blog pages show at most', 'stream' ),
|
259 |
+
'posts_per_rss' => __( 'Syndication feeds show the most recent', 'stream' ),
|
260 |
+
'rss_use_excerpt' => __( 'For each article in a feed, show', 'stream' ),
|
261 |
+
'blog_public' => __( 'Search Engine Visibility', 'stream' ),
|
262 |
+
// Discussion
|
263 |
+
'default_pingback_flag' => __( 'Default article settings', 'stream' ),
|
264 |
+
'default_ping_status' => __( 'Default article settings', 'stream' ),
|
265 |
+
'default_comment_status' => __( 'Default article settings', 'stream' ),
|
266 |
+
'require_name_email' => __( 'Other comment settings', 'stream' ),
|
267 |
+
'comment_registration' => __( 'Other comment settings', 'stream' ),
|
268 |
+
'close_comments_for_old_posts' => __( 'Other comment settings', 'stream' ),
|
269 |
+
'close_comments_days_old' => __( 'Other comment settings', 'stream' ),
|
270 |
+
'thread_comments' => __( 'Other comment settings', 'stream' ),
|
271 |
+
'thread_comments_depth' => __( 'Other comment settings', 'stream' ),
|
272 |
+
'page_comments' => __( 'Other comment settings', 'stream' ),
|
273 |
+
'comments_per_page' => __( 'Other comment settings', 'stream' ),
|
274 |
+
'default_comments_page' => __( 'Other comment settings', 'stream' ),
|
275 |
+
'comment_order' => __( 'Other comment settings', 'stream' ),
|
276 |
+
'comments_notify' => __( 'E-mail me whenever', 'stream' ),
|
277 |
+
'moderation_notify' => __( 'E-mail me whenever', 'stream' ),
|
278 |
+
'comment_moderation' => __( 'Before a comment appears', 'stream' ),
|
279 |
+
'comment_whitelist' => __( 'Before a comment appears', 'stream' ),
|
280 |
+
'comment_max_links' => __( 'Comment Moderation', 'stream' ),
|
281 |
+
'moderation_keys' => __( 'Comment Moderation', 'stream' ),
|
282 |
+
'blacklist_keys' => __( 'Comment Blacklist', 'stream' ),
|
283 |
+
'show_avatars' => __( 'Show Avatars', 'stream' ),
|
284 |
+
'avatar_rating' => __( 'Maximum Rating', 'stream' ),
|
285 |
+
'avatar_default' => __( 'Default Avatar', 'stream' ),
|
286 |
+
// Media
|
287 |
+
'thumbnail_size_w' => __( 'Thumbnail size', 'stream' ),
|
288 |
+
'thumbnail_size_h' => __( 'Thumbnail size', 'stream' ),
|
289 |
+
'thumbnail_crop' => __( 'Thumbnail size', 'stream' ),
|
290 |
+
'medium_size_w' => __( 'Medium size', 'stream' ),
|
291 |
+
'medium_size_h' => __( 'Medium size', 'stream' ),
|
292 |
+
'large_size_w' => __( 'Large size', 'stream' ),
|
293 |
+
'large_size_h' => __( 'Large size', 'stream' ),
|
294 |
+
'uploads_use_yearmonth_folders' => __( 'Uploading Files', 'stream' ),
|
295 |
+
// Permalinks
|
296 |
+
'permalink_structure' => __( 'Permalink Settings', 'stream' ),
|
297 |
+
'category_base' => __( 'Category base', 'stream' ),
|
298 |
+
'tag_base' => __( 'Tag base', 'stream' ),
|
299 |
+
// Network
|
300 |
+
'registrationnotification' => __( 'Registration notification', 'stream' ),
|
301 |
+
'registration' => __( 'Allow new registrations', 'stream' ),
|
302 |
+
'add_new_users' => __( 'Add New Users', 'stream' ),
|
303 |
+
'menu_items' => __( 'Enable administration menus', 'stream' ),
|
304 |
+
'upload_space_check_disabled' => __( 'Site upload space check', 'stream' ),
|
305 |
+
'blog_upload_space' => __( 'Site upload space', 'stream' ),
|
306 |
+
'upload_filetypes' => __( 'Upload file types', 'stream' ),
|
307 |
+
'site_name' => __( 'Network Title', 'stream' ),
|
308 |
+
'first_post' => __( 'First Post', 'stream' ),
|
309 |
+
'first_page' => __( 'First Page', 'stream' ),
|
310 |
+
'first_comment' => __( 'First Comment', 'stream' ),
|
311 |
+
'first_comment_url' => __( 'First Comment URL', 'stream' ),
|
312 |
+
'first_comment_author' => __( 'First Comment Author', 'stream' ),
|
313 |
+
'welcome_email' => __( 'Welcome Email', 'stream' ),
|
314 |
+
'welcome_user_email' => __( 'Welcome User Email', 'stream' ),
|
315 |
+
'fileupload_maxk' => __( 'Max upload file size', 'stream' ),
|
316 |
+
'global_terms_enabled' => __( 'Terms Enabled', 'stream' ),
|
317 |
+
'illegal_names' => __( 'Banned Names', 'stream' ),
|
318 |
+
'limited_email_domains' => __( 'Limited Email Registrations', 'stream' ),
|
319 |
+
'banned_email_domains' => __( 'Banned Email Domains', 'stream' ),
|
320 |
+
'WPLANG' => __( 'Network Language', 'stream' ),
|
321 |
+
'admin_email' => __( 'Network Admin Email', 'stream' ),
|
322 |
+
'user_count' => __( 'User Count', 'stream' ),
|
323 |
+
// Other
|
324 |
+
'wp_stream_db' => __( 'Stream Database Version', 'stream' ),
|
325 |
+
'wp_stream_connected_sites' => __( 'Stream Connected Sites', 'stream' ),
|
326 |
+
);
|
327 |
+
|
328 |
+
if ( isset( $labels[ $field_key ] ) ) {
|
329 |
+
return $labels[ $field_key ];
|
330 |
+
}
|
331 |
+
|
332 |
+
return $field_key;
|
333 |
+
}
|
334 |
+
|
335 |
+
/**
|
336 |
+
* Enqueue jQuery Color plugin
|
337 |
+
*
|
338 |
+
* @action admin_enqueue_scripts
|
339 |
+
* @return void
|
340 |
+
*/
|
341 |
+
public static function enqueue_jquery_color() {
|
342 |
+
wp_enqueue_script( 'jquery-color' );
|
343 |
+
}
|
344 |
+
|
345 |
+
/**
|
346 |
+
* Return translated labels for all serialized Settings found in WordPress.
|
347 |
+
*
|
348 |
+
* @return string Field key translation or key itself if not found
|
349 |
+
*/
|
350 |
+
public static function get_serialized_field_label( $option_name, $field_key ) {
|
351 |
+
$labels = array(
|
352 |
+
'theme_mods' => array(
|
353 |
+
// Custom Background
|
354 |
+
'background_image' => __( 'Background Image', 'stream' ),
|
355 |
+
'background_position_x' => __( 'Background Position', 'stream' ),
|
356 |
+
'background_repeat' => __( 'Background Repeat', 'stream' ),
|
357 |
+
'background_attachment' => __( 'Background Attachment', 'stream' ),
|
358 |
+
'background_color' => __( 'Background Color', 'stream' ),
|
359 |
+
// Custom Header
|
360 |
+
'header_image' => __( 'Header Image', 'stream' ),
|
361 |
+
'header_textcolor' => __( 'Text Color', 'stream' ),
|
362 |
+
),
|
363 |
+
);
|
364 |
+
|
365 |
+
/**
|
366 |
+
* Filter allows for insertion of serialized labels
|
367 |
+
*
|
368 |
+
* @param array $lables Serialized labels
|
369 |
+
* @return array Updated array of serialzed labels
|
370 |
+
*/
|
371 |
+
$labels = apply_filters( 'wp_stream_serialized_labels', $labels );
|
372 |
+
|
373 |
+
if ( isset( $labels[ $option_name ] ) && isset( $labels[ $option_name ][ $field_key ] ) ) {
|
374 |
+
return $labels[ $option_name ][ $field_key ];
|
375 |
+
}
|
376 |
+
|
377 |
+
return $field_key;
|
378 |
+
}
|
379 |
+
|
380 |
+
/**
|
381 |
+
* Add action links to Stream drop row in admin list screen
|
382 |
+
*
|
383 |
+
* @filter wp_stream_action_links_{connector}
|
384 |
+
*
|
385 |
+
* @param array $links Previous links registered
|
386 |
+
* @param object $record Stream record
|
387 |
+
*
|
388 |
+
* @return array Action links
|
389 |
+
*/
|
390 |
+
public static function action_links( $links, $record ) {
|
391 |
+
$context_labels = self::get_context_labels();
|
392 |
+
|
393 |
+
$rules = array(
|
394 |
+
'stream' => array(
|
395 |
+
'menu_slug' => 'wp_stream',
|
396 |
+
'submenu_slug' => WP_Stream_Admin::SETTINGS_PAGE_SLUG,
|
397 |
+
'url' => function( $rule, $record ) {
|
398 |
+
$option_key = wp_stream_get_meta( $record, 'option_key', true );
|
399 |
+
$url_tab = null;
|
400 |
+
|
401 |
+
if ( '' !== $option_key ) {
|
402 |
+
foreach ( WP_Stream_Settings::get_fields() as $tab_name => $tab_properties ) {
|
403 |
+
foreach ( $tab_properties['fields'] as $field ) {
|
404 |
+
$field_key = sprintf( '%s_%s', $tab_name, $field['name'] );
|
405 |
+
if ( $field_key === $option_key ) {
|
406 |
+
$url_tab = $tab_name;
|
407 |
+
break 2;
|
408 |
+
}
|
409 |
+
}
|
410 |
+
}
|
411 |
+
}
|
412 |
+
|
413 |
+
return add_query_arg(
|
414 |
+
array(
|
415 |
+
'page' => $rule['submenu_slug'],
|
416 |
+
'tab' => $url_tab,
|
417 |
+
),
|
418 |
+
admin_url( 'admin.php' )
|
419 |
+
);
|
420 |
+
},
|
421 |
+
'applicable' => function( $submenu, $record ) {
|
422 |
+
return $record->context === 'wp_stream';
|
423 |
+
}
|
424 |
+
),
|
425 |
+
'background_header' => array(
|
426 |
+
'menu_slug' => 'themes.php',
|
427 |
+
'submenu_slug' => function( $record ) {
|
428 |
+
return str_replace( '_', '-', $record->context );
|
429 |
+
},
|
430 |
+
'url' => function( $rule, $record ) {
|
431 |
+
return add_query_arg( 'page', $rule['submenu_slug']( $record ), admin_url( $rule['menu_slug'] ) );
|
432 |
+
},
|
433 |
+
'applicable' => function( $submenu, $record ) {
|
434 |
+
return in_array( $record->context, array( 'custom_header', 'custom_background' ) );
|
435 |
+
}
|
436 |
+
),
|
437 |
+
'general' => array(
|
438 |
+
'menu_slug' => 'options-general.php',
|
439 |
+
'submenu_slug' => function( $record ) {
|
440 |
+
return sprintf( 'options-%s.php', $record->context );
|
441 |
+
},
|
442 |
+
'url' => function( $rule, $record ) {
|
443 |
+
return admin_url( $rule['submenu_slug']( $record ) );
|
444 |
+
},
|
445 |
+
'applicable' => function( $submenu, $record ) {
|
446 |
+
return ! empty( $submenu['options-general.php'] );
|
447 |
+
},
|
448 |
+
),
|
449 |
+
'network' => array(
|
450 |
+
'menu_slug' => 'settings.php',
|
451 |
+
'submenu_slug' => function( $record ) {
|
452 |
+
return 'settings.php';
|
453 |
+
},
|
454 |
+
'url' => function( $rule, $record ) {
|
455 |
+
return network_admin_url( $rule['menu_slug'] );
|
456 |
+
},
|
457 |
+
'applicable' => function( $submenu, $record ) {
|
458 |
+
if ( ! $record->blog_id ) {
|
459 |
+
return ! empty( $submenu['settings.php'] );
|
460 |
+
}
|
461 |
+
return false;
|
462 |
+
},
|
463 |
+
),
|
464 |
+
);
|
465 |
+
|
466 |
+
if ( 'settings' !== $record->context && in_array( $record->context, array_keys( $context_labels ) ) ) {
|
467 |
+
global $submenu;
|
468 |
+
|
469 |
+
$applicable_rules = array_filter(
|
470 |
+
$rules,
|
471 |
+
function( $rule ) use ( $submenu, $record ) {
|
472 |
+
return call_user_func( $rule['applicable'], $submenu, $record );
|
473 |
+
}
|
474 |
+
);
|
475 |
+
|
476 |
+
if ( ! empty( $applicable_rules ) ) {
|
477 |
+
// The first applicable rule wins
|
478 |
+
$rule = array_shift( $applicable_rules );
|
479 |
+
$menu_slug = $rule['menu_slug'];
|
480 |
+
$submenu_slug = ( is_object( $rule['submenu_slug'] ) && $rule['submenu_slug'] instanceOf Closure ? $rule['submenu_slug']( $record ) : $rule['submenu_slug'] );
|
481 |
+
$url = $rule['url']( $rule, $record );
|
482 |
+
|
483 |
+
if ( isset( $submenu[ $menu_slug ] ) ) {
|
484 |
+
$found_submenus = wp_list_filter(
|
485 |
+
$submenu[ $menu_slug ],
|
486 |
+
array( 2 => $submenu_slug )
|
487 |
+
);
|
488 |
+
}
|
489 |
+
|
490 |
+
if ( ! empty( $found_submenus ) ) {
|
491 |
+
$target_submenu = array_pop( $found_submenus );
|
492 |
+
list( $menu_title, $capability ) = $target_submenu;
|
493 |
+
|
494 |
+
if ( current_user_can( $capability ) ) {
|
495 |
+
$url = apply_filters( 'wp_stream_action_link_url', $url, $record );
|
496 |
+
$text = sprintf( __( 'Edit %s Settings', 'stream' ), $context_labels[ $record->context ] );
|
497 |
+
$field_name = wp_stream_get_meta( $record, 'option_key', true );
|
498 |
+
|
499 |
+
if ( '' === $field_name ) {
|
500 |
+
$field_name = wp_stream_get_meta( $record, 'option', true );
|
501 |
+
}
|
502 |
+
|
503 |
+
if ( '' !== $field_name ) {
|
504 |
+
$url = sprintf( '%s#%s%s', rtrim( preg_replace( '/#.*/', '', $url ), '/' ), self::HIGHLIGHT_FIELD_URL_HASH_PREFIX, $field_name );
|
505 |
+
}
|
506 |
+
|
507 |
+
$links[ $text ] = $url;
|
508 |
+
}
|
509 |
+
}
|
510 |
+
}
|
511 |
+
}
|
512 |
+
|
513 |
+
return $links;
|
514 |
+
}
|
515 |
+
|
516 |
+
/**
|
517 |
+
* Trigger this connector core tracker, only on options.php page
|
518 |
+
*
|
519 |
+
* @action whitelist_options
|
520 |
+
*/
|
521 |
+
public static function callback_whitelist_options( $options ) {
|
522 |
+
add_action( 'updated_option', array( __CLASS__, 'callback' ), 10, 3 );
|
523 |
+
|
524 |
+
return $options;
|
525 |
+
}
|
526 |
+
|
527 |
+
/**
|
528 |
+
* Trigger this connector core tracker, only on options-permalink.php page
|
529 |
+
*
|
530 |
+
* @action update_option_permalink_structure
|
531 |
+
*/
|
532 |
+
public static function callback_update_option_permalink_structure( $old_value, $value ) {
|
533 |
+
self::callback_updated_option( 'permalink_structure', $old_value, $value );
|
534 |
+
}
|
535 |
+
|
536 |
+
/**
|
537 |
+
* Trigger this connector core tracker, only on network/settings.php page
|
538 |
+
*
|
539 |
+
* @action update_site_option
|
540 |
+
*/
|
541 |
+
public static function callback_update_site_option( $option, $value, $old_value ) {
|
542 |
+
self::callback_updated_option( $option, $value, $old_value );
|
543 |
+
}
|
544 |
+
|
545 |
+
/**
|
546 |
+
* Trigger this connector core tracker, only on options-permalink.php page
|
547 |
+
*
|
548 |
+
* @action update_option_category_base
|
549 |
+
*/
|
550 |
+
public static function callback_update_option_category_base( $old_value, $value ) {
|
551 |
+
self::callback_updated_option( 'category_base', $old_value, $value );
|
552 |
+
}
|
553 |
+
|
554 |
+
/**
|
555 |
+
* Trigger this connector core tracker, only on options-permalink.php page
|
556 |
+
*
|
557 |
+
* @action update_option_tag_base
|
558 |
+
*/
|
559 |
+
public static function callback_update_option_tag_base( $old_value, $value ) {
|
560 |
+
self::callback_updated_option( 'tag_base', $old_value, $value );
|
561 |
+
}
|
562 |
+
|
563 |
+
/**
|
564 |
+
* Track updated settings
|
565 |
+
*
|
566 |
+
* @action updated_option
|
567 |
+
*/
|
568 |
+
public static function callback_updated_option( $option, $old_value, $value ) {
|
569 |
+
global $whitelist_options, $new_whitelist_options;
|
570 |
+
|
571 |
+
if ( 0 === strpos( $option, '_transient_' ) || 0 === strpos( $option, '_site_transient_' ) ) {
|
572 |
+
return;
|
573 |
+
}
|
574 |
+
|
575 |
+
$options = array_merge(
|
576 |
+
(array) $whitelist_options,
|
577 |
+
(array) $new_whitelist_options,
|
578 |
+
array( 'permalink' => self::$permalink_options ),
|
579 |
+
array( 'network' => self::$network_options )
|
580 |
+
);
|
581 |
+
|
582 |
+
foreach ( $options as $key => $opts ) {
|
583 |
+
if ( in_array( $option, $opts ) ) {
|
584 |
+
$context = $key;
|
585 |
+
break;
|
586 |
+
}
|
587 |
+
}
|
588 |
+
|
589 |
+
if ( ! isset( $context ) ) {
|
590 |
+
$context = 'settings';
|
591 |
+
}
|
592 |
+
|
593 |
+
$changed_options = array();
|
594 |
+
$option_group = self::is_key_option_group( $option, $old_value, $value );
|
595 |
+
|
596 |
+
if ( $option_group ) {
|
597 |
+
foreach ( self::get_changed_keys( $old_value, $value ) as $field_key ) {
|
598 |
+
if ( ! self::is_key_ignored( $option, $field_key ) ) {
|
599 |
+
$key_context = self::get_context_by_key( $option, $field_key );
|
600 |
+
$changed_options[] = array(
|
601 |
+
'label' => self::get_serialized_field_label( $option, $field_key ),
|
602 |
+
'option' => $option,
|
603 |
+
'option_key' => $field_key,
|
604 |
+
'context' => ( false !== $key_context ? $key_context : $context ),
|
605 |
+
// Prevent fatal error when saving option as array
|
606 |
+
'old_value' => isset( $old_value[ $field_key ] ) ? (string) maybe_serialize( $old_value[ $field_key ] ) : null,
|
607 |
+
'value' => isset( $value[ $field_key ] ) ? (string) maybe_serialize( $value[ $field_key ] ) : null,
|
608 |
+
);
|
609 |
+
}
|
610 |
+
}
|
611 |
+
} else {
|
612 |
+
$changed_options[] = array(
|
613 |
+
'label' => self::get_field_label( $option ),
|
614 |
+
'option' => $option,
|
615 |
+
'context' => $context,
|
616 |
+
// Prevent fatal error when saving option as array
|
617 |
+
'old_value' => (string) maybe_serialize( $old_value ),
|
618 |
+
'value' => (string) maybe_serialize( $value ),
|
619 |
+
);
|
620 |
+
}
|
621 |
+
|
622 |
+
foreach ( $changed_options as $properties ) {
|
623 |
+
self::log(
|
624 |
+
__( '"%s" setting was updated', 'stream' ),
|
625 |
+
$properties,
|
626 |
+
null,
|
627 |
+
$properties['context'],
|
628 |
+
'updated'
|
629 |
+
);
|
630 |
+
}
|
631 |
+
}
|
632 |
+
|
633 |
+
/**
|
634 |
+
* Add class to highlight field by URL param
|
635 |
+
*
|
636 |
+
* @action admin_head
|
637 |
+
*/
|
638 |
+
public static function highlight_field() {
|
639 |
+
?>
|
640 |
+
<script>
|
641 |
+
(function ($) {
|
642 |
+
$(function () {
|
643 |
+
var hashPrefix = <?php echo json_encode( self::HIGHLIGHT_FIELD_URL_HASH_PREFIX ) ?>,
|
644 |
+
hashFieldName = "",
|
645 |
+
fieldNames = [],
|
646 |
+
$select2Choices = {},
|
647 |
+
$field = {};
|
648 |
+
|
649 |
+
if (location.hash.substr(1, hashPrefix.length) === hashPrefix) {
|
650 |
+
hashFieldName = location.hash.substr(hashPrefix.length + 1);
|
651 |
+
fieldNames = [hashFieldName];
|
652 |
+
|
653 |
+
$field = $("input, textarea, select").filter(function () {
|
654 |
+
return fieldNames.indexOf( $(this).attr("name") ) > -1;
|
655 |
+
});
|
656 |
+
|
657 |
+
// try to find wp_stream field
|
658 |
+
if ( $field.length === 0 ) {
|
659 |
+
fieldNames = [
|
660 |
+
"wp_stream_" + hashFieldName,
|
661 |
+
"wp_stream[" + hashFieldName + "]"
|
662 |
+
];
|
663 |
+
|
664 |
+
$field = $("input, textarea, select, div").filter(function () {
|
665 |
+
return fieldNames.indexOf( $(this).attr("id") ) > -1;
|
666 |
+
});
|
667 |
+
|
668 |
+
// if the field has been selectified, the list is the one to be colorized
|
669 |
+
$select2Choices = $field.find(".select2-choices");
|
670 |
+
if ( $select2Choices.length === 1 ) {
|
671 |
+
$field = $select2Choices;
|
672 |
+
}
|
673 |
+
}
|
674 |
+
|
675 |
+
$("html, body")
|
676 |
+
.animate({
|
677 |
+
scrollTop: ($field.closest("tr").length === 1 ? $field.closest("tr") : $field).offset().top - $("#wpadminbar").height()
|
678 |
+
}, 1000, function () {
|
679 |
+
|
680 |
+
$field
|
681 |
+
.css("background", $(this).css("background-color"))
|
682 |
+
.animate({
|
683 |
+
backgroundColor: "#fffedf",
|
684 |
+
}, 250);
|
685 |
+
|
686 |
+
$("label")
|
687 |
+
.filter(function () {
|
688 |
+
return fieldNames.indexOf( $(this).attr("for") ) > -1;
|
689 |
+
})
|
690 |
+
.animate({
|
691 |
+
color: "#d54e21"
|
692 |
+
}, 250);
|
693 |
+
}
|
694 |
+
);
|
695 |
+
}
|
696 |
+
});
|
697 |
+
}(jQuery));
|
698 |
+
</script>
|
699 |
+
<?php
|
700 |
+
}
|
701 |
+
|
702 |
+
}
|
connectors/class-wp-stream-connector-stream.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Stream extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector name/slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'stream';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'wp_stream_site_connected',
|
19 |
+
'wp_stream_site_disconnected',
|
20 |
+
);
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Return translated connector label
|
24 |
+
*
|
25 |
+
* @return string
|
26 |
+
*/
|
27 |
+
public static function get_label() {
|
28 |
+
return __( 'Stream', 'stream' );
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Return translated context labels
|
33 |
+
*
|
34 |
+
* @return array
|
35 |
+
*/
|
36 |
+
public static function get_context_labels() {
|
37 |
+
return array(
|
38 |
+
'site' => __( 'Site', 'stream' ),
|
39 |
+
);
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Return translated action labels
|
44 |
+
*
|
45 |
+
* @return array
|
46 |
+
*/
|
47 |
+
public static function get_action_labels() {
|
48 |
+
return array(
|
49 |
+
'connected' => __( 'Connected', 'stream' ),
|
50 |
+
'disconnected' => __( 'Disconnected', 'stream' ),
|
51 |
+
);
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Site is connected
|
56 |
+
*
|
57 |
+
* @param string $site_uuid
|
58 |
+
* @param string $api_key
|
59 |
+
* @param int $blog_id
|
60 |
+
*
|
61 |
+
* @return void
|
62 |
+
*/
|
63 |
+
public static function callback_wp_stream_site_connected( $site_uuid, $api_key, $blog_id ) {
|
64 |
+
self::log(
|
65 |
+
__( 'Site connected to Stream', 'stream' ),
|
66 |
+
array(
|
67 |
+
'site_uuid' => $site_uuid,
|
68 |
+
'api_key' => $api_key,
|
69 |
+
),
|
70 |
+
$blog_id,
|
71 |
+
'site',
|
72 |
+
'connected'
|
73 |
+
);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Site is disconnected
|
78 |
+
*
|
79 |
+
* @param string $site_uuid
|
80 |
+
* @param string $api_key
|
81 |
+
* @param int $blog_id
|
82 |
+
*
|
83 |
+
* @return void
|
84 |
+
*/
|
85 |
+
public static function callback_wp_stream_site_disconnected( $site_uuid, $api_key, $blog_id ) {
|
86 |
+
self::log(
|
87 |
+
__( 'Site disconnected from Stream', 'stream' ),
|
88 |
+
array(
|
89 |
+
'site_uuid' => $site_uuid,
|
90 |
+
'api_key' => $api_key,
|
91 |
+
),
|
92 |
+
$blog_id,
|
93 |
+
'site',
|
94 |
+
'disconnected'
|
95 |
+
);
|
96 |
+
}
|
97 |
+
|
98 |
+
}
|
connectors/class-wp-stream-connector-taxonomies.php
ADDED
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Taxonomies extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'taxonomies';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Actions registered for this connector
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
*/
|
17 |
+
public static $actions = array(
|
18 |
+
'created_term',
|
19 |
+
'delete_term',
|
20 |
+
'edit_term',
|
21 |
+
'edited_term',
|
22 |
+
);
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Cache term values before update, used by callback_edit_term/callback_edited_term
|
26 |
+
*
|
27 |
+
* @var Object
|
28 |
+
*/
|
29 |
+
public static $cached_term_before_update;
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Cache taxonomy labels
|
33 |
+
*
|
34 |
+
* @var array
|
35 |
+
*/
|
36 |
+
public static $context_labels;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Return translated connector label
|
40 |
+
*
|
41 |
+
* @return string Translated connector label
|
42 |
+
*/
|
43 |
+
public static function get_label() {
|
44 |
+
return __( 'Taxonomies', 'stream' );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Return translated action labels
|
49 |
+
*
|
50 |
+
* @return array Action label translations
|
51 |
+
*/
|
52 |
+
public static function get_action_labels() {
|
53 |
+
return array(
|
54 |
+
'created' => __( 'Created', 'stream' ),
|
55 |
+
'updated' => __( 'Updated', 'stream' ),
|
56 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
57 |
+
);
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Return translated context labels
|
62 |
+
*
|
63 |
+
* @return array Context label translations
|
64 |
+
*/
|
65 |
+
public static function get_context_labels() {
|
66 |
+
global $wp_taxonomies;
|
67 |
+
|
68 |
+
$labels = wp_list_pluck( $wp_taxonomies, 'labels' );
|
69 |
+
|
70 |
+
self::$context_labels = wp_list_pluck( $labels, 'singular_name' );
|
71 |
+
|
72 |
+
add_action( 'registered_taxonomy', array( __CLASS__, '_registered_taxonomy' ), 10, 3 );
|
73 |
+
|
74 |
+
return self::$context_labels;
|
75 |
+
}
|
76 |
+
|
77 |
+
/**
|
78 |
+
* Add action links to Stream drop row in admin list screen
|
79 |
+
*
|
80 |
+
* @filter wp_stream_action_links_{connector}
|
81 |
+
*
|
82 |
+
* @param array $links Previous links registered
|
83 |
+
* @param object $record Stream record
|
84 |
+
*
|
85 |
+
* @return array Action links
|
86 |
+
*/
|
87 |
+
public static function action_links( $links, $record ) {
|
88 |
+
if ( $record->object_id && 'deleted' !== $record->action && ( $term = get_term_by( 'term_taxonomy_id', $record->object_id, $record->context ) ) ) {
|
89 |
+
if ( ! is_wp_error( $term ) ) {
|
90 |
+
$tax_obj = get_taxonomy( $term->taxonomy );
|
91 |
+
$tax_label = isset( $tax_obj->labels->singular_name ) ? $tax_obj->labels->singular_name : null;
|
92 |
+
|
93 |
+
$links[ sprintf( _x( 'Edit %s', 'Term singular name', 'stream' ), $tax_label ) ] = get_edit_term_link( $term->term_id, $term->taxonomy );
|
94 |
+
$links[ __( 'View', 'stream' ) ] = get_term_link( $term->term_id, $term->taxonomy );
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
return $links;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Catch registration of taxonomies after inital loading, so we can cache its labels
|
103 |
+
*
|
104 |
+
* @action registered_taxonomy
|
105 |
+
*
|
106 |
+
* @param string $taxonomy Taxonomy slug
|
107 |
+
* @param array|string $object_type Object type or array of object types
|
108 |
+
* @param array|string $args Array or string of taxonomy registration arguments
|
109 |
+
*/
|
110 |
+
public static function _registered_taxonomy( $taxonomy, $object_type, $args ) {
|
111 |
+
$taxonomy_obj = (object) $args;
|
112 |
+
$label = get_taxonomy_labels( $taxonomy_obj )->name;
|
113 |
+
|
114 |
+
self::$context_labels[ $taxonomy ] = $label;
|
115 |
+
|
116 |
+
WP_Stream_Connectors::$term_labels['stream_context'][ $taxonomy ] = $label;
|
117 |
+
}
|
118 |
+
|
119 |
+
/**
|
120 |
+
* Tracks creation of terms
|
121 |
+
*
|
122 |
+
* @action created_term
|
123 |
+
*/
|
124 |
+
public static function callback_created_term( $term_id, $tt_id, $taxonomy ) {
|
125 |
+
if ( in_array( $taxonomy, self::get_excluded_taxonomies() ) ) {
|
126 |
+
return;
|
127 |
+
}
|
128 |
+
|
129 |
+
$term = get_term( $term_id, $taxonomy );
|
130 |
+
$term_name = $term->name;
|
131 |
+
$taxonomy_label = strtolower( self::$context_labels[ $taxonomy ] );
|
132 |
+
$term_parent = $term->parent;
|
133 |
+
|
134 |
+
self::log(
|
135 |
+
_x(
|
136 |
+
'"%1$s" %2$s created',
|
137 |
+
'1: Term name, 2: Taxonomy singular label',
|
138 |
+
'stream'
|
139 |
+
),
|
140 |
+
compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
|
141 |
+
$tt_id,
|
142 |
+
$taxonomy,
|
143 |
+
'created'
|
144 |
+
);
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Tracks deletion of taxonomy terms
|
149 |
+
*
|
150 |
+
* @action delete_term
|
151 |
+
*/
|
152 |
+
public static function callback_delete_term( $term_id, $tt_id, $taxonomy, $deleted_term ) {
|
153 |
+
if ( in_array( $taxonomy, self::get_excluded_taxonomies() ) ) {
|
154 |
+
return;
|
155 |
+
}
|
156 |
+
|
157 |
+
$term_name = $deleted_term->name;
|
158 |
+
$term_parent = $deleted_term->parent;
|
159 |
+
$taxonomy_label = strtolower( self::$context_labels[ $taxonomy ] );
|
160 |
+
|
161 |
+
self::log(
|
162 |
+
_x(
|
163 |
+
'"%1$s" %2$s deleted',
|
164 |
+
'1: Term name, 2: Taxonomy singular label',
|
165 |
+
'stream'
|
166 |
+
),
|
167 |
+
compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
|
168 |
+
$tt_id,
|
169 |
+
$taxonomy,
|
170 |
+
'deleted'
|
171 |
+
);
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Tracks updates of taxonomy terms
|
176 |
+
*
|
177 |
+
* @action edit_term
|
178 |
+
*/
|
179 |
+
public static function callback_edit_term( $term_id, $tt_id, $taxonomy ) {
|
180 |
+
self::$cached_term_before_update = get_term( $term_id, $taxonomy );
|
181 |
+
}
|
182 |
+
|
183 |
+
public static function callback_edited_term( $term_id, $tt_id, $taxonomy ) {
|
184 |
+
if ( in_array( $taxonomy, self::get_excluded_taxonomies() ) ) {
|
185 |
+
return;
|
186 |
+
}
|
187 |
+
|
188 |
+
$term = self::$cached_term_before_update;
|
189 |
+
|
190 |
+
if ( ! $term ) { // For some reason!
|
191 |
+
$term = get_term( $term_id, $taxonomy );
|
192 |
+
}
|
193 |
+
|
194 |
+
$term_name = $term->name;
|
195 |
+
$taxonomy_label = strtolower( self::$context_labels[ $taxonomy ] );
|
196 |
+
$term_parent = $term->parent;
|
197 |
+
|
198 |
+
self::log(
|
199 |
+
_x(
|
200 |
+
'"%1$s" %2$s updated',
|
201 |
+
'1: Term name, 2: Taxonomy singular label',
|
202 |
+
'stream'
|
203 |
+
),
|
204 |
+
compact( 'term_name', 'taxonomy_label', 'term_id', 'taxonomy', 'term_parent' ),
|
205 |
+
$tt_id,
|
206 |
+
$taxonomy,
|
207 |
+
'updated'
|
208 |
+
);
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Constructs list of excluded taxonomies for the Taxonomies connector
|
213 |
+
*
|
214 |
+
* @return array List of excluded taxonomies
|
215 |
+
*/
|
216 |
+
public static function get_excluded_taxonomies() {
|
217 |
+
return apply_filters(
|
218 |
+
'wp_stream_taxonomies_exclude_taxonomies',
|
219 |
+
array(
|
220 |
+
'nav_menu',
|
221 |
+
)
|
222 |
+
);
|
223 |
+
}
|
224 |
+
|
225 |
+
}
|
connectors/class-wp-stream-connector-users.php
ADDED
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Users extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'users';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Stores users object before the user being deleted.
|
14 |
+
*
|
15 |
+
* @var array
|
16 |
+
* @access protected
|
17 |
+
*/
|
18 |
+
protected static $_users_object_pre_deleted = array();
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Actions registered for this connector
|
22 |
+
*
|
23 |
+
* @var array
|
24 |
+
*/
|
25 |
+
public static $actions = array(
|
26 |
+
'user_register',
|
27 |
+
'profile_update',
|
28 |
+
'password_reset',
|
29 |
+
'retrieve_password',
|
30 |
+
'set_logged_in_cookie',
|
31 |
+
'clear_auth_cookie',
|
32 |
+
'delete_user',
|
33 |
+
'deleted_user',
|
34 |
+
'set_user_role',
|
35 |
+
);
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Return translated connector label
|
39 |
+
*
|
40 |
+
* @return string Translated connector label
|
41 |
+
*/
|
42 |
+
public static function get_label() {
|
43 |
+
return __( 'Users', 'stream' );
|
44 |
+
}
|
45 |
+
|
46 |
+
/**
|
47 |
+
* Return translated action term labels
|
48 |
+
*
|
49 |
+
* @return array Action terms label translation
|
50 |
+
*/
|
51 |
+
public static function get_action_labels() {
|
52 |
+
return array(
|
53 |
+
'updated' => __( 'Updated', 'stream' ),
|
54 |
+
'created' => __( 'Created', 'stream' ),
|
55 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
56 |
+
'password-reset' => __( 'Password Reset', 'stream' ),
|
57 |
+
'forgot-password' => __( 'Lost Password', 'stream' ),
|
58 |
+
'login' => __( 'Log In', 'stream' ),
|
59 |
+
'logout' => __( 'Log Out', 'stream' ),
|
60 |
+
);
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Return translated context labels
|
65 |
+
*
|
66 |
+
* @return array Context label translations
|
67 |
+
*/
|
68 |
+
public static function get_context_labels() {
|
69 |
+
return array(
|
70 |
+
'users' => __( 'Users', 'stream' ),
|
71 |
+
'sessions' => __( 'Sessions', 'stream' ),
|
72 |
+
'profiles' => __( 'Profiles', 'stream' ),
|
73 |
+
);
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Add action links to Stream drop row in admin list screen
|
78 |
+
*
|
79 |
+
* @filter wp_stream_action_links_{connector}
|
80 |
+
*
|
81 |
+
* @param array $links Previous links registered
|
82 |
+
* @param object $record Stream record
|
83 |
+
*
|
84 |
+
* @return array Action links
|
85 |
+
*/
|
86 |
+
public static function action_links( $links, $record ) {
|
87 |
+
if ( $record->object_id ) {
|
88 |
+
if ( $link = get_edit_user_link( $record->object_id ) ) {
|
89 |
+
$links [ __( 'Edit User', 'stream' ) ] = $link;
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
return $links;
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* Get an array of role lables assigned to a specific user.
|
98 |
+
*
|
99 |
+
* @param object|int $user User object or user ID to get roles for
|
100 |
+
* @return array $labels An array of role labels
|
101 |
+
*/
|
102 |
+
public static function get_role_labels( $user ) {
|
103 |
+
if ( is_int( $user ) ) {
|
104 |
+
$user = get_user_by( 'id', $user );
|
105 |
+
}
|
106 |
+
|
107 |
+
if ( ! is_a( $user, 'WP_User' ) ) {
|
108 |
+
return array();
|
109 |
+
}
|
110 |
+
|
111 |
+
global $wp_roles;
|
112 |
+
|
113 |
+
$roles = $wp_roles->get_names();
|
114 |
+
$labels = array();
|
115 |
+
|
116 |
+
foreach ( $roles as $role => $label ) {
|
117 |
+
if ( in_array( $role, (array) $user->roles ) ) {
|
118 |
+
$labels[] = translate_user_role( $label );
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
return $labels;
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Log user registrations
|
127 |
+
*
|
128 |
+
* @action user_register
|
129 |
+
* @param int $user_id Newly registered user ID
|
130 |
+
*/
|
131 |
+
public static function callback_user_register( $user_id ) {
|
132 |
+
$current_user = wp_get_current_user();
|
133 |
+
$registered_user = get_user_by( 'id', $user_id );
|
134 |
+
|
135 |
+
if ( ! $current_user->ID ) { // Non logged-in user registered themselves
|
136 |
+
$message = __( 'New user registration', 'stream' );
|
137 |
+
$user_to_log = $registered_user->ID;
|
138 |
+
} else { // Current logged-in user created a new user
|
139 |
+
$message = _x(
|
140 |
+
'New user account created for %1$s (%2$s)',
|
141 |
+
'1: User display name, 2: User role',
|
142 |
+
'stream'
|
143 |
+
);
|
144 |
+
$user_to_log = $current_user->ID;
|
145 |
+
}
|
146 |
+
|
147 |
+
self::log(
|
148 |
+
$message,
|
149 |
+
array(
|
150 |
+
'display_name' => ( $registered_user->display_name ) ? $registered_user->display_name : $registered_user->user_login,
|
151 |
+
'roles' => implode( ', ', self::get_role_labels( $user_id ) ),
|
152 |
+
),
|
153 |
+
$registered_user->ID,
|
154 |
+
'users',
|
155 |
+
'created',
|
156 |
+
$user_to_log
|
157 |
+
);
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Log profile update
|
162 |
+
*
|
163 |
+
* @action profile_update
|
164 |
+
*/
|
165 |
+
public static function callback_profile_update( $user_id, $user ) {
|
166 |
+
self::log(
|
167 |
+
__( '%s\'s profile was updated', 'stream' ),
|
168 |
+
array(
|
169 |
+
'display_name' => $user->display_name,
|
170 |
+
),
|
171 |
+
$user->ID,
|
172 |
+
'profiles',
|
173 |
+
'updated'
|
174 |
+
);
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Log role transition
|
179 |
+
*
|
180 |
+
* @action set_user_role
|
181 |
+
*/
|
182 |
+
public static function callback_set_user_role( $user_id, $new_role, $old_roles ) {
|
183 |
+
if ( empty( $old_roles ) ) {
|
184 |
+
return;
|
185 |
+
}
|
186 |
+
|
187 |
+
global $wp_roles;
|
188 |
+
|
189 |
+
self::log(
|
190 |
+
_x(
|
191 |
+
'%1$s\'s role was changed from %2$s to %3$s',
|
192 |
+
'1: User display name, 2: Old role, 3: New role',
|
193 |
+
'stream'
|
194 |
+
),
|
195 |
+
array(
|
196 |
+
'display_name' => get_user_by( 'id', $user_id )->display_name,
|
197 |
+
'old_role' => translate_user_role( $wp_roles->role_names[ $old_roles[0] ] ),
|
198 |
+
'new_role' => translate_user_role( $wp_roles->role_names[ $new_role ] ),
|
199 |
+
),
|
200 |
+
$user_id,
|
201 |
+
'profiles',
|
202 |
+
'updated'
|
203 |
+
);
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Log password reset
|
208 |
+
*
|
209 |
+
* @action password_reset
|
210 |
+
*/
|
211 |
+
public static function callback_password_reset( $user ) {
|
212 |
+
self::log(
|
213 |
+
__( '%s\'s password was reset', 'stream' ),
|
214 |
+
array(
|
215 |
+
'email' => $user->display_name,
|
216 |
+
),
|
217 |
+
$user->ID,
|
218 |
+
'profiles',
|
219 |
+
'password-reset',
|
220 |
+
$user->ID
|
221 |
+
);
|
222 |
+
}
|
223 |
+
|
224 |
+
/**
|
225 |
+
* Log user requests to retrieve passwords
|
226 |
+
*
|
227 |
+
* @action retrieve_password
|
228 |
+
*/
|
229 |
+
public static function callback_retrieve_password( $user_login ) {
|
230 |
+
if ( wp_stream_filter_var( $user_login, FILTER_VALIDATE_EMAIL ) ) {
|
231 |
+
$user = get_user_by( 'email', $user_login );
|
232 |
+
} else {
|
233 |
+
$user = get_user_by( 'login', $user_login );
|
234 |
+
}
|
235 |
+
|
236 |
+
self::log(
|
237 |
+
__( '%s\'s password was requested to be reset', 'stream' ),
|
238 |
+
array( 'display_name' => $user->display_name ),
|
239 |
+
$user->ID,
|
240 |
+
'sessions',
|
241 |
+
'forgot-password',
|
242 |
+
$user->ID
|
243 |
+
);
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Log user login
|
248 |
+
*
|
249 |
+
* @action set_logged_in_cookie
|
250 |
+
*/
|
251 |
+
public static function callback_set_logged_in_cookie( $logged_in_cookie, $expire, $expiration, $user_id ) {
|
252 |
+
$user = get_user_by( 'id', $user_id );
|
253 |
+
|
254 |
+
self::log(
|
255 |
+
__( '%s logged in', 'stream' ),
|
256 |
+
array( 'display_name' => $user->display_name ),
|
257 |
+
$user->ID,
|
258 |
+
'sessions',
|
259 |
+
'login',
|
260 |
+
$user->ID
|
261 |
+
);
|
262 |
+
}
|
263 |
+
|
264 |
+
/**
|
265 |
+
* Log user logout
|
266 |
+
*
|
267 |
+
* @action clear_auth_cookie
|
268 |
+
*/
|
269 |
+
public static function callback_clear_auth_cookie() {
|
270 |
+
$user = wp_get_current_user();
|
271 |
+
|
272 |
+
// For some reason, incognito mode calls clear_auth_cookie on failed login attempts
|
273 |
+
if ( empty( $user ) || ! $user->exists() ) {
|
274 |
+
return;
|
275 |
+
}
|
276 |
+
|
277 |
+
self::log(
|
278 |
+
__( '%s logged out', 'stream' ),
|
279 |
+
array( 'display_name' => $user->display_name ),
|
280 |
+
$user->ID,
|
281 |
+
'sessions',
|
282 |
+
'logout',
|
283 |
+
$user->ID
|
284 |
+
);
|
285 |
+
}
|
286 |
+
|
287 |
+
/**
|
288 |
+
* There's no logging in this callback's action, the reason
|
289 |
+
* behind this hook is so that we can store user objects before
|
290 |
+
* being deleted. During `deleted_user` hook, our callback
|
291 |
+
* receives $user_id param but it's useless as the user record
|
292 |
+
* was already removed from DB.
|
293 |
+
*
|
294 |
+
* @action delete_user
|
295 |
+
* @param int $user_id User ID that maybe deleted
|
296 |
+
*/
|
297 |
+
public static function callback_delete_user( $user_id ) {
|
298 |
+
if ( ! isset( self::$_users_object_pre_deleted[ $user_id ] ) ) {
|
299 |
+
self::$_users_object_pre_deleted[ $user_id ] = get_user_by( 'id', $user_id );
|
300 |
+
}
|
301 |
+
}
|
302 |
+
|
303 |
+
/**
|
304 |
+
* Log deleted user.
|
305 |
+
*
|
306 |
+
* @action deleted_user
|
307 |
+
* @param int $user_id Deleted user ID
|
308 |
+
*/
|
309 |
+
public static function callback_deleted_user( $user_id ) {
|
310 |
+
$user = wp_get_current_user();
|
311 |
+
|
312 |
+
if ( isset( self::$_users_object_pre_deleted[ $user_id ] ) ) {
|
313 |
+
$message = _x(
|
314 |
+
'%1$s\'s account was deleted (%2$s)',
|
315 |
+
'1: User display name, 2: User roles',
|
316 |
+
'stream'
|
317 |
+
);
|
318 |
+
$display_name = self::$_users_object_pre_deleted[ $user_id ]->display_name;
|
319 |
+
$deleted_user = self::$_users_object_pre_deleted[ $user_id ];
|
320 |
+
unset( self::$_users_object_pre_deleted[ $user_id ] );
|
321 |
+
} else {
|
322 |
+
$message = __( 'User account #%d was deleted', 'stream' );
|
323 |
+
$display_name = $user_id;
|
324 |
+
$deleted_user = $user_id;
|
325 |
+
}
|
326 |
+
|
327 |
+
self::log(
|
328 |
+
$message,
|
329 |
+
array(
|
330 |
+
'display_name' => $display_name,
|
331 |
+
'roles' => implode( ', ', self::get_role_labels( $deleted_user ) ),
|
332 |
+
),
|
333 |
+
$user_id,
|
334 |
+
'users',
|
335 |
+
'deleted',
|
336 |
+
$user->ID
|
337 |
+
);
|
338 |
+
}
|
339 |
+
|
340 |
+
}
|
connectors/class-wp-stream-connector-widgets.php
ADDED
@@ -0,0 +1,805 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Widgets extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Whether or not 'created' and 'deleted' actions should be logged. Normally
|
7 |
+
* the sidebar 'added' and 'removed' actions will correspond with these.
|
8 |
+
* See note below with usage.
|
9 |
+
*
|
10 |
+
* @var bool
|
11 |
+
*/
|
12 |
+
public static $verbose_widget_created_deleted_actions = false;
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Connector slug
|
16 |
+
*
|
17 |
+
* @var string
|
18 |
+
*/
|
19 |
+
public static $name = 'widgets';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Actions registered for this connector
|
23 |
+
*
|
24 |
+
* @var array
|
25 |
+
*/
|
26 |
+
public static $actions = array(
|
27 |
+
'update_option_sidebars_widgets',
|
28 |
+
'updated_option',
|
29 |
+
);
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Store the initial sidebars_widgets option when the customizer does its
|
33 |
+
* multiple rounds of saving to the sidebars_widgets option.
|
34 |
+
*
|
35 |
+
* @var array
|
36 |
+
*/
|
37 |
+
protected static $customizer_initial_sidebars_widgets = null;
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Return translated connector label
|
41 |
+
*
|
42 |
+
* @return string Translated connector label
|
43 |
+
*/
|
44 |
+
public static function get_label() {
|
45 |
+
return __( 'Widgets', 'stream' );
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Return translated action labels
|
50 |
+
*
|
51 |
+
* @return array Action label translations
|
52 |
+
*/
|
53 |
+
public static function get_action_labels() {
|
54 |
+
return array(
|
55 |
+
'added' => __( 'Added', 'stream' ),
|
56 |
+
'removed' => __( 'Removed', 'stream' ),
|
57 |
+
'moved' => __( 'Moved', 'stream' ),
|
58 |
+
'created' => __( 'Created', 'stream' ),
|
59 |
+
'deleted' => __( 'Deleted', 'stream' ),
|
60 |
+
'deactivated' => __( 'Deactivated', 'stream' ),
|
61 |
+
'reactivated' => __( 'Reactivated', 'stream' ),
|
62 |
+
'updated' => __( 'Updated', 'stream' ),
|
63 |
+
'sorted' => __( 'Sorted', 'stream' ),
|
64 |
+
);
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Return translated context labels
|
69 |
+
*
|
70 |
+
* @return array Context label translations
|
71 |
+
*/
|
72 |
+
public static function get_context_labels() {
|
73 |
+
global $wp_registered_sidebars;
|
74 |
+
|
75 |
+
$labels = array();
|
76 |
+
|
77 |
+
foreach ( $wp_registered_sidebars as $sidebar ) {
|
78 |
+
$labels[ $sidebar['id'] ] = $sidebar['name'];
|
79 |
+
}
|
80 |
+
|
81 |
+
$labels['wp_inactive_widgets'] = __( 'Inactive Widgets', 'stream' );
|
82 |
+
$labels['orphaned_widgets'] = __( 'Orphaned Widgets', 'stream' );
|
83 |
+
|
84 |
+
return $labels;
|
85 |
+
}
|
86 |
+
|
87 |
+
/**
|
88 |
+
* Add action links to Stream drop row in admin list screen
|
89 |
+
*
|
90 |
+
* @filter wp_stream_action_links_{connector}
|
91 |
+
*
|
92 |
+
* @param array $links Previous links registered
|
93 |
+
* @param object $record Stream record
|
94 |
+
*
|
95 |
+
* @return array Action links
|
96 |
+
*/
|
97 |
+
public static function action_links( $links, $record ) {
|
98 |
+
if ( $sidebar = wp_stream_get_meta( $record, 'sidebar_id', true ) ) {
|
99 |
+
global $wp_registered_sidebars;
|
100 |
+
|
101 |
+
if ( array_key_exists( $sidebar, $wp_registered_sidebars ) ) {
|
102 |
+
$links[ __( 'Edit Widget Area', 'stream' ) ] = admin_url( 'widgets.php#' . $sidebar ); // xss ok (@todo fix WPCS rule)
|
103 |
+
}
|
104 |
+
// @todo Also old_sidebar_id and new_sidebar_id
|
105 |
+
// @todo Add Edit Widget link
|
106 |
+
}
|
107 |
+
|
108 |
+
return $links;
|
109 |
+
}
|
110 |
+
|
111 |
+
/**
|
112 |
+
* Tracks addition/deletion/reordering/deactivation of widgets from sidebars
|
113 |
+
*
|
114 |
+
* @action update_option_sidebars_widgets
|
115 |
+
* @param array $old Old sidebars widgets
|
116 |
+
* @param array $new New sidebars widgets
|
117 |
+
* @return void
|
118 |
+
*/
|
119 |
+
public static function callback_update_option_sidebars_widgets( $old, $new ) {
|
120 |
+
// Disable listener if we're switching themes
|
121 |
+
if ( did_action( 'after_switch_theme' ) ) {
|
122 |
+
return;
|
123 |
+
}
|
124 |
+
|
125 |
+
if ( did_action( 'customize_save' ) ) {
|
126 |
+
if ( is_null( self::$customizer_initial_sidebars_widgets ) ) {
|
127 |
+
self::$customizer_initial_sidebars_widgets = $old;
|
128 |
+
add_action( 'customize_save_after', array( __CLASS__, '_callback_customize_save_after' ) );
|
129 |
+
}
|
130 |
+
} else {
|
131 |
+
self::handle_sidebars_widgets_changes( $old, $new );
|
132 |
+
}
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Since the sidebars_widgets may get updated multiple times when saving
|
137 |
+
* changes to Widgets in the Customizer, defer handling the changes until
|
138 |
+
* customize_save_after.
|
139 |
+
*
|
140 |
+
* @see self::callback_update_option_sidebars_widgets()
|
141 |
+
*/
|
142 |
+
public static function _callback_customize_save_after() {
|
143 |
+
$old_sidebars_widgets = self::$customizer_initial_sidebars_widgets;
|
144 |
+
$new_sidebars_widgets = get_option( 'sidebars_widgets' );
|
145 |
+
|
146 |
+
self::handle_sidebars_widgets_changes( $old_sidebars_widgets, $new_sidebars_widgets );
|
147 |
+
}
|
148 |
+
|
149 |
+
/**
|
150 |
+
* @param array $old
|
151 |
+
* @param array $new
|
152 |
+
*/
|
153 |
+
protected static function handle_sidebars_widgets_changes( $old, $new ) {
|
154 |
+
unset( $old['array_version'] );
|
155 |
+
unset( $new['array_version'] );
|
156 |
+
|
157 |
+
if ( $old === $new ) {
|
158 |
+
return;
|
159 |
+
}
|
160 |
+
|
161 |
+
self::handle_deactivated_widgets( $old, $new );
|
162 |
+
self::handle_reactivated_widgets( $old, $new );
|
163 |
+
self::handle_widget_removal( $old, $new );
|
164 |
+
self::handle_widget_addition( $old, $new );
|
165 |
+
self::handle_widget_reordering( $old, $new );
|
166 |
+
self::handle_widget_moved( $old, $new );
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* Track deactivation of widgets from sidebars
|
171 |
+
*
|
172 |
+
* @param array $old Old sidebars widgets
|
173 |
+
* @param array $new New sidebars widgets
|
174 |
+
* @return void
|
175 |
+
*/
|
176 |
+
static protected function handle_deactivated_widgets( $old, $new ) {
|
177 |
+
$new_deactivated_widget_ids = array_diff( $new['wp_inactive_widgets'], $old['wp_inactive_widgets'] );
|
178 |
+
|
179 |
+
foreach ( $new_deactivated_widget_ids as $widget_id ) {
|
180 |
+
$sidebar_id = '';
|
181 |
+
|
182 |
+
foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
|
183 |
+
if ( in_array( $widget_id, $old_widget_ids ) ) {
|
184 |
+
$sidebar_id = $old_sidebar_id;
|
185 |
+
break;
|
186 |
+
}
|
187 |
+
}
|
188 |
+
|
189 |
+
$action = 'deactivated';
|
190 |
+
$name = self::get_widget_name( $widget_id );
|
191 |
+
$title = self::get_widget_title( $widget_id );
|
192 |
+
$labels = self::get_context_labels();
|
193 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
194 |
+
|
195 |
+
if ( $name && $title ) {
|
196 |
+
$message = _x( '%1$s widget named "%2$s" from "%3$s" deactivated', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
|
197 |
+
} elseif ( $name ) {
|
198 |
+
// Empty title, but we have the name
|
199 |
+
$message = _x( '%1$s widget from "%3$s" deactivated', '1: Name, 3: Sidebar Name', 'stream' );
|
200 |
+
} elseif ( $title ) {
|
201 |
+
// Likely a single widget since no name is available
|
202 |
+
$message = _x( 'Unknown widget type named "%2$s" from "%3$s" deactivated', '2: Title, 3: Sidebar Name', 'stream' );
|
203 |
+
} else {
|
204 |
+
// Neither a name nor a title are available, so use the widget ID
|
205 |
+
$message = _x( '%4$s widget from "%3$s" deactivated', '4: Widget ID, 3: Sidebar Name', 'stream' );
|
206 |
+
}
|
207 |
+
|
208 |
+
$message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
|
209 |
+
|
210 |
+
self::log(
|
211 |
+
$message,
|
212 |
+
compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
|
213 |
+
null,
|
214 |
+
'wp_inactive_widgets',
|
215 |
+
$action
|
216 |
+
);
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
/**
|
221 |
+
* Track reactivation of widgets from sidebars
|
222 |
+
*
|
223 |
+
* @param array $old Old sidebars widgets
|
224 |
+
* @param array $new New sidebars widgets
|
225 |
+
* @return void
|
226 |
+
*/
|
227 |
+
static protected function handle_reactivated_widgets( $old, $new ) {
|
228 |
+
$new_reactivated_widget_ids = array_diff( $old['wp_inactive_widgets'], $new['wp_inactive_widgets'] );
|
229 |
+
|
230 |
+
foreach ( $new_reactivated_widget_ids as $widget_id ) {
|
231 |
+
$sidebar_id = '';
|
232 |
+
|
233 |
+
foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
|
234 |
+
if ( in_array( $widget_id, $new_widget_ids ) ) {
|
235 |
+
$sidebar_id = $new_sidebar_id;
|
236 |
+
break;
|
237 |
+
}
|
238 |
+
}
|
239 |
+
|
240 |
+
$action = 'reactivated';
|
241 |
+
$name = self::get_widget_name( $widget_id );
|
242 |
+
$title = self::get_widget_title( $widget_id );
|
243 |
+
|
244 |
+
if ( $name && $title ) {
|
245 |
+
$message = _x( '%1$s widget named "%2$s" reactivated', '1: Name, 2: Title', 'stream' );
|
246 |
+
} elseif ( $name ) {
|
247 |
+
// Empty title, but we have the name
|
248 |
+
$message = _x( '%1$s widget reactivated', '1: Name', 'stream' );
|
249 |
+
} elseif ( $title ) {
|
250 |
+
// Likely a single widget since no name is available
|
251 |
+
$message = _x( 'Unknown widget type named "%2$s" reactivated', '2: Title', 'stream' );
|
252 |
+
} else {
|
253 |
+
// Neither a name nor a title are available, so use the widget ID
|
254 |
+
$message = _x( '%3$s widget reactivated', '3: Widget ID', 'stream' );
|
255 |
+
}
|
256 |
+
|
257 |
+
$message = sprintf( $message, $name, $title, $widget_id );
|
258 |
+
|
259 |
+
self::log(
|
260 |
+
$message,
|
261 |
+
compact( 'title', 'name', 'widget_id', 'sidebar_id' ),
|
262 |
+
null,
|
263 |
+
$sidebar_id,
|
264 |
+
$action
|
265 |
+
);
|
266 |
+
}
|
267 |
+
}
|
268 |
+
|
269 |
+
/**
|
270 |
+
* Track deletion of widgets from sidebars
|
271 |
+
*
|
272 |
+
* @param array $old Old sidebars widgets
|
273 |
+
* @param array $new New sidebars widgets
|
274 |
+
* @return void
|
275 |
+
*/
|
276 |
+
static protected function handle_widget_removal( $old, $new ) {
|
277 |
+
$all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
|
278 |
+
$all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
|
279 |
+
// @todo In the customizer, moving widgets to other sidebars is problematic because each sidebar is registered as a separate setting; so we need to make sure that all $_POST['customized'] are applied?
|
280 |
+
// @todo The widget option is getting updated before the sidebars_widgets are updated, so we need to hook into the option update to try to cache any deletions for future lookup
|
281 |
+
|
282 |
+
$deleted_widget_ids = array_diff( $all_old_widget_ids, $all_new_widget_ids );
|
283 |
+
|
284 |
+
foreach ( $deleted_widget_ids as $widget_id ) {
|
285 |
+
$sidebar_id = '';
|
286 |
+
|
287 |
+
foreach ( $old as $old_sidebar_id => $old_widget_ids ) {
|
288 |
+
if ( in_array( $widget_id, $old_widget_ids ) ) {
|
289 |
+
$sidebar_id = $old_sidebar_id;
|
290 |
+
break;
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
$action = 'removed';
|
295 |
+
$name = self::get_widget_name( $widget_id );
|
296 |
+
$title = self::get_widget_title( $widget_id );
|
297 |
+
$labels = self::get_context_labels();
|
298 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
299 |
+
|
300 |
+
if ( $name && $title ) {
|
301 |
+
$message = _x( '%1$s widget named "%2$s" removed from "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
|
302 |
+
} elseif ( $name ) {
|
303 |
+
// Empty title, but we have the name
|
304 |
+
$message = _x( '%1$s widget removed from "%3$s"', '1: Name, 3: Sidebar Name', 'stream' );
|
305 |
+
} elseif ( $title ) {
|
306 |
+
// Likely a single widget since no name is available
|
307 |
+
$message = _x( 'Unknown widget type named "%2$s" removed from "%3$s"', '2: Title, 3: Sidebar Name', 'stream' );
|
308 |
+
} else {
|
309 |
+
// Neither a name nor a title are available, so use the widget ID
|
310 |
+
$message = _x( '%4$s widget removed from "%3$s"', '4: Widget ID, 3: Sidebar Name', 'stream' );
|
311 |
+
}
|
312 |
+
|
313 |
+
$message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
|
314 |
+
|
315 |
+
self::log(
|
316 |
+
$message,
|
317 |
+
compact( 'widget_id', 'sidebar_id' ),
|
318 |
+
null,
|
319 |
+
$sidebar_id,
|
320 |
+
$action
|
321 |
+
);
|
322 |
+
}
|
323 |
+
}
|
324 |
+
|
325 |
+
/**
|
326 |
+
* Track reactivation of widgets from sidebars
|
327 |
+
*
|
328 |
+
* @param array $old Old sidebars widgets
|
329 |
+
* @param array $new New sidebars widgets
|
330 |
+
* @return void
|
331 |
+
*/
|
332 |
+
static protected function handle_widget_addition( $old, $new ) {
|
333 |
+
$all_old_widget_ids = array_unique( call_user_func_array( 'array_merge', $old ) );
|
334 |
+
$all_new_widget_ids = array_unique( call_user_func_array( 'array_merge', $new ) );
|
335 |
+
$added_widget_ids = array_diff( $all_new_widget_ids, $all_old_widget_ids );
|
336 |
+
|
337 |
+
foreach ( $added_widget_ids as $widget_id ) {
|
338 |
+
$sidebar_id = '';
|
339 |
+
|
340 |
+
foreach ( $new as $new_sidebar_id => $new_widget_ids ) {
|
341 |
+
if ( in_array( $widget_id, $new_widget_ids ) ) {
|
342 |
+
$sidebar_id = $new_sidebar_id;
|
343 |
+
break;
|
344 |
+
}
|
345 |
+
}
|
346 |
+
|
347 |
+
$action = 'added';
|
348 |
+
$name = self::get_widget_name( $widget_id );
|
349 |
+
$title = self::get_widget_title( $widget_id );
|
350 |
+
$labels = self::get_context_labels();
|
351 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
352 |
+
|
353 |
+
if ( $name && $title ) {
|
354 |
+
$message = _x( '%1$s widget named "%2$s" added to "%3$s"', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
|
355 |
+
} elseif ( $name ) {
|
356 |
+
// Empty title, but we have the name
|
357 |
+
$message = _x( '%1$s widget added to "%3$s"', '1: Name, 3: Sidebar Name', 'stream' );
|
358 |
+
} elseif ( $title ) {
|
359 |
+
// Likely a single widget since no name is available
|
360 |
+
$message = _x( 'Unknown widget type named "%2$s" added to "%3$s"', '2: Title, 3: Sidebar Name', 'stream' );
|
361 |
+
} else {
|
362 |
+
// Neither a name nor a title are available, so use the widget ID
|
363 |
+
$message = _x( '%4$s widget added to "%3$s"', '4: Widget ID, 3: Sidebar Name', 'stream' );
|
364 |
+
}
|
365 |
+
|
366 |
+
$message = sprintf( $message, $name, $title, $sidebar_name, $widget_id );
|
367 |
+
|
368 |
+
self::log(
|
369 |
+
$message,
|
370 |
+
compact( 'widget_id', 'sidebar_id' ), // @todo Do we care about sidebar_id in meta if it is already context? But there is no 'context' for what the context signifies
|
371 |
+
null,
|
372 |
+
$sidebar_id,
|
373 |
+
$action
|
374 |
+
);
|
375 |
+
}
|
376 |
+
}
|
377 |
+
|
378 |
+
/**
|
379 |
+
* Track reordering of widgets
|
380 |
+
*
|
381 |
+
* @param array $old Old sidebars widgets
|
382 |
+
* @param array $new New sidebars widgets
|
383 |
+
* @return void
|
384 |
+
*/
|
385 |
+
static protected function handle_widget_reordering( $old, $new ) {
|
386 |
+
$all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
|
387 |
+
|
388 |
+
foreach ( $all_sidebar_ids as $sidebar_id ) {
|
389 |
+
if ( $old[ $sidebar_id ] === $new[ $sidebar_id ] ) {
|
390 |
+
continue;
|
391 |
+
}
|
392 |
+
|
393 |
+
// Use intersect to ignore widget additions and removals
|
394 |
+
$all_widget_ids = array_unique( array_merge( $old[ $sidebar_id ], $new[ $sidebar_id ] ) );
|
395 |
+
$common_widget_ids = array_intersect( $old[ $sidebar_id ], $new[ $sidebar_id ] );
|
396 |
+
$uncommon_widget_ids = array_diff( $all_widget_ids, $common_widget_ids );
|
397 |
+
$new_widget_ids = array_values( array_diff( $new[ $sidebar_id ], $uncommon_widget_ids ) );
|
398 |
+
$old_widget_ids = array_values( array_diff( $old[ $sidebar_id ], $uncommon_widget_ids ) );
|
399 |
+
$widget_order_changed = ( $new_widget_ids !== $old_widget_ids );
|
400 |
+
|
401 |
+
if ( $widget_order_changed ) {
|
402 |
+
$labels = self::get_context_labels();
|
403 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
404 |
+
$old_widget_ids = $old[ $sidebar_id ];
|
405 |
+
$message = _x( 'Widgets reordered in "%s"', 'Sidebar name', 'stream' );
|
406 |
+
$message = sprintf( $message, $sidebar_name );
|
407 |
+
|
408 |
+
self::log(
|
409 |
+
$message,
|
410 |
+
compact( 'sidebar_id', 'old_widget_ids' ),
|
411 |
+
null,
|
412 |
+
$sidebar_id,
|
413 |
+
'sorted'
|
414 |
+
);
|
415 |
+
}
|
416 |
+
}
|
417 |
+
|
418 |
+
}
|
419 |
+
|
420 |
+
/**
|
421 |
+
* Track movement of widgets to other sidebars
|
422 |
+
*
|
423 |
+
* @param array $old Old sidebars widgets
|
424 |
+
* @param array $new New sidebars widgets
|
425 |
+
* @return void
|
426 |
+
*/
|
427 |
+
static protected function handle_widget_moved( $old, $new ) {
|
428 |
+
$all_sidebar_ids = array_intersect( array_keys( $old ), array_keys( $new ) );
|
429 |
+
|
430 |
+
foreach ( $all_sidebar_ids as $new_sidebar_id ) {
|
431 |
+
if ( $old[ $new_sidebar_id ] === $new[ $new_sidebar_id ] ) {
|
432 |
+
continue;
|
433 |
+
}
|
434 |
+
|
435 |
+
$new_widget_ids = array_diff( $new[ $new_sidebar_id ], $old[ $new_sidebar_id ] );
|
436 |
+
|
437 |
+
foreach ( $new_widget_ids as $widget_id ) {
|
438 |
+
// Now find the sidebar that the widget was originally located in, as long it is not wp_inactive_widgets
|
439 |
+
$old_sidebar_id = null;
|
440 |
+
foreach ( $old as $sidebar_id => $old_widget_ids ) {
|
441 |
+
if ( in_array( $widget_id, $old_widget_ids ) ) {
|
442 |
+
$old_sidebar_id = $sidebar_id;
|
443 |
+
break;
|
444 |
+
}
|
445 |
+
}
|
446 |
+
|
447 |
+
if ( ! $old_sidebar_id || 'wp_inactive_widgets' === $old_sidebar_id || 'wp_inactive_widgets' === $new_sidebar_id ) {
|
448 |
+
continue;
|
449 |
+
}
|
450 |
+
|
451 |
+
assert( $old_sidebar_id !== $new_sidebar_id );
|
452 |
+
|
453 |
+
$name = self::get_widget_name( $widget_id );
|
454 |
+
$title = self::get_widget_title( $widget_id );
|
455 |
+
$labels = self::get_context_labels();
|
456 |
+
$old_sidebar_name = isset( $labels[ $old_sidebar_id ] ) ? $labels[ $old_sidebar_id ] : $old_sidebar_id;
|
457 |
+
$new_sidebar_name = isset( $labels[ $new_sidebar_id ] ) ? $labels[ $new_sidebar_id ] : $new_sidebar_id;
|
458 |
+
|
459 |
+
if ( $name && $title ) {
|
460 |
+
$message = _x( '%1$s widget named "%2$s" moved from "%4$s" to "%5$s"', '1: Name, 2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
|
461 |
+
} elseif ( $name ) {
|
462 |
+
// Empty title, but we have the name
|
463 |
+
$message = _x( '%1$s widget moved from "%4$s" to "%5$s"', '1: Name, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
|
464 |
+
} elseif ( $title ) {
|
465 |
+
// Likely a single widget since no name is available
|
466 |
+
$message = _x( 'Unknown widget type named "%2$s" moved from "%4$s" to "%5$s"', '2: Title, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
|
467 |
+
} else {
|
468 |
+
// Neither a name nor a title are available, so use the widget ID
|
469 |
+
$message = _x( '%3$s widget moved from "%4$s" to "%5$s"', '3: Widget ID, 4: Old Sidebar Name, 5: New Sidebar Name', 'stream' );
|
470 |
+
}
|
471 |
+
|
472 |
+
$message = sprintf( $message, $name, $title, $widget_id, $old_sidebar_name, $new_sidebar_name );
|
473 |
+
$sidebar_id = $new_sidebar_id;
|
474 |
+
|
475 |
+
self::log(
|
476 |
+
$message,
|
477 |
+
compact( 'widget_id', 'sidebar_id', 'old_sidebar_id' ),
|
478 |
+
null,
|
479 |
+
$sidebar_id,
|
480 |
+
'moved'
|
481 |
+
);
|
482 |
+
}
|
483 |
+
}
|
484 |
+
|
485 |
+
}
|
486 |
+
|
487 |
+
/**
|
488 |
+
* Track changes to widgets
|
489 |
+
*
|
490 |
+
* @faction updated_option
|
491 |
+
* @param string $option_name
|
492 |
+
* @param array $old_value
|
493 |
+
* @param array $new_value
|
494 |
+
*/
|
495 |
+
public static function callback_updated_option( $option_name, $old_value, $new_value ) {
|
496 |
+
if ( ! preg_match( '/^widget_(.+)$/', $option_name, $matches ) || ! is_array( $new_value ) ) {
|
497 |
+
return;
|
498 |
+
}
|
499 |
+
|
500 |
+
$is_multi = ! empty( $new_value['_multiwidget'] );
|
501 |
+
$widget_id_base = $matches[1];
|
502 |
+
|
503 |
+
$creates = array();
|
504 |
+
$updates = array();
|
505 |
+
$deletes = array();
|
506 |
+
|
507 |
+
if ( $is_multi ) {
|
508 |
+
$widget_id_format = "$widget_id_base-%d";
|
509 |
+
|
510 |
+
unset( $new_value['_multiwidget'] );
|
511 |
+
unset( $old_value['_multiwidget'] );
|
512 |
+
|
513 |
+
/**
|
514 |
+
* Created widgets
|
515 |
+
*/
|
516 |
+
$created_widget_numbers = array_diff( array_keys( $new_value ), array_keys( $old_value ) );
|
517 |
+
|
518 |
+
foreach ( $created_widget_numbers as $widget_number ) {
|
519 |
+
$instance = $new_value[ $widget_number ];
|
520 |
+
$widget_id = sprintf( $widget_id_format, $widget_number );
|
521 |
+
$name = self::get_widget_name( $widget_id );
|
522 |
+
$title = ! empty( $instance['title'] ) ? $instance['title'] : null;
|
523 |
+
$sidebar_id = self::get_widget_sidebar_id( $widget_id ); // @todo May not be assigned yet
|
524 |
+
|
525 |
+
$creates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
|
526 |
+
}
|
527 |
+
|
528 |
+
/**
|
529 |
+
* Updated widgets
|
530 |
+
*/
|
531 |
+
$updated_widget_numbers = array_intersect( array_keys( $old_value ), array_keys( $new_value ) );
|
532 |
+
|
533 |
+
foreach ( $updated_widget_numbers as $widget_number ) {
|
534 |
+
$new_instance = $new_value[ $widget_number ];
|
535 |
+
$old_instance = $old_value[ $widget_number ];
|
536 |
+
|
537 |
+
if ( $old_instance !== $new_instance ) {
|
538 |
+
$widget_id = sprintf( $widget_id_format, $widget_number );
|
539 |
+
$name = self::get_widget_name( $widget_id );
|
540 |
+
$title = ! empty( $new_instance['title'] ) ? $new_instance['title'] : null;
|
541 |
+
$sidebar_id = self::get_widget_sidebar_id( $widget_id );
|
542 |
+
$labels = self::get_context_labels();
|
543 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
544 |
+
|
545 |
+
$updates[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'old_instance', 'sidebar_name' );
|
546 |
+
}
|
547 |
+
}
|
548 |
+
|
549 |
+
/**
|
550 |
+
* Deleted widgets
|
551 |
+
*/
|
552 |
+
$deleted_widget_numbers = array_diff( array_keys( $old_value ), array_keys( $new_value ) );
|
553 |
+
|
554 |
+
foreach ( $deleted_widget_numbers as $widget_number ) {
|
555 |
+
$instance = $old_value[ $widget_number ];
|
556 |
+
$widget_id = sprintf( $widget_id_format, $widget_number );
|
557 |
+
$name = self::get_widget_name( $widget_id );
|
558 |
+
$title = ! empty( $instance['title'] ) ? $instance['title'] : null;
|
559 |
+
$sidebar_id = self::get_widget_sidebar_id( $widget_id ); // @todo May not be assigned anymore
|
560 |
+
|
561 |
+
$deletes[] = compact( 'name', 'title', 'widget_id', 'sidebar_id', 'instance' );
|
562 |
+
}
|
563 |
+
} else {
|
564 |
+
// Doing our best guess for tracking changes to old single widgets, assuming their options start with 'widget_'
|
565 |
+
$widget_id = $widget_id_base;
|
566 |
+
$name = $widget_id; // There aren't names available for single widgets
|
567 |
+
$title = ! empty( $new_value['title'] ) ? $new_value['title'] : null;
|
568 |
+
$sidebar_id = self::get_widget_sidebar_id( $widget_id );
|
569 |
+
$old_instance = $old_value;
|
570 |
+
$labels = self::get_context_labels();
|
571 |
+
$sidebar_name = isset( $labels[ $sidebar_id ] ) ? $labels[ $sidebar_id ] : $sidebar_id;
|
572 |
+
|
573 |
+
$updates[] = compact( 'widget_id', 'title', 'name', 'sidebar_id', 'old_instance', 'sidebar_name' );
|
574 |
+
}
|
575 |
+
|
576 |
+
/**
|
577 |
+
* Log updated actions
|
578 |
+
*/
|
579 |
+
foreach ( $updates as $update ) {
|
580 |
+
if ( $update['name'] && $update['title'] ) {
|
581 |
+
$message = _x( '%1$s widget named "%2$s" in "%3$s" updated', '1: Name, 2: Title, 3: Sidebar Name', 'stream' );
|
582 |
+
} elseif ( $update['name'] ) {
|
583 |
+
// Empty title, but we have the name
|
584 |
+
$message = _x( '%1$s widget in "%3$s" updated', '1: Name, 3: Sidebar Name', 'stream' );
|
585 |
+
} elseif ( $update['title'] ) {
|
586 |
+
// Likely a single widget since no name is available
|
587 |
+
$message = _x( 'Unknown widget type named "%2$s" in "%3$s" updated', '2: Title, 3: Sidebar Name', 'stream' );
|
588 |
+
} else {
|
589 |
+
// Neither a name nor a title are available, so use the widget ID
|
590 |
+
$message = _x( '%4$s widget in "%3$s" updated', '4: Widget ID, 3: Sidebar Name', 'stream' );
|
591 |
+
}
|
592 |
+
|
593 |
+
$message = sprintf( $message, $update['name'], $update['title'], $update['sidebar_name'], $update['widget_id'] );
|
594 |
+
|
595 |
+
unset( $update['title'], $update['name'] );
|
596 |
+
|
597 |
+
self::log(
|
598 |
+
$message,
|
599 |
+
$update,
|
600 |
+
null,
|
601 |
+
$update['sidebar_id'],
|
602 |
+
'updated'
|
603 |
+
);
|
604 |
+
}
|
605 |
+
|
606 |
+
/**
|
607 |
+
* In the normal case, widgets are never created or deleted in a vacuum.
|
608 |
+
* Created widgets are immediately assigned to a sidebar, and deleted
|
609 |
+
* widgets are immediately removed from their assigned sidebar. If,
|
610 |
+
* however, widget instances get manipulated programmatically, it is
|
611 |
+
* possible that they could be orphaned, in which case the following
|
612 |
+
* actions would be useful to log.
|
613 |
+
*/
|
614 |
+
if ( self::$verbose_widget_created_deleted_actions ) {
|
615 |
+
// We should only do these if not captured by an update to the sidebars_widgets option
|
616 |
+
/**
|
617 |
+
* Log created actions
|
618 |
+
*/
|
619 |
+
foreach ( $creates as $create ) {
|
620 |
+
if ( $create['name'] && $create['title'] ) {
|
621 |
+
$message = _x( '%1$s widget named "%2$s" created', '1: Name, 2: Title', 'stream' );
|
622 |
+
} elseif ( $create['name'] ) {
|
623 |
+
// Empty title, but we have the name
|
624 |
+
$message = _x( '%1$s widget created', '1: Name', 'stream' );
|
625 |
+
} elseif ( $create['title'] ) {
|
626 |
+
// Likely a single widget since no name is available
|
627 |
+
$message = _x( 'Unknown widget type named "%2$s" created', '2: Title', 'stream' );
|
628 |
+
} else {
|
629 |
+
// Neither a name nor a title are available, so use the widget ID
|
630 |
+
$message = _x( '%3$s widget created', '3: Widget ID', 'stream' );
|
631 |
+
}
|
632 |
+
|
633 |
+
$message = sprintf( $message, $create['name'], $create['title'], $create['widget_id'] );
|
634 |
+
|
635 |
+
unset( $create['title'], $create['name'] );
|
636 |
+
|
637 |
+
self::log(
|
638 |
+
$message,
|
639 |
+
$create,
|
640 |
+
null,
|
641 |
+
$create['sidebar_id'],
|
642 |
+
'created'
|
643 |
+
);
|
644 |
+
}
|
645 |
+
|
646 |
+
/**
|
647 |
+
* Log deleted actions
|
648 |
+
*/
|
649 |
+
foreach ( $deletes as $delete ) {
|
650 |
+
if ( $delete['name'] && $delete['title'] ) {
|
651 |
+
$message = _x( '%1$s widget named "%2$s" deleted', '1: Name, 2: Title', 'stream' );
|
652 |
+
} elseif ( $delete['name'] ) {
|
653 |
+
// Empty title, but we have the name
|
654 |
+
$message = _x( '%1$s widget deleted', '1: Name', 'stream' );
|
655 |
+
} elseif ( $delete['title'] ) {
|
656 |
+
// Likely a single widget since no name is available
|
657 |
+
$message = _x( 'Unknown widget type named "%2$s" deleted', '2: Title', 'stream' );
|
658 |
+
} else {
|
659 |
+
// Neither a name nor a title are available, so use the widget ID
|
660 |
+
$message = _x( '%3$s widget deleted', '3: Widget ID', 'stream' );
|
661 |
+
}
|
662 |
+
|
663 |
+
$message = sprintf( $message, $delete['name'], $delete['title'], $delete['widget_id'] );
|
664 |
+
|
665 |
+
unset( $delete['title'], $delete['name'] );
|
666 |
+
|
667 |
+
self::log(
|
668 |
+
$message,
|
669 |
+
$delete,
|
670 |
+
null,
|
671 |
+
$delete['sidebar_id'],
|
672 |
+
'deleted'
|
673 |
+
);
|
674 |
+
}
|
675 |
+
}
|
676 |
+
}
|
677 |
+
|
678 |
+
/**
|
679 |
+
* @param string $widget_id
|
680 |
+
* @return string
|
681 |
+
*/
|
682 |
+
public static function get_widget_title( $widget_id ) {
|
683 |
+
$instance = self::get_widget_instance( $widget_id );
|
684 |
+
return ! empty( $instance['title'] ) ? $instance['title'] : null;
|
685 |
+
}
|
686 |
+
|
687 |
+
/**
|
688 |
+
* @param string $widget_id
|
689 |
+
* @return string|null
|
690 |
+
*/
|
691 |
+
public static function get_widget_name( $widget_id ) {
|
692 |
+
$widget_obj = self::get_widget_object( $widget_id );
|
693 |
+
return $widget_obj ? $widget_obj->name : null;
|
694 |
+
}
|
695 |
+
|
696 |
+
/**
|
697 |
+
* @param $widget_id
|
698 |
+
* @return array|null
|
699 |
+
*/
|
700 |
+
public static function parse_widget_id( $widget_id ) {
|
701 |
+
if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
|
702 |
+
return array(
|
703 |
+
'id_base' => $matches[1],
|
704 |
+
'widget_number' => intval( $matches[2] ),
|
705 |
+
);
|
706 |
+
} else {
|
707 |
+
return null;
|
708 |
+
}
|
709 |
+
}
|
710 |
+
|
711 |
+
/**
|
712 |
+
* @param string $widget_id
|
713 |
+
* @return WP_Widget|null
|
714 |
+
*/
|
715 |
+
public static function get_widget_object( $widget_id ) {
|
716 |
+
global $wp_widget_factory;
|
717 |
+
|
718 |
+
$parsed_widget_id = self::parse_widget_id( $widget_id );
|
719 |
+
|
720 |
+
if ( ! $parsed_widget_id ) {
|
721 |
+
return null;
|
722 |
+
}
|
723 |
+
|
724 |
+
$id_base = $parsed_widget_id['id_base'];
|
725 |
+
|
726 |
+
$id_base_to_widget_class_map = array_combine(
|
727 |
+
wp_list_pluck( $wp_widget_factory->widgets, 'id_base' ),
|
728 |
+
array_keys( $wp_widget_factory->widgets )
|
729 |
+
);
|
730 |
+
|
731 |
+
if ( ! isset( $id_base_to_widget_class_map[ $id_base ] ) ) {
|
732 |
+
return null;
|
733 |
+
}
|
734 |
+
|
735 |
+
return $wp_widget_factory->widgets[ $id_base_to_widget_class_map[ $id_base ] ];
|
736 |
+
}
|
737 |
+
|
738 |
+
/**
|
739 |
+
* Returns widget instance settings
|
740 |
+
*
|
741 |
+
* @param string $widget_id Widget ID, ex: pages-1
|
742 |
+
* @return array|null Widget instance
|
743 |
+
*/
|
744 |
+
public static function get_widget_instance( $widget_id ) {
|
745 |
+
$instance = null;
|
746 |
+
$parsed_widget_id = self::parse_widget_id( $widget_id );
|
747 |
+
$widget_obj = self::get_widget_object( $widget_id );
|
748 |
+
|
749 |
+
if ( $widget_obj && $parsed_widget_id ) {
|
750 |
+
$settings = $widget_obj->get_settings();
|
751 |
+
$multi_number = $parsed_widget_id['widget_number'];
|
752 |
+
|
753 |
+
if ( isset( $settings[ $multi_number ] ) && ! empty( $settings[ $multi_number ]['title'] ) ) {
|
754 |
+
$instance = $settings[ $multi_number ];
|
755 |
+
}
|
756 |
+
} else {
|
757 |
+
// Single widgets, try our best guess at the option used
|
758 |
+
$potential_instance = get_option( "widget_{$widget_id}" );
|
759 |
+
|
760 |
+
if ( ! empty( $potential_instance ) && ! empty( $potential_instance['title'] ) ) {
|
761 |
+
$instance = $potential_instance;
|
762 |
+
}
|
763 |
+
}
|
764 |
+
|
765 |
+
return $instance;
|
766 |
+
}
|
767 |
+
|
768 |
+
/**
|
769 |
+
* Get global sidebars widgets
|
770 |
+
*
|
771 |
+
* @return array
|
772 |
+
*/
|
773 |
+
public static function get_sidebars_widgets() {
|
774 |
+
/**
|
775 |
+
* Filter allows for insertion of sidebar widgets
|
776 |
+
* @todo Do we need this filter?
|
777 |
+
*
|
778 |
+
* @param array Sidebar Widgets in Options table
|
779 |
+
* @param array Inserted Sidebar Widgets
|
780 |
+
* @return array Array of updated Sidebar Widgets
|
781 |
+
*/
|
782 |
+
return apply_filters( 'sidebars_widgets', get_option( 'sidebars_widgets', array() ) );
|
783 |
+
}
|
784 |
+
|
785 |
+
/**
|
786 |
+
* Return the sidebar of a certain widget, based on widget_id
|
787 |
+
*
|
788 |
+
* @param string $widget_id Widget id, ex: pages-1
|
789 |
+
* @return string Sidebar id
|
790 |
+
*/
|
791 |
+
public static function get_widget_sidebar_id( $widget_id ) {
|
792 |
+
$sidebars_widgets = self::get_sidebars_widgets();
|
793 |
+
|
794 |
+
unset( $sidebars_widgets['array_version'] );
|
795 |
+
|
796 |
+
foreach ( $sidebars_widgets as $sidebar_id => $widget_ids ) {
|
797 |
+
if ( in_array( $widget_id, $widget_ids ) ) {
|
798 |
+
return $sidebar_id;
|
799 |
+
}
|
800 |
+
}
|
801 |
+
|
802 |
+
return 'orphaned_widgets';
|
803 |
+
}
|
804 |
+
|
805 |
+
}
|
connectors/class-wp-stream-connector-woocommerce.php
ADDED
@@ -0,0 +1,776 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_Woocommerce extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Context name
|
7 |
+
* @var string
|
8 |
+
*/
|
9 |
+
public static $name = 'woocommerce';
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Holds tracked plugin minimum version required
|
13 |
+
*
|
14 |
+
* @const string
|
15 |
+
*/
|
16 |
+
const PLUGIN_MIN_VERSION = '2.1.10';
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Actions registered for this context
|
20 |
+
* @var array
|
21 |
+
*/
|
22 |
+
public static $actions = array(
|
23 |
+
'wp_stream_record_array',
|
24 |
+
'updated_option',
|
25 |
+
'transition_post_status',
|
26 |
+
'deleted_post',
|
27 |
+
'woocommerce_order_status_changed',
|
28 |
+
'woocommerce_attribute_added',
|
29 |
+
'woocommerce_attribute_updated',
|
30 |
+
'woocommerce_attribute_deleted',
|
31 |
+
'woocommerce_tax_rate_added',
|
32 |
+
'woocommerce_tax_rate_updated',
|
33 |
+
'woocommerce_tax_rate_deleted',
|
34 |
+
);
|
35 |
+
|
36 |
+
public static $taxonomies = array(
|
37 |
+
'product_type',
|
38 |
+
'product_cat',
|
39 |
+
'product_tag',
|
40 |
+
'product_shipping_class',
|
41 |
+
'shop_order_status',
|
42 |
+
);
|
43 |
+
|
44 |
+
public static $post_types = array(
|
45 |
+
'product',
|
46 |
+
'product_variation',
|
47 |
+
'shop_order',
|
48 |
+
'shop_coupon',
|
49 |
+
);
|
50 |
+
|
51 |
+
private static $order_update_logged = false;
|
52 |
+
|
53 |
+
private static $settings_pages = array();
|
54 |
+
|
55 |
+
private static $settings = array();
|
56 |
+
|
57 |
+
private static $custom_settings = array();
|
58 |
+
|
59 |
+
public static function register() {
|
60 |
+
parent::register();
|
61 |
+
|
62 |
+
add_filter( 'wp_stream_posts_exclude_post_types', array( __CLASS__, 'exclude_order_post_types' ) );
|
63 |
+
add_action( 'wp_stream_comments_exclude_comment_types', array( __CLASS__, 'exclude_order_comment_types' ) );
|
64 |
+
|
65 |
+
self::get_woocommerce_settings_fields();
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
70 |
+
*
|
71 |
+
* @return bool
|
72 |
+
*/
|
73 |
+
public static function is_dependency_satisfied() {
|
74 |
+
global $woocommerce;
|
75 |
+
|
76 |
+
if ( class_exists( 'WooCommerce' ) && version_compare( $woocommerce->version, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
77 |
+
return true;
|
78 |
+
}
|
79 |
+
|
80 |
+
return false;
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Return translated context label
|
85 |
+
*
|
86 |
+
* @return string Translated context label
|
87 |
+
*/
|
88 |
+
public static function get_label() {
|
89 |
+
return _x( 'WooCommerce', 'woocommerce', 'stream' );
|
90 |
+
}
|
91 |
+
|
92 |
+
/**
|
93 |
+
* Return translated action labels
|
94 |
+
*
|
95 |
+
* @return array Action label translations
|
96 |
+
*/
|
97 |
+
public static function get_action_labels() {
|
98 |
+
return array(
|
99 |
+
'updated' => _x( 'Updated', 'woocommerce', 'stream' ),
|
100 |
+
'created' => _x( 'Created', 'woocommerce', 'stream' ),
|
101 |
+
'trashed' => _x( 'Trashed', 'woocommerce', 'stream' ),
|
102 |
+
'deleted' => _x( 'Deleted', 'woocommerce', 'stream' ),
|
103 |
+
);
|
104 |
+
}
|
105 |
+
|
106 |
+
/**
|
107 |
+
* Return translated context labels
|
108 |
+
*
|
109 |
+
* @return array Context label translations
|
110 |
+
*/
|
111 |
+
public static function get_context_labels() {
|
112 |
+
$context_labels = array();
|
113 |
+
|
114 |
+
if ( class_exists( 'WP_Stream_Connector_Posts' ) ) {
|
115 |
+
$context_labels = array_merge(
|
116 |
+
$context_labels,
|
117 |
+
WP_Stream_Connector_Posts::get_context_labels()
|
118 |
+
);
|
119 |
+
}
|
120 |
+
|
121 |
+
$custom_context_labels = array(
|
122 |
+
'attributes' => _x( 'Attributes', 'woocommerce', 'stream' ),
|
123 |
+
);
|
124 |
+
|
125 |
+
$context_labels = array_merge(
|
126 |
+
$context_labels,
|
127 |
+
$custom_context_labels,
|
128 |
+
self::$settings_pages
|
129 |
+
);
|
130 |
+
|
131 |
+
return apply_filters( 'wp_stream_woocommerce_contexts', $context_labels );
|
132 |
+
}
|
133 |
+
|
134 |
+
/**
|
135 |
+
* Return settings used by WooCommerce that aren't registered
|
136 |
+
*
|
137 |
+
* @return array Custom settings with translated title and page
|
138 |
+
*/
|
139 |
+
public static function get_custom_settings() {
|
140 |
+
$custom_settings = array(
|
141 |
+
'woocommerce_frontend_css_colors' => array(
|
142 |
+
'title' => __( 'Frontend Styles', 'stream' ),
|
143 |
+
'page' => 'wc-settings',
|
144 |
+
'tab' => 'general',
|
145 |
+
'section' => '',
|
146 |
+
'type' => __( 'setting', 'stream' ),
|
147 |
+
),
|
148 |
+
'woocommerce_default_gateway' => array(
|
149 |
+
'title' => __( 'Gateway Display Default', 'stream' ),
|
150 |
+
'page' => 'wc-settings',
|
151 |
+
'tab' => 'checkout',
|
152 |
+
'section' => '',
|
153 |
+
'type' => __( 'setting', 'stream' ),
|
154 |
+
),
|
155 |
+
'woocommerce_gateway_order' => array(
|
156 |
+
'title' => __( 'Gateway Display Order', 'stream' ),
|
157 |
+
'page' => 'wc-settings',
|
158 |
+
'tab' => 'checkout',
|
159 |
+
'section' => '',
|
160 |
+
'type' => __( 'setting', 'stream' ),
|
161 |
+
),
|
162 |
+
'woocommerce_default_shipping_method' => array(
|
163 |
+
'title' => __( 'Shipping Methods Default', 'stream' ),
|
164 |
+
'page' => 'wc-settings',
|
165 |
+
'tab' => 'shipping',
|
166 |
+
'section' => '',
|
167 |
+
'type' => __( 'setting', 'stream' ),
|
168 |
+
),
|
169 |
+
'woocommerce_shipping_method_order' => array(
|
170 |
+
'title' => __( 'Shipping Methods Order', 'stream' ),
|
171 |
+
'page' => 'wc-settings',
|
172 |
+
'tab' => 'shipping',
|
173 |
+
'section' => '',
|
174 |
+
'type' => __( 'setting', 'stream' ),
|
175 |
+
),
|
176 |
+
'shipping_debug_mode' => array(
|
177 |
+
'title' => __( 'Shipping Debug Mode', 'stream' ),
|
178 |
+
'page' => 'wc-status',
|
179 |
+
'tab' => 'tools',
|
180 |
+
'section' => '',
|
181 |
+
'type' => __( 'tool', 'stream' ),
|
182 |
+
),
|
183 |
+
'template_debug_mode' => array(
|
184 |
+
'title' => __( 'Template Debug Mode', 'stream' ),
|
185 |
+
'page' => 'wc-status',
|
186 |
+
'tab' => 'tools',
|
187 |
+
'section' => '',
|
188 |
+
'type' => __( 'tool', 'stream' ),
|
189 |
+
),
|
190 |
+
'uninstall_data' => array(
|
191 |
+
'title' => __( 'Remove post types on uninstall', 'stream' ),
|
192 |
+
'page' => 'wc-status',
|
193 |
+
'tab' => 'tools',
|
194 |
+
'section' => '',
|
195 |
+
'type' => __( 'tool', 'stream' ),
|
196 |
+
),
|
197 |
+
);
|
198 |
+
|
199 |
+
return apply_filters( 'wp_stream_woocommerce_custom_settings', $custom_settings );
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Add action links to Stream drop row in admin list screen
|
204 |
+
*
|
205 |
+
* @filter wp_stream_action_links_{connector}
|
206 |
+
*
|
207 |
+
* @param array $links Previous links registered
|
208 |
+
* @param object $record Stream record
|
209 |
+
*
|
210 |
+
* @return array Action links
|
211 |
+
*/
|
212 |
+
public static function action_links( $links, $record ) {
|
213 |
+
if ( in_array( $record->context, self::$post_types ) && get_post( $record->object_id ) ) {
|
214 |
+
if ( $link = get_edit_post_link( $record->object_id ) ) {
|
215 |
+
$post_type_name = WP_Stream_Connector_Posts::get_post_type_name( get_post_type( $record->object_id ) );
|
216 |
+
$links[ sprintf( _x( 'Edit %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = $link;
|
217 |
+
}
|
218 |
+
|
219 |
+
if ( post_type_exists( get_post_type( $record->object_id ) ) && $link = get_permalink( $record->object_id ) ) {
|
220 |
+
$links[ __( 'View', 'stream' ) ] = $link;
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
$context_labels = self::get_context_labels();
|
225 |
+
$option_key = wp_stream_get_meta( $record, 'option', true );
|
226 |
+
$option_page = wp_stream_get_meta( $record, 'page', true );
|
227 |
+
$option_tab = wp_stream_get_meta( $record, 'tab', true );
|
228 |
+
$option_section = wp_stream_get_meta( $record, 'section', true );
|
229 |
+
|
230 |
+
if ( $option_key && $option_tab ) {
|
231 |
+
$text = sprintf( __( 'Edit WooCommerce %s', 'stream' ), $context_labels[ $record->context ] );;
|
232 |
+
$url = add_query_arg(
|
233 |
+
array( 'page' => $option_page, 'tab' => $option_tab, 'section' => $option_section ),
|
234 |
+
admin_url( 'admin.php' ) // Not self_admin_url here, as WooCommerce doesn't exist in Network Admin
|
235 |
+
);
|
236 |
+
|
237 |
+
$links[ $text ] = $url . '#wp-stream-highlight:' . $option_key;
|
238 |
+
}
|
239 |
+
|
240 |
+
return $links;
|
241 |
+
}
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Prevent the Stream Posts connector from logging orders
|
245 |
+
* so that we can handle them differently here
|
246 |
+
*
|
247 |
+
* @filter wp_stream_posts_exclude_post_types
|
248 |
+
* @param array $post_types Ignored post types
|
249 |
+
* @return array Filtered post types
|
250 |
+
*/
|
251 |
+
public static function exclude_order_post_types( $post_types ) {
|
252 |
+
$post_types[] = 'shop_order';
|
253 |
+
|
254 |
+
return $post_types;
|
255 |
+
}
|
256 |
+
|
257 |
+
/**
|
258 |
+
* Prevent the Stream Comments connector from logging status
|
259 |
+
* change comments on orders
|
260 |
+
*
|
261 |
+
* @filter wp_stream_commnent_exclude_comment_types
|
262 |
+
* @param array $comment_types Ignored post types
|
263 |
+
* @return array Filtered post types
|
264 |
+
*/
|
265 |
+
public static function exclude_order_comment_types( $comment_types ) {
|
266 |
+
$comment_types[] = 'order_note';
|
267 |
+
|
268 |
+
return $comment_types;
|
269 |
+
}
|
270 |
+
|
271 |
+
/**
|
272 |
+
* Log Order major status changes ( creating / updating / trashing )
|
273 |
+
*
|
274 |
+
* @action transition_post_status
|
275 |
+
*/
|
276 |
+
public static function callback_transition_post_status( $new, $old, $post ) {
|
277 |
+
// Only track orders
|
278 |
+
if ( 'shop_order' !== $post->post_type ) {
|
279 |
+
return;
|
280 |
+
}
|
281 |
+
|
282 |
+
// Don't track customer actions
|
283 |
+
if ( ! is_admin() ) {
|
284 |
+
return;
|
285 |
+
}
|
286 |
+
|
287 |
+
// Don't track minor status change actions
|
288 |
+
if ( in_array( wp_stream_filter_input( INPUT_GET, 'action' ), array( 'mark_processing', 'mark_on-hold', 'mark_completed' ) ) || defined( 'DOING_AJAX' ) ) {
|
289 |
+
return;
|
290 |
+
}
|
291 |
+
|
292 |
+
// Don't log updates when more than one happens at the same time
|
293 |
+
if ( $post->ID === self::$order_update_logged ) {
|
294 |
+
return;
|
295 |
+
}
|
296 |
+
|
297 |
+
if ( in_array( $new, array( 'auto-draft', 'draft', 'inherit' ) ) ) {
|
298 |
+
return;
|
299 |
+
} elseif ( 'auto-draft' === $old && 'publish' === $new ) {
|
300 |
+
$message = _x(
|
301 |
+
'%s created',
|
302 |
+
'Order title',
|
303 |
+
'stream'
|
304 |
+
);
|
305 |
+
$action = 'created';
|
306 |
+
} elseif ( 'trash' === $new ) {
|
307 |
+
$message = _x(
|
308 |
+
'%s trashed',
|
309 |
+
'Order title',
|
310 |
+
'stream'
|
311 |
+
);
|
312 |
+
$action = 'trashed';
|
313 |
+
} elseif ( 'trash' === $old && 'publish' === $new ) {
|
314 |
+
$message = _x(
|
315 |
+
'%s restored from the trash',
|
316 |
+
'Order title',
|
317 |
+
'stream'
|
318 |
+
);
|
319 |
+
$action = 'untrashed';
|
320 |
+
} else {
|
321 |
+
$message = _x(
|
322 |
+
'%s updated',
|
323 |
+
'Order title',
|
324 |
+
'stream'
|
325 |
+
);
|
326 |
+
}
|
327 |
+
|
328 |
+
if ( empty( $action ) ) {
|
329 |
+
$action = 'updated';
|
330 |
+
}
|
331 |
+
|
332 |
+
$order = new WC_Order( $post->ID );
|
333 |
+
$order_title = __( 'Order number', 'stream' ) . ' ' . esc_html( $order->get_order_number() );
|
334 |
+
$order_type_name = __( 'order', 'stream' );
|
335 |
+
|
336 |
+
self::log(
|
337 |
+
$message,
|
338 |
+
array(
|
339 |
+
'post_title' => $order_title,
|
340 |
+
'singular_name' => $order_type_name,
|
341 |
+
'new_status' => $new,
|
342 |
+
'old_status' => $old,
|
343 |
+
'revision_id' => null,
|
344 |
+
),
|
345 |
+
$post->ID,
|
346 |
+
$post->post_type,
|
347 |
+
$action
|
348 |
+
);
|
349 |
+
|
350 |
+
self::$order_update_logged = $post->ID;
|
351 |
+
}
|
352 |
+
|
353 |
+
/**
|
354 |
+
* Log order deletion
|
355 |
+
*
|
356 |
+
* @action deleted_post
|
357 |
+
*/
|
358 |
+
public static function callback_deleted_post( $post_id ) {
|
359 |
+
$post = get_post( $post_id );
|
360 |
+
|
361 |
+
// We check if post is an instance of WP_Post as it doesn't always resolve in unit testing
|
362 |
+
if ( ! ( $post instanceof WP_Post ) || 'shop_order' !== $post->post_type ) {
|
363 |
+
return;
|
364 |
+
}
|
365 |
+
|
366 |
+
// Ignore auto-drafts that are deleted by the system, see issue-293
|
367 |
+
if ( 'auto-draft' === $post->post_status ) {
|
368 |
+
return;
|
369 |
+
}
|
370 |
+
|
371 |
+
$order = new WC_Order( $post->ID );
|
372 |
+
$order_title = __( 'Order number', 'stream' ) . ' ' . esc_html( $order->get_order_number() );
|
373 |
+
$order_type_name = __( 'order', 'stream' );
|
374 |
+
|
375 |
+
self::log(
|
376 |
+
_x(
|
377 |
+
'"%s" deleted from trash',
|
378 |
+
'Order title',
|
379 |
+
'stream'
|
380 |
+
),
|
381 |
+
array(
|
382 |
+
'post_title' => $order_title,
|
383 |
+
'singular_name' => $order_type_name,
|
384 |
+
),
|
385 |
+
$post->ID,
|
386 |
+
$post->post_type,
|
387 |
+
'deleted'
|
388 |
+
);
|
389 |
+
}
|
390 |
+
|
391 |
+
/**
|
392 |
+
* Log Order minor status changes ( pending / on-hold / failed / processing / completed / refunded / cancelled )
|
393 |
+
*
|
394 |
+
* @action woocommerce_order_status_changed
|
395 |
+
*/
|
396 |
+
public static function callback_woocommerce_order_status_changed( $order_id, $old, $new ) {
|
397 |
+
// Don't track customer actions
|
398 |
+
if ( ! is_admin() ) {
|
399 |
+
return;
|
400 |
+
}
|
401 |
+
|
402 |
+
$old_status = get_term_by( 'slug', $old, 'shop_order_status' );
|
403 |
+
$new_status = get_term_by( 'slug', $new, 'shop_order_status' );
|
404 |
+
|
405 |
+
// Don't track new statuses
|
406 |
+
if ( ! $old_status ) {
|
407 |
+
return;
|
408 |
+
}
|
409 |
+
|
410 |
+
$message = _x(
|
411 |
+
'%1$s status changed from %2$s to %3$s',
|
412 |
+
'1. Order title, 2. Old status, 3. New status',
|
413 |
+
'stream'
|
414 |
+
);
|
415 |
+
|
416 |
+
$order = new WC_Order( $order_id );
|
417 |
+
$order_title = __( 'Order number', 'stream' ) . ' ' . esc_html( $order->get_order_number() );
|
418 |
+
$order_type_name = __( 'order', 'stream' );
|
419 |
+
$new_status_name = strtolower( $new_status->name );
|
420 |
+
$old_status_name = strtolower( $old_status->name );
|
421 |
+
|
422 |
+
self::log(
|
423 |
+
$message,
|
424 |
+
array(
|
425 |
+
'post_title' => $order_title,
|
426 |
+
'old_status_name' => $old_status_name,
|
427 |
+
'new_status_name' => $new_status_name,
|
428 |
+
'singular_name' => $order_type_name,
|
429 |
+
'new_status' => $new,
|
430 |
+
'old_status' => $old,
|
431 |
+
'revision_id' => null,
|
432 |
+
),
|
433 |
+
$order_id,
|
434 |
+
'shop_order',
|
435 |
+
$new_status_name
|
436 |
+
);
|
437 |
+
}
|
438 |
+
|
439 |
+
/**
|
440 |
+
* Log adding a product attribute
|
441 |
+
*
|
442 |
+
* @action woocommerce_attribute_added
|
443 |
+
*/
|
444 |
+
public static function callback_woocommerce_attribute_added( $attribute_id, $attribute ) {
|
445 |
+
self::log(
|
446 |
+
_x(
|
447 |
+
'"%s" product attribute created',
|
448 |
+
'Term name',
|
449 |
+
'stream'
|
450 |
+
),
|
451 |
+
$attribute,
|
452 |
+
$attribute_id,
|
453 |
+
'attributes',
|
454 |
+
'created'
|
455 |
+
);
|
456 |
+
}
|
457 |
+
|
458 |
+
/**
|
459 |
+
* Log updating a product attribute
|
460 |
+
*
|
461 |
+
* @action woocommerce_attribute_updated
|
462 |
+
*/
|
463 |
+
public static function callback_woocommerce_attribute_updated( $attribute_id, $attribute ) {
|
464 |
+
self::log(
|
465 |
+
_x(
|
466 |
+
'"%s" product attribute updated',
|
467 |
+
'Term name',
|
468 |
+
'stream'
|
469 |
+
),
|
470 |
+
$attribute,
|
471 |
+
$attribute_id,
|
472 |
+
'attributes',
|
473 |
+
'updated'
|
474 |
+
);
|
475 |
+
}
|
476 |
+
|
477 |
+
/**
|
478 |
+
* Log deleting a product attribute
|
479 |
+
*
|
480 |
+
* @action woocommerce_attribute_updated
|
481 |
+
*/
|
482 |
+
public static function callback_woocommerce_attribute_deleted( $attribute_id, $attribute_name ) {
|
483 |
+
self::log(
|
484 |
+
_x(
|
485 |
+
'"%s" product attribute deleted',
|
486 |
+
'Term name',
|
487 |
+
'stream'
|
488 |
+
),
|
489 |
+
array(
|
490 |
+
'attribute_name' => $attribute_name,
|
491 |
+
),
|
492 |
+
$attribute_id,
|
493 |
+
'attributes',
|
494 |
+
'deleted'
|
495 |
+
);
|
496 |
+
}
|
497 |
+
|
498 |
+
/**
|
499 |
+
* Log adding a tax rate
|
500 |
+
*
|
501 |
+
* @action woocommerce_tax_rate_added
|
502 |
+
*/
|
503 |
+
public static function callback_woocommerce_tax_rate_added( $tax_rate_id, $tax_rate ) {
|
504 |
+
self::log(
|
505 |
+
_x(
|
506 |
+
'"%4$s" tax rate created',
|
507 |
+
'Tax rate name',
|
508 |
+
'stream'
|
509 |
+
),
|
510 |
+
$tax_rate,
|
511 |
+
$tax_rate_id,
|
512 |
+
'tax',
|
513 |
+
'created'
|
514 |
+
);
|
515 |
+
}
|
516 |
+
|
517 |
+
/**
|
518 |
+
* Log updating a tax rate
|
519 |
+
*
|
520 |
+
* @action woocommerce_tax_rate_updated
|
521 |
+
*/
|
522 |
+
public static function callback_woocommerce_tax_rate_updated( $tax_rate_id, $tax_rate ) {
|
523 |
+
self::log(
|
524 |
+
_x(
|
525 |
+
'"%4$s" tax rate updated',
|
526 |
+
'Tax rate name',
|
527 |
+
'stream'
|
528 |
+
),
|
529 |
+
$tax_rate,
|
530 |
+
$tax_rate_id,
|
531 |
+
'tax',
|
532 |
+
'updated'
|
533 |
+
);
|
534 |
+
}
|
535 |
+
|
536 |
+
/**
|
537 |
+
* Log deleting a tax rate
|
538 |
+
*
|
539 |
+
* @action woocommerce_tax_rate_updated
|
540 |
+
*/
|
541 |
+
public static function callback_woocommerce_tax_rate_deleted( $tax_rate_id ) {
|
542 |
+
global $wpdb;
|
543 |
+
|
544 |
+
$tax_rate_name = $wpdb->get_var(
|
545 |
+
$wpdb->prepare(
|
546 |
+
"SELECT tax_rate_name FROM {$wpdb->prefix}woocommerce_tax_rates
|
547 |
+
WHERE tax_rate_id = %s
|
548 |
+
",
|
549 |
+
$tax_rate_id
|
550 |
+
)
|
551 |
+
);
|
552 |
+
|
553 |
+
self::log(
|
554 |
+
_x(
|
555 |
+
'"%s" tax rate deleted',
|
556 |
+
'Tax rate name',
|
557 |
+
'stream'
|
558 |
+
),
|
559 |
+
array(
|
560 |
+
'tax_rate_name' => $tax_rate_name,
|
561 |
+
),
|
562 |
+
$tax_rate_id,
|
563 |
+
'tax',
|
564 |
+
'deleted'
|
565 |
+
);
|
566 |
+
}
|
567 |
+
|
568 |
+
/**
|
569 |
+
* Filter records and take-over our precious data
|
570 |
+
*
|
571 |
+
* @filter wp_stream_record_array
|
572 |
+
*
|
573 |
+
* @param array $recordarr Record data to be inserted
|
574 |
+
*
|
575 |
+
* @return array Filtered record data
|
576 |
+
*/
|
577 |
+
public static function callback_wp_stream_record_array( $recordarr ) {
|
578 |
+
foreach ( $recordarr as $key => $record ) {
|
579 |
+
// Change connector::posts records
|
580 |
+
if ( 'posts' === $record['connector'] && in_array( $record['context'], self::$post_types ) ) {
|
581 |
+
$recordarr[ $key ]['connector'] = self::$name;
|
582 |
+
} elseif ( 'taxonomies' === $record['connector'] && in_array( $record['context'], self::$taxonomies ) ) {
|
583 |
+
$recordarr[ $key ]['connector'] = self::$name;
|
584 |
+
} elseif ( 'settings' === $record['connector'] ) {
|
585 |
+
$option = isset( $record['meta']['option_key'] ) ? $record['meta']['option_key'] : false;
|
586 |
+
|
587 |
+
if ( $option && isset( self::$settings[ $option ] ) ) {
|
588 |
+
return false;
|
589 |
+
}
|
590 |
+
}
|
591 |
+
}
|
592 |
+
|
593 |
+
return $recordarr;
|
594 |
+
}
|
595 |
+
|
596 |
+
public static function callback_updated_option( $option_key, $old_value, $value ) {
|
597 |
+
$options = array( $option_key );
|
598 |
+
|
599 |
+
if ( is_array( $old_value ) || is_array( $value ) ) {
|
600 |
+
foreach ( self::get_changed_keys( $old_value, $value ) as $field_key ) {
|
601 |
+
$options[] = $field_key;
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
foreach ( $options as $option ) {
|
606 |
+
if ( ! array_key_exists( $option, self::$settings ) ) {
|
607 |
+
continue;
|
608 |
+
}
|
609 |
+
|
610 |
+
self::log(
|
611 |
+
__( '"%1$s" %2$s updated', 'stream' ),
|
612 |
+
array(
|
613 |
+
'label' => self::$settings[ $option ]['title'],
|
614 |
+
'type' => self::$settings[ $option ]['type'],
|
615 |
+
'page' => self::$settings[ $option ]['page'],
|
616 |
+
'tab' => self::$settings[ $option ]['tab'],
|
617 |
+
'section' => self::$settings[ $option ]['section'],
|
618 |
+
'option' => $option,
|
619 |
+
// Prevent fatal error when saving option as array
|
620 |
+
'old_value' => maybe_serialize( $old_value ),
|
621 |
+
'value' => maybe_serialize( $value ),
|
622 |
+
),
|
623 |
+
null,
|
624 |
+
self::$settings[ $option ]['tab'],
|
625 |
+
'updated'
|
626 |
+
);
|
627 |
+
}
|
628 |
+
}
|
629 |
+
|
630 |
+
public static function get_woocommerce_settings_fields() {
|
631 |
+
if ( ! defined( 'WC_VERSION' ) || ! class_exists( 'WC_Admin_Settings' ) ) {
|
632 |
+
return;
|
633 |
+
}
|
634 |
+
|
635 |
+
if ( ! empty( self::$settings ) ) {
|
636 |
+
return self::$settings;
|
637 |
+
}
|
638 |
+
|
639 |
+
$settings_cache_key = 'stream_connector_woocommerce_settings_' . sanitize_key( WC_VERSION );
|
640 |
+
|
641 |
+
if ( $settings_transient = get_transient( $settings_cache_key ) ) {
|
642 |
+
$settings = $settings_transient['settings'];
|
643 |
+
$settings_pages = $settings_transient['settings_pages'];
|
644 |
+
} else {
|
645 |
+
global $woocommerce;
|
646 |
+
|
647 |
+
$settings = array();
|
648 |
+
$settings_pages = array();
|
649 |
+
|
650 |
+
foreach ( WC_Admin_Settings::get_settings_pages() as $page ) {
|
651 |
+
// Get ID / Label of the page, since they're protected, by hacking into
|
652 |
+
// the callback filter for 'woocommerce_settings_tabs_array'
|
653 |
+
$info = $page->add_settings_page( array() );
|
654 |
+
$page_id = key( $info );
|
655 |
+
$page_label = current( $info );
|
656 |
+
$sections = $page->get_sections();
|
657 |
+
|
658 |
+
if ( empty( $sections ) ) {
|
659 |
+
$sections[''] = $page_label;
|
660 |
+
}
|
661 |
+
|
662 |
+
$settings_pages[ $page_id ] = $page_label;
|
663 |
+
|
664 |
+
// Remove non-fields ( sections, titles and whatever )
|
665 |
+
$fields = array();
|
666 |
+
|
667 |
+
foreach ( $sections as $section_key => $section_label ) {
|
668 |
+
$_fields = array_filter(
|
669 |
+
$page->get_settings( $section_key ),
|
670 |
+
function( $item ) {
|
671 |
+
return isset( $item['id'] ) && ( ! in_array( $item['type'], array( 'title', 'sectionend' ) ) );
|
672 |
+
}
|
673 |
+
);
|
674 |
+
|
675 |
+
foreach ( $_fields as $field ) {
|
676 |
+
$title = isset( $field['title'] ) ? $field['title'] : $field['desc'];
|
677 |
+
$fields[ $field['id'] ] = array(
|
678 |
+
'title' => $title,
|
679 |
+
'page' => 'wc-settings',
|
680 |
+
'tab' => $page_id,
|
681 |
+
'section' => $section_key,
|
682 |
+
'type' => __( 'setting', 'stream' ),
|
683 |
+
);
|
684 |
+
}
|
685 |
+
}
|
686 |
+
|
687 |
+
// Store fields in the global array to be searched later
|
688 |
+
$settings = array_merge( $settings, $fields );
|
689 |
+
}
|
690 |
+
|
691 |
+
// Provide additional context for each of the settings pages
|
692 |
+
array_walk( $settings_pages, function( &$value ) {
|
693 |
+
$value .= ' ' . __( 'Settings', 'stream' );
|
694 |
+
});
|
695 |
+
|
696 |
+
// Load Payment Gateway Settings
|
697 |
+
$payment_gateway_settings = array();
|
698 |
+
$payment_gateways = $woocommerce->payment_gateways();
|
699 |
+
|
700 |
+
foreach ( $payment_gateways->payment_gateways as $section_key => $payment_gateway ) {
|
701 |
+
$title = $payment_gateway->title;
|
702 |
+
$key = $payment_gateway->plugin_id . $payment_gateway->id . '_settings';
|
703 |
+
|
704 |
+
$payment_gateway_settings[ $key ] = array(
|
705 |
+
'title' => $title,
|
706 |
+
'page' => 'wc-settings',
|
707 |
+
'tab' => 'checkout',
|
708 |
+
'section' => strtolower( $section_key ),
|
709 |
+
'type' => __( 'payment gateway', 'stream' ),
|
710 |
+
);
|
711 |
+
}
|
712 |
+
|
713 |
+
$settings = array_merge( $settings, $payment_gateway_settings );
|
714 |
+
|
715 |
+
// Load Shipping Method Settings
|
716 |
+
$shipping_method_settings = array();
|
717 |
+
$shipping_methods = $woocommerce->shipping();
|
718 |
+
|
719 |
+
foreach ( $shipping_methods->shipping_methods as $section_key => $shipping_method ) {
|
720 |
+
$title = $shipping_method->title;
|
721 |
+
$key = $shipping_method->plugin_id . $shipping_method->id . '_settings';
|
722 |
+
|
723 |
+
$shipping_method_settings[ $key ] = array(
|
724 |
+
'title' => $title,
|
725 |
+
'page' => 'wc-settings',
|
726 |
+
'tab' => 'shipping',
|
727 |
+
'section' => strtolower( $section_key ),
|
728 |
+
'type' => __( 'shipping method', 'stream' ),
|
729 |
+
);
|
730 |
+
}
|
731 |
+
|
732 |
+
$settings = array_merge( $settings, $shipping_method_settings );
|
733 |
+
|
734 |
+
// Load Email Settings
|
735 |
+
$email_settings = array();
|
736 |
+
$emails = $woocommerce->mailer();
|
737 |
+
|
738 |
+
foreach ( $emails->emails as $section_key => $email ) {
|
739 |
+
$title = $email->title;
|
740 |
+
$key = $email->plugin_id . $email->id . '_settings';
|
741 |
+
|
742 |
+
$email_settings[ $key ] = array(
|
743 |
+
'title' => $title,
|
744 |
+
'page' => 'wc-settings',
|
745 |
+
'tab' => 'email',
|
746 |
+
'section' => strtolower( $section_key ),
|
747 |
+
'type' => __( 'email', 'stream' ),
|
748 |
+
);
|
749 |
+
}
|
750 |
+
|
751 |
+
$settings = array_merge( $settings, $email_settings );
|
752 |
+
|
753 |
+
// Tools page
|
754 |
+
$tools_page = array(
|
755 |
+
'tools' => __( 'Tools', 'stream' )
|
756 |
+
);
|
757 |
+
|
758 |
+
$settings_pages = array_merge( $settings_pages, $tools_page );
|
759 |
+
|
760 |
+
// Cache the results
|
761 |
+
$settings_cache = array(
|
762 |
+
'settings' => $settings,
|
763 |
+
'settings_pages' => $settings_pages,
|
764 |
+
);
|
765 |
+
|
766 |
+
set_transient( $settings_cache_key, $settings_cache, MINUTE_IN_SECONDS * 60 * 6 );
|
767 |
+
}
|
768 |
+
|
769 |
+
$custom_settings = self::get_custom_settings();
|
770 |
+
self::$settings = array_merge( $settings, $custom_settings );
|
771 |
+
self::$settings_pages = $settings_pages;
|
772 |
+
|
773 |
+
return self::$settings;
|
774 |
+
}
|
775 |
+
|
776 |
+
}
|
connectors/class-wp-stream-connector-wordpress-seo.php
ADDED
@@ -0,0 +1,487 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Connector_WordPress_SEO extends WP_Stream_Connector {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Connector slug
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $name = 'wordpress-seo';
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Holds tracked plugin minimum version required
|
14 |
+
*
|
15 |
+
* @const string
|
16 |
+
*/
|
17 |
+
const PLUGIN_MIN_VERSION = '1.5.3.3';
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Actions registered for this connector
|
21 |
+
*
|
22 |
+
* @var array
|
23 |
+
*/
|
24 |
+
public static $actions = array(
|
25 |
+
'wpseo_handle_import',
|
26 |
+
'wpseo_import',
|
27 |
+
'seo_page_wpseo_files',
|
28 |
+
'added_post_meta',
|
29 |
+
'updated_post_meta',
|
30 |
+
'deleted_post_meta',
|
31 |
+
);
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Tracking registered Settings, with overridden data
|
35 |
+
*
|
36 |
+
* @var array
|
37 |
+
*/
|
38 |
+
public static $option_groups = array();
|
39 |
+
|
40 |
+
/**
|
41 |
+
* Check if plugin dependencies are satisfied and add an admin notice if not
|
42 |
+
*
|
43 |
+
* @return bool
|
44 |
+
*/
|
45 |
+
public static function is_dependency_satisfied() {
|
46 |
+
if ( defined( 'WPSEO_VERSION' ) && version_compare( WPSEO_VERSION, self::PLUGIN_MIN_VERSION, '>=' ) ) {
|
47 |
+
return true;
|
48 |
+
}
|
49 |
+
|
50 |
+
return false;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Return translated connector label
|
55 |
+
*
|
56 |
+
* @return string Translated connector label
|
57 |
+
*/
|
58 |
+
public static function get_label() {
|
59 |
+
return _x( 'WordPress SEO', 'wordpress-seo', 'stream' );
|
60 |
+
}
|
61 |
+
|
62 |
+
/**
|
63 |
+
* Return translated action labels
|
64 |
+
*
|
65 |
+
* @return array Action label translations
|
66 |
+
*/
|
67 |
+
public static function get_action_labels() {
|
68 |
+
return array(
|
69 |
+
'created' => _x( 'Created', 'wordpress-seo', 'stream' ),
|
70 |
+
'updated' => _x( 'Updated', 'wordpress-seo', 'stream' ),
|
71 |
+
'added' => _x( 'Added', 'wordpress-seo', 'stream' ),
|
72 |
+
'deleted' => _x( 'Deleted', 'wordpress-seo', 'stream' ),
|
73 |
+
'exported' => _x( 'Exported', 'wordpress-seo', 'stream' ),
|
74 |
+
'imported' => _x( 'Imported', 'wordpress-seo', 'stream' ),
|
75 |
+
);
|
76 |
+
}
|
77 |
+
|
78 |
+
/**
|
79 |
+
* Return translated context labels
|
80 |
+
*
|
81 |
+
* @return array Context label translations
|
82 |
+
*/
|
83 |
+
public static function get_context_labels() {
|
84 |
+
return array(
|
85 |
+
'wpseo_dashboard' => _x( 'Dashboard', 'wordpress-seo', 'stream' ),
|
86 |
+
'wpseo_titles' => _x( 'Titles & Metas', 'wordpress-seo', 'stream' ),
|
87 |
+
'wpseo_social' => _x( 'Social', 'wordpress-seo', 'stream' ),
|
88 |
+
'wpseo_xml' => _x( 'XML Sitemaps', 'wordpress-seo', 'stream' ),
|
89 |
+
'wpseo_permalinks' => _x( 'Permalinks', 'wordpress-seo', 'stream' ),
|
90 |
+
'wpseo_internal-links' => _x( 'Internal Links', 'wordpress-seo', 'stream' ),
|
91 |
+
'wpseo_rss' => _x( 'RSS', 'wordpress-seo', 'stream' ),
|
92 |
+
'wpseo_import' => _x( 'Import & Export', 'wordpress-seo', 'stream' ),
|
93 |
+
'wpseo_bulk-title-editor' => _x( 'Bulk Title Editor', 'wordpress-seo', 'stream' ),
|
94 |
+
'wpseo_bulk-description-editor' => _x( 'Bulk Description Editor', 'wordpress-seo', 'stream' ),
|
95 |
+
'wpseo_files' => _x( 'Files', 'wordpress-seo', 'stream' ),
|
96 |
+
'wpseo_meta' => _x( 'Content', 'wordpress-seo', 'stream' ),
|
97 |
+
);
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Add action links to Stream drop row in admin list screen
|
102 |
+
*
|
103 |
+
* @filter wp_stream_action_links_{connector}
|
104 |
+
*
|
105 |
+
* @param array $links Previous links registered
|
106 |
+
* @param object $record Stream record
|
107 |
+
*
|
108 |
+
* @return array Action links
|
109 |
+
*/
|
110 |
+
public static function action_links( $links, $record ) {
|
111 |
+
$contexts = self::get_context_labels();
|
112 |
+
|
113 |
+
// Options
|
114 |
+
if ( $option = wp_stream_get_meta( $record, 'option', true ) ) {
|
115 |
+
$key = wp_stream_get_meta( $record, 'option_key', true );
|
116 |
+
|
117 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
118 |
+
array(
|
119 |
+
'page' => $record->context,
|
120 |
+
),
|
121 |
+
admin_url( 'admin.php' )
|
122 |
+
) . '#stream-highlight-' . esc_attr( $key );
|
123 |
+
} elseif ( 'wpseo_files' === $record->context ) {
|
124 |
+
$links[ __( 'Edit', 'stream' ) ] = add_query_arg(
|
125 |
+
array(
|
126 |
+
'page' => $record->context,
|
127 |
+
),
|
128 |
+
admin_url( 'admin.php' )
|
129 |
+
);
|
130 |
+
} elseif ( 'wpseo_meta' === $record->context ) {
|
131 |
+
$post = get_post( $record->object_id );
|
132 |
+
|
133 |
+
if ( $post ) {
|
134 |
+
$post_type_name = WP_Stream_Connector_Posts::get_post_type_name( get_post_type( $post->ID ) );
|
135 |
+
|
136 |
+
if ( 'trash' === $post->post_status ) {
|
137 |
+
$untrash = wp_nonce_url(
|
138 |
+
add_query_arg(
|
139 |
+
array(
|
140 |
+
'action' => 'untrash',
|
141 |
+
'post' => $post->ID,
|
142 |
+
),
|
143 |
+
admin_url( 'post.php' )
|
144 |
+
),
|
145 |
+
sprintf( 'untrash-post_%d', $post->ID )
|
146 |
+
);
|
147 |
+
|
148 |
+
$delete = wp_nonce_url(
|
149 |
+
add_query_arg(
|
150 |
+
array(
|
151 |
+
'action' => 'delete',
|
152 |
+
'post' => $post->ID,
|
153 |
+
),
|
154 |
+
admin_url( 'post.php' )
|
155 |
+
),
|
156 |
+
sprintf( 'delete-post_%d', $post->ID )
|
157 |
+
);
|
158 |
+
|
159 |
+
$links[ sprintf( esc_html_x( 'Restore %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = $untrash;
|
160 |
+
$links[ sprintf( esc_html_x( 'Delete %s Permenantly', 'Post type singular name', 'stream' ), $post_type_name ) ] = $delete;
|
161 |
+
} else {
|
162 |
+
$links[ sprintf( esc_html_x( 'Edit %s', 'Post type singular name', 'stream' ), $post_type_name ) ] = get_edit_post_link( $post->ID );
|
163 |
+
|
164 |
+
if ( $view_link = get_permalink( $post->ID ) ) {
|
165 |
+
$links[ esc_html__( 'View', 'stream' ) ] = $view_link;
|
166 |
+
}
|
167 |
+
|
168 |
+
if ( $revision_id = wp_stream_get_meta( $record, 'revision_id', true ) ) {
|
169 |
+
$links[ esc_html__( 'Revision', 'stream' ) ] = get_edit_post_link( $revision_id );
|
170 |
+
}
|
171 |
+
}
|
172 |
+
}
|
173 |
+
}
|
174 |
+
|
175 |
+
return $links;
|
176 |
+
}
|
177 |
+
|
178 |
+
public static function register() {
|
179 |
+
parent::register();
|
180 |
+
|
181 |
+
foreach ( WPSEO_Options::$options as $class ) {
|
182 |
+
/* @var $class WPSEO_Options */
|
183 |
+
self::$option_groups[ $class::get_instance()->group_name ] = array(
|
184 |
+
'class' => $class,
|
185 |
+
'name' => $class::get_instance()->option_name,
|
186 |
+
);
|
187 |
+
}
|
188 |
+
|
189 |
+
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'admin_enqueue_scripts' ) );
|
190 |
+
add_filter( 'wp_stream_log_data', array( __CLASS__, 'log_override' ) );
|
191 |
+
}
|
192 |
+
|
193 |
+
public static function admin_enqueue_scripts( $hook ) {
|
194 |
+
if ( 0 === strpos( $hook, 'seo_page_' ) ) {
|
195 |
+
$src = WP_STREAM_URL . '/ui/js/wpseo-admin.js';
|
196 |
+
wp_enqueue_script( 'stream-connector-wpseo', $src, array( 'jquery' ), WP_Stream::VERSION );
|
197 |
+
}
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Track importing settings from other plugins
|
202 |
+
*/
|
203 |
+
public static function callback_wpseo_handle_import() {
|
204 |
+
$imports = array(
|
205 |
+
'importheadspace' => __( 'HeadSpace2', 'stream' ), # type = checkbox
|
206 |
+
'importaioseo' => __( 'All-in-One SEO', 'stream' ), # type = checkbox
|
207 |
+
'importaioseoold' => __( 'OLD All-in-One SEO', 'stream' ), # type = checkbox
|
208 |
+
'importwoo' => __( 'WooThemes SEO framework', 'stream' ), # type = checkbox
|
209 |
+
'importrobotsmeta' => __( 'Robots Meta (by Yoast)', 'stream' ), # type = checkbox
|
210 |
+
'importrssfooter' => __( 'RSS Footer (by Yoast)', 'stream' ), # type = checkbox
|
211 |
+
'importbreadcrumbs' => __( 'Yoast Breadcrumbs', 'stream' ), # type = checkbox
|
212 |
+
);
|
213 |
+
|
214 |
+
$opts = wp_stream_filter_input( INPUT_POST, 'wpseo' );
|
215 |
+
|
216 |
+
foreach ( $imports as $key => $name ) {
|
217 |
+
if ( isset( $opts[ $key ] ) ) {
|
218 |
+
self::log(
|
219 |
+
sprintf(
|
220 |
+
__( 'Imported settings from %1$s%2$s', 'stream' ),
|
221 |
+
$name,
|
222 |
+
isset( $opts['deleteolddata'] ) ? __( ', and deleted old data', 'stream' ) : ''
|
223 |
+
),
|
224 |
+
array(
|
225 |
+
'key' => $key,
|
226 |
+
'deleteolddata' => isset( $opts['deleteolddata'] ),
|
227 |
+
),
|
228 |
+
null,
|
229 |
+
'wpseo_import',
|
230 |
+
'imported'
|
231 |
+
);
|
232 |
+
}
|
233 |
+
}
|
234 |
+
}
|
235 |
+
|
236 |
+
public static function callback_wpseo_import() {
|
237 |
+
$opts = wp_stream_filter_input( INPUT_POST, 'wpseo' );
|
238 |
+
|
239 |
+
if ( wp_stream_filter_input( INPUT_POST, 'wpseo_export' ) ) {
|
240 |
+
self::log(
|
241 |
+
sprintf(
|
242 |
+
__( 'Exported settings%s', 'stream' ),
|
243 |
+
isset( $opts['include_taxonomy_meta'] ) ? __( ', including taxonomy meta', 'stream' ) : ''
|
244 |
+
),
|
245 |
+
array(
|
246 |
+
'include_taxonomy_meta' => isset( $opts['include_taxonomy_meta'] ),
|
247 |
+
),
|
248 |
+
null,
|
249 |
+
'wpseo_import',
|
250 |
+
'exported'
|
251 |
+
);
|
252 |
+
} elseif ( isset( $_FILES['settings_import_file'] ) ) {
|
253 |
+
self::log(
|
254 |
+
sprintf(
|
255 |
+
__( 'Tried importing settings from "%s"', 'stream' ),
|
256 |
+
$_FILES['settings_import_file']['name']
|
257 |
+
),
|
258 |
+
array(
|
259 |
+
'file' => $_FILES['settings_import_file']['name'],
|
260 |
+
),
|
261 |
+
null,
|
262 |
+
'wpseo_import',
|
263 |
+
'exported'
|
264 |
+
);
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
public static function callback_seo_page_wpseo_files() {
|
269 |
+
if ( wp_stream_filter_input( INPUT_POST, 'create_robots' ) ) {
|
270 |
+
$message = __( 'Tried creating robots.txt file', 'stream' );
|
271 |
+
} elseif ( wp_stream_filter_input( INPUT_POST, 'submitrobots' ) ) {
|
272 |
+
$message = __( 'Tried updating robots.txt file', 'stream' );
|
273 |
+
} elseif ( wp_stream_filter_input( INPUT_POST, 'submithtaccess' ) ) {
|
274 |
+
$message = __( 'Tried updating htaccess file', 'stream' );
|
275 |
+
}
|
276 |
+
|
277 |
+
if ( isset( $message ) ) {
|
278 |
+
self::log(
|
279 |
+
$message,
|
280 |
+
array(),
|
281 |
+
null,
|
282 |
+
'wpseo_files',
|
283 |
+
'updated'
|
284 |
+
);
|
285 |
+
}
|
286 |
+
}
|
287 |
+
|
288 |
+
public static function callback_added_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
|
289 |
+
self::meta( $object_id, $meta_key, $meta_value );
|
290 |
+
}
|
291 |
+
public static function callback_updated_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
|
292 |
+
self::meta( $object_id, $meta_key, $meta_value );
|
293 |
+
}
|
294 |
+
public static function callback_deleted_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
|
295 |
+
self::meta( $object_id, $meta_key, $meta_value );
|
296 |
+
}
|
297 |
+
|
298 |
+
private static function meta( $object_id, $meta_key, $meta_value ) {
|
299 |
+
$prefix = WPSEO_Meta::$meta_prefix;
|
300 |
+
|
301 |
+
WPSEO_Metabox::translate_meta_boxes();
|
302 |
+
|
303 |
+
if ( 0 !== strpos( $meta_key, $prefix ) ) {
|
304 |
+
return;
|
305 |
+
}
|
306 |
+
|
307 |
+
$key = str_replace( $prefix, '', $meta_key );
|
308 |
+
|
309 |
+
foreach ( WPSEO_Meta::$meta_fields as $tab => $fields ) {
|
310 |
+
if ( isset( $fields[ $key ] ) ) {
|
311 |
+
$field = $fields[ $key ];
|
312 |
+
break;
|
313 |
+
}
|
314 |
+
}
|
315 |
+
|
316 |
+
if ( ! isset( $field, $field['title'], $tab ) || '' === $field['title'] ) {
|
317 |
+
return;
|
318 |
+
}
|
319 |
+
|
320 |
+
$post = get_post( $object_id );
|
321 |
+
$post_type_label = get_post_type_labels( get_post_type_object( $post->post_type ) )->singular_name;
|
322 |
+
|
323 |
+
self::log(
|
324 |
+
sprintf(
|
325 |
+
__( 'Updated "%1$s" of "%2$s" %3$s', 'stream' ),
|
326 |
+
$field['title'],
|
327 |
+
$post->post_title,
|
328 |
+
$post_type_label
|
329 |
+
),
|
330 |
+
array(
|
331 |
+
'meta_key' => $meta_key,
|
332 |
+
'meta_value' => $meta_value,
|
333 |
+
'post_type' => $post->post_type,
|
334 |
+
),
|
335 |
+
$object_id,
|
336 |
+
'wpseo_meta',
|
337 |
+
'updated'
|
338 |
+
);
|
339 |
+
}
|
340 |
+
|
341 |
+
/**
|
342 |
+
* Override connector log for our own Settings / Actions
|
343 |
+
*
|
344 |
+
* @param array $data
|
345 |
+
*
|
346 |
+
* @return array|bool
|
347 |
+
*/
|
348 |
+
public static function log_override( $data ) {
|
349 |
+
if ( ! is_array( $data ) ) {
|
350 |
+
return $data;
|
351 |
+
}
|
352 |
+
|
353 |
+
global $pagenow;
|
354 |
+
|
355 |
+
if ( 'options.php' === $pagenow && 'settings' === $data['connector'] && wp_stream_filter_input( INPUT_POST, '_wp_http_referer' ) ) {
|
356 |
+
if ( ! isset( $data['args']['context'] ) || ! isset( self::$option_groups[ $data['args']['context'] ] ) ) {
|
357 |
+
return $data;
|
358 |
+
}
|
359 |
+
|
360 |
+
$page = preg_match( '#page=([^&]*)#', wp_stream_filter_input( INPUT_POST, '_wp_http_referer' ), $match ) ? $match[1] : '';
|
361 |
+
$labels = self::get_context_labels();
|
362 |
+
|
363 |
+
if ( ! isset( $labels[ $page ] ) ) {
|
364 |
+
return $data;
|
365 |
+
}
|
366 |
+
|
367 |
+
if ( ! ( $label = self::settings_labels( $data['args']['option_key'] ) ) ) {
|
368 |
+
$data['message'] = __( '%s settings updated', 'stream' );
|
369 |
+
$label = $labels[ $page ];
|
370 |
+
}
|
371 |
+
|
372 |
+
$data['args']['label'] = $label;
|
373 |
+
$data['args']['context'] = $page;
|
374 |
+
$data['context'] = $page;
|
375 |
+
$data['connector'] = self::$name;
|
376 |
+
}
|
377 |
+
|
378 |
+
return $data;
|
379 |
+
}
|
380 |
+
|
381 |
+
private static function settings_labels( $option ) {
|
382 |
+
$labels = array(
|
383 |
+
// wp-content/plugins/wordpress-seo/admin/pages/dashboard.php:
|
384 |
+
'yoast_tracking' => _x( 'Allow tracking of this WordPress install\'s anonymous data.', 'wordpress-seo', 'stream' ), # type = checkbox
|
385 |
+
'disableadvanced_meta' => _x( 'Disable the Advanced part of the WordPress SEO meta box', 'wordpress-seo', 'stream' ), # type = checkbox
|
386 |
+
'alexaverify' => _x( 'Alexa Verification ID', 'wordpress-seo', 'stream' ), # type = textinput
|
387 |
+
'msverify' => _x( 'Bing Webmaster Tools', 'wordpress-seo', 'stream' ), # type = textinput
|
388 |
+
'googleverify' => _x( 'Google Webmaster Tools', 'wordpress-seo', 'stream' ), # type = textinput
|
389 |
+
'pinterestverify' => _x( 'Pinterest', 'wordpress-seo', 'stream' ), # type = textinput
|
390 |
+
'yandexverify' => _x( 'Yandex Webmaster Tools', 'wordpress-seo', 'stream' ), # type = textinput
|
391 |
+
|
392 |
+
// wp-content/plugins/wordpress-seo/admin/pages/internal-links.php:
|
393 |
+
'breadcrumbs-enable' => _x( 'Enable Breadcrumbs', 'wordpress-seo', 'stream' ), # type = checkbox
|
394 |
+
'breadcrumbs-sep' => _x( 'Separator between breadcrumbs', 'wordpress-seo', 'stream' ), # type = textinput
|
395 |
+
'breadcrumbs-home' => _x( 'Anchor text for the Homepage', 'wordpress-seo', 'stream' ), # type = textinput
|
396 |
+
'breadcrumbs-prefix' => _x( 'Prefix for the breadcrumb path', 'wordpress-seo', 'stream' ), # type = textinput
|
397 |
+
'breadcrumbs-archiveprefix' => _x( 'Prefix for Archive breadcrumbs', 'wordpress-seo', 'stream' ), # type = textinput
|
398 |
+
'breadcrumbs-searchprefix' => _x( 'Prefix for Search Page breadcrumbs', 'wordpress-seo', 'stream' ), # type = textinput
|
399 |
+
'breadcrumbs-404crumb' => _x( 'Breadcrumb for 404 Page', 'wordpress-seo', 'stream' ), # type = textinput
|
400 |
+
'breadcrumbs-blog-remove' => _x( 'Remove Blog page from Breadcrumbs', 'wordpress-seo', 'stream' ), # type = checkbox
|
401 |
+
'breadcrumbs-boldlast' => _x( 'Bold the last page in the breadcrumb', 'wordpress-seo', 'stream' ), # type = checkbox
|
402 |
+
|
403 |
+
// wp-content/plugins/wordpress-seo/admin/pages/metas.php:
|
404 |
+
'forcerewritetitle' => _x( 'Force rewrite titles', 'wordpress-seo', 'stream' ), # type = checkbox
|
405 |
+
'noindex-subpages-wpseo' => _x( 'Noindex subpages of archives', 'wordpress-seo', 'stream' ), # type = checkbox
|
406 |
+
'usemetakeywords' => _x( 'Use <code>meta</code> keywords tag?', 'wordpress-seo', 'stream' ), # type = checkbox
|
407 |
+
'noodp' => _x( 'Add <code>noodp</code> meta robots tag sitewide', 'wordpress-seo', 'stream' ), # type = checkbox
|
408 |
+
'noydir' => _x( 'Add <code>noydir</code> meta robots tag sitewide', 'wordpress-seo', 'stream' ), # type = checkbox
|
409 |
+
'hide-rsdlink' => _x( 'Hide RSD Links', 'wordpress-seo', 'stream' ), # type = checkbox
|
410 |
+
'hide-wlwmanifest' => _x( 'Hide WLW Manifest Links', 'wordpress-seo', 'stream' ), # type = checkbox
|
411 |
+
'hide-shortlink' => _x( 'Hide Shortlink for posts', 'wordpress-seo', 'stream' ), # type = checkbox
|
412 |
+
'hide-feedlinks' => _x( 'Hide RSS Links', 'wordpress-seo', 'stream' ), # type = checkbox
|
413 |
+
'disable-author' => _x( 'Disable the author archives', 'wordpress-seo', 'stream' ), # type = checkbox
|
414 |
+
'disable-date' => _x( 'Disable the date-based archives', 'wordpress-seo', 'stream' ), # type = checkbox
|
415 |
+
|
416 |
+
// wp-content/plugins/wordpress-seo/admin/pages/network.php:
|
417 |
+
'access' => _x( 'Who should have access to the WordPress SEO settings', 'wordpress-seo', 'stream' ), # type = select
|
418 |
+
'defaultblog' => _x( 'New blogs get the SEO settings from this blog', 'wordpress-seo', 'stream' ), # type = textinput
|
419 |
+
'restoreblog' => _x( 'Blog ID', 'wordpress-seo', 'stream' ), # type = textinput
|
420 |
+
|
421 |
+
// wp-content/plugins/wordpress-seo/admin/pages/permalinks.php:
|
422 |
+
'stripcategorybase' => _x( 'Strip the category base (usually <code>/category/</code>) from the category URL.', 'wordpress-seo', 'stream' ), # type = checkbox
|
423 |
+
'trailingslash' => _x( 'Enforce a trailing slash on all category and tag URL\'s', 'wordpress-seo', 'stream' ), # type = checkbox
|
424 |
+
'cleanslugs' => _x( 'Remove stop words from slugs.', 'wordpress-seo', 'stream' ), # type = checkbox
|
425 |
+
'redirectattachment' => _x( 'Redirect attachment URL\'s to parent post URL.', 'wordpress-seo', 'stream' ), # type = checkbox
|
426 |
+
'cleanreplytocom' => _x( 'Remove the <code>?replytocom</code> variables.', 'wordpress-seo', 'stream' ), # type = checkbox
|
427 |
+
'cleanpermalinks' => _x( 'Redirect ugly URL\'s to clean permalinks. (Not recommended in many cases!)', 'wordpress-seo', 'stream' ), # type = checkbox
|
428 |
+
'force_transport' => _x( 'Force Transport', 'wordpress-seo', 'stream' ), # type = select
|
429 |
+
'cleanpermalink-googlesitesearch' => _x( 'Prevent cleaning out Google Site Search URL\'s.', 'wordpress-seo', 'stream' ), # type = checkbox
|
430 |
+
'cleanpermalink-googlecampaign' => _x( 'Prevent cleaning out Google Analytics Campaign & Google AdWords Parameters.', 'wordpress-seo', 'stream' ), # type = checkbox
|
431 |
+
'cleanpermalink-extravars' => _x( 'Other variables not to clean', 'wordpress-seo', 'stream' ), # type = textinput
|
432 |
+
|
433 |
+
// wp-content/plugins/wordpress-seo/admin/pages/social.php:
|
434 |
+
'opengraph' => _x( 'Add Open Graph meta data', 'wordpress-seo', 'stream' ), # type = checkbox
|
435 |
+
'facebook_site' => _x( 'Facebook Page URL', 'wordpress-seo', 'stream' ), # type = textinput
|
436 |
+
'og_frontpage_image' => _x( 'Image URL', 'wordpress-seo', 'stream' ), # type = textinput
|
437 |
+
'og_frontpage_desc' => _x( 'Description', 'wordpress-seo', 'stream' ), # type = textinput
|
438 |
+
'og_default_image' => _x( 'Image URL', 'wordpress-seo', 'stream' ), # type = textinput
|
439 |
+
'twitter' => _x( 'Add Twitter card meta data', 'wordpress-seo', 'stream' ), # type = checkbox
|
440 |
+
'twitter_site' => _x( 'Site Twitter Username', 'wordpress-seo', 'stream' ), # type = textinput
|
441 |
+
'twitter_card_type' => _x( 'The default card type to use', 'wordpress-seo', 'stream' ), # type = select
|
442 |
+
'googleplus' => _x( 'Add Google+ specific post meta data (excluding author metadata)', 'wordpress-seo', 'stream' ), # type = checkbox
|
443 |
+
'plus-publisher' => _x( 'Google Publisher Page', 'wordpress-seo', 'stream' ), # type = textinput
|
444 |
+
|
445 |
+
// wp-content/plugins/wordpress-seo/admin/pages/xml-sitemaps.php:
|
446 |
+
'enablexmlsitemap' => _x( 'Check this box to enable XML sitemap functionality.', 'wordpress-seo', 'stream' ), # type = checkbox
|
447 |
+
'disable_author_sitemap' => _x( 'Disable author/user sitemap', 'wordpress-seo', 'stream' ), # type = checkbox
|
448 |
+
'xml_ping_yahoo' => _x( 'Ping Yahoo!', 'wordpress-seo', 'stream' ), # type = checkbox
|
449 |
+
'xml_ping_ask' => _x( 'Ping Ask.com', 'wordpress-seo', 'stream' ), # type = checkbox
|
450 |
+
'entries-per-page' => _x( 'Max entries per sitemap page', 'wordpress-seo', 'stream' ), # type = textinput
|
451 |
+
|
452 |
+
// Added manually
|
453 |
+
'rssbefore' => _x( 'Content to put before each post in the feed', 'wordpress-seo', 'stream' ),
|
454 |
+
'rssafter' => _x( 'Content to put after each post', 'wordpress-seo', 'stream' ),
|
455 |
+
);
|
456 |
+
|
457 |
+
$ast_labels = array(
|
458 |
+
'title-' => _x( 'Title template', 'wordpress-seo', 'stream' ), # type = textinput
|
459 |
+
'metadesc-' => _x( 'Meta description template', 'wordpress-seo', 'stream' ), # type = textarea
|
460 |
+
'metakey-' => _x( 'Meta keywords template', 'wordpress-seo', 'stream' ), # type = textinput
|
461 |
+
'noindex-' => _x( 'Meta Robots', 'wordpress-seo', 'stream' ), # type = checkbox
|
462 |
+
'noauthorship-' => _x( 'Authorship', 'wordpress-seo', 'stream' ), # type = checkbox
|
463 |
+
'showdate-' => _x( 'Show date in snippet preview?', 'wordpress-seo', 'stream' ), # type = checkbox
|
464 |
+
'hideeditbox-' => _x( 'WordPress SEO Meta Box', 'wordpress-seo', 'stream' ), # type = checkbox
|
465 |
+
'bctitle-' => _x( 'Breadcrumbs Title', 'wordpress-seo', 'stream' ), # type = textinput
|
466 |
+
'post_types-' => _x( 'Post types', 'wordpress-seo', 'stream' ), # type = checkbox
|
467 |
+
'taxonomies-' => _x( 'Taxonomies', 'wordpress-seo', 'stream' ), # type = checkbox
|
468 |
+
);
|
469 |
+
|
470 |
+
if ( $option ) {
|
471 |
+
if ( isset( $labels[ $option ] ) ) {
|
472 |
+
return $labels[ $option ];
|
473 |
+
} else {
|
474 |
+
foreach ( $ast_labels as $key => $trans ) {
|
475 |
+
if ( 0 === strpos( $option, $key ) ) {
|
476 |
+
return $trans;
|
477 |
+
}
|
478 |
+
}
|
479 |
+
|
480 |
+
return false;
|
481 |
+
}
|
482 |
+
}
|
483 |
+
|
484 |
+
return $labels;
|
485 |
+
}
|
486 |
+
|
487 |
+
}
|
contributing.md
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Contributing to this project
|
2 |
+
|
3 |
+
Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
|
4 |
+
|
5 |
+
Following these guidelines will help us get back to you more quickly, and will show that you care about making Stream better just like we do. In return, we'll do our best to respond to your issue or pull request as soon as possible with the same respect.
|
6 |
+
|
7 |
+
|
8 |
+
## Use the issue tracker
|
9 |
+
|
10 |
+
The [issue tracker](https://github.com/wp-stream/stream/issues) is the preferred channel for [bug reports](#bugs), [features requests](#features) and [submitting pull requests](#pull-requests), but please respect the following restrictions:
|
11 |
+
|
12 |
+
* Support issues or usage questions that are not bugs should be posted on the [Plugin Support Forum](http://wordpress.org/support/plugin/stream).
|
13 |
+
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
|
14 |
+
|
15 |
+
|
16 |
+
<a name="bugs"></a>
|
17 |
+
## Bug reports
|
18 |
+
|
19 |
+
A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports with complete error messages, environment details and screenshots are extremely helpful — thank you!
|
20 |
+
|
21 |
+
Guidelines for bug reports:
|
22 |
+
|
23 |
+
1. **Check if the bug has already been fixed** — Someone may already be on top of it, so try to reproduce it using the latest from the `master` branch.
|
24 |
+
|
25 |
+
2. **Use the [GitHub issue search](https://github.com/wp-stream/stream/search?type=Issues)** — Someone might already know about it, so please check if the issue has already been reported.
|
26 |
+
|
27 |
+
3. **Isolate the problem** — The better you can determine exactly what behavior(s) cause the issue, the faster and more effectively it can be resolved. “I’m getting an error message.” is not a good bug report. A good bug report shouldn't leave others needing to contact you for more information.
|
28 |
+
|
29 |
+
Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) experience the problem? What outcome did you expect, and how did it differ from what you actually saw? All these details will help people to fix any potential bugs.
|
30 |
+
|
31 |
+
Example:
|
32 |
+
|
33 |
+
> Short and descriptive example bug report title
|
34 |
+
>
|
35 |
+
> A summary of the issue and the environment/browser in which it occurs. If
|
36 |
+
> suitable, include the steps required to reproduce the bug.
|
37 |
+
>
|
38 |
+
> 1. This is the first step
|
39 |
+
> 2. This is the second step
|
40 |
+
> 3. Further steps, etc.
|
41 |
+
>
|
42 |
+
> Any other information you want to share that is relevant to the issue being reported. This might include the lines of code that you have identified as causing the bug, and potential solutions (and your opinions on their merits).
|
43 |
+
|
44 |
+
**Note:** In an effort to keep open issues to a manageable number, we will close any issues that do not provide enough information for us to be able to work on a solution. You will be encouraged to provide the necessary details, after which we will reopen the issue.
|
45 |
+
|
46 |
+
<a name="features"></a>
|
47 |
+
## Feature requests
|
48 |
+
|
49 |
+
Feature requests are very welcome! But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible.
|
50 |
+
|
51 |
+
Building something great means choosing features carefully especially because it is much, much easier to add features than it is to take them away. Additions to Stream will be evaluated on a combination of scope (how well it fits into the project), maintenance burden and general usefulness to users.
|
52 |
+
|
53 |
+
<a name="pull-requests"></a>
|
54 |
+
## Pull requests
|
55 |
+
|
56 |
+
Good pull requests — patches, improvements, new features — are a fantastic help.
|
57 |
+
They should remain focused in scope and avoid containing unrelated commits.
|
58 |
+
|
59 |
+
**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project. You can solicit feedback and opinions in an open enhancement issue, or [create a new one](https://github.com/wp-stream/stream/issues/new).
|
60 |
+
|
61 |
+
Please use the [git flow for pull requests](#git-flow) and follow [WordPress Coding Standards](http://make.wordpress.org/core/handbook/coding-standards/) before submitting your work. Adhering to these guidelines is the best way to get your work included in Stream.
|
62 |
+
|
63 |
+
<a name="git-flow"></a>
|
64 |
+
#### Git Flow for pull requests
|
65 |
+
|
66 |
+
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes:
|
67 |
+
|
68 |
+
```bash
|
69 |
+
# Clone your fork of the repo into the current directory
|
70 |
+
git clone git@github.com:<YOUR_USERNAME>/stream.git
|
71 |
+
# Navigate to the newly cloned directory
|
72 |
+
cd stream
|
73 |
+
# Assign the original repo to a remote called "upstream"
|
74 |
+
git remote add upstream https://github.com/wp-stream/stream
|
75 |
+
```
|
76 |
+
|
77 |
+
2. If you cloned a while ago, get the latest changes from upstream:
|
78 |
+
|
79 |
+
```bash
|
80 |
+
git checkout master
|
81 |
+
git pull upstream master
|
82 |
+
```
|
83 |
+
|
84 |
+
3. Create a new topic branch (off the `master` branch) to contain your feature, change, or fix:
|
85 |
+
|
86 |
+
```bash
|
87 |
+
git checkout -b <topic-branch-name>
|
88 |
+
```
|
89 |
+
|
90 |
+
4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public.
|
91 |
+
|
92 |
+
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
93 |
+
|
94 |
+
```bash
|
95 |
+
git pull [--rebase] upstream master
|
96 |
+
```
|
97 |
+
|
98 |
+
6. Push your topic branch up to your fork:
|
99 |
+
|
100 |
+
```bash
|
101 |
+
git push origin <topic-branch-name>
|
102 |
+
```
|
103 |
+
|
104 |
+
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) (with a clear title and description) to the `develop` branch.
|
105 |
+
|
106 |
+
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to license your work under the [GPL v2 license](http://www.gnu.org/licenses/gpl-2.0.html).
|
extensions/notifications/class-wp-stream-notifications.php
ADDED
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Hold Stream Notifications instance
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $instance;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Screen ID for my admin page
|
14 |
+
* @var string
|
15 |
+
*/
|
16 |
+
public static $screen_id;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Holds admin notices messages
|
20 |
+
*
|
21 |
+
* @var array
|
22 |
+
*/
|
23 |
+
public static $messages = array();
|
24 |
+
|
25 |
+
/*
|
26 |
+
* List of registered adapters
|
27 |
+
* @var array
|
28 |
+
*/
|
29 |
+
public static $adapters = array();
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Matcher object
|
33 |
+
*
|
34 |
+
* @var WP_Stream_Notifications_Matcher
|
35 |
+
*/
|
36 |
+
public $matcher;
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Page slug for notifications list table screen
|
40 |
+
*
|
41 |
+
* @const string
|
42 |
+
*/
|
43 |
+
const NOTIFICATIONS_PAGE_SLUG = 'wp_stream_notifications';
|
44 |
+
// Todo: We should probably check whether the current user has caps to
|
45 |
+
// view and edit the notifications as this can differ from caps to Stream.
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Capability for the Notifications to be viewed
|
49 |
+
*
|
50 |
+
* @const string
|
51 |
+
*/
|
52 |
+
const VIEW_CAP = 'view_stream_notifications';
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Return active instance of this class, create one if it doesn't exist
|
56 |
+
*
|
57 |
+
* @return WP_Stream_Notifications
|
58 |
+
*/
|
59 |
+
public static function get_instance() {
|
60 |
+
if ( empty( self::$instance ) ) {
|
61 |
+
self::$instance = new self();
|
62 |
+
}
|
63 |
+
return self::$instance;
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Class constructor
|
68 |
+
*/
|
69 |
+
private function __construct() {
|
70 |
+
define( 'WP_STREAM_NOTIFICATIONS_DIR', WP_STREAM_EXTENSIONS_DIR . 'notifications/' ); // Has trailing slash
|
71 |
+
define( 'WP_STREAM_NOTIFICATIONS_URL', WP_STREAM_URL . 'extensions/notifications/' ); // Has trailing slash
|
72 |
+
define( 'WP_STREAM_NOTIFICATIONS_INC_DIR', WP_STREAM_NOTIFICATIONS_DIR . 'includes/' ); // Has trailing slash
|
73 |
+
|
74 |
+
if ( ! apply_filters( 'wp_stream_notifications_load', true ) ) {
|
75 |
+
return;
|
76 |
+
}
|
77 |
+
|
78 |
+
add_action( 'init', array( $this, 'load' ) );
|
79 |
+
|
80 |
+
// Register post type
|
81 |
+
require_once WP_STREAM_NOTIFICATIONS_INC_DIR . 'class-wp-stream-notifications-post-type.php';
|
82 |
+
|
83 |
+
WP_Stream_Notifications_Post_Type::get_instance();
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Load our classes, actions/filters, only if our big brother is activated.
|
88 |
+
* GO GO GO!
|
89 |
+
*
|
90 |
+
* @return void
|
91 |
+
*/
|
92 |
+
public function load() {
|
93 |
+
// Register new submenu
|
94 |
+
if ( ! apply_filters( 'wp_stream_notifications_disallow_site_access', false ) && ! WP_Stream_Admin::$disable_access && ( WP_Stream::is_connected() || WP_Stream::is_development_mode() ) ) {
|
95 |
+
add_action( 'admin_menu', array( $this, 'register_menu' ), 11 );
|
96 |
+
}
|
97 |
+
|
98 |
+
// Load settings, enabling extensions to hook in
|
99 |
+
require_once WP_STREAM_NOTIFICATIONS_INC_DIR . 'class-wp-stream-notifications-settings.php';
|
100 |
+
add_action( 'init', array( 'WP_Stream_Notifications_Settings', 'load' ), 9 );
|
101 |
+
|
102 |
+
if ( WP_Stream::$api->is_restricted() ) {
|
103 |
+
add_action( 'in_admin_header', array( __CLASS__, 'in_admin_header' ) );
|
104 |
+
return;
|
105 |
+
}
|
106 |
+
|
107 |
+
// Include all adapters
|
108 |
+
include_once WP_STREAM_NOTIFICATIONS_INC_DIR . 'class-wp-stream-notifications-adapter.php';
|
109 |
+
$adapters = array( 'email', 'push', 'sms' );
|
110 |
+
|
111 |
+
foreach ( $adapters as $adapter ) {
|
112 |
+
include WP_STREAM_NOTIFICATIONS_INC_DIR . 'adapters/class-wp-stream-notifications-adapter-' . $adapter . '.php';
|
113 |
+
}
|
114 |
+
|
115 |
+
// Load Matcher
|
116 |
+
include_once WP_STREAM_NOTIFICATIONS_INC_DIR . 'class-wp-stream-notifications-matcher.php';
|
117 |
+
$this->matcher = new WP_Stream_Notifications_Matcher();
|
118 |
+
}
|
119 |
+
|
120 |
+
public static function in_admin_header() {
|
121 |
+
global $typenow;
|
122 |
+
|
123 |
+
if ( WP_Stream_Notifications_Post_Type::POSTTYPE !== $typenow ) {
|
124 |
+
return;
|
125 |
+
}
|
126 |
+
?>
|
127 |
+
<div class="stream-example">
|
128 |
+
<div class="stream-example-modal">
|
129 |
+
<h1><i class="dashicons dashicons-admin-comments"></i> <?php _e( 'Stream Notifications', 'stream' ) ?></h1>
|
130 |
+
<p><?php _e( 'Get notified instantly when important changes are made on your site.', 'stream' ) ?></p>
|
131 |
+
<ul>
|
132 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Create notification rules quickly and easily', 'stream' ) ?></li>
|
133 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Smart and powerful trigger matching', 'stream' ) ?></li>
|
134 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Fully customized e-mail and SMS alerts', 'stream' ) ?></li>
|
135 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Push alerts to your smartphone or tablet', 'stream' ) ?></li>
|
136 |
+
</ul>
|
137 |
+
<a href="<?php echo esc_url( WP_Stream_Admin::account_url( sprintf( 'upgrade?site_uuid=%s', WP_Stream::$api->site_uuid ) ) ); ?>" class="button button-primary button-large"><?php _e( 'Upgrade to Pro', 'stream' ) ?></a>
|
138 |
+
</div>
|
139 |
+
</div>
|
140 |
+
<?php
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Register Notification menu under Stream's main one
|
145 |
+
*
|
146 |
+
* @action admin_menu
|
147 |
+
* @return void
|
148 |
+
*/
|
149 |
+
public function register_menu() {
|
150 |
+
self::$screen_id = add_submenu_page(
|
151 |
+
WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
152 |
+
__( 'Notifications', 'stream' ),
|
153 |
+
__( 'Notifications', 'stream' ),
|
154 |
+
self::VIEW_CAP,
|
155 |
+
sprintf( 'edit.php?post_type=%s', WP_Stream_Notifications_Post_Type::POSTTYPE )
|
156 |
+
);
|
157 |
+
}
|
158 |
+
|
159 |
+
public static function register_adapter( $adapter, $name, $title ) {
|
160 |
+
self::$adapters[ $name ] = array(
|
161 |
+
'title' => $title,
|
162 |
+
'class' => $adapter,
|
163 |
+
);
|
164 |
+
}
|
165 |
+
|
166 |
+
/**
|
167 |
+
* Display all messages on admin board
|
168 |
+
*
|
169 |
+
* @return void
|
170 |
+
*/
|
171 |
+
public static function admin_notices() {
|
172 |
+
foreach ( self::$messages as $message ) {
|
173 |
+
echo wp_kses_post( $message );
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Plugin activation routine
|
179 |
+
* @return void
|
180 |
+
*/
|
181 |
+
public function on_activation() {
|
182 |
+
// Add sample rule
|
183 |
+
$args = array(
|
184 |
+
'post_type' => WP_Stream_Notifications_Post_Type::POSTTYPE,
|
185 |
+
'post_status' => 'any',
|
186 |
+
'posts_per_page' => 1,
|
187 |
+
);
|
188 |
+
|
189 |
+
if ( ! get_posts( $args ) ) {
|
190 |
+
$this->add_sample_rule();
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Add a sample rule, used upon activation
|
196 |
+
*
|
197 |
+
*/
|
198 |
+
public function add_sample_rule() {
|
199 |
+
$postarr = array(
|
200 |
+
'post_title' => __( 'Sample Rule', 'stream' ),
|
201 |
+
'post_status' => 'draft',
|
202 |
+
'post_type' => WP_Stream_Notifications_Post_Type::POSTTYPE,
|
203 |
+
);
|
204 |
+
|
205 |
+
$meta = array(
|
206 |
+
'triggers' => array(
|
207 |
+
array(
|
208 |
+
'group' => 0,
|
209 |
+
'relation' => 'and',
|
210 |
+
'type' => 'author_role',
|
211 |
+
'operator' => '!=',
|
212 |
+
'value' => 'administrator',
|
213 |
+
),
|
214 |
+
array(
|
215 |
+
'group' => 0,
|
216 |
+
'relation' => 'and',
|
217 |
+
'type' => 'action',
|
218 |
+
'operator' => '=',
|
219 |
+
'value' => 'updated',
|
220 |
+
),
|
221 |
+
array(
|
222 |
+
'group' => 1,
|
223 |
+
'relation' => 'and',
|
224 |
+
'type' => 'author_role',
|
225 |
+
'operator' => '=',
|
226 |
+
'value' => 'administrator',
|
227 |
+
),
|
228 |
+
array(
|
229 |
+
'group' => 1,
|
230 |
+
'relation' => 'and',
|
231 |
+
'type' => 'connector',
|
232 |
+
'operator' => '=',
|
233 |
+
'value' => 'widgets',
|
234 |
+
),
|
235 |
+
array(
|
236 |
+
'group' => 1,
|
237 |
+
'relation' => 'and',
|
238 |
+
'type' => 'action',
|
239 |
+
'operator' => '=',
|
240 |
+
'value' => 'sorted',
|
241 |
+
),
|
242 |
+
),
|
243 |
+
'groups' => array(
|
244 |
+
1 => array(
|
245 |
+
'group' => 0,
|
246 |
+
'relation' => 'or',
|
247 |
+
),
|
248 |
+
),
|
249 |
+
'alerts' => array(
|
250 |
+
array(
|
251 |
+
'type' => 'email',
|
252 |
+
'users' => '1',
|
253 |
+
'emails' => '',
|
254 |
+
'subject' => sprintf( __( '[Site Activity Alert] %s', 'stream' ), get_bloginfo( 'name' ) ),
|
255 |
+
'message' => sprintf( __( 'The following just happened on your site: %s by %s Date of action: %s', 'stream' ), "\r\n\r\n{summary}", "{author.display_name}\r\n\r\n", '{created}' )
|
256 |
+
),
|
257 |
+
),
|
258 |
+
);
|
259 |
+
|
260 |
+
$post_id = wp_insert_post( $postarr );
|
261 |
+
|
262 |
+
if ( is_a( $post_id, 'WP_Error' ) ) {
|
263 |
+
return $post_id;
|
264 |
+
}
|
265 |
+
|
266 |
+
foreach ( $meta as $key => $val ) {
|
267 |
+
update_post_meta( $post_id, $key, $val );
|
268 |
+
}
|
269 |
+
}
|
270 |
+
|
271 |
+
}
|
272 |
+
|
273 |
+
$GLOBALS['wp_stream_notifications'] = WP_Stream_Notifications::get_instance();
|
extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-email.php
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications_Adapter_Email extends WP_Stream_Notifications_Adapter {
|
4 |
+
|
5 |
+
public static function register( $title = '' ) {
|
6 |
+
parent::register( __( 'Email', 'stream' ) );
|
7 |
+
}
|
8 |
+
|
9 |
+
public static function fields() {
|
10 |
+
return array(
|
11 |
+
'users' => array(
|
12 |
+
'title' => __( 'Send to Users', 'stream' ),
|
13 |
+
'type' => 'hidden',
|
14 |
+
'multiple' => true,
|
15 |
+
'ajax' => true,
|
16 |
+
'key' => 'author',
|
17 |
+
'hint' => __( 'Alert specific users via email.', 'stream' ),
|
18 |
+
),
|
19 |
+
'emails' => array(
|
20 |
+
'title' => __( 'Send to Emails', 'stream' ),
|
21 |
+
'type' => 'text',
|
22 |
+
'tags' => true,
|
23 |
+
'hint' => __( 'Alert any arbitrary email address not tied to a specific user.', 'stream' ),
|
24 |
+
),
|
25 |
+
'subject' => array(
|
26 |
+
'title' => __( 'Subject', 'stream' ),
|
27 |
+
'type' => 'text',
|
28 |
+
'hint' => __( 'Data tags are allowed.', 'stream' ),
|
29 |
+
),
|
30 |
+
'message' => array(
|
31 |
+
'title' => __( 'Message', 'stream' ),
|
32 |
+
'type' => 'textarea',
|
33 |
+
'hint' => __( 'HTML and data tags are allowed.', 'stream' ),
|
34 |
+
),
|
35 |
+
);
|
36 |
+
}
|
37 |
+
|
38 |
+
public function send( $log ) {
|
39 |
+
$users = $this->params['users'];
|
40 |
+
$user_emails = array();
|
41 |
+
if ( $users ) {
|
42 |
+
$user_query = new WP_User_Query(
|
43 |
+
array(
|
44 |
+
'include' => $users,
|
45 |
+
'fields' => array( 'user_email' ),
|
46 |
+
)
|
47 |
+
);
|
48 |
+
$user_emails = wp_list_pluck( $user_query->results, 'user_email' );
|
49 |
+
}
|
50 |
+
$emails = $this->replace( $this->params['emails'], $log );
|
51 |
+
$emails = explode( ',', $emails );
|
52 |
+
if ( ! empty( $user_emails ) ) {
|
53 |
+
$emails = array_merge( $emails, $user_emails );
|
54 |
+
}
|
55 |
+
$emails = array_filter( $emails );
|
56 |
+
$subject = $this->replace( $this->params['subject'], $log );
|
57 |
+
$message = $this->replace( $this->params['message'], $log );
|
58 |
+
wp_mail( $emails, $subject, $message );
|
59 |
+
}
|
60 |
+
|
61 |
+
}
|
62 |
+
|
63 |
+
WP_Stream_Notifications_Adapter_Email::register();
|
extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-push.php
ADDED
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications_Adapter_Push extends WP_Stream_Notifications_Adapter {
|
4 |
+
|
5 |
+
const PUSHOVER_OPTION_NAME = 'ckpn_pushover_notifications_settings';
|
6 |
+
|
7 |
+
public static function register( $title = '' ) {
|
8 |
+
parent::register( __( 'Push', 'stream' ) );
|
9 |
+
add_filter( 'wp_stream_serialized_labels', array( __CLASS__, 'pushover_key_labels' ) );
|
10 |
+
}
|
11 |
+
|
12 |
+
public static function get_application_key() {
|
13 |
+
$options = get_option( self::PUSHOVER_OPTION_NAME, array() );
|
14 |
+
$result = ( isset( $options['application_key'] ) && ! empty( $options['application_key'] ) ) ? $options['application_key'] : false;
|
15 |
+
|
16 |
+
return $result;
|
17 |
+
}
|
18 |
+
|
19 |
+
public static function fields() {
|
20 |
+
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
21 |
+
|
22 |
+
$plugin_path = defined( 'CKPN_FILE' ) ? CKPN_FILE : null;
|
23 |
+
$is_installed = ( $plugin_path && defined( 'WP_PLUGIN_DIR' ) && file_exists( trailingslashit( WP_PLUGIN_DIR ) . $plugin_path ) );
|
24 |
+
|
25 |
+
if ( ! $is_installed ) {
|
26 |
+
$fields = array(
|
27 |
+
'error' => array(
|
28 |
+
'title' => __( 'Missing Required Plugin', 'stream' ),
|
29 |
+
'type' => 'error',
|
30 |
+
'message' => sprintf(
|
31 |
+
__( 'Please install and activate the %1$s plugin to enable push alerts.', 'stream' ),
|
32 |
+
sprintf(
|
33 |
+
'<a href="%1$s" target="_blank">%2$s</a>',
|
34 |
+
esc_url( 'http://wordpress.org/plugins/pushover-notifications/' ),
|
35 |
+
__( 'Pushover Notifications', 'stream' )
|
36 |
+
)
|
37 |
+
),
|
38 |
+
),
|
39 |
+
);
|
40 |
+
} elseif ( ! is_plugin_active( $plugin_path ) ) {
|
41 |
+
$fields = array(
|
42 |
+
'error' => array(
|
43 |
+
'title' => __( 'Required Plugin Not Activated', 'stream' ),
|
44 |
+
'type' => 'error',
|
45 |
+
'message' => sprintf(
|
46 |
+
__( 'Please activate the %1$s plugin to enable push alerts.', 'stream' ),
|
47 |
+
sprintf(
|
48 |
+
'<a href="%1$s">%2$s</a>',
|
49 |
+
self_admin_url( 'plugins.php' ),
|
50 |
+
__( 'Pushover Notifications', 'stream' )
|
51 |
+
)
|
52 |
+
),
|
53 |
+
),
|
54 |
+
);
|
55 |
+
} elseif ( false !== self::get_application_key() ) {
|
56 |
+
$fields = array(
|
57 |
+
'users' => array(
|
58 |
+
'title' => __( 'Send to Users', 'stream' ),
|
59 |
+
'type' => 'hidden',
|
60 |
+
'multiple' => true,
|
61 |
+
'ajax' => true,
|
62 |
+
'key' => 'author',
|
63 |
+
'args' => array(
|
64 |
+
'push' => true,
|
65 |
+
),
|
66 |
+
'hint' => array(
|
67 |
+
// hint 1
|
68 |
+
__( 'Alert specific users via push.', 'stream' ),
|
69 |
+
|
70 |
+
// hint 2
|
71 |
+
sprintf(
|
72 |
+
__( 'Only those users with a %s in their profile can be selected.', 'stream' ),
|
73 |
+
sprintf(
|
74 |
+
'<a href="%s" target="_blank">%s</a>',
|
75 |
+
self_admin_url( 'profile.php#wp-stream-highlight:ckpn_user_key' ),
|
76 |
+
__( 'Pushover User Key', 'stream' )
|
77 |
+
)
|
78 |
+
),
|
79 |
+
),
|
80 |
+
),
|
81 |
+
'subject' => array(
|
82 |
+
'title' => __( 'Subject', 'stream' ),
|
83 |
+
'type' => 'text',
|
84 |
+
'hint' => __( 'Data tags are allowed.', 'stream' ),
|
85 |
+
),
|
86 |
+
'message' => array(
|
87 |
+
'title' => __( 'Message', 'stream' ),
|
88 |
+
'type' => 'textarea',
|
89 |
+
'hint' => __( 'Data tags are allowed.', 'stream' ),
|
90 |
+
),
|
91 |
+
);
|
92 |
+
} else {
|
93 |
+
$fields = array(
|
94 |
+
'error' => array(
|
95 |
+
'title' => __( 'Application key is missing', 'stream' ),
|
96 |
+
'type' => 'error',
|
97 |
+
'message' => sprintf(
|
98 |
+
__( 'Please provide your Application key on %1$s.', 'stream' ),
|
99 |
+
sprintf(
|
100 |
+
'<a href="%1$s">%2$s</a>',
|
101 |
+
self_admin_url( 'options-general.php?page=pushover-notifications' ),
|
102 |
+
__( 'Pushover Notifications settings page', 'stream' )
|
103 |
+
)
|
104 |
+
),
|
105 |
+
),
|
106 |
+
);
|
107 |
+
}
|
108 |
+
|
109 |
+
return $fields;
|
110 |
+
}
|
111 |
+
|
112 |
+
public function send( $log ) {
|
113 |
+
$application_key = self::get_application_key();
|
114 |
+
|
115 |
+
if ( false === $application_key ) {
|
116 |
+
return false;
|
117 |
+
}
|
118 |
+
|
119 |
+
if ( ! empty( $this->params['users'] ) ) {
|
120 |
+
$users_ids = explode( ',', $this->params['users'] );
|
121 |
+
$users = get_users( array(
|
122 |
+
'include' => $users_ids,
|
123 |
+
'fields' => 'ID',
|
124 |
+
'meta_key' => 'ckpn_user_key',
|
125 |
+
) );
|
126 |
+
$users_pushover_keys = array_map(
|
127 |
+
function( $user_id ) {
|
128 |
+
return get_user_meta( $user_id, 'ckpn_user_key', true );
|
129 |
+
},
|
130 |
+
$users
|
131 |
+
);
|
132 |
+
}
|
133 |
+
|
134 |
+
$subject = isset( $this->params['subject'] ) ? $this->replace( $this->params['subject'], $log ) : null;
|
135 |
+
$message = isset( $this->params['message'] ) ? $this->replace( $this->params['message'], $log ) : null;
|
136 |
+
|
137 |
+
$post_fields = array(
|
138 |
+
'token' => $application_key,
|
139 |
+
'message' => $message,
|
140 |
+
'title' => $subject,
|
141 |
+
);
|
142 |
+
|
143 |
+
$connection = curl_init();
|
144 |
+
|
145 |
+
if ( ! isset( $users_pushover_keys ) || ! $users_pushover_keys ) {
|
146 |
+
return false;
|
147 |
+
}
|
148 |
+
|
149 |
+
foreach ( $users_pushover_keys as $key ) {
|
150 |
+
$post_fields['user'] = $key;
|
151 |
+
curl_setopt_array(
|
152 |
+
$connection,
|
153 |
+
array(
|
154 |
+
CURLOPT_URL => 'https://api.pushover.net/1/messages.json',
|
155 |
+
CURLOPT_POST => true,
|
156 |
+
CURLOPT_RETURNTRANSFER => 1,
|
157 |
+
CURLOPT_POSTFIELDS => http_build_query( $post_fields ),
|
158 |
+
)
|
159 |
+
);
|
160 |
+
$response = curl_exec( $connection );
|
161 |
+
}
|
162 |
+
curl_close( $connection );
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* @filter wp_stream_serialized_labels
|
167 |
+
*/
|
168 |
+
public static function pushover_key_labels( $labels ) {
|
169 |
+
$labels[ self::PUSHOVER_OPTION_NAME ] = array(
|
170 |
+
'application_key' => __( 'Application API Token/Key', 'stream' ),
|
171 |
+
'api_key' => __( 'Your User Key', 'stream' ),
|
172 |
+
'new_user' => __( 'New Users', 'stream' ),
|
173 |
+
'new_post' => __( 'New Posts are Published', 'stream' ),
|
174 |
+
'new_post_roles' => __( 'Roles to Notify', 'stream' ),
|
175 |
+
'new_comment' => __( 'New Comments', 'stream' ),
|
176 |
+
'notify_authors' => __( 'Notify the Post Author (for multi-author blogs)', 'stream' ),
|
177 |
+
'password_reset' => __( 'Notify users when password resets are requested for their accounts', 'stream' ),
|
178 |
+
'core_update' => __( 'WordPress Core Update is Available', 'stream' ),
|
179 |
+
'plugin_updates' => __( 'Plugin & Theme Updates are Available', 'stream' ),
|
180 |
+
'multiple_keys' => __( 'Use Multiple Application Keys', 'stream' ),
|
181 |
+
'sslverify' => __( 'Verify SSL from api.pushover.net', 'stream' ),
|
182 |
+
'logging' => __( 'Enable Logging', 'stream' ),
|
183 |
+
);
|
184 |
+
|
185 |
+
return $labels;
|
186 |
+
}
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
WP_Stream_Notifications_Adapter_Push::register();
|
extensions/notifications/includes/adapters/class-wp-stream-notifications-adapter-sms.php
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notification_Adapter_SMS extends WP_Stream_Notifications_Adapter {
|
4 |
+
|
5 |
+
public static function register( $title = '' ) {
|
6 |
+
parent::register( __( 'SMS', 'stream' ) );
|
7 |
+
}
|
8 |
+
|
9 |
+
public static function fields() {
|
10 |
+
return array(
|
11 |
+
'mobile_number' => array(
|
12 |
+
'title' => __( 'Send to Mobile Number', 'stream' ),
|
13 |
+
'type' => 'text',
|
14 |
+
'multiple' => false,
|
15 |
+
'tags' => true,
|
16 |
+
'hint' => __( 'Enter mobile numbers without dashes (ex: 8885550000)', 'stream' ),
|
17 |
+
),
|
18 |
+
'carrier' => array(
|
19 |
+
'title' => __( 'Carrier', 'stream' ),
|
20 |
+
'type' => 'select',
|
21 |
+
'hint' => __( 'Select your mobile service provider.', 'stream' ),
|
22 |
+
'ajax' => false,
|
23 |
+
'options' => array(
|
24 |
+
'@sms.3rivers.net' => esc_html__( '3 River Wireless', 'stream' ),
|
25 |
+
'@paging.acswireless.com' => esc_html__( 'ACS Wireless', 'stream' ),
|
26 |
+
'@message.alltel.com' => esc_html__( 'Alltel', 'stream' ),
|
27 |
+
'@txt.att.net' => esc_html__( 'AT&T, Cingular, Net10 or Tracfone', 'stream' ),
|
28 |
+
'@bellmobility.ca' => esc_html__( 'Bell Canada', 'stream' ),
|
29 |
+
'@txt.bell.ca' => esc_html__( 'Bell Mobility (Canada), Presidents Choice or Solo Mobile', 'stream' ),
|
30 |
+
'@txt.bellmobility.ca' => esc_html__( 'Bell Mobility', 'stream' ),
|
31 |
+
'@blueskyfrog.com' => esc_html__( 'Blue Sky Frog', 'stream' ),
|
32 |
+
'@sms.bluecell.com' => esc_html__( 'Bluegrass Cellular', 'stream' ),
|
33 |
+
'@myboostmobile.com' => esc_html__( 'Boost Mobile', 'stream' ),
|
34 |
+
'@bplmobile.com' => esc_html__( 'BPL Mobile', 'stream' ),
|
35 |
+
'@cwwsms.com' => esc_html__( 'Carolina West Wireless', 'stream' ),
|
36 |
+
'@mobile.celloneusa.com' => esc_html__( 'Cellular One', 'stream' ),
|
37 |
+
'@csouth1.com' => esc_html__( 'Cellular South', 'stream' ),
|
38 |
+
'@messaging.centurytel.net' => esc_html__( 'CenturyTel', 'stream' ),
|
39 |
+
'@msg.clearnet.com' => esc_html__( 'Clearnet', 'stream' ),
|
40 |
+
'@comcastpcs.textmsg.com' => esc_html__( 'Comcast', 'stream' ),
|
41 |
+
'@corrwireless.net' => esc_html__( 'Corr Wireless Communications', 'stream' ),
|
42 |
+
'@sms.mycricket.com' => esc_html__( 'Cricket', 'stream' ),
|
43 |
+
'@mobile.dobson.net' => esc_html__( 'Dobson', 'stream' ),
|
44 |
+
'@sms.edgewireless.com' => esc_html__( 'Edge Wireless', 'stream' ),
|
45 |
+
'@fido.ca' => esc_html__( 'Fido', 'stream' ),
|
46 |
+
'@sms.goldentele.com' => esc_html__( 'Golden Telecom', 'stream' ),
|
47 |
+
'@text.houstoncellular.net' => esc_html__( 'Houston Cellular', 'stream' ),
|
48 |
+
'@ideacellular.net' => esc_html__( 'Idea Cellular', 'stream' ),
|
49 |
+
'@ivctext.com' => esc_html__( 'Illinois Valley Cellular', 'stream' ),
|
50 |
+
'@inlandlink.com' => esc_html__( 'Inland Cellular Telephone', 'stream' ),
|
51 |
+
'@pagemci.com' => esc_html__( 'MCI', 'stream' ),
|
52 |
+
'@page.metrocall.com' => esc_html__( 'Metrocall', 'stream' ),
|
53 |
+
'@my2way.com' => esc_html__( 'Metrocall 2-way', 'stream' ),
|
54 |
+
'@mymetropcs.com' => esc_html__( 'Metro PCS', 'stream' ),
|
55 |
+
'@clearlydigital.com' => esc_html__( 'Midwest Wireless', 'stream' ),
|
56 |
+
'@mobilecomm.net' => esc_html__( 'Mobilcomm', 'stream' ),
|
57 |
+
'@text.mtsmobility.com' => esc_html__( 'MTS', 'stream' ),
|
58 |
+
'@messaging.nextel.com' => esc_html__( 'Nextel', 'stream' ),
|
59 |
+
'@onlinebeep.net' => esc_html__( 'OnlineBeep', 'stream' ),
|
60 |
+
'@pcsone.net' => esc_html__( 'PCS One', 'stream' ),
|
61 |
+
'@sms.pscel.com' => esc_html__( 'Public Service Cellular', 'stream' ),
|
62 |
+
'@qwestmp.com' => esc_html__( 'Qwest', 'stream' ),
|
63 |
+
'@pcs.rogers.com' => esc_html__( 'Rogers AT&T Wireless and Rogers Canada', 'stream' ),
|
64 |
+
'@satellink.net' => esc_html__( 'Satellink', 'stream' ),
|
65 |
+
'@messaging.sprintpcs.com' => esc_html__( 'Sprint or Helio', 'stream' ),
|
66 |
+
'@tms.suncom.com' => esc_html__( 'Suncom and Triton', 'stream' ),
|
67 |
+
'@mobile.surewest.com' => esc_html__( 'Surewest Communications', 'stream' ),
|
68 |
+
'@tmomail.net' => esc_html__( 'T-Mobile', 'stream' ),
|
69 |
+
'@msg.telus.com' => esc_html__( 'Telus', 'stream' ),
|
70 |
+
'@utext.com' => esc_html__( 'Unicel', 'stream' ),
|
71 |
+
'@email.uscc.net' => esc_html__( 'US Cellular', 'stream' ),
|
72 |
+
'@uswestdatamail.com' => esc_html__( 'US West', 'stream' ),
|
73 |
+
'@vtext.com' => esc_html__( 'Verizon or Straight Talk', 'stream' ),
|
74 |
+
'@vmobl.com' => esc_html__( 'Virgin Mobile', 'stream' ),
|
75 |
+
'@vmobile.ca' => esc_html__( 'Virgin Mobile Canada', 'stream' ),
|
76 |
+
'@sms.wcc.net' => esc_html__( 'West Central Wireless', 'stream' ),
|
77 |
+
'@cellularonewest.com' => esc_html__( 'Western Wireless', 'stream' ),
|
78 |
+
),
|
79 |
+
),
|
80 |
+
'message' => array(
|
81 |
+
'title' => __( 'Message', 'stream' ),
|
82 |
+
'type' => 'textarea',
|
83 |
+
'hint' => __( 'Data tags are allowed. HTML is not allowed.', 'stream' ),
|
84 |
+
),
|
85 |
+
);
|
86 |
+
}
|
87 |
+
|
88 |
+
public function send( $log ) {
|
89 |
+
$number = preg_replace( '/\D/', '', $this->params['mobile_number'] ); // Removes all non-numeric characters
|
90 |
+
$to = sanitize_email( $number . $this->params['carrier'] );
|
91 |
+
$message = $this->replace( strip_tags( $this->params['message'] ), $log );
|
92 |
+
|
93 |
+
wp_mail( $to, null, $message );
|
94 |
+
}
|
95 |
+
|
96 |
+
}
|
97 |
+
|
98 |
+
WP_Stream_Notification_Adapter_SMS::register();
|
extensions/notifications/includes/class-wp-stream-notifications-adapter.php
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
abstract class WP_Stream_Notifications_Adapter {
|
4 |
+
|
5 |
+
public $params = array();
|
6 |
+
|
7 |
+
public static function register( $title ) {
|
8 |
+
$class = get_called_class();
|
9 |
+
$name = strtolower( str_replace( 'WP_Stream_Notifications_Adapter_', '', $class ) );
|
10 |
+
WP_Stream_Notifications::register_adapter( $class, $name, $title );
|
11 |
+
}
|
12 |
+
|
13 |
+
public static function fields() {
|
14 |
+
return array();
|
15 |
+
}
|
16 |
+
|
17 |
+
public static function hints() {
|
18 |
+
return '';
|
19 |
+
}
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Replace placeholders in alert[field]s with proper info from the log
|
23 |
+
* @param string $haystack Text to replace in
|
24 |
+
* @param array $log Log array
|
25 |
+
* @return string
|
26 |
+
*/
|
27 |
+
public static function replace( $haystack, $log ) {
|
28 |
+
if ( preg_match_all( '#{([^}]+)}#', $haystack, $placeholders ) ) {
|
29 |
+
|
30 |
+
foreach ( $placeholders[1] as $placeholder ) {
|
31 |
+
$value = false;
|
32 |
+
switch ( $placeholder ) {
|
33 |
+
case 'summary':
|
34 |
+
case 'object_id':
|
35 |
+
case 'author':
|
36 |
+
case 'ip':
|
37 |
+
$value = $log[ $placeholder ];
|
38 |
+
break;
|
39 |
+
case 'created':
|
40 |
+
$value = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $log[ $placeholder ] ) ) );
|
41 |
+
break;
|
42 |
+
case 'connector':
|
43 |
+
$value = WP_Stream_Connectors::$term_labels['stream_connector'][ $log[ $placeholder ] ];
|
44 |
+
break;
|
45 |
+
case 'context':
|
46 |
+
$value = WP_Stream_Connectors::$term_labels['stream_context'][ $log['context'] ];
|
47 |
+
break;
|
48 |
+
case 'action':
|
49 |
+
$value = WP_Stream_Connectors::$term_labels['stream_action'][ $log['action'] ];
|
50 |
+
break;
|
51 |
+
case ( false !== strpos( $placeholder, 'meta.' ) ):
|
52 |
+
$meta_key = substr( $placeholder, 5 );
|
53 |
+
if ( isset( $log['meta'][ $meta_key ] ) ) {
|
54 |
+
$value = $log['meta'][ $meta_key ];
|
55 |
+
}
|
56 |
+
break;
|
57 |
+
case ( false !== strpos( $placeholder, 'author.' ) ):
|
58 |
+
$meta_key = substr( $placeholder, 7 );
|
59 |
+
$author = get_userdata( $log['author'] );
|
60 |
+
if ( $author && isset( $author->{$meta_key} ) ) {
|
61 |
+
$value = $author->{$meta_key};
|
62 |
+
}
|
63 |
+
break;
|
64 |
+
// TODO Move this part to Stream base, and abstract it
|
65 |
+
case ( false !== strpos( $placeholder, 'object.' ) ):
|
66 |
+
$meta_key = substr( $placeholder, 7 );
|
67 |
+
$context = $log['context'];
|
68 |
+
// can only guess the object type, since there is no
|
69 |
+
// actual reference here
|
70 |
+
switch ( $context ) {
|
71 |
+
case 'post':
|
72 |
+
case 'page':
|
73 |
+
case 'media':
|
74 |
+
$object = get_post( $log['object_id'] );
|
75 |
+
break;
|
76 |
+
case 'users':
|
77 |
+
$object = get_userdata( $log['object_id'] );
|
78 |
+
break;
|
79 |
+
case 'comment':
|
80 |
+
$object = get_comment( $log['object_id'] );
|
81 |
+
break;
|
82 |
+
case 'term':
|
83 |
+
case 'category':
|
84 |
+
case 'post_tag':
|
85 |
+
case 'link_category':
|
86 |
+
$object = get_term( $log['object_id'], $log['meta']['taxonomy'] );
|
87 |
+
break;
|
88 |
+
default:
|
89 |
+
$object = apply_filters( 'wp_stream_notifications_record_object', $log['object_id'], $log );
|
90 |
+
break;
|
91 |
+
}
|
92 |
+
if ( is_object( $object ) && isset( $object->{$meta_key} ) ) {
|
93 |
+
$value = $object->{$meta_key};
|
94 |
+
}
|
95 |
+
break;
|
96 |
+
}
|
97 |
+
if ( $value ) {
|
98 |
+
$haystack = str_replace( "{{$placeholder}}", $value, $haystack );
|
99 |
+
}
|
100 |
+
}
|
101 |
+
}
|
102 |
+
return $haystack;
|
103 |
+
}
|
104 |
+
|
105 |
+
function load( $alert ) {
|
106 |
+
$params = array();
|
107 |
+
$fields = $this::fields();
|
108 |
+
foreach ( $fields as $field => $options ) {
|
109 |
+
$params[ $field ] = isset( $alert[ $field ] )
|
110 |
+
? $alert[ $field ]
|
111 |
+
: null;
|
112 |
+
}
|
113 |
+
$this->params = $params;
|
114 |
+
return $this;
|
115 |
+
}
|
116 |
+
|
117 |
+
abstract function send( $log );
|
118 |
+
|
119 |
+
}
|
extensions/notifications/includes/class-wp-stream-notifications-list-table.php
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications_List_Table {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Hold Singleton instance
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $instance;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Return active instance of this class, create one if it doesn't exist
|
14 |
+
*
|
15 |
+
* @return WP_Stream_Notifications_List_Table
|
16 |
+
*/
|
17 |
+
public static function get_instance() {
|
18 |
+
if ( empty( self::$instance ) ) {
|
19 |
+
self::$instance = new self();
|
20 |
+
}
|
21 |
+
|
22 |
+
return self::$instance;
|
23 |
+
}
|
24 |
+
|
25 |
+
private function __construct( $args = array() ) {
|
26 |
+
add_filter( 'manage_stream-notification_posts_columns', array( $this, 'column_heads' ) );
|
27 |
+
add_action( 'manage_stream-notification_posts_custom_column', array( $this, 'column_content' ), 10, 2 );
|
28 |
+
add_action( 'manage_edit-stream-notification_sortable_columns', array( $this, 'column_sortable' ) );
|
29 |
+
add_filter( 'request', array( $this, 'column_sortable_request' ) );
|
30 |
+
|
31 |
+
// Add inline row actions
|
32 |
+
add_filter( 'post_row_actions', array( $this, 'row_actions' ), 10, 2 );
|
33 |
+
|
34 |
+
// Add bulk actions
|
35 |
+
add_action( 'admin_head', array( $this, 'scripts' ) );
|
36 |
+
// Parse actions
|
37 |
+
add_action( 'load-edit.php', array( $this, 'actions' ) );
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @filter manage_posts_columns
|
42 |
+
*
|
43 |
+
* @param $cols
|
44 |
+
*
|
45 |
+
* @return array
|
46 |
+
*/
|
47 |
+
public function column_heads( $cols ) {
|
48 |
+
$new = array(
|
49 |
+
'type' => esc_html__( 'Type', 'stream' ),
|
50 |
+
'occ' => esc_html__( 'Occurrences', 'stream' ),
|
51 |
+
);
|
52 |
+
|
53 |
+
$cols = array_merge( array_splice( $cols, 0, 2 ), $new, array_splice( $cols, 0 ) );
|
54 |
+
|
55 |
+
return $cols;
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @action manage_posts_custom_column
|
60 |
+
*
|
61 |
+
* @param $name
|
62 |
+
* @param $post_id
|
63 |
+
*/
|
64 |
+
public function column_content( $name, $post_id ) {
|
65 |
+
if ( 'type' === $name ) {
|
66 |
+
echo esc_html( $this->get_rule_alert_types( $post_id ) );
|
67 |
+
} elseif ( 'occ' === $name ) {
|
68 |
+
echo absint( ( $occ = get_post_meta( $post_id, 'occurrences', true ) ) ? $occ : 0 );
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* @filter manage_posts_sortable_columns
|
74 |
+
*
|
75 |
+
* @param $sortable
|
76 |
+
*
|
77 |
+
* @return array
|
78 |
+
*/
|
79 |
+
public function column_sortable( $sortable ) {
|
80 |
+
return $sortable + array(
|
81 |
+
'occ' => array( 'occurrences', true ),
|
82 |
+
);
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* @filter request
|
87 |
+
*
|
88 |
+
* @param array $vars
|
89 |
+
*
|
90 |
+
* @return mixed
|
91 |
+
*/
|
92 |
+
public function column_sortable_request( $vars ) {
|
93 |
+
if ( ! is_admin() ) {
|
94 |
+
return $vars;
|
95 |
+
}
|
96 |
+
|
97 |
+
if ( isset( $vars['orderby'] ) && 'occurrences' === $vars['orderby'] ) {
|
98 |
+
$vars['meta_query'] = array(
|
99 |
+
'relation' => 'OR',
|
100 |
+
0 => array(
|
101 |
+
'key' => 'occurrences',
|
102 |
+
'compare' => 'EXISTS',
|
103 |
+
),
|
104 |
+
1 => array(
|
105 |
+
'key' => 'occurrences',
|
106 |
+
'compare' => 'NOT EXISTS',
|
107 |
+
),
|
108 |
+
);
|
109 |
+
$vars['meta_key'] = 'occurrences';
|
110 |
+
$vars['orderby'] = 'meta_value_num';
|
111 |
+
}
|
112 |
+
|
113 |
+
return $vars;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Retrieve rule alert types' labels
|
118 |
+
*
|
119 |
+
* @param $post_id
|
120 |
+
*
|
121 |
+
* @return string
|
122 |
+
*/
|
123 |
+
function get_rule_alert_types( $post_id ) {
|
124 |
+
$alerts = get_post_meta( $post_id, 'alerts', true );
|
125 |
+
|
126 |
+
if ( empty( $alerts ) ) {
|
127 |
+
return esc_html__( 'N/A', 'stream' );
|
128 |
+
} else {
|
129 |
+
$types = wp_list_pluck( $alerts, 'type' );
|
130 |
+
$titles = wp_list_pluck(
|
131 |
+
array_intersect_key(
|
132 |
+
WP_Stream_Notifications::$adapters,
|
133 |
+
array_flip( $types )
|
134 |
+
),
|
135 |
+
'title'
|
136 |
+
);
|
137 |
+
|
138 |
+
return implode( ', ', $titles );
|
139 |
+
}
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* @filter post_row_actions
|
144 |
+
*
|
145 |
+
* @param $actions
|
146 |
+
*
|
147 |
+
* @return array
|
148 |
+
*/
|
149 |
+
public function row_actions( $actions ) {
|
150 |
+
global $typenow;
|
151 |
+
|
152 |
+
if ( WP_Stream_Notifications_Post_Type::POSTTYPE !== $typenow ) {
|
153 |
+
return $actions;
|
154 |
+
}
|
155 |
+
|
156 |
+
unset( $actions['view'] );
|
157 |
+
unset( $actions['inline hide-if-no-js'] );
|
158 |
+
|
159 |
+
global $post;
|
160 |
+
|
161 |
+
$published = ( 'publish' === $post->post_status );
|
162 |
+
|
163 |
+
$new = array();
|
164 |
+
$url = wp_nonce_url(
|
165 |
+
add_query_arg(
|
166 |
+
array(
|
167 |
+
'post_type' => WP_Stream_Notifications_Post_Type::POSTTYPE,
|
168 |
+
'action' => $published ? 'unpublish' : 'publish',
|
169 |
+
'id' => $post->ID,
|
170 |
+
),
|
171 |
+
admin_url( 'edit.php' )
|
172 |
+
)
|
173 |
+
);
|
174 |
+
$new['publish'] = sprintf(
|
175 |
+
'<a href="%s">%s</a>',
|
176 |
+
$url,
|
177 |
+
$published ? __( 'Deactivate', 'stream' ) : __( 'Activate', 'stream' )
|
178 |
+
);
|
179 |
+
|
180 |
+
return array_merge( $new, $actions );
|
181 |
+
}
|
182 |
+
|
183 |
+
/**
|
184 |
+
* @action load-edit.php
|
185 |
+
*/
|
186 |
+
public function actions() {
|
187 |
+
if ( ! isset( $_REQUEST['action'] ) || ! isset( $_REQUEST['post_type'] ) || WP_Stream_Notifications_Post_Type::POSTTYPE !== wp_stream_filter_input( INPUT_GET, 'post_type' ) ) {
|
188 |
+
return;
|
189 |
+
}
|
190 |
+
|
191 |
+
$action = $_REQUEST['action'];
|
192 |
+
$request = isset( $_REQUEST['post'] ) ? ( is_array( $_REQUEST['post'] ) ? $_REQUEST['post'] : explode( ',', $_REQUEST['post'] ) ) : isset( $_REQUEST['id'] ) ? array( $_REQUEST['id'] ) : array();
|
193 |
+
$ids = array_map( 'absint', $request );
|
194 |
+
|
195 |
+
if ( empty( $action ) || empty( $ids ) ) {
|
196 |
+
return;
|
197 |
+
}
|
198 |
+
|
199 |
+
if ( in_array( $action, array( 'publish', 'unpublish' ) ) ) {
|
200 |
+
$status = ( 'publish' === $action ) ? 'publish' : 'draft';
|
201 |
+
|
202 |
+
foreach ( $ids as $id ) {
|
203 |
+
wp_update_post(
|
204 |
+
array(
|
205 |
+
'ID' => $id,
|
206 |
+
'post_status' => $status,
|
207 |
+
)
|
208 |
+
);
|
209 |
+
}
|
210 |
+
|
211 |
+
wp_safe_redirect(
|
212 |
+
add_query_arg(
|
213 |
+
array(
|
214 |
+
'updated' => count( $ids ),
|
215 |
+
),
|
216 |
+
remove_query_arg(
|
217 |
+
array( 'action', 'action2', 'id', 'ids', 'post', '_wp_http_referer', 'post_status', 'mode', 'm' )
|
218 |
+
)
|
219 |
+
)
|
220 |
+
);
|
221 |
+
|
222 |
+
exit; // Without this, the page displays the weird 'Are you sure you want this?'
|
223 |
+
}
|
224 |
+
}
|
225 |
+
|
226 |
+
/**
|
227 |
+
* @filter admin_head
|
228 |
+
*/
|
229 |
+
public function scripts() {
|
230 |
+
if ( 'edit-' . WP_Stream_Notifications_Post_Type::POSTTYPE !== get_current_screen()->id ) {
|
231 |
+
return;
|
232 |
+
}
|
233 |
+
|
234 |
+
wp_enqueue_script( 'stream-notifications-list-actions', WP_STREAM_NOTIFICATIONS_URL . 'ui/js/list.js', array( 'jquery', 'underscore' ), WP_STREAM::VERSION );
|
235 |
+
|
236 |
+
wp_localize_script( 'stream-notifications-list-actions', 'stream_notifications_options',
|
237 |
+
array(
|
238 |
+
'bulkActions' => array(
|
239 |
+
'publish' => __( 'Publish', 'stream' ),
|
240 |
+
'unpublish' => __( 'Unpublish', 'stream' ),
|
241 |
+
)
|
242 |
+
)
|
243 |
+
);
|
244 |
+
}
|
245 |
+
|
246 |
+
}
|
extensions/notifications/includes/class-wp-stream-notifications-matcher.php
ADDED
@@ -0,0 +1,511 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications_Matcher {
|
4 |
+
|
5 |
+
const CACHE_KEY = 'stream-notification-rules';
|
6 |
+
|
7 |
+
/**
|
8 |
+
* @todo fix deprecated actions/filters
|
9 |
+
*/
|
10 |
+
public function __construct() {
|
11 |
+
// Refresh rules cache on updating/deleting posts
|
12 |
+
add_action( 'save_post', array( $this, 'refresh_cache_on_save' ), 10, 2 );
|
13 |
+
add_action( 'delete_post', array( $this, 'refresh_cache_on_delete' ), 10, 1 );
|
14 |
+
|
15 |
+
// Match all new type=stream records
|
16 |
+
add_action( 'wp_stream_records_inserted', array( $this, 'match' ), 10, 2 );
|
17 |
+
}
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Refresh cache on saving a rule
|
21 |
+
*
|
22 |
+
* @action save_post
|
23 |
+
*
|
24 |
+
* @param $post_id
|
25 |
+
* @param null $post
|
26 |
+
*
|
27 |
+
* @return void
|
28 |
+
*/
|
29 |
+
public function refresh_cache_on_save( $post_id, $post = null ) {
|
30 |
+
if ( ! isset( $post ) ) {
|
31 |
+
$post = get_post( $post_id );
|
32 |
+
}
|
33 |
+
|
34 |
+
if ( WP_Stream_Notifications_Post_Type::POSTTYPE === $post->post_type ) {
|
35 |
+
$this->rules( true );
|
36 |
+
}
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Refresh cache on deleting a rule
|
41 |
+
*
|
42 |
+
* @action delete_post
|
43 |
+
*
|
44 |
+
* @param $post_id
|
45 |
+
*
|
46 |
+
* @return void
|
47 |
+
*/
|
48 |
+
public function refresh_cache_on_delete( $post_id ) {
|
49 |
+
$post = get_post( $post_id );
|
50 |
+
|
51 |
+
if ( WP_Stream_Notifications_Post_Type::POSTTYPE !== $post->post_type ) {
|
52 |
+
return;
|
53 |
+
}
|
54 |
+
|
55 |
+
add_action(
|
56 |
+
'deleted_post', function ( $deleted_id ) use ( $post_id ) {
|
57 |
+
if ( $deleted_id === $post_id ) {
|
58 |
+
$this->rules( true );
|
59 |
+
}
|
60 |
+
}
|
61 |
+
);
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Generate a proper format of triggers/alerts to be used/cached
|
66 |
+
*
|
67 |
+
* @param bool $force_refresh Ignore cached version
|
68 |
+
*
|
69 |
+
* @return array|mixed|void
|
70 |
+
*/
|
71 |
+
public function rules( $force_refresh = false ) {
|
72 |
+
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
73 |
+
$force_refresh = true;
|
74 |
+
}
|
75 |
+
|
76 |
+
// Check if we have a valid cache
|
77 |
+
if ( ! $force_refresh && false !== ( $rules = get_transient( self::CACHE_KEY ) ) ) {
|
78 |
+
return $rules;
|
79 |
+
}
|
80 |
+
|
81 |
+
// Get rules
|
82 |
+
$args = array(
|
83 |
+
'post_type' => WP_Stream_Notifications_Post_Type::POSTTYPE,
|
84 |
+
'post_status' => 'publish',
|
85 |
+
'posts_per_page' => -1,
|
86 |
+
);
|
87 |
+
$query = new WP_Query( $args );
|
88 |
+
$rules = $query->get_posts();
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Allow developers to add/modify rules
|
92 |
+
*
|
93 |
+
* @param array $rules Rules for the current blog
|
94 |
+
* @param array $args Query args used
|
95 |
+
*
|
96 |
+
* @return array
|
97 |
+
*/
|
98 |
+
$rules = apply_filters( 'wp_stream_notifications_rules', $rules, $args );
|
99 |
+
|
100 |
+
$rules = $this->format( $rules );
|
101 |
+
|
102 |
+
// Cache the new rules
|
103 |
+
set_transient( self::CACHE_KEY, $rules );
|
104 |
+
|
105 |
+
return $rules;
|
106 |
+
}
|
107 |
+
|
108 |
+
public function match( $records ) {
|
109 |
+
$rules = $this->rules();
|
110 |
+
$rule_match = array();
|
111 |
+
|
112 |
+
foreach ( $records as $record ) {
|
113 |
+
foreach ( $rules as $rule_id => $rule ) {
|
114 |
+
$rule_match[ $rule_id ] = $this->match_group( $rule['triggers'], $record );
|
115 |
+
}
|
116 |
+
}
|
117 |
+
|
118 |
+
$rule_match = array_keys( array_filter( $rule_match ) );
|
119 |
+
$matching_rules = array_intersect_key( $rules, array_flip( $rule_match ) );
|
120 |
+
|
121 |
+
$this->alert( $matching_rules, $records );
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* Match a group of chunked triggers against a log operation
|
126 |
+
*
|
127 |
+
* @param array $chunks Chunks of triggers, usually from group[triggers]
|
128 |
+
* @param array $log Log operation array
|
129 |
+
*
|
130 |
+
* @return bool Matching result
|
131 |
+
*/
|
132 |
+
private function match_group( $chunks, $record ) {
|
133 |
+
// Separate triggers by 'AND'/'OR' relation, to be able to fail early
|
134 |
+
// and not have to traverse the whole trigger tree
|
135 |
+
foreach ( $chunks as $chunk ) {
|
136 |
+
$results = array();
|
137 |
+
|
138 |
+
foreach ( $chunk as $trigger ) {
|
139 |
+
$is_group = isset( $trigger['triggers'] );
|
140 |
+
|
141 |
+
if ( $is_group ) {
|
142 |
+
$results[] = $this->match_group( $trigger['triggers'], $record );
|
143 |
+
} else {
|
144 |
+
$results[] = $this->match_trigger( $trigger, $record );
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
// If the whole chunk fails, fail the whole group
|
149 |
+
if ( 0 === count( array_filter( $results ) ) ) {
|
150 |
+
return false;
|
151 |
+
}
|
152 |
+
}
|
153 |
+
|
154 |
+
// If nothing fails, group matches
|
155 |
+
return true;
|
156 |
+
}
|
157 |
+
|
158 |
+
public function match_trigger( $trigger, $record ) {
|
159 |
+
$type = isset( $trigger['type'] ) ? $trigger['type'] : null;
|
160 |
+
$needle = isset( $trigger['value'] ) ? $trigger['value'] : null;
|
161 |
+
$operator = isset( $trigger['operator'] ) ? $trigger['operator'] : null;
|
162 |
+
$negative = ( isset( $operator[0] ) && '!' === $operator[0] );
|
163 |
+
$haystack = null;
|
164 |
+
|
165 |
+
// Post-specific triggers dirty work
|
166 |
+
if ( false !== strpos( $trigger['type'], 'post_' ) ) {
|
167 |
+
$post = get_post( $record['object_id'] );
|
168 |
+
|
169 |
+
if ( empty( $post ) ) {
|
170 |
+
return false;
|
171 |
+
}
|
172 |
+
}
|
173 |
+
|
174 |
+
switch ( $type ) {
|
175 |
+
case 'search':
|
176 |
+
$haystack = strtolower( $record['summary'] );
|
177 |
+
$needle = strtolower( $needle );
|
178 |
+
break;
|
179 |
+
case 'object_id':
|
180 |
+
$haystack = $record['object_id'];
|
181 |
+
break;
|
182 |
+
case 'author':
|
183 |
+
$haystack = $record['author'];
|
184 |
+
break;
|
185 |
+
case 'author_role':
|
186 |
+
$user = get_userdata( $record['author'] );
|
187 |
+
$haystack = ( is_object( $user ) && $user->exists() && $user->roles ) ? $user->roles[0] : false;
|
188 |
+
break;
|
189 |
+
case 'ip':
|
190 |
+
$haystack = $record['ip'];
|
191 |
+
break;
|
192 |
+
case 'date':
|
193 |
+
$haystack = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $record['created'] ) ), 'Ymd' );
|
194 |
+
$needle = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $needle ) ), 'Ymd' );
|
195 |
+
break;
|
196 |
+
case 'weekday':
|
197 |
+
if ( isset( $needle[0] ) && preg_match( '#\d+#', $needle[0], $weekday_match ) ) {
|
198 |
+
$haystack = get_date_from_gmt( date( 'Y-m-d H:i:s', strtotime( $record['created'] ) ), 'w' );
|
199 |
+
$needle = $weekday_match[0];
|
200 |
+
}
|
201 |
+
break;
|
202 |
+
case 'connector':
|
203 |
+
$haystack = $record['connector'];
|
204 |
+
break;
|
205 |
+
case 'context':
|
206 |
+
$haystack = $record['context'];
|
207 |
+
break;
|
208 |
+
case 'action':
|
209 |
+
$haystack = $record['action'];
|
210 |
+
break;
|
211 |
+
|
212 |
+
/* Context-aware triggers */
|
213 |
+
case 'post':
|
214 |
+
case 'user':
|
215 |
+
case 'term':
|
216 |
+
$haystack = $record['object_id'];
|
217 |
+
break;
|
218 |
+
case 'term_parent':
|
219 |
+
$parent = get_term( $record['meta']['term_parent'], $record['meta']['taxonomy'] );
|
220 |
+
if ( empty( $parent ) || is_wp_error( $parent ) ) {
|
221 |
+
return false;
|
222 |
+
} else {
|
223 |
+
$haystack = $parent->term_taxonomy_id;
|
224 |
+
}
|
225 |
+
break;
|
226 |
+
case 'tax':
|
227 |
+
if ( empty( $record['meta']['taxonomy'] ) ) {
|
228 |
+
return false;
|
229 |
+
}
|
230 |
+
$haystack = $record['meta']['taxonomy'];
|
231 |
+
break;
|
232 |
+
|
233 |
+
case 'post_title':
|
234 |
+
$haystack = $post->post_title;
|
235 |
+
break;
|
236 |
+
case 'post_slug':
|
237 |
+
$haystack = $post->post_name;
|
238 |
+
break;
|
239 |
+
case 'post_content':
|
240 |
+
$haystack = $post->post_content;
|
241 |
+
break;
|
242 |
+
case 'post_excerpt':
|
243 |
+
$haystack = $post->post_excerpt;
|
244 |
+
break;
|
245 |
+
case 'post_status':
|
246 |
+
$haystack = get_post_status( $post->ID );
|
247 |
+
break;
|
248 |
+
case 'post_format':
|
249 |
+
$haystack = get_post_format( $post );
|
250 |
+
break;
|
251 |
+
case 'post_parent':
|
252 |
+
$haystack = wp_get_post_parent_id( $post->ID );
|
253 |
+
break;
|
254 |
+
case 'post_thumbnail':
|
255 |
+
if ( ! function_exists( 'get_post_thumbnail_id' ) ) {
|
256 |
+
return false;
|
257 |
+
}
|
258 |
+
$haystack = get_post_thumbnail_id( $post->ID ) > 0;
|
259 |
+
break;
|
260 |
+
case 'post_comment_status':
|
261 |
+
$haystack = $post->comment_status;
|
262 |
+
break;
|
263 |
+
case 'post_comment_count':
|
264 |
+
$haystack = get_comment_count( $post->ID );
|
265 |
+
break;
|
266 |
+
default:
|
267 |
+
return false;
|
268 |
+
break;
|
269 |
+
}
|
270 |
+
|
271 |
+
$match = false;
|
272 |
+
|
273 |
+
switch ( $operator ) {
|
274 |
+
case '=':
|
275 |
+
case '!=':
|
276 |
+
case '>=':
|
277 |
+
case '<=':
|
278 |
+
$needle = is_array( $needle ) ? $needle : explode( ',', $needle );
|
279 |
+
$match = (bool) array_intersect( $needle, (array) $haystack );
|
280 |
+
break;
|
281 |
+
// string special comparison operators
|
282 |
+
case 'contains':
|
283 |
+
case '!contains':
|
284 |
+
$match = ( false !== strpos( $haystack, $needle ) );
|
285 |
+
break;
|
286 |
+
case 'starts':
|
287 |
+
$match = ( 0 === strpos( $haystack, $needle ) );
|
288 |
+
break;
|
289 |
+
case 'ends':
|
290 |
+
$match = ( strlen( $haystack ) - strlen( $needle ) === strrpos( $haystack, $needle ) );
|
291 |
+
break;
|
292 |
+
case 'regex':
|
293 |
+
$match = preg_match( $needle, $haystack ) > 0;
|
294 |
+
break;
|
295 |
+
// date operators
|
296 |
+
case '<':
|
297 |
+
case '<=':
|
298 |
+
$match = $match || ( $haystack < $needle );
|
299 |
+
break;
|
300 |
+
case '>':
|
301 |
+
case '>=':
|
302 |
+
$match = $match || ( $haystack > $needle );
|
303 |
+
break;
|
304 |
+
}
|
305 |
+
|
306 |
+
$result = ( $match == ! $negative ); // Loose comparison needed
|
307 |
+
|
308 |
+
return $result;
|
309 |
+
}
|
310 |
+
|
311 |
+
/**
|
312 |
+
* Format rules to be usable during the matching process
|
313 |
+
*
|
314 |
+
* @param array $rules Array of rule IDs
|
315 |
+
*
|
316 |
+
* @return array Reformatted array of groups/triggers
|
317 |
+
*/
|
318 |
+
private function format( $rules ) {
|
319 |
+
$output = array();
|
320 |
+
|
321 |
+
foreach ( $rules as $rule ) {
|
322 |
+
$rule_id = $rule->ID;
|
323 |
+
$meta = get_post_meta( $rule_id );
|
324 |
+
$args = array();
|
325 |
+
|
326 |
+
foreach ( array( 'triggers', 'groups', 'alerts' ) as $key ) {
|
327 |
+
if ( isset( $meta[ $key ] ) ) {
|
328 |
+
$args[ $key ] = array_filter( maybe_unserialize( $meta[ $key ][0] ) );
|
329 |
+
}
|
330 |
+
}
|
331 |
+
|
332 |
+
// Bail early if no triggers or alerts are defined
|
333 |
+
if ( empty( $args['triggers'] ) || empty( $args['alerts'] ) ) {
|
334 |
+
continue;
|
335 |
+
}
|
336 |
+
|
337 |
+
$output[ $rule_id ] = array();
|
338 |
+
|
339 |
+
// Generate an easy-to-parse tree of triggers/groups
|
340 |
+
$args['triggers'] = $this->generate_tree(
|
341 |
+
$this->generate_flattened_tree(
|
342 |
+
$args['triggers'],
|
343 |
+
$args['groups']
|
344 |
+
)
|
345 |
+
);
|
346 |
+
|
347 |
+
// Chunkify! @see generate_group_chunks
|
348 |
+
$args['triggers'] = $this->generate_group_chunks(
|
349 |
+
$args['triggers'][0]['triggers']
|
350 |
+
);
|
351 |
+
|
352 |
+
// Add alerts
|
353 |
+
$output[ $rule_id ] = $args;
|
354 |
+
}
|
355 |
+
|
356 |
+
return $output;
|
357 |
+
}
|
358 |
+
|
359 |
+
/**
|
360 |
+
* Return all of group's ancestors starting with the root
|
361 |
+
*/
|
362 |
+
private function generate_group_chain( $groups, $group_id ) {
|
363 |
+
$chain = array();
|
364 |
+
|
365 |
+
while ( isset( $groups[ $group_id ] ) ) {
|
366 |
+
$chain[] = $group_id;
|
367 |
+
$group_id = $groups[ $group_id ]['group'];
|
368 |
+
}
|
369 |
+
|
370 |
+
return array_reverse( $chain );
|
371 |
+
}
|
372 |
+
|
373 |
+
/**
|
374 |
+
* Takes the groups and triggers and creates a flattened tree,
|
375 |
+
* which is an pre-order walkthrough of the tree we want to construct
|
376 |
+
* http://en.wikipedia.org/wiki/Tree_traversal#Pre-order
|
377 |
+
*/
|
378 |
+
private function generate_flattened_tree( $triggers, $groups ) {
|
379 |
+
// Seed the tree with the universal group
|
380 |
+
if ( ! isset( $groups[0] ) ) {
|
381 |
+
$groups[0] = array( 'group' => null, 'relation' => 'and' );
|
382 |
+
}
|
383 |
+
|
384 |
+
$flattened_tree = array( array( 'item' => $groups['0'], 'level' => 0, 'type' => 'group' ) );
|
385 |
+
$current_group_chain = array( '0' );
|
386 |
+
$level = 1;
|
387 |
+
|
388 |
+
foreach ( $triggers as $key => $trigger ) {
|
389 |
+
$active_group = end( $current_group_chain );
|
390 |
+
|
391 |
+
// If the trigger goes to any other than actually opened group, we need to traverse the tree first
|
392 |
+
if ( $trigger['group'] != $active_group ) {
|
393 |
+
$trigger_group_chain = $this->generate_group_chain( $groups, $trigger['group'] );
|
394 |
+
$common_ancestors = array_intersect( $current_group_chain, $trigger_group_chain );
|
395 |
+
$newly_inserted_groups = array_diff( $trigger_group_chain, $current_group_chain );
|
396 |
+
$steps_back = $level - count( $common_ancestors );
|
397 |
+
|
398 |
+
// First take the steps back until we reach a common ancestor
|
399 |
+
for ( $i = 0; $i < $steps_back; $i ++ ) {
|
400 |
+
array_pop( $current_group_chain );
|
401 |
+
$level --;
|
402 |
+
}
|
403 |
+
|
404 |
+
// Then go forward and generate group nodes until the trigger is ready to be inserted
|
405 |
+
foreach ( $newly_inserted_groups as $group ) {
|
406 |
+
$flattened_tree[] = array( 'item' => $groups[ $group ], 'level' => $level ++, 'type' => 'group' );
|
407 |
+
$current_group_chain[] = $group;
|
408 |
+
}
|
409 |
+
}
|
410 |
+
|
411 |
+
// Now we're sure the trigger goes to a correct position
|
412 |
+
$flattened_tree[] = array( 'item' => $trigger, 'level' => $level, 'type' => 'trigger' );
|
413 |
+
}
|
414 |
+
|
415 |
+
return $flattened_tree;
|
416 |
+
}
|
417 |
+
|
418 |
+
/**
|
419 |
+
* Takes the flattened tree and generates a proper tree
|
420 |
+
*/
|
421 |
+
private function generate_tree( $flattened_tree ) {
|
422 |
+
// Our recurrent step
|
423 |
+
$recurrent_step = function ( $level, $i ) use ( $flattened_tree, &$recurrent_step ) {
|
424 |
+
$return = array();
|
425 |
+
|
426 |
+
for ( $i; $i < count( $flattened_tree ); $i ++ ) {
|
427 |
+
// If we're on the correct level, we're going to insert the node
|
428 |
+
if ( $flattened_tree[ $i ]['level'] === $level ) {
|
429 |
+
if ( 'trigger' === $flattened_tree[ $i ]['type'] ) {
|
430 |
+
$return[] = $flattened_tree[ $i ]['item'];
|
431 |
+
// If the node is a group, we need to call the recursive function
|
432 |
+
// in order to construct the tree for us further
|
433 |
+
} else {
|
434 |
+
$return[] = array(
|
435 |
+
'relation' => $flattened_tree[ $i ]['item']['relation'],
|
436 |
+
'triggers' => call_user_func( $recurrent_step, $level + 1, $i + 1 ),
|
437 |
+
);
|
438 |
+
}
|
439 |
+
// If we're on a lower level, we came back and we can return this branch
|
440 |
+
} elseif ( $flattened_tree[ $i ]['level'] < $level ) {
|
441 |
+
return $return;
|
442 |
+
}
|
443 |
+
}
|
444 |
+
|
445 |
+
return $return;
|
446 |
+
};
|
447 |
+
|
448 |
+
return call_user_func( $recurrent_step, 0, 0 );
|
449 |
+
}
|
450 |
+
|
451 |
+
/**
|
452 |
+
* Split trigger trees by relation, so we can fail trigger trees early if
|
453 |
+
* an effective trigger is not matched
|
454 |
+
*
|
455 |
+
* A chunk would be a bulk of triggers that only matches if ANY of its
|
456 |
+
* nested triggers are matched
|
457 |
+
*
|
458 |
+
* @param $triggers
|
459 |
+
*
|
460 |
+
* @internal array $group Group array, ex: array(
|
461 |
+
* 'relation' => 'and',
|
462 |
+
* 'trigger' => array( arr trigger1, arr trigger2 )
|
463 |
+
* );
|
464 |
+
*
|
465 |
+
* @return array Chunks of triggers, split based on their relation
|
466 |
+
*/
|
467 |
+
private function generate_group_chunks( $triggers ) {
|
468 |
+
$chunks = array();
|
469 |
+
$current_chunk = -1;
|
470 |
+
|
471 |
+
foreach ( $triggers as $trigger ) {
|
472 |
+
// If is a group, chunks its children as well
|
473 |
+
if ( isset( $trigger['triggers'] ) ) {
|
474 |
+
$trigger['triggers'] = $this->generate_group_chunks( $trigger['triggers'] );
|
475 |
+
}
|
476 |
+
|
477 |
+
// If relation=and, start a new chunk, else join the previous chunk
|
478 |
+
if ( 'and' === $trigger['relation'] ) {
|
479 |
+
$chunks[] = array( $trigger );
|
480 |
+
$current_chunk = count( $chunks ) - 1;
|
481 |
+
} else {
|
482 |
+
$chunks[ $current_chunk ][] = $trigger;
|
483 |
+
}
|
484 |
+
}
|
485 |
+
|
486 |
+
return $chunks;
|
487 |
+
}
|
488 |
+
|
489 |
+
private function alert( $rules, $records ) {
|
490 |
+
foreach ( $rules as $rule_id => $rule ) {
|
491 |
+
// Update occurrences
|
492 |
+
update_post_meta(
|
493 |
+
$rule_id,
|
494 |
+
'occurrences',
|
495 |
+
( (int) get_post_meta( $rule_id, 'occurrences', true ) ) + 1
|
496 |
+
);
|
497 |
+
|
498 |
+
foreach ( $records as $record ) {
|
499 |
+
foreach ( $rule['alerts'] as $alert ) {
|
500 |
+
if ( ! isset( WP_Stream_Notifications::$adapters[ $alert['type'] ] ) ) {
|
501 |
+
continue;
|
502 |
+
}
|
503 |
+
|
504 |
+
$adapter = new WP_Stream_Notifications::$adapters[ $alert['type'] ]['class'];
|
505 |
+
$adapter->load( $alert )->send( $record );
|
506 |
+
}
|
507 |
+
}
|
508 |
+
}
|
509 |
+
}
|
510 |
+
|
511 |
+
}
|
extensions/notifications/includes/class-wp-stream-notifications-post-type.php
ADDED
@@ -0,0 +1,850 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Notifications_Post_Type {
|
4 |
+
|
5 |
+
private static $instance;
|
6 |
+
|
7 |
+
const POSTTYPE = 'stream_notification'; // Must be less than 20 chars
|
8 |
+
|
9 |
+
public static function get_instance() {
|
10 |
+
if ( ! self::$instance ) {
|
11 |
+
self::$instance = new self();
|
12 |
+
}
|
13 |
+
|
14 |
+
return self::$instance;
|
15 |
+
}
|
16 |
+
|
17 |
+
private function __construct() {
|
18 |
+
$this->register_post_type();
|
19 |
+
|
20 |
+
// AJAX end point for form auto completion
|
21 |
+
add_action( 'wp_ajax_stream_notification_endpoint', array( $this, 'form_ajax_ep' ) );
|
22 |
+
// Occurance reset
|
23 |
+
add_action( 'wp_ajax_stream-notifications-reset-occ', array( $this, 'ajax_reset_occ' ) );
|
24 |
+
|
25 |
+
// Enqueue our form scripts
|
26 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 11 );
|
27 |
+
|
28 |
+
// define `search_in` arg for WP_User_Query
|
29 |
+
add_filter( 'user_search_columns', array( $this, 'define_search_in_arg' ), 10, 3 );
|
30 |
+
|
31 |
+
// Change title placeholder
|
32 |
+
add_filter( 'enter_title_here', array( $this, 'title_placeholder' ), 10, 2 );
|
33 |
+
|
34 |
+
// Save meta data
|
35 |
+
add_action( 'save_post', array( $this, 'save' ), 10, 2 );
|
36 |
+
|
37 |
+
// Load list-table customizations
|
38 |
+
add_action( 'admin_init', array( $this, 'load_list_table' ) );
|
39 |
+
}
|
40 |
+
|
41 |
+
private function register_post_type() {
|
42 |
+
register_post_type(
|
43 |
+
self::POSTTYPE, array(
|
44 |
+
'label' => __( 'Stream Notification Rule', 'stream' ),
|
45 |
+
'labels' => array(
|
46 |
+
'name' => __( 'Stream Notification Rules', 'stream' ),
|
47 |
+
'singular_name' => __( 'Stream Notification Rule', 'stream' ),
|
48 |
+
'menu_name' => __( 'Notifications', 'stream' ),
|
49 |
+
'add_new' => _x( 'New Rule', 'Stream Notifications', 'stream' ),
|
50 |
+
'add_new_item' => _x( 'Add New Rule', 'Stream Notifications', 'stream' ),
|
51 |
+
'new_item' => _x( 'New Stream Notification Rule', 'Stream Notifications', 'stream' ),
|
52 |
+
'edit_item' => _x( 'Edit Stream Notification Rule', 'Stream Notifications', 'stream' ),
|
53 |
+
'view_item' => _x( 'View Stream Notification Rule', 'Stream Notifications', 'stream' ),
|
54 |
+
'search_items' => _x( 'Search Rules', 'Stream Notifications', 'stream' ),
|
55 |
+
'not_found' => _x( 'No notification rules found.', 'Stream Notifications', 'stream' ),
|
56 |
+
'not_found_in_trash' => _x( 'No notification rules found in Trash.', 'Stream Notifications', 'stream' ),
|
57 |
+
),
|
58 |
+
'public' => false,
|
59 |
+
'show_ui' => true,
|
60 |
+
'show_in_nav_menus' => false,
|
61 |
+
'show_in_menu' => false,
|
62 |
+
'exclude_from_search' => true,
|
63 |
+
'publicly_queryable' => false,
|
64 |
+
'supports' => WP_Stream::$api->is_restricted() ? false : array( 'title', 'author' ),
|
65 |
+
'register_meta_box_cb' => WP_Stream::$api->is_restricted() ? null : array( $this, 'metaboxes' ),
|
66 |
+
'rewrite' => false,
|
67 |
+
)
|
68 |
+
);
|
69 |
+
}
|
70 |
+
|
71 |
+
public function metaboxes( $post ) {
|
72 |
+
if ( self::POSTTYPE !== $post->post_type ) {
|
73 |
+
return;
|
74 |
+
}
|
75 |
+
|
76 |
+
add_meta_box( 'stream-notifications-triggers', __( 'Triggers', 'stream' ), array( $this, 'metabox_triggers' ), self::POSTTYPE );
|
77 |
+
add_meta_box( 'stream-notifications-alerts', __( 'Alerts', 'stream' ), array( $this, 'metabox_alerts' ), self::POSTTYPE );
|
78 |
+
add_meta_box( 'stream-notifications-data-tags', __( 'Data Tags', 'stream' ), array( $this, 'metabox_data_tags' ), self::POSTTYPE, 'side' );
|
79 |
+
|
80 |
+
add_action( 'post_submitbox_misc_actions', array( $this, 'metabox_save' ) );
|
81 |
+
|
82 |
+
add_action(
|
83 |
+
'edit_form_advanced', function () {
|
84 |
+
global $post;
|
85 |
+
include WP_STREAM_NOTIFICATIONS_DIR . 'views/form-templates.php';
|
86 |
+
}
|
87 |
+
);
|
88 |
+
}
|
89 |
+
|
90 |
+
/**
|
91 |
+
* Enqueue our scripts, in our own page only
|
92 |
+
*
|
93 |
+
* @action admin_enqueue_scripts
|
94 |
+
*
|
95 |
+
* @param string $hook Current admin page slug
|
96 |
+
*
|
97 |
+
* @return void
|
98 |
+
*/
|
99 |
+
public function enqueue_scripts( $hook ) {
|
100 |
+
global $typenow;
|
101 |
+
|
102 |
+
if ( ! in_array( $hook, array( 'post-new.php', 'post.php' ) ) || self::POSTTYPE !== $typenow ) {
|
103 |
+
return;
|
104 |
+
}
|
105 |
+
|
106 |
+
wp_enqueue_style( 'select2' );
|
107 |
+
wp_enqueue_script( 'select2' );
|
108 |
+
wp_enqueue_script( 'underscore' );
|
109 |
+
wp_enqueue_style( 'jquery-ui' );
|
110 |
+
wp_enqueue_script( 'jquery-ui-datepicker' );
|
111 |
+
wp_enqueue_style( 'wp-stream-datepicker' );
|
112 |
+
wp_enqueue_script( 'jquery-ui-accordion' );
|
113 |
+
wp_enqueue_script( 'accordion' );
|
114 |
+
wp_enqueue_style( 'stream-notifications-form', WP_STREAM_NOTIFICATIONS_URL . '/ui/css/form.css' );
|
115 |
+
wp_enqueue_script( 'stream-notifications-form', WP_STREAM_NOTIFICATIONS_URL . '/ui/js/form.js', array( 'underscore', 'select2' ) );
|
116 |
+
wp_localize_script( 'stream-notifications-form', 'stream_notifications', $this->get_js_options() );
|
117 |
+
}
|
118 |
+
|
119 |
+
public function metabox_triggers() {
|
120 |
+
?>
|
121 |
+
<a class="add-trigger button button-secondary" href="#add-trigger" data-group="0"><?php esc_html_e( '+ Add Trigger', 'stream' ) ?></a>
|
122 |
+
<a class="add-trigger-group button button-primary" href="#add-trigger-group" data-group="0"><?php esc_html_e( '+ Add Group', 'stream' ) ?></a>
|
123 |
+
<div class="group" rel="0"></div>
|
124 |
+
<?php
|
125 |
+
}
|
126 |
+
|
127 |
+
public function metabox_alerts() {
|
128 |
+
?>
|
129 |
+
<a class="add-alert button button-secondary" href="#add-alert"><?php esc_html_e( '+ Add Alert', 'stream' ) ?></a>
|
130 |
+
<?php
|
131 |
+
}
|
132 |
+
|
133 |
+
public function metabox_save() {
|
134 |
+
global $post;
|
135 |
+
if ( 'auto-draft' === $post->post_status ) {
|
136 |
+
return;
|
137 |
+
}
|
138 |
+
|
139 |
+
$reset_link = add_query_arg(
|
140 |
+
array(
|
141 |
+
'action' => 'stream-notifications-reset-occ',
|
142 |
+
'id' => absint( $post->ID ),
|
143 |
+
'wp_stream_nonce' => wp_create_nonce( 'reset-occ_' . absint( $post->ID ) ),
|
144 |
+
),
|
145 |
+
admin_url( 'admin-ajax.php' )
|
146 |
+
);
|
147 |
+
|
148 |
+
$occ = absint( get_post_meta( $post->ID, 'occurrences', true ) );
|
149 |
+
|
150 |
+
?>
|
151 |
+
<div class="occurrences misc-pub-section">
|
152 |
+
<p>
|
153 |
+
<?php
|
154 |
+
echo sprintf(
|
155 |
+
_n(
|
156 |
+
'This rule has occurred %1$s time.',
|
157 |
+
'This rule has occurred %1$s times.',
|
158 |
+
$occ,
|
159 |
+
'stream'
|
160 |
+
),
|
161 |
+
sprintf( '<strong>%d</strong>', $occ ? $occ : 0 )
|
162 |
+
) // xss okay
|
163 |
+
?>
|
164 |
+
</p>
|
165 |
+
<?php if ( 0 !== $occ ) : ?>
|
166 |
+
<p>
|
167 |
+
<a href="<?php echo esc_url( $reset_link ) ?>" class="button button-secondary reset-occ">
|
168 |
+
<?php esc_html_e( 'Reset Count', 'stream' ) ?>
|
169 |
+
</a>
|
170 |
+
</p>
|
171 |
+
<?php endif; ?>
|
172 |
+
</div>
|
173 |
+
<?php
|
174 |
+
}
|
175 |
+
|
176 |
+
public function metabox_data_tags() {
|
177 |
+
$data_tags = array(
|
178 |
+
__( 'Basic', 'stream' ) => array(
|
179 |
+
'summary' => __( 'Summary message of the triggered record.', 'stream' ),
|
180 |
+
'author' => __( 'User ID of the triggered record author.', 'stream' ),
|
181 |
+
'connector' => __( 'Connector of the triggered record.', 'stream' ),
|
182 |
+
'context' => __( 'Context of the triggered record.', 'stream' ),
|
183 |
+
'action' => __( 'Action of the triggered record.', 'stream' ),
|
184 |
+
'created' => __( 'Timestamp of triggered record.', 'stream' ),
|
185 |
+
'ip' => __( 'IP of the triggered record author.', 'stream' ),
|
186 |
+
'object_id' => __( 'Object ID of the triggered record.', 'stream' ),
|
187 |
+
),
|
188 |
+
__( 'Advanced', 'stream' ) => array(
|
189 |
+
'object.' => __(
|
190 |
+
'Specific object data of the record, relative to what the object type is:
|
191 |
+
<br /><br />
|
192 |
+
<strong>{object.post_title}</strong>
|
193 |
+
<br />
|
194 |
+
<strong>{object.post_excerpt}</strong>
|
195 |
+
<br />
|
196 |
+
<strong>{object.post_status}</strong>
|
197 |
+
<br />
|
198 |
+
<a href="http://codex.wordpress.org/Function_Reference/get_userdata#Notes" target="_blank">See Codex for more Post values</a>
|
199 |
+
<br /><br />
|
200 |
+
<strong>{object.name}</strong>
|
201 |
+
<br />
|
202 |
+
<strong>{object.taxonomy}</strong>
|
203 |
+
<br />
|
204 |
+
<strong>{object.description}</strong>
|
205 |
+
<br />
|
206 |
+
<a href="http://codex.wordpress.org/Function_Reference/get_userdata#Notes" target="_blank">See Codex for more Term values</a>', 'stream'
|
207 |
+
),
|
208 |
+
|
209 |
+
'author.' => __(
|
210 |
+
'Specific user data of the record author:
|
211 |
+
<br /><br />
|
212 |
+
<strong>{author.display_name}</strong>
|
213 |
+
<br />
|
214 |
+
<strong>{author.user_email}</strong>
|
215 |
+
<br />
|
216 |
+
<strong>{author.user_login}</strong>
|
217 |
+
<br />
|
218 |
+
<a href="http://codex.wordpress.org/Function_Reference/get_userdata#Notes" target="_blank">See Codex for more User values</a>', 'stream'
|
219 |
+
),
|
220 |
+
'meta.' => __(
|
221 |
+
'Specific meta data of the record, used to display specific meta values created by Connectors.
|
222 |
+
<br /><br />
|
223 |
+
Example: <strong>{meta.old_theme}</strong> to display the old theme name when a new theme is activated.', 'stream'
|
224 |
+
),
|
225 |
+
),
|
226 |
+
);
|
227 |
+
$allowed_html = array(
|
228 |
+
'a' => array(
|
229 |
+
'href' => array(),
|
230 |
+
'target' => array(),
|
231 |
+
),
|
232 |
+
'code' => array(),
|
233 |
+
'strong' => array(),
|
234 |
+
'br' => array(),
|
235 |
+
);
|
236 |
+
?>
|
237 |
+
<div id="data-tag-glossary" class="accordion-container">
|
238 |
+
<ul class="outer-border">
|
239 |
+
<?php foreach ( $data_tags as $section => $tags ) : ?>
|
240 |
+
<li class="control-section accordion-section">
|
241 |
+
<h3 class="accordion-section-title hndle" title="<?php echo esc_attr( $section ) ?>"><?php echo esc_html( $section ) ?></h3>
|
242 |
+
<div class="accordion-section-content">
|
243 |
+
<div class="inside">
|
244 |
+
<dl>
|
245 |
+
<?php foreach ( $tags as $tag => $desc ) : ?>
|
246 |
+
<dt><code>{<?php echo esc_html( $tag ) ?>}</code></dt>
|
247 |
+
<dd><?php echo wp_kses( $desc, $allowed_html ) ?></dd>
|
248 |
+
<?php endforeach; ?>
|
249 |
+
</dl>
|
250 |
+
</div>
|
251 |
+
</div>
|
252 |
+
</li>
|
253 |
+
<?php endforeach; ?>
|
254 |
+
</ul>
|
255 |
+
</div>
|
256 |
+
<?php
|
257 |
+
}
|
258 |
+
|
259 |
+
/**
|
260 |
+
* Save rule meta data
|
261 |
+
*
|
262 |
+
* @action save_post
|
263 |
+
*
|
264 |
+
* @param int $post_id
|
265 |
+
* @param object $post
|
266 |
+
*
|
267 |
+
* @return void
|
268 |
+
*/
|
269 |
+
public function save( $post_id, $post ) {
|
270 |
+
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
|
271 |
+
return;
|
272 |
+
}
|
273 |
+
|
274 |
+
if ( self::POSTTYPE !== $post->post_type ) {
|
275 |
+
return;
|
276 |
+
}
|
277 |
+
|
278 |
+
$defaults = array(
|
279 |
+
'triggers' => array(),
|
280 |
+
'groups' => array(),
|
281 |
+
'alerts' => array(),
|
282 |
+
);
|
283 |
+
|
284 |
+
$args = wp_parse_args( $_POST, $defaults );
|
285 |
+
$meta = array_intersect_key( $args, array_flip( array( 'triggers', 'groups', 'alerts' ) ) );
|
286 |
+
|
287 |
+
foreach ( $meta as $key => $vals ) {
|
288 |
+
update_post_meta( $post_id, $key, $vals );
|
289 |
+
}
|
290 |
+
}
|
291 |
+
|
292 |
+
/**
|
293 |
+
* Callback for form AJAX operations
|
294 |
+
*
|
295 |
+
* @action wp_ajax_stream_notifications_endpoint
|
296 |
+
* @return void
|
297 |
+
*/
|
298 |
+
public function form_ajax_ep() {
|
299 |
+
$type = wp_stream_filter_input( INPUT_POST, 'type' );
|
300 |
+
$is_single = wp_stream_filter_input( INPUT_POST, 'single' );
|
301 |
+
$query = wp_stream_filter_input( INPUT_POST, 'q' );
|
302 |
+
$args = json_decode( wp_stream_filter_input( INPUT_POST, 'args' ), true );
|
303 |
+
|
304 |
+
if ( ! is_array( $args ) ) {
|
305 |
+
$args = array();
|
306 |
+
}
|
307 |
+
|
308 |
+
if ( $is_single ) {
|
309 |
+
switch ( $type ) {
|
310 |
+
case 'author':
|
311 |
+
case 'post_author':
|
312 |
+
case 'user':
|
313 |
+
$user_ids = explode( ',', $query );
|
314 |
+
$user_query = new WP_User_Query(
|
315 |
+
array(
|
316 |
+
'include' => $user_ids,
|
317 |
+
'fields' => array( 'ID', 'user_email', 'display_name' ),
|
318 |
+
)
|
319 |
+
);
|
320 |
+
if ( $user_query->results ) {
|
321 |
+
$data = $this->format_json_for_select2( $user_query->results, 'ID', 'display_name' );
|
322 |
+
} else {
|
323 |
+
$data = array();
|
324 |
+
}
|
325 |
+
break;
|
326 |
+
case 'post':
|
327 |
+
case 'post_parent':
|
328 |
+
$args = array(
|
329 |
+
'post_type' => 'any',
|
330 |
+
'post_status' => 'any',
|
331 |
+
'posts_per_page' => - 1,
|
332 |
+
'post__in' => explode( ',', $query ),
|
333 |
+
);
|
334 |
+
$posts = get_posts( $args );
|
335 |
+
$items = array_combine( wp_list_pluck( $posts, 'ID' ), wp_list_pluck( $posts, 'post_title' ) );
|
336 |
+
$data = $this->format_json_for_select2( $items );
|
337 |
+
break;
|
338 |
+
case 'tax':
|
339 |
+
$items = get_taxonomies( null, 'objects' );
|
340 |
+
$items = wp_list_pluck( $items, 'labels' );
|
341 |
+
$items = wp_list_pluck( $items, 'name' );
|
342 |
+
$query = explode( ',', $query );
|
343 |
+
$chosen = array_intersect_key( $items, array_flip( $query ) );
|
344 |
+
$data = $this->format_json_for_select2( $chosen );
|
345 |
+
break;
|
346 |
+
case 'term':
|
347 |
+
case 'term_parent':
|
348 |
+
$tax = isset( $args['tax'] ) ? $args['tax'] : null;
|
349 |
+
$query = explode( ',', $query );
|
350 |
+
$terms = $this->get_terms( $query, $tax );
|
351 |
+
$data = $this->format_json_for_select2( $terms );
|
352 |
+
break;
|
353 |
+
}
|
354 |
+
} else {
|
355 |
+
switch ( $type ) {
|
356 |
+
case 'author':
|
357 |
+
case 'post_author':
|
358 |
+
case 'user':
|
359 |
+
$users = get_users(
|
360 |
+
array(
|
361 |
+
'search' => '*' . $query . '*',
|
362 |
+
'search_in' => array(
|
363 |
+
'user_login',
|
364 |
+
'display_name',
|
365 |
+
'user_email',
|
366 |
+
'user_nicename',
|
367 |
+
),
|
368 |
+
'meta_key' => ( isset( $args['push'] ) && $args['push'] ) ? 'ckpn_user_key' : null,
|
369 |
+
)
|
370 |
+
);
|
371 |
+
$data = $this->format_json_for_select2( $users, 'ID', 'display_name' );
|
372 |
+
break;
|
373 |
+
case 'action':
|
374 |
+
case 'context':
|
375 |
+
$items = WP_Stream_Connectors::$term_labels[ 'stream_' . $type ];
|
376 |
+
$items = preg_grep( sprintf( '/%s/i', $query ), $items );
|
377 |
+
$data = $this->format_json_for_select2( $items );
|
378 |
+
break;
|
379 |
+
case 'post':
|
380 |
+
case 'post_parent':
|
381 |
+
$posts = get_posts( 'post_type=any&post_status=any&posts_per_page=-1&s=' . $query );
|
382 |
+
$items = array_combine( wp_list_pluck( $posts, 'ID' ), wp_list_pluck( $posts, 'post_title' ) );
|
383 |
+
$data = $this->format_json_for_select2( $items );
|
384 |
+
break;
|
385 |
+
case 'tax':
|
386 |
+
$items = get_taxonomies( null, 'objects' );
|
387 |
+
$items = wp_list_pluck( $items, 'labels' );
|
388 |
+
$items = wp_list_pluck( $items, 'name' );
|
389 |
+
$items = preg_grep( sprintf( '/%s/i', $query ), $items );
|
390 |
+
$data = $this->format_json_for_select2( $items );
|
391 |
+
break;
|
392 |
+
case 'term':
|
393 |
+
case 'term_parent':
|
394 |
+
$tax = isset( $args['tax'] ) ? $args['tax'] : null;
|
395 |
+
$terms = $this->get_terms( $query, $tax );
|
396 |
+
$data = $this->format_json_for_select2( $terms );
|
397 |
+
break;
|
398 |
+
}
|
399 |
+
}
|
400 |
+
|
401 |
+
// Add gravatar for authors
|
402 |
+
if ( 'author' === $type && get_option( 'show_avatars' ) ) {
|
403 |
+
foreach ( $data as $i => $item ) {
|
404 |
+
if ( $avatar = get_avatar( $item['id'], 20 ) ) {
|
405 |
+
$item['avatar'] = $avatar;
|
406 |
+
}
|
407 |
+
$data[ $i ] = $item;
|
408 |
+
}
|
409 |
+
}
|
410 |
+
|
411 |
+
if ( ! empty( $data ) ) {
|
412 |
+
wp_send_json_success( $data );
|
413 |
+
} else {
|
414 |
+
wp_send_json_error();
|
415 |
+
}
|
416 |
+
}
|
417 |
+
|
418 |
+
/**
|
419 |
+
* Callback for Reset Occurrences count AJAX request
|
420 |
+
*/
|
421 |
+
public function ajax_reset_occ() {
|
422 |
+
$id = wp_stream_filter_input( INPUT_GET, 'id' );
|
423 |
+
$nonce = wp_stream_filter_input( INPUT_GET, 'wp_stream_nonce' );
|
424 |
+
|
425 |
+
if ( ! wp_verify_nonce( $nonce, 'reset-occ_' . $id ) ) {
|
426 |
+
wp_send_json_error( esc_html__( 'Invalid nonce', 'stream' ) );
|
427 |
+
}
|
428 |
+
|
429 |
+
// Loose comparison needed
|
430 |
+
if ( empty( $id ) || (int) $id != $id ) {
|
431 |
+
wp_send_json_error( esc_html__( 'Invalid record ID', 'stream' ) );
|
432 |
+
}
|
433 |
+
|
434 |
+
update_post_meta( $id, 'occurrences', 0 );
|
435 |
+
wp_send_json_success();
|
436 |
+
}
|
437 |
+
|
438 |
+
/**
|
439 |
+
* Format JS options for the form, to be used with wp_localize_script
|
440 |
+
*
|
441 |
+
* @return array Options for our form JS handling
|
442 |
+
*/
|
443 |
+
public function get_js_options() {
|
444 |
+
global $wp_roles;
|
445 |
+
$args = array();
|
446 |
+
|
447 |
+
$connectors = WP_Stream_Connectors::$term_labels['stream_connector'];
|
448 |
+
asort( $connectors );
|
449 |
+
|
450 |
+
$roles = $wp_roles->roles;
|
451 |
+
$roles_arr = array_combine( array_keys( $roles ), wp_list_pluck( $roles, 'name' ) );
|
452 |
+
|
453 |
+
$default_operators = array(
|
454 |
+
'=' => esc_html__( 'is', 'stream' ),
|
455 |
+
'!=' => esc_html__( 'is not', 'stream' ),
|
456 |
+
);
|
457 |
+
|
458 |
+
$text_operator = array(
|
459 |
+
'=' => esc_html__( 'is', 'stream' ),
|
460 |
+
'!=' => esc_html__( 'is not', 'stream' ),
|
461 |
+
'contains' => esc_html__( 'contains', 'stream' ),
|
462 |
+
'!contains' => esc_html__( 'does not contain', 'stream' ),
|
463 |
+
'starts' => esc_html__( 'starts with', 'stream' ),
|
464 |
+
'ends' => esc_html__( 'ends with', 'stream' ),
|
465 |
+
'regex' => esc_html__( 'regex', 'stream' ),
|
466 |
+
);
|
467 |
+
|
468 |
+
$numeric_operators = array(
|
469 |
+
'=' => esc_html__( 'equals', 'stream' ),
|
470 |
+
'!=' => esc_html__( 'not equal', 'stream' ),
|
471 |
+
'<' => esc_html__( 'less than', 'stream' ),
|
472 |
+
'<=' => esc_html__( 'equal or less than', 'stream' ),
|
473 |
+
'>' => esc_html__( 'greater than', 'stream' ),
|
474 |
+
'>=' => esc_html__( 'equal or greater than', 'stream' ),
|
475 |
+
);
|
476 |
+
|
477 |
+
$args['types'] = array(
|
478 |
+
'search' => array(
|
479 |
+
'title' => esc_html__( 'Summary', 'stream' ),
|
480 |
+
'type' => 'text',
|
481 |
+
'operators' => $text_operator,
|
482 |
+
),
|
483 |
+
'object_id' => array(
|
484 |
+
'title' => esc_html__( 'Object ID', 'stream' ),
|
485 |
+
'type' => 'text',
|
486 |
+
'tags' => true,
|
487 |
+
'operators' => $default_operators,
|
488 |
+
),
|
489 |
+
|
490 |
+
'author_role' => array(
|
491 |
+
'title' => esc_html__( 'Author Role', 'stream' ),
|
492 |
+
'type' => 'select',
|
493 |
+
'multiple' => true,
|
494 |
+
'operators' => $default_operators,
|
495 |
+
'options' => $roles_arr,
|
496 |
+
),
|
497 |
+
|
498 |
+
'author' => array(
|
499 |
+
'title' => esc_html__( 'Author', 'stream' ),
|
500 |
+
'type' => 'text',
|
501 |
+
'ajax' => true,
|
502 |
+
'operators' => $default_operators,
|
503 |
+
),
|
504 |
+
|
505 |
+
'ip' => array(
|
506 |
+
'title' => esc_html__( 'IP', 'stream' ),
|
507 |
+
'type' => 'text',
|
508 |
+
'subtype' => 'ip',
|
509 |
+
'tags' => true,
|
510 |
+
'operators' => $default_operators,
|
511 |
+
),
|
512 |
+
|
513 |
+
'date' => array(
|
514 |
+
'title' => esc_html__( 'Date', 'stream' ),
|
515 |
+
'type' => 'date',
|
516 |
+
'operators' => array(
|
517 |
+
'=' => esc_html__( 'is on', 'stream' ),
|
518 |
+
'!=' => esc_html__( 'is not on', 'stream' ),
|
519 |
+
'<' => esc_html__( 'is before', 'stream' ),
|
520 |
+
'<=' => esc_html__( 'is on or before', 'stream' ),
|
521 |
+
'>' => esc_html__( 'is after', 'stream' ),
|
522 |
+
'>=' => esc_html__( 'is on or after', 'stream' ),
|
523 |
+
),
|
524 |
+
),
|
525 |
+
|
526 |
+
'weekday' => array(
|
527 |
+
'title' => esc_html__( 'Day of Week', 'stream' ),
|
528 |
+
'type' => 'select',
|
529 |
+
'multiple' => true,
|
530 |
+
'operators' => $default_operators,
|
531 |
+
'options' => array_combine(
|
532 |
+
array_map(
|
533 |
+
function ( $weekday_index ) {
|
534 |
+
return sprintf( 'weekday_%d', $weekday_index % 7 );
|
535 |
+
},
|
536 |
+
range( get_option( 'start_of_week' ), get_option( 'start_of_week' ) + 6 )
|
537 |
+
),
|
538 |
+
array_map(
|
539 |
+
function ( $weekday_index ) {
|
540 |
+
global $wp_locale;
|
541 |
+
return $wp_locale->get_weekday( $weekday_index % 7 );
|
542 |
+
},
|
543 |
+
range( get_option( 'start_of_week' ), get_option( 'start_of_week' ) + 6 )
|
544 |
+
)
|
545 |
+
),
|
546 |
+
),
|
547 |
+
|
548 |
+
// TODO: find a way to introduce meta to the rules, problem: not translatable since it is
|
549 |
+
// generated on run time with no prior definition
|
550 |
+
// 'meta_query' => array(),
|
551 |
+
|
552 |
+
'connector' => array(
|
553 |
+
'title' => esc_html__( 'Connector', 'stream' ),
|
554 |
+
'type' => 'select',
|
555 |
+
'multiple' => true,
|
556 |
+
'operators' => $default_operators,
|
557 |
+
'options' => $connectors,
|
558 |
+
),
|
559 |
+
'context' => array(
|
560 |
+
'title' => esc_html__( 'Context', 'stream' ),
|
561 |
+
'type' => 'select',
|
562 |
+
'multiple' => true,
|
563 |
+
'operators' => $default_operators,
|
564 |
+
'options' => WP_Stream_Connectors::$term_labels['stream_context'],
|
565 |
+
),
|
566 |
+
'action' => array(
|
567 |
+
'title' => esc_html__( 'Action', 'stream' ),
|
568 |
+
'type' => 'select',
|
569 |
+
'multiple' => true,
|
570 |
+
'operators' => $default_operators,
|
571 |
+
'options' => WP_Stream_Connectors::$term_labels['stream_action'],
|
572 |
+
),
|
573 |
+
);
|
574 |
+
|
575 |
+
// Connector-based triggers
|
576 |
+
$args['special_types'] = array(
|
577 |
+
'post' => array(
|
578 |
+
'title' => esc_html__( '- Post', 'stream' ),
|
579 |
+
'type' => 'text',
|
580 |
+
'ajax' => true,
|
581 |
+
'connector' => 'posts',
|
582 |
+
'operators' => $default_operators,
|
583 |
+
),
|
584 |
+
'post_title' => array(
|
585 |
+
'title' => esc_html__( '- Post: Title', 'stream' ),
|
586 |
+
'type' => 'text',
|
587 |
+
'connector' => 'posts',
|
588 |
+
'operators' => $text_operator,
|
589 |
+
),
|
590 |
+
'post_slug' => array(
|
591 |
+
'title' => esc_html__( '- Post: Slug', 'stream' ),
|
592 |
+
'type' => 'text',
|
593 |
+
'connector' => 'posts',
|
594 |
+
'operators' => $text_operator,
|
595 |
+
),
|
596 |
+
'post_content' => array(
|
597 |
+
'title' => esc_html__( '- Post: Content', 'stream' ),
|
598 |
+
'type' => 'text',
|
599 |
+
'connector' => 'posts',
|
600 |
+
'operators' => $text_operator,
|
601 |
+
),
|
602 |
+
'post_excerpt' => array(
|
603 |
+
'title' => esc_html__( '- Post: Excerpt', 'stream' ),
|
604 |
+
'type' => 'text',
|
605 |
+
'connector' => 'posts',
|
606 |
+
'operators' => $text_operator,
|
607 |
+
),
|
608 |
+
'post_author' => array(
|
609 |
+
'title' => esc_html__( '- Post: Author', 'stream' ),
|
610 |
+
'type' => 'text',
|
611 |
+
'ajax' => true,
|
612 |
+
'connector' => 'posts',
|
613 |
+
'operators' => $default_operators,
|
614 |
+
),
|
615 |
+
'post_status' => array(
|
616 |
+
'title' => esc_html__( '- Post: Status', 'stream' ),
|
617 |
+
'type' => 'select',
|
618 |
+
'connector' => 'posts',
|
619 |
+
'options' => wp_list_pluck( $GLOBALS['wp_post_statuses'], 'label' ),
|
620 |
+
'operators' => $default_operators,
|
621 |
+
),
|
622 |
+
'post_format' => array(
|
623 |
+
'title' => esc_html__( '- Post: Format', 'stream' ),
|
624 |
+
'type' => 'select',
|
625 |
+
'connector' => 'posts',
|
626 |
+
'options' => get_post_format_strings(),
|
627 |
+
'operators' => $default_operators,
|
628 |
+
),
|
629 |
+
'post_parent' => array(
|
630 |
+
'title' => esc_html__( '- Post: Parent', 'stream' ),
|
631 |
+
'type' => 'text',
|
632 |
+
'ajax' => true,
|
633 |
+
'connector' => 'posts',
|
634 |
+
'operators' => $default_operators,
|
635 |
+
),
|
636 |
+
'post_thumbnail' => array(
|
637 |
+
'title' => esc_html__( '- Post: Featured Image', 'stream' ),
|
638 |
+
'type' => 'select',
|
639 |
+
'connector' => 'posts',
|
640 |
+
'options' => array(
|
641 |
+
'0' => esc_html__( 'None', 'stream' ),
|
642 |
+
'1' => esc_html__( 'Has one', 'stream' )
|
643 |
+
),
|
644 |
+
'operators' => $default_operators,
|
645 |
+
),
|
646 |
+
'post_comment_status' => array(
|
647 |
+
'title' => esc_html__( '- Post: Comment Status', 'stream' ),
|
648 |
+
'type' => 'select',
|
649 |
+
'connector' => 'posts',
|
650 |
+
'options' => array(
|
651 |
+
'open' => esc_html__( 'Open', 'stream' ),
|
652 |
+
'closed' => esc_html__( 'Closed', 'stream' )
|
653 |
+
),
|
654 |
+
'operators' => $default_operators,
|
655 |
+
),
|
656 |
+
'post_comment_count' => array(
|
657 |
+
'title' => esc_html__( '- Post: Comment Count', 'stream' ),
|
658 |
+
'type' => 'text',
|
659 |
+
'connector' => 'posts',
|
660 |
+
'operators' => $numeric_operators,
|
661 |
+
),
|
662 |
+
'user' => array(
|
663 |
+
'title' => esc_html__( '- User', 'stream' ),
|
664 |
+
'type' => 'text',
|
665 |
+
'ajax' => true,
|
666 |
+
'connector' => 'users',
|
667 |
+
'operators' => $default_operators,
|
668 |
+
),
|
669 |
+
'user_role' => array(
|
670 |
+
'title' => esc_html__( '- User: Role', 'stream' ),
|
671 |
+
'type' => 'select',
|
672 |
+
'connector' => 'users',
|
673 |
+
'options' => $roles_arr,
|
674 |
+
'operators' => $default_operators,
|
675 |
+
),
|
676 |
+
'tax' => array(
|
677 |
+
'title' => esc_html__( '- Taxonomy', 'stream' ),
|
678 |
+
'type' => 'text',
|
679 |
+
'ajax' => true,
|
680 |
+
'connector' => 'taxonomies',
|
681 |
+
'operators' => $default_operators,
|
682 |
+
),
|
683 |
+
'term' => array(
|
684 |
+
'title' => esc_html__( '- Term', 'stream' ),
|
685 |
+
'type' => 'text',
|
686 |
+
'ajax' => true,
|
687 |
+
'connector' => 'taxonomies',
|
688 |
+
'operators' => $default_operators,
|
689 |
+
),
|
690 |
+
'term_parent' => array(
|
691 |
+
'title' => esc_html__( '- Term: Parent', 'stream' ),
|
692 |
+
'type' => 'text',
|
693 |
+
'ajax' => true,
|
694 |
+
'connector' => 'taxonomies',
|
695 |
+
'operators' => $default_operators,
|
696 |
+
),
|
697 |
+
);
|
698 |
+
|
699 |
+
$args['adapters'] = array();
|
700 |
+
|
701 |
+
foreach ( WP_Stream_Notifications::$adapters as $name => $options ) {
|
702 |
+
$args['adapters'][ $name ] = array(
|
703 |
+
'title' => $options['title'],
|
704 |
+
'fields' => $options['class']::fields(),
|
705 |
+
'hints' => $options['class']::hints(),
|
706 |
+
);
|
707 |
+
}
|
708 |
+
|
709 |
+
// Localization
|
710 |
+
$args['i18n'] = array(
|
711 |
+
'empty_triggers' => esc_html__( 'You cannot save a rule without any triggers.', 'stream' ),
|
712 |
+
'invalid_first_trigger' => esc_html__( 'You cannot save a rule with an empty first trigger.', 'stream' ),
|
713 |
+
'ajax_error' => esc_html__( 'There was an error submitting your request, please try again.', 'stream' ),
|
714 |
+
'confirm_reset' => esc_html__( 'Are you sure you want to reset occurrences for this rule? This cannot be undone.', 'stream' ),
|
715 |
+
);
|
716 |
+
|
717 |
+
global $post;
|
718 |
+
|
719 |
+
if ( ( $meta = get_post_meta( $post->ID ) ) && isset( $meta['triggers'] ) ) {
|
720 |
+
|
721 |
+
$args['meta'] = array(
|
722 |
+
'triggers' => maybe_unserialize( $meta['triggers'][0] ),
|
723 |
+
'groups' => maybe_unserialize( $meta['groups'][0] ),
|
724 |
+
'alerts' => maybe_unserialize( $meta['alerts'][0] ),
|
725 |
+
);
|
726 |
+
}
|
727 |
+
|
728 |
+
return apply_filters( 'wp_stream_notifications_js_args', $args );
|
729 |
+
}
|
730 |
+
|
731 |
+
/**
|
732 |
+
* Take an (associative) array and format it for select2 AJAX result parser
|
733 |
+
*
|
734 |
+
* @param array $data (associative) Data array
|
735 |
+
* @param string $key Key of the ID column, null if associative array
|
736 |
+
* @param string $val Key of the Title column, null if associative array
|
737 |
+
*
|
738 |
+
* @return array Formatted array, [ { id: %, title: % }, .. ]
|
739 |
+
*/
|
740 |
+
public function format_json_for_select2( $data, $key = null, $val = null ) {
|
741 |
+
$return = array();
|
742 |
+
if ( is_null( $key ) && is_null( $val ) ) { // for flat associative array
|
743 |
+
$keys = array_keys( $data );
|
744 |
+
$vals = array_values( $data );
|
745 |
+
} else {
|
746 |
+
$keys = wp_list_pluck( $data, $key );
|
747 |
+
$vals = wp_list_pluck( $data, $val );
|
748 |
+
}
|
749 |
+
foreach ( $keys as $idx => $key ) {
|
750 |
+
$return[] = array(
|
751 |
+
'id' => $key,
|
752 |
+
'text' => $vals[ $idx ],
|
753 |
+
);
|
754 |
+
}
|
755 |
+
|
756 |
+
return $return;
|
757 |
+
}
|
758 |
+
|
759 |
+
/**
|
760 |
+
* Search for terms in a specific taxonomy
|
761 |
+
*
|
762 |
+
* @param string $search Search keyword
|
763 |
+
* @param array $taxonomies Taxonomies to search in
|
764 |
+
*
|
765 |
+
* @return array
|
766 |
+
*/
|
767 |
+
public function get_terms( $search, $taxonomies = array() ) {
|
768 |
+
global $wpdb;
|
769 |
+
$taxonomies = (array) $taxonomies;
|
770 |
+
|
771 |
+
$sql = "SELECT tt.term_taxonomy_id id, t.name, t.slug, tt.taxonomy, tt.description
|
772 |
+
FROM $wpdb->terms t
|
773 |
+
JOIN $wpdb->term_taxonomy tt USING ( term_id )
|
774 |
+
WHERE
|
775 |
+
";
|
776 |
+
|
777 |
+
if ( is_array( $search ) ) {
|
778 |
+
$search = array_map( 'intval', $search );
|
779 |
+
$where = sprintf( 'tt.term_taxonomy_id IN ( %s )', implode( ', ', $search ) );
|
780 |
+
} else {
|
781 |
+
$where = '
|
782 |
+
t.name LIKE %s
|
783 |
+
OR
|
784 |
+
t.slug LIKE %s
|
785 |
+
OR
|
786 |
+
tt.taxonomy LIKE %s
|
787 |
+
OR
|
788 |
+
tt.description LIKE %s
|
789 |
+
';
|
790 |
+
$where = $wpdb->prepare( $where, "%$search%", "%$search%", "%$search%", "%$search%" );
|
791 |
+
}
|
792 |
+
|
793 |
+
$sql .= $where;
|
794 |
+
$results = $wpdb->get_results( $sql );
|
795 |
+
|
796 |
+
$return = array();
|
797 |
+
foreach ( $results as $result ) {
|
798 |
+
$return[ $result->id ] = sprintf( '%s - %s', $result->name, $result->taxonomy );
|
799 |
+
}
|
800 |
+
|
801 |
+
return $return;
|
802 |
+
}
|
803 |
+
|
804 |
+
/**
|
805 |
+
* @filter user_search_columns
|
806 |
+
*/
|
807 |
+
public function define_search_in_arg( $search_columns, $search, $query ) {
|
808 |
+
$search_in = $query->get( 'search_in' );
|
809 |
+
$search_columns = ! is_null( $search_in ) ? (array) $search_in : $search_columns;
|
810 |
+
|
811 |
+
return $search_columns;
|
812 |
+
}
|
813 |
+
|
814 |
+
/**
|
815 |
+
* Change Post Title placeholder in post edit screen
|
816 |
+
*
|
817 |
+
* @filter enter_title_here
|
818 |
+
*
|
819 |
+
* @param $text
|
820 |
+
* @param $post
|
821 |
+
*
|
822 |
+
* @return string
|
823 |
+
*/
|
824 |
+
public function title_placeholder( $text, $post ) {
|
825 |
+
if ( self::POSTTYPE === $post->post_type ) {
|
826 |
+
$text = __( 'Enter Rule Title here', 'stream' );
|
827 |
+
}
|
828 |
+
|
829 |
+
return $text;
|
830 |
+
}
|
831 |
+
|
832 |
+
/**
|
833 |
+
* Apply list actions, and load our list-table object
|
834 |
+
*
|
835 |
+
* @action load-edit.php
|
836 |
+
*
|
837 |
+
* @return void
|
838 |
+
*/
|
839 |
+
public function load_list_table() {
|
840 |
+
global $typenow;
|
841 |
+
|
842 |
+
if ( self::POSTTYPE !== $typenow || WP_Stream::$api->is_restricted( true ) ) {
|
843 |
+
return;
|
844 |
+
}
|
845 |
+
|
846 |
+
require_once WP_STREAM_NOTIFICATIONS_INC_DIR . 'class-wp-stream-notifications-list-table.php';
|
847 |
+
WP_Stream_Notifications_List_Table::get_instance();
|
848 |
+
}
|
849 |
+
|
850 |
+
}
|
extensions/notifications/includes/class-wp-stream-notifications-settings.php
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Settings class for Stream Notifications
|
4 |
+
*
|
5 |
+
* @author X-Team <x-team.com>
|
6 |
+
* @author Shady Sharaf <shady@x-team.com>, Jaroslav Polakovič <dero@x-team.com>
|
7 |
+
*/
|
8 |
+
class WP_Stream_Notifications_Settings {
|
9 |
+
|
10 |
+
public static $fields = array();
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Public constructor
|
14 |
+
*/
|
15 |
+
public static function load() {
|
16 |
+
// User and role caps
|
17 |
+
add_filter( 'user_has_cap', array( __CLASS__, '_filter_user_caps' ), 10, 4 );
|
18 |
+
add_filter( 'role_has_cap', array( __CLASS__, '_filter_role_caps' ), 10, 3 );
|
19 |
+
|
20 |
+
if ( WP_Stream_API::is_restricted() ) {
|
21 |
+
return;
|
22 |
+
}
|
23 |
+
|
24 |
+
// Add Notifications settings tab to Stream settings
|
25 |
+
add_filter( 'wp_stream_settings_option_fields', array( __CLASS__, '_register_settings' ) );
|
26 |
+
}
|
27 |
+
|
28 |
+
public static function get_fields() {
|
29 |
+
if ( empty( self::$fields ) ) {
|
30 |
+
$fields = array();
|
31 |
+
|
32 |
+
self::$fields = apply_filters( 'wp_stream_notifications_option_fields', $fields );
|
33 |
+
}
|
34 |
+
|
35 |
+
return self::$fields;
|
36 |
+
}
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Appends Notifications settings to Stream settings
|
40 |
+
*
|
41 |
+
* @filter wp_stream_settings_option_fields
|
42 |
+
*/
|
43 |
+
public static function _register_settings( $stream_fields ) {
|
44 |
+
return array_merge( $stream_fields, self::get_fields() );
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Filter user caps to dynamically grant our view cap based on allowed roles
|
49 |
+
*
|
50 |
+
* @filter user_has_cap
|
51 |
+
*
|
52 |
+
* @param $allcaps
|
53 |
+
* @param $caps
|
54 |
+
* @param $args
|
55 |
+
* @param $user
|
56 |
+
*
|
57 |
+
* @return array
|
58 |
+
*/
|
59 |
+
public static function _filter_user_caps( $allcaps, $caps, $args, $user = null ) {
|
60 |
+
$user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
|
61 |
+
|
62 |
+
foreach ( $caps as $cap ) {
|
63 |
+
if ( WP_Stream_Notifications::VIEW_CAP === $cap ) {
|
64 |
+
foreach ( $user->roles as $role ) {
|
65 |
+
if ( self::_role_can_access_notifications( $role ) ) {
|
66 |
+
$allcaps[ $cap ] = true;
|
67 |
+
break 2;
|
68 |
+
}
|
69 |
+
}
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
return $allcaps;
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Filter role caps to dynamically grant our view cap based on allowed roles
|
78 |
+
*
|
79 |
+
* @filter role_has_cap
|
80 |
+
*
|
81 |
+
* @param $allcaps
|
82 |
+
* @param $cap
|
83 |
+
* @param $role
|
84 |
+
*
|
85 |
+
* @return array
|
86 |
+
*/
|
87 |
+
public static function _filter_role_caps( $allcaps, $cap, $role ) {
|
88 |
+
if ( WP_Stream_Notifications::VIEW_CAP === $cap && self::_role_can_access_notifications( $role ) ) {
|
89 |
+
$allcaps[ $cap ] = true;
|
90 |
+
}
|
91 |
+
|
92 |
+
return $allcaps;
|
93 |
+
}
|
94 |
+
|
95 |
+
private static function _role_can_access_notifications( $role ) {
|
96 |
+
if ( ! isset( WP_Stream_Settings::$options['notifications_role_access'] ) ) {
|
97 |
+
WP_Stream_Settings::$options['notifications_role_access'] = array( 'administrator' );
|
98 |
+
}
|
99 |
+
|
100 |
+
if ( in_array( $role, WP_Stream_Settings::$options['notifications_role_access'] ) ) {
|
101 |
+
return true;
|
102 |
+
}
|
103 |
+
|
104 |
+
return false;
|
105 |
+
}
|
106 |
+
|
107 |
+
}
|
extensions/notifications/ui/css/form.css
ADDED
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Title */
|
2 |
+
|
3 |
+
.post-type-stream-notification #titlediv {
|
4 |
+
margin-bottom: 20px;
|
5 |
+
}
|
6 |
+
|
7 |
+
|
8 |
+
/* Triggers and Alerts */
|
9 |
+
|
10 |
+
#stream-notifications-triggers .field,
|
11 |
+
#stream-notifications-triggers .trigger-type,
|
12 |
+
#stream-notifications-triggers .trigger-options,
|
13 |
+
#stream-notifications-triggers .trigger-value {
|
14 |
+
float: left;
|
15 |
+
margin-right: 6px;
|
16 |
+
}
|
17 |
+
|
18 |
+
#stream-notifications-triggers .trigger .form-row {
|
19 |
+
clear: both;
|
20 |
+
overflow: hidden;
|
21 |
+
margin-bottom: 10px;
|
22 |
+
background: #eee;
|
23 |
+
padding: 10px;
|
24 |
+
}
|
25 |
+
|
26 |
+
#stream-notifications-triggers .trigger-value {
|
27 |
+
width: 200px;
|
28 |
+
}
|
29 |
+
|
30 |
+
#stream-notifications-triggers .inside {
|
31 |
+
padding-bottom: 3px;
|
32 |
+
}
|
33 |
+
|
34 |
+
#stream-notifications-triggers .inside,
|
35 |
+
#stream-notifications-alerts .inside {
|
36 |
+
margin-top: 12px;
|
37 |
+
}
|
38 |
+
|
39 |
+
#stream-notifications-alerts .inside {
|
40 |
+
padding: 0;
|
41 |
+
}
|
42 |
+
|
43 |
+
#stream-notifications-triggers .group {
|
44 |
+
background: rgba(0, 0, 0, 0.08);
|
45 |
+
padding: 20px 20px 12px;
|
46 |
+
margin-bottom: 10px;
|
47 |
+
min-height: 16px;
|
48 |
+
clear: both;
|
49 |
+
}
|
50 |
+
|
51 |
+
#stream-notifications-triggers .inside > .group {
|
52 |
+
margin: 0;
|
53 |
+
min-height: 0;
|
54 |
+
background: none;
|
55 |
+
padding: 0;
|
56 |
+
|
57 |
+
-webkit-box-shadow: none;
|
58 |
+
box-shadow: none;
|
59 |
+
}
|
60 |
+
|
61 |
+
#stream-notifications-triggers .group .form-row {
|
62 |
+
background: rgba(0, 0, 0, 0.03);
|
63 |
+
}
|
64 |
+
|
65 |
+
#stream-notifications-triggers .group,
|
66 |
+
#stream-notifications-triggers .group .form-row {
|
67 |
+
margin-left: 90px;
|
68 |
+
|
69 |
+
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
|
70 |
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
|
71 |
+
}
|
72 |
+
|
73 |
+
#stream-notifications-triggers .add-trigger,
|
74 |
+
#stream-notifications-triggers .add-trigger-group {
|
75 |
+
margin-right: 4px !important;
|
76 |
+
margin-bottom: 10px !important;
|
77 |
+
}
|
78 |
+
|
79 |
+
#stream-notifications-triggers .group .delete-trigger,
|
80 |
+
#stream-notifications-alerts .alert .delete-alert {
|
81 |
+
line-height: 29px;
|
82 |
+
}
|
83 |
+
|
84 |
+
#stream-notifications-triggers .group .delete-trigger,
|
85 |
+
#stream-notifications-triggers .group .delete-group,
|
86 |
+
#stream-notifications-alerts .alert .delete-alert {
|
87 |
+
color: #a00;
|
88 |
+
text-decoration: none;
|
89 |
+
}
|
90 |
+
|
91 |
+
#stream-notifications-triggers .group .delete-trigger:hover,
|
92 |
+
#stream-notifications-triggers .group .delete-group:hover,
|
93 |
+
#stream-notifications-alerts .alert .delete-alert:hover {
|
94 |
+
color: #f00;
|
95 |
+
text-decoration: none;
|
96 |
+
}
|
97 |
+
|
98 |
+
#stream-notifications-triggers .inside > .group,
|
99 |
+
#stream-notifications-triggers .inside > .group > .group,
|
100 |
+
#stream-notifications-alerts .inside > .alert {
|
101 |
+
margin-left: 0;
|
102 |
+
}
|
103 |
+
|
104 |
+
#stream-notifications-triggers .inside > .group > .trigger > .form-row {
|
105 |
+
margin-top: 10px;
|
106 |
+
}
|
107 |
+
|
108 |
+
#stream-notifications-triggers .inside > .group > .trigger.first > .form-row {
|
109 |
+
margin-top: 0;
|
110 |
+
}
|
111 |
+
|
112 |
+
#stream-notifications-triggers .inside > .group > .trigger > .form-row,
|
113 |
+
#stream-notifications-alerts .inside > .alert > .form-row {
|
114 |
+
margin-left: 0;
|
115 |
+
}
|
116 |
+
|
117 |
+
#stream-notifications-triggers .group-meta {
|
118 |
+
float: left;
|
119 |
+
margin-top: -10px;
|
120 |
+
margin-left: -10px;
|
121 |
+
padding: 0 0 12px;
|
122 |
+
}
|
123 |
+
|
124 |
+
#stream-notifications-triggers .group-meta a.delete-group {
|
125 |
+
line-height: 28px;
|
126 |
+
font-size: 10px;
|
127 |
+
}
|
128 |
+
|
129 |
+
#stream-notifications-triggers .group .trigger:first-of-type .field.relation,
|
130 |
+
#stream-notifications-triggers .trigger.first .field.relation {
|
131 |
+
display: none;
|
132 |
+
}
|
133 |
+
|
134 |
+
#stream-notifications-triggers .trigger.first .field.type {
|
135 |
+
margin-left: 99px;
|
136 |
+
}
|
137 |
+
|
138 |
+
#stream-notifications-triggers .delete-trigger,
|
139 |
+
#stream-notifications-alerts .delete-alert {
|
140 |
+
float: right;
|
141 |
+
}
|
142 |
+
|
143 |
+
#stream-notifications-alerts .inside > .alert {
|
144 |
+
margin: 0;
|
145 |
+
padding: 0;
|
146 |
+
border: none;
|
147 |
+
}
|
148 |
+
|
149 |
+
#stream-notifications-alerts .add-alert {
|
150 |
+
margin: 0 0 12px 12px;
|
151 |
+
}
|
152 |
+
|
153 |
+
#stream-notifications-alerts .select2-container {
|
154 |
+
max-width: 300px;
|
155 |
+
}
|
156 |
+
|
157 |
+
#stream-notifications-alerts .select2-container.alert-type {
|
158 |
+
min-width: 150px;
|
159 |
+
}
|
160 |
+
|
161 |
+
#stream-notifications-alerts .alert .form-row .type {
|
162 |
+
padding: 12px;
|
163 |
+
border: 1px solid #ddd;
|
164 |
+
box-shadow: inset #f5f5f5 0 1px 0 0;
|
165 |
+
background: #eaeaea;
|
166 |
+
}
|
167 |
+
|
168 |
+
#stream-notifications-alerts .alert .form-row .type .circle {
|
169 |
+
display: inline-block;
|
170 |
+
height: 22px;
|
171 |
+
width: 22px;
|
172 |
+
font-size: 12px;
|
173 |
+
line-height: 23px;
|
174 |
+
border-radius: 12px;
|
175 |
+
text-align: center;
|
176 |
+
background: #aaa;
|
177 |
+
color: #fff;
|
178 |
+
margin-right: 10px;
|
179 |
+
vertical-align: middle;
|
180 |
+
}
|
181 |
+
|
182 |
+
#stream-notifications-alerts table.alert-options {
|
183 |
+
margin-top: 0;
|
184 |
+
}
|
185 |
+
|
186 |
+
#stream-notifications-alerts table.alert-options tbody tr th.label {
|
187 |
+
width: 24%;
|
188 |
+
vertical-align: top;
|
189 |
+
background: #f9f9f9;
|
190 |
+
border-right: 1px solid #e1e1e1;
|
191 |
+
border-top: 1px solid #f0f0f0;
|
192 |
+
}
|
193 |
+
|
194 |
+
#stream-notifications-alerts table.alert-options tbody tr th.label label {
|
195 |
+
display: block;
|
196 |
+
font-size: 12px;
|
197 |
+
font-weight: bold;
|
198 |
+
padding: 0;
|
199 |
+
margin: 0 0 3px;
|
200 |
+
color: #333;
|
201 |
+
}
|
202 |
+
|
203 |
+
#stream-notifications-alerts table.alert-options tbody tr th .description {
|
204 |
+
display: block;
|
205 |
+
font-size: 12px;
|
206 |
+
line-height: 16px;
|
207 |
+
font-weight: normal;
|
208 |
+
font-style: normal;
|
209 |
+
color: #888;
|
210 |
+
}
|
211 |
+
|
212 |
+
#stream-notifications-alerts table.alert-options tbody tr th .description a {
|
213 |
+
color: #0074a2;
|
214 |
+
}
|
215 |
+
|
216 |
+
#stream-notifications-alerts table.alert-options tbody tr th,
|
217 |
+
#stream-notifications-alerts table.alert-options tbody tr td {
|
218 |
+
padding: 13px 15px;
|
219 |
+
|
220 |
+
}
|
221 |
+
|
222 |
+
#stream-notifications-alerts table.alert-options tbody tr td {
|
223 |
+
border-top: 1px solid #f5f5f5;
|
224 |
+
}
|
225 |
+
|
226 |
+
|
227 |
+
/* Data Tags */
|
228 |
+
|
229 |
+
#stream-notifications-data-tags .inside {
|
230 |
+
margin: 0 !important;
|
231 |
+
padding: 0 !important;
|
232 |
+
}
|
233 |
+
|
234 |
+
#stream-notifications-data-tags .accordion-section,
|
235 |
+
#stream-notifications-data-tags .accordion-section.bottom h3 {
|
236 |
+
border-bottom: none;
|
237 |
+
}
|
238 |
+
|
239 |
+
#stream-notifications-data-tags .accordion-section.open,
|
240 |
+
#stream-notifications-data-tags .accordion-section.open.bottom h3 {
|
241 |
+
border-bottom: 1px solid #dfdfdf;
|
242 |
+
}
|
243 |
+
|
244 |
+
#stream-notifications-data-tags .accordion-section-content {
|
245 |
+
padding-bottom: 5px;
|
246 |
+
}
|
247 |
+
|
248 |
+
#stream-notifications-data-tags .accordion-section-title {
|
249 |
+
padding-left: 30px;
|
250 |
+
}
|
251 |
+
|
252 |
+
#stream-notifications-data-tags .accordion-section-title:after {
|
253 |
+
display: none;
|
254 |
+
}
|
255 |
+
|
256 |
+
#stream-notifications-data-tags .accordion-section-title:before {
|
257 |
+
color: #aaa;
|
258 |
+
position: absolute;
|
259 |
+
left: 6px;
|
260 |
+
float: left;
|
261 |
+
content: '\f140';
|
262 |
+
font: 400 20px/1 dashicons;
|
263 |
+
speak: none;
|
264 |
+
display: block;
|
265 |
+
padding: 0;
|
266 |
+
text-indent: 0;
|
267 |
+
text-align: center;
|
268 |
+
text-decoration: none !important;
|
269 |
+
|
270 |
+
-webkit-font-smoothing: antialiased;
|
271 |
+
-moz-osx-font-smoothing: grayscale;
|
272 |
+
}
|
273 |
+
|
274 |
+
#stream-notifications-data-tags .accordion-section-title:hover:before {
|
275 |
+
color: #777;
|
276 |
+
}
|
277 |
+
|
278 |
+
#stream-notifications-data-tags .accordion-section.open .accordion-section-title:before {
|
279 |
+
content: '\f142';
|
280 |
+
}
|
281 |
+
|
282 |
+
#stream-notifications-data-tags dl {
|
283 |
+
margin-bottom: 0;
|
284 |
+
}
|
285 |
+
|
286 |
+
#stream-notifications-data-tags dd {
|
287 |
+
margin: 0;
|
288 |
+
padding-bottom: 15px;
|
289 |
+
}
|
290 |
+
|
291 |
+
.stream_page_wp_stream_notifications .alert {
|
292 |
+
background: none;
|
293 |
+
margin: 0;
|
294 |
+
padding: 0;
|
295 |
+
border: none;
|
296 |
+
text-shadow: none;
|
297 |
+
color: #444;
|
298 |
+
|
299 |
+
-webkit-border-radius: 0;
|
300 |
+
-moz-border-radius: 0;
|
301 |
+
border-radius: 0;
|
302 |
+
}
|
303 |
+
|
304 |
+
|
305 |
+
/* Select2 */
|
306 |
+
|
307 |
+
.post-type-stream-notification .select2-container {
|
308 |
+
margin-right: 6px;
|
309 |
+
}
|
310 |
+
|
311 |
+
.post-type-stream-notification .select2-results li {
|
312 |
+
margin-bottom: 2px;
|
313 |
+
}
|
314 |
+
|
315 |
+
.post-type-stream-notification .select2-container .select2-choice > .select2-chosen {
|
316 |
+
font-size: 13px;
|
317 |
+
}
|
318 |
+
|
319 |
+
.post-type-stream-notification .select2-container.trigger-type {
|
320 |
+
width: 180px !important;
|
321 |
+
}
|
322 |
+
|
323 |
+
.post-type-stream-notification .select2-container.trigger-operator {
|
324 |
+
width: 140px !important;
|
325 |
+
}
|
326 |
+
|
327 |
+
.post-type-stream-notification .select2-choices img.avatar,
|
328 |
+
.post-type-stream-notification .select2-results img.avatar {
|
329 |
+
vertical-align: middle;
|
330 |
+
margin-right: 5px;
|
331 |
+
margin-bottom: 2px;
|
332 |
+
|
333 |
+
-webkit-border-radius: 3px;
|
334 |
+
-moz-border-radius: 3px;
|
335 |
+
border-radius: 3px;
|
336 |
+
}
|
337 |
+
|
338 |
+
.post-type-stream-notification .select2-search-choice-close {
|
339 |
+
-webkit-transition: none;
|
340 |
+
-moz-transition: none;
|
341 |
+
-o-transition: all 0 none;
|
342 |
+
transition: none;
|
343 |
+
}
|
344 |
+
|
345 |
+
|
346 |
+
/* Hide Post Publish Elements */
|
347 |
+
|
348 |
+
#edit-slug-box,
|
349 |
+
#preview-action,
|
350 |
+
#normal-sortables,
|
351 |
+
.misc-pub-section.misc-pub-visibility {
|
352 |
+
display: none;
|
353 |
+
}
|
extensions/notifications/ui/images/stream-notifications-example.jpg
ADDED
Binary file
|
extensions/notifications/ui/js/form.js
ADDED
@@ -0,0 +1,571 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* globals stream_notifications, ajaxurl, _, alert, confirm */
|
2 |
+
jQuery( function( $ ) {
|
3 |
+
'use strict';
|
4 |
+
|
5 |
+
_.templateSettings.variable = 'vars';
|
6 |
+
|
7 |
+
$( '#toplevel_page_wp_stream' )
|
8 |
+
.add( '#toplevel_page_wp_stream > a.toplevel_page_wp_stream' )
|
9 |
+
.removeClass( 'wp-not-current-submenu' )
|
10 |
+
.addClass( 'wp-has-current-submenu wp-menu-open' );
|
11 |
+
|
12 |
+
$.datepicker.setDefaults({
|
13 |
+
dateFormat: 'yy/mm/dd',
|
14 |
+
minDate: 0
|
15 |
+
});
|
16 |
+
|
17 |
+
var types = stream_notifications.types,
|
18 |
+
|
19 |
+
divTriggers = $( '#stream-notifications-triggers' ), // Trigger Playground
|
20 |
+
divAlerts = $( '#stream-notifications-alerts .inside' ), // Alerts Playground
|
21 |
+
|
22 |
+
iGroup = 0,
|
23 |
+
|
24 |
+
btns = {
|
25 |
+
add_trigger: '.add-trigger',
|
26 |
+
add_alert: '.add-alert',
|
27 |
+
add_group: '.add-trigger-group',
|
28 |
+
del: '#delete-trigger'
|
29 |
+
},
|
30 |
+
|
31 |
+
tmpl = _.template( $( 'script#trigger-template-row' ).html() ),
|
32 |
+
tmpl_options = _.template( $( 'script#trigger-template-options' ).html() ),
|
33 |
+
tmpl_group = _.template( $( 'script#trigger-template-group' ).html() ),
|
34 |
+
tmpl_alert = _.template( $( 'script#alert-template-row' ).html() ),
|
35 |
+
tmpl_alert_options = _.template( $( 'script#alert-template-options' ).html() ),
|
36 |
+
|
37 |
+
select2_format = function( item ) {
|
38 |
+
return item.text;
|
39 |
+
},
|
40 |
+
|
41 |
+
select2_args = {
|
42 |
+
allowClear: true,
|
43 |
+
minimumResultsForSearch: 8,
|
44 |
+
width: '160px',
|
45 |
+
format: select2_format,
|
46 |
+
formatSelection: select2_format,
|
47 |
+
formatResult: select2_format,
|
48 |
+
|
49 |
+
// Only allow multi items if we have a proper operator
|
50 |
+
maximumSelectionSize: function() {
|
51 |
+
var item = $( '.select2-container-active.trigger-value' );
|
52 |
+
if ( ! item.size() ) {
|
53 |
+
return 0;
|
54 |
+
}
|
55 |
+
|
56 |
+
var operator = item.parents( '.form-row' ).first().find( 'select.trigger-operator' ).val();
|
57 |
+
if ( ! operator ) {
|
58 |
+
return 0;
|
59 |
+
}
|
60 |
+
|
61 |
+
if ( operator.match(/in$/) ) {
|
62 |
+
return 0;
|
63 |
+
} else {
|
64 |
+
return 1;
|
65 |
+
}
|
66 |
+
}
|
67 |
+
|
68 |
+
},
|
69 |
+
|
70 |
+
datify = function( elements ) {
|
71 |
+
$( elements ).each( function() {
|
72 |
+
$( this ).datepicker();
|
73 |
+
});
|
74 |
+
|
75 |
+
$( '#ui-datepicker-div' ).addClass( 'stream-datepicker' );
|
76 |
+
},
|
77 |
+
|
78 |
+
selectify = function( elements, args ) {
|
79 |
+
args = args || {};
|
80 |
+
$.extend( args, select2_args );
|
81 |
+
|
82 |
+
$( elements ).filter( ':not(.select2-offscreen)' ).each( function() {
|
83 |
+
var $this = $( this ),
|
84 |
+
elementArgs = jQuery.extend( {}, args ),
|
85 |
+
tORa = $this.closest( '#stream-notifications-alerts, #stream-notifications-triggers' ).attr( 'id' ).replace( 'stream-notifications-', '' );
|
86 |
+
|
87 |
+
elementArgs.width = parseInt( $this.css( 'width' ), 10 ) + 30;
|
88 |
+
if ( $this.hasClass( 'ip' ) ) {
|
89 |
+
elementArgs.ajax = {
|
90 |
+
type: 'POST',
|
91 |
+
url: ajaxurl,
|
92 |
+
dataType: 'json',
|
93 |
+
quietMillis: 500,
|
94 |
+
data: function( term ) {
|
95 |
+
return {
|
96 |
+
find: term,
|
97 |
+
limit: 10,
|
98 |
+
action: 'stream_get_ips'
|
99 |
+
};
|
100 |
+
},
|
101 |
+
results: function( response ) {
|
102 |
+
var answer = {
|
103 |
+
results: []
|
104 |
+
};
|
105 |
+
|
106 |
+
if ( true !== response.success || undefined === response.data ) {
|
107 |
+
return answer;
|
108 |
+
}
|
109 |
+
|
110 |
+
$.each(response.data, function( key, ip ) {
|
111 |
+
answer.results.push({
|
112 |
+
id: ip,
|
113 |
+
text: ip
|
114 |
+
});
|
115 |
+
});
|
116 |
+
|
117 |
+
return answer;
|
118 |
+
}
|
119 |
+
},
|
120 |
+
elementArgs.initSelection = function( item, callback ) {
|
121 |
+
callback( item.data( 'selected' ) );
|
122 |
+
},
|
123 |
+
elementArgs.formatNoMatches = function() {
|
124 |
+
return '';
|
125 |
+
},
|
126 |
+
elementArgs.createSearchChoice = function( term ) {
|
127 |
+
var ip_chunks = [];
|
128 |
+
|
129 |
+
ip_chunks = term.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
130 |
+
|
131 |
+
if ( null === ip_chunks ) {
|
132 |
+
return;
|
133 |
+
}
|
134 |
+
|
135 |
+
// remove whole match
|
136 |
+
ip_chunks.shift();
|
137 |
+
|
138 |
+
ip_chunks = $.grep(
|
139 |
+
ip_chunks,
|
140 |
+
function( chunk ) {
|
141 |
+
var numeric = parseInt(chunk, 10);
|
142 |
+
|
143 |
+
return numeric <= 255 && numeric.toString() === chunk;
|
144 |
+
}
|
145 |
+
);
|
146 |
+
|
147 |
+
if (ip_chunks.length < 4) {
|
148 |
+
return;
|
149 |
+
}
|
150 |
+
|
151 |
+
return {
|
152 |
+
id: term,
|
153 |
+
text: term
|
154 |
+
};
|
155 |
+
};
|
156 |
+
} else if ( $this.hasClass( 'ajax' ) ) {
|
157 |
+
var type = '';
|
158 |
+
if ( ! ( type = $this.data( 'ajax-key' ) ) ) {
|
159 |
+
if ( 'triggers' === tORa ) {
|
160 |
+
type = $this.parents( '.form-row' ).first().find( 'select.trigger-type' ).val();
|
161 |
+
} else {
|
162 |
+
type = $this.parents( '.form-row' ).eq(1).find( 'select.alert-type' ).val();
|
163 |
+
}
|
164 |
+
}
|
165 |
+
elementArgs.minimumInputLength = 3;
|
166 |
+
elementArgs.ajax = {
|
167 |
+
url: ajaxurl,
|
168 |
+
type: 'post',
|
169 |
+
dataType: 'json',
|
170 |
+
data: function( term ) {
|
171 |
+
return {
|
172 |
+
action: 'stream_notification_endpoint',
|
173 |
+
type: type,
|
174 |
+
q: term,
|
175 |
+
args: $( this ).attr( 'data-args' )
|
176 |
+
};
|
177 |
+
},
|
178 |
+
results: function( data ) {
|
179 |
+
var r = data.data || [];
|
180 |
+
return {results: r};
|
181 |
+
}
|
182 |
+
};
|
183 |
+
elementArgs.initSelection = function( element, callback ) {
|
184 |
+
var id = $(element).val();
|
185 |
+
if ( '' !== id ) {
|
186 |
+
$.ajax({
|
187 |
+
url: ajaxurl,
|
188 |
+
type: 'post',
|
189 |
+
data: {
|
190 |
+
action: 'stream_notification_endpoint',
|
191 |
+
q: id,
|
192 |
+
single: 1,
|
193 |
+
type: type,
|
194 |
+
args: $( this ).attr( 'data-args' )
|
195 |
+
},
|
196 |
+
dataType: 'json'
|
197 |
+
}).done( function( data ) { callback( data.data ); } );
|
198 |
+
}
|
199 |
+
};
|
200 |
+
elementArgs.formatResult = function( object ) {
|
201 |
+
var result = object.text;
|
202 |
+
|
203 |
+
if ( object.hasOwnProperty( 'avatar' ) ) {
|
204 |
+
result = object.avatar + result;
|
205 |
+
}
|
206 |
+
|
207 |
+
return result;
|
208 |
+
};
|
209 |
+
elementArgs.formatSelection = function( object ) {
|
210 |
+
var result = object.text;
|
211 |
+
|
212 |
+
if ( object.hasOwnProperty( 'avatar' ) ) {
|
213 |
+
result += '<i class="icon16 icon-users"></i>';
|
214 |
+
}
|
215 |
+
|
216 |
+
return result;
|
217 |
+
};
|
218 |
+
}
|
219 |
+
|
220 |
+
$this.select2( elementArgs );
|
221 |
+
$this.on( 'select2_populate', function( e, val ) {
|
222 |
+
var $this = $( this );
|
223 |
+
if ( ! val ) {
|
224 |
+
return;
|
225 |
+
}
|
226 |
+
if ( $this.hasClass( 'ajax' ) ) {
|
227 |
+
$.ajax({
|
228 |
+
url: ajaxurl,
|
229 |
+
type: 'post',
|
230 |
+
data: {
|
231 |
+
action: 'stream_notification_endpoint',
|
232 |
+
q: val,
|
233 |
+
single: 1,
|
234 |
+
type: type,
|
235 |
+
args: $( this ).attr( 'data-args' )
|
236 |
+
},
|
237 |
+
dataType: 'json',
|
238 |
+
success: function( j ) {
|
239 |
+
$this.select2( 'data', j.data );
|
240 |
+
}
|
241 |
+
});
|
242 |
+
} else if ( $this.hasClass( 'tags' ) ) {
|
243 |
+
$this.select2( 'data', $.map(
|
244 |
+
val.split( ',' ),
|
245 |
+
function( ip ) {
|
246 |
+
return { id: ip, text: ip };
|
247 |
+
}
|
248 |
+
) );
|
249 |
+
} else {
|
250 |
+
$this.select2( 'val', val.split( ',' ) );
|
251 |
+
}
|
252 |
+
} );
|
253 |
+
});
|
254 |
+
},
|
255 |
+
|
256 |
+
add_trigger = function( group_index ) {
|
257 |
+
var index = 0,
|
258 |
+
lastItem = null,
|
259 |
+
group = divTriggers.find( '.group[rel=' + group_index + ']' ),
|
260 |
+
i = null,
|
261 |
+
type = null,
|
262 |
+
types = {},
|
263 |
+
connectors = {}
|
264 |
+
;
|
265 |
+
|
266 |
+
if ( ( lastItem = divTriggers.find( '.trigger' ).last() ) && lastItem.size() ) {
|
267 |
+
index = parseInt( lastItem.attr( 'rel' ), 10 ) + 1;
|
268 |
+
}
|
269 |
+
|
270 |
+
// Get adjacent trigger[type=connector] to filter special trigger types
|
271 |
+
connectors = group.find( 'select.trigger-type option:selected[value=connector]' );
|
272 |
+
connectors = connectors.map( function() {
|
273 |
+
return $( this ).parents( '.trigger' ).first().find( ':input.trigger-value' ).val();
|
274 |
+
}).toArray();
|
275 |
+
if ( connectors.length ) {
|
276 |
+
for ( i in stream_notifications.special_types ) {
|
277 |
+
type = stream_notifications.special_types[i];
|
278 |
+
if ( -1 !== connectors.indexOf( type.connector ) ) {
|
279 |
+
types[i] = type;
|
280 |
+
}
|
281 |
+
}
|
282 |
+
}
|
283 |
+
|
284 |
+
group.append( tmpl( $.extend(
|
285 |
+
{ index: index, group: group_index },
|
286 |
+
stream_notifications,
|
287 |
+
{ types: $.extend( {}, stream_notifications.types, types ) }
|
288 |
+
) ) );
|
289 |
+
group.find( '.trigger' ).first().addClass( 'first' );
|
290 |
+
selectify( group.find( 'select' ) );
|
291 |
+
},
|
292 |
+
|
293 |
+
add_alert = function() {
|
294 |
+
var index = divAlerts.find( '.alert' ).size();
|
295 |
+
|
296 |
+
divAlerts.append( tmpl_alert( $.extend(
|
297 |
+
{ index: index },
|
298 |
+
stream_notifications
|
299 |
+
) ) );
|
300 |
+
selectify( divAlerts.find( '.alert select' ) );
|
301 |
+
},
|
302 |
+
|
303 |
+
display_error = function( key ) {
|
304 |
+
if ( $( '.error' ).filter( function() { return $( this ).attr( 'data-key' ) === key; } ).length === 0 ) {
|
305 |
+
$( 'body,html' ).scrollTop(0);
|
306 |
+
$( '.wrap > h2' )
|
307 |
+
.after(
|
308 |
+
$( '<div></div>' )
|
309 |
+
.addClass( 'updated error fade' )
|
310 |
+
.attr( 'data-key', key )
|
311 |
+
.hide()
|
312 |
+
.append(
|
313 |
+
$( '<p></p>' ).text(stream_notifications.i18n[key])
|
314 |
+
)
|
315 |
+
)
|
316 |
+
.next( '.updated' )
|
317 |
+
.fadeIn( 'normal' )
|
318 |
+
.delay( 3000 )
|
319 |
+
.fadeOut( 'normal', function() { $( this ).remove(); } );
|
320 |
+
}
|
321 |
+
};
|
322 |
+
|
323 |
+
divTriggers
|
324 |
+
// Add new rule
|
325 |
+
.on( 'click.sn', btns.add_trigger, function( e ) {
|
326 |
+
e.preventDefault();
|
327 |
+
|
328 |
+
add_trigger( $( this ).data( 'group' ) );
|
329 |
+
})
|
330 |
+
|
331 |
+
// Add new group
|
332 |
+
.on( 'click.sn', btns.add_group, function( e, groupIndex ) {
|
333 |
+
e.preventDefault();
|
334 |
+
var $this = $( this ),
|
335 |
+
parentGroupIndex = $this.data( 'group' ),
|
336 |
+
group = divTriggers.find( '.group[rel=' + $this.data( 'group' ) + ']' );
|
337 |
+
|
338 |
+
if ( ! groupIndex ) {
|
339 |
+
groupIndex = ++iGroup;
|
340 |
+
}
|
341 |
+
|
342 |
+
group.append( tmpl_group( { index: groupIndex, parent: parentGroupIndex } ) );
|
343 |
+
selectify( group.find( '.field.relation select' ) );
|
344 |
+
})
|
345 |
+
|
346 |
+
// Delete a trigger
|
347 |
+
.on( 'click.sn', '.delete-trigger', function( e ) {
|
348 |
+
e.preventDefault();
|
349 |
+
var $this = $( this );
|
350 |
+
|
351 |
+
$this.closest( '.trigger' ).remove();
|
352 |
+
|
353 |
+
// add `first` class in case the first trigger was removed
|
354 |
+
$this.closest( '.group' ).find( '.trigger' ).first().addClass( 'first' );
|
355 |
+
})
|
356 |
+
|
357 |
+
// Delete a group
|
358 |
+
.on( 'click.sn', '.delete-group', function( e ) {
|
359 |
+
e.preventDefault();
|
360 |
+
var $this = $( this );
|
361 |
+
|
362 |
+
$this.parents( '.group' ).first().remove();
|
363 |
+
})
|
364 |
+
|
365 |
+
// Reveal rule options after choosing rule type
|
366 |
+
.on( 'change.sn', '.trigger-type', function() {
|
367 |
+
var $this = $( this ),
|
368 |
+
options = null,
|
369 |
+
index = $this.parents( '.trigger' ).first().attr( 'rel' );
|
370 |
+
|
371 |
+
if ( ( 'undefined' !== typeof types[ $this.val() ] ) ) {
|
372 |
+
options = types[ $this.val() ];
|
373 |
+
} else {
|
374 |
+
options = stream_notifications.special_types[ $this.val() ];
|
375 |
+
}
|
376 |
+
|
377 |
+
$this.next( '.trigger-options' ).remove();
|
378 |
+
|
379 |
+
if ( ! options ) { return; }
|
380 |
+
|
381 |
+
$this.after( tmpl_options( $.extend( options, { index: index } ) ) );
|
382 |
+
selectify( $this.parent().find( 'select' ) );
|
383 |
+
selectify( $this.parent().find( 'input.tags, input.ajax' ), { tags: [] } );
|
384 |
+
datify( $this.parent().find( '.type-date' ) );
|
385 |
+
})
|
386 |
+
;
|
387 |
+
|
388 |
+
divAlerts
|
389 |
+
// Add new alert
|
390 |
+
.on( 'click.sn', btns.add_alert, function( e ) {
|
391 |
+
e.preventDefault();
|
392 |
+
add_alert();
|
393 |
+
$( 'html, body' ).animate({
|
394 |
+
scrollTop: divAlerts.find( '.alert' ).last().offset().top
|
395 |
+
}, 400);
|
396 |
+
})
|
397 |
+
|
398 |
+
// Reveal alert options after choosing alert type
|
399 |
+
.on( 'change.sn', '.alert-type', function() {
|
400 |
+
var $this = $( this ),
|
401 |
+
$wrapper = $this.closest( '.alert' ),
|
402 |
+
$alert = {},
|
403 |
+
$copy = {},
|
404 |
+
options = stream_notifications.adapters[ $this.val() ],
|
405 |
+
type = $this.val(),
|
406 |
+
index = $wrapper.attr( 'rel' );
|
407 |
+
|
408 |
+
$wrapper.find( '.alert-options' ).hide();
|
409 |
+
|
410 |
+
if ( ! options ) { return; }
|
411 |
+
|
412 |
+
$copy = $wrapper
|
413 |
+
.find( '.alert-options' )
|
414 |
+
.filter( function() {
|
415 |
+
return $( this ).attr( 'data-type' ) === type;
|
416 |
+
});
|
417 |
+
$wrapper.find( '.alert-options' ).hide();
|
418 |
+
|
419 |
+
if( 0 === $copy.length ) { // render new alert template
|
420 |
+
$alert = $( tmpl_alert_options( $.extend( options, { type: type, index: index } ) ) );
|
421 |
+
$alert.appendTo( $wrapper );
|
422 |
+
selectify( $alert.find( 'select' ) );
|
423 |
+
selectify( $alert.find( 'input.tags, input.ajax' ), { tags: [] } );
|
424 |
+
} else { // copy found, just show it
|
425 |
+
$copy.show();
|
426 |
+
}
|
427 |
+
})
|
428 |
+
|
429 |
+
// Delete an alert
|
430 |
+
.on( 'click.sn', '.delete-alert', function( e ) {
|
431 |
+
e.preventDefault();
|
432 |
+
var $this = $( this );
|
433 |
+
|
434 |
+
$this.parents( '.alert' ).first().remove();
|
435 |
+
|
436 |
+
$( '.alert .circle' ).each( function( index ) {
|
437 |
+
$( this ).text(index + 1);
|
438 |
+
});
|
439 |
+
})
|
440 |
+
;
|
441 |
+
|
442 |
+
// Populate form values if it exists
|
443 |
+
if ( 'undefined' !== typeof stream_notifications.meta ) {
|
444 |
+
|
445 |
+
// Triggers
|
446 |
+
jQuery.each( stream_notifications.meta.triggers, function( i, trigger ) {
|
447 |
+
var groupDiv = divTriggers.find( '.group' ).filter( '[rel='+trigger.group+']' ),
|
448 |
+
row,
|
449 |
+
valueField;
|
450 |
+
|
451 |
+
// create the group if it doesn't exist
|
452 |
+
if ( ! groupDiv.size() ) {
|
453 |
+
var group = stream_notifications.meta.groups[trigger.group];
|
454 |
+
$( btns.add_group ).filter( '[data-group='+group.group+']' ).trigger( 'click', trigger.group);
|
455 |
+
groupDiv = divTriggers.find( '.group' ).filter( '[rel='+trigger.group+']' );
|
456 |
+
groupDiv.find( 'select.group-relation' ).select2( 'val', group.relation );
|
457 |
+
}
|
458 |
+
|
459 |
+
// create the new row, by clicking the add-trigger button in the appropriate group
|
460 |
+
divTriggers.find( btns.add_trigger ).filter( '[data-group='+trigger.group+']' ).trigger( 'click' );
|
461 |
+
// debugger; # DEBUG
|
462 |
+
|
463 |
+
// populate values
|
464 |
+
row = groupDiv.find( '.trigger:last' );
|
465 |
+
row.find( 'select.trigger-relation' ).select2( 'val', trigger.relation ).trigger( 'change' );
|
466 |
+
row.find( 'select.trigger-type' ).select2( 'val', trigger.type ).trigger( 'change' );
|
467 |
+
row.find( 'select.trigger-operator' ).select2( 'val', trigger.operator ).trigger( 'change' );
|
468 |
+
|
469 |
+
// populate the trigger value, according to the trigger type
|
470 |
+
if ( trigger.value ) {
|
471 |
+
valueField = row.find( '.trigger-value:not(.select2-container)' ).eq(0);
|
472 |
+
if ( valueField.is( 'select' ) || valueField.is( '.ajax, .ip' ) ) {
|
473 |
+
valueField.trigger( 'select2_populate', trigger.value );
|
474 |
+
// valueField.select2( 'val', trigger.value ).trigger( 'change' );
|
475 |
+
} else {
|
476 |
+
valueField.val( trigger.value ).trigger( 'change' );
|
477 |
+
}
|
478 |
+
}
|
479 |
+
} );
|
480 |
+
|
481 |
+
// Alerts
|
482 |
+
jQuery.each( stream_notifications.meta.alerts, function( i, alert ) {
|
483 |
+
var row,
|
484 |
+
optionFields;
|
485 |
+
|
486 |
+
// create the new row, by clicking the add-alert button
|
487 |
+
add_alert();
|
488 |
+
|
489 |
+
// populate values
|
490 |
+
row = divAlerts.find( '.alert:last' );
|
491 |
+
row.find( 'select.alert-type' ).select2( 'val', alert.type ).trigger( 'change' );
|
492 |
+
optionFields = row.find( '.alert-options' );
|
493 |
+
optionFields.find( ':input[name]' ).each( function( i, el ) {
|
494 |
+
var $this = $(el),
|
495 |
+
name,
|
496 |
+
val;
|
497 |
+
name = $this.attr( 'name' ).match(/\[([a-z_\-]+)\]$/)[1];
|
498 |
+
if ( 'undefined' !== typeof alert[name] ) {
|
499 |
+
val = alert[name];
|
500 |
+
if ( $this.hasClass( 'select2-offscreen' ) ) {
|
501 |
+
$this.trigger( 'select2_populate', val );
|
502 |
+
// $this.select2( 'val', val ).trigger( 'change' );
|
503 |
+
} else {
|
504 |
+
$this.val( val ).trigger( 'change' );
|
505 |
+
}
|
506 |
+
}
|
507 |
+
});
|
508 |
+
});
|
509 |
+
}
|
510 |
+
|
511 |
+
$( '#rule-form' ).submit( function() {
|
512 |
+
// Do not submit if no triggers exist
|
513 |
+
if ( divTriggers.find( '.trigger' ).size() < 1 ) {
|
514 |
+
display_error( 'empty_triggers' );
|
515 |
+
return false;
|
516 |
+
}
|
517 |
+
|
518 |
+
// Do not submit if no working triggers exist
|
519 |
+
if ( null === $( '.trigger-type:first' ).select2( 'data' ) ) {
|
520 |
+
display_error( 'invalid_first_trigger' );
|
521 |
+
return false;
|
522 |
+
}
|
523 |
+
|
524 |
+
$( '.alert-options:hidden' ).remove();
|
525 |
+
});
|
526 |
+
|
527 |
+
divAlerts
|
528 |
+
.on( 'click', '.toggler', function( e ) {
|
529 |
+
e.preventDefault();
|
530 |
+
var rel = this.rel,
|
531 |
+
toggled = $( rel ),
|
532 |
+
toggler = $( this )
|
533 |
+
;
|
534 |
+
|
535 |
+
if ( ! toggled.is( ':visible' ) ) {
|
536 |
+
toggled.slideDown( 'fast' );
|
537 |
+
toggler.data( 'text', toggler.text() );
|
538 |
+
toggler.text( toggler.data( 'text-toggle' ) );
|
539 |
+
} else {
|
540 |
+
toggled.slideUp( 'fast' );
|
541 |
+
toggler.text( toggler.data( 'text' ) );
|
542 |
+
}
|
543 |
+
} );
|
544 |
+
|
545 |
+
// Autofocus for earlier browsers
|
546 |
+
$( '[autofocus]' ).focus();
|
547 |
+
|
548 |
+
// Reset occurrences link
|
549 |
+
$( 'a.reset-occ' ).click( function( e ) {
|
550 |
+
e.preventDefault();
|
551 |
+
|
552 |
+
if ( ! confirm( stream_notifications.i18n.confirm_reset ) ) {
|
553 |
+
return;
|
554 |
+
}
|
555 |
+
|
556 |
+
$.getJSON( this.href, {}, function( j ) {
|
557 |
+
var div = $( '.submitbox .occurrences strong' );
|
558 |
+
if ( j.success ) {
|
559 |
+
div.html( div.html().replace(/\d+/, 0) );
|
560 |
+
} else {
|
561 |
+
alert( stream_notifications.i18n.ajax_error );
|
562 |
+
}
|
563 |
+
} );
|
564 |
+
});
|
565 |
+
|
566 |
+
// Add empty trigger if no triggers are visible
|
567 |
+
if ( 0 === $( '.trigger' ).length ) {
|
568 |
+
add_trigger( 0 );
|
569 |
+
}
|
570 |
+
|
571 |
+
});
|
extensions/notifications/ui/js/list.js
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* globals stream_notifications_options, _ */
|
2 |
+
jQuery( function( $ ) {
|
3 |
+
|
4 |
+
if ( stream_notifications_options.bulkActions ) {
|
5 |
+
var $bulkSelect = $( '.bulkactions select' ),
|
6 |
+
opts = '';
|
7 |
+
|
8 |
+
_.each( stream_notifications_options.bulkActions, function( el, i ) {
|
9 |
+
opts += '<option value="%">%</option>'.replace( '%', i ).replace( '%', el );
|
10 |
+
});
|
11 |
+
|
12 |
+
$bulkSelect.find( 'option:first' ).after( opts );
|
13 |
+
}
|
14 |
+
});
|
extensions/notifications/views/form-templates.php
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script type="text/template" id="trigger-template-row">
|
2 |
+
<div class="trigger" rel="<%- vars.index %>">
|
3 |
+
<div class="form-row">
|
4 |
+
<input type="hidden" name="triggers[<%- vars.index %>][group]" value="<%- vars.group %>"/>
|
5 |
+
<div class="field relation">
|
6 |
+
<select name="triggers[<%- vars.index %>][relation]" class="trigger-relation">
|
7 |
+
<option value="and"><?php esc_html_e( 'AND', 'stream' ) ?></option>
|
8 |
+
<option value="or"><?php esc_html_e( 'OR', 'stream' ) ?></option>
|
9 |
+
</select>
|
10 |
+
</div>
|
11 |
+
<div class="field type">
|
12 |
+
<select name="triggers[<%- vars.index %>][type]" class="trigger-type" rel="<%- vars.index %>" placeholder="Choose Rule">
|
13 |
+
<option></option>
|
14 |
+
<% _.each( vars.types, function( type, name ) { %>
|
15 |
+
<option value="<%- name %>"><%- type.title %></option>
|
16 |
+
<% }); %>
|
17 |
+
</select>
|
18 |
+
</div>
|
19 |
+
<a href="#" class="delete-trigger"><?php esc_html_e( 'Delete', 'stream' ) ?></a>
|
20 |
+
</div>
|
21 |
+
</div>
|
22 |
+
</script>
|
23 |
+
|
24 |
+
<script type="text/template" id="trigger-template-group">
|
25 |
+
<div class="group" rel="<%- vars.index %>">
|
26 |
+
<div class="group-meta">
|
27 |
+
<input type="hidden" name="groups[<%- vars.index %>][group]" value="<%- vars.parent %>"/>
|
28 |
+
<div class="field relation">
|
29 |
+
<select name="groups[<%- vars.index %>][relation]" class="group-relation">
|
30 |
+
<option value="and"><?php esc_html_e( 'AND', 'stream' ) ?></option>
|
31 |
+
<option value="or"><?php esc_html_e( 'OR', 'stream' ) ?></option>
|
32 |
+
</select>
|
33 |
+
</div>
|
34 |
+
<a href="#add-trigger" class="add-trigger button button-secondary" data-group="<%- vars.index %>"><?php esc_html_e( '+ Add Trigger', 'stream' ) ?></a>
|
35 |
+
<a href="#add-trigger-group" class="add-trigger-group button button-primary" data-group="<%- vars.index %>"><?php esc_html_e( '+ Add Group', 'stream' ) ?></a>
|
36 |
+
<a href="#" class="delete-group"><?php esc_html_e( 'Delete Group', 'stream' ) ?></a>
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
</script>
|
40 |
+
|
41 |
+
<script type="text/template" id="trigger-template-options">
|
42 |
+
<div class="trigger-options">
|
43 |
+
<div class="field operator">
|
44 |
+
<select name="triggers[<%- vars.index %>][operator]" class="trigger-operator">
|
45 |
+
<% _.each( vars.operators, function( list, name ) { %>
|
46 |
+
<option value="<%- name %>"><%- list %></option>
|
47 |
+
<% }); %>
|
48 |
+
</select>
|
49 |
+
</div>
|
50 |
+
<div class="field value">
|
51 |
+
<% if ( ['select', 'ajax'].indexOf( vars.type ) != -1 ) { %>
|
52 |
+
<select name="triggers[<%- vars.index %>][value]<% if ( vars.multiple ) { %>[]<% } %>" class="trigger-value<% if ( vars.subtype ) { %> <%- vars.subtype %><% } %>" data-ajax="<% ( vars.ajax ) %>" <% if ( vars.multiple ) { %> multiple="multiple"<% } %>>
|
53 |
+
<option></option>
|
54 |
+
<% if ( vars.options ) { %>
|
55 |
+
<% _.each( vars.options, function( list, name ) { %>
|
56 |
+
<option value="<%- name %>"><%- list %></option>
|
57 |
+
<% }); %>
|
58 |
+
<% } %>
|
59 |
+
</select>
|
60 |
+
<% } else { %>
|
61 |
+
<input type="text" name="triggers[<%- vars.index %>][value]" class="trigger-value type-<%- vars.type %> <% if ( vars.tags ) { %>tags<% } %> <% if ( vars.ajax ) { %>ajax<% } %><% if ( vars.subtype ) { %> <%- vars.subtype %><% } %>">
|
62 |
+
<% } // endif%>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
</script>
|
66 |
+
|
67 |
+
<script type="text/template" id="alert-template-row">
|
68 |
+
<div class="alert" rel="<%- vars.index %>">
|
69 |
+
<div class="form-row">
|
70 |
+
<div class="type">
|
71 |
+
<span class="circle"><%- vars.index + 1 %></span>
|
72 |
+
<select name="alerts[<%- vars.index %>][type]" class="alert-type" rel="<%- vars.index %>" placeholder="Choose Type">
|
73 |
+
<option></option>
|
74 |
+
<% _.each( vars.adapters, function( type, name ) { %>
|
75 |
+
<option value="<%- name %>"><%- type.title %></option>
|
76 |
+
<% }); %>
|
77 |
+
</select>
|
78 |
+
<a href="#" class="delete-alert alignright"><?php esc_html_e( 'Delete', 'stream' ) ?></a>
|
79 |
+
<div class="clear"></div>
|
80 |
+
</div>
|
81 |
+
</div>
|
82 |
+
</div>
|
83 |
+
</script>
|
84 |
+
|
85 |
+
<script type="text/template" id="alert-template-options">
|
86 |
+
<table class="alert-options form-table" data-type="<%- vars.type %>">
|
87 |
+
<% for ( field_name in vars.fields ) { var field = vars.fields[field_name]; %>
|
88 |
+
<% var argsHTML = ( typeof field.args === "object" ? "data-args=" + JSON.stringify( field.args ) : "" ); %>
|
89 |
+
<tr>
|
90 |
+
<th class="label">
|
91 |
+
<label><%- field.title %></label>
|
92 |
+
<% if ( field.hint ) { %>
|
93 |
+
<% var hints = ( typeof field.hint === "object" ? field.hint : [field.hint] ); %>
|
94 |
+
<% for ( i in hints ) { var hint = hints[i]; %>
|
95 |
+
<p class="description"><%= hint %></p>
|
96 |
+
<% } %>
|
97 |
+
<% } %>
|
98 |
+
</th>
|
99 |
+
<td>
|
100 |
+
<div class="field value">
|
101 |
+
<% if ( ['select'].indexOf( field.type ) != -1 ) { %>
|
102 |
+
<select name="alerts[<%- vars.index %>][<%- field_name %>]" class="alert-value widefat" data-ajax="<% ( field.ajax ) %>" <% if ( field.multiple ) { %> multiple="multiple"<% } %> <%- argsHTML %>>
|
103 |
+
<option></option>
|
104 |
+
<% if ( field.options ) { %>
|
105 |
+
<% _.each( field.options, function( list, name ) { %>
|
106 |
+
<option value="<%- name %>"><%- list %></option>
|
107 |
+
<% }); %>
|
108 |
+
<% } %>
|
109 |
+
</select>
|
110 |
+
<% } else if ( ['textarea'].indexOf( field.type ) != -1 ) { %>
|
111 |
+
<textarea name="alerts[<%- vars.index %>][<%- field_name %>]" class="alert-value large-text code" rows="10" cols="80" <%- argsHTML %>></textarea>
|
112 |
+
<% } else if ( ['error'].indexOf( field.type ) != -1 ) { %>
|
113 |
+
<%= field.message %>
|
114 |
+
<% } else { %>
|
115 |
+
<input type="text" name="alerts[<%- vars.index %>][<%- field_name %>]" class="alert-value widefat <% if ( field.tags ) { %>tags<% } %> <% if ( field.ajax ) { %>ajax<% } %>" <% if ( field.ajax && field.key ) { %> data-ajax-key="<%- field.key %>"<% } %> <%- argsHTML %>>
|
116 |
+
<% } %>
|
117 |
+
<% if ( field.after ) { %>
|
118 |
+
<%- field.after %>
|
119 |
+
<% } %>
|
120 |
+
</div>
|
121 |
+
</td>
|
122 |
+
</tr>
|
123 |
+
<% } %>
|
124 |
+
<% if ( typeof vars.hints != 'undefined' && vars.hints ) { %>
|
125 |
+
<tr>
|
126 |
+
<th></th>
|
127 |
+
<td><%= vars.hints %></td>
|
128 |
+
</tr>
|
129 |
+
<% } %>
|
130 |
+
</table>
|
131 |
+
|
132 |
+
</script>
|
extensions/reports/class-wp-stream-reports.php
ADDED
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Reports {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Hold Stream Reports instance
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $instance;
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Screen ID for my admin page
|
14 |
+
* @var string
|
15 |
+
*/
|
16 |
+
public static $screen_id;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Holds admin notices messages
|
20 |
+
*
|
21 |
+
* @var array
|
22 |
+
*/
|
23 |
+
public static $messages = array();
|
24 |
+
|
25 |
+
/**
|
26 |
+
* Hold the nonce name
|
27 |
+
*/
|
28 |
+
public static $nonce;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Page slug for notifications list table screen
|
32 |
+
*
|
33 |
+
* @const string
|
34 |
+
*/
|
35 |
+
const REPORTS_PAGE_SLUG = 'wp_stream_reports';
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Capability for the Notifications to be viewed
|
39 |
+
*
|
40 |
+
* @const string
|
41 |
+
*/
|
42 |
+
const VIEW_CAP = 'view_stream_reports';
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Class constructor
|
46 |
+
*/
|
47 |
+
private function __construct() {
|
48 |
+
define( 'WP_STREAM_REPORTS_DIR', WP_STREAM_EXTENSIONS_DIR . 'reports/' ); // Has trailing slash
|
49 |
+
define( 'WP_STREAM_REPORTS_URL', WP_STREAM_URL . 'extensions/reports/' ); // Has trailing slash
|
50 |
+
define( 'WP_STREAM_REPORTS_INC_DIR', WP_STREAM_REPORTS_DIR . 'includes/' ); // Has trailing slash
|
51 |
+
define( 'WP_STREAM_REPORTS_VIEW_DIR', WP_STREAM_REPORTS_DIR . 'views/' ); // Has trailing slash
|
52 |
+
|
53 |
+
if ( ! apply_filters( 'wp_stream_reports_load', true ) ) {
|
54 |
+
return;
|
55 |
+
}
|
56 |
+
|
57 |
+
add_action( 'init', array( $this, 'load' ) );
|
58 |
+
}
|
59 |
+
|
60 |
+
/**
|
61 |
+
* Load our classes, actions/filters, only if our big brother is activated.
|
62 |
+
* GO GO GO!
|
63 |
+
*
|
64 |
+
* @return void
|
65 |
+
*/
|
66 |
+
public function load() {
|
67 |
+
// Register new submenu
|
68 |
+
if ( ! apply_filters( 'wp_stream_reports_disallow_site_access', false ) && ! WP_Stream_Admin::$disable_access && ( WP_Stream::is_connected() || WP_Stream::is_development_mode() ) ) {
|
69 |
+
add_action( 'admin_menu', array( $this, 'register_menu' ), 11 );
|
70 |
+
}
|
71 |
+
|
72 |
+
add_action( 'all_admin_notices', array( $this, 'admin_notices' ) );
|
73 |
+
|
74 |
+
// Load settings
|
75 |
+
require_once WP_STREAM_REPORTS_INC_DIR . 'class-wp-stream-reports-settings.php';
|
76 |
+
add_action( 'init', array( 'WP_Stream_Reports_Settings', 'load' ), 9 );
|
77 |
+
|
78 |
+
// Load date interval
|
79 |
+
require_once WP_STREAM_CLASS_DIR . 'class-wp-stream-date-interval.php';
|
80 |
+
require_once WP_STREAM_REPORTS_INC_DIR . 'class-wp-stream-reports-date-interval.php';
|
81 |
+
add_action( 'init', array( 'WP_Stream_Reports_Date_Interval', 'get_instance' ) );
|
82 |
+
|
83 |
+
// Load metaboxes and charts
|
84 |
+
require_once WP_STREAM_REPORTS_INC_DIR . 'class-wp-stream-reports-meta-boxes.php';
|
85 |
+
require_once WP_STREAM_REPORTS_INC_DIR . 'class-wp-stream-reports-charts.php';
|
86 |
+
add_action( 'init', array( 'WP_Stream_Reports_Metaboxes', 'get_instance' ), 12 );
|
87 |
+
|
88 |
+
// Load template tags
|
89 |
+
require_once WP_STREAM_REPORTS_INC_DIR . 'template-tags.php';
|
90 |
+
|
91 |
+
// Register and enqueue the administration scripts
|
92 |
+
add_action( 'admin_enqueue_scripts', array( $this, 'register_ui_assets' ), 20 );
|
93 |
+
add_action( 'admin_print_scripts', array( $this, 'dequeue_media_conflicts' ), 9999 );
|
94 |
+
}
|
95 |
+
|
96 |
+
/**
|
97 |
+
* @param array $ajax_hooks associative array of ajax hooks action to actual functionname
|
98 |
+
* @param object $referer Class refering the action
|
99 |
+
*/
|
100 |
+
public static function handle_ajax_request( $ajax_hooks, $referer ) {
|
101 |
+
// If we are not in ajax mode, return early
|
102 |
+
if ( ! defined( 'DOING_AJAX' ) || ! is_object( $referer ) ) {
|
103 |
+
return;
|
104 |
+
}
|
105 |
+
|
106 |
+
foreach ( $ajax_hooks as $hook => $function ) {
|
107 |
+
add_action( "wp_ajax_{$hook}", array( $referer, $function ) );
|
108 |
+
}
|
109 |
+
|
110 |
+
// Check referer here so we don't have to check it on every function call
|
111 |
+
if ( array_key_exists( $_REQUEST['action'], $ajax_hooks ) ) {
|
112 |
+
// Checking permission
|
113 |
+
if ( ! current_user_can( WP_Stream_Reports::VIEW_CAP ) ) {
|
114 |
+
wp_die( __( 'Cheating huh?', 'stream' ) );
|
115 |
+
}
|
116 |
+
check_admin_referer( 'stream-reports-page', 'wp_stream_reports_nonce' );
|
117 |
+
}
|
118 |
+
}
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Register Notification menu under Stream's main one
|
122 |
+
*
|
123 |
+
* @action admin_menu
|
124 |
+
* @return void
|
125 |
+
*/
|
126 |
+
public function register_menu() {
|
127 |
+
self::$screen_id = add_submenu_page(
|
128 |
+
WP_Stream_Admin::RECORDS_PAGE_SLUG,
|
129 |
+
__( 'Reports', 'stream' ),
|
130 |
+
__( 'Reports', 'stream' ),
|
131 |
+
self::VIEW_CAP,
|
132 |
+
self::REPORTS_PAGE_SLUG,
|
133 |
+
array( $this, 'page' )
|
134 |
+
);
|
135 |
+
|
136 |
+
// Create nonce right away so it is accessible everywhere
|
137 |
+
self::$nonce = array( 'wp_stream_reports_nonce' => wp_create_nonce( 'stream-reports-page' ) );
|
138 |
+
|
139 |
+
$metabox = WP_Stream_Reports_Metaboxes::get_instance();
|
140 |
+
add_action( 'load-' . self::$screen_id, array( $metabox, 'load_page' ) );
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Register and enqueue the scripts related to our plugin.
|
145 |
+
*
|
146 |
+
* @action admin_enqueue_scripts
|
147 |
+
* @uses wp_register_script
|
148 |
+
* @uses wp_enqueue_script
|
149 |
+
*
|
150 |
+
* @param $pagename the actual page name
|
151 |
+
*
|
152 |
+
* @return void
|
153 |
+
*/
|
154 |
+
public function register_ui_assets( $pagename ) {
|
155 |
+
// JavaScript registration
|
156 |
+
wp_register_script(
|
157 |
+
'stream-reports-d3',
|
158 |
+
WP_STREAM_REPORTS_URL . 'ui/lib/d3/d3.min.js',
|
159 |
+
array(),
|
160 |
+
'3.5.3',
|
161 |
+
true
|
162 |
+
);
|
163 |
+
|
164 |
+
wp_register_script(
|
165 |
+
'stream-reports-nvd3',
|
166 |
+
WP_STREAM_REPORTS_URL . 'ui/lib/nvd3/nv.d3.min.js',
|
167 |
+
array( 'stream-reports-d3' ),
|
168 |
+
'1.1.15b',
|
169 |
+
true
|
170 |
+
);
|
171 |
+
|
172 |
+
wp_register_script(
|
173 |
+
'stream-reports',
|
174 |
+
WP_STREAM_REPORTS_URL . 'ui/js/stream-reports.js',
|
175 |
+
array( 'stream-reports-nvd3', 'jquery', 'underscore', 'jquery-ui-datepicker' ),
|
176 |
+
WP_STREAM::VERSION,
|
177 |
+
true
|
178 |
+
);
|
179 |
+
|
180 |
+
// CSS registration
|
181 |
+
wp_register_style(
|
182 |
+
'stream-reports-nvd3',
|
183 |
+
WP_STREAM_REPORTS_URL . 'ui/lib/nvd3/nv.d3.min.css',
|
184 |
+
array(),
|
185 |
+
'1.1.15b',
|
186 |
+
'screen'
|
187 |
+
);
|
188 |
+
|
189 |
+
wp_register_style(
|
190 |
+
'stream-reports',
|
191 |
+
WP_STREAM_REPORTS_URL . 'ui/css/stream-reports.css',
|
192 |
+
array( 'stream-reports-nvd3', 'wp-stream-datepicker' ),
|
193 |
+
WP_STREAM::VERSION,
|
194 |
+
'screen'
|
195 |
+
);
|
196 |
+
|
197 |
+
// If we are not on the right page we return early
|
198 |
+
if ( $pagename !== self::$screen_id ) {
|
199 |
+
return;
|
200 |
+
}
|
201 |
+
|
202 |
+
// Localization
|
203 |
+
wp_localize_script(
|
204 |
+
'stream-reports',
|
205 |
+
'wp_stream_reports',
|
206 |
+
array(
|
207 |
+
'i18n' => array(
|
208 |
+
'configure' => __( 'Configure', 'stream' ),
|
209 |
+
'cancel' => __( 'Cancel', 'stream' ),
|
210 |
+
'deletemsg' => __( 'Do you really want to delete this section? This cannot be undone.', 'stream' ),
|
211 |
+
),
|
212 |
+
'gmt_offset' => get_option( 'gmt_offset' ),
|
213 |
+
)
|
214 |
+
);
|
215 |
+
|
216 |
+
// Scripts
|
217 |
+
wp_enqueue_script( 'stream-reports' );
|
218 |
+
wp_enqueue_script( 'select2' );
|
219 |
+
wp_enqueue_script( 'common' );
|
220 |
+
wp_enqueue_script( 'dashboard' );
|
221 |
+
wp_enqueue_script( 'postbox' );
|
222 |
+
|
223 |
+
// Styles
|
224 |
+
wp_enqueue_style( 'stream-reports' );
|
225 |
+
wp_enqueue_style( 'select2' );
|
226 |
+
}
|
227 |
+
|
228 |
+
/**
|
229 |
+
* Admin page callback function, redirects to each respective method based
|
230 |
+
* on $_GET['view']
|
231 |
+
*
|
232 |
+
* @return void
|
233 |
+
*/
|
234 |
+
public function page() {
|
235 |
+
// Page class
|
236 |
+
$class = 'metabox-holder columns-' . get_current_screen()->get_columns();
|
237 |
+
$add_url = add_query_arg(
|
238 |
+
array_merge(
|
239 |
+
array(
|
240 |
+
'action' => 'wp_stream_reports_add_metabox',
|
241 |
+
),
|
242 |
+
self::$nonce
|
243 |
+
),
|
244 |
+
admin_url( 'admin-ajax.php' )
|
245 |
+
);
|
246 |
+
|
247 |
+
$sections = WP_Stream_Reports_Settings::get_user_options( 'sections' );
|
248 |
+
$no_reports = empty( $sections );
|
249 |
+
$create_url = add_query_arg(
|
250 |
+
array_merge(
|
251 |
+
array(
|
252 |
+
'action' => 'wp_stream_reports_default_reports',
|
253 |
+
),
|
254 |
+
self::$nonce
|
255 |
+
),
|
256 |
+
admin_url( 'admin-ajax.php' )
|
257 |
+
);
|
258 |
+
|
259 |
+
$no_reports_message = __( "There's nothing here! Do you want to <a href=\"%s\">create some reports</a>?", 'stream' );
|
260 |
+
$no_reports_message = sprintf( $no_reports_message, $create_url );
|
261 |
+
|
262 |
+
$view = (object) array(
|
263 |
+
'slug' => 'all',
|
264 |
+
'path' => null,
|
265 |
+
);
|
266 |
+
|
267 |
+
// Avoid throwing Notices by testing the variable
|
268 |
+
if ( isset( $_GET['view'] ) && ! empty( $_GET['view'] ) ){
|
269 |
+
$view->slug = sanitize_file_name( wp_unslash( $_GET['view'] ) );
|
270 |
+
}
|
271 |
+
|
272 |
+
// First we check if the file exists in our plugin folder, otherwhise give the user an error
|
273 |
+
if ( ! file_exists( WP_STREAM_REPORTS_VIEW_DIR . $view->slug . '.php' ) ){
|
274 |
+
$view->slug = 'error';
|
275 |
+
}
|
276 |
+
|
277 |
+
// Define the path for the view we
|
278 |
+
$view->path = WP_STREAM_REPORTS_VIEW_DIR . $view->slug . '.php';
|
279 |
+
|
280 |
+
// Execute some actions before including the view, to allow others to hook in here
|
281 |
+
// Use these to do stuff related to the view you are working with
|
282 |
+
do_action( 'wp_stream_reports_view', $view );
|
283 |
+
do_action( "wp_stream_reports_view-{$view->slug}", $view );
|
284 |
+
|
285 |
+
include_once $view->path;
|
286 |
+
}
|
287 |
+
|
288 |
+
/**
|
289 |
+
* Remove conflicting JS libraries caused by other plugins loading on pages not belonging to them
|
290 |
+
*/
|
291 |
+
function dequeue_media_conflicts() {
|
292 |
+
if ( 'stream_page_' . self::REPORTS_PAGE_SLUG !== get_current_screen()->id ) {
|
293 |
+
return;
|
294 |
+
}
|
295 |
+
|
296 |
+
wp_dequeue_script( 'media-upload' );
|
297 |
+
wp_enqueue_script( 'media-editor' );
|
298 |
+
wp_dequeue_script( 'media-audiovideo' );
|
299 |
+
wp_dequeue_script( 'mce-view' );
|
300 |
+
wp_dequeue_script( 'image-edit' );
|
301 |
+
wp_dequeue_script( 'media-editor' );
|
302 |
+
wp_dequeue_script( 'media-audiovideo' );
|
303 |
+
}
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Display all messages on admin board
|
307 |
+
*
|
308 |
+
* @return void
|
309 |
+
*/
|
310 |
+
public static function admin_notices() {
|
311 |
+
foreach ( self::$messages as $message ) {
|
312 |
+
echo wp_kses_post( $message );
|
313 |
+
}
|
314 |
+
}
|
315 |
+
|
316 |
+
/**
|
317 |
+
* Return active instance of WP_Stream_Reports, create one if it doesn't exist
|
318 |
+
*
|
319 |
+
* @return WP_Stream_Reports
|
320 |
+
*/
|
321 |
+
public static function get_instance() {
|
322 |
+
if ( empty( self::$instance ) ) {
|
323 |
+
$class = __CLASS__;
|
324 |
+
self::$instance = new $class;
|
325 |
+
}
|
326 |
+
return self::$instance;
|
327 |
+
}
|
328 |
+
|
329 |
+
}
|
330 |
+
|
331 |
+
$GLOBALS['wp_stream_reports'] = WP_Stream_Reports::get_instance();
|
extensions/reports/includes/class-wp-stream-reports-charts.php
ADDED
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Reports_Charts {
|
4 |
+
|
5 |
+
public function __construct() {
|
6 |
+
|
7 |
+
// Load records
|
8 |
+
add_filter( 'wp_stream_reports_load_records', array( $this, 'sort_coordinates_by_count' ), 10, 2 );
|
9 |
+
add_filter( 'wp_stream_reports_load_records', array( $this, 'limit_coordinates' ), 10, 2 );
|
10 |
+
|
11 |
+
// Make charts
|
12 |
+
add_filter( 'wp_stream_reports_make_chart', array( $this, 'pie_chart_coordinates' ), 10, 2 );
|
13 |
+
add_filter( 'wp_stream_reports_make_chart', array( $this, 'bar_chart_coordinates' ), 10, 2 );
|
14 |
+
add_filter( 'wp_stream_reports_make_chart', array( $this, 'line_chart_coordinates' ), 10, 2 );
|
15 |
+
|
16 |
+
// Chart finalization
|
17 |
+
add_filter( 'wp_stream_reports_finalize_chart', array( $this, 'apply_chart_settings' ), 10, 2 );
|
18 |
+
|
19 |
+
}
|
20 |
+
|
21 |
+
public function get_chart_options( $args, $records ) {
|
22 |
+
|
23 |
+
$coordinates = apply_filters( 'wp_stream_reports_make_chart', $records, $args );
|
24 |
+
$values = apply_filters( 'wp_stream_reports_finalize_chart', $coordinates, $args );
|
25 |
+
|
26 |
+
$show_controls = count( $values ) > 1;
|
27 |
+
|
28 |
+
return array(
|
29 |
+
'type' => $args['chart_type'],
|
30 |
+
'guidelines' => true,
|
31 |
+
'tooltip' => array(
|
32 |
+
'show' => true,
|
33 |
+
),
|
34 |
+
'values' => $values,
|
35 |
+
'controls' => $show_controls,
|
36 |
+
'stacked' => (bool) $args['group'],
|
37 |
+
'grouped' => false,
|
38 |
+
);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Sorts each set of data by the number of records in them
|
43 |
+
*/
|
44 |
+
public function sort_coordinates_by_count( $records ) {
|
45 |
+
$counts = array();
|
46 |
+
foreach ( $records as $field => $data ){
|
47 |
+
|
48 |
+
$count = count( $data );
|
49 |
+
if ( ! array_key_exists( $count, $counts ) ) {
|
50 |
+
$counts[ $count ] = array();
|
51 |
+
}
|
52 |
+
|
53 |
+
$counts[ $count ][] = array(
|
54 |
+
'key' => $field,
|
55 |
+
'data' => $data,
|
56 |
+
);
|
57 |
+
}
|
58 |
+
|
59 |
+
krsort( $counts );
|
60 |
+
|
61 |
+
$output = array();
|
62 |
+
foreach ( $counts as $count => $element ) {
|
63 |
+
|
64 |
+
foreach ( $element as $element_data ) {
|
65 |
+
$output[ $element_data['key'] ] = $element_data['data'];
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
return $output;
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Merges all records past limit into single record
|
74 |
+
*/
|
75 |
+
public function limit_coordinates( $records, $args ) {
|
76 |
+
$limit = apply_filters( 'wp_stream_reports_record_limit', 10 );
|
77 |
+
if ( 0 === $limit ) {
|
78 |
+
return $records;
|
79 |
+
}
|
80 |
+
|
81 |
+
$top_elements = array_slice( $records, 0, $limit, true );
|
82 |
+
$leftover_elements = array_slice( $records, $limit );
|
83 |
+
|
84 |
+
if ( ! $leftover_elements ) {
|
85 |
+
return $top_elements;
|
86 |
+
}
|
87 |
+
|
88 |
+
$other_element = array();
|
89 |
+
foreach ( $leftover_elements as $data ) {
|
90 |
+
$other_element = array_merge( $other_element, $data );
|
91 |
+
}
|
92 |
+
|
93 |
+
$top_elements['report-others'] = $other_element;
|
94 |
+
|
95 |
+
return $top_elements;
|
96 |
+
}
|
97 |
+
|
98 |
+
public function line_chart_coordinates( $records, $args ) {
|
99 |
+
if ( 'line' !== $args['chart_type'] ) {
|
100 |
+
return $records;
|
101 |
+
}
|
102 |
+
|
103 |
+
$sorted = array();
|
104 |
+
|
105 |
+
// Get date count for each sort
|
106 |
+
foreach ( $records as $type => $items ) {
|
107 |
+
$sorted[ $type ] = $this->count_by_field( 'created', $items, array( $this, 'collapse_dates' ) );
|
108 |
+
}
|
109 |
+
|
110 |
+
$sorted = $this->pad_fields( $sorted );
|
111 |
+
|
112 |
+
foreach ( $sorted as $type => &$items ) {
|
113 |
+
ksort( $items );
|
114 |
+
}
|
115 |
+
|
116 |
+
$coordinates = array();
|
117 |
+
|
118 |
+
foreach ( $sorted as $line_name => $points ) {
|
119 |
+
$line_data = array(
|
120 |
+
'key' => $line_name,
|
121 |
+
'values' => array(),
|
122 |
+
);
|
123 |
+
|
124 |
+
foreach ( $points as $x => $y ) {
|
125 |
+
$line_data['values'][] = array(
|
126 |
+
'x' => $x,
|
127 |
+
'y' => $y,
|
128 |
+
);
|
129 |
+
}
|
130 |
+
|
131 |
+
$coordinates[] = $line_data;
|
132 |
+
}
|
133 |
+
|
134 |
+
return $coordinates;
|
135 |
+
}
|
136 |
+
|
137 |
+
public function pie_chart_coordinates( $records, $args ) {
|
138 |
+
if ( 'pie' !== $args['chart_type'] ) {
|
139 |
+
return $records;
|
140 |
+
}
|
141 |
+
|
142 |
+
$counts = array();
|
143 |
+
|
144 |
+
foreach ( $records as $type => $items ) {
|
145 |
+
$counts[] = array(
|
146 |
+
'key' => $type,
|
147 |
+
'value' => count( $items ),
|
148 |
+
);
|
149 |
+
}
|
150 |
+
|
151 |
+
return $counts;
|
152 |
+
}
|
153 |
+
|
154 |
+
public function bar_chart_coordinates( $records, $args ) {
|
155 |
+
if ( 'multibar' !== $args['chart_type'] ) {
|
156 |
+
return $records;
|
157 |
+
}
|
158 |
+
|
159 |
+
$sorted = array();
|
160 |
+
|
161 |
+
// Get date count for each sort
|
162 |
+
foreach ( $records as $type => $items ) {
|
163 |
+
$sorted[ $type ] = $this->count_by_field( 'created', $items, array( $this, 'collapse_dates' ) );
|
164 |
+
}
|
165 |
+
|
166 |
+
$sorted = $this->pad_fields( $sorted );
|
167 |
+
|
168 |
+
foreach ( $sorted as $type => &$items ) {
|
169 |
+
ksort( $items );
|
170 |
+
}
|
171 |
+
|
172 |
+
$coordinates = array();
|
173 |
+
|
174 |
+
foreach ( $sorted as $line_name => $points ) {
|
175 |
+
$line_data = array(
|
176 |
+
'key' => $line_name,
|
177 |
+
'values' => array(),
|
178 |
+
);
|
179 |
+
|
180 |
+
foreach ( $points as $x => $y ) {
|
181 |
+
$line_data['values'][] = array(
|
182 |
+
'x' => $x,
|
183 |
+
'y' => $y,
|
184 |
+
);
|
185 |
+
}
|
186 |
+
|
187 |
+
$coordinates[] = $line_data;
|
188 |
+
}
|
189 |
+
|
190 |
+
return $coordinates;
|
191 |
+
}
|
192 |
+
|
193 |
+
/**
|
194 |
+
* Counts the number of objects with similar field properties in an array
|
195 |
+
* @return array
|
196 |
+
*/
|
197 |
+
public function count_by_field( $field, $records, $callback = '' ) {
|
198 |
+
$sorted = $this->group_by_field( $field, $records, $callback );
|
199 |
+
$counts = array();
|
200 |
+
|
201 |
+
foreach ( array_keys( $sorted ) as $key ) {
|
202 |
+
$counts[ $key ] = count( $sorted[ $key ] );
|
203 |
+
}
|
204 |
+
|
205 |
+
return $counts;
|
206 |
+
}
|
207 |
+
|
208 |
+
/**
|
209 |
+
* Groups objects with similar field properties into arrays
|
210 |
+
* @return array
|
211 |
+
*/
|
212 |
+
public function group_by_field( $field, $records, $callback = '' ) {
|
213 |
+
$sorted = array();
|
214 |
+
|
215 |
+
foreach ( $records as $record ) {
|
216 |
+
$key = $record->$field;
|
217 |
+
|
218 |
+
if ( is_callable( $callback ) ) {
|
219 |
+
$key = call_user_func( $callback, $key );
|
220 |
+
}
|
221 |
+
|
222 |
+
if ( array_key_exists( $key, $sorted ) && is_array( $sorted[ $key ] ) ) {
|
223 |
+
$sorted[ $key ][] = $record;
|
224 |
+
} else {
|
225 |
+
$sorted[ $key ] = array( $record );
|
226 |
+
}
|
227 |
+
}
|
228 |
+
|
229 |
+
return $sorted;
|
230 |
+
}
|
231 |
+
|
232 |
+
/**
|
233 |
+
* Offsets the record created date by the timezone
|
234 |
+
* @return array
|
235 |
+
*/
|
236 |
+
public function offset_record_dates( $records ) {
|
237 |
+
$offset = get_option( 'gmt_offset' );
|
238 |
+
foreach ( $records as $record => $items ) {
|
239 |
+
foreach ( $items as $key => $item ) {
|
240 |
+
$records[ $record ][ $key ]->created = wp_stream_get_iso_8601_extended_date( strtotime( $item->created ), $offset );
|
241 |
+
}
|
242 |
+
}
|
243 |
+
return $records;
|
244 |
+
}
|
245 |
+
|
246 |
+
/**
|
247 |
+
* Adds blank fields for all keys present in any array
|
248 |
+
* @return array
|
249 |
+
*/
|
250 |
+
public function pad_fields( $records ) {
|
251 |
+
$keys = array();
|
252 |
+
|
253 |
+
foreach ( $records as $dataset ) {
|
254 |
+
$keys = array_unique( array_merge( $keys, array_keys( $dataset ) ) );
|
255 |
+
}
|
256 |
+
|
257 |
+
$new_records = array();
|
258 |
+
|
259 |
+
foreach ( $keys as $key ) {
|
260 |
+
foreach ( $records as $data_key => $dataset ) {
|
261 |
+
if ( ! array_key_exists( $data_key, $new_records ) ) {
|
262 |
+
$new_records[ $data_key ] = array();
|
263 |
+
}
|
264 |
+
|
265 |
+
$new_records[ $data_key ][ $key ] = isset( $records[ $data_key ][ $key ] ) ? $records[ $data_key ][ $key ] : 0;
|
266 |
+
}
|
267 |
+
}
|
268 |
+
|
269 |
+
return $new_records;
|
270 |
+
}
|
271 |
+
|
272 |
+
/**
|
273 |
+
* Used to group data points by day
|
274 |
+
*/
|
275 |
+
protected function collapse_dates( $date ) {
|
276 |
+
return strtotime( date( 'Y-m-d', strtotime( $date ) ) );
|
277 |
+
}
|
278 |
+
|
279 |
+
/**
|
280 |
+
* Disable coordinate plots that have been disabled by the user
|
281 |
+
*/
|
282 |
+
public function apply_chart_settings( $coordinates, $args ) {
|
283 |
+
foreach ( $coordinates as $key => $dataset ) {
|
284 |
+
if ( in_array( $key, $args['disabled'] ) ) {
|
285 |
+
$coordinates[ $key ]['disabled'] = true;
|
286 |
+
}
|
287 |
+
}
|
288 |
+
|
289 |
+
return $coordinates;
|
290 |
+
}
|
291 |
+
|
292 |
+
}
|
extensions/reports/includes/class-wp-stream-reports-date-interval.php
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class WP_Stream_Reports_Date_Interval extends WP_Stream_Date_Interval {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Hold WP_Stream_Reports_Date_Interval instance
|
7 |
+
*
|
8 |
+
* @var string
|
9 |
+
*/
|
10 |
+
public static $instance;
|
11 |
+
|
12 |
+
/*
|
13 |
+
* Handle parent constructor
|
14 |
+
*/
|
15 |
+
public function __construct() {
|
16 |
+
// Call parent constructor
|
17 |
+
parent::__construct();
|
18 |
+
|
19 |
+
// Ajax declaration to save time interval
|
20 |
+
$ajax_hooks = array(
|
21 |
+
'wp_stream_reports_save_interval' => 'save_interval',
|
22 |
+
);
|
23 |
+
|
24 |
+
// Register all ajax action and check referer for this class
|
25 |
+
WP_Stream_Reports::handle_ajax_request( $ajax_hooks, $this );
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Handle ajax saving of time intervals
|
30 |
+
*/
|
31 |
+
public function save_interval() {
|
32 |
+
$interval = array(
|
33 |
+
'key' => wp_stream_filter_input( INPUT_GET, 'key', FILTER_SANITIZE_STRING, array( 'default' => '' ) ),
|
34 |
+
'start' => wp_stream_filter_input( INPUT_GET, 'start', FILTER_SANITIZE_STRING, array( 'default' => '' ) ),
|
35 |
+
'end' => wp_stream_filter_input( INPUT_GET, 'end', FILTER_SANITIZE_STRING, array( 'default' => '' ) ),
|
36 |
+
);
|
37 |
+
|
38 |
+
// Get predefined interval for validation
|
39 |
+
$avail_intervals = $this->get_predefined_intervals();
|
40 |
+
|
41 |
+
if ( '' !== $interval['key'] && 'custom' !== $interval['key'] && ! isset( $avail_intervals[ $interval['key'] ] ) ) {
|
42 |
+
wp_die( esc_html__( 'That time interval is not available.', 'stream' ) );
|
43 |
+
}
|
44 |
+
|
45 |
+
// Only store dates if we are dealing with custom dates and no relative preset
|
46 |
+
if ( 'custom' !== $interval['key'] ) {
|
47 |
+
$interval['start'] = '';
|
48 |
+
$interval['end'] = '';
|
49 |
+
}
|
50 |
+
|
51 |
+
WP_Stream_Reports_Settings::update_user_option_and_redirect( 'interval', $interval );
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Return active instance of WP_Stream_Reports, create one if it doesn't exist
|
56 |
+
*
|
57 |
+
* @return WP_Stream_Reports
|
58 |
+
*/
|
59 |
+
public static function get_instance() {
|
60 |
+
if ( empty( self::$instance ) ) {
|
61 |
+
$class = __CLASS__;
|
62 |
+
self::$instance = new $class;
|
63 |
+
}
|
64 |
+
return self::$instance;
|
65 |
+
}
|
66 |
+
|
67 |
+
}
|
extensions/reports/includes/class-wp-stream-reports-meta-boxes.php
ADDED
@@ -0,0 +1,895 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Section class for Stream Reports
|
4 |
+
*
|
5 |
+
* @author X-Team <x-team.com>
|
6 |
+
* @author Jonathan Bardo <jonathan.bardo@x-team.com>
|
7 |
+
*/
|
8 |
+
class WP_Stream_Reports_Metaboxes {
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Hold Stream Reports Section instance
|
12 |
+
*
|
13 |
+
* @var string
|
14 |
+
*/
|
15 |
+
public static $instance;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Hold all the available sections on the page.
|
19 |
+
*
|
20 |
+
* @var array
|
21 |
+
*/
|
22 |
+
public static $sections;
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Holds the meta box id prefix
|
26 |
+
*/
|
27 |
+
const META_PREFIX = 'wp-stream-reports-';
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Public constructor
|
31 |
+
*/
|
32 |
+
public function __construct() {
|
33 |
+
// Get all sections from the database
|
34 |
+
self::$sections = WP_Stream_Reports_Settings::get_user_options( 'sections' );
|
35 |
+
$this->charts = new WP_Stream_Reports_Charts();
|
36 |
+
|
37 |
+
if ( isset( self::$sections[0] ) && isset( self::$sections[0]['data_type'] ) ) {
|
38 |
+
$this->migrate_settings();
|
39 |
+
}
|
40 |
+
|
41 |
+
// Finalize charts
|
42 |
+
add_filter( 'wp_stream_reports_finalize_chart', array( $this, 'translate_labels' ), 10, 2 );
|
43 |
+
|
44 |
+
// Get chart labels
|
45 |
+
add_filter( 'wp_stream_reports_get_label', array( $this, 'translate_data_type_labels' ), 10, 2 );
|
46 |
+
|
47 |
+
$ajax_hooks = array(
|
48 |
+
'wp_stream_reports_add_metabox' => 'add_metabox',
|
49 |
+
'wp_stream_reports_delete_metabox' => 'delete_metabox',
|
50 |
+
'wp_stream_reports_default_reports' => 'setup_user',
|
51 |
+
'wp_stream_reports_save_metabox_config' => 'save_metabox_config',
|
52 |
+
'wp_stream_reports_save_chart_height' => 'save_chart_height',
|
53 |
+
'wp_stream_reports_save_chart_options' => 'save_chart_options',
|
54 |
+
'wp_stream_reports_update_metabox_display' => 'update_metabox_display',
|
55 |
+
);
|
56 |
+
|
57 |
+
// Register all ajax action and check referer for this class
|
58 |
+
WP_Stream_Reports::handle_ajax_request( $ajax_hooks, $this );
|
59 |
+
}
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Runs on a user's first visit to setup sample data
|
63 |
+
*/
|
64 |
+
public function setup_user() {
|
65 |
+
$sections = array(
|
66 |
+
array(
|
67 |
+
'id' => 0,
|
68 |
+
'title' => __( 'All Activity by Author', 'stream' ),
|
69 |
+
'chart_type' => 'line',
|
70 |
+
'selector_id' => 'author',
|
71 |
+
),
|
72 |
+
array(
|
73 |
+
'id' => 1,
|
74 |
+
'title' => __( 'All Activity by Action', 'stream' ),
|
75 |
+
'chart_type' => 'line',
|
76 |
+
'selector_id' => 'action',
|
77 |
+
),
|
78 |
+
array(
|
79 |
+
'id' => 2,
|
80 |
+
'title' => __( 'All Activity by Author Role', 'stream' ),
|
81 |
+
'chart_type' => 'multibar',
|
82 |
+
'selector_id' => 'author_role',
|
83 |
+
'group' => true,
|
84 |
+
),
|
85 |
+
array(
|
86 |
+
'id' => 3,
|
87 |
+
'title' => __( 'Comments Activity by Action', 'stream' ),
|
88 |
+
'chart_type' => 'pie',
|
89 |
+
'connector_id' => 'comments',
|
90 |
+
'context_id' => 'comments',
|
91 |
+
'selector_id' => 'action',
|
92 |
+
),
|
93 |
+
);
|
94 |
+
|
95 |
+
WP_Stream_Reports_Settings::update_user_option( 'sections', $sections );
|
96 |
+
|
97 |
+
$order = array(
|
98 |
+
'normal' => sprintf( '%1$s0,%1$s2', self::META_PREFIX ),
|
99 |
+
'side' => sprintf( '%1$s1,%1$s3', self::META_PREFIX ),
|
100 |
+
);
|
101 |
+
|
102 |
+
update_user_option( get_current_user_id(), 'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG, $order, true );
|
103 |
+
|
104 |
+
$interval = array(
|
105 |
+
'key' => 'last-30-days',
|
106 |
+
'start' => '',
|
107 |
+
'end' => '',
|
108 |
+
);
|
109 |
+
|
110 |
+
WP_Stream_Reports_Settings::update_user_option_and_redirect( 'interval', $interval );
|
111 |
+
}
|
112 |
+
|
113 |
+
public static function in_admin_header() {
|
114 |
+
|
115 |
+
?>
|
116 |
+
<div class="stream-example">
|
117 |
+
<div class="stream-example-modal">
|
118 |
+
<h1><i class="dashicons dashicons-chart-area"></i> <?php _e( 'Stream Reports', 'stream' ) ?></h1>
|
119 |
+
<p><?php _e( 'Generate stunning visuals of logged-in user activity and share them with stakeholders or your clients.', 'stream' ) ?></p>
|
120 |
+
<ul>
|
121 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Fully-interactive charts', 'stream' ) ?></li>
|
122 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Monitor team contributions', 'stream' ) ?></li>
|
123 |
+
<li><i class="dashicons dashicons-yes"></i> <?php _e( 'Responsive for any screen size', 'stream' ) ?></li>
|
124 |
+
</ul>
|
125 |
+
<a href="<?php echo esc_url( WP_Stream_Admin::account_url( sprintf( 'upgrade?site_uuid=%s', WP_Stream::$api->site_uuid ) ) ); ?>" class="button button-primary button-large"><?php _e( 'Upgrade to Pro', 'stream' ) ?></a>
|
126 |
+
</div>
|
127 |
+
</div>
|
128 |
+
<?php
|
129 |
+
}
|
130 |
+
|
131 |
+
public function load_page() {
|
132 |
+
if ( WP_Stream::$api->is_restricted() ) {
|
133 |
+
add_action( 'in_admin_header', array( __CLASS__, 'in_admin_header' ) );
|
134 |
+
return;
|
135 |
+
}
|
136 |
+
|
137 |
+
if ( is_admin() && WP_Stream_Reports_Settings::is_first_visit() ) {
|
138 |
+
$this->setup_user();
|
139 |
+
}
|
140 |
+
|
141 |
+
$this->existing_records = $this->get_existing_records();
|
142 |
+
|
143 |
+
// Add screen option for chart height
|
144 |
+
add_filter( 'screen_settings', array( $this, 'chart_height_display' ), 10, 2 );
|
145 |
+
|
146 |
+
// Enqueue all core scripts required for this page to work
|
147 |
+
add_screen_option( 'layout_columns', array( 'max' => 2, 'default' => 2 ) );
|
148 |
+
|
149 |
+
// Add all metaboxes
|
150 |
+
foreach ( self::$sections as $key => $section ) {
|
151 |
+
$delete_url = add_query_arg(
|
152 |
+
array_merge(
|
153 |
+
array(
|
154 |
+
'action' => 'wp_stream_reports_delete_metabox',
|
155 |
+
'key' => $key,
|
156 |
+
),
|
157 |
+
WP_Stream_Reports::$nonce
|
158 |
+
),
|
159 |
+
admin_url( 'admin-ajax.php' )
|
160 |
+
);
|
161 |
+
|
162 |
+
// Configure button
|
163 |
+
$configure = sprintf(
|
164 |
+
'<span class="postbox-title-action">
|
165 |
+
<a href="javascript:void(0);" class="edit-box open-box">%3$s</a>
|
166 |
+
</span>
|
167 |
+
<span class="postbox-title-action postbox-delete-action">
|
168 |
+
<a href="%1$s">
|
169 |
+
%2$s
|
170 |
+
</a>
|
171 |
+
</span>',
|
172 |
+
esc_url( $delete_url ),
|
173 |
+
esc_html__( 'Delete', 'stream' ),
|
174 |
+
esc_html__( 'Configure', 'stream' )
|
175 |
+
);
|
176 |
+
|
177 |
+
// Parse default argument
|
178 |
+
$section = $this->parse_section( $section );
|
179 |
+
|
180 |
+
// Set the key for template use
|
181 |
+
$section['key'] = $key;
|
182 |
+
$section['generated_title'] = $this->get_generated_title( $section );
|
183 |
+
|
184 |
+
// Generate the title automatically if not already set
|
185 |
+
$title = empty( $section['title'] ) ? $section['generated_title'] : $section['title'];
|
186 |
+
|
187 |
+
// Add the actual metabox
|
188 |
+
add_meta_box(
|
189 |
+
self::META_PREFIX . $key,
|
190 |
+
sprintf( '<span class="title">%s</span>%s', esc_html( $title ), $configure ), // xss ok
|
191 |
+
array( $this, 'metabox_content' ),
|
192 |
+
WP_Stream_Reports::$screen_id,
|
193 |
+
$section['context'],
|
194 |
+
$section['priority'],
|
195 |
+
$section
|
196 |
+
);
|
197 |
+
}
|
198 |
+
}
|
199 |
+
|
200 |
+
/**
|
201 |
+
* Parses the section arguments and provides defaults
|
202 |
+
*/
|
203 |
+
protected function parse_section( $section ) {
|
204 |
+
$default = array(
|
205 |
+
'title' => '',
|
206 |
+
'priority' => 'default',
|
207 |
+
'context' => 'normal',
|
208 |
+
'chart_type' => 'line',
|
209 |
+
'connector_id' => '',
|
210 |
+
'context_id' => '',
|
211 |
+
'action_id' => '',
|
212 |
+
'selector_id' => '',
|
213 |
+
'is_new' => false,
|
214 |
+
'disabled' => array(),
|
215 |
+
'group' => false,
|
216 |
+
);
|
217 |
+
|
218 |
+
return wp_parse_args( $section, $default );
|
219 |
+
}
|
220 |
+
|
221 |
+
/**
|
222 |
+
* This is the content of the metabox
|
223 |
+
*
|
224 |
+
* @param $object
|
225 |
+
* @param $section
|
226 |
+
*/
|
227 |
+
public function metabox_content( $object, $section ) {
|
228 |
+
$args = $section['args'];
|
229 |
+
|
230 |
+
// Assigning template vars
|
231 |
+
$key = $section['args']['key'];
|
232 |
+
|
233 |
+
$chart_types = $this->get_chart_types();
|
234 |
+
|
235 |
+
if ( array_key_exists( $args['chart_type'], $chart_types ) ) {
|
236 |
+
$chart_types[ $args['chart_type'] ] .= ' active';
|
237 |
+
} else {
|
238 |
+
$args['chart_type'] = 'line';
|
239 |
+
}
|
240 |
+
|
241 |
+
$configure_class = '';
|
242 |
+
if ( $args['is_new'] ) {
|
243 |
+
$configure_class = 'stream-reports-expand';
|
244 |
+
unset( self::$sections[ $key ]['is_new'] );
|
245 |
+
WP_Stream_Reports_Settings::update_user_option( 'sections', self::$sections );
|
246 |
+
}
|
247 |
+
|
248 |
+
$chart_height = WP_Stream_Reports_Settings::get_user_options( 'chart_height' , 300 );
|
249 |
+
$data_types = $this->get_contexts();
|
250 |
+
$action_types = $this->get_actions();
|
251 |
+
$selector_types = $this->get_selector_types();
|
252 |
+
|
253 |
+
include WP_STREAM_REPORTS_VIEW_DIR . 'meta-box.php';
|
254 |
+
}
|
255 |
+
|
256 |
+
public function translate_labels( $coordinates, $args ) {
|
257 |
+
foreach ( $coordinates as $key => $dataset ) {
|
258 |
+
$coordinates[ $key ]['key'] = $this->get_label( $dataset['key'], $args['selector_id'] );
|
259 |
+
}
|
260 |
+
|
261 |
+
return $coordinates;
|
262 |
+
}
|
263 |
+
|
264 |
+
protected function get_label( $value, $grouping ) {
|
265 |
+
if ( 'report-others' === $value ) {
|
266 |
+
return __( 'All Others', 'stream' );
|
267 |
+
}
|
268 |
+
|
269 |
+
switch ( $grouping ) {
|
270 |
+
case 'action':
|
271 |
+
$output = isset( WP_Stream_Connectors::$term_labels['stream_action'][ $value ] ) ? WP_Stream_Connectors::$term_labels['stream_action'][ $value ] : $value;
|
272 |
+
break;
|
273 |
+
case 'author':
|
274 |
+
if ( $value ) {
|
275 |
+
$user_info = get_userdata( $value );
|
276 |
+
$output = isset( $user_info->display_name ) ? $user_info->display_name : sprintf( __( 'User ID: %d', 'stream' ), $value );
|
277 |
+
} else {
|
278 |
+
$output = __( 'N/A', 'stream' );
|
279 |
+
}
|
280 |
+
break;
|
281 |
+
case 'author_role':
|
282 |
+
$output = ucfirst( $value );
|
283 |
+
break;
|
284 |
+
case 'connector':
|
285 |
+
$output = isset( WP_Stream_Connectors::$term_labels['stream_connector'][ $value ] ) ? WP_Stream_Connectors::$term_labels['stream_connector'][ $value ] : $value;
|
286 |
+
break;
|
287 |
+
case 'context':
|
288 |
+
$output = isset( WP_Stream_Connectors::$term_labels['stream_context'][ $value ] ) ? WP_Stream_Connectors::$term_labels['stream_context'][ $value ] : $value;
|
289 |
+
break;
|
290 |
+
default:
|
291 |
+
// Allow plugins to translate the label
|
292 |
+
$output = apply_filters( 'wp_stream_reports_get_label', $value, $grouping );
|
293 |
+
break;
|
294 |
+
}
|
295 |
+
return $output;
|
296 |
+
}
|
297 |
+
|
298 |
+
public function translate_data_type_labels( $value, $grouping ) {
|
299 |
+
return $this->get_data_types( $value ) ? $this->get_data_types( $value ) : $value;
|
300 |
+
}
|
301 |
+
|
302 |
+
/**
|
303 |
+
* Returns data type labels, or a single data type's label'
|
304 |
+
* @return string
|
305 |
+
*/
|
306 |
+
protected function get_data_types( $key = '' ) {
|
307 |
+
$labels = array(
|
308 |
+
'all' => __( 'All Activity', 'stream' ),
|
309 |
+
'connector' => array(
|
310 |
+
'title' => __( 'Connector Activity', 'stream' ),
|
311 |
+
'group' => 'connector',
|
312 |
+
'options' => WP_Stream_Connectors::$term_labels['stream_connector'],
|
313 |
+
'disable' => array(
|
314 |
+
'connector',
|
315 |
+
),
|
316 |
+
),
|
317 |
+
'context' => array(
|
318 |
+
'title' => __( 'Context Activity', 'stream' ),
|
319 |
+
'group' => 'context',
|
320 |
+
'options' => WP_Stream_Connectors::$term_labels['stream_context'],
|
321 |
+
'disable' => array(
|
322 |
+
'context'
|
323 |
+
),
|
324 |
+
),
|
325 |
+
'action' => array(
|
326 |
+
'title' => __( 'Actions Activity', 'stream' ),
|
327 |
+
'group' => 'action',
|
328 |
+
'options' => WP_Stream_Connectors::$term_labels['stream_action'],
|
329 |
+
'disable' => array(
|
330 |
+
'action'
|
331 |
+
),
|
332 |
+
),
|
333 |
+
);
|
334 |
+
|
335 |
+
$labels = apply_filters( 'wp_stream_reports_data_types', $labels );
|
336 |
+
|
337 |
+
if ( '' === $key ) {
|
338 |
+
$output = $labels;
|
339 |
+
} elseif ( array_key_exists( $key, $labels ) ) {
|
340 |
+
$output = $labels[ $key ];
|
341 |
+
} else {
|
342 |
+
$output = false;
|
343 |
+
}
|
344 |
+
|
345 |
+
return $output;
|
346 |
+
}
|
347 |
+
|
348 |
+
/**
|
349 |
+
* Returns chart types available
|
350 |
+
* @return string
|
351 |
+
*/
|
352 |
+
protected function get_chart_types() {
|
353 |
+
return array(
|
354 |
+
'line' => 'dashicons-chart-area',
|
355 |
+
'pie' => 'dashicons-chart-pie',
|
356 |
+
'multibar' => 'dashicons-chart-bar',
|
357 |
+
);
|
358 |
+
}
|
359 |
+
|
360 |
+
/**
|
361 |
+
* Returns selector type labels, or a single selector type's label'
|
362 |
+
* @return string
|
363 |
+
*/
|
364 |
+
protected function get_selector_types( $key = '' ) {
|
365 |
+
$labels = array(
|
366 |
+
'action' => __( 'Action', 'stream' ),
|
367 |
+
'author' => __( 'Author', 'stream' ),
|
368 |
+
'author_role' => __( 'Author Role', 'stream' ),
|
369 |
+
'connector' => __( 'Connector', 'stream' ),
|
370 |
+
'context' => __( 'Context', 'stream' ),
|
371 |
+
'ip' => __( 'IP Address', 'stream' ),
|
372 |
+
);
|
373 |
+
|
374 |
+
$labels = apply_filters( 'wp_stream_reports_selector_types', $labels );
|
375 |
+
|
376 |
+
if ( empty( $key ) ) {
|
377 |
+
$output = $labels;
|
378 |
+
} elseif ( array_key_exists( $key, $labels ) ) {
|
379 |
+
$output = $labels[ $key ];
|
380 |
+
} else {
|
381 |
+
$output = false;
|
382 |
+
}
|
383 |
+
|
384 |
+
return $output;
|
385 |
+
}
|
386 |
+
|
387 |
+
public function load_metabox_records( $args ) {
|
388 |
+
$date_interval = $this->get_date_interval();
|
389 |
+
|
390 |
+
$query_args = array(
|
391 |
+
'records_per_page' => -1,
|
392 |
+
'date_from' => $date_interval['start'],
|
393 |
+
'date_to' => $date_interval['end'],
|
394 |
+
);
|
395 |
+
|
396 |
+
$available_args = array(
|
397 |
+
'action' => 'action_id',
|
398 |
+
'connector' => 'connector_id',
|
399 |
+
'context' => 'context_id',
|
400 |
+
);
|
401 |
+
foreach ( $available_args as $query_key => $args_key ) {
|
402 |
+
if ( isset( $args[ $args_key ] ) ) {
|
403 |
+
$query_args[ $query_key ] = $args[ $args_key ];
|
404 |
+
}
|
405 |
+
}
|
406 |
+
|
407 |
+
$selector = $args['selector_id'];
|
408 |
+
$available_selectors = array( 'author', 'author_role', 'action', 'context', 'connector', 'ip' );
|
409 |
+
|
410 |
+
if ( ! in_array( $selector, $available_selectors ) ) {
|
411 |
+
return array();
|
412 |
+
}
|
413 |
+
|
414 |
+
$query_args = apply_filters( 'wp_stream_reports_query_args', $query_args, $args );
|
415 |
+
$records = wp_stream_query( $query_args );
|
416 |
+
|
417 |
+
if ( 'author_role' === $selector ) {
|
418 |
+
foreach ( $records as $key => $record ) {
|
419 |
+
$user = get_userdata( $record->author );
|
420 |
+
if ( $user ) {
|
421 |
+
$record->author_role = join( ',', $user->roles );
|
422 |
+
} else if ( 0 === $record->author ) {
|
423 |
+
$record->author_role = __( 'N/A', 'stream' );
|
424 |
+
} else {
|
425 |
+
$record->author_role = __( 'Unknown', 'stream' );
|
426 |
+
}
|
427 |
+
}
|
428 |
+
}
|
429 |
+
|
430 |
+
$records = $this->charts->group_by_field( $selector, $records );
|
431 |
+
$records = $this->charts->offset_record_dates( $records );
|
432 |
+
|
433 |
+
return apply_filters( 'wp_stream_reports_load_records', $records, $args );
|
434 |
+
}
|
435 |
+
|
436 |
+
protected function get_date_interval(){
|
437 |
+
$date = new WP_Stream_Date_Interval();
|
438 |
+
$default_interval = array(
|
439 |
+
'key' => 'all-time',
|
440 |
+
'start' => '',
|
441 |
+
'end' => '',
|
442 |
+
);
|
443 |
+
|
444 |
+
$user_interval = WP_Stream_Reports_Settings::get_user_options( 'interval', $default_interval );
|
445 |
+
$user_interval_key = $user_interval['key'];
|
446 |
+
$available_intervals = $date->get_predefined_intervals();
|
447 |
+
|
448 |
+
if ( array_key_exists( $user_interval_key, $available_intervals ) ) {
|
449 |
+
$interval = $available_intervals[ $user_interval_key ];
|
450 |
+
$user_interval['start'] = isset( $interval['start'] ) ? $interval['start']->toDateString() : null;
|
451 |
+
$user_interval['end'] = isset( $interval['end'] ) ? $interval['end']->toDateString() : null;
|
452 |
+
}
|
453 |
+
|
454 |
+
return $user_interval;
|
455 |
+
}
|
456 |
+
|
457 |
+
/**
|
458 |
+
* Creates a title generated from the arguments for the chart
|
459 |
+
*/
|
460 |
+
protected function get_generated_title( $args ) {
|
461 |
+
if ( empty( $args['selector_id'] ) ) {
|
462 |
+
return sprintf( esc_html__( 'Report %d', 'stream' ), absint( $args['key'] + 1 ) );
|
463 |
+
}
|
464 |
+
|
465 |
+
if ( ! empty( $args['context_id'] ) ) {
|
466 |
+
$dataset = $this->get_label( $args['context_id'], 'context' );
|
467 |
+
} else if ( ! empty( $args['connector_id'] ) ) {
|
468 |
+
$dataset = $this->get_label( $args['connector_id'], 'connector' );
|
469 |
+
} else {
|
470 |
+
$dataset = '';
|
471 |
+
}
|
472 |
+
|
473 |
+
$action = ( ! empty( $args['action_id'] ) ) ? $this->get_label( $args['action_id'], 'action' ) : null;
|
474 |
+
$selector = ( ! empty( $args['selector_id'] ) ) ? $this->get_selector_types( $args['selector_id'] ) : null;
|
475 |
+
|
476 |
+
if ( ! empty( $action ) ) {
|
477 |
+
if ( ! empty( $dataset ) ) {
|
478 |
+
$string = _x(
|
479 |
+
'%1$s in %2$s by %3$s',
|
480 |
+
'1: Action 2: Dataset 3: Selector',
|
481 |
+
'stream'
|
482 |
+
);
|
483 |
+
} else {
|
484 |
+
$string = _x(
|
485 |
+
'All %1$s by %3$s',
|
486 |
+
'1: Action 3: Selector',
|
487 |
+
'stream'
|
488 |
+
);
|
489 |
+
}
|
490 |
+
} else if ( ! empty( $dataset ) ) {
|
491 |
+
$string = _x(
|
492 |
+
'All Activity in %2$s by %3$s',
|
493 |
+
'2: Dataset 3: Selector',
|
494 |
+
'stream'
|
495 |
+
);
|
496 |
+
} else {
|
497 |
+
$string = _x(
|
498 |
+
'All Activity by %3$s',
|
499 |
+
'3: Selector',
|
500 |
+
'stream'
|
501 |
+
);
|
502 |
+
}
|
503 |
+
|
504 |
+
$title = sprintf( $string, $action, $dataset, $selector );
|
505 |
+
return $title;
|
506 |
+
}
|
507 |
+
|
508 |
+
/**
|
509 |
+
* Update configuration array from ajax call and save this to the user option
|
510 |
+
*/
|
511 |
+
public function save_metabox_config() {
|
512 |
+
$id = wp_stream_filter_input( INPUT_GET, 'section_id', FILTER_SANITIZE_NUMBER_INT );
|
513 |
+
|
514 |
+
$input = array(
|
515 |
+
'id' => wp_stream_filter_input( INPUT_GET, 'section_id', FILTER_SANITIZE_NUMBER_INT ),
|
516 |
+
'title' => wp_stream_filter_input( INPUT_GET, 'title', FILTER_SANITIZE_STRING ),
|
517 |
+
'chart_type' => wp_stream_filter_input( INPUT_GET, 'chart_type', FILTER_SANITIZE_STRING ),
|
518 |
+
'connector_id' => wp_stream_filter_input( INPUT_GET, 'data_connector', FILTER_SANITIZE_STRING ),
|
519 |
+
'context_id' => wp_stream_filter_input( INPUT_GET, 'data_context', FILTER_SANITIZE_STRING ),
|
520 |
+
'action_id' => wp_stream_filter_input( INPUT_GET, 'data_action', FILTER_SANITIZE_STRING ),
|
521 |
+
'selector_id' => wp_stream_filter_input( INPUT_GET, 'data_selector', FILTER_SANITIZE_STRING ),
|
522 |
+
);
|
523 |
+
|
524 |
+
$required_fields = array( 'id', 'title', 'chart_type', 'selector_id' );
|
525 |
+
foreach ( $required_fields as $key ){
|
526 |
+
if ( $input[ $key ] === null ) {
|
527 |
+
wp_send_json_error( array( 'missing' => $key, 'value' => $input[ $key ] ) );
|
528 |
+
}
|
529 |
+
}
|
530 |
+
|
531 |
+
// Store the chart configuration
|
532 |
+
self::$sections[ $id ] = $input;
|
533 |
+
|
534 |
+
// Update the database option
|
535 |
+
WP_Stream_Reports_Settings::ajax_update_user_option( 'sections', self::$sections );
|
536 |
+
}
|
537 |
+
|
538 |
+
/**
|
539 |
+
* Instantly update chart based on user configuration
|
540 |
+
*/
|
541 |
+
public function update_metabox_display() {
|
542 |
+
$section_id = wp_stream_filter_input( INPUT_GET, 'section_id', FILTER_SANITIZE_NUMBER_INT );
|
543 |
+
$section = $this->get_section( $section_id );
|
544 |
+
|
545 |
+
$args = $this->parse_section( $section );
|
546 |
+
|
547 |
+
$chart_types = $this->get_chart_types();
|
548 |
+
|
549 |
+
if ( ! array_key_exists( $args['chart_type'], $chart_types ) ) {
|
550 |
+
$args['chart_type'] = 'line';
|
551 |
+
}
|
552 |
+
|
553 |
+
$records = $this->load_metabox_records( $args );
|
554 |
+
$chart_options = $this->charts->get_chart_options( $args, $records );
|
555 |
+
|
556 |
+
wp_send_json_success(
|
557 |
+
array(
|
558 |
+
'options' => $chart_options,
|
559 |
+
'title' => $section['title'],
|
560 |
+
'generated_title' => $this->get_generated_title( $args ),
|
561 |
+
)
|
562 |
+
);
|
563 |
+
}
|
564 |
+
|
565 |
+
/**
|
566 |
+
* This function will handle the ajax request to add a metabox to the page.
|
567 |
+
*/
|
568 |
+
public function add_metabox() {
|
569 |
+
// Add a new section
|
570 |
+
self::$sections[] = array(
|
571 |
+
'is_new' => true,
|
572 |
+
);
|
573 |
+
|
574 |
+
// Push new metabox to top of the display
|
575 |
+
$new_section_id = 'wp-stream-reports-' . ( count( self::$sections ) - 1 );
|
576 |
+
$order = get_user_option( 'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG );
|
577 |
+
$normal_order = explode( ',', $order['normal'] );
|
578 |
+
|
579 |
+
array_unshift( $normal_order, $new_section_id );
|
580 |
+
$order['normal'] = join( ',', $normal_order );
|
581 |
+
update_user_option( get_current_user_id(), 'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG, $order, true );
|
582 |
+
|
583 |
+
WP_Stream_Reports_Settings::update_user_option_and_redirect( 'sections', self::$sections );
|
584 |
+
}
|
585 |
+
|
586 |
+
/**
|
587 |
+
* This function will remove the metabox from the current view.
|
588 |
+
*/
|
589 |
+
public function delete_metabox() {
|
590 |
+
$meta_key = wp_stream_filter_input( INPUT_GET, 'key', FILTER_SANITIZE_NUMBER_INT );
|
591 |
+
|
592 |
+
// Unset the metabox from the array.
|
593 |
+
unset( self::$sections[ $meta_key ] );
|
594 |
+
|
595 |
+
// If there is no more section. We delete the user option.
|
596 |
+
if ( empty( self::$sections ) ) {
|
597 |
+
delete_user_option(
|
598 |
+
get_current_user_id(),
|
599 |
+
'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG,
|
600 |
+
true
|
601 |
+
);
|
602 |
+
} else {
|
603 |
+
// Delete the metabox from the page ordering as well
|
604 |
+
// There might be a better way on handling this I'm sure (stream_page should not be hardcoded)
|
605 |
+
$user_options = get_user_option( 'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG );
|
606 |
+
}
|
607 |
+
|
608 |
+
if ( ! empty( $user_options ) ) {
|
609 |
+
// Remove the one we are deleting from the list
|
610 |
+
foreach ( $user_options as $key => &$string ) {
|
611 |
+
$order = explode( ',', $string );
|
612 |
+
if ( false !== ( $key = array_search( self::META_PREFIX . $meta_key, $order ) ) ) {
|
613 |
+
unset( $order[ $key ] );
|
614 |
+
$string = implode( ',', $order );
|
615 |
+
}
|
616 |
+
}
|
617 |
+
|
618 |
+
// Save the ordering again
|
619 |
+
update_user_option(
|
620 |
+
get_current_user_id(),
|
621 |
+
'meta-box-order_stream_page_' . WP_Stream_Reports::REPORTS_PAGE_SLUG,
|
622 |
+
$user_options,
|
623 |
+
true
|
624 |
+
);
|
625 |
+
}
|
626 |
+
|
627 |
+
WP_Stream_Reports_Settings::update_user_option_and_redirect( 'sections', self::$sections );
|
628 |
+
}
|
629 |
+
|
630 |
+
public function save_chart_options(){
|
631 |
+
$section_id = wp_stream_filter_input( INPUT_GET, 'section_id', FILTER_SANITIZE_NUMBER_INT );
|
632 |
+
$section = $this->get_section( $section_id );
|
633 |
+
$type = wp_stream_filter_input( INPUT_GET, 'update_type', FILTER_SANITIZE_STRING );
|
634 |
+
|
635 |
+
if ( 'disable' === $type ) {
|
636 |
+
if ( ! isset( $_GET['update_payload'] ) || ! is_array( $_GET['update_payload'] ) ) {
|
637 |
+
wp_send_json_error();
|
638 |
+
}
|
639 |
+
|
640 |
+
$payload = array();
|
641 |
+
foreach ( $_GET['update_payload'] as $key => $value ) {
|
642 |
+
if ( 'true' === $value ) {
|
643 |
+
$payload[] = absint( $key );
|
644 |
+
}
|
645 |
+
}
|
646 |
+
|
647 |
+
$section['disabled'] = $payload;
|
648 |
+
} elseif ( 'group' === $type ) {
|
649 |
+
$payload = wp_stream_filter_input( INPUT_GET, 'update_payload', FILTER_SANITIZE_STRING );
|
650 |
+
$section['group'] = 'true' === $payload;
|
651 |
+
}
|
652 |
+
|
653 |
+
// Store the chart configuration
|
654 |
+
self::$sections[ $section_id ] = $section;
|
655 |
+
|
656 |
+
// Update the database option
|
657 |
+
WP_Stream_Reports_Settings::ajax_update_user_option( 'sections', self::$sections );
|
658 |
+
}
|
659 |
+
|
660 |
+
public function save_chart_height(){
|
661 |
+
$chart_height = wp_stream_filter_input( INPUT_GET, 'chart_height', FILTER_SANITIZE_NUMBER_INT );
|
662 |
+
|
663 |
+
if ( false === $chart_height ) {
|
664 |
+
wp_send_json_error();
|
665 |
+
}
|
666 |
+
|
667 |
+
// Update the database option
|
668 |
+
WP_Stream_Reports_Settings::ajax_update_user_option( 'chart_height', $chart_height );
|
669 |
+
}
|
670 |
+
|
671 |
+
public function chart_height_display( $status, $args ) {
|
672 |
+
$user_id = get_current_user_id();
|
673 |
+
$option = WP_Stream_Reports_Settings::get_user_options( 'chart_height', 300 );
|
674 |
+
$nonce = wp_create_nonce( 'wp_stream_reports_chart_height_nonce' );
|
675 |
+
ob_start();
|
676 |
+
?>
|
677 |
+
<fieldset>
|
678 |
+
<h5><?php esc_html_e( 'Chart height', 'stream' ); ?></h5>
|
679 |
+
<div><input type="hidden" name="update_chart_height_nonce" id="update_chart_height_nonce" value="<?php echo esc_attr( $nonce ); ?>"></div>
|
680 |
+
<div><input type="hidden" name="update_chart_height_user" id="update_chart_height_user" value="<?php echo esc_attr( $user_id ); ?>"></div>
|
681 |
+
<div class="metabox-prefs stream-reports-chart-height-option">
|
682 |
+
<label for="chart-height">
|
683 |
+
<input type="number" step="50" min="100" max="950" name="chart_height" id="chart_height" maxlength="3" value="<?php echo esc_attr( $option ); ?>">
|
684 |
+
<?php esc_html_e( 'px', 'stream' ); ?>
|
685 |
+
</label>
|
686 |
+
<input type="submit" id="chart_height_apply" class="button" value="<?php esc_attr_e( 'Apply', 'stream' ); ?>">
|
687 |
+
<span class="spinner"></span>
|
688 |
+
</div>
|
689 |
+
</fieldset>
|
690 |
+
<?php
|
691 |
+
return ob_get_clean();
|
692 |
+
}
|
693 |
+
|
694 |
+
protected function get_section( $id ) {
|
695 |
+
if ( empty( self::$sections ) ) {
|
696 |
+
self::$sections = WP_Stream_Reports_Settings::get_user_options( 'sections' );
|
697 |
+
}
|
698 |
+
|
699 |
+
$section = self::$sections[ $id ];
|
700 |
+
$section = $this->parse_section( $section );
|
701 |
+
|
702 |
+
$section['key'] = $id;
|
703 |
+
|
704 |
+
return $section;
|
705 |
+
}
|
706 |
+
|
707 |
+
public function get_contexts() {
|
708 |
+
|
709 |
+
// Add Connectors as parents, and apply the Contexts as children
|
710 |
+
$contexts = $this->assemble_records( 'context' );
|
711 |
+
$connectors = $this->assemble_records( 'connector' );
|
712 |
+
foreach ( $connectors as $connector => $item ) {
|
713 |
+
$context_items[ $connector ]['label'] = $item['label'];
|
714 |
+
$context_items[ $connector ]['connector'] = $connector;
|
715 |
+
$context_items[ $connector ]['disabled'] = $item['disabled'];
|
716 |
+
foreach ( $contexts as $context_value => $context_item ) {
|
717 |
+
if ( isset( WP_Stream_Connectors::$contexts[ $connector ] ) && array_key_exists( $context_value, WP_Stream_Connectors::$contexts[ $connector ] ) ) {
|
718 |
+
$context_items[ $connector ]['children'][ $context_value ] = $context_item;
|
719 |
+
$context_items[ $connector ]['children'][ $context_value ]['connector'] = $connector;
|
720 |
+
$context_items[ $connector ]['children'][ $context_value ]['context'] = $context_value;
|
721 |
+
}
|
722 |
+
}
|
723 |
+
}
|
724 |
+
|
725 |
+
foreach ( $context_items as $context_value => $context_item ) {
|
726 |
+
if ( ! isset( $context_item['children'] ) || empty( $context_item['children'] ) ) {
|
727 |
+
unset( $context_items[ $context_value ] );
|
728 |
+
}
|
729 |
+
}
|
730 |
+
|
731 |
+
$all_items = array(
|
732 |
+
'label' => __( 'All Contexts', 'stream' )
|
733 |
+
);
|
734 |
+
|
735 |
+
$new_array = apply_filters( 'wp_stream_reports_get_contexts', $context_items );
|
736 |
+
return array_merge( array( $all_items ), $new_array );
|
737 |
+
}
|
738 |
+
|
739 |
+
public function get_actions() {
|
740 |
+
$actions = $this->assemble_records( 'action' );
|
741 |
+
foreach ( $actions as $id => $item ) {
|
742 |
+
$actions[ $id ]['action'] = $id;
|
743 |
+
}
|
744 |
+
|
745 |
+
$all_actions = array(
|
746 |
+
'label' => __( 'All Actions', 'stream' )
|
747 |
+
);
|
748 |
+
|
749 |
+
$new_array = array_merge( array( $all_actions ), $actions );
|
750 |
+
return apply_filters( 'wp_stream_reports_get_actions', $new_array );
|
751 |
+
}
|
752 |
+
|
753 |
+
/**
|
754 |
+
* Assembles records for display
|
755 |
+
*
|
756 |
+
* Gathers list of all authors/connectors, then compares it to
|
757 |
+
* results of existing records. All items that do not exist in records
|
758 |
+
* get assigned a disabled value of "true".
|
759 |
+
*
|
760 |
+
* @param string Column requested
|
761 |
+
*
|
762 |
+
* @return array options to be displayed in search filters
|
763 |
+
*/
|
764 |
+
function assemble_records( $column ) {
|
765 |
+
|
766 |
+
$available_columns = array( 'context', 'connector', 'action' );
|
767 |
+
if ( ! in_array( $column, $available_columns ) ) {
|
768 |
+
return;
|
769 |
+
}
|
770 |
+
|
771 |
+
$prefixed_column = sprintf( 'stream_%s', $column );
|
772 |
+
$all_records = WP_Stream_Connectors::$term_labels[ $prefixed_column ];
|
773 |
+
|
774 |
+
$active_records = array();
|
775 |
+
$disabled_records = array();
|
776 |
+
|
777 |
+
foreach ( $all_records as $record => $label ) {
|
778 |
+
if ( isset( $this->existing_records[ $column ] ) && in_array( $record, $this->existing_records[ $column ] ) ) {
|
779 |
+
$active_records[ $record ] = array( 'label' => $label, 'disabled' => '' );
|
780 |
+
} else {
|
781 |
+
$disabled_records[ $record ] = array( 'label' => $label, 'disabled' => 'disabled="disabled"' );
|
782 |
+
}
|
783 |
+
}
|
784 |
+
|
785 |
+
// Remove WP-CLI pseudo user if no records with user=0 exist
|
786 |
+
if ( isset( $disabled_records[0] ) ) {
|
787 |
+
unset( $disabled_records[0] );
|
788 |
+
}
|
789 |
+
|
790 |
+
$sort = function ( $a, $b ) use ( $column ) {
|
791 |
+
$label_a = (string) $a['label'];
|
792 |
+
$label_b = (string) $b['label'];
|
793 |
+
if ( $label_a === $label_b ) {
|
794 |
+
return 0;
|
795 |
+
}
|
796 |
+
return strtolower( $label_a ) < strtolower( $label_b ) ? -1 : 1;
|
797 |
+
};
|
798 |
+
uasort( $active_records, $sort );
|
799 |
+
uasort( $disabled_records, $sort );
|
800 |
+
|
801 |
+
// Not using array_merge() in order to preserve the array index for the Authors dropdown which uses the user_id as the key
|
802 |
+
$all_records = $active_records + $disabled_records;
|
803 |
+
|
804 |
+
return $all_records;
|
805 |
+
}
|
806 |
+
|
807 |
+
/**
|
808 |
+
* Gets existing records for filtering dropdown menus
|
809 |
+
*
|
810 |
+
* @return array
|
811 |
+
*/
|
812 |
+
function get_existing_records() {
|
813 |
+
$existing_records = array();
|
814 |
+
|
815 |
+
$args = array(
|
816 |
+
'aggregations' => array(
|
817 |
+
'connector',
|
818 |
+
'context',
|
819 |
+
'action',
|
820 |
+
),
|
821 |
+
);
|
822 |
+
|
823 |
+
$query = wp_stream_query( $args );
|
824 |
+
$query_meta = WP_Stream::$db->get_query_meta();
|
825 |
+
|
826 |
+
if ( isset( $query_meta->aggregations ) ) {
|
827 |
+
foreach ( $query_meta->aggregations as $field => $aggregation ) {
|
828 |
+
$existing_records[ $field ] = array();
|
829 |
+
foreach ( $aggregation->buckets as $bucket ) {
|
830 |
+
$existing_records[ $field ][] = $bucket->key;
|
831 |
+
}
|
832 |
+
}
|
833 |
+
}
|
834 |
+
|
835 |
+
return $existing_records;
|
836 |
+
}
|
837 |
+
|
838 |
+
public function migrate_settings() {
|
839 |
+
$sections = self::$sections;
|
840 |
+
foreach ( $sections as $key => $args ) {
|
841 |
+
switch ( $args['data_group'] ) {
|
842 |
+
case 'action' :
|
843 |
+
$sections[ $key ]['action_id'] = $args['data_type'];
|
844 |
+
break;
|
845 |
+
case 'connector' :
|
846 |
+
$sections[ $key ]['connector_id'] = $args['data_type'];
|
847 |
+
break;
|
848 |
+
case 'context' :
|
849 |
+
$sections[ $key ]['connector_id'] = $this->find_connector_by_context( $args['data_type'] );
|
850 |
+
$sections[ $key ]['context_id'] = $args['data_type'];
|
851 |
+
break;
|
852 |
+
case 'other' :
|
853 |
+
break;
|
854 |
+
}
|
855 |
+
|
856 |
+
if ( isset( $args['selector_type'] ) ) {
|
857 |
+
$sections[ $key ]['selector_id'] = $args['selector_type'];
|
858 |
+
}
|
859 |
+
|
860 |
+
unset( $sections[ $key ]['data_group'] );
|
861 |
+
unset( $sections[ $key ]['data_type'] );
|
862 |
+
unset( $sections[ $key ]['selector_type'] );
|
863 |
+
}
|
864 |
+
|
865 |
+
WP_Stream_Reports_Settings::update_user_option_and_redirect( 'sections', $sections );
|
866 |
+
}
|
867 |
+
|
868 |
+
public function find_connector_by_context( $context ) {
|
869 |
+
$output = 'unknown';
|
870 |
+
$connectors = $this->assemble_records( 'connector' );
|
871 |
+
foreach ( $connectors as $connector => $item ) {
|
872 |
+
if ( isset( WP_Stream_Connectors::$contexts[ $connector ] ) && array_key_exists( $context, WP_Stream_Connectors::$contexts[ $connector ] ) ) {
|
873 |
+
$output = $connector;
|
874 |
+
break;
|
875 |
+
}
|
876 |
+
}
|
877 |
+
|
878 |
+
return $output;
|
879 |
+
}
|
880 |
+
|
881 |
+
/**
|
882 |
+
* Return active instance of WP_Stream_Reports_Metaboxes, create one if it doesn't exist
|
883 |
+
*
|
884 |
+
* @return WP_Stream_Reports_Metaboxes
|
885 |
+
*/
|
886 |
+
public static function get_instance() {
|
887 |
+
if ( empty( self::$instance ) ) {
|
888 |
+
$class = __CLASS__;
|
889 |
+
self::$instance = new $class;
|
890 |
+
}
|
891 |
+
|
892 |
+
return self::$instance;
|
893 |
+
}
|
894 |
+
|
895 |
+
}
|
extensions/reports/includes/class-wp-stream-reports-settings.php
ADDED
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Settings class for Stream Reports
|
4 |
+
*
|
5 |
+
* @author X-Team <x-team.com>
|
6 |
+
* @author Shady Sharaf <shady@x-team.com>
|
7 |
+
* @author Jaroslav Polakovič <dero@x-team.com>
|
8 |
+
* @author Jonathan Bardo <jonathan.bardo@x-team.com>
|
9 |
+
*/
|
10 |
+
class WP_Stream_Reports_Settings {
|
11 |
+
|
12 |
+
/**
|
13 |
+
* Contains the option fields for the settings
|
14 |
+
*
|
15 |
+
* @var array $fields
|
16 |
+
*/
|
17 |
+
public static $fields = array();
|
18 |
+
|
19 |
+
/**
|
20 |
+
* Contains the array of user options for the plugin
|
21 |
+
*
|
22 |
+
* @var array $user_options
|
23 |
+
*/
|
24 |
+
private static $user_options;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Holds the user option name (key)
|
28 |
+
*/
|
29 |
+
const OPTION_NAME = 'stream_reports_settings';
|
30 |
+
|
31 |
+
/**
|
32 |
+
* Public constructor
|
33 |
+
*/
|
34 |
+
public static function load() {
|
35 |
+
// User and role caps
|
36 |
+
add_filter( 'user_has_cap', array( __CLASS__, '_filter_user_caps' ), 10, 4 );
|
37 |
+
add_filter( 'role_has_cap', array( __CLASS__, '_filter_role_caps' ), 10, 3 );
|
38 |
+
|
39 |
+
if ( WP_Stream::$api->is_restricted() ) {
|
40 |
+
return;
|
41 |
+
}
|
42 |
+
|
43 |
+
// Add Reports settings tab to Stream settings
|
44 |
+
add_filter( 'wp_stream_settings_option_fields', array( __CLASS__, '_register_settings' ) );
|
45 |
+
}
|
46 |
+
|
47 |
+
public static function get_fields() {
|
48 |
+
if ( empty( self::$fields ) ) {
|
49 |
+
$fields = array();
|
50 |
+
|
51 |
+
self::$fields = apply_filters( 'wp_stream_reports_option_fields', $fields );
|
52 |
+
}
|
53 |
+
|
54 |
+
return self::$fields;
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Appends Reports settings to Stream settings
|
59 |
+
*
|
60 |
+
* @filter wp_stream_settings_option_fields
|
61 |
+
*/
|
62 |
+
public static function _register_settings( $stream_fields ) {
|
63 |
+
return array_merge( $stream_fields, self::get_fields() );
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Filter user caps to dynamically grant our view cap based on allowed roles
|
68 |
+
*
|
69 |
+
* @filter user_has_cap
|
70 |
+
*
|
71 |
+
* @param $allcaps
|
72 |
+
* @param $caps
|
73 |
+
* @param $args
|
74 |
+
* @param $user
|
75 |
+
*
|
76 |
+
* @return array
|
77 |
+
*/
|
78 |
+
public static function _filter_user_caps( $allcaps, $caps, $args, $user = null ) {
|
79 |
+
$user = is_a( $user, 'WP_User' ) ? $user : wp_get_current_user();
|
80 |
+
|
81 |
+
foreach ( $caps as $cap ) {
|
82 |
+
if ( WP_Stream_Reports::VIEW_CAP === $cap ) {
|
83 |
+
foreach ( $user->roles as $role ) {
|
84 |
+
if ( self::_role_can_access( $role ) ) {
|
85 |
+
$allcaps[ $cap ] = true;
|
86 |
+
break 2;
|
87 |
+
}
|
88 |
+
}
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
return $allcaps;
|
93 |
+
}
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Filter role caps to dynamically grant our view cap based on allowed roles
|
97 |
+
*
|
98 |
+
* @filter role_has_cap
|
99 |
+
*
|
100 |
+
* @param $allcaps
|
101 |
+
* @param $cap
|
102 |
+
* @param $role
|
103 |
+
*
|
104 |
+
* @return array
|
105 |
+
*/
|
106 |
+
public static function _filter_role_caps( $allcaps, $cap, $role ) {
|
107 |
+
if ( WP_Stream_Reports::VIEW_CAP === $cap && self::_role_can_access( $role ) ) {
|
108 |
+
$allcaps[ $cap ] = true;
|
109 |
+
}
|
110 |
+
|
111 |
+
return $allcaps;
|
112 |
+
}
|
113 |
+
|
114 |
+
private static function _role_can_access( $role ) {
|
115 |
+
// Default role if one is not set by default
|
116 |
+
if ( ! isset( WP_Stream_Settings::$options['reports_role_access'] ) ) {
|
117 |
+
WP_Stream_Settings::$options['reports_role_access'] = array( 'administrator' );
|
118 |
+
}
|
119 |
+
|
120 |
+
if ( in_array( $role, WP_Stream_Settings::$options['reports_role_access'] ) ) {
|
121 |
+
return true;
|
122 |
+
}
|
123 |
+
|
124 |
+
return false;
|
125 |
+
}
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Returns true if the settings have not been setup for this user
|
129 |
+
*
|
130 |
+
* @return boolean
|
131 |
+
*/
|
132 |
+
public static function is_first_visit() {
|
133 |
+
if ( ! get_user_option( self::get_option_key() ) ) {
|
134 |
+
return true;
|
135 |
+
}
|
136 |
+
|
137 |
+
return false;
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Get user option and store it in a static var for easy access
|
142 |
+
*
|
143 |
+
* @param null $key
|
144 |
+
* @param array $default
|
145 |
+
*
|
146 |
+
* @return array
|
147 |
+
*/
|
148 |
+
public static function get_user_options( $key = null, $default = array() ) {
|
149 |
+
if ( empty( self::$user_options ) ) {
|
150 |
+
self::$user_options = get_user_option( self::get_option_key() );
|
151 |
+
}
|
152 |
+
|
153 |
+
if ( is_null( $key ) ) {
|
154 |
+
// Return empty array if no user option is in DB
|
155 |
+
$output = ( self::$user_options ) ?: array();
|
156 |
+
} else {
|
157 |
+
$output = isset( self::$user_options[ $key ] ) ? self::$user_options[ $key ] : $default;
|
158 |
+
}
|
159 |
+
|
160 |
+
return $output;
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Handle option updating in the database
|
165 |
+
*
|
166 |
+
* @param string $key
|
167 |
+
* @param mixed $option
|
168 |
+
* @param bool $redirect If the function must redirect and exit here
|
169 |
+
*/
|
170 |
+
public static function update_user_option( $key, $option ) {
|
171 |
+
$user_options = self::get_user_options();
|
172 |
+
|
173 |
+
if ( ! isset( $user_options[ $key ] ) ) {
|
174 |
+
$user_options[ $key ] = array();
|
175 |
+
}
|
176 |
+
|
177 |
+
// Don't re-save if the value hasn't changed
|
178 |
+
if ( $user_options[ $key ] != $option ) {
|
179 |
+
$user_options[ $key ] = $option;
|
180 |
+
$is_saved = update_user_option( get_current_user_id(), self::get_option_key(), $user_options );
|
181 |
+
} else {
|
182 |
+
$is_saved = true;
|
183 |
+
}
|
184 |
+
|
185 |
+
return $is_saved;
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* Handles saving during AJAX requests
|
190 |
+
*
|
191 |
+
* @param string $key
|
192 |
+
* @param mixed $option
|
193 |
+
*/
|
194 |
+
public static function ajax_update_user_option( $key, $option ) {
|
195 |
+
check_ajax_referer( 'stream-reports-page', 'wp_stream_reports_nonce' );
|
196 |
+
|
197 |
+
$is_saved = self::update_user_option( $key, $option );
|
198 |
+
|
199 |
+
if ( $is_saved ) {
|
200 |
+
wp_send_json_success();
|
201 |
+
} else {
|
202 |
+
wp_send_json_error();
|
203 |
+
}
|
204 |
+
}
|
205 |
+
|
206 |
+
/**
|
207 |
+
* Updates the user option and redirects back to the main page if successful
|
208 |
+
*
|
209 |
+
* @param string $key
|
210 |
+
* @param mixed $option
|
211 |
+
*/
|
212 |
+
public static function update_user_option_and_redirect( $key, $option ) {
|
213 |
+
$is_saved = self::update_user_option( $key, $option );
|
214 |
+
|
215 |
+
if ( $is_saved ) {
|
216 |
+
wp_safe_redirect(
|
217 |
+
add_query_arg(
|
218 |
+
array( 'page' => WP_Stream_Reports::REPORTS_PAGE_SLUG ),
|
219 |
+
self_admin_url( 'admin.php' )
|
220 |
+
)
|
221 |
+
);
|
222 |
+
|
223 |
+
exit;
|
224 |
+
} else {
|
225 |
+
wp_die( __( "Uh no! This wasn't suppose to happen :(", 'stream' ) );
|
226 |
+
}
|
227 |
+
}
|
228 |
+
|
229 |
+
public static function get_option_key() {
|
230 |
+
return self::OPTION_NAME;
|
231 |
+
}
|
232 |
+
|
233 |
+
}
|
extensions/reports/includes/template-tags.php
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
function wp_stream_reports_selector( $data_types, $args, $class ) {
|
4 |
+
$options = array();
|
5 |
+
foreach ( $data_types as $key => $item ) {
|
6 |
+
$selected = false;
|
7 |
+
|
8 |
+
if ( isset( $item['connector'] ) && $item['connector'] == $args['connector_id'] && isset( $item['context'] ) && $item['context'] == null ) {
|
9 |
+
$selected = true;
|
10 |
+
} else if ( isset( $item['action'] ) && $item['action'] == $args['action_id'] ) {
|
11 |
+
$selected = true;
|
12 |
+
}
|
13 |
+
|
14 |
+
$option_args = array(
|
15 |
+
'value' => $key,
|
16 |
+
'label' => isset( $item['label'] ) ? $item['label'] : null,
|
17 |
+
'selected' => selected( $selected, true, false ),
|
18 |
+
'disabled' => isset( $item['disabled'] ) ? $item['disabled'] : null,
|
19 |
+
'class' => isset( $item['children'] ) ? 'level-1' : null,
|
20 |
+
|
21 |
+
'connector' => isset( $item['connector'] ) ? $item['connector'] : null,
|
22 |
+
'context' => isset( $item['context'] ) ? $item['context'] : null,
|
23 |
+
'action' => isset( $item['action'] ) ? $item['action'] : null,
|
24 |
+
);
|
25 |
+
$options[] = wp_stream_reports_filter_option( $option_args );
|
26 |
+
|
27 |
+
if ( isset( $item['children'] ) ) {
|
28 |
+
foreach ( $item['children'] as $child_value => $child_item ) {
|
29 |
+
$selected = false;
|
30 |
+
if ( isset( $child_item['connector'] ) && $child_item['connector'] == $args['connector_id'] && isset( $child_item['context'] ) && $child_item['context'] == $args['context_id'] ) {
|
31 |
+
$selected = true;
|
32 |
+
}
|
33 |
+
|
34 |
+
$option_args = array(
|
35 |
+
'value' => $child_value,
|
36 |
+
'label' => isset( $child_item['label'] ) ? $child_item['label'] : null,
|
37 |
+
'selected' => selected( $selected, true, false ),
|
38 |
+
'disabled' => isset( $child_item['disabled'] ) ? $child_item['disabled'] : null,
|
39 |
+
'class' => 'level-2',
|
40 |
+
|
41 |
+
'connector' => isset( $child_item['connector'] ) ? $child_item['connector'] : null,
|
42 |
+
'context' => isset( $child_item['context'] ) ? $child_item['context'] : null,
|
43 |
+
'action' => isset( $child_item['action'] ) ? $child_item['action'] : null,
|
44 |
+
);
|
45 |
+
$options[] = wp_stream_reports_filter_option( $option_args );
|
46 |
+
}
|
47 |
+
}
|
48 |
+
}
|
49 |
+
|
50 |
+
$allowed_html = array(
|
51 |
+
'option' => array(
|
52 |
+
'value' => array(),
|
53 |
+
'selected' => array(),
|
54 |
+
'disabled' => array(),
|
55 |
+
'class' => array(),
|
56 |
+
'data-connector' => array(),
|
57 |
+
'data-context' => array(),
|
58 |
+
'data-action' => array(),
|
59 |
+
),
|
60 |
+
);
|
61 |
+
|
62 |
+
printf(
|
63 |
+
'<select class="%s">%s</select>',
|
64 |
+
esc_attr( $class ),
|
65 |
+
wp_kses( implode( '', $options ), $allowed_html )
|
66 |
+
);
|
67 |
+
}
|
68 |
+
|
69 |
+
function wp_stream_reports_filter_option( $args ) {
|
70 |
+
$defaults = array(
|
71 |
+
'value' => null,
|
72 |
+
'selected' => null,
|
73 |
+
'disabled' => null,
|
74 |
+
'class' => null,
|
75 |
+
'label' => null,
|
76 |
+
'connector' => null,
|
77 |
+
'context' => null,
|
78 |
+
'action' => null,
|
79 |
+
);
|
80 |
+
|
81 |
+
$args = wp_parse_args( $args, $defaults );
|
82 |
+
return sprintf(
|
83 |
+
'<option value="%s" %s %s %s %s %s class="%s">%s</option>',
|
84 |
+
esc_attr( $args['value'] ),
|
85 |
+
$args['selected'],
|
86 |
+
$args['disabled'],
|
87 |
+
$args['connector'] ? sprintf( 'data-connector="%s"', esc_attr( $args['connector'] ) ) : null,
|
88 |
+
$args['context'] ? sprintf( 'data-context="%s"', esc_attr( $args['context'] ) ) : null,
|
89 |
+
$args['action'] ? sprintf( 'data-action="%s"', esc_attr( $args['action'] ) ) : null,
|
90 |
+
$args['class'] ? esc_attr( $args['class'] ) : null,
|
91 |
+
esc_html( $args['label'] )
|
92 |
+
);
|
93 |
+
}
|
94 |
+
|
95 |
+
function wp_stream_reports_intervals_html() {
|
96 |
+
$date = WP_Stream_Reports_Date_Interval::get_instance();
|
97 |
+
|
98 |
+
// Default interval
|
99 |
+
$default = array(
|
100 |
+
'key' => 'all-time',
|
101 |
+
'start' => '',
|
102 |
+
'end' => '',
|
103 |
+
);
|
104 |
+
$user_interval = WP_Stream_Reports_Settings::get_user_options( 'interval', $default );
|
105 |
+
$save_interval_url = add_query_arg(
|
106 |
+
array_merge(
|
107 |
+
array(
|
108 |
+
'action' => 'wp_stream_reports_save_interval',
|
109 |
+
),
|
110 |
+
WP_Stream_Reports::$nonce
|
111 |
+
),
|
112 |
+
admin_url( 'admin-ajax.php' )
|
113 |
+
);
|
114 |
+
|
115 |
+
include WP_STREAM_REPORTS_VIEW_DIR . 'intervals.php';
|
116 |
+
}
|
extensions/reports/ui/css/stream-reports.css
ADDED
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Media queries to alternate between 1 columns and 2 columns depending on screen size */
|
2 |
+
|
3 |
+
@media only screen and (max-width: 850px) {
|
4 |
+
#wpbody-content #dashboard-widgets.columns-2 .postbox-container {
|
5 |
+
width: 100% !important;
|
6 |
+
}
|
7 |
+
}
|
8 |
+
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Configure Section
|
12 |
+
* (There is override of core css declaration)
|
13 |
+
*/
|
14 |
+
|
15 |
+
.stream_page_wp_stream_reports #dashboard-widgets h3 input.title {
|
16 |
+
width: 220px;
|
17 |
+
line-height: 19px;
|
18 |
+
}
|
19 |
+
|
20 |
+
.stream_page_wp_stream_reports #dashboard-widgets h3 .postbox-title-action {
|
21 |
+
position: inherit;;
|
22 |
+
padding: 0;
|
23 |
+
margin-right: 7px;
|
24 |
+
float: right;
|
25 |
+
}
|
26 |
+
|
27 |
+
.js.stream_page_wp_stream_reports #dashboard-widgets h3 .postbox-title-action.postbox-delete-action a {
|
28 |
+
color : #a00;
|
29 |
+
visibility: hidden;
|
30 |
+
margin-right: 7px;
|
31 |
+
}
|
32 |
+
|
33 |
+
.js.stream_page_wp_stream_reports #dashboard-widgets h3 .postbox-title-action.postbox-delete-action a.visible {
|
34 |
+
visibility: visible;
|
35 |
+
}
|
36 |
+
|
37 |
+
.js.stream_page_wp_stream_reports #dashboard-widgets h3 .postbox-title-action.postbox-delete-action a:hover {
|
38 |
+
color : #f00;
|
39 |
+
}
|
40 |
+
|
41 |
+
.stream_page_wp_stream_reports .date-interval {
|
42 |
+
margin: 10px 10px 3px;
|
43 |
+
}
|
44 |
+
|
45 |
+
.stream_page_wp_stream_reports .postbox:hover .edit-box {
|
46 |
+
display: inline;
|
47 |
+
}
|
48 |
+
|
49 |
+
.stream_page_wp_stream_reports .postbox.configure .handlediv {
|
50 |
+
visibility: hidden;
|
51 |
+
}
|
52 |
+
|
53 |
+
.stream_page_wp_stream_reports .postbox > .inside {
|
54 |
+
margin: 0;
|
55 |
+
}
|
56 |
+
|
57 |
+
.stream_page_wp_stream_reports .postbox .configure {
|
58 |
+
height: 0px;
|
59 |
+
overflow: hidden;
|
60 |
+
margin: -1px 0 0 0;
|
61 |
+
border-bottom: 1px solid #eee;
|
62 |
+
}
|
63 |
+
|
64 |
+
.stream_page_wp_stream_reports .postbox .configure .inside {
|
65 |
+
margin: 0;
|
66 |
+
padding: 10px 12px 0;
|
67 |
+
min-height: 28px;
|
68 |
+
}
|
69 |
+
|
70 |
+
.stream_page_wp_stream_reports .postbox .configure.visible {
|
71 |
+
height: auto;
|
72 |
+
overflow: hidden;
|
73 |
+
}
|
74 |
+
|
75 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-option,
|
76 |
+
.stream_page_wp_stream_reports .postbox .configure .grouping-separator,
|
77 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types {
|
78 |
+
float: left;
|
79 |
+
margin-bottom: 10px;
|
80 |
+
}
|
81 |
+
|
82 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-option {
|
83 |
+
margin-right: 5px;
|
84 |
+
width: 20%;
|
85 |
+
max-width: 150px;
|
86 |
+
min-width: 120px;
|
87 |
+
}
|
88 |
+
|
89 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-dataset {
|
90 |
+
width: 30%;
|
91 |
+
max-width: 250px;
|
92 |
+
min-width: 165px;
|
93 |
+
}
|
94 |
+
|
95 |
+
.stream_page_wp_stream_reports .postbox .configure .grouping-separator {
|
96 |
+
margin-right: 5px;
|
97 |
+
margin-left: 2px;
|
98 |
+
line-height: 28px;
|
99 |
+
}
|
100 |
+
|
101 |
+
.stream_page_wp_stream_reports .postbox .configure input.button {
|
102 |
+
float: right;
|
103 |
+
}
|
104 |
+
|
105 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types {
|
106 |
+
display: inline-block;
|
107 |
+
line-height: 28px;
|
108 |
+
}
|
109 |
+
|
110 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types .dashicons {
|
111 |
+
padding-left: 5px;
|
112 |
+
color: #888888;
|
113 |
+
cursor: pointer;
|
114 |
+
line-height: 28px;
|
115 |
+
}
|
116 |
+
|
117 |
+
.stream_page_wp_stream_reports .postbox .configure .spinner {
|
118 |
+
margin: 4px 8px 0 0;
|
119 |
+
}
|
120 |
+
|
121 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types .dashicons.active {
|
122 |
+
color: #000;
|
123 |
+
}
|
124 |
+
|
125 |
+
.stream_page_wp_stream_reports .hndle {
|
126 |
+
position: relative;
|
127 |
+
}
|
128 |
+
|
129 |
+
.stream_page_wp_stream_reports .postbox .clear-title {
|
130 |
+
display: inline;
|
131 |
+
position: relative;
|
132 |
+
cursor: pointer;
|
133 |
+
left: -20px;
|
134 |
+
top: 2px;
|
135 |
+
width: 20px;
|
136 |
+
height: 28px;
|
137 |
+
font-size: 14px;
|
138 |
+
padding-top: 1px;
|
139 |
+
padding-right: 2px;
|
140 |
+
}
|
141 |
+
|
142 |
+
.stream_page_wp_stream_reports .postbox .clear-title:before {
|
143 |
+
content: '\f158';
|
144 |
+
}
|
145 |
+
|
146 |
+
.stream_page_wp_stream_reports .stream-reports-chart-height-option{
|
147 |
+
width: 200px;
|
148 |
+
}
|
149 |
+
|
150 |
+
.stream_page_wp_stream_reports #dashboard-widgets .postbox-container {
|
151 |
+
position: relative;
|
152 |
+
}
|
153 |
+
|
154 |
+
.stream_page_wp_stream_reports .no-reports-message {
|
155 |
+
position: absolute;
|
156 |
+
top: calc(50% - 25px);
|
157 |
+
width: calc(100% - 50px);
|
158 |
+
padding: 0 25px;
|
159 |
+
font-size: 18px;
|
160 |
+
line-height: 28px;
|
161 |
+
font-weight: 700;
|
162 |
+
text-align: center;
|
163 |
+
color: #000;
|
164 |
+
}
|
165 |
+
|
166 |
+
@media only screen and (max-width: 799px) {
|
167 |
+
.stream_page_wp_stream_reports .no-reports-message {
|
168 |
+
margin-top: 50px;
|
169 |
+
}
|
170 |
+
}
|
171 |
+
|
172 |
+
.stream_page_wp_stream_reports .no-reports-message a {
|
173 |
+
font-weight: normal;
|
174 |
+
}
|
175 |
+
|
176 |
+
.stream_page_wp_stream_reports .no-reports-message span {
|
177 |
+
padding: 0 5px;
|
178 |
+
text-transform: uppercase;
|
179 |
+
}
|
180 |
+
|
181 |
+
@media only screen and (max-width: 782px) {
|
182 |
+
.stream_page_wp_stream_reports .date-interval .field-predefined,
|
183 |
+
.stream_page_wp_stream_reports .date-interval .date-inputs {
|
184 |
+
margin-bottom: 10px;
|
185 |
+
}
|
186 |
+
|
187 |
+
.stream_page_wp_stream_reports .date-interval .button {
|
188 |
+
float: left;
|
189 |
+
clear: left;
|
190 |
+
}
|
191 |
+
|
192 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-option {
|
193 |
+
width: 46%;
|
194 |
+
margin: 0 0 6px;
|
195 |
+
max-width: none;
|
196 |
+
min-width: 0;
|
197 |
+
}
|
198 |
+
|
199 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-selector {
|
200 |
+
float: right;
|
201 |
+
}
|
202 |
+
|
203 |
+
.stream_page_wp_stream_reports .postbox .configure .grouping-separator {
|
204 |
+
width: 8%;
|
205 |
+
text-align: center;
|
206 |
+
margin: 0 0 6px;
|
207 |
+
}
|
208 |
+
|
209 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types {
|
210 |
+
clear: left;
|
211 |
+
line-height: 36px;
|
212 |
+
margin-bottom: 0;
|
213 |
+
}
|
214 |
+
|
215 |
+
.stream_page_wp_stream_reports .postbox .configure .chart-types .dashicons {
|
216 |
+
line-height: 36px;
|
217 |
+
}
|
218 |
+
|
219 |
+
.stream_page_wp_stream_reports .postbox .configure input.button {
|
220 |
+
margin-bottom: 6px;
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
|
225 |
+
/* Charts */
|
226 |
+
|
227 |
+
.stream_page_wp_stream_reports .postbox .inside {
|
228 |
+
padding: 0;
|
229 |
+
}
|
230 |
+
|
231 |
+
.stream_page_wp_stream_reports .postbox .inside .chart {
|
232 |
+
position: relative;
|
233 |
+
padding: 0;
|
234 |
+
}
|
235 |
+
|
236 |
+
.stream_page_wp_stream_reports .postbox .inside .chart .chart-loading {
|
237 |
+
display: none;
|
238 |
+
position: absolute;
|
239 |
+
top: calc(50% - 12px);
|
240 |
+
width: 100%;
|
241 |
+
font-size: 18px;
|
242 |
+
font-weight: 700;
|
243 |
+
text-align: center;
|
244 |
+
color: #000;
|
245 |
+
z-index: 1;
|
246 |
+
}
|
247 |
+
|
248 |
+
.stream_page_wp_stream_reports .postbox .inside .chart .chart-loading > span {
|
249 |
+
padding: 6px 12px;
|
250 |
+
background-color: #f9f9f9;
|
251 |
+
}
|
252 |
+
|
253 |
+
.stream_page_wp_stream_reports .postbox .inside .chart svg {
|
254 |
+
padding: 12px;
|
255 |
+
}
|
256 |
+
|
257 |
+
.stream_page_wp_stream_reports .postbox .inside .chart svg g.nv-axisMaxMin:nth-child(3) text {
|
258 |
+
text-anchor: end !important;
|
259 |
+
}
|
260 |
+
|
261 |
+
.stream_page_wp_stream_reports .postbox .inside .chart .chart-loading > span,
|
262 |
+
.stream_page_wp_stream_reports .nvtooltip.xy-tooltip {
|
263 |
+
font-family: "Open Sans", sans-serif !important;
|
264 |
+
margin: 20px 0 0 12px;
|
265 |
+
border: 1px solid #e5e5e5;
|
266 |
+
|
267 |
+
-webkit-border-radius: 6px;
|
268 |
+
-moz-border-radius: 6px;
|
269 |
+
border-radius: 6px;
|
270 |
+
|
271 |
+
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
272 |
+
-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
273 |
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
274 |
+
}
|
275 |
+
|
276 |
+
.stream_page_wp_stream_reports .nvtooltip.xy-tooltip h3 {
|
277 |
+
font-weight: bold;
|
278 |
+
font-size: 13px;
|
279 |
+
}
|
280 |
+
|
281 |
+
.stream_page_wp_stream_reports svg text {
|
282 |
+
font-family: "Open Sans", sans-serif !important;
|
283 |
+
}
|
284 |
+
|
285 |
+
.stream_page_wp_stream_reports svg .nv-axis .tick text,
|
286 |
+
.stream_page_wp_stream_reports svg .nv-axis .nv-axisMaxMin text {
|
287 |
+
font-size: 10px;
|
288 |
+
}
|
extensions/reports/ui/images/stream-reports-example.jpg
ADDED
Binary file
|
extensions/reports/ui/js/stream-reports.js
ADDED
@@ -0,0 +1,778 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/*jslint nomen: true */
|
2 |
+
/*global jQuery, _, nv, d3, stream, wp_stream_reports, ajaxurl, document, window */
|
3 |
+
( function( window, $, _, nv, d3, wp_stream_reports ) {
|
4 |
+
'use strict';
|
5 |
+
|
6 |
+
var report = {};
|
7 |
+
|
8 |
+
report.intervals = {
|
9 |
+
init: function( $wrapper ) {
|
10 |
+
this.wrapper = $wrapper;
|
11 |
+
this.save_interval( this.wrapper.find( '.button-primary' ) );
|
12 |
+
|
13 |
+
this.$ = this.wrapper.each( function() {
|
14 |
+
var container = $( _.last( arguments ) ),
|
15 |
+
dateinputs = container.find( '.date-inputs' ),
|
16 |
+
from = container.find( '.field-from' ),
|
17 |
+
to = container.find( '.field-to' ),
|
18 |
+
to_remove = to.prev( '.date-remove' ),
|
19 |
+
from_remove = from.prev( '.date-remove' ),
|
20 |
+
predefined = container.children( '.field-predefined' ),
|
21 |
+
datepickers = $( '' ).add( to ).add( from );
|
22 |
+
|
23 |
+
if ( _.isFunction( $.fn.datepicker ) ) {
|
24 |
+
|
25 |
+
// Apply a GMT offset due to Date() using the visitor's local time
|
26 |
+
var siteGMTOffsetHours = parseFloat( wp_stream_reports.gmt_offset ),
|
27 |
+
localGMTOffsetHours = new Date().getTimezoneOffset() / 60 * -1,
|
28 |
+
totalGMTOffsetHours = siteGMTOffsetHours - localGMTOffsetHours,
|
29 |
+
localTime = new Date(),
|
30 |
+
siteTime = new Date( localTime.getTime() + ( totalGMTOffsetHours * 60 * 60 * 1000 ) ),
|
31 |
+
dayOffset = '0';
|
32 |
+
|
33 |
+
// Check if the site date is different from the local date, and set a day offset
|
34 |
+
if ( localTime.getDate() !== siteTime.getDate() || localTime.getMonth() !== siteTime.getMonth() ) {
|
35 |
+
if ( localTime.getTime() < siteTime.getTime() ) {
|
36 |
+
dayOffset = '+1d';
|
37 |
+
} else {
|
38 |
+
dayOffset = '-1d';
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
datepickers.datepicker({
|
43 |
+
dateFormat: 'yy/mm/dd',
|
44 |
+
maxDate: Number( dayOffset ),
|
45 |
+
defaultDate: siteTime,
|
46 |
+
beforeShow: function() {
|
47 |
+
$( this ).prop( 'disabled', true );
|
48 |
+
},
|
49 |
+
onClose: function() {
|
50 |
+
$( this ).prop( 'disabled', false );
|
51 |
+
}
|
52 |
+
});
|
53 |
+
|
54 |
+
datepickers.datepicker( 'widget' ).addClass( 'stream-datepicker' );
|
55 |
+
}
|
56 |
+
|
57 |
+
if ( _.isFunction( $.fn.select2 ) ) {
|
58 |
+
predefined.select2({
|
59 |
+
allowClear: true
|
60 |
+
});
|
61 |
+
}
|
62 |
+
|
63 |
+
if ( '' !== from.val() ) {
|
64 |
+
from_remove.show();
|
65 |
+
}
|
66 |
+
|
67 |
+
if ( '' !== to.val() ) {
|
68 |
+
to_remove.show();
|
69 |
+
}
|
70 |
+
|
71 |
+
predefined.on({
|
72 |
+
'change': function() {
|
73 |
+
var value = $( this ).val(),
|
74 |
+
option = predefined.find( '[value="' + value + '"]' ),
|
75 |
+
to_val = option.data( 'to' ),
|
76 |
+
from_val = option.data( 'from' );
|
77 |
+
|
78 |
+
if ( 'custom' === value ) {
|
79 |
+
dateinputs.show();
|
80 |
+
return false;
|
81 |
+
} else {
|
82 |
+
dateinputs.hide();
|
83 |
+
datepickers.datepicker( 'hide' );
|
84 |
+
}
|
85 |
+
|
86 |
+
from.val( from_val ).trigger( 'change', [true] );
|
87 |
+
to.val( to_val ).trigger( 'change', [true] );
|
88 |
+
|
89 |
+
if ( _.isFunction( $.fn.datepicker ) && datepickers.datepicker( 'widget' ).is( ':visible' ) ) {
|
90 |
+
datepickers.datepicker( 'refresh' ).datepicker( 'hide' );
|
91 |
+
}
|
92 |
+
},
|
93 |
+
'select2-removed': function() {
|
94 |
+
predefined.val( '' ).trigger( 'change' );
|
95 |
+
},
|
96 |
+
'check_options': function() {
|
97 |
+
if ( '' !== to.val() && '' !== from.val() ) {
|
98 |
+
var option = predefined.find( 'option' ).filter( '[data-to="' + to.val() + '"]' ).filter( '[data-from="' + from.val() + '"]' );
|
99 |
+
if ( 0 !== option.length ) {
|
100 |
+
predefined.val( option.attr( 'value' ) ).trigger( 'change',[true] );
|
101 |
+
} else {
|
102 |
+
predefined.val( 'custom' ).trigger( 'change',[true] );
|
103 |
+
}
|
104 |
+
} else if ( '' === to.val() && '' === from.val() ) {
|
105 |
+
predefined.val( '' ).trigger( 'change',[true] );
|
106 |
+
} else {
|
107 |
+
predefined.val( 'custom' ).trigger( 'change',[true] );
|
108 |
+
}
|
109 |
+
}
|
110 |
+
});
|
111 |
+
|
112 |
+
from.on({
|
113 |
+
'change': function() {
|
114 |
+
|
115 |
+
if ( '' !== from.val() ) {
|
116 |
+
from_remove.show();
|
117 |
+
to.datepicker( 'option', 'minDate', from.val() );
|
118 |
+
} else {
|
119 |
+
from_remove.hide();
|
120 |
+
}
|
121 |
+
|
122 |
+
if ( true === _.last( arguments ) ) {
|
123 |
+
return false;
|
124 |
+
}
|
125 |
+
|
126 |
+
predefined.trigger( 'check_options' );
|
127 |
+
}
|
128 |
+
});
|
129 |
+
|
130 |
+
to.on({
|
131 |
+
'change': function() {
|
132 |
+
if ( '' !== to.val() ) {
|
133 |
+
to_remove.show();
|
134 |
+
from.datepicker( 'option', 'maxDate', to.val() );
|
135 |
+
} else {
|
136 |
+
to_remove.hide();
|
137 |
+
}
|
138 |
+
|
139 |
+
if ( true === _.last( arguments ) ) {
|
140 |
+
return false;
|
141 |
+
}
|
142 |
+
|
143 |
+
predefined.trigger( 'check_options' );
|
144 |
+
}
|
145 |
+
});
|
146 |
+
|
147 |
+
// Trigger change on load
|
148 |
+
predefined.trigger( 'change' );
|
149 |
+
|
150 |
+
$( '' ).add( from_remove ).add( to_remove ).on({
|
151 |
+
'click': function() {
|
152 |
+
$( this ).next( 'input' ).val( '' ).trigger( 'change' );
|
153 |
+
}
|
154 |
+
});
|
155 |
+
});
|
156 |
+
},
|
157 |
+
|
158 |
+
save_interval: function( $btn ) {
|
159 |
+
var $wrapper = this.wrapper;
|
160 |
+
$btn.click( function() {
|
161 |
+
var data = {
|
162 |
+
key: $wrapper.find( 'select.field-predefined' ).find( ':selected' ).val(),
|
163 |
+
start: $wrapper.find( '.date-inputs .field-from' ).val(),
|
164 |
+
end: $wrapper.find( '.date-inputs .field-to' ).val()
|
165 |
+
};
|
166 |
+
|
167 |
+
// Add params to URL
|
168 |
+
$( this ).attr( 'href', $( this ).attr( 'href' ) + '&' + $.param( data ) );
|
169 |
+
});
|
170 |
+
}
|
171 |
+
};
|
172 |
+
|
173 |
+
/**
|
174 |
+
* Screen options
|
175 |
+
*/
|
176 |
+
report.screen = {
|
177 |
+
init: function( chartHeightOption, applyBtn ) {
|
178 |
+
this.$chartHeightOption = chartHeightOption;
|
179 |
+
this.$applyBtn = applyBtn;
|
180 |
+
|
181 |
+
this.configureOptions();
|
182 |
+
},
|
183 |
+
|
184 |
+
configureOptions: function() {
|
185 |
+
var parent = this;
|
186 |
+
|
187 |
+
this.$applyBtn.click( function( e ) {
|
188 |
+
e.preventDefault();
|
189 |
+
|
190 |
+
var $spinner = $( this ).siblings( '.spinner' );
|
191 |
+
$spinner.show();
|
192 |
+
|
193 |
+
$.ajax({
|
194 |
+
type: 'GET',
|
195 |
+
url: ajaxurl,
|
196 |
+
data: {
|
197 |
+
action: 'wp_stream_reports_save_chart_height',
|
198 |
+
wp_stream_reports_nonce: $( '#wp_stream_reports_nonce' ).val(),
|
199 |
+
chart_height: parent.$chartHeightOption.val()
|
200 |
+
},
|
201 |
+
dataType: 'json',
|
202 |
+
success: function() {
|
203 |
+
location.reload( true );
|
204 |
+
}
|
205 |
+
});
|
206 |
+
|
207 |
+
return false;
|
208 |
+
});
|
209 |
+
}
|
210 |
+
};
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Metabox logic logic
|
214 |
+
*/
|
215 |
+
report.metabox = {
|
216 |
+
init: function( configureDiv, deleteBtn, configureBtn ) {
|
217 |
+
// Variables
|
218 |
+
this.$configureDiv = configureDiv;
|
219 |
+
this.$deleteBtn = deleteBtn;
|
220 |
+
this.$configureBtn = configureBtn;
|
221 |
+
|
222 |
+
// Let's configure event listener for all sections
|
223 |
+
this.configureSection();
|
224 |
+
},
|
225 |
+
|
226 |
+
configureSection: function() {
|
227 |
+
var parent = this;
|
228 |
+
|
229 |
+
// Trigger select2js
|
230 |
+
this.$configureDiv.find( 'select.chart-option' ).select2({
|
231 |
+
minimumResultsForSearch: 8
|
232 |
+
});
|
233 |
+
|
234 |
+
// Change chart type toggle
|
235 |
+
this.$configureDiv.find( '.chart-types .dashicons' ).click( function() {
|
236 |
+
var $target = $( this );
|
237 |
+
if ( ! $target.hasClass( 'active' ) ) {
|
238 |
+
$target.siblings().removeClass( 'active' );
|
239 |
+
$target.addClass( 'active' );
|
240 |
+
}
|
241 |
+
});
|
242 |
+
|
243 |
+
// Bind handler to save button
|
244 |
+
this.$btnSave = this.$configureDiv.find( '.button-primary' ).click( this.configureSave );
|
245 |
+
|
246 |
+
// Confirmation of deletion
|
247 |
+
this.$deleteBtn.click( function() {
|
248 |
+
if ( ! window.confirm( wp_stream_reports.i18n.deletemsg ) ) {
|
249 |
+
return false;
|
250 |
+
}
|
251 |
+
});
|
252 |
+
|
253 |
+
this.$configureDiv.find( '.chart-dataset' ).on( 'change', function() {
|
254 |
+
var selectors = parent.$configureDiv.find( '.chart-selector' );
|
255 |
+
selectors.find( 'option' ).removeAttr( 'disabled' );
|
256 |
+
|
257 |
+
var option = parent.$configureDiv.find( '.chart-dataset :selected' );
|
258 |
+
if ( 'all' === $( option ).val() ) {
|
259 |
+
return;
|
260 |
+
}
|
261 |
+
|
262 |
+
var disable = option.closest( 'optgroup' ).data( 'disable-selectors' );
|
263 |
+
var disabled_selectors = disable.split( ',' );
|
264 |
+
|
265 |
+
for ( var i = 0; i < disabled_selectors.length; i++ ) {
|
266 |
+
option = selectors.find( 'option[value="' + disabled_selectors[i] + '"]');
|
267 |
+
option.attr( 'disabled', 'disabled' );
|
268 |
+
if ( option.is( ':selected' ) ) {
|
269 |
+
option.removeAttr( 'selected' );
|
270 |
+
selectors.trigger( 'change' );
|
271 |
+
}
|
272 |
+
}
|
273 |
+
|
274 |
+
});
|
275 |
+
|
276 |
+
this.$configureDiv.parents( '.postbox' ).on( 'keyup paste', '.title', function() {
|
277 |
+
|
278 |
+
var $inputBox = $( this );
|
279 |
+
|
280 |
+
if ( '' === $( this ).val() && $( this ).siblings( '.clear-title' ).length ) {
|
281 |
+
$( this ).siblings( '.clear-title' ).remove();
|
282 |
+
}
|
283 |
+
|
284 |
+
if ( '' !== $( this ).val() && ! $( this ).siblings( '.clear-title' ).length ) {
|
285 |
+
$( this ).after(
|
286 |
+
$( '<i/>', {
|
287 |
+
'class': 'clear-title dashicons',
|
288 |
+
'click': function() {
|
289 |
+
$inputBox.val( '' );
|
290 |
+
$inputBox.trigger( 'keyup' );
|
291 |
+
}
|
292 |
+
})
|
293 |
+
);
|
294 |
+
}
|
295 |
+
|
296 |
+
});
|
297 |
+
|
298 |
+
// Configuration toggle
|
299 |
+
this.$configureBtn.on( 'click.streamReports', function() {
|
300 |
+
var $target = $( this );
|
301 |
+
|
302 |
+
var $curPostbox = $target.parents( '.postbox' );
|
303 |
+
|
304 |
+
var realTitle = $curPostbox.find( '.chart-title' ).val();
|
305 |
+
var generatedTitle = $curPostbox.find( '.chart-generated-title' ).val();
|
306 |
+
var displayedTitle = realTitle;
|
307 |
+
if ( '' === displayedTitle ) {
|
308 |
+
displayedTitle = generatedTitle;
|
309 |
+
}
|
310 |
+
|
311 |
+
var $titleText, $inputBox;
|
312 |
+
|
313 |
+
// Remove event handler added by core and add it back when user click cancel or save
|
314 |
+
if ( $target.text() === wp_stream_reports.i18n.configure ) {
|
315 |
+
$titleText = $curPostbox.find( '.hndle .title' );
|
316 |
+
$inputBox = $( '<input/>', {
|
317 |
+
'type': 'text',
|
318 |
+
'class': 'title',
|
319 |
+
'value': realTitle,
|
320 |
+
'placeholder': generatedTitle
|
321 |
+
});
|
322 |
+
$titleText.replaceWith( $inputBox );
|
323 |
+
$inputBox.trigger( 'keyup' );
|
324 |
+
|
325 |
+
// Add class to container
|
326 |
+
$curPostbox.addClass( 'configure' );
|
327 |
+
|
328 |
+
// Click function management
|
329 |
+
parent.$clickFunction = $._data( $curPostbox.find( 'h3' ).get( 0 ) ).events.click[0].handler;
|
330 |
+
$curPostbox.find( 'h3' ).off( 'click.postboxes' );
|
331 |
+
|
332 |
+
// Switch configure button text
|
333 |
+
$target.text( wp_stream_reports.i18n.cancel );
|
334 |
+
} else {
|
335 |
+
$inputBox = $curPostbox.find( '.hndle .title' );
|
336 |
+
$titleText = $( '<span/>', {
|
337 |
+
'class': 'title',
|
338 |
+
'text': displayedTitle
|
339 |
+
});
|
340 |
+
|
341 |
+
if ( '' === $titleText.text() ) {
|
342 |
+
$titleText.text( $inputBox.attr( 'placeholder' ) );
|
343 |
+
}
|
344 |
+
|
345 |
+
$inputBox.replaceWith( $titleText );
|
346 |
+
$titleText.siblings( '.clear-title' ).remove();
|
347 |
+
|
348 |
+
// Remove class from container
|
349 |
+
$curPostbox.removeClass( 'configure' );
|
350 |
+
|
351 |
+
// Click function management
|
352 |
+
$curPostbox.find( 'h3' ).on( 'click.postboxes', parent.$clickFunction );
|
353 |
+
|
354 |
+
// Switch cancel button text
|
355 |
+
$target.text( wp_stream_reports.i18n.configure );
|
356 |
+
}
|
357 |
+
|
358 |
+
// Always show the cancel button
|
359 |
+
$target.toggleClass( 'edit-box' );
|
360 |
+
|
361 |
+
// Show the delete button
|
362 |
+
$target.parent().next().find( 'a' ).toggleClass( 'visible' );
|
363 |
+
|
364 |
+
//Open the section if it's hidden
|
365 |
+
$curPostbox.removeClass( 'closed' );
|
366 |
+
|
367 |
+
// Show the configure div
|
368 |
+
$curPostbox.find( '.inside .configure' ).toggleClass( 'visible' );
|
369 |
+
});
|
370 |
+
|
371 |
+
this.$configureDiv.filter( '.stream-reports-expand' ).closest( '.postbox' ).find( '.postbox-title-action a.open-box').click();
|
372 |
+
},
|
373 |
+
|
374 |
+
configureSave: function() {
|
375 |
+
var parent = $( this ).parents( '.configure' ), $spinner, $postbox, $cancelBtn;
|
376 |
+
|
377 |
+
// Postbox container
|
378 |
+
$postbox = $( this ).parents( '.postbox' );
|
379 |
+
|
380 |
+
// Show the spinner
|
381 |
+
$spinner = $postbox.find( '.configure .spinner' );
|
382 |
+
$spinner.show();
|
383 |
+
|
384 |
+
// Cancel button
|
385 |
+
$cancelBtn = $postbox.find( '.hndle .open-box' );
|
386 |
+
|
387 |
+
var id = $( this ).data( 'id' );
|
388 |
+
var option = parent.find( '.chart-dataset' ).select2( 'data' ).element[0].dataset;
|
389 |
+
var connector = option.connector;
|
390 |
+
var context = option.context;
|
391 |
+
var action = parent.find( '.chart-action' ).select2( 'data' ).element[0].dataset.action;
|
392 |
+
var selector = parent.find( '.chart-selector' ).select2( 'data' ).id;
|
393 |
+
|
394 |
+
// Send the new
|
395 |
+
$.ajax({
|
396 |
+
type: 'GET',
|
397 |
+
url: ajaxurl,
|
398 |
+
data: {
|
399 |
+
action: 'wp_stream_reports_save_metabox_config',
|
400 |
+
wp_stream_reports_nonce: $( '#wp_stream_reports_nonce' ).val(),
|
401 |
+
section_id: id,
|
402 |
+
title: $postbox.find( '.title' ).val(),
|
403 |
+
chart_type: parent.find( '.chart-types .active' ).data( 'type' ),
|
404 |
+
data_connector: connector,
|
405 |
+
data_context: context,
|
406 |
+
data_action: action,
|
407 |
+
data_selector: selector
|
408 |
+
},
|
409 |
+
dataType: 'json',
|
410 |
+
success: function( data ) {
|
411 |
+
$spinner.hide(0,function() {
|
412 |
+
$cancelBtn.trigger( 'click' );
|
413 |
+
});
|
414 |
+
|
415 |
+
if ( true === data.success ) {
|
416 |
+
|
417 |
+
$.ajax({
|
418 |
+
type: 'GET',
|
419 |
+
url: ajaxurl,
|
420 |
+
data: {
|
421 |
+
action: 'wp_stream_reports_update_metabox_display',
|
422 |
+
wp_stream_reports_nonce: $( '#wp_stream_reports_nonce' ).val(),
|
423 |
+
section_id: id
|
424 |
+
},
|
425 |
+
dataType: 'json',
|
426 |
+
success: stream.report.chart.loadSectionCallback( $postbox )
|
427 |
+
});
|
428 |
+
|
429 |
+
}
|
430 |
+
|
431 |
+
|
432 |
+
}
|
433 |
+
});
|
434 |
+
}
|
435 |
+
};
|
436 |
+
|
437 |
+
/**
|
438 |
+
* Chart logic
|
439 |
+
*/
|
440 |
+
report.chart = {
|
441 |
+
_: {
|
442 |
+
// Default Options
|
443 |
+
'opts': {
|
444 |
+
// Store the jQuery elements we are using
|
445 |
+
'$': null,
|
446 |
+
|
447 |
+
// Margin on the sides of the chart
|
448 |
+
'margin': {
|
449 |
+
'left': 30,
|
450 |
+
'right': 25
|
451 |
+
},
|
452 |
+
|
453 |
+
// Width of the canvas
|
454 |
+
'width': 'this',
|
455 |
+
|
456 |
+
// Height of the Canvas
|
457 |
+
'height': 'this',
|
458 |
+
|
459 |
+
// Actual data that will be plotted
|
460 |
+
'values': {},
|
461 |
+
|
462 |
+
// Global Label options
|
463 |
+
'label': {
|
464 |
+
'show': true,
|
465 |
+
'threshold': 0.05,
|
466 |
+
'type': 'percent'
|
467 |
+
},
|
468 |
+
|
469 |
+
// Global legend options
|
470 |
+
'legend': {
|
471 |
+
'show': true
|
472 |
+
},
|
473 |
+
|
474 |
+
'tooltip': {
|
475 |
+
'show': true
|
476 |
+
},
|
477 |
+
|
478 |
+
// y Axis information
|
479 |
+
'yAxis': {
|
480 |
+
'show': true,
|
481 |
+
'label': null,
|
482 |
+
'format': ',r',
|
483 |
+
'reduceTicks': false
|
484 |
+
},
|
485 |
+
|
486 |
+
// x Axis information
|
487 |
+
'xAxis': {
|
488 |
+
'show': true,
|
489 |
+
'label': null,
|
490 |
+
'format': ',r',
|
491 |
+
'reduceTicks': true,
|
492 |
+
'rotateLabels': 0
|
493 |
+
},
|
494 |
+
|
495 |
+
// Group opts
|
496 |
+
'group': {
|
497 |
+
'spacing': 0.1
|
498 |
+
},
|
499 |
+
|
500 |
+
// Use interactive guidelines
|
501 |
+
'guidelines': false,
|
502 |
+
|
503 |
+
// Use interactive guidelines
|
504 |
+
'showValues': false,
|
505 |
+
|
506 |
+
// Show Controls
|
507 |
+
'controls': true,
|
508 |
+
|
509 |
+
// Type of the Chart
|
510 |
+
'type': false,
|
511 |
+
|
512 |
+
// Miliseconds on the animation, or false to deactivate
|
513 |
+
'animate': 350,
|
514 |
+
|
515 |
+
// Check if a graph need to be draw
|
516 |
+
'draw': null,
|
517 |
+
|
518 |
+
// Whether to stack the chart by default
|
519 |
+
'stacked': false
|
520 |
+
}
|
521 |
+
},
|
522 |
+
|
523 |
+
stateChangeCallback: function( section_id ) {
|
524 |
+
var id = section_id;
|
525 |
+
return function( e ) {
|
526 |
+
var data = {
|
527 |
+
'type': 'none'
|
528 |
+
};
|
529 |
+
|
530 |
+
if ( undefined !== e.stacked ) {
|
531 |
+
data.type = 'group';
|
532 |
+
data.payload = e.stacked;
|
533 |
+
} else if ( undefined !== e.disabled ) {
|
534 |
+
data.type = 'disable';
|
535 |
+
data.payload = e.disabled;
|
536 |
+
}
|
537 |
+
|
538 |
+
if ( 'none' !== data.type ) {
|
539 |
+
$.ajax({
|
540 |
+
type: 'GET',
|
541 |
+
url: ajaxurl,
|
542 |
+
data: {
|
543 |
+
'action': 'wp_stream_reports_save_chart_options',
|
544 |
+
'wp_stream_reports_nonce': $( '#wp_stream_reports_nonce' ).val(),
|
545 |
+
'section_id': id,
|
546 |
+
'update_type': data.type,
|
547 |
+
'update_payload': data.payload
|
548 |
+
},
|
549 |
+
dataType: 'json'
|
550 |
+
});
|
551 |
+
}
|
552 |
+
};
|
553 |
+
},
|
554 |
+
|
555 |
+
// Build all the opts to be drawn later
|
556 |
+
init: function( elements, $columns ) {
|
557 |
+
this.elements = elements;
|
558 |
+
this.$columns = $columns;
|
559 |
+
},
|
560 |
+
|
561 |
+
// Loads and redraws a section's chart and configuration options
|
562 |
+
loadSection: function( $section ) {
|
563 |
+
|
564 |
+
var section_id = $section.data( 'section-id' );
|
565 |
+
var $chart = $section.find( '.chart' );
|
566 |
+
|
567 |
+
$chart.find( '.chart-loading' ).show();
|
568 |
+
|
569 |
+
$.ajax({
|
570 |
+
type: 'GET',
|
571 |
+
url: ajaxurl,
|
572 |
+
data: {
|
573 |
+
action: 'wp_stream_reports_update_metabox_display',
|
574 |
+
wp_stream_reports_nonce: $( '#wp_stream_reports_nonce' ).val(),
|
575 |
+
section_id: section_id
|
576 |
+
},
|
577 |
+
dataType: 'json',
|
578 |
+
success: stream.report.chart.refreshSectionCallback( $section, $chart )
|
579 |
+
});
|
580 |
+
|
581 |
+
},
|
582 |
+
|
583 |
+
loadSectionCallback: function( $section ) {
|
584 |
+
return function() {
|
585 |
+
stream.report.chart.loadSection( $section );
|
586 |
+
};
|
587 |
+
},
|
588 |
+
|
589 |
+
refreshSectionCallback: function( $section, $chart ) {
|
590 |
+
return function( data ) {
|
591 |
+
|
592 |
+
// Update chart data
|
593 |
+
$chart.data( 'report', data.data.options );
|
594 |
+
$chart.find( 'svg' ).html( '<svg></svg>' );
|
595 |
+
var opts = $.extend( true, {}, report.chart._.opts, { '$': this.elements }, ( 'undefined' !== typeof opts ? opts : {} ) );
|
596 |
+
stream.report.chart.drawChart( $section.find( '.section-id' ).val(), $chart, opts, stream.report.chart.$columns );
|
597 |
+
|
598 |
+
// Update title values
|
599 |
+
$section.find( '.chart-title' ).val( data.data.title );
|
600 |
+
$section.find( '.chart-generated-title' ).val( data.data.generated_title );
|
601 |
+
|
602 |
+
// Update Title Text
|
603 |
+
var newTitle = data.data.title;
|
604 |
+
if ( '' === newTitle || null === newTitle ) {
|
605 |
+
newTitle = data.data.generated_title;
|
606 |
+
}
|
607 |
+
$section.find( '.hndle .title' ).text( newTitle );
|
608 |
+
|
609 |
+
$chart.find( '.chart-loading' ).hide();
|
610 |
+
|
611 |
+
};
|
612 |
+
},
|
613 |
+
|
614 |
+
// Grab all the opts and draw the chart on the screen
|
615 |
+
drawChart: function( k, el, opts, $columns ) {
|
616 |
+
var $el = $( el ),
|
617 |
+
data = $el.data( 'report', $.extend( true, {}, opts, { 'id': _.uniqueId( '__stream-report-chart-' ) }, $el.data( 'report' ) ) ).data( 'report' );
|
618 |
+
|
619 |
+
if ( $( el ).parents( '.postbox.closed' ).length ) {
|
620 |
+
return;
|
621 |
+
}
|
622 |
+
|
623 |
+
var section_id = $( el ).parents( '.postbox' ).find( '.section-id' ).val();
|
624 |
+
|
625 |
+
if ( 'parent' === data.width || 'parent' === data._width ) {
|
626 |
+
data.width = $el.parent().innerWidth();
|
627 |
+
data._width = 'parent';
|
628 |
+
} else if ( 'this' === data.width || 'this' === data._width ) {
|
629 |
+
data.width = $el.innerWidth();
|
630 |
+
data._width = 'this';
|
631 |
+
}
|
632 |
+
if ( 'parent' === data.height || 'parent' === data._height ) {
|
633 |
+
data.height = $el.parent().innerHeight();
|
634 |
+
data._height = 'parent';
|
635 |
+
} else if ( 'this' === data.height || 'this' === data._height ) {
|
636 |
+
data.height = $el.innerHeight();
|
637 |
+
data._height = 'this';
|
638 |
+
}
|
639 |
+
|
640 |
+
// This is very important, if you build the SVG live it was bugging...
|
641 |
+
data.svg = $el.find( 'svg' );
|
642 |
+
data.d3 = d3.select( data.svg[0] );
|
643 |
+
var dateFormat = function( d ) {
|
644 |
+
var milliseconds = d * 1000;
|
645 |
+
|
646 |
+
return d3.time.format( '%Y/%m/%d' )( new Date( milliseconds ) );
|
647 |
+
};
|
648 |
+
|
649 |
+
nv.addGraph( function() {
|
650 |
+
switch ( data.type ) {
|
651 |
+
case 'donut':
|
652 |
+
case 'pie':
|
653 |
+
data.chart = nv.models.pieChart();
|
654 |
+
data.chart.valueFormat( d3.format( ',f' ) );
|
655 |
+
data.chart.x( function( d ) { return d.key; } );
|
656 |
+
data.chart.y( function( d ) { return d.value; } );
|
657 |
+
if ( 'donut' === data.type ) {
|
658 |
+
data.chart.donut( true );
|
659 |
+
}
|
660 |
+
break;
|
661 |
+
case 'line':
|
662 |
+
data.chart = nv.models.lineChart();
|
663 |
+
data.chart.xAxis.tickFormat( dateFormat );
|
664 |
+
break;
|
665 |
+
case 'multibar':
|
666 |
+
data.chart = nv.models.multiBarChart();
|
667 |
+
data.chart.yAxis.tickFormat( d3.format( ',f' ) );
|
668 |
+
data.chart.xAxis.tickFormat( dateFormat );
|
669 |
+
break;
|
670 |
+
|
671 |
+
case 'multibar-horizontal':
|
672 |
+
data.chart = nv.models.multiBarHorizontalChart();
|
673 |
+
data.chart.xAxis.tickFormat( dateFormat );
|
674 |
+
break;
|
675 |
+
|
676 |
+
default: // If we don't have a type of chart defined it gets out...
|
677 |
+
return;
|
678 |
+
}
|
679 |
+
|
680 |
+
var mapValidation = [
|
681 |
+
{ data: data.donutRatio, _function: data.chart.donutRatio },
|
682 |
+
{ data: data.label.show, _function: data.chart.showLabels },
|
683 |
+
{ data: data.showValues, _function: data.chart.showValues },
|
684 |
+
{ data: data.label.threshold, _function: data.chart.labelThreshold },
|
685 |
+
{ data: data.label.type, _function: data.chart.labelType },
|
686 |
+
{ data: data.group.spacing, _function: data.chart.groupSpacing },
|
687 |
+
{ data: data.guidelines, _function: data.chart.useInteractiveGuideline },
|
688 |
+
{ data: data.animate, _function: data.chart.transitionDuration },
|
689 |
+
{ data: data.legend.show, _function: data.chart.showLegend },
|
690 |
+
{ data: data.yAxis.show, _function: data.chart.showYAxis },
|
691 |
+
{ data: data.yAxis.reduceTicks, _function: data.chart.reduceYTicks },
|
692 |
+
{ data: data.xAxis.show, _function: data.chart.showXAxis },
|
693 |
+
{ data: data.xAxis.reduceTicks, _function: data.chart.reduceXTicks },
|
694 |
+
{ data: data.controls, _function: data.chart.showControls },
|
695 |
+
{ data: data.margin, _function: data.chart.margin },
|
696 |
+
{ data: data.tooltip.show, _function: data.chart.tooltips },
|
697 |
+
{ data: data.stacked, _function: data.chart.stacked }
|
698 |
+
];
|
699 |
+
|
700 |
+
_.map( mapValidation, function( value ) {
|
701 |
+
if ( null !== value.data && _.isFunction( value._function ) ) {
|
702 |
+
value._function( value.data );
|
703 |
+
}
|
704 |
+
});
|
705 |
+
|
706 |
+
mapValidation = [
|
707 |
+
{data: data.yAxis.label, object: data.chart.yAxis, _function: 'data.chart.yAxis.axisLabel'},
|
708 |
+
{data: data.yAxis.format, object: data.chart.yAxis, _function: 'data.chart.yAxistickFormat', format: true},
|
709 |
+
{data: data.xAxis.label, object: data.chart.xAxis, _function: 'data.chart.xAxis.axisLabel'},
|
710 |
+
{data: data.xAxis.format, object: data.chart.xAxis, _function: 'data.chart.xAxis.tickFormat', format: true}
|
711 |
+
];
|
712 |
+
|
713 |
+
_.map( mapValidation, function( value ) {
|
714 |
+
if ( null !== value.data && _.isObject( value.object ) && _.isFunction( value._function ) ) {
|
715 |
+
if ( ! _.isUndefined( value.format ) ) {
|
716 |
+
value._function( d3.format( value.data ) );
|
717 |
+
} else {
|
718 |
+
value._function( value.data );
|
719 |
+
}
|
720 |
+
}
|
721 |
+
});
|
722 |
+
|
723 |
+
data.d3.datum( data.values ).call( data.chart );
|
724 |
+
|
725 |
+
//Update the chart when window resizes.
|
726 |
+
nv.utils.windowResize( data.chart.update );
|
727 |
+
$columns.click( data.chart.update );
|
728 |
+
|
729 |
+
data.chart.dispatch.on( 'stateChange', stream.report.chart.stateChangeCallback( section_id ) );
|
730 |
+
|
731 |
+
return data.chart;
|
732 |
+
});
|
733 |
+
}
|
734 |
+
|
735 |
+
};
|
736 |
+
|
737 |
+
window.stream = $.extend( true, ( !_.isObject( window.stream ) ? {} : window.stream ), { 'report': report } );
|
738 |
+
|
739 |
+
/**
|
740 |
+
* Document Ready actions
|
741 |
+
*/
|
742 |
+
$( document ).ready( function() {
|
743 |
+
stream.report.intervals.init(
|
744 |
+
$( '.date-interval' )
|
745 |
+
);
|
746 |
+
|
747 |
+
stream.report.screen.init(
|
748 |
+
$( '#chart_height' ),
|
749 |
+
$( '#chart_height_apply' )
|
750 |
+
);
|
751 |
+
|
752 |
+
$( '.stream_page_wp_stream_reports .postbox' ).each( function() {
|
753 |
+
var id = $( this ).find( '.section-id' ).val();
|
754 |
+
$( this ).data( 'section-id', id );
|
755 |
+
});
|
756 |
+
|
757 |
+
stream.report.chart.init(
|
758 |
+
$( '.stream_page_wp_stream_reports .chart' ),
|
759 |
+
$( '.columns-prefs input[type="radio"]' )
|
760 |
+
);
|
761 |
+
|
762 |
+
$( '.stream_page_wp_stream_reports .chart' ).each( function() {
|
763 |
+
stream.report.chart.loadSection( $( this ).parents( '.postbox' ) );
|
764 |
+
});
|
765 |
+
|
766 |
+
$( '.postbox.closed' ).bind( 'click.initOpen', function() {
|
767 |
+
stream.report.chart.loadSection( $( this ) );
|
768 |
+
$( this ).unbind( 'click.initOpen' );
|
769 |
+
});
|
770 |
+
|
771 |
+
stream.report.metabox.init(
|
772 |
+
$( '.postbox .inside .configure' ),
|
773 |
+
$( '.postbox-delete-action a' ),
|
774 |
+
$( '.postbox-title-action .edit-box' )
|
775 |
+
);
|
776 |
+
});
|
777 |
+
|
778 |
+
} ( window, jQuery.noConflict(), _.noConflict(), nv, d3, wp_stream_reports ) );
|
extensions/reports/ui/lib/d3/d3.min.js
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
!function(){function n(n){return null!=n&&!isNaN(n)}function t(n){return n.length}function e(n){for(var t=1;n*t%1;)t*=10;return t}function r(n,t){try{for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}catch(r){n.prototype=t}}function u(){}function i(n){return aa+n in this}function o(n){return n=aa+n,n in this&&delete this[n]}function a(){var n=[];return this.forEach(function(t){n.push(t)}),n}function c(){var n=0;for(var t in this)t.charCodeAt(0)===ca&&++n;return n}function s(){for(var n in this)if(n.charCodeAt(0)===ca)return!1;return!0}function l(){}function f(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function h(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.substring(1);for(var e=0,r=sa.length;r>e;++e){var u=sa[e]+t;if(u in n)return u}}function g(){}function p(){}function v(n){function t(){for(var t,r=e,u=-1,i=r.length;++u<i;)(t=r[u].on)&&t.apply(this,arguments);return n}var e=[],r=new u;return t.on=function(t,u){var i,o=r.get(t);return arguments.length<2?o&&o.on:(o&&(o.on=null,e=e.slice(0,i=e.indexOf(o)).concat(e.slice(i+1)),r.remove(t)),u&&e.push(r.set(t,{on:u})),n)},t}function d(){Xo.event.preventDefault()}function m(){for(var n,t=Xo.event;n=t.sourceEvent;)t=n;return t}function y(n){for(var t=new p,e=0,r=arguments.length;++e<r;)t[arguments[e]]=v(t);return t.of=function(e,r){return function(u){try{var i=u.sourceEvent=Xo.event;u.target=n,Xo.event=u,t[u.type].apply(e,r)}finally{Xo.event=i}}},t}function x(n){return fa(n,da),n}function M(n){return"function"==typeof n?n:function(){return ha(n,this)}}function _(n){return"function"==typeof n?n:function(){return ga(n,this)}}function b(n,t){function e(){this.removeAttribute(n)}function r(){this.removeAttributeNS(n.space,n.local)}function u(){this.setAttribute(n,t)}function i(){this.setAttributeNS(n.space,n.local,t)}function o(){var e=t.apply(this,arguments);null==e?this.removeAttribute(n):this.setAttribute(n,e)}function a(){var e=t.apply(this,arguments);null==e?this.removeAttributeNS(n.space,n.local):this.setAttributeNS(n.space,n.local,e)}return n=Xo.ns.qualify(n),null==t?n.local?r:e:"function"==typeof t?n.local?a:o:n.local?i:u}function w(n){return n.trim().replace(/\s+/g," ")}function S(n){return new RegExp("(?:^|\\s+)"+Xo.requote(n)+"(?:\\s+|$)","g")}function k(n){return n.trim().split(/^|\s+/)}function E(n,t){function e(){for(var e=-1;++e<u;)n[e](this,t)}function r(){for(var e=-1,r=t.apply(this,arguments);++e<u;)n[e](this,r)}n=k(n).map(A);var u=n.length;return"function"==typeof t?r:e}function A(n){var t=S(n);return function(e,r){if(u=e.classList)return r?u.add(n):u.remove(n);var u=e.getAttribute("class")||"";r?(t.lastIndex=0,t.test(u)||e.setAttribute("class",w(u+" "+n))):e.setAttribute("class",w(u.replace(t," ")))}}function C(n,t,e){function r(){this.style.removeProperty(n)}function u(){this.style.setProperty(n,t,e)}function i(){var r=t.apply(this,arguments);null==r?this.style.removeProperty(n):this.style.setProperty(n,r,e)}return null==t?r:"function"==typeof t?i:u}function N(n,t){function e(){delete this[n]}function r(){this[n]=t}function u(){var e=t.apply(this,arguments);null==e?delete this[n]:this[n]=e}return null==t?e:"function"==typeof t?u:r}function L(n){return"function"==typeof n?n:(n=Xo.ns.qualify(n)).local?function(){return this.ownerDocument.createElementNS(n.space,n.local)}:function(){return this.ownerDocument.createElementNS(this.namespaceURI,n)}}function z(n){return{__data__:n}}function q(n){return function(){return va(this,n)}}function T(n){return arguments.length||(n=Xo.ascending),function(t,e){return t&&e?n(t.__data__,e.__data__):!t-!e}}function R(n,t){for(var e=0,r=n.length;r>e;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function D(n){return fa(n,ya),n}function P(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t<c;);return o}}function U(){var n=this.__transition__;n&&++n.active}function j(n,t,e){function r(){var t=this[o];t&&(this.removeEventListener(n,t,t.$),delete this[o])}function u(){var u=c(t,Bo(arguments));r.call(this),this.addEventListener(n,this[o]=u,u.$=e),u._=t}function i(){var t,e=new RegExp("^__on([^.]+)"+Xo.requote(n)+"$");for(var r in this)if(t=r.match(e)){var u=this[r];this.removeEventListener(t[1],u,u.$),delete this[r]}}var o="__on"+n,a=n.indexOf("."),c=H;a>0&&(n=n.substring(0,a));var s=Ma.get(n);return s&&(n=s,c=F),a?t?u:r:t?g:i}function H(n,t){return function(e){var r=Xo.event;Xo.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{Xo.event=r}}}function F(n,t){var e=H(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function O(){var n=".dragsuppress-"+ ++ba,t="click"+n,e=Xo.select(Go).on("touchmove"+n,d).on("dragstart"+n,d).on("selectstart"+n,d);if(_a){var r=Jo.style,u=r[_a];r[_a]="none"}return function(i){function o(){e.on(t,null)}e.on(n,null),_a&&(r[_a]=u),i&&(e.on(t,function(){d(),o()},!0),setTimeout(o,0))}}function Y(n,t){t.changedTouches&&(t=t.changedTouches[0]);var e=n.ownerSVGElement||n;if(e.createSVGPoint){var r=e.createSVGPoint();if(0>wa&&(Go.scrollX||Go.scrollY)){e=Xo.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var u=e[0][0].getScreenCTM();wa=!(u.f||u.e),e.remove()}return wa?(r.x=t.pageX,r.y=t.pageY):(r.x=t.clientX,r.y=t.clientY),r=r.matrixTransform(n.getScreenCTM().inverse()),[r.x,r.y]}var i=n.getBoundingClientRect();return[t.clientX-i.left-n.clientLeft,t.clientY-i.top-n.clientTop]}function I(n){return n>0?1:0>n?-1:0}function Z(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function V(n){return n>1?0:-1>n?Sa:Math.acos(n)}function X(n){return n>1?Ea:-1>n?-Ea:Math.asin(n)}function $(n){return((n=Math.exp(n))-1/n)/2}function B(n){return((n=Math.exp(n))+1/n)/2}function W(n){return((n=Math.exp(2*n))-1)/(n+1)}function J(n){return(n=Math.sin(n/2))*n}function G(){}function K(n,t,e){return new Q(n,t,e)}function Q(n,t,e){this.h=n,this.s=t,this.l=e}function nt(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,gt(u(n+120),u(n),u(n-120))}function tt(n,t,e){return new et(n,t,e)}function et(n,t,e){this.h=n,this.c=t,this.l=e}function rt(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),ut(e,Math.cos(n*=Na)*t,Math.sin(n)*t)}function ut(n,t,e){return new it(n,t,e)}function it(n,t,e){this.l=n,this.a=t,this.b=e}function ot(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=ct(u)*Fa,r=ct(r)*Oa,i=ct(i)*Ya,gt(lt(3.2404542*u-1.5371385*r-.4985314*i),lt(-.969266*u+1.8760108*r+.041556*i),lt(.0556434*u-.2040259*r+1.0572252*i))}function at(n,t,e){return n>0?tt(Math.atan2(e,t)*La,Math.sqrt(t*t+e*e),n):tt(0/0,0/0,n)}function ct(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function st(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function lt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function ft(n){return gt(n>>16,255&n>>8,255&n)}function ht(n){return ft(n)+""}function gt(n,t,e){return new pt(n,t,e)}function pt(n,t,e){this.r=n,this.g=t,this.b=e}function vt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function dt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Mt(u[0]),Mt(u[1]),Mt(u[2]))}return(i=Va.get(n))?t(i.r,i.g,i.b):(null!=n&&"#"===n.charAt(0)&&(4===n.length?(o=n.charAt(1),o+=o,a=n.charAt(2),a+=a,c=n.charAt(3),c+=c):7===n.length&&(o=n.substring(1,3),a=n.substring(3,5),c=n.substring(5,7)),o=parseInt(o,16),a=parseInt(a,16),c=parseInt(c,16)),t(o,a,c))}function mt(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),K(r,u,c)}function yt(n,t,e){n=xt(n),t=xt(t),e=xt(e);var r=st((.4124564*n+.3575761*t+.1804375*e)/Fa),u=st((.2126729*n+.7151522*t+.072175*e)/Oa),i=st((.0193339*n+.119192*t+.9503041*e)/Ya);return ut(116*u-16,500*(r-u),200*(u-i))}function xt(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Mt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function _t(n){return"function"==typeof n?n:function(){return n}}function bt(n){return n}function wt(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),St(t,e,n,r)}}function St(n,t,e,r){function u(){var n,t=c.status;if(!t&&c.responseText||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return o.error.call(i,r),void 0}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=Xo.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,s=null;return!Go.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=Xo.event;Xo.event=n;try{o.progress.call(i,c)}finally{Xo.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(s=n,i):s},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(Bo(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var l in a)c.setRequestHeader(l,a[l]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=s&&(c.responseType=s),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},Xo.rebind(i,o,"on"),null==r?i:i.get(kt(r))}function kt(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Et(){var n=At(),t=Ct()-n;t>24?(isFinite(t)&&(clearTimeout(Wa),Wa=setTimeout(Et,t)),Ba=0):(Ba=1,Ga(Et))}function At(){var n=Date.now();for(Ja=Xa;Ja;)n>=Ja.t&&(Ja.f=Ja.c(n-Ja.t)),Ja=Ja.n;return n}function Ct(){for(var n,t=Xa,e=1/0;t;)t.f?t=n?n.n=t.n:Xa=t.n:(t.t<e&&(e=t.t),t=(n=t).n);return $a=n,e}function Nt(n,t){return t-(n?Math.ceil(Math.log(n)/Math.LN10):1)}function Lt(n,t){var e=Math.pow(10,3*oa(8-t));return{scale:t>8?function(n){return n/e}:function(n){return n*e},symbol:n}}function zt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r?function(n){for(var t=n.length,u=[],i=0,o=r[0];t>0&&o>0;)u.push(n.substring(t-=o,t+o)),o=r[i=(i+1)%r.length];return u.reverse().join(e)}:bt;return function(n){var e=Qa.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"",c=e[4]||"",s=e[5],l=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1;switch(h&&(h=+h.substring(1)),(s||"0"===r&&"="===o)&&(s=r="0",o="=",f&&(l-=Math.floor((l-1)/4))),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=nc.get(g)||qt;var y=s&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):a;if(0>p){var c=Xo.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x=n.lastIndexOf("."),M=0>x?n:n.substring(0,x),_=0>x?"":t+n.substring(x+1);!s&&f&&(M=i(M));var b=v.length+M.length+_.length+(y?0:u.length),w=l>b?new Array(b=l-b+1).join(r):"";return y&&(M=i(w+M)),u+=v,n=M+_,("<"===o?u+n+w:">"===o?w+u+n:"^"===o?w.substring(0,b>>=1)+u+n+w.substring(b):u+(y?n:w+n))+e}}}function qt(n){return n+""}function Tt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Rt(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new ec(e-1)),1),e}function i(n,e){return t(n=new ec(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{ec=Tt;var r=new Tt;return r._=n,o(r,t,e)}finally{ec=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Dt(n);return c.floor=c,c.round=Dt(r),c.ceil=Dt(u),c.offset=Dt(i),c.range=a,n}function Dt(n){return function(t,e){try{ec=Tt;var r=new Tt;return r._=t,n(r,e)._}finally{ec=Date}}}function Pt(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++a<r;)37===n.charCodeAt(a)&&(o.push(n.substring(c,a)),null!=(u=uc[e=n.charAt(++a)])&&(e=n.charAt(++a)),(i=C[e])&&(e=i(t,null==u?"e"===e?" ":"0":u)),o.push(e),c=a+1);return o.push(n.substring(c,a)),o.join("")}var r=n.length;return t.parse=function(t){var r={y:1900,m:0,d:1,H:0,M:0,S:0,L:0,Z:null},u=e(r,n,t,0);if(u!=t.length)return null;"p"in r&&(r.H=r.H%12+12*r.p);var i=null!=r.Z&&ec!==Tt,o=new(i?Tt:ec);return"j"in r?o.setFullYear(r.y,0,r.j):"w"in r&&("W"in r||"U"in r)?(o.setFullYear(r.y,0,1),o.setFullYear(r.y,0,"W"in r?(r.w+6)%7+7*r.W-(o.getDay()+5)%7:r.w+7*r.U-(o.getDay()+6)%7)):o.setFullYear(r.y,r.m,r.d),o.setHours(r.H+Math.floor(r.Z/100),r.M+r.Z%100,r.S,r.L),i?o._:o},t.toString=function(){return n},t}function e(n,t,e,r){for(var u,i,o,a=0,c=t.length,s=e.length;c>a;){if(r>=s)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=N[o in uc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){b.lastIndex=0;var r=b.exec(t.substring(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){M.lastIndex=0;var r=M.exec(t.substring(e));return r?(n.w=_.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.substring(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.substring(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,C.c.toString(),t,r)}function c(n,t,r){return e(n,C.x.toString(),t,r)}function s(n,t,r){return e(n,C.X.toString(),t,r)}function l(n,t,e){var r=x.get(t.substring(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{ec=Tt;var t=new ec;return t._=n,r(t)}finally{ec=Date}}var r=t(n);return e.parse=function(n){try{ec=Tt;var t=r.parse(n);return t&&t._}finally{ec=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ee;var x=Xo.map(),M=jt(v),_=Ht(v),b=jt(d),w=Ht(d),S=jt(m),k=Ht(m),E=jt(y),A=Ht(y);p.forEach(function(n,t){x.set(n.toLowerCase(),t)});var C={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Ut(n.getDate(),t,2)},e:function(n,t){return Ut(n.getDate(),t,2)},H:function(n,t){return Ut(n.getHours(),t,2)},I:function(n,t){return Ut(n.getHours()%12||12,t,2)},j:function(n,t){return Ut(1+tc.dayOfYear(n),t,3)},L:function(n,t){return Ut(n.getMilliseconds(),t,3)},m:function(n,t){return Ut(n.getMonth()+1,t,2)},M:function(n,t){return Ut(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Ut(n.getSeconds(),t,2)},U:function(n,t){return Ut(tc.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Ut(tc.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Ut(n.getFullYear()%100,t,2)},Y:function(n,t){return Ut(n.getFullYear()%1e4,t,4)},Z:ne,"%":function(){return"%"}},N={a:r,A:u,b:i,B:o,c:a,d:Bt,e:Bt,H:Jt,I:Jt,j:Wt,L:Qt,m:$t,M:Gt,p:l,S:Kt,U:Ot,w:Ft,W:Yt,x:c,X:s,y:Zt,Y:It,Z:Vt,"%":te};return t}function Ut(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function jt(n){return new RegExp("^(?:"+n.map(Xo.requote).join("|")+")","i")}function Ht(n){for(var t=new u,e=-1,r=n.length;++e<r;)t.set(n[e].toLowerCase(),e);return t}function Ft(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+1));return r?(n.w=+r[0],e+r[0].length):-1}function Ot(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e));return r?(n.U=+r[0],e+r[0].length):-1}function Yt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e));return r?(n.W=+r[0],e+r[0].length):-1}function It(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+4));return r?(n.y=+r[0],e+r[0].length):-1}function Zt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.y=Xt(+r[0]),e+r[0].length):-1}function Vt(n,t,e){return/^[+-]\d{4}$/.test(t=t.substring(e,e+5))?(n.Z=+t,e+5):-1}function Xt(n){return n+(n>68?1900:2e3)}function $t(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Bt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function Wt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function Jt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function Gt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function Kt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function Qt(n,t,e){ic.lastIndex=0;var r=ic.exec(t.substring(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ne(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=~~(oa(t)/60),u=oa(t)%60;return e+Ut(r,"0",2)+Ut(u,"0",2)}function te(n,t,e){oc.lastIndex=0;var r=oc.exec(t.substring(e,e+1));return r?e+r[0].length:-1}function ee(n){for(var t=n.length,e=-1;++e<t;)n[e][0]=this(n[e][0]);return function(t){for(var e=0,r=n[e];!r[1](t);)r=n[++e];return r[0](t)}}function re(){}function ue(n,t,e){var r=e.s=n+t,u=r-n,i=r-u;e.t=n-i+(t-u)}function ie(n,t){n&&lc.hasOwnProperty(n.type)&&lc[n.type](n,t)}function oe(n,t,e){var r,u=-1,i=n.length-e;for(t.lineStart();++u<i;)r=n[u],t.point(r[0],r[1],r[2]);t.lineEnd()}function ae(n,t){var e=-1,r=n.length;for(t.polygonStart();++e<r;)oe(n[e],t,1);t.polygonEnd()}function ce(){function n(n,t){n*=Na,t=t*Na/2+Sa/4;var e=n-r,o=Math.cos(t),a=Math.sin(t),c=i*a,s=u*o+c*Math.cos(e),l=c*Math.sin(e);hc.add(Math.atan2(l,s)),r=n,u=o,i=a}var t,e,r,u,i;gc.point=function(o,a){gc.point=n,r=(t=o)*Na,u=Math.cos(a=(e=a)*Na/2+Sa/4),i=Math.sin(a)},gc.lineEnd=function(){n(t,e)}}function se(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function le(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function fe(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function he(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ge(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function pe(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function ve(n){return[Math.atan2(n[1],n[0]),X(n[2])]}function de(n,t){return oa(n[0]-t[0])<Aa&&oa(n[1]-t[1])<Aa}function me(n,t){n*=Na;var e=Math.cos(t*=Na);ye(e*Math.cos(n),e*Math.sin(n),Math.sin(t))}function ye(n,t,e){++pc,dc+=(n-dc)/pc,mc+=(t-mc)/pc,yc+=(e-yc)/pc}function xe(){function n(n,u){n*=Na;var i=Math.cos(u*=Na),o=i*Math.cos(n),a=i*Math.sin(n),c=Math.sin(u),s=Math.atan2(Math.sqrt((s=e*c-r*a)*s+(s=r*o-t*c)*s+(s=t*a-e*o)*s),t*o+e*a+r*c);vc+=s,xc+=s*(t+(t=o)),Mc+=s*(e+(e=a)),_c+=s*(r+(r=c)),ye(t,e,r)}var t,e,r;kc.point=function(u,i){u*=Na;var o=Math.cos(i*=Na);t=o*Math.cos(u),e=o*Math.sin(u),r=Math.sin(i),kc.point=n,ye(t,e,r)}}function Me(){kc.point=me}function _e(){function n(n,t){n*=Na;var e=Math.cos(t*=Na),o=e*Math.cos(n),a=e*Math.sin(n),c=Math.sin(t),s=u*c-i*a,l=i*o-r*c,f=r*a-u*o,h=Math.sqrt(s*s+l*l+f*f),g=r*o+u*a+i*c,p=h&&-V(g)/h,v=Math.atan2(h,g);bc+=p*s,wc+=p*l,Sc+=p*f,vc+=v,xc+=v*(r+(r=o)),Mc+=v*(u+(u=a)),_c+=v*(i+(i=c)),ye(r,u,i)}var t,e,r,u,i;kc.point=function(o,a){t=o,e=a,kc.point=n,o*=Na;var c=Math.cos(a*=Na);r=c*Math.cos(o),u=c*Math.sin(o),i=Math.sin(a),ye(r,u,i)},kc.lineEnd=function(){n(t,e),kc.lineEnd=Me,kc.point=me}}function be(){return!0}function we(n,t,e,r,u){var i=[],o=[];if(n.forEach(function(n){if(!((t=n.length-1)<=0)){var t,e=n[0],r=n[t];if(de(e,r)){u.lineStart();for(var a=0;t>a;++a)u.point((e=n[a])[0],e[1]);return u.lineEnd(),void 0}var c=new ke(e,n,null,!0),s=new ke(e,null,c,!1);c.o=s,i.push(c),o.push(s),c=new ke(r,n,null,!1),s=new ke(r,null,c,!0),c.o=s,i.push(c),o.push(s)}}),o.sort(t),Se(i),Se(o),i.length){for(var a=0,c=e,s=o.length;s>a;++a)o[a].e=c=!c;for(var l,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;l=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,s=l.length;s>a;++a)u.point((f=l[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){l=g.p.z;for(var a=l.length-1;a>=0;--a)u.point((f=l[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,l=g.z,p=!p}while(!g.v);u.lineEnd()}}}function Se(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r<t;)u.n=e=n[r],e.p=u,u=e;u.n=e=n[0],e.p=u}}function ke(n,t,e,r){this.x=n,this.z=t,this.o=e,this.e=r,this.v=!1,this.n=this.p=null}function Ee(n,t,e,r){return function(u,i){function o(t,e){var r=u(t,e);n(t=r[0],e=r[1])&&i.point(t,e)}function a(n,t){var e=u(n,t);d.point(e[0],e[1])}function c(){y.point=a,d.lineStart()}function s(){y.point=o,d.lineEnd()}function l(n,t){v.push([n,t]);var e=u(n,t);M.point(e[0],e[1])}function f(){M.lineStart(),v=[]}function h(){l(v[0][0],v[0][1]),M.lineEnd();var n,t=M.clean(),e=x.buffer(),r=e.length;if(v.pop(),p.push(v),v=null,r){if(1&t){n=e[0];var u,r=n.length-1,o=-1;for(i.lineStart();++o<r;)i.point((u=n[o])[0],u[1]);return i.lineEnd(),void 0}r>1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Ae))}}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:s,polygonStart:function(){y.point=l,y.lineStart=f,y.lineEnd=h,g=[],p=[],i.polygonStart()},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=s,g=Xo.merge(g);var n=Le(m,p);g.length?we(g,Ne,n,e,i):n&&(i.lineStart(),e(null,null,1,i),i.lineEnd()),i.polygonEnd(),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},x=Ce(),M=t(x);return y}}function Ae(n){return n.length>1}function Ce(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:g,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ne(n,t){return((n=n.x)[0]<0?n[1]-Ea-Aa:Ea-n[1])-((t=t.x)[0]<0?t[1]-Ea-Aa:Ea-t[1])}function Le(n,t){var e=n[0],r=n[1],u=[Math.sin(e),-Math.cos(e),0],i=0,o=0;hc.reset();for(var a=0,c=t.length;c>a;++a){var s=t[a],l=s.length;if(l)for(var f=s[0],h=f[0],g=f[1]/2+Sa/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===l&&(d=0),n=s[d];var m=n[0],y=n[1]/2+Sa/4,x=Math.sin(y),M=Math.cos(y),_=m-h,b=oa(_)>Sa,w=p*x;if(hc.add(Math.atan2(w*Math.sin(_),v*M+w*Math.cos(_))),i+=b?_+(_>=0?ka:-ka):_,b^h>=e^m>=e){var S=fe(se(f),se(n));pe(S);var k=fe(u,S);pe(k);var E=(b^_>=0?-1:1)*X(k[2]);(r>E||r===E&&(S[0]||S[1]))&&(o+=b^_>=0?1:-1)}if(!d++)break;h=m,p=x,v=M,f=n}}return(-Aa>i||Aa>i&&0>hc)^1&o}function ze(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?Sa:-Sa,c=oa(i-e);oa(c-Sa)<Aa?(n.point(e,r=(r+o)/2>0?Ea:-Ea),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=Sa&&(oa(e-u)<Aa&&(e-=u*Aa),oa(i-a)<Aa&&(i-=a*Aa),r=qe(e,r,i,o),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),t=0),n.point(e=i,r=o),u=a},lineEnd:function(){n.lineEnd(),e=r=0/0},clean:function(){return 2-t}}}function qe(n,t,e,r){var u,i,o=Math.sin(n-e);return oa(o)>Aa?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function Te(n,t,e,r){var u;if(null==n)u=e*Ea,r.point(-Sa,u),r.point(0,u),r.point(Sa,u),r.point(Sa,0),r.point(Sa,-u),r.point(0,-u),r.point(-Sa,-u),r.point(-Sa,0),r.point(-Sa,u);else if(oa(n[0]-t[0])>Aa){var i=n[0]<t[0]?Sa:-Sa;u=e*i/2,r.point(-i,u),r.point(0,u),r.point(i,u)}else r.point(t[0],t[1])}function Re(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,s,l;return{lineStart:function(){s=c=!1,l=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?Sa:-Sa),h):0;if(!e&&(s=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(de(e,g)||de(p,g))&&(p[0]+=Aa,p[1]+=Aa,v=t(p[0],p[1]))),v!==c)l=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(l=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&de(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return l|(s&&c)<<1}}}function r(n,t,e){var r=se(n),u=se(t),o=[1,0,0],a=fe(r,u),c=le(a,a),s=a[0],l=c-s*s;if(!l)return!e&&n;var f=i*c/l,h=-i*s/l,g=fe(o,a),p=ge(o,f),v=ge(a,h);he(p,v);var d=g,m=le(p,d),y=le(d,d),x=m*m-y*(le(p,p)-1);if(!(0>x)){var M=Math.sqrt(x),_=ge(d,(-m-M)/y);if(he(_,p),_=ve(_),!e)return _;var b,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(b=w,w=S,S=b);var A=S-w,C=oa(A-Sa)<Aa,N=C||Aa>A;if(!C&&k>E&&(b=k,k=E,E=b),N?C?k+E>0^_[1]<(oa(_[0]-w)<Aa?k:E):k<=_[1]&&_[1]<=E:A>Sa^(w<=_[0]&&_[0]<=S)){var L=ge(d,(-m+M)/y);return he(L,p),[_,ve(L)]}}}function u(t,e){var r=o?n:Sa-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=oa(i)>Aa,c=cr(n,6*Na);return Ee(t,e,c,o?[0,-n]:[-Sa,n-Sa])}function De(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,s=o.y,l=a.x,f=a.y,h=0,g=1,p=l-c,v=f-s;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-s,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-s,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:s+h*v}),1>g&&(u.b={x:c+g*p,y:s+g*v}),u}}}}}}function Pe(n,t,e,r){function u(r,u){return oa(r[0]-n)<Aa?u>0?0:3:oa(r[0]-e)<Aa?u>0?2:1:oa(r[1]-t)<Aa?u>0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,s=a[0];c>o;++o)i=a[o],s[1]<=r?i[1]>r&&Z(s,i,n)>0&&++t:i[1]<=r&&Z(s,i,n)<0&&--t,s=i;return 0!==t}function s(i,a,c,s){var l=0,f=0;if(null==i||(l=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do s.point(0===l||3===l?n:e,l>1?r:t);while((l=(l+c+4)%4)!==f)}else s.point(a[0],a[1])}function l(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){l(n,t)&&a.point(n,t)}function h(){N.point=p,d&&d.push(m=[]),S=!0,w=!1,_=b=0/0}function g(){v&&(p(y,x),M&&w&&A.rejoin(),v.push(A.buffer())),N.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Ac,Math.min(Ac,n)),t=Math.max(-Ac,Math.min(Ac,t));var e=l(n,t);if(d&&m.push([n,t]),S)y=n,x=t,M=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:_,y:b},b:{x:n,y:t}};C(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}_=n,b=t,w=e}var v,d,m,y,x,M,_,b,w,S,k,E=a,A=Ce(),C=De(n,t,e,r),N={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=Xo.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),s(null,null,1,a),a.lineEnd()),u&&we(v,i,t,s,a),a.polygonEnd()),v=d=m=null}};return N}}function Ue(n,t){function e(e,r){return e=n(e,r),t(e[0],e[1])}return n.invert&&t.invert&&(e.invert=function(e,r){return e=t.invert(e,r),e&&n.invert(e[0],e[1])}),e}function je(n){var t=0,e=Sa/3,r=nr(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*Sa/180,e=n[1]*Sa/180):[180*(t/Sa),180*(e/Sa)]},u}function He(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,X((i-(n*n+e*e)*u*u)/(2*u))]},e}function Fe(){function n(n,t){Nc+=u*n-r*t,r=n,u=t}var t,e,r,u;Rc.point=function(i,o){Rc.point=n,t=r=i,e=u=o},Rc.lineEnd=function(){n(t,e)}}function Oe(n,t){Lc>n&&(Lc=n),n>qc&&(qc=n),zc>t&&(zc=t),t>Tc&&(Tc=t)}function Ye(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Ie(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Ie(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Ie(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Ze(n,t){dc+=n,mc+=t,++yc}function Ve(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);xc+=o*(t+n)/2,Mc+=o*(e+r)/2,_c+=o,Ze(t=n,e=r)}var t,e;Pc.point=function(r,u){Pc.point=n,Ze(t=r,e=u)}}function Xe(){Pc.point=Ze}function $e(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);xc+=o*(r+n)/2,Mc+=o*(u+t)/2,_c+=o,o=u*n-r*t,bc+=o*(r+n),wc+=o*(u+t),Sc+=3*o,Ze(r=n,u=t)}var t,e,r,u;Pc.point=function(i,o){Pc.point=n,Ze(t=r=i,e=u=o)},Pc.lineEnd=function(){n(t,e)}}function Be(n){function t(t,e){n.moveTo(t,e),n.arc(t,e,o,0,ka)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:g};return a}function We(n){function t(n){return(a?r:e)(n)}function e(t){return Ke(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){x=0/0,S.point=i,t.lineStart()}function i(e,r){var i=se([e,r]),o=n(e,r);u(x,M,y,_,b,w,x=o[0],M=o[1],y=e,_=i[0],b=i[1],w=i[2],a,t),t.point(x,M)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=s,S.lineEnd=l}function s(n,t){i(f=n,h=t),g=x,p=M,v=_,d=b,m=w,S.point=i}function l(){u(x,M,y,_,b,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,x,M,_,b,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,s,l,f,h,g,p,v,d,m){var y=l-t,x=f-e,M=y*y+x*x;if(M>4*i&&d--){var _=a+g,b=c+p,w=s+v,S=Math.sqrt(_*_+b*b+w*w),k=Math.asin(w/=S),E=oa(oa(w)-1)<Aa||oa(r-h)<Aa?(r+h)/2:Math.atan2(b,_),A=n(E,k),C=A[0],N=A[1],L=C-t,z=N-e,q=x*L-y*z;(q*q/M>i||oa((y*L+x*z)/M-.5)>.3||o>a*g+c*p+s*v)&&(u(t,e,r,a,c,s,C,N,E,_/=S,b/=S,w,d,m),m.point(C,N),u(C,N,E,_,b,w,l,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Na),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function Je(n){var t=We(function(t,e){return n([t*La,e*La])});return function(n){return tr(t(n))}}function Ge(n){this.stream=n}function Ke(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function Qe(n){return nr(function(){return n})()}function nr(n){function t(n){return n=a(n[0]*Na,n[1]*Na),[n[0]*h+c,s-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(s-n[1])/h),n&&[n[0]*La,n[1]*La]}function r(){a=Ue(o=ur(m,y,x),i);var n=i(v,d);return c=g-n[0]*h,s=p+n[1]*h,u()}function u(){return l&&(l.valid=!1,l=null),t}var i,o,a,c,s,l,f=We(function(n,t){return n=i(n,t),[n[0]*h+c,s-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,y=0,x=0,M=Ec,_=bt,b=null,w=null;return t.stream=function(n){return l&&(l.valid=!1),l=tr(M(o,f(_(n)))),l.valid=!0,l},t.clipAngle=function(n){return arguments.length?(M=null==n?(b=n,Ec):Re((b=+n)*Na),u()):b
|
2 |
+
},t.clipExtent=function(n){return arguments.length?(w=n,_=n?Pe(n[0][0],n[0][1],n[1][0],n[1][1]):bt,u()):w},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Na,d=n[1]%360*Na,r()):[v*La,d*La]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Na,y=n[1]%360*Na,x=n.length>2?n[2]%360*Na:0,r()):[m*La,y*La,x*La]},Xo.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function tr(n){return Ke(n,function(t,e){n.point(t*Na,e*Na)})}function er(n,t){return[n,t]}function rr(n,t){return[n>Sa?n-ka:-Sa>n?n+ka:n,t]}function ur(n,t,e){return n?t||e?Ue(or(n),ar(t,e)):or(n):t||e?ar(t,e):rr}function ir(n){return function(t,e){return t+=n,[t>Sa?t-ka:-Sa>t?t+ka:t,e]}}function or(n){var t=ir(n);return t.invert=ir(-n),t}function ar(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*r+a*u;return[Math.atan2(c*i-l*o,a*r-s*u),X(l*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,s=Math.sin(t),l=s*i-c*o;return[Math.atan2(c*i+s*o,a*r+l*u),X(l*r-a*u)]},e}function cr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=sr(e,u),i=sr(e,i),(o>0?i>u:u>i)&&(u+=o*ka)):(u=n+o*ka,i=n-.5*c);for(var s,l=u;o>0?l>i:i>l;l-=c)a.point((s=ve([e,-r*Math.cos(l),-r*Math.sin(l)]))[0],s[1])}}function sr(n,t){var e=se(t);e[0]-=n,pe(e);var r=V(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Aa)%(2*Math.PI)}function lr(n,t,e){var r=Xo.range(n,t-Aa,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function fr(n,t,e){var r=Xo.range(n,t-Aa,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function hr(n){return n.source}function gr(n){return n.target}function pr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),s=u*Math.sin(n),l=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(J(r-t)+u*o*J(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*l,u=e*s+t*f,o=e*i+t*a;return[Math.atan2(u,r)*La,Math.atan2(o,Math.sqrt(r*r+u*u))*La]}:function(){return[n*La,t*La]};return p.distance=h,p}function vr(){function n(n,u){var i=Math.sin(u*=Na),o=Math.cos(u),a=oa((n*=Na)-t),c=Math.cos(a);Uc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;jc.point=function(u,i){t=u*Na,e=Math.sin(i*=Na),r=Math.cos(i),jc.point=n},jc.lineEnd=function(){jc.point=jc.lineEnd=g}}function dr(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function mr(n,t){function e(n,t){var e=oa(oa(t)-Ea)<Aa?0:o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(Sa/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=I(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Ea]},e):xr}function yr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return oa(u)<Aa?er:(e.invert=function(n,t){var e=i-t;return[Math.atan2(n,e)/u,i-I(u)*Math.sqrt(n*n+e*e)]},e)}function xr(n,t){return[n,Math.log(Math.tan(Sa/4+t/2))]}function Mr(n){var t,e=Qe(n),r=e.scale,u=e.translate,i=e.clipExtent;return e.scale=function(){var n=r.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.translate=function(){var n=u.apply(e,arguments);return n===e?t?e.clipExtent(null):e:n},e.clipExtent=function(n){var o=i.apply(e,arguments);if(o===e){if(t=null==n){var a=Sa*r(),c=u();i([[c[0]-a,c[1]-a],[c[0]+a,c[1]+a]])}}else t&&(o=null);return o},e.clipExtent(null)}function _r(n,t){return[Math.log(Math.tan(Sa/4+t/2)),-n]}function br(n){return n[0]}function wr(n){return n[1]}function Sr(n){for(var t=n.length,e=[0,1],r=2,u=2;t>u;u++){for(;r>1&&Z(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function kr(n,t){return n[0]-t[0]||n[1]-t[1]}function Er(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Ar(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],s=e[1],l=t[1]-c,f=r[1]-s,h=(a*(c-s)-f*(u-i))/(f*o-a*l);return[u+h*o,c+h*l]}function Cr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Nr(){Jr(this),this.edge=this.site=this.circle=null}function Lr(n){var t=Jc.pop()||new Nr;return t.site=n,t}function zr(n){Or(n),$c.remove(n),Jc.push(n),Jr(n)}function qr(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];zr(n);for(var c=i;c.circle&&oa(e-c.circle.x)<Aa&&oa(r-c.circle.cy)<Aa;)i=c.P,a.unshift(c),zr(c),c=i;a.unshift(c),Or(c);for(var s=o;s.circle&&oa(e-s.circle.x)<Aa&&oa(r-s.circle.cy)<Aa;)o=s.N,a.push(s),zr(s),s=o;a.push(s),Or(s);var l,f=a.length;for(l=1;f>l;++l)s=a[l],c=a[l-1],$r(s.edge,c.site,s.site,u);c=a[0],s=a[f-1],s.edge=Vr(c.site,s.site,null,u),Fr(c),Fr(s)}function Tr(n){for(var t,e,r,u,i=n.x,o=n.y,a=$c._;a;)if(r=Rr(a,o)-i,r>Aa)a=a.L;else{if(u=i-Dr(a,o),!(u>Aa)){r>-Aa?(t=a.P,e=a):u>-Aa?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Lr(n);if($c.insert(t,c),t||e){if(t===e)return Or(t),e=Lr(t.site),$c.insert(c,e),c.edge=e.edge=Vr(t.site,c.site),Fr(t),Fr(e),void 0;if(!e)return c.edge=Vr(t.site,c.site),void 0;Or(t),Or(e);var s=t.site,l=s.x,f=s.y,h=n.x-l,g=n.y-f,p=e.site,v=p.x-l,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,x=v*v+d*d,M={x:(d*y-g*x)/m+l,y:(h*x-v*y)/m+f};$r(e.edge,s,p,M),c.edge=Vr(s,n,null,M),e.edge=Vr(n,p,null,M),Fr(t),Fr(e)}}function Rr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,s=c-t;if(!s)return a;var l=a-r,f=1/i-1/s,h=l/s;return f?(-h+Math.sqrt(h*h-2*f*(l*l/(-2*s)-c+s/2+u-i/2)))/f+r:(r+a)/2}function Dr(n,t){var e=n.N;if(e)return Rr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Pr(n){this.site=n,this.edges=[]}function Ur(n){for(var t,e,r,u,i,o,a,c,s,l,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Xc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)l=a[o].end(),r=l.x,u=l.y,s=a[++o%c].start(),t=s.x,e=s.y,(oa(r-t)>Aa||oa(u-e)>Aa)&&(a.splice(o,0,new Br(Xr(i.site,l,oa(r-f)<Aa&&p-u>Aa?{x:f,y:oa(t-f)<Aa?e:p}:oa(u-p)<Aa&&h-r>Aa?{x:oa(e-p)<Aa?t:h,y:p}:oa(r-h)<Aa&&u-g>Aa?{x:h,y:oa(t-h)<Aa?e:g}:oa(u-g)<Aa&&r-f>Aa?{x:oa(e-g)<Aa?t:f,y:g}:null),i.site,null)),++c)}function jr(n,t){return t.angle-n.angle}function Hr(){Jr(this),this.x=this.y=this.arc=this.site=this.cy=null}function Fr(n){var t=n.P,e=n.N;if(t&&e){var r=t.site,u=n.site,i=e.site;if(r!==i){var o=u.x,a=u.y,c=r.x-o,s=r.y-a,l=i.x-o,f=i.y-a,h=2*(c*f-s*l);if(!(h>=-Ca)){var g=c*c+s*s,p=l*l+f*f,v=(f*g-s*p)/h,d=(c*p-l*g)/h,f=d+a,m=Gc.pop()||new Hr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,x=Wc._;x;)if(m.y<x.y||m.y===x.y&&m.x<=x.x){if(!x.L){y=x.P;break}x=x.L}else{if(!x.R){y=x;break}x=x.R}Wc.insert(y,m),y||(Bc=m)}}}}function Or(n){var t=n.circle;t&&(t.P||(Bc=t.N),Wc.remove(t),Gc.push(t),Jr(t),n.circle=null)}function Yr(n){for(var t,e=Vc,r=De(n[0][0],n[0][1],n[1][0],n[1][1]),u=e.length;u--;)t=e[u],(!Ir(t,n)||!r(t)||oa(t.a.x-t.b.x)<Aa&&oa(t.a.y-t.b.y)<Aa)&&(t.a=t.b=null,e.splice(u,1))}function Ir(n,t){var e=n.b;if(e)return!0;var r,u,i=n.a,o=t[0][0],a=t[1][0],c=t[0][1],s=t[1][1],l=n.l,f=n.r,h=l.x,g=l.y,p=f.x,v=f.y,d=(h+p)/2,m=(g+v)/2;if(v===g){if(o>d||d>=a)return;if(h>p){if(i){if(i.y>=s)return}else i={x:d,y:c};e={x:d,y:s}}else{if(i){if(i.y<c)return}else i={x:d,y:s};e={x:d,y:c}}}else if(r=(h-p)/(v-g),u=m-r*d,-1>r||r>1)if(h>p){if(i){if(i.y>=s)return}else i={x:(c-u)/r,y:c};e={x:(s-u)/r,y:s}}else{if(i){if(i.y<c)return}else i={x:(s-u)/r,y:s};e={x:(c-u)/r,y:c}}else if(v>g){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.x<o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}return n.a=i,n.b=e,!0}function Zr(n,t){this.l=n,this.r=t,this.a=this.b=null}function Vr(n,t,e,r){var u=new Zr(n,t);return Vc.push(u),e&&$r(u,n,t,e),r&&$r(u,t,n,r),Xc[n.i].edges.push(new Br(u,n,t)),Xc[t.i].edges.push(new Br(u,t,n)),u}function Xr(n,t,e){var r=new Zr(n,null);return r.a=t,r.b=e,Vc.push(r),r}function $r(n,t,e,r){n.a||n.b?n.l===e?n.b=r:n.a=r:(n.a=r,n.l=t,n.r=e)}function Br(n,t,e){var r=n.a,u=n.b;this.edge=n,this.site=t,this.angle=e?Math.atan2(e.y-t.y,e.x-t.x):n.l===t?Math.atan2(u.x-r.x,r.y-u.y):Math.atan2(r.x-u.x,u.y-r.y)}function Wr(){this._=null}function Jr(n){n.U=n.C=n.L=n.R=n.P=n.N=null}function Gr(n,t){var e=t,r=t.R,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.R=r.L,e.R&&(e.R.U=e),r.L=e}function Kr(n,t){var e=t,r=t.L,u=e.U;u?u.L===e?u.L=r:u.R=r:n._=r,r.U=u,e.U=r,e.L=r.R,e.L&&(e.L.U=e),r.R=e}function Qr(n){for(;n.L;)n=n.L;return n}function nu(n,t){var e,r,u,i=n.sort(tu).pop();for(Vc=[],Xc=new Array(n.length),$c=new Wr,Wc=new Wr;;)if(u=Bc,i&&(!u||i.y<u.y||i.y===u.y&&i.x<u.x))(i.x!==e||i.y!==r)&&(Xc[i.i]=new Pr(i),Tr(i),e=i.x,r=i.y),i=n.pop();else{if(!u)break;qr(u.arc)}t&&(Yr(t),Ur(t));var o={cells:Xc,edges:Vc};return $c=Wc=Vc=Xc=null,o}function tu(n,t){return t.y-n.y||t.x-n.x}function eu(n,t,e){return(n.x-e.x)*(t.y-n.y)-(n.x-t.x)*(e.y-n.y)}function ru(n){return n.x}function uu(n){return n.y}function iu(){return{leaf:!0,nodes:[],point:null,x:null,y:null}}function ou(n,t,e,r,u,i){if(!n(t,e,r,u,i)){var o=.5*(e+u),a=.5*(r+i),c=t.nodes;c[0]&&ou(n,c[0],e,r,o,a),c[1]&&ou(n,c[1],o,r,u,a),c[2]&&ou(n,c[2],e,a,o,i),c[3]&&ou(n,c[3],o,a,u,i)}}function au(n,t){n=Xo.rgb(n),t=Xo.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+vt(Math.round(e+i*n))+vt(Math.round(r+o*n))+vt(Math.round(u+a*n))}}function cu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=fu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function su(n,t){return t-=n=+n,function(e){return n+t*e}}function lu(n,t){var e,r,u,i,o,a=0,c=0,s=[],l=[];for(n+="",t+="",Qc.lastIndex=0,r=0;e=Qc.exec(t);++r)e.index&&s.push(t.substring(a,c=e.index)),l.push({i:s.length,x:e[0]}),s.push(null),a=Qc.lastIndex;for(a<t.length&&s.push(t.substring(a)),r=0,i=l.length;(e=Qc.exec(n))&&i>r;++r)if(o=l[r],o.x==e[0]){if(o.i)if(null==s[o.i+1])for(s[o.i-1]+=o.x,s.splice(o.i,1),u=r+1;i>u;++u)l[u].i--;else for(s[o.i-1]+=o.x+s[o.i+1],s.splice(o.i,2),u=r+1;i>u;++u)l[u].i-=2;else if(null==s[o.i+1])s[o.i]=o.x;else for(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1),u=r+1;i>u;++u)l[u].i--;l.splice(r,1),i--,r--}else o.x=su(parseFloat(e[0]),parseFloat(o.x));for(;i>r;)o=l.pop(),null==s[o.i+1]?s[o.i]=o.x:(s[o.i]=o.x+s[o.i+1],s.splice(o.i+1,1)),i--;return 1===s.length?null==s[0]?(o=l[0].x,function(n){return o(n)+""}):function(){return t}:function(n){for(r=0;i>r;++r)s[(o=l[r]).i]=o.x(n);return s.join("")}}function fu(n,t){for(var e,r=Xo.interpolators.length;--r>=0&&!(e=Xo.interpolators[r](n,t)););return e}function hu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(fu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function gu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function pu(n){return function(t){return 1-n(1-t)}}function vu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function du(n){return n*n}function mu(n){return n*n*n}function yu(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function xu(n){return function(t){return Math.pow(t,n)}}function Mu(n){return 1-Math.cos(n*Ea)}function _u(n){return Math.pow(2,10*(n-1))}function bu(n){return 1-Math.sqrt(1-n*n)}function wu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/ka*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*ka/t)}}function Su(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function ku(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Eu(n,t){n=Xo.hcl(n),t=Xo.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return rt(e+i*n,r+o*n,u+a*n)+""}}function Au(n,t){n=Xo.hsl(n),t=Xo.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return nt(e+i*n,r+o*n,u+a*n)+""}}function Cu(n,t){n=Xo.lab(n),t=Xo.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ot(e+i*n,r+o*n,u+a*n)+""}}function Nu(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Lu(n){var t=[n.a,n.b],e=[n.c,n.d],r=qu(t),u=zu(t,e),i=qu(Tu(e,t,-u))||0;t[0]*e[1]<e[0]*t[1]&&(t[0]*=-1,t[1]*=-1,r*=-1,u*=-1),this.rotate=(r?Math.atan2(t[1],t[0]):Math.atan2(-e[0],e[1]))*La,this.translate=[n.e,n.f],this.scale=[r,i],this.skew=i?Math.atan2(u,i)*La:0}function zu(n,t){return n[0]*t[0]+n[1]*t[1]}function qu(n){var t=Math.sqrt(zu(n,n));return t&&(n[0]/=t,n[1]/=t),t}function Tu(n,t,e){return n[0]+=e*t[0],n[1]+=e*t[1],n}function Ru(n,t){var e,r=[],u=[],i=Xo.transform(n),o=Xo.transform(t),a=i.translate,c=o.translate,s=i.rotate,l=o.rotate,f=i.skew,h=o.skew,g=i.scale,p=o.scale;return a[0]!=c[0]||a[1]!=c[1]?(r.push("translate(",null,",",null,")"),u.push({i:1,x:su(a[0],c[0])},{i:3,x:su(a[1],c[1])})):c[0]||c[1]?r.push("translate("+c+")"):r.push(""),s!=l?(s-l>180?l+=360:l-s>180&&(s+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:su(s,l)})):l&&r.push(r.pop()+"rotate("+l+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:su(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:su(g[0],p[0])},{i:e-2,x:su(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i<e;)r[(t=u[i]).i]=t.x(n);return r.join("")}}function Du(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return(e-n)*t}}function Pu(n,t){return t=t-(n=+n)?1/(t-n):0,function(e){return Math.max(0,Math.min(1,(e-n)*t))}}function Uu(n){for(var t=n.source,e=n.target,r=Hu(t,e),u=[t];t!==r;)t=t.parent,u.push(t);for(var i=u.length;e!==r;)u.splice(i,0,e),e=e.parent;return u}function ju(n){for(var t=[],e=n.parent;null!=e;)t.push(n),n=e,e=e.parent;return t.push(n),t}function Hu(n,t){if(n===t)return n;for(var e=ju(n),r=ju(t),u=e.pop(),i=r.pop(),o=null;u===i;)o=u,u=e.pop(),i=r.pop();return o}function Fu(n){n.fixed|=2}function Ou(n){n.fixed&=-7}function Yu(n){n.fixed|=4,n.px=n.x,n.py=n.y}function Iu(n){n.fixed&=-5}function Zu(n,t,e){var r=0,u=0;if(n.charge=0,!n.leaf)for(var i,o=n.nodes,a=o.length,c=-1;++c<a;)i=o[c],null!=i&&(Zu(i,t,e),n.charge+=i.charge,r+=i.charge*i.cx,u+=i.charge*i.cy);if(n.point){n.leaf||(n.point.x+=Math.random()-.5,n.point.y+=Math.random()-.5);var s=t*e[n.point.index];n.charge+=n.pointCharge=s,r+=s*n.point.x,u+=s*n.point.y}n.cx=r/n.charge,n.cy=u/n.charge}function Vu(n,t){return Xo.rebind(n,t,"sort","children","value"),n.nodes=n,n.links=Wu,n}function Xu(n){return n.children}function $u(n){return n.value}function Bu(n,t){return t.value-n.value}function Wu(n){return Xo.merge(n.map(function(n){return(n.children||[]).map(function(t){return{source:n,target:t}})}))}function Ju(n){return n.x}function Gu(n){return n.y}function Ku(n,t,e){n.y0=t,n.y=e}function Qu(n){return Xo.range(n.length)}function ni(n){for(var t=-1,e=n[0].length,r=[];++t<e;)r[t]=0;return r}function ti(n){for(var t,e=1,r=0,u=n[0][1],i=n.length;i>e;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function ei(n){return n.reduce(ri,0)}function ri(n,t){return n+t[1]}function ui(n,t){return ii(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function ii(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function oi(n){return[Xo.min(n),Xo.max(n)]}function ai(n,t){return n.parent==t.parent?1:2}function ci(n){var t=n.children;return t&&t.length?t[0]:n._tree.thread}function si(n){var t,e=n.children;return e&&(t=e.length)?e[t-1]:n._tree.thread}function li(n,t){var e=n.children;if(e&&(u=e.length))for(var r,u,i=-1;++i<u;)t(r=li(e[i],t),n)>0&&(n=r);return n}function fi(n,t){return n.x-t.x}function hi(n,t){return t.x-n.x}function gi(n,t){return n.depth-t.depth}function pi(n,t){function e(n,r){var u=n.children;if(u&&(o=u.length))for(var i,o,a=null,c=-1;++c<o;)i=u[c],e(i,a),a=i;t(n,r)}e(n,null)}function vi(n){for(var t,e=0,r=0,u=n.children,i=u.length;--i>=0;)t=u[i]._tree,t.prelim+=e,t.mod+=e,e+=t.shift+(r+=t.change)}function di(n,t,e){n=n._tree,t=t._tree;var r=e/(t.number-n.number);n.change+=r,t.change-=r,t.shift+=e,t.prelim+=e,t.mod+=e}function mi(n,t,e){return n._tree.ancestor.parent==t.parent?n._tree.ancestor:e}function yi(n,t){return n.value-t.value}function xi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Mi(n,t){n._pack_next=t,t._pack_prev=n}function _i(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function bi(n){function t(n){l=Math.min(n.x-n.r,l),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(s=e.length)){var e,r,u,i,o,a,c,s,l=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(wi),r=e[0],r.x=-r.r,r.y=0,t(r),s>1&&(u=e[1],u.x=u.r,u.y=0,t(u),s>2))for(i=e[2],Ei(r,u,i),t(i),xi(r,i),r._pack_prev=i,xi(i,u),u=r._pack_next,o=3;s>o;o++){Ei(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(_i(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!_i(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.r<r.r?Mi(r,u=a):Mi(r=c,u),o--):(xi(r,i),u=i,t(i))}var m=(l+f)/2,y=(h+g)/2,x=0;for(o=0;s>o;o++)i=e[o],i.x-=m,i.y-=y,x=Math.max(x,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=x,e.forEach(Si)}}function wi(n){n._pack_next=n._pack_prev=n}function Si(n){delete n._pack_next,delete n._pack_prev}function ki(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i<o;)ki(u[i],t,e,r)}function Ei(n,t,e){var r=n.r+e.r,u=t.x-n.x,i=t.y-n.y;if(r&&(u||i)){var o=t.r+e.r,a=u*u+i*i;o*=o,r*=r;var c=.5+(r-o)/(2*a),s=Math.sqrt(Math.max(0,2*o*(r+a)-(r-=a)*r-o*o))/(2*a);e.x=n.x+c*u+s*i,e.y=n.y+c*i-s*u}else e.x=n.x+r,e.y=n.y}function Ai(n){return 1+Xo.max(n,function(n){return n.y})}function Ci(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Ni(n){var t=n.children;return t&&t.length?Ni(t[0]):n}function Li(n){var t,e=n.children;return e&&(t=e.length)?Li(e[t-1]):n}function zi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function qi(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Ti(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ri(n){return n.rangeExtent?n.rangeExtent():Ti(n.range())}function Di(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Pi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Ui(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ls}function ji(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]<n[0]&&(n=n.slice().reverse(),t=t.slice().reverse());++o<=a;)u.push(e(n[o-1],n[o])),i.push(r(t[o-1],t[o]));return function(t){var e=Xo.bisect(n,t,1,a)-1;return i[e](u[e](t))}}function Hi(n,t,e,r){function u(){var u=Math.min(n.length,t.length)>2?ji:Di,c=r?Pu:Du;return o=u(n,t,c,e),a=u(t,n,c,fu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Nu)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Ii(n,t)},i.tickFormat=function(t,e){return Zi(n,t,e)},i.nice=function(t){return Oi(n,t),u()},i.copy=function(){return Hi(n,t,e,r)},u()}function Fi(n,t){return Xo.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Oi(n,t){return Pi(n,Ui(Yi(n,t)[2]))}function Yi(n,t){null==t&&(t=10);var e=Ti(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Ii(n,t){return Xo.range.apply(Xo,Yi(n,t))}function Zi(n,t,e){var r=Yi(n,t);return Xo.format(e?e.replace(Qa,function(n,t,e,u,i,o,a,c,s,l){return[t,e,u,i,o,a,c,s||"."+Xi(l,r),l].join("")}):",."+Vi(r[2])+"f")}function Vi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Xi(n,t){var e=Vi(t[2]);return n in fs?Math.abs(e-Vi(Math.max(Math.abs(t[0]),Math.abs(t[1]))))+ +("e"!==n):e-2*("%"===n)}function $i(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Pi(r.map(u),e?Math:gs);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Ti(r),o=[],a=n[0],c=n[1],s=Math.floor(u(a)),l=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(l-s)){if(e){for(;l>s;s++)for(var h=1;f>h;h++)o.push(i(s)*h);o.push(i(s))}else for(o.push(i(s));s++<l;)for(var h=f-1;h>0;h--)o.push(i(s)*h);for(s=0;o[s]<a;s++);for(l=o.length;o[l-1]>c;l--);o=o.slice(s,l)}return o},o.tickFormat=function(n,t){if(!arguments.length)return hs;arguments.length<2?t=hs:"function"!=typeof t&&(t=Xo.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return $i(n.copy(),t,e,r)},Fi(o,n)}function Bi(n,t,e){function r(t){return n(u(t))}var u=Wi(t),i=Wi(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Ii(e,n)},r.tickFormat=function(n,t){return Zi(e,n,t)},r.nice=function(n){return r.domain(Oi(e,n))},r.exponent=function(o){return arguments.length?(u=Wi(t=o),i=Wi(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Bi(n.copy(),t,e)},Fi(r,n)}function Wi(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Ji(n,t){function e(e){return o[((i.get(e)||"range"===t.t&&i.set(e,n.push(e)))-1)%o.length]}function r(t,e){return Xo.range(n.length).map(function(n){return t+e*n})}var i,o,a;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new u;for(var o,a=-1,c=r.length;++a<c;)i.has(o=r[a])||i.set(o,n.push(o));return e[t.t].apply(e,t.a)},e.range=function(n){return arguments.length?(o=n,a=0,t={t:"range",a:arguments},e):o},e.rangePoints=function(u,i){arguments.length<2&&(i=0);var c=u[0],s=u[1],l=(s-c)/(Math.max(1,n.length-1)+i);return o=r(n.length<2?(c+s)/2:c+l*i/2,l),a=0,t={t:"rangePoints",a:arguments},e},e.rangeBands=function(u,i,c){arguments.length<2&&(i=0),arguments.length<3&&(c=i);var s=u[1]<u[0],l=u[s-0],f=u[1-s],h=(f-l)/(n.length-i+2*c);return o=r(l+h*c,h),s&&o.reverse(),a=h*(1-i),t={t:"rangeBands",a:arguments},e},e.rangeRoundBands=function(u,i,c){arguments.length<2&&(i=0),arguments.length<3&&(c=i);var s=u[1]<u[0],l=u[s-0],f=u[1-s],h=Math.floor((f-l)/(n.length-i+2*c)),g=f-l-(n.length-i)*h;return o=r(l+Math.round(g/2),h),s&&o.reverse(),a=Math.round(h*(1-i)),t={t:"rangeRoundBands",a:arguments},e},e.rangeBand=function(){return a},e.rangeExtent=function(){return Ti(t.a[0])},e.copy=function(){return Ji(n,t)},e.domain(n)}function Gi(n,t){function e(){var e=0,i=t.length;for(u=[];++e<i;)u[e-1]=Xo.quantile(n,e/i);return r}function r(n){return isNaN(n=+n)?void 0:t[Xo.bisect(u,n)]}var u;return r.domain=function(t){return arguments.length?(n=t.filter(function(n){return!isNaN(n)}).sort(Xo.ascending),e()):n},r.range=function(n){return arguments.length?(t=n,e()):t},r.quantiles=function(){return u},r.invertExtent=function(e){return e=t.indexOf(e),0>e?[0/0,0/0]:[e>0?u[e-1]:n[0],e<u.length?u[e]:n[n.length-1]]},r.copy=function(){return Gi(n,t)},e()}function Ki(n,t,e){function r(t){return e[Math.max(0,Math.min(o,Math.floor(i*(t-n))))]}function u(){return i=e.length/(t-n),o=e.length-1,r}var i,o;return r.domain=function(e){return arguments.length?(n=+e[0],t=+e[e.length-1],u()):[n,t]},r.range=function(n){return arguments.length?(e=n,u()):e},r.invertExtent=function(t){return t=e.indexOf(t),t=0>t?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return Ki(n,t,e)},u()}function Qi(n,t){function e(e){return e>=e?t[Xo.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return Qi(n,t)},e}function no(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Ii(n,t)},t.tickFormat=function(t,e){return Zi(n,t,e)},t.copy=function(){return no(n)},t}function to(n){return n.innerRadius}function eo(n){return n.outerRadius}function ro(n){return n.startAngle}function uo(n){return n.endAngle}function io(n){function t(t){function o(){s.push("M",i(n(l),a))}for(var c,s=[],l=[],f=-1,h=t.length,g=_t(e),p=_t(r);++f<h;)u.call(this,c=t[f],f)?l.push([+g.call(this,c,f),+p.call(this,c,f)]):l.length&&(o(),l=[]);return l.length&&o(),s.length?s.join(""):null}var e=br,r=wr,u=be,i=oo,o=i.key,a=.7;return t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t.defined=function(n){return arguments.length?(u=n,t):u},t.interpolate=function(n){return arguments.length?(o="function"==typeof n?i=n:(i=Ms.get(n)||oo).key,t):o},t.tension=function(n){return arguments.length?(a=n,t):a},t}function oo(n){return n.join("L")}function ao(n){return oo(n)+"Z"}function co(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r[0]+(r=n[t])[0])/2,"V",r[1]);return e>1&&u.push("H",r[0]),u.join("")}function so(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("V",(r=n[t])[1],"H",r[0]);return u.join("")}function lo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t<e;)u.push("H",(r=n[t])[0],"V",r[1]);return u.join("")}function fo(n,t){return n.length<4?oo(n):n[1]+po(n.slice(1,n.length-1),vo(n,t))}function ho(n,t){return n.length<3?oo(n):n[0]+po((n.push(n[0]),n),vo([n[n.length-2]].concat(n,[n[1]]),t))}function go(n,t){return n.length<3?oo(n):n[0]+po(n,vo(n,t))}function po(n,t){if(t.length<1||n.length!=t.length&&n.length!=t.length+2)return oo(n);var e=n.length!=t.length,r="",u=n[0],i=n[1],o=t[0],a=o,c=1;if(e&&(r+="Q"+(i[0]-2*o[0]/3)+","+(i[1]-2*o[1]/3)+","+i[0]+","+i[1],u=n[1],c=2),t.length>1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var s=2;s<t.length;s++,c++)i=n[c],a=t[s],r+="S"+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1]}if(e){var l=n[c];r+="Q"+(i[0]+2*a[0]/3)+","+(i[1]+2*a[1]/3)+","+l[0]+","+l[1]}return r}function vo(n,t){for(var e,r=[],u=(1-t)/2,i=n[0],o=n[1],a=1,c=n.length;++a<c;)e=i,i=o,o=n[a],r.push([u*(o[0]-e[0]),u*(o[1]-e[1])]);return r}function mo(n){if(n.length<3)return oo(n);var t=1,e=n.length,r=n[0],u=r[0],i=r[1],o=[u,u,u,(r=n[1])[0]],a=[i,i,i,r[1]],c=[u,",",i,"L",_o(ws,o),",",_o(ws,a)];for(n.push(n[e-1]);++t<=e;)r=n[t],o.shift(),o.push(r[0]),a.shift(),a.push(r[1]),bo(c,o,a);return n.pop(),c.push("L",r),c.join("")}function yo(n){if(n.length<4)return oo(n);for(var t,e=[],r=-1,u=n.length,i=[0],o=[0];++r<3;)t=n[r],i.push(t[0]),o.push(t[1]);for(e.push(_o(ws,i)+","+_o(ws,o)),--r;++r<u;)t=n[r],i.shift(),i.push(t[0]),o.shift(),o.push(t[1]),bo(e,i,o);return e.join("")}function xo(n){for(var t,e,r=-1,u=n.length,i=u+4,o=[],a=[];++r<4;)e=n[r%u],o.push(e[0]),a.push(e[1]);for(t=[_o(ws,o),",",_o(ws,a)],--r;++r<i;)e=n[r%u],o.shift(),o.push(e[0]),a.shift(),a.push(e[1]),bo(t,o,a);return t.join("")}function Mo(n,t){var e=n.length-1;if(e)for(var r,u,i=n[0][0],o=n[0][1],a=n[e][0]-i,c=n[e][1]-o,s=-1;++s<=e;)r=n[s],u=s/e,r[0]=t*r[0]+(1-t)*(i+u*a),r[1]=t*r[1]+(1-t)*(o+u*c);return mo(n)}function _o(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]+n[3]*t[3]}function bo(n,t,e){n.push("C",_o(_s,t),",",_o(_s,e),",",_o(bs,t),",",_o(bs,e),",",_o(ws,t),",",_o(ws,e))}function wo(n,t){return(t[1]-n[1])/(t[0]-n[0])}function So(n){for(var t=0,e=n.length-1,r=[],u=n[0],i=n[1],o=r[0]=wo(u,i);++t<e;)r[t]=(o+(o=wo(u=i,i=n[t+1])))/2;return r[t]=o,r}function ko(n){for(var t,e,r,u,i=[],o=So(n),a=-1,c=n.length-1;++a<c;)t=wo(n[a],n[a+1]),oa(t)<Aa?o[a]=o[a+1]=0:(e=o[a]/t,r=o[a+1]/t,u=e*e+r*r,u>9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function Eo(n){return n.length<3?oo(n):n[0]+po(n,ko(n))}function Ao(n){for(var t,e,r,u=-1,i=n.length;++u<i;)t=n[u],e=t[0],r=t[1]+ys,t[0]=e*Math.cos(r),t[1]=e*Math.sin(r);return n}function Co(n){function t(t){function c(){v.push("M",a(n(m),f),l,s(n(d.reverse()),f),"Z")}for(var h,g,p,v=[],d=[],m=[],y=-1,x=t.length,M=_t(e),_=_t(u),b=e===r?function(){return g}:_t(r),w=u===i?function(){return p}:_t(i);++y<x;)o.call(this,h=t[y],y)?(d.push([g=+M.call(this,h,y),p=+_.call(this,h,y)]),m.push([+b.call(this,h,y),+w.call(this,h,y)])):d.length&&(c(),d=[],m=[]);return d.length&&c(),v.length?v.join(""):null}var e=br,r=br,u=0,i=wr,o=be,a=oo,c=a.key,s=a,l="L",f=.7;return t.x=function(n){return arguments.length?(e=r=n,t):r},t.x0=function(n){return arguments.length?(e=n,t):e},t.x1=function(n){return arguments.length?(r=n,t):r},t.y=function(n){return arguments.length?(u=i=n,t):i},t.y0=function(n){return arguments.length?(u=n,t):u},t.y1=function(n){return arguments.length?(i=n,t):i},t.defined=function(n){return arguments.length?(o=n,t):o},t.interpolate=function(n){return arguments.length?(c="function"==typeof n?a=n:(a=Ms.get(n)||oo).key,s=a.reverse||a,l=a.closed?"M":"L",t):c},t.tension=function(n){return arguments.length?(f=n,t):f},t}function No(n){return n.radius}function Lo(n){return[n.x,n.y]}function zo(n){return function(){var t=n.apply(this,arguments),e=t[0],r=t[1]+ys;return[e*Math.cos(r),e*Math.sin(r)]}}function qo(){return 64}function To(){return"circle"}function Ro(n){var t=Math.sqrt(n/Sa);return"M0,"+t+"A"+t+","+t+" 0 1,1 0,"+-t+"A"+t+","+t+" 0 1,1 0,"+t+"Z"}function Do(n,t){return fa(n,Ns),n.id=t,n}function Po(n,t,e,r){var u=n.id;return R(n,"function"==typeof e?function(n,i,o){n.__transition__[u].tween.set(t,r(e.call(n,n.__data__,i,o)))}:(e=r(e),function(n){n.__transition__[u].tween.set(t,e)}))}function Uo(n){return null==n&&(n=""),function(){this.textContent=n}}function jo(n,t,e,r){var i=n.__transition__||(n.__transition__={active:0,count:0}),o=i[e];if(!o){var a=r.time;o=i[e]={tween:new u,time:a,ease:r.ease,delay:r.delay,duration:r.duration},++i.count,Xo.timer(function(r){function u(r){return i.active>e?s():(i.active=e,o.event&&o.event.start.call(n,l,t),o.tween.forEach(function(e,r){(r=r.call(n,l,t))&&v.push(r)}),Xo.timer(function(){return p.c=c(r||1)?be:c,1},0,a),void 0)}function c(r){if(i.active!==e)return s();for(var u=r/g,a=f(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,l,t),s()):void 0}function s(){return--i.count?delete i[e]:delete n.__transition__,1}var l=n.__data__,f=o.ease,h=o.delay,g=o.duration,p=Ja,v=[];return p.t=h+a,r>=h?u(r-h):(p.c=u,void 0)},0,a)}}function Ho(n,t){n.attr("transform",function(n){return"translate("+t(n)+",0)"})}function Fo(n,t){n.attr("transform",function(n){return"translate(0,"+t(n)+")"})}function Oo(n){return n.toISOString()}function Yo(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=Xo.bisect(js,u);return i==js.length?[t.year,Yi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/js[i-1]<js[i]/u?i-1:i]:[Os,Yi(n,e)[2]]
|
3 |
+
}return r.invert=function(t){return Io(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain(t),r):n.domain().map(Io)},r.nice=function(n,t){function e(e){return!isNaN(e)&&!n.range(e,Io(+e+1),t).length}var i=r.domain(),o=Ti(i),a=null==n?u(o,10):"number"==typeof n&&u(o,n);return a&&(n=a[0],t=a[1]),r.domain(Pi(i,t>1?{floor:function(t){for(;e(t=n.floor(t));)t=Io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Ti(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Yo(n.copy(),t,e)},Fi(r,n)}function Io(n){return new Date(n)}function Zo(n){return JSON.parse(n.responseText)}function Vo(n){var t=Wo.createRange();return t.selectNode(Wo.body),t.createContextualFragment(n.responseText)}var Xo={version:"3.4.2"};Date.now||(Date.now=function(){return+new Date});var $o=[].slice,Bo=function(n){return $o.call(n)},Wo=document,Jo=Wo.documentElement,Go=window;try{Bo(Jo.childNodes)[0].nodeType}catch(Ko){Bo=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}try{Wo.createElement("div").style.setProperty("opacity",0,"")}catch(Qo){var na=Go.Element.prototype,ta=na.setAttribute,ea=na.setAttributeNS,ra=Go.CSSStyleDeclaration.prototype,ua=ra.setProperty;na.setAttribute=function(n,t){ta.call(this,n,t+"")},na.setAttributeNS=function(n,t,e){ea.call(this,n,t,e+"")},ra.setProperty=function(n,t,e){ua.call(this,n,t+"",e)}}Xo.ascending=function(n,t){return t>n?-1:n>t?1:n>=t?0:0/0},Xo.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},Xo.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i&&!(null!=(e=n[u])&&e>=e);)e=void 0;for(;++u<i;)null!=(r=n[u])&&e>r&&(e=r)}else{for(;++u<i&&!(null!=(e=t.call(n,n[u],u))&&e>=e);)e=void 0;for(;++u<i;)null!=(r=t.call(n,n[u],u))&&e>r&&(e=r)}return e},Xo.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u<i&&!(null!=(e=n[u])&&e>=e);)e=void 0;for(;++u<i;)null!=(r=n[u])&&r>e&&(e=r)}else{for(;++u<i&&!(null!=(e=t.call(n,n[u],u))&&e>=e);)e=void 0;for(;++u<i;)null!=(r=t.call(n,n[u],u))&&r>e&&(e=r)}return e},Xo.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i<o&&!(null!=(e=u=n[i])&&e>=e);)e=u=void 0;for(;++i<o;)null!=(r=n[i])&&(e>r&&(e=r),r>u&&(u=r))}else{for(;++i<o&&!(null!=(e=u=t.call(n,n[i],i))&&e>=e);)e=void 0;for(;++i<o;)null!=(r=t.call(n,n[i],i))&&(e>r&&(e=r),r>u&&(u=r))}return[e,u]},Xo.sum=function(n,t){var e,r=0,u=n.length,i=-1;if(1===arguments.length)for(;++i<u;)isNaN(e=+n[i])||(r+=e);else for(;++i<u;)isNaN(e=+t.call(n,n[i],i))||(r+=e);return r},Xo.mean=function(t,e){var r,u=t.length,i=0,o=-1,a=0;if(1===arguments.length)for(;++o<u;)n(r=t[o])&&(i+=(r-i)/++a);else for(;++o<u;)n(r=e.call(t,t[o],o))&&(i+=(r-i)/++a);return a?i:void 0},Xo.quantile=function(n,t){var e=(n.length-1)*t+1,r=Math.floor(e),u=+n[r-1],i=e-r;return i?u+i*(n[r]-u):u},Xo.median=function(t,e){return arguments.length>1&&(t=t.map(e)),t=t.filter(n),t.length?Xo.quantile(t.sort(Xo.ascending),.5):void 0},Xo.bisector=function(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n.call(t,t[i],i)<e?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;e<n.call(t,t[i],i)?u=i:r=i+1}return r}}};var ia=Xo.bisector(function(n){return n});Xo.bisectLeft=ia.left,Xo.bisect=Xo.bisectRight=ia.right,Xo.shuffle=function(n){for(var t,e,r=n.length;r;)e=0|Math.random()*r--,t=n[r],n[r]=n[e],n[e]=t;return n},Xo.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},Xo.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},Xo.zip=function(){if(!(u=arguments.length))return[];for(var n=-1,e=Xo.min(arguments,t),r=new Array(e);++n<e;)for(var u,i=-1,o=r[n]=new Array(u);++i<u;)o[i]=arguments[i][n];return r},Xo.transpose=function(n){return Xo.zip.apply(Xo,n)},Xo.keys=function(n){var t=[];for(var e in n)t.push(e);return t},Xo.values=function(n){var t=[];for(var e in n)t.push(n[e]);return t},Xo.entries=function(n){var t=[];for(var e in n)t.push({key:e,value:n[e]});return t},Xo.merge=function(n){for(var t,e,r,u=n.length,i=-1,o=0;++i<u;)o+=n[i].length;for(e=new Array(o);--u>=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var oa=Math.abs;Xo.range=function(n,t,r){if(arguments.length<3&&(r=1,arguments.length<2&&(t=n,n=0)),1/0===(t-n)/r)throw new Error("infinite range");var u,i=[],o=e(oa(r)),a=-1;if(n*=o,t*=o,r*=o,0>r)for(;(u=n+r*++a)>t;)i.push(u/o);else for(;(u=n+r*++a)<t;)i.push(u/o);return i},Xo.map=function(n){var t=new u;if(n instanceof u)n.forEach(function(n,e){t.set(n,e)});else for(var e in n)t.set(e,n[e]);return t},r(u,{has:i,get:function(n){return this[aa+n]},set:function(n,t){return this[aa+n]=t},remove:o,keys:a,values:function(){var n=[];return this.forEach(function(t,e){n.push(e)}),n},entries:function(){var n=[];return this.forEach(function(t,e){n.push({key:t,value:e})}),n},size:c,empty:s,forEach:function(n){for(var t in this)t.charCodeAt(0)===ca&&n.call(this,t.substring(1),this[t])}});var aa="\x00",ca=aa.charCodeAt(0);Xo.nest=function(){function n(t,a,c){if(c>=o.length)return r?r.call(i,a):e?a.sort(e):a;for(var s,l,f,h,g=-1,p=a.length,v=o[c++],d=new u;++g<p;)(h=d.get(s=v(l=a[g])))?h.push(l):d.set(s,[l]);return t?(l=t(),f=function(e,r){l.set(e,n(t,r,c))}):(l={},f=function(e,r){l[e]=n(t,r,c)}),d.forEach(f),l}function t(n,e){if(e>=o.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,i={},o=[],a=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(Xo.map,e,0),0)},i.key=function(n){return o.push(n),i},i.sortKeys=function(n){return a[o.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},Xo.set=function(n){var t=new l;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},r(l,{has:i,add:function(n){return this[aa+n]=!0,n},remove:function(n){return n=aa+n,n in this&&delete this[n]},values:a,size:c,empty:s,forEach:function(n){for(var t in this)t.charCodeAt(0)===ca&&n.call(this,t.substring(1))}}),Xo.behavior={},Xo.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r<u;)n[e=arguments[r]]=f(n,t,t[e]);return n};var sa=["webkit","ms","moz","Moz","o","O"];Xo.dispatch=function(){for(var n=new p,t=-1,e=arguments.length;++t<e;)n[arguments[t]]=v(n);return n},p.prototype.on=function(n,t){var e=n.indexOf("."),r="";if(e>=0&&(r=n.substring(e+1),n=n.substring(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},Xo.event=null,Xo.requote=function(n){return n.replace(la,"\\$&")};var la=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,fa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ha=function(n,t){return t.querySelector(n)},ga=function(n,t){return t.querySelectorAll(n)},pa=Jo[h(Jo,"matchesSelector")],va=function(n,t){return pa.call(n,t)};"function"==typeof Sizzle&&(ha=function(n,t){return Sizzle(n,t)[0]||null},ga=function(n,t){return Sizzle.uniqueSort(Sizzle(n,t))},va=Sizzle.matchesSelector),Xo.selection=function(){return xa};var da=Xo.selection.prototype=[];da.select=function(n){var t,e,r,u,i=[];n=M(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]),t.parentNode=(r=this[o]).parentNode;for(var c=-1,s=r.length;++c<s;)(u=r[c])?(t.push(e=n.call(u,u.__data__,c,o)),e&&"__data__"in u&&(e.__data__=u.__data__)):t.push(null)}return x(i)},da.selectAll=function(n){var t,e,r=[];n=_(n);for(var u=-1,i=this.length;++u<i;)for(var o=this[u],a=-1,c=o.length;++a<c;)(e=o[a])&&(r.push(t=Bo(n.call(e,e.__data__,a,u))),t.parentNode=e);return x(r)};var ma={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};Xo.ns={prefix:ma,qualify:function(n){var t=n.indexOf(":"),e=n;return t>=0&&(e=n.substring(0,t),n=n.substring(t+1)),ma.hasOwnProperty(e)?{space:ma[e],local:n}:n}},da.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=Xo.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(b(t,n[t]));return this}return this.each(b(n,t))},da.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=k(n)).length,u=-1;if(t=e.classList){for(;++u<r;)if(!t.contains(n[u]))return!1}else for(t=e.getAttribute("class");++u<r;)if(!S(n[u]).test(t))return!1;return!0}for(t in n)this.each(E(t,n[t]));return this}return this.each(E(n,t))},da.style=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t="");for(e in n)this.each(C(e,n[e],t));return this}if(2>r)return Go.getComputedStyle(this.node(),null).getPropertyValue(n);e=""}return this.each(C(n,t,e))},da.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(N(t,n[t]));return this}return this.each(N(n,t))},da.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},da.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},da.append=function(n){return n=L(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},da.insert=function(n,t){return n=L(n),t=M(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},da.remove=function(){return this.each(function(){var n=this.parentNode;n&&n.removeChild(this)})},da.data=function(n,t){function e(n,e){var r,i,o,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new u,y=new u,x=[];for(r=-1;++r<a;)d=t.call(i=n[r],i.__data__,r),m.has(d)?v[r]=i:m.set(d,i),x.push(d);for(r=-1;++r<f;)d=t.call(e,o=e[r],r),(i=m.get(d))?(g[r]=i,i.__data__=o):y.has(d)||(p[r]=z(o)),y.set(d,o),m.remove(d);for(r=-1;++r<a;)m.has(x[r])&&(v[r]=n[r])}else{for(r=-1;++r<h;)i=n[r],o=e[r],i?(i.__data__=o,g[r]=i):p[r]=z(o);for(;f>r;++r)p[r]=z(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,c.push(p),s.push(g),l.push(v)}var r,i,o=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++o<a;)(i=r[o])&&(n[o]=i.__data__);return n}var c=D([]),s=x([]),l=x([]);if("function"==typeof n)for(;++o<a;)e(r=this[o],n.call(r,r.parentNode.__data__,o));else for(;++o<a;)e(r=this[o],n);return s.enter=function(){return c},s.exit=function(){return l},s},da.datum=function(n){return arguments.length?this.property("__data__",n):this.property("__data__")},da.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=q(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return x(u)},da.order=function(){for(var n=-1,t=this.length;++n<t;)for(var e,r=this[n],u=r.length-1,i=r[u];--u>=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},da.sort=function(n){n=T.apply(this,arguments);for(var t=-1,e=this.length;++t<e;)this[t].sort(n);return this.order()},da.each=function(n){return R(this,function(t,e,r){n.call(t,t.__data__,e,r)})},da.call=function(n){var t=Bo(arguments);return n.apply(t[0]=this,t),this},da.empty=function(){return!this.node()},da.node=function(){for(var n=0,t=this.length;t>n;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},da.size=function(){var n=0;return this.each(function(){++n}),n};var ya=[];Xo.selection.enter=D,Xo.selection.enter.prototype=ya,ya.append=da.append,ya.empty=da.empty,ya.node=da.node,ya.call=da.call,ya.size=da.size,ya.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++a<c;){r=(u=this[a]).update,o.push(t=[]),t.parentNode=u.parentNode;for(var s=-1,l=u.length;++s<l;)(i=u[s])?(t.push(r[s]=e=n.call(u.parentNode,i.__data__,s,a)),e.__data__=i.__data__):t.push(null)}return x(o)},ya.insert=function(n,t){return arguments.length<2&&(t=P(this)),da.insert.call(this,n,t)},da.transition=function(){for(var n,t,e=ks||++Ls,r=[],u=Es||{time:Date.now(),ease:yu,delay:0,duration:250},i=-1,o=this.length;++i<o;){r.push(n=[]);for(var a=this[i],c=-1,s=a.length;++c<s;)(t=a[c])&&jo(t,c,e,u),n.push(t)}return Do(r,e)},da.interrupt=function(){return this.each(U)},Xo.select=function(n){var t=["string"==typeof n?ha(n,Wo):n];return t.parentNode=Jo,x([t])},Xo.selectAll=function(n){var t=Bo("string"==typeof n?ga(n,Wo):n);return t.parentNode=Jo,x([t])};var xa=Xo.select(Jo);da.on=function(n,t,e){var r=arguments.length;if(3>r){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(j(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(j(n,t,e))};var Ma=Xo.map({mouseenter:"mouseover",mouseleave:"mouseout"});Ma.forEach(function(n){"on"+n in Wo&&Ma.remove(n)});var _a="onselectstart"in Wo?null:h(Jo.style,"userSelect"),ba=0;Xo.mouse=function(n){return Y(n,m())};var wa=/WebKit/.test(Go.navigator.userAgent)?-1:0;Xo.touches=function(n,t){return arguments.length<2&&(t=m().touches),t?Bo(t).map(function(t){var e=Y(n,t);return e.identifier=t.identifier,e}):[]},Xo.behavior.drag=function(){function n(){this.on("mousedown.drag",o).on("touchstart.drag",a)}function t(){return Xo.event.changedTouches[0].identifier}function e(n,t){return Xo.touches(n).filter(function(n){return n.identifier===t})[0]}function r(n,t,e,r){return function(){function o(){var n=t(l,g),e=n[0]-v[0],r=n[1]-v[1];d|=e|r,v=n,f({type:"drag",x:n[0]+c[0],y:n[1]+c[1],dx:e,dy:r})}function a(){m.on(e+"."+p,null).on(r+"."+p,null),y(d&&Xo.event.target===h),f({type:"dragend"})}var c,s=this,l=s.parentNode,f=u.of(s,arguments),h=Xo.event.target,g=n(),p=null==g?"drag":"drag-"+g,v=t(l,g),d=0,m=Xo.select(Go).on(e+"."+p,o).on(r+"."+p,a),y=O();i?(c=i.apply(s,arguments),c=[c.x-v[0],c.y-v[1]]):c=[0,0],f({type:"dragstart"})}}var u=y(n,"drag","dragstart","dragend"),i=null,o=r(g,Xo.mouse,"mousemove","mouseup"),a=r(t,e,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},Xo.rebind(n,u,"on")};var Sa=Math.PI,ka=2*Sa,Ea=Sa/2,Aa=1e-6,Ca=Aa*Aa,Na=Sa/180,La=180/Sa,za=Math.SQRT2,qa=2,Ta=4;Xo.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=B(v),o=i/(qa*h)*(e*W(za*t+v)-$(v));return[r+o*s,u+o*l,i*e/B(za*t+v)]}return[r+n*s,u+n*l,i*Math.exp(za*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],s=o-r,l=a-u,f=s*s+l*l,h=Math.sqrt(f),g=(c*c-i*i+Ta*f)/(2*i*qa*h),p=(c*c-i*i-Ta*f)/(2*c*qa*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/za;return e.duration=1e3*y,e},Xo.behavior.zoom=function(){function n(n){n.on(A,s).on(Pa+".zoom",f).on(C,h).on("dblclick.zoom",g).on(L,l)}function t(n){return[(n[0]-S.x)/S.k,(n[1]-S.y)/S.k]}function e(n){return[n[0]*S.k+S.x,n[1]*S.k+S.y]}function r(n){S.k=Math.max(E[0],Math.min(E[1],n))}function u(n,t){t=e(t),S.x+=n[0]-t[0],S.y+=n[1]-t[1]}function i(){_&&_.domain(M.range().map(function(n){return(n-S.x)/S.k}).map(M.invert)),w&&w.domain(b.range().map(function(n){return(n-S.y)/S.k}).map(b.invert))}function o(n){n({type:"zoomstart"})}function a(n){i(),n({type:"zoom",scale:S.k,translate:[S.x,S.y]})}function c(n){n({type:"zoomend"})}function s(){function n(){l=1,u(Xo.mouse(r),g),a(i)}function e(){f.on(C,Go===r?h:null).on(N,null),p(l&&Xo.event.target===s),c(i)}var r=this,i=z.of(r,arguments),s=Xo.event.target,l=0,f=Xo.select(Go).on(C,n).on(N,e),g=t(Xo.mouse(r)),p=O();U.call(r),o(i)}function l(){function n(){var n=Xo.touches(g);return h=S.k,n.forEach(function(n){n.identifier in v&&(v[n.identifier]=t(n))}),n}function e(){for(var t=Xo.event.changedTouches,e=0,i=t.length;i>e;++e)v[t[e].identifier]=null;var o=n(),c=Date.now();if(1===o.length){if(500>c-x){var s=o[0],l=v[s.identifier];r(2*S.k),u(s,l),d(),a(p)}x=c}else if(o.length>1){var s=o[0],f=o[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function i(){for(var n,t,e,i,o=Xo.touches(g),c=0,s=o.length;s>c;++c,i=null)if(e=o[c],i=v[e.identifier]){if(t)break;n=e,t=i}if(i){var l=(l=e[0]-n[0])*l+(l=e[1]-n[1])*l,f=m&&Math.sqrt(l/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+i[0])/2,(t[1]+i[1])/2],r(f*h)}x=null,u(n,t),a(p)}function f(){if(Xo.event.touches.length){for(var t=Xo.event.changedTouches,e=0,r=t.length;r>e;++e)delete v[t[e].identifier];for(var u in v)return void n()}b.on(M,null).on(_,null),w.on(A,s).on(L,l),k(),c(p)}var h,g=this,p=z.of(g,arguments),v={},m=0,y=Xo.event.changedTouches[0].identifier,M="touchmove.zoom-"+y,_="touchend.zoom-"+y,b=Xo.select(Go).on(M,i).on(_,f),w=Xo.select(g).on(A,null).on(L,e),k=O();U.call(g),e(),o(p)}function f(){var n=z.of(this,arguments);m?clearTimeout(m):(U.call(this),o(n)),m=setTimeout(function(){m=null,c(n)},50),d();var e=v||Xo.mouse(this);p||(p=t(e)),r(Math.pow(2,.002*Ra())*S.k),u(e,p),a(n)}function h(){p=null}function g(){var n=z.of(this,arguments),e=Xo.mouse(this),i=t(e),s=Math.log(S.k)/Math.LN2;o(n),r(Math.pow(2,Xo.event.shiftKey?Math.ceil(s)-1:Math.floor(s)+1)),u(e,i),a(n),c(n)}var p,v,m,x,M,_,b,w,S={x:0,y:0,k:1},k=[960,500],E=Da,A="mousedown.zoom",C="mousemove.zoom",N="mouseup.zoom",L="touchstart.zoom",z=y(n,"zoomstart","zoom","zoomend");return n.event=function(n){n.each(function(){var n=z.of(this,arguments),t=S;ks?Xo.select(this).transition().each("start.zoom",function(){S=this.__chart__||{x:0,y:0,k:1},o(n)}).tween("zoom:zoom",function(){var e=k[0],r=k[1],u=e/2,i=r/2,o=Xo.interpolateZoom([(u-S.x)/S.k,(i-S.y)/S.k,e/S.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),c=e/r[2];this.__chart__=S={x:u-r[0]*c,y:i-r[1]*c,k:c},a(n)}}).each("end.zoom",function(){c(n)}):(this.__chart__=S,o(n),a(n),c(n))})},n.translate=function(t){return arguments.length?(S={x:+t[0],y:+t[1],k:S.k},i(),n):[S.x,S.y]},n.scale=function(t){return arguments.length?(S={x:S.x,y:S.y,k:+t},i(),n):S.k},n.scaleExtent=function(t){return arguments.length?(E=null==t?Da:[+t[0],+t[1]],n):E},n.center=function(t){return arguments.length?(v=t&&[+t[0],+t[1]],n):v},n.size=function(t){return arguments.length?(k=t&&[+t[0],+t[1]],n):k},n.x=function(t){return arguments.length?(_=t,M=t.copy(),S={x:0,y:0,k:1},n):_},n.y=function(t){return arguments.length?(w=t,b=t.copy(),S={x:0,y:0,k:1},n):w},Xo.rebind(n,z,"on")};var Ra,Da=[0,1/0],Pa="onwheel"in Wo?(Ra=function(){return-Xo.event.deltaY*(Xo.event.deltaMode?120:1)},"wheel"):"onmousewheel"in Wo?(Ra=function(){return Xo.event.wheelDelta},"mousewheel"):(Ra=function(){return-Xo.event.detail},"MozMousePixelScroll");G.prototype.toString=function(){return this.rgb()+""},Xo.hsl=function(n,t,e){return 1===arguments.length?n instanceof Q?K(n.h,n.s,n.l):dt(""+n,mt,K):K(+n,+t,+e)};var Ua=Q.prototype=new G;Ua.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),K(this.h,this.s,this.l/n)},Ua.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),K(this.h,this.s,n*this.l)},Ua.rgb=function(){return nt(this.h,this.s,this.l)},Xo.hcl=function(n,t,e){return 1===arguments.length?n instanceof et?tt(n.h,n.c,n.l):n instanceof it?at(n.l,n.a,n.b):at((n=yt((n=Xo.rgb(n)).r,n.g,n.b)).l,n.a,n.b):tt(+n,+t,+e)};var ja=et.prototype=new G;ja.brighter=function(n){return tt(this.h,this.c,Math.min(100,this.l+Ha*(arguments.length?n:1)))},ja.darker=function(n){return tt(this.h,this.c,Math.max(0,this.l-Ha*(arguments.length?n:1)))},ja.rgb=function(){return rt(this.h,this.c,this.l).rgb()},Xo.lab=function(n,t,e){return 1===arguments.length?n instanceof it?ut(n.l,n.a,n.b):n instanceof et?rt(n.l,n.c,n.h):yt((n=Xo.rgb(n)).r,n.g,n.b):ut(+n,+t,+e)};var Ha=18,Fa=.95047,Oa=1,Ya=1.08883,Ia=it.prototype=new G;Ia.brighter=function(n){return ut(Math.min(100,this.l+Ha*(arguments.length?n:1)),this.a,this.b)},Ia.darker=function(n){return ut(Math.max(0,this.l-Ha*(arguments.length?n:1)),this.a,this.b)},Ia.rgb=function(){return ot(this.l,this.a,this.b)},Xo.rgb=function(n,t,e){return 1===arguments.length?n instanceof pt?gt(n.r,n.g,n.b):dt(""+n,gt,nt):gt(~~n,~~t,~~e)};var Za=pt.prototype=new G;Za.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),gt(Math.min(255,~~(t/n)),Math.min(255,~~(e/n)),Math.min(255,~~(r/n)))):gt(u,u,u)},Za.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),gt(~~(n*this.r),~~(n*this.g),~~(n*this.b))},Za.hsl=function(){return mt(this.r,this.g,this.b)},Za.toString=function(){return"#"+vt(this.r)+vt(this.g)+vt(this.b)};var Va=Xo.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Va.forEach(function(n,t){Va.set(n,ft(t))}),Xo.functor=_t,Xo.xhr=wt(bt),Xo.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=St(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(l>=s)return o;if(u)return u=!1,i;var t=l;if(34===n.charCodeAt(t)){for(var e=t;e++<s;)if(34===n.charCodeAt(e)){if(34!==n.charCodeAt(e+1))break;++e}l=e+2;var r=n.charCodeAt(e+1);return 13===r?(u=!0,10===n.charCodeAt(e+2)&&++l):10===r&&(u=!0),n.substring(t+1,e).replace(/""/g,'"')}for(;s>l;){var r=n.charCodeAt(l++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(l)&&(++l,++a);else if(r!==c)continue;return n.substring(t,l-a)}return n.substring(t)}for(var r,u,i={},o={},a=[],s=n.length,l=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();(!t||(h=t(h,f++)))&&a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new l,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},Xo.csv=Xo.dsv(",","text/csv"),Xo.tsv=Xo.dsv(" ","text/tab-separated-values");var Xa,$a,Ba,Wa,Ja,Ga=Go[h(Go,"requestAnimationFrame")]||function(n){setTimeout(n,17)};Xo.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};$a?$a.n=i:Xa=i,$a=i,Ba||(Wa=clearTimeout(Wa),Ba=1,Ga(Et))},Xo.timer.flush=function(){At(),Ct()},Xo.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var Ka=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Lt);Xo.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=Xo.round(n,Nt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((0>=e?e+1:e-1)/3)))),Ka[8+e/3]};var Qa=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,nc=Xo.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=Xo.round(n,Nt(n,t))).toFixed(Math.max(0,Math.min(20,Nt(n*(1+1e-15),t))))}}),tc=Xo.time={},ec=Date;Tt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){rc.setUTCDate.apply(this._,arguments)},setDay:function(){rc.setUTCDay.apply(this._,arguments)},setFullYear:function(){rc.setUTCFullYear.apply(this._,arguments)},setHours:function(){rc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){rc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){rc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){rc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){rc.setUTCSeconds.apply(this._,arguments)},setTime:function(){rc.setTime.apply(this._,arguments)}};var rc=Date.prototype;tc.year=Rt(function(n){return n=tc.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),tc.years=tc.year.range,tc.years.utc=tc.year.utc.range,tc.day=Rt(function(n){var t=new ec(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),tc.days=tc.day.range,tc.days.utc=tc.day.utc.range,tc.dayOfYear=function(n){var t=tc.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=tc[n]=Rt(function(n){return(n=tc.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=tc.year(n).getDay();return Math.floor((tc.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});tc[n+"s"]=e.range,tc[n+"s"].utc=e.utc.range,tc[n+"OfYear"]=function(n){var e=tc.year(n).getDay();return Math.floor((tc.dayOfYear(n)+(e+t)%7)/7)}}),tc.week=tc.sunday,tc.weeks=tc.sunday.range,tc.weeks.utc=tc.sunday.utc.range,tc.weekOfYear=tc.sundayOfYear;var uc={"-":"",_:" ",0:"0"},ic=/^\s*\d+/,oc=/^%/;Xo.locale=function(n){return{numberFormat:zt(n),timeFormat:Pt(n)}};var ac=Xo.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});Xo.format=ac.numberFormat,Xo.geo={},re.prototype={s:0,t:0,add:function(n){ue(n,this.t,cc),ue(cc.s,this.s,this),this.s?this.t+=cc.t:this.s=cc.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var cc=new re;Xo.geo.stream=function(n,t){n&&sc.hasOwnProperty(n.type)?sc[n.type](n,t):ie(n,t)};var sc={Feature:function(n,t){ie(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++r<u;)ie(e[r].geometry,t)}},lc={Sphere:function(n,t){t.sphere()},Point:function(n,t){n=n.coordinates,t.point(n[0],n[1],n[2])},MultiPoint:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)n=e[r],t.point(n[0],n[1],n[2])},LineString:function(n,t){oe(n.coordinates,t,0)},MultiLineString:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)oe(e[r],t,0)},Polygon:function(n,t){ae(n.coordinates,t)},MultiPolygon:function(n,t){for(var e=n.coordinates,r=-1,u=e.length;++r<u;)ae(e[r],t)},GeometryCollection:function(n,t){for(var e=n.geometries,r=-1,u=e.length;++r<u;)ie(e[r],t)}};Xo.geo.area=function(n){return fc=0,Xo.geo.stream(n,gc),fc};var fc,hc=new re,gc={sphere:function(){fc+=4*Sa},point:g,lineStart:g,lineEnd:g,polygonStart:function(){hc.reset(),gc.lineStart=ce},polygonEnd:function(){var n=2*hc;fc+=0>n?4*Sa+n:n,gc.lineStart=gc.lineEnd=gc.point=g}};Xo.geo.bounds=function(){function n(n,t){x.push(M=[l=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=se([t*Na,e*Na]);if(m){var u=fe(m,r),i=[u[1],-u[0],0],o=fe(i,u);pe(o),o=ve(o);var c=t-p,s=c>0?1:-1,v=o[0]*La*s,d=oa(c)>180;if(d^(v>s*p&&s*t>v)){var y=o[1]*La;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>s*p&&s*t>v)){var y=-o[1]*La;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t):h>=l?(l>t&&(l=t),t>h&&(h=t)):t>p?a(l,t)>a(l,h)&&(h=t):a(t,h)>a(l,h)&&(l=t)}else n(t,e);m=r,p=t}function e(){_.point=t}function r(){M[0]=l,M[1]=h,_.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=oa(r)>180?r+(r>0?360:-360):r}else v=n,d=e;gc.point(n,e),t(n,e)}function i(){gc.lineStart()}function o(){u(v,d),gc.lineEnd(),oa(y)>Aa&&(l=-(h=180)),M[0]=l,M[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function s(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:n<t[0]||t[1]<n}var l,f,h,g,p,v,d,m,y,x,M,_={point:n,lineStart:e,lineEnd:r,polygonStart:function(){_.point=u,_.lineStart=i,_.lineEnd=o,y=0,gc.polygonStart()},polygonEnd:function(){gc.polygonEnd(),_.point=n,_.lineStart=e,_.lineEnd=r,0>hc?(l=-(h=180),f=-(g=90)):y>Aa?g=90:-Aa>y&&(f=-90),M[0]=l,M[1]=h
|
4 |
+
}};return function(n){g=h=-(l=f=1/0),x=[],Xo.geo.stream(n,_);var t=x.length;if(t){x.sort(c);for(var e,r=1,u=x[0],i=[u];t>r;++r)e=x[r],s(e[0],u)||s(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,l=e[0],h=u[1])}return x=M=null,1/0===l||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[l,f],[h,g]]}}(),Xo.geo.centroid=function(n){pc=vc=dc=mc=yc=xc=Mc=_c=bc=wc=Sc=0,Xo.geo.stream(n,kc);var t=bc,e=wc,r=Sc,u=t*t+e*e+r*r;return Ca>u&&(t=xc,e=Mc,r=_c,Aa>vc&&(t=dc,e=mc,r=yc),u=t*t+e*e+r*r,Ca>u)?[0/0,0/0]:[Math.atan2(e,t)*La,X(r/Math.sqrt(u))*La]};var pc,vc,dc,mc,yc,xc,Mc,_c,bc,wc,Sc,kc={sphere:g,point:me,lineStart:xe,lineEnd:Me,polygonStart:function(){kc.lineStart=_e},polygonEnd:function(){kc.lineStart=xe}},Ec=Ee(be,ze,Te,[-Sa,-Sa/2]),Ac=1e9;Xo.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Pe(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(Xo.geo.conicEqualArea=function(){return je(He)}).raw=He,Xo.geo.albers=function(){return Xo.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},Xo.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=Xo.geo.albers(),o=Xo.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=Xo.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var s=i.scale(),l=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[l-.455*s,f-.238*s],[l+.455*s,f+.238*s]]).stream(c).point,r=o.translate([l-.307*s,f+.201*s]).clipExtent([[l-.425*s+Aa,f+.12*s+Aa],[l-.214*s-Aa,f+.234*s-Aa]]).stream(c).point,u=a.translate([l-.205*s,f+.212*s]).clipExtent([[l-.214*s+Aa,f+.166*s+Aa],[l-.115*s-Aa,f+.234*s-Aa]]).stream(c).point,n},n.scale(1070)};var Cc,Nc,Lc,zc,qc,Tc,Rc={point:g,lineStart:g,lineEnd:g,polygonStart:function(){Nc=0,Rc.lineStart=Fe},polygonEnd:function(){Rc.lineStart=Rc.lineEnd=Rc.point=g,Cc+=oa(Nc/2)}},Dc={point:Oe,lineStart:g,lineEnd:g,polygonStart:g,polygonEnd:g},Pc={point:Ze,lineStart:Ve,lineEnd:Xe,polygonStart:function(){Pc.lineStart=$e},polygonEnd:function(){Pc.point=Ze,Pc.lineStart=Ve,Pc.lineEnd=Xe}};Xo.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),Xo.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Cc=0,Xo.geo.stream(n,u(Rc)),Cc},n.centroid=function(n){return dc=mc=yc=xc=Mc=_c=bc=wc=Sc=0,Xo.geo.stream(n,u(Pc)),Sc?[bc/Sc,wc/Sc]:_c?[xc/_c,Mc/_c]:yc?[dc/yc,mc/yc]:[0/0,0/0]},n.bounds=function(n){return qc=Tc=-(Lc=zc=1/0),Xo.geo.stream(n,u(Dc)),[[Lc,zc],[qc,Tc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||Je(n):bt,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Ye:new Be(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(Xo.geo.albersUsa()).context(null)},Xo.geo.transform=function(n){return{stream:function(t){var e=new Ge(t);for(var r in n)e[r]=n[r];return e}}},Ge.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},Xo.geo.projection=Qe,Xo.geo.projectionMutator=nr,(Xo.geo.equirectangular=function(){return Qe(er)}).raw=er.invert=er,Xo.geo.rotation=function(n){function t(t){return t=n(t[0]*Na,t[1]*Na),t[0]*=La,t[1]*=La,t}return n=ur(n[0]%360*Na,n[1]*Na,n.length>2?n[2]*Na:0),t.invert=function(t){return t=n.invert(t[0]*Na,t[1]*Na),t[0]*=La,t[1]*=La,t},t},rr.invert=er,Xo.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=ur(-n[0]*Na,-n[1]*Na,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=La,n[1]*=La}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=cr((t=+r)*Na,u*Na),n):t},n.precision=function(r){return arguments.length?(e=cr(t*Na,(u=+r)*Na),n):u},n.angle(90)},Xo.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Na,u=n[1]*Na,i=t[1]*Na,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),s=Math.cos(u),l=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=s*l-c*f*a)*e),c*l+s*f*a)},Xo.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return Xo.range(Math.ceil(i/d)*d,u,d).map(h).concat(Xo.range(Math.ceil(s/m)*m,c,m).map(g)).concat(Xo.range(Math.ceil(r/p)*p,e,p).filter(function(n){return oa(n%d)>Aa}).map(l)).concat(Xo.range(Math.ceil(a/v)*v,o,v).filter(function(n){return oa(n%m)>Aa}).map(f))}var e,r,u,i,o,a,c,s,l,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(s).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],s=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),s>c&&(t=s,s=c,c=t),n.precision(y)):[[i,s],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,l=lr(a,o,90),f=fr(r,e,y),h=lr(s,c,90),g=fr(i,u,y),n):y},n.majorExtent([[-180,-90+Aa],[180,90-Aa]]).minorExtent([[-180,-80-Aa],[180,80+Aa]])},Xo.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=hr,u=gr;return n.distance=function(){return Xo.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},Xo.geo.interpolate=function(n,t){return pr(n[0]*Na,n[1]*Na,t[0]*Na,t[1]*Na)},Xo.geo.length=function(n){return Uc=0,Xo.geo.stream(n,jc),Uc};var Uc,jc={sphere:g,point:g,lineStart:vr,lineEnd:g,polygonStart:g,polygonEnd:g},Hc=dr(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(Xo.geo.azimuthalEqualArea=function(){return Qe(Hc)}).raw=Hc;var Fc=dr(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},bt);(Xo.geo.azimuthalEquidistant=function(){return Qe(Fc)}).raw=Fc,(Xo.geo.conicConformal=function(){return je(mr)}).raw=mr,(Xo.geo.conicEquidistant=function(){return je(yr)}).raw=yr;var Oc=dr(function(n){return 1/n},Math.atan);(Xo.geo.gnomonic=function(){return Qe(Oc)}).raw=Oc,xr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ea]},(Xo.geo.mercator=function(){return Mr(xr)}).raw=xr;var Yc=dr(function(){return 1},Math.asin);(Xo.geo.orthographic=function(){return Qe(Yc)}).raw=Yc;var Ic=dr(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(Xo.geo.stereographic=function(){return Qe(Ic)}).raw=Ic,_r.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ea]},(Xo.geo.transverseMercator=function(){var n=Mr(_r),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[-n[1],n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},n.rotate([0,0])}).raw=_r,Xo.geom={},Xo.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=_t(e),i=_t(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(kr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var s=Sr(a),l=Sr(c),f=l[0]===s[0],h=l[l.length-1]===s[s.length-1],g=[];for(t=s.length-1;t>=0;--t)g.push(n[a[s[t]][2]]);for(t=+f;t<l.length-h;++t)g.push(n[a[l[t]][2]]);return g}var e=br,r=wr;return arguments.length?t(n):(t.x=function(n){return arguments.length?(e=n,t):e},t.y=function(n){return arguments.length?(r=n,t):r},t)},Xo.geom.polygon=function(n){return fa(n,Zc),n};var Zc=Xo.geom.polygon.prototype=[];Zc.area=function(){for(var n,t=-1,e=this.length,r=this[e-1],u=0;++t<e;)n=r,r=this[t],u+=n[1]*r[0]-n[0]*r[1];return.5*u},Zc.centroid=function(n){var t,e,r=-1,u=this.length,i=0,o=0,a=this[u-1];for(arguments.length||(n=-1/(6*this.area()));++r<u;)t=a,a=this[r],e=t[0]*a[1]-a[0]*t[1],i+=(t[0]+a[0])*e,o+=(t[1]+a[1])*e;return[i*n,o*n]},Zc.clip=function(n){for(var t,e,r,u,i,o,a=Cr(n),c=-1,s=this.length-Cr(this),l=this[s-1];++c<s;){for(t=n.slice(),n.length=0,u=this[c],i=t[(r=t.length-a)-1],e=-1;++e<r;)o=t[e],Er(o,l,u)?(Er(i,l,u)||n.push(Ar(i,o,l,u)),n.push(o)):Er(i,l,u)&&n.push(Ar(i,o,l,u)),i=o;a&&n.push(n[0]),l=u}return n};var Vc,Xc,$c,Bc,Wc,Jc=[],Gc=[];Pr.prototype.prepare=function(){for(var n,t=this.edges,e=t.length;e--;)n=t[e].edge,n.b&&n.a||t.splice(e,1);return t.sort(jr),t.length},Br.prototype={start:function(){return this.edge.l===this.site?this.edge.a:this.edge.b},end:function(){return this.edge.l===this.site?this.edge.b:this.edge.a}},Wr.prototype={insert:function(n,t){var e,r,u;if(n){if(t.P=n,t.N=n.N,n.N&&(n.N.P=t),n.N=t,n.R){for(n=n.R;n.L;)n=n.L;n.L=t}else n.R=t;e=n}else this._?(n=Qr(this._),t.P=null,t.N=n,n.P=n.L=t,e=n):(t.P=t.N=null,this._=t,e=null);for(t.L=t.R=null,t.U=e,t.C=!0,n=t;e&&e.C;)r=e.U,e===r.L?(u=r.R,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.R&&(Gr(this,e),n=e,e=n.U),e.C=!1,r.C=!0,Kr(this,r))):(u=r.L,u&&u.C?(e.C=u.C=!1,r.C=!0,n=r):(n===e.L&&(Kr(this,e),n=e,e=n.U),e.C=!1,r.C=!0,Gr(this,r))),e=n.U;this._.C=!1},remove:function(n){n.N&&(n.N.P=n.P),n.P&&(n.P.N=n.N),n.N=n.P=null;var t,e,r,u=n.U,i=n.L,o=n.R;if(e=i?o?Qr(o):i:o,u?u.L===n?u.L=e:u.R=e:this._=e,i&&o?(r=e.C,e.C=n.C,e.L=i,i.U=e,e!==o?(u=e.U,e.U=n.U,n=e.R,u.L=n,e.R=o,o.U=e):(e.U=u,u=e,n=e.R)):(r=n.C,n=e),n&&(n.U=u),!r){if(n&&n.C)return n.C=!1,void 0;do{if(n===this._)break;if(n===u.L){if(t=u.R,t.C&&(t.C=!1,u.C=!0,Gr(this,u),t=u.R),t.L&&t.L.C||t.R&&t.R.C){t.R&&t.R.C||(t.L.C=!1,t.C=!0,Kr(this,t),t=u.R),t.C=u.C,u.C=t.R.C=!1,Gr(this,u),n=this._;break}}else if(t=u.L,t.C&&(t.C=!1,u.C=!0,Kr(this,u),t=u.L),t.L&&t.L.C||t.R&&t.R.C){t.L&&t.L.C||(t.R.C=!1,t.C=!0,Gr(this,t),t=u.L),t.C=u.C,u.C=t.L.C=!1,Kr(this,u),n=this._;break}t.C=!0,n=u,u=u.U}while(!n.C);n&&(n.C=!1)}}},Xo.geom.voronoi=function(n){function t(n){var t=new Array(n.length),r=a[0][0],u=a[0][1],i=a[1][0],o=a[1][1];return nu(e(n),a).cells.forEach(function(e,a){var c=e.edges,s=e.site,l=t[a]=c.length?c.map(function(n){var t=n.start();return[t.x,t.y]}):s.x>=r&&s.x<=i&&s.y>=u&&s.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];l.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Aa)*Aa,y:Math.round(o(n,t)/Aa)*Aa,i:t}})}var r=br,u=wr,i=r,o=u,a=Kc;return n?t(n):(t.links=function(n){return nu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return nu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(jr),c=-1,s=a.length,l=a[s-1].edge,f=l.l===o?l.r:l.l;++c<s;)u=l,i=f,l=a[c].edge,f=l.l===o?l.r:l.l,r<i.i&&r<f.i&&eu(o,i,f)<0&&t.push([n[r],n[i.i],n[f.i]])}),t},t.x=function(n){return arguments.length?(i=_t(r=n),t):r},t.y=function(n){return arguments.length?(o=_t(u=n),t):u},t.clipExtent=function(n){return arguments.length?(a=null==n?Kc:n,t):a===Kc?null:a},t.size=function(n){return arguments.length?t.clipExtent(n&&[[0,0],n]):a===Kc?null:a&&a[1]},t)};var Kc=[[-1e6,-1e6],[1e6,1e6]];Xo.geom.delaunay=function(n){return Xo.geom.voronoi().triangles(n)},Xo.geom.quadtree=function(n,t,e,r,u){function i(n){function i(n,t,e,r,u,i,o,a){if(!isNaN(e)&&!isNaN(r))if(n.leaf){var c=n.x,l=n.y;if(null!=c)if(oa(c-e)+oa(l-r)<.01)s(n,t,e,r,u,i,o,a);else{var f=n.point;n.x=n.y=n.point=null,s(n,f,c,l,u,i,o,a),s(n,t,e,r,u,i,o,a)}else n.x=e,n.y=r,n.point=t}else s(n,t,e,r,u,i,o,a)}function s(n,t,e,r,u,o,a,c){var s=.5*(u+a),l=.5*(o+c),f=e>=s,h=r>=l,g=(h<<1)+f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=iu()),f?u=s:a=s,h?o=l:c=l,i(n,t,e,r,u,o,a,c)}var l,f,h,g,p,v,d,m,y,x=_t(a),M=_t(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)l=n[g],l.x<v&&(v=l.x),l.y<d&&(d=l.y),l.x>m&&(m=l.x),l.y>y&&(y=l.y),f.push(l.x),h.push(l.y);else for(g=0;p>g;++g){var _=+x(l=n[g],g),b=+M(l,g);v>_&&(v=_),d>b&&(d=b),_>m&&(m=_),b>y&&(y=b),f.push(_),h.push(b)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=iu();if(k.add=function(n){i(k,n,+x(n,++g),+M(n,g),v,d,m,y)},k.visit=function(n){ou(n,k,v,d,m,y)},g=-1,null==t){for(;++g<p;)i(k,n[g],f[g],h[g],v,d,m,y);--g}else n.forEach(k.add);return f=h=n=l=null,k}var o,a=br,c=wr;return(o=arguments.length)?(a=ru,c=uu,3===o&&(u=e,r=t,e=t=0),i(n)):(i.x=function(n){return arguments.length?(a=n,i):a},i.y=function(n){return arguments.length?(c=n,i):c},i.extent=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=+n[0][0],e=+n[0][1],r=+n[1][0],u=+n[1][1]),i):null==t?null:[[t,e],[r,u]]},i.size=function(n){return arguments.length?(null==n?t=e=r=u=null:(t=e=0,r=+n[0],u=+n[1]),i):null==t?null:[r-t,u-e]},i)},Xo.interpolateRgb=au,Xo.interpolateObject=cu,Xo.interpolateNumber=su,Xo.interpolateString=lu;var Qc=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;Xo.interpolate=fu,Xo.interpolators=[function(n,t){var e=typeof t;return("string"===e?Va.has(t)||/^(#|rgb\(|hsl\()/.test(t)?au:lu:t instanceof G?au:"object"===e?Array.isArray(t)?hu:cu:su)(n,t)}],Xo.interpolateArray=hu;var ns=function(){return bt},ts=Xo.map({linear:ns,poly:xu,quad:function(){return du},cubic:function(){return mu},sin:function(){return Mu},exp:function(){return _u},circle:function(){return bu},elastic:wu,back:Su,bounce:function(){return ku}}),es=Xo.map({"in":bt,out:pu,"in-out":vu,"out-in":function(n){return vu(pu(n))}});Xo.ease=function(n){var t=n.indexOf("-"),e=t>=0?n.substring(0,t):n,r=t>=0?n.substring(t+1):"in";return e=ts.get(e)||ns,r=es.get(r)||bt,gu(r(e.apply(null,$o.call(arguments,1))))},Xo.interpolateHcl=Eu,Xo.interpolateHsl=Au,Xo.interpolateLab=Cu,Xo.interpolateRound=Nu,Xo.transform=function(n){var t=Wo.createElementNS(Xo.ns.prefix.svg,"g");return(Xo.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Lu(e?e.matrix:rs)})(n)},Lu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var rs={a:1,b:0,c:0,d:1,e:0,f:0};Xo.interpolateTransform=Ru,Xo.layout={},Xo.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++e<r;)t.push(Uu(n[e]));return t}},Xo.layout.chord=function(){function n(){var n,s,f,h,g,p={},v=[],d=Xo.range(i),m=[];for(e=[],r=[],n=0,h=-1;++h<i;){for(s=0,g=-1;++g<i;)s+=u[h][g];v.push(s),m.push(Xo.range(i)),n+=s}for(o&&d.sort(function(n,t){return o(v[n],v[t])}),a&&m.forEach(function(n,t){n.sort(function(n,e){return a(u[t][n],u[t][e])})}),n=(ka-l*i)/n,s=0,h=-1;++h<i;){for(f=s,g=-1;++g<i;){var y=d[h],x=m[y][g],M=u[y][x],_=s,b=s+=M*n;p[y+"-"+x]={index:y,subindex:x,startAngle:_,endAngle:b,value:M}}r[y]={index:y,startAngle:f,endAngle:s,value:(s-f)/n},s+=l}for(h=-1;++h<i;)for(g=h-1;++g<i;){var w=p[h+"-"+g],S=p[g+"-"+h];(w.value||S.value)&&e.push(w.value<S.value?{source:S,target:w}:{source:w,target:S})}c&&t()}function t(){e.sort(function(n,t){return c((n.source.value+n.target.value)/2,(t.source.value+t.target.value)/2)})}var e,r,u,i,o,a,c,s={},l=0;return s.matrix=function(n){return arguments.length?(i=(u=n)&&u.length,e=r=null,s):u},s.padding=function(n){return arguments.length?(l=n,e=r=null,s):l},s.sortGroups=function(n){return arguments.length?(o=n,e=r=null,s):o},s.sortSubgroups=function(n){return arguments.length?(a=n,e=null,s):a},s.sortChords=function(n){return arguments.length?(c=n,e&&t(),s):c},s.chords=function(){return e||n(),e},s.groups=function(){return r||n(),r},s},Xo.layout.force=function(){function n(n){return function(t,e,r,u){if(t.point!==n){var i=t.cx-n.x,o=t.cy-n.y,a=u-e,c=i*i+o*o;if(c>a*a/d){if(p>c){var s=t.charge/c;n.px-=i*s,n.py-=o*s}return!0}if(t.point&&c&&p>c){var s=t.pointCharge/c;n.px-=i*s,n.py-=o*s}}return!t.charge}}function t(n){n.px=Xo.event.x,n.py=Xo.event.y,a.resume()}var e,r,u,i,o,a={},c=Xo.dispatch("start","tick","end"),s=[1,1],l=.9,f=us,h=is,g=-30,p=os,v=.1,d=.64,m=[],y=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,x,M,_=m.length,b=y.length;for(e=0;b>e;++e)a=y[e],f=a.source,h=a.target,x=h.x-f.x,M=h.y-f.y,(p=x*x+M*M)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,x*=p,M*=p,h.x-=x*(d=f.weight/(h.weight+f.weight)),h.y-=M*d,f.x+=x*(d=1-d),f.y+=M*d);if((d=r*v)&&(x=s[0]/2,M=s[1]/2,e=-1,d))for(;++e<_;)a=m[e],a.x+=(x-a.x)*d,a.y+=(M-a.y)*d;if(g)for(Zu(t=Xo.geom.quadtree(m),r,o),e=-1;++e<_;)(a=m[e]).fixed||t.visit(n(a));for(e=-1;++e<_;)a=m[e],a.fixed?(a.x=a.px,a.y=a.py):(a.x-=(a.px-(a.px=a.x))*l,a.y-=(a.py-(a.py=a.y))*l);c.tick({type:"tick",alpha:r})},a.nodes=function(n){return arguments.length?(m=n,a):m},a.links=function(n){return arguments.length?(y=n,a):y},a.size=function(n){return arguments.length?(s=n,a):s},a.linkDistance=function(n){return arguments.length?(f="function"==typeof n?n:+n,a):f},a.distance=a.linkDistance,a.linkStrength=function(n){return arguments.length?(h="function"==typeof n?n:+n,a):h},a.friction=function(n){return arguments.length?(l=+n,a):l},a.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,a):g},a.chargeDistance=function(n){return arguments.length?(p=n*n,a):Math.sqrt(p)},a.gravity=function(n){return arguments.length?(v=+n,a):v},a.theta=function(n){return arguments.length?(d=n*n,a):Math.sqrt(d)},a.alpha=function(n){return arguments.length?(n=+n,r?r=n>0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),Xo.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=y[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,s=o.length;++a<s;)if(!isNaN(i=o[a][n]))return i;return Math.random()*r}var t,e,r,c=m.length,l=y.length,p=s[0],v=s[1];for(t=0;c>t;++t)(r=m[t]).index=t,r.weight=0;for(t=0;l>t;++t)r=y[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;l>t;++t)u[t]=+f.call(this,y[t],t);else for(t=0;l>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;l>t;++t)i[t]=+h.call(this,y[t],t);else for(t=0;l>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=Xo.behavior.drag().origin(bt).on("dragstart.force",Fu).on("drag.force",t).on("dragend.force",Ou)),arguments.length?(this.on("mouseover.force",Yu).on("mouseout.force",Iu).call(e),void 0):e},Xo.rebind(a,c,"on")};var us=20,is=1,os=1/0;Xo.layout.hierarchy=function(){function n(t,o,a){var c=u.call(e,t,o);if(t.depth=o,a.push(t),c&&(s=c.length)){for(var s,l,f=-1,h=t.children=new Array(s),g=0,p=o+1;++f<s;)l=h[f]=n(c[f],p,a),l.parent=t,g+=l.value;r&&h.sort(r),i&&(t.value=g)}else delete t.children,i&&(t.value=+i.call(e,t,o)||0);return t}function t(n,r){var u=n.children,o=0;if(u&&(a=u.length))for(var a,c=-1,s=r+1;++c<a;)o+=t(u[c],s);else i&&(o=+i.call(e,n,r)||0);return i&&(n.value=o),o}function e(t){var e=[];return n(t,0,e),e}var r=Bu,u=Xu,i=$u;return e.sort=function(n){return arguments.length?(r=n,e):r},e.children=function(n){return arguments.length?(u=n,e):u},e.value=function(n){return arguments.length?(i=n,e):i},e.revalue=function(n){return t(n,0),n},e},Xo.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,s=-1;for(r=t.value?r/t.value:0;++s<o;)n(a=i[s],e,c=a.value*r,u),e+=c}}function t(n){var e=n.children,r=0;if(e&&(u=e.length))for(var u,i=-1;++i<u;)r=Math.max(r,t(e[i]));return 1+r}function e(e,i){var o=r.call(this,e,i);return n(o[0],0,u[0],u[1]/t(o[0])),o}var r=Xo.layout.hierarchy(),u=[1,1];return e.size=function(n){return arguments.length?(u=n,e):u},Vu(e,r)},Xo.layout.pie=function(){function n(i){var o=i.map(function(e,r){return+t.call(n,e,r)}),a=+("function"==typeof r?r.apply(this,arguments):r),c=(("function"==typeof u?u.apply(this,arguments):u)-a)/Xo.sum(o),s=Xo.range(i.length);null!=e&&s.sort(e===as?function(n,t){return o[t]-o[n]}:function(n,t){return e(i[n],i[t])});var l=[];return s.forEach(function(n){var t;l[n]={data:i[n],value:t=o[n],startAngle:a,endAngle:a+=t*c}}),l}var t=Number,e=as,r=0,u=ka;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n};var as={};Xo.layout.stack=function(){function n(a,c){var s=a.map(function(e,r){return t.call(n,e,r)}),l=s.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,l,c);s=Xo.permute(s,f),l=Xo.permute(l,f);var h,g,p,v=r.call(n,l,c),d=s.length,m=s[0].length;for(g=0;m>g;++g)for(u.call(n,s[0][g],p=v[g],l[0][g][1]),h=1;d>h;++h)u.call(n,s[h][g],p+=l[h-1][g][1],l[h][g][1]);return a}var t=bt,e=Qu,r=ni,u=Ku,i=Ju,o=Gu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:cs.get(t)||Qu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:ss.get(t)||ni,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var cs=Xo.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(ti),i=n.map(ei),o=Xo.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,s=[],l=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],s.push(e)):(c+=i[e],l.push(e));return l.reverse().concat(s)},reverse:function(n){return Xo.range(n.length).reverse()},"default":Qu}),ss=Xo.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,s,l=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=s=0,e=1;h>e;++e){for(t=0,u=0;l>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];l>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,s>c&&(s=c)}for(e=0;h>e;++e)g[e]-=s;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ni});Xo.layout.histogram=function(){function n(n,i){for(var o,a,c=[],s=n.map(e,this),l=r.call(this,s,i),f=u.call(this,l,s,i),i=-1,h=s.length,g=f.length-1,p=t?1:1/h;++i<g;)o=c[i]=[],o.dx=f[i+1]-(o.x=f[i]),o.y=0;if(g>0)for(i=-1;++i<h;)a=s[i],a>=l[0]&&a<=l[1]&&(o=c[Xo.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=oi,u=ui;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=_t(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return ii(n,t)}:_t(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},Xo.layout.tree=function(){function n(n,i){function o(n,t){var r=n.children,u=n._tree;if(r&&(i=r.length)){for(var i,a,s,l=r[0],f=l,h=-1;++h<i;)s=r[h],o(s,a),f=c(s,a,f),a=s;vi(n);var g=.5*(l._tree.prelim+s._tree.prelim);t?(u.prelim=t._tree.prelim+e(n,t),u.mod=u.prelim-g):u.prelim=g}else t&&(u.prelim=t._tree.prelim+e(n,t))}function a(n,t){n.x=n._tree.prelim+t;var e=n.children;if(e&&(r=e.length)){var r,u=-1;for(t+=n._tree.mod;++u<r;)a(e[u],t)}}function c(n,t,r){if(t){for(var u,i=n,o=n,a=t,c=n.parent.children[0],s=i._tree.mod,l=o._tree.mod,f=a._tree.mod,h=c._tree.mod;a=si(a),i=ci(i),a&&i;)c=ci(c),o=si(o),o._tree.ancestor=n,u=a._tree.prelim+f-i._tree.prelim-s+e(a,i),u>0&&(di(mi(a,n,r),n,u),s+=u,l+=u),f+=a._tree.mod,s+=i._tree.mod,h+=c._tree.mod,l+=o._tree.mod;a&&!si(o)&&(o._tree.thread=a,o._tree.mod+=f-l),i&&!ci(c)&&(c._tree.thread=i,c._tree.mod+=s-h,r=n)}return r}var s=t.call(this,n,i),l=s[0];pi(l,function(n,t){n._tree={ancestor:n,prelim:0,mod:0,change:0,shift:0,number:t?t._tree.number+1:0}}),o(l),a(l,-l._tree.prelim);var f=li(l,hi),h=li(l,fi),g=li(l,gi),p=f.x-e(f,h)/2,v=h.x+e(h,f)/2,d=g.depth||1;return pi(l,u?function(n){n.x*=r[0],n.y=n.depth*r[1],delete n._tree}:function(n){n.x=(n.x-p)/(v-p)*r[0],n.y=n.depth/d*r[1],delete n._tree}),s}var t=Xo.layout.hierarchy().sort(null).value(null),e=ai,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Vu(n,t)},Xo.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],s=u[1],l=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,pi(a,function(n){n.r=+l(n.value)}),pi(a,bi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/s))/2;pi(a,function(n){n.r+=f}),pi(a,bi),pi(a,function(n){n.r-=f})}return ki(a,c/2,s/2,t?1:1/Math.max(2*a.r/c,2*a.r/s)),o}var t,e=Xo.layout.hierarchy().sort(yi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Vu(n,e)},Xo.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],s=0;pi(c,function(n){var t=n.children;t&&t.length?(n.x=Ci(t),n.y=Ai(t)):(n.x=o?s+=e(n,o):0,n.y=0,o=n)});var l=Ni(c),f=Li(c),h=l.x-e(l,f)/2,g=f.x+e(f,l)/2;return pi(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=Xo.layout.hierarchy().sort(null).value(null),e=ai,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Vu(n,t)},Xo.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++u<i;)r=(e=n[u]).value*(0>t?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,s=f(e),l=[],h=i.slice(),p=1/0,v="slice"===g?s.dx:"dice"===g?s.dy:"slice-dice"===g?1&e.depth?s.dy:s.dx:Math.min(s.dx,s.dy);for(n(h,s.dx*s.dy/e.value),l.area=0;(c=h.length)>0;)l.push(o=h[c-1]),l.area+=o.area,"squarify"!==g||(a=r(l,v))<=p?(h.pop(),p=a):(l.area-=l.pop().area,u(l,v,s,!1),v=Math.min(s.dx,s.dy),l.length=l.area=0,p=1/0);l.length&&(u(l,v,s,!0),l.length=l.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++o<a;)(e=n[o].area)&&(i>e&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,s=e.y,l=t?c(n.area/t):0;if(t==e.dx){for((r||l>e.dy)&&(l=e.dy);++i<o;)u=n[i],u.x=a,u.y=s,u.dy=l,a+=u.dx=Math.min(e.x+e.dx-a,l?c(u.area/l):0);u.z=!0,u.dx+=e.x+e.dx-a,e.y+=l,e.dy-=l}else{for((r||l>e.dx)&&(l=e.dx);++i<o;)u=n[i],u.x=a,u.y=s,u.dx=l,s+=u.dy=Math.min(e.y+e.dy-s,l?c(u.area/l):0);u.z=!1,u.dy+=e.y+e.dy-s,e.x+=l,e.dx-=l}}function i(r){var u=o||a(r),i=u[0];return i.x=0,i.y=0,i.dx=s[0],i.dy=s[1],o&&a.revalue(i),n([i],i.dx*i.dy/i.value),(o?e:t)(i),h&&(o=u),u}var o,a=Xo.layout.hierarchy(),c=Math.round,s=[1,1],l=null,f=zi,h=!1,g="squarify",p=.5*(1+Math.sqrt(5));return i.size=function(n){return arguments.length?(s=n,i):s},i.padding=function(n){function t(t){var e=n.call(i,t,t.depth);return null==e?zi(t):qi(t,"number"==typeof e?[e,e,e,e]:e)}function e(t){return qi(t,n)}if(!arguments.length)return l;var r;return f=null==(l=n)?zi:"function"==(r=typeof n)?t:"number"===r?(n=[n,n,n,n],e):e,i},i.round=function(n){return arguments.length?(c=n?Math.round:Number,i):c!=Number},i.sticky=function(n){return arguments.length?(h=n,o=null,i):h},i.ratio=function(n){return arguments.length?(p=n,i):p},i.mode=function(n){return arguments.length?(g=n+"",i):g},Vu(i,a)},Xo.random={normal:function(n,t){var e=arguments.length;return 2>e&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=Xo.random.normal.apply(Xo,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=Xo.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},Xo.scale={};var ls={floor:bt,ceil:bt};Xo.scale.linear=function(){return Hi([0,1],[0,1],fu,!1)};var fs={s:1,g:1,p:1,r:1,e:1};Xo.scale.log=function(){return $i(Xo.scale.linear().domain([0,1]),10,!0,[1,10])};var hs=Xo.format(".0e"),gs={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};Xo.scale.pow=function(){return Bi(Xo.scale.linear(),1,[0,1])},Xo.scale.sqrt=function(){return Xo.scale.pow().exponent(.5)},Xo.scale.ordinal=function(){return Ji([],{t:"range",a:[[]]})},Xo.scale.category10=function(){return Xo.scale.ordinal().range(ps)},Xo.scale.category20=function(){return Xo.scale.ordinal().range(vs)},Xo.scale.category20b=function(){return Xo.scale.ordinal().range(ds)},Xo.scale.category20c=function(){return Xo.scale.ordinal().range(ms)};var ps=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(ht),vs=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(ht),ds=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(ht),ms=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(ht);Xo.scale.quantile=function(){return Gi([],[])
|
5 |
+
},Xo.scale.quantize=function(){return Ki(0,1,[0,1])},Xo.scale.threshold=function(){return Qi([.5],[0,1])},Xo.scale.identity=function(){return no([0,1])},Xo.svg={},Xo.svg.arc=function(){function n(){var n=t.apply(this,arguments),i=e.apply(this,arguments),o=r.apply(this,arguments)+ys,a=u.apply(this,arguments)+ys,c=(o>a&&(c=o,o=a,a=c),a-o),s=Sa>c?"0":"1",l=Math.cos(o),f=Math.sin(o),h=Math.cos(a),g=Math.sin(a);return c>=xs?n?"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"M0,"+n+"A"+n+","+n+" 0 1,0 0,"+-n+"A"+n+","+n+" 0 1,0 0,"+n+"Z":"M0,"+i+"A"+i+","+i+" 0 1,1 0,"+-i+"A"+i+","+i+" 0 1,1 0,"+i+"Z":n?"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L"+n*h+","+n*g+"A"+n+","+n+" 0 "+s+",0 "+n*l+","+n*f+"Z":"M"+i*l+","+i*f+"A"+i+","+i+" 0 "+s+",1 "+i*h+","+i*g+"L0,0"+"Z"}var t=to,e=eo,r=ro,u=uo;return n.innerRadius=function(e){return arguments.length?(t=_t(e),n):t},n.outerRadius=function(t){return arguments.length?(e=_t(t),n):e},n.startAngle=function(t){return arguments.length?(r=_t(t),n):r},n.endAngle=function(t){return arguments.length?(u=_t(t),n):u},n.centroid=function(){var n=(t.apply(this,arguments)+e.apply(this,arguments))/2,i=(r.apply(this,arguments)+u.apply(this,arguments))/2+ys;return[Math.cos(i)*n,Math.sin(i)*n]},n};var ys=-Ea,xs=ka-Aa;Xo.svg.line=function(){return io(bt)};var Ms=Xo.map({linear:oo,"linear-closed":ao,step:co,"step-before":so,"step-after":lo,basis:mo,"basis-open":yo,"basis-closed":xo,bundle:Mo,cardinal:go,"cardinal-open":fo,"cardinal-closed":ho,monotone:Eo});Ms.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var _s=[0,2/3,1/3,0],bs=[0,1/3,2/3,0],ws=[0,1/6,2/3,1/6];Xo.svg.line.radial=function(){var n=io(Ao);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},so.reverse=lo,lo.reverse=so,Xo.svg.area=function(){return Co(bt)},Xo.svg.area.radial=function(){var n=Co(Ao);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},Xo.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),s=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,s)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,s.r,s.p0)+r(s.r,s.p1,s.a1-s.a0)+u(s.r,s.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)+ys,l=s.call(n,u,r)+ys;return{r:i,a0:o,a1:l,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(l),i*Math.sin(l)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Sa)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=hr,o=gr,a=No,c=ro,s=uo;return n.radius=function(t){return arguments.length?(a=_t(t),n):a},n.source=function(t){return arguments.length?(i=_t(t),n):i},n.target=function(t){return arguments.length?(o=_t(t),n):o},n.startAngle=function(t){return arguments.length?(c=_t(t),n):c},n.endAngle=function(t){return arguments.length?(s=_t(t),n):s},n},Xo.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=hr,e=gr,r=Lo;return n.source=function(e){return arguments.length?(t=_t(e),n):t},n.target=function(t){return arguments.length?(e=_t(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},Xo.svg.diagonal.radial=function(){var n=Xo.svg.diagonal(),t=Lo,e=n.projection;return n.projection=function(n){return arguments.length?e(zo(t=n)):t},n},Xo.svg.symbol=function(){function n(n,r){return(Ss.get(t.call(this,n,r))||Ro)(e.call(this,n,r))}var t=To,e=qo;return n.type=function(e){return arguments.length?(t=_t(e),n):t},n.size=function(t){return arguments.length?(e=_t(t),n):e},n};var Ss=Xo.map({circle:Ro,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Cs)),e=t*Cs;return"M0,"+-t+"L"+e+",0"+" 0,"+t+" "+-e+",0"+"Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/As),e=t*As/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/As),e=t*As/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});Xo.svg.symbolTypes=Ss.keys();var ks,Es,As=Math.sqrt(3),Cs=Math.tan(30*Na),Ns=[],Ls=0;Ns.call=da.call,Ns.empty=da.empty,Ns.node=da.node,Ns.size=da.size,Xo.transition=function(n){return arguments.length?ks?n.transition():n:xa.transition()},Xo.transition.prototype=Ns,Ns.select=function(n){var t,e,r,u=this.id,i=[];n=M(n);for(var o=-1,a=this.length;++o<a;){i.push(t=[]);for(var c=this[o],s=-1,l=c.length;++s<l;)(r=c[s])&&(e=n.call(r,r.__data__,s,o))?("__data__"in r&&(e.__data__=r.__data__),jo(e,s,u,r.__transition__[u]),t.push(e)):t.push(null)}return Do(i,u)},Ns.selectAll=function(n){var t,e,r,u,i,o=this.id,a=[];n=_(n);for(var c=-1,s=this.length;++c<s;)for(var l=this[c],f=-1,h=l.length;++f<h;)if(r=l[f]){i=r.__transition__[o],e=n.call(r,r.__data__,f,c),a.push(t=[]);for(var g=-1,p=e.length;++g<p;)(u=e[g])&&jo(u,g,o,i),t.push(u)}return Do(a,o)},Ns.filter=function(n){var t,e,r,u=[];"function"!=typeof n&&(n=q(n));for(var i=0,o=this.length;o>i;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Do(u,this.id)},Ns.tween=function(n,t){var e=this.id;return arguments.length<2?this.node().__transition__[e].tween.get(n):R(this,null==t?function(t){t.__transition__[e].tween.remove(n)}:function(r){r.__transition__[e].tween.set(n,t)})},Ns.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Ru:fu,a=Xo.ns.qualify(n);return Po(this,"attr."+n,t,a.local?i:u)},Ns.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=Xo.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Ns.style=function(n,t,e){function r(){this.style.removeProperty(n)}function u(t){return null==t?r:(t+="",function(){var r,u=Go.getComputedStyle(this,null).getPropertyValue(n);return u!==t&&(r=fu(u,t),function(t){this.style.setProperty(n,r(t),e)})})}var i=arguments.length;if(3>i){if("string"!=typeof n){2>i&&(t="");for(e in n)this.style(e,n[e],t);return this}e=""}return Po(this,"style."+n,t,u)},Ns.styleTween=function(n,t,e){function r(r,u){var i=t.call(this,r,u,Go.getComputedStyle(this,null).getPropertyValue(n));return i&&function(t){this.style.setProperty(n,i(t),e)}}return arguments.length<3&&(e=""),this.tween("style."+n,r)},Ns.text=function(n){return Po(this,"text",n,Uo)},Ns.remove=function(){return this.each("end.transition",function(){var n;this.__transition__.count<2&&(n=this.parentNode)&&n.removeChild(this)})},Ns.ease=function(n){var t=this.id;return arguments.length<1?this.node().__transition__[t].ease:("function"!=typeof n&&(n=Xo.ease.apply(Xo,arguments)),R(this,function(e){e.__transition__[t].ease=n}))},Ns.delay=function(n){var t=this.id;return R(this,"function"==typeof n?function(e,r,u){e.__transition__[t].delay=+n.call(e,e.__data__,r,u)}:(n=+n,function(e){e.__transition__[t].delay=n}))},Ns.duration=function(n){var t=this.id;return R(this,"function"==typeof n?function(e,r,u){e.__transition__[t].duration=Math.max(1,n.call(e,e.__data__,r,u))}:(n=Math.max(1,n),function(e){e.__transition__[t].duration=n}))},Ns.each=function(n,t){var e=this.id;if(arguments.length<2){var r=Es,u=ks;ks=e,R(this,function(t,r,u){Es=t.__transition__[e],n.call(t,t.__data__,r,u)}),Es=r,ks=u}else R(this,function(r){var u=r.__transition__[e];(u.event||(u.event=Xo.dispatch("start","end"))).on(n,t)});return this},Ns.transition=function(){for(var n,t,e,r,u=this.id,i=++Ls,o=[],a=0,c=this.length;c>a;a++){o.push(n=[]);for(var t=this[a],s=0,l=t.length;l>s;s++)(e=t[s])&&(r=Object.create(e.__transition__[u]),r.delay+=r.duration,jo(e,s,i,r)),n.push(e)}return Do(o,i)},Xo.svg.axis=function(){function n(n){n.each(function(){var n,s=Xo.select(this),l=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):bt:t,p=s.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Aa),d=Xo.transition(p.exit()).style("opacity",Aa).remove(),m=Xo.transition(p).style("opacity",1),y=Ri(f),x=s.selectAll(".domain").data([0]),M=(x.enter().append("path").attr("class","domain"),Xo.transition(x));v.append("line"),v.append("text");var _=v.select("line"),b=m.select("line"),w=p.select("text").text(g),S=v.select("text"),k=m.select("text");switch(r){case"bottom":n=Ho,_.attr("y2",u),S.attr("y",Math.max(u,0)+o),b.attr("x2",0).attr("y2",u),k.attr("x",0).attr("y",Math.max(u,0)+o),w.attr("dy",".71em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+i+"V0H"+y[1]+"V"+i);break;case"top":n=Ho,_.attr("y2",-u),S.attr("y",-(Math.max(u,0)+o)),b.attr("x2",0).attr("y2",-u),k.attr("x",0).attr("y",-(Math.max(u,0)+o)),w.attr("dy","0em").style("text-anchor","middle"),M.attr("d","M"+y[0]+","+-i+"V0H"+y[1]+"V"+-i);break;case"left":n=Fo,_.attr("x2",-u),S.attr("x",-(Math.max(u,0)+o)),b.attr("x2",-u).attr("y2",0),k.attr("x",-(Math.max(u,0)+o)).attr("y",0),w.attr("dy",".32em").style("text-anchor","end"),M.attr("d","M"+-i+","+y[0]+"H0V"+y[1]+"H"+-i);break;case"right":n=Fo,_.attr("x2",u),S.attr("x",Math.max(u,0)+o),b.attr("x2",u).attr("y2",0),k.attr("x",Math.max(u,0)+o).attr("y",0),w.attr("dy",".32em").style("text-anchor","start"),M.attr("d","M"+i+","+y[0]+"H0V"+y[1]+"H"+i)}if(f.rangeBand){var E=f,A=E.rangeBand()/2;l=f=function(n){return E(n)+A}}else l.rangeBand?l=f:d.call(n,f);v.call(n,l),m.call(n,f)})}var t,e=Xo.scale.linear(),r=zs,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in qs?t+"":zs,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var zs="bottom",qs={top:1,right:1,bottom:1,left:1};Xo.svg.brush=function(){function n(i){i.each(function(){var i=Xo.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=i.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),i.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=i.selectAll(".resize").data(p,bt);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Ts[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,f=Xo.transition(i),h=Xo.transition(o);c&&(l=Ri(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),e(f)),s&&(l=Ri(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),r(f)),t(f)})}function t(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+l[+/e$/.test(n)]+","+f[+/^s/.test(n)]+")"})}function e(n){n.select(".extent").attr("x",l[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",l[1]-l[0])}function r(n){n.select(".extent").attr("y",f[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",f[1]-f[0])}function u(){function u(){32==Xo.event.keyCode&&(C||(x=null,L[0]-=l[1],L[1]-=f[1],C=2),d())}function p(){32==Xo.event.keyCode&&2==C&&(L[0]+=l[1],L[1]+=f[1],C=0,d())}function v(){var n=Xo.mouse(_),u=!1;M&&(n[0]+=M[0],n[1]+=M[1]),C||(Xo.event.altKey?(x||(x=[(l[0]+l[1])/2,(f[0]+f[1])/2]),L[0]=l[+(n[0]<x[0])],L[1]=f[+(n[1]<x[1])]):x=null),E&&m(n,c,0)&&(e(S),u=!0),A&&m(n,s,1)&&(r(S),u=!0),u&&(t(S),w({type:"brush",mode:C?"move":"resize"}))}function m(n,t,e){var r,u,a=Ri(t),c=a[0],s=a[1],p=L[e],v=e?f:l,d=v[1]-v[0];return C&&(c-=p,s-=d+p),r=(e?g:h)?Math.max(c,Math.min(s,n[e])):n[e],C?u=(r+=p)+d:(x&&(p=Math.max(c,Math.min(s,2*x[e]-r))),r>p?(u=r,r=p):u=p),v[0]!=r||v[1]!=u?(e?o=null:i=null,v[0]=r,v[1]=u,!0):void 0}function y(){v(),S.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),Xo.select("body").style("cursor",null),z.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),N(),w({type:"brushend"})}var x,M,_=this,b=Xo.select(Xo.event.target),w=a.of(_,arguments),S=Xo.select(_),k=b.datum(),E=!/^(n|s)$/.test(k)&&c,A=!/^(e|w)$/.test(k)&&s,C=b.classed("extent"),N=O(),L=Xo.mouse(_),z=Xo.select(Go).on("keydown.brush",u).on("keyup.brush",p);if(Xo.event.changedTouches?z.on("touchmove.brush",v).on("touchend.brush",y):z.on("mousemove.brush",v).on("mouseup.brush",y),S.interrupt().selectAll("*").interrupt(),C)L[0]=l[0]-L[0],L[1]=f[0]-L[1];else if(k){var q=+/w$/.test(k),T=+/^n/.test(k);M=[l[1-q]-L[0],f[1-T]-L[1]],L[0]=l[q],L[1]=f[T]}else Xo.event.altKey&&(x=L.slice());S.style("pointer-events","none").selectAll(".resize").style("display",null),Xo.select("body").style("cursor",b.style("cursor")),w({type:"brushstart"}),v()}var i,o,a=y(n,"brushstart","brush","brushend"),c=null,s=null,l=[0,0],f=[0,0],h=!0,g=!0,p=Rs[0];return n.event=function(n){n.each(function(){var n=a.of(this,arguments),t={x:l,y:f,i:i,j:o},e=this.__chart__||t;this.__chart__=t,ks?Xo.select(this).transition().each("start.brush",function(){i=e.i,o=e.j,l=e.x,f=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=hu(l,t.x),r=hu(f,t.y);return i=o=null,function(u){l=t.x=e(u),f=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){i=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,p=Rs[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,p=Rs[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(h=!!t[0],g=!!t[1]):c?h=!!t:s&&(g=!!t),n):c&&s?[h,g]:c?h:s?g:null},n.extent=function(t){var e,r,u,a,h;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),i=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(h=e,e=r,r=h),(e!=l[0]||r!=l[1])&&(l=[e,r])),s&&(u=t[0],a=t[1],c&&(u=u[1],a=a[1]),o=[u,a],s.invert&&(u=s(u),a=s(a)),u>a&&(h=u,u=a,a=h),(u!=f[0]||a!=f[1])&&(f=[u,a])),n):(c&&(i?(e=i[0],r=i[1]):(e=l[0],r=l[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(h=e,e=r,r=h))),s&&(o?(u=o[0],a=o[1]):(u=f[0],a=f[1],s.invert&&(u=s.invert(u),a=s.invert(a)),u>a&&(h=u,u=a,a=h))),c&&s?[[e,u],[r,a]]:c?[e,r]:s&&[u,a])},n.clear=function(){return n.empty()||(l=[0,0],f=[0,0],i=o=null),n},n.empty=function(){return!!c&&l[0]==l[1]||!!s&&f[0]==f[1]},Xo.rebind(n,a,"on")};var Ts={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Rs=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Ds=tc.format=ac.timeFormat,Ps=Ds.utc,Us=Ps("%Y-%m-%dT%H:%M:%S.%LZ");Ds.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Oo:Us,Oo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Oo.toString=Us.toString,tc.second=Rt(function(n){return new ec(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),tc.seconds=tc.second.range,tc.seconds.utc=tc.second.utc.range,tc.minute=Rt(function(n){return new ec(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),tc.minutes=tc.minute.range,tc.minutes.utc=tc.minute.utc.range,tc.hour=Rt(function(n){var t=n.getTimezoneOffset()/60;return new ec(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),tc.hours=tc.hour.range,tc.hours.utc=tc.hour.utc.range,tc.month=Rt(function(n){return n=tc.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),tc.months=tc.month.range,tc.months.utc=tc.month.utc.range;var js=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Hs=[[tc.second,1],[tc.second,5],[tc.second,15],[tc.second,30],[tc.minute,1],[tc.minute,5],[tc.minute,15],[tc.minute,30],[tc.hour,1],[tc.hour,3],[tc.hour,6],[tc.hour,12],[tc.day,1],[tc.day,2],[tc.week,1],[tc.month,1],[tc.month,3],[tc.year,1]],Fs=Ds.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",be]]),Os={range:function(n,t,e){return Xo.range(+n,+t,e).map(Io)},floor:bt,ceil:bt};Hs.year=tc.year,tc.scale=function(){return Yo(Xo.scale.linear(),Hs,Fs)};var Ys=Hs.map(function(n){return[n[0].utc,n[1]]}),Is=Ps.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",be]]);Ys.year=tc.year.utc,tc.scale.utc=function(){return Yo(Xo.scale.linear(),Ys,Is)},Xo.text=wt(function(n){return n.responseText}),Xo.json=function(n,t){return St(n,"application/json",Zo,t)},Xo.html=function(n,t){return St(n,"text/html",Vo,t)},Xo.xml=wt(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(Xo):"object"==typeof module&&module.exports?module.exports=Xo:this.d3=Xo}();
|
extensions/reports/ui/lib/nvd3/nv.d3.min.css
ADDED
@@ -0,0 +1 @@
|
|
|
1 |
+
.chartWrap{margin:0;padding:0;overflow:hidden}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 250ms linear;-moz-transition:opacity 250ms linear;-webkit-transition:opacity 250ms linear;transition-delay:250ms;-moz-transition-delay:250ms;-webkit-transition-delay:250ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{position:absolute;pointer-events:none}svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}svg text{font:400 12px Arial}svg .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .disabled circle{fill-opacity:0}.nvd3 .nv-axis{pointer-events:none}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-bars .negative rect{zfill:brown}.nvd3 .nv-bars rect{zfill:#4682b4;fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups path.nv-line{fill:none;stroke-width:1.5px}.nvd3 .nv-groups path.nv-line.nv-thin-line{stroke-width:1px}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3 .nv-line.hover path{stroke-width:6px}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}.nvd3 .nv-distribution{pointer-events:none}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:4px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel{font-weight:700}.nvd3.nv-historicalStockChart .nv-dragTarget{fill-opacity:0;stroke:none;cursor:move}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-indentedtree .name{margin-left:5px}.nvd3.nv-indentedtree .clickable{color:#08C;cursor:pointer}.nvd3.nv-indentedtree span.clickable:hover{color:#005580;text-decoration:underline}.nvd3.nv-indentedtree .nv-childrenCount{display:inline-block;margin-left:5px}.nvd3.nv-indentedtree .nv-treeicon{cursor:pointer}.nvd3.nv-indentedtree .nv-treeicon.nv-folded{cursor:pointer}.nvd3 .background path{fill:none;stroke:#ccc;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke:#4682b4;stroke-opacity:.7}.nvd3 .brush .extent{fill-opacity:.3;stroke:#fff;shape-rendering:crispEdges}.nvd3 .axis line,.axis path{fill:none;stroke:#000;shape-rendering:crispEdges}.nvd3 .axis text{text-shadow:0 1px 0 #fff}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}
|
extensions/reports/ui/lib/nvd3/nv.d3.min.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function(){function t(e,t){return(new Date(t,e+1,0)).getDate()}function n(e,t,n){return function(r,i,s){var o=e(r),u=[];o<r&&t(o);if(s>1)while(o<i){var a=new Date(+o);n(a)%s===0&&u.push(a),t(o)}else while(o<i)u.push(new Date(+o)),t(o);return u}}var e=window.nv||{};e.version="1.1.15b",e.dev=!0,window.nv=e,e.tooltip=e.tooltip||{},e.utils=e.utils||{},e.models=e.models||{},e.charts={},e.graphs=[],e.logs={},e.dispatch=d3.dispatch("render_start","render_end"),e.dev&&(e.dispatch.on("render_start",function(t){e.logs.startTime=+(new Date)}),e.dispatch.on("render_end",function(t){e.logs.endTime=+(new Date),e.logs.totalTime=e.logs.endTime-e.logs.startTime,e.log("total",e.logs.totalTime)})),e.log=function(){if(e.dev&&console.log&&console.log.apply)console.log.apply(console,arguments);else if(e.dev&&typeof console.log=="function"&&Function.prototype.bind){var t=Function.prototype.bind.call(console.log,console);t.apply(console,arguments)}return arguments[arguments.length-1]},e.render=function(n){n=n||1,e.render.active=!0,e.dispatch.render_start(),setTimeout(function(){var t,r;for(var i=0;i<n&&(r=e.render.queue[i]);i++)t=r.generate(),typeof r.callback==typeof Function&&r.callback(t),e.graphs.push(t);e.render.queue.splice(0,i),e.render.queue.length?setTimeout(arguments.callee,0):(e.dispatch.render_end(),e.render.active=!1)},0)},e.render.active=!1,e.render.queue=[],e.addGraph=function(t){typeof arguments[0]==typeof Function&&(t={generate:arguments[0],callback:arguments[1]}),e.render.queue.push(t),e.render.active||e.render()},e.identity=function(e){return e},e.strip=function(e){return e.replace(/(\s|&)/g,"")},d3.time.monthEnd=function(e){return new Date(e.getFullYear(),e.getMonth(),0)},d3.time.monthEnds=n(d3.time.monthEnd,function(e){e.setUTCDate(e.getUTCDate()+1),e.setDate(t(e.getMonth()+1,e.getFullYear()))},function(e){return e.getMonth()}),e.interactiveGuideline=function(){"use strict";function c(o){o.each(function(o){function g(){var e=d3.mouse(this),n=e[0],r=e[1],o=!0,a=!1;l&&(n=d3.event.offsetX,r=d3.event.offsetY,d3.event.target.tagName!=="svg"&&(o=!1),d3.event.target.className.baseVal.match("nv-legend")&&(a=!0)),o&&(n-=i.left,r-=i.top);if(n<0||r<0||n>p||r>d||d3.event.relatedTarget&&d3.event.relatedTarget.ownerSVGElement===undefined||a){if(l&&d3.event.relatedTarget&&d3.event.relatedTarget.ownerSVGElement===undefined&&d3.event.relatedTarget.className.match(t.nvPointerEventsClass))return;u.elementMouseout({mouseX:n,mouseY:r}),c.renderGuideLine(null);return}var f=s.invert(n);u.elementMousemove({mouseX:n,mouseY:r,pointXValue:f}),d3.event.type==="dblclick"&&u.elementDblclick({mouseX:n,mouseY:r,pointXValue:f})}var h=d3.select(this),p=n||960,d=r||400,v=h.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([o]),m=v.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");m.append("g").attr("class","nv-interactiveGuideLine");if(!f)return;f.on("mousemove",g,!0).on("mouseout",g,!0).on("dblclick",g),c.renderGuideLine=function(t){if(!a)return;var n=v.select(".nv-interactiveGuideLine").selectAll("line").data(t!=null?[e.utils.NaNtoZero(t)]:[],String);n.enter().append("line").attr("class","nv-guideline").attr("x1",function(e){return e}).attr("x2",function(e){return e}).attr("y1",d).attr("y2",0),n.exit().remove()}})}var t=e.models.tooltip(),n=null,r=null,i={left:0,top:0},s=d3.scale.linear(),o=d3.scale.linear(),u=d3.dispatch("elementMousemove","elementMouseout","elementDblclick"),a=!0,f=null,l=navigator.userAgent.indexOf("MSIE")!==-1;return c.dispatch=u,c.tooltip=t,c.margin=function(e){return arguments.length?(i.top=typeof e.top!="undefined"?e.top:i.top,i.left=typeof e.left!="undefined"?e.left:i.left,c):i},c.width=function(e){return arguments.length?(n=e,c):n},c.height=function(e){return arguments.length?(r=e,c):r},c.xScale=function(e){return arguments.length?(s=e,c):s},c.showGuideLine=function(e){return arguments.length?(a=e,c):a},c.svgContainer=function(e){return arguments.length?(f=e,c):f},c},e.interactiveBisect=function(e,t,n){"use strict";if(!e instanceof Array)return null;typeof n!="function"&&(n=function(e,t){return e.x});var r=d3.bisector(n).left,i=d3.max([0,r(e,t)-1]),s=n(e[i],i);typeof s=="undefined"&&(s=i);if(s===t)return i;var o=d3.min([i+1,e.length-1]),u=n(e[o],o);return typeof u=="undefined"&&(u=o),Math.abs(u-t)>=Math.abs(s-t)?i:o},e.nearestValueIndex=function(e,t,n){"use strict";var r=Infinity,i=null;return e.forEach(function(e,s){var o=Math.abs(t-e);o<=r&&o<n&&(r=o,i=s)}),i},function(){"use strict";window.nv.tooltip={},window.nv.models.tooltip=function(){function y(){if(a){var e=d3.select(a);e.node().tagName!=="svg"&&(e=e.select("svg"));var t=e.node()?e.attr("viewBox"):null;if(t){t=t.split(" ");var n=parseInt(e.style("width"))/t[2];l.left=l.left*n,l.top=l.top*n}}}function b(e){var t;a?t=d3.select(a):t=d3.select("body");var n=t.select(".nvtooltip");return n.node()===null&&(n=t.append("div").attr("class","nvtooltip "+(u?u:"xy-tooltip")).attr("id",h)),n.node().innerHTML=e,n.style("top",0).style("left",0).style("opacity",0),n.selectAll("div, table, td, tr").classed(p,!0),n.classed(p,!0),n.node()}function w(){if(!c)return;if(!g(n))return;y();var t=l.left,u=o!=null?o:l.top,h=b(m(n));f=h;if(a){var p=a.getElementsByTagName("svg")[0],d=p?p.getBoundingClientRect():a.getBoundingClientRect(),v={left:0,top:0};if(p){var E=p.getBoundingClientRect(),S=a.getBoundingClientRect(),x=E.top;if(x<0){var T=a.getBoundingClientRect();x=Math.abs(x)>T.height?0:x}v.top=Math.abs(x-S.top),v.left=Math.abs(E.left-S.left)}t+=a.offsetLeft+v.left-2*a.scrollLeft,u+=a.offsetTop+v.top-2*a.scrollTop}return s&&s>0&&(u=Math.floor(u/s)*s),e.tooltip.calcTooltipPosition([t,u],r,i,h),w}var t=null,n=null,r="w",i=50,s=25,o=null,u=null,a=null,f=null,l={left:null,top:null},c=!0,h="nvtooltip-"+Math.floor(Math.random()*1e5),p="nv-pointer-events-none",d=function(e,t){return e},v=function(e){return e},m=function(e){if(t!=null)return t;if(e==null)return"";var n=d3.select(document.createElement("table")),r=n.selectAll("thead").data([e]).enter().append("thead");r.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(v(e.value));var i=n.selectAll("tbody").data([e]).enter().append("tbody"),s=i.selectAll("tr").data(function(e){return e.series}).enter().append("tr").classed("highlight",function(e){return e.highlight});s.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(e){return e.color}),s.append("td").classed("key",!0).html(function(e){return e.key}),s.append("td").classed("value",!0).html(function(e,t){return d(e.value,t)}),s.selectAll("td").each(function(e){if(e.highlight){var t=d3.scale.linear().domain([0,1]).range(["#fff",e.color]),n=.6;d3.select(this).style("border-bottom-color",t(n)).style("border-top-color",t(n))}});var o=n.node().outerHTML;return e.footer!==undefined&&(o+="<div class='footer'>"+e.footer+"</div>"),o},g=function(e){return e&&e.series&&e.series.length>0?!0:!1};return w.nvPointerEventsClass=p,w.content=function(e){return arguments.length?(t=e,w):t},w.tooltipElem=function(){return f},w.contentGenerator=function(e){return arguments.length?(typeof e=="function"&&(m=e),w):m},w.data=function(e){return arguments.length?(n=e,w):n},w.gravity=function(e){return arguments.length?(r=e,w):r},w.distance=function(e){return arguments.length?(i=e,w):i},w.snapDistance=function(e){return arguments.length?(s=e,w):s},w.classes=function(e){return arguments.length?(u=e,w):u},w.chartContainer=function(e){return arguments.length?(a=e,w):a},w.position=function(e){return arguments.length?(l.left=typeof e.left!="undefined"?e.left:l.left,l.top=typeof e.top!="undefined"?e.top:l.top,w):l},w.fixedTop=function(e){return arguments.length?(o=e,w):o},w.enabled=function(e){return arguments.length?(c=e,w):c},w.valueFormatter=function(e){return arguments.length?(typeof e=="function"&&(d=e),w):d},w.headerFormatter=function(e){return arguments.length?(typeof e=="function"&&(v=e),w):v},w.id=function(){return h},w},e.tooltip.show=function(t,n,r,i,s,o){var u=document.createElement("div");u.className="nvtooltip "+(o?o:"xy-tooltip");var a=s;if(!s||s.tagName.match(/g|svg/i))a=document.getElementsByTagName("body")[0];u.style.left=0,u.style.top=0,u.style.opacity=0,u.innerHTML=n,a.appendChild(u),s&&(t[0]=t[0]-s.scrollLeft,t[1]=t[1]-s.scrollTop),e.tooltip.calcTooltipPosition(t,r,i,u)},e.tooltip.findFirstNonSVGParent=function(e){while(e.tagName.match(/^g|svg$/i)!==null)e=e.parentNode;return e},e.tooltip.findTotalOffsetTop=function(e,t){var n=t;do isNaN(e.offsetTop)||(n+=e.offsetTop);while(e=e.offsetParent);return n},e.tooltip.findTotalOffsetLeft=function(e,t){var n=t;do isNaN(e.offsetLeft)||(n+=e.offsetLeft);while(e=e.offsetParent);return n},e.tooltip.calcTooltipPosition=function(t,n,r,i){var s=parseInt(i.offsetHeight),o=parseInt(i.offsetWidth),u=e.utils.windowSize().width,a=e.utils.windowSize().height,f=window.pageYOffset,l=window.pageXOffset,c,h;a=window.innerWidth>=document.body.scrollWidth?a:a-16,u=window.innerHeight>=document.body.scrollHeight?u:u-16,n=n||"s",r=r||20;var p=function(t){return e.tooltip.findTotalOffsetTop(t,h)},d=function(t){return e.tooltip.findTotalOffsetLeft(t,c)};switch(n){case"e":c=t[0]-o-r,h=t[1]-s/2;var v=d(i),m=p(i);v<l&&(c=t[0]+r>l?t[0]+r:l-v+c),m<f&&(h=f-m+h),m+s>f+a&&(h=f+a-m+h-s);break;case"w":c=t[0]+r,h=t[1]-s/2;var v=d(i),m=p(i);v+o>u&&(c=t[0]-o-r),m<f&&(h=f+5),m+s>f+a&&(h=f+a-m+h-s);break;case"n":c=t[0]-o/2-5,h=t[1]+r;var v=d(i),m=p(i);v<l&&(c=l+5),v+o>u&&(c=c-o/2+5),m+s>f+a&&(h=f+a-m+h-s);break;case"s":c=t[0]-o/2,h=t[1]-s-r;var v=d(i),m=p(i);v<l&&(c=l+5),v+o>u&&(c=c-o/2+5),f>m&&(h=f);break;case"none":c=t[0],h=t[1]-r;var v=d(i),m=p(i)}return i.style.left=c+"px",i.style.top=h+"px",i.style.opacity=1,i.style.position="absolute",i},e.tooltip.cleanup=function(){var e=document.getElementsByClassName("nvtooltip"),t=[];while(e.length)t.push(e[0]),e[0].style.transitionDelay="0 !important",e[0].style.opacity=0,e[0].className="nvtooltip-pending-removal";setTimeout(function(){while(t.length){var e=t.pop();e.parentNode.removeChild(e)}},500)}}(),e.utils.windowSize=function(){var e={width:640,height:480};return document.body&&document.body.offsetWidth&&(e.width=document.body.offsetWidth,e.height=document.body.offsetHeight),document.compatMode=="CSS1Compat"&&document.documentElement&&document.documentElement.offsetWidth&&(e.width=document.documentElement.offsetWidth,e.height=document.documentElement.offsetHeight),window.innerWidth&&window.innerHeight&&(e.width=window.innerWidth,e.height=window.innerHeight),e},e.utils.windowResize=function(e){if(e===undefined)return;var t=window.onresize;window.onresize=function(n){typeof t=="function"&&t(n),e(n)}},e.utils.getColor=function(t){return arguments.length?Object.prototype.toString.call(t)==="[object Array]"?function(e,n){return e.color||t[n%t.length]}:t:e.utils.defaultColor()},e.utils.defaultColor=function(){var e=d3.scale.category20().range();return function(t,n){return t.color||e[n%e.length]}},e.utils.customTheme=function(e,t,n){t=t||function(e){return e.key},n=n||d3.scale.category20().range();var r=n.length;return function(i,s){var o=t(i);return r||(r=n.length),typeof e[o]!="undefined"?typeof e[o]=="function"?e[o]():e[o]:n[--r]}},e.utils.pjax=function(t,n){function r(r){d3.html(r,function(r){var i=d3.select(n).node();i.parentNode.replaceChild(d3.select(r).select(n).node(),i),e.utils.pjax(t,n)})}d3.selectAll(t).on("click",function(){history.pushState(this.href,this.textContent,this.href),r(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&r(d3.event.state)})},e.utils.calcApproxTextWidth=function(e){if(typeof e.style=="function"&&typeof e.text=="function"){var t=parseInt(e.style("font-size").replace("px","")),n=e.text().length;return n*t*.5}return 0},e.utils.NaNtoZero=function(e){return typeof e!="number"||isNaN(e)||e===null||e===Infinity?0:e},e.utils.optionsFunc=function(e){return e&&d3.map(e).forEach(function(e,t){typeof this[e]=="function"&&this[e](t)}.bind(this)),this},e.models.axis=function(){"use strict";function m(e){return e.each(function(e){var i=d3.select(this),m=i.selectAll("g.nv-wrap.nv-axis").data([e]),g=m.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),y=g.append("g"),b=m.select("g");p!==null?t.ticks(p):(t.orient()=="top"||t.orient()=="bottom")&&t.ticks(Math.abs(s.range()[1]-s.range()[0])/100),b.transition().call(t),v=v||t.scale();var w=t.tickFormat();w==null&&(w=v.tickFormat());var E=b.selectAll("text.nv-axislabel").data([o||null]);E.exit().remove();switch(t.orient()){case"top":E.enter().append("text").attr("class","nv-axislabel");var S=s.range().length==2?s.range()[1]:s.range()[s.range().length-1]+(s.range()[1]-s.range()[0]);E.attr("text-anchor","middle").attr("y",0).attr("x",S/2);if(u){var x=m.selectAll("g.nv-axisMaxMin").data(s.domain());x.enter().append("g").attr("class","nv-axisMaxMin").append("text"),x.exit().remove(),x.attr("transform",function(e,t){return"translate("+s(e)+",0)"}).select("text").attr("dy","-0.5em").attr("y",-t.tickPadding()).attr("text-anchor","middle").text(function(e,t){var n=w(e);return(""+n).match("NaN")?"":n}),x.transition().attr("transform",function(e,t){return"translate("+s.range()[t]+",0)"})}break;case"bottom":var T=36,N=30,C=b.selectAll("g").select("text");if(f%360){C.each(function(e,t){var n=this.getBBox().width;n>N&&(N=n)});var k=Math.abs(Math.sin(f*Math.PI/180)),T=(k?k*N:N)+30;C.attr("transform",function(e,t,n){return"rotate("+f+" 0,0)"}).style("text-anchor",f%360>0?"start":"end")}E.enter().append("text").attr("class","nv-axislabel");var S=s.range().length==2?s.range()[1]:s.range()[s.range().length-1]+(s.range()[1]-s.range()[0]);E.attr("text-anchor","middle").attr("y",T).attr("x",S/2);if(u){var x=m.selectAll("g.nv-axisMaxMin").data([s.domain()[0],s.domain()[s.domain().length-1]]);x.enter().append("g").attr("class","nv-axisMaxMin").append("text"),x.exit().remove(),x.attr("transform",function(e,t){return"translate("+(s(e)+(h?s.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",t.tickPadding()).attr("transform",function(e,t,n){return"rotate("+f+" 0,0)"}).style("text-anchor",f?f%360>0?"start":"end":"middle").text(function(e,t){var n=w(e);return(""+n).match("NaN")?"":n}),x.transition().attr("transform",function(e,t){return"translate("+(s(e)+(h?s.rangeBand()/2:0))+",0)"})}c&&C.attr("transform",function(e,t){return"translate(0,"+(t%2==0?"0":"12")+")"});break;case"right":E.enter().append("text").attr("class","nv-axislabel"),E.style("text-anchor",l?"middle":"begin").attr("transform",l?"rotate(90)":"").attr("y",l?-Math.max(n.right,r)+12:-10).attr("x",l?s.range()[0]/2:t.tickPadding());if(u){var x=m.selectAll("g.nv-axisMaxMin").data(s.domain());x.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(e,t){return"translate(0,"+s(e)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",t.tickPadding()).style("text-anchor","start").text(function(e,t){var n=w(e);return(""+n).match("NaN")?"":n}),x.transition().attr("transform",function(e,t){return"translate(0,"+s.range()[t]+")"}).select("text").style("opacity",1)}break;case"left":E.enter().append("text").attr("class","nv-axislabel"),E.style("text-anchor",l?"middle":"end").attr("transform",l?"rotate(-90)":"").attr("y",l?-Math.max(n.left,r)+d:-10).attr("x",l?-s.range()[0]/2:-t.tickPadding());if(u){var x=m.selectAll("g.nv-axisMaxMin").data(s.domain());x.enter().append("g").attr("class","nv-axisMaxMin").append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(e,t){return"translate(0,"+v(e)+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-t.tickPadding()).attr("text-anchor","end").text(function(e,t){var n=w(e);return(""+n).match("NaN")?"":n}),x.transition().attr("transform",function(e,t){return"translate(0,"+s.range()[t]+")"}).select("text").style("opacity",1)}}E.text(function(e){return e}),u&&(t.orient()==="left"||t.orient()==="right")&&(b.selectAll("g").each(function(e,t){d3.select(this).select("text").attr("opacity",1);if(s(e)<s.range()[1]+10||s(e)>s.range()[0]-10)(e>1e-10||e<-1e-10)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0)}),s.domain()[0]==s.domain()[1]&&s.domain()[0]==0&&m.selectAll("g.nv-axisMaxMin").style("opacity",function(e,t){return t?0:1}));if(u&&(t.orient()==="top"||t.orient()==="bottom")){var L=[];m.selectAll("g.nv-axisMaxMin").each(function(e,t){try{t?L.push(s(e)-this.getBBox().width-4):L.push(s(e)+this.getBBox().width+4)}catch(n){t?L.push(s(e)-4):L.push(s(e)+4)}}),b.selectAll("g").each(function(e,t){if(s(e)<L[0]||s(e)>L[1])e>1e-10||e<-1e-10?d3.select(this).remove():d3.select(this).select("text").remove()})}a&&b.selectAll(".tick").filter(function(e){return!parseFloat(Math.round(e.__data__*1e5)/1e6)&&e.__data__!==undefined}).classed("zero",!0),v=s.copy()}),m}var t=d3.svg.axis(),n={top:0,right:0,bottom:0,left:0},r=75,i=60,s=d3.scale.linear(),o=null,u=!0,a=!0,f=0,l=!0,c=!1,h=!1,p=null,d=12;t.scale(s).orient("bottom").tickFormat(function(e){return e});var v;return m.axis=t,d3.rebind(m,t,"orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"),d3.rebind(m,s,"domain","range","rangeBand","rangeBands"),m.options=e.utils.optionsFunc.bind(m),m.margin=function(e){return arguments.length?(n.top=typeof e.top!="undefined"?e.top:n.top,n.right=typeof e.right!="undefined"?e.right:n.right,n.bottom=typeof e.bottom!="undefined"?e.bottom:n.bottom,n.left=typeof e.left!="undefined"?e.left:n.left,m):n},m.width=function(e){return arguments.length?(r=e,m):r},m.ticks=function(e){return arguments.length?(p=e,m):p},m.height=function(e){return arguments.length?(i=e,m):i},m.axisLabel=function(e){return arguments.length?(o=e,m):o},m.showMaxMin=function(e){return arguments.length?(u=e,m):u},m.highlightZero=function(e){return arguments.length?(a=e,m):a},m.scale=function(e){return arguments.length?(s=e,t.scale(s),h=typeof s.rangeBands=="function",d3.rebind(m,s,"domain","range","rangeBand","rangeBands"),m):s},m.rotateYLabel=function(e){return arguments.length?(l=e,m):l},m.rotateLabels=function(e){return arguments.length?(f=e,m):f},m.staggerLabels=function(e){return arguments.length?(c=e,m):c},m.axisLabelDistance=function(e){return arguments.length?(d=e,m):d},m},e.models.bullet=function(){"use strict";function m(e){return e.each(function(e,n){var p=c-t.left-t.right,m=h-t.top-t.bottom,g=d3.select(this),y=i.call(this,e,n).slice().sort(d3.descending),b=s.call(this,e,n).slice().sort(d3.descending),w=o.call(this,e,n).slice().sort(d3.descending),E=u.call(this,e,n).slice(),S=a.call(this,e,n).slice(),x=f.call(this,e,n).slice(),T=d3.scale.linear().domain(d3.extent(d3.merge([l,y]))).range(r?[p,0]:[0,p]),N=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(T.range());this.__chart__=T;var C=d3.min(y),k=d3.max(y),L=y[1],A=g.selectAll("g.nv-wrap.nv-bullet").data([e]),O=A.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),M=O.append("g"),_=A.select("g");M.append("rect").attr("class","nv-range nv-rangeMax"),M.append("rect").attr("class","nv-range nv-rangeAvg"),M.append("rect").attr("class","nv-range nv-rangeMin"),M.append("rect").attr("class","nv-measure"),M.append("path").attr("class","nv-markerTriangle"),A.attr("transform","translate("+t.left+","+t.top+")");var D=function(e){return Math.abs(N(e)-N(0))},P=function(e){return Math.abs(T(e)-T(0))},H=function(e){return e<0?N(e):N(0)},B=function(e){return e<0?T(e):T(0)};_.select("rect.nv-rangeMax").attr("height",m).attr("width",P(k>0?k:C)).attr("x",B(k>0?k:C)).datum(k>0?k:C),_.select("rect.nv-rangeAvg").attr("height",m).attr("width",P(L)).attr("x",B(L)).datum(L),_.select("rect.nv-rangeMin").attr("height",m).attr("width",P(k)).attr("x",B(k)).attr("width",P(k>0?C:k)).attr("x",B(k>0?C:k)).datum(k>0?C:k),_.select("rect.nv-measure").style("fill",d).attr("height",m/3).attr("y",m/3).attr("width",w<0?T(0)-T(w[0]):T(w[0])-T(0)).attr("x",B(w)).on("mouseover",function(){v.elementMouseover({value:w[0],label:x[0]||"Current",pos:[T(w[0]),m/2]})}).on("mouseout",function(){v.elementMouseout({value:w[0],label:x[0]||"Current"})});var j=m/6;b[0]?_.selectAll("path.nv-markerTriangle").attr("transform",function(e){return"translate("+T(b[0])+","+m/2+")"}).attr("d","M0,"+j+"L"+j+","+ -j+" "+ -j+","+ -j+"Z").on("mouseover",function(){v.elementMouseover({value:b[0],label:S[0]||"Previous",pos:[T(b[0]),m/2]})}).on("mouseout",function(){v.elementMouseout({value:b[0],label:S[0]||"Previous"})}):_.selectAll("path.nv-markerTriangle").remove(),A.selectAll(".nv-range").on("mouseover",function(e,t){var n=E[t]||(t?t==1?"Mean":"Minimum":"Maximum");v.elementMouseover({value:e,label:n,pos:[T(e),m/2]})}).on("mouseout",function(e,t){var n=E[t]||(t?t==1?"Mean":"Minimum":"Maximum");v.elementMouseout({value:e,label:n})})}),m}var t={top:0,right:0,bottom:0,left:0},n="left",r=!1,i=function(e){return e.ranges},s=function(e){return e.markers},o=function(e){return e.measures},u=function(e){return e.rangeLabels?e.rangeLabels:[]},a=function(e){return e.markerLabels?e.markerLabels:[]},f=function(e){return e.measureLabels?e.measureLabels:[]},l=[0],c=380,h=30,p=null,d=e.utils.getColor(["#1f77b4"]),v=d3.dispatch("elementMouseover","elementMouseout");return m.dispatch=v,m.options=e.utils.optionsFunc.bind(m),m.orient=function(e){return arguments.length?(n=e,r=n=="right"||n=="bottom",m):n},m.ranges=function(e){return arguments.length?(i=e,m):i},m.markers=function(e){return arguments.length?(s=e,m):s},m.measures=function(e){return arguments.length?(o=e,m):o},m.forceX=function(e){return arguments.length?(l=e,m):l},m.width=function(e){return arguments.length?(c=e,m):c},m.height=function(e){return arguments.length?(h=e,m):h},m.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,m):t},m.tickFormat=function(e){return arguments.length?(p=e,m):p},m.color=function(t){return arguments.length?(d=e.utils.getColor(t),m):d},m},e.models.bulletChart=function(){"use strict";function m(e){return e.each(function(n,h){var g=d3.select(this),y=(a||parseInt(g.style("width"))||960)-i.left-i.right,b=f-i.top-i.bottom,w=this;m.update=function(){m(e)},m.container=this;if(!n||!s.call(this,n,h)){var E=g.selectAll(".nv-noData").data([p]);return E.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),E.attr("x",i.left+y/2).attr("y",18+i.top+b/2).text(function(e){return e}),m}g.selectAll(".nv-noData").remove();var S=s.call(this,n,h).slice().sort(d3.descending),x=o.call(this,n,h).slice().sort(d3.descending),T=u.call(this,n,h).slice().sort(d3.descending),N=g.selectAll("g.nv-wrap.nv-bulletChart").data([n]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),k=C.append("g"),L=N.select("g");k.append("g").attr("class","nv-bulletWrap"),k.append("g").attr("class","nv-titles"),N.attr("transform","translate("+i.left+","+i.top+")");var A=d3.scale.linear().domain([0,Math.max(S[0],x[0],T[0])]).range(r?[y,0]:[0,y]),O=this.__chart__||d3.scale.linear().domain([0,Infinity]).range(A.range());this.__chart__=A;var M=function(e){return Math.abs(O(e)-O(0))},_=function(e){return Math.abs(A(e)-A(0))},D=k.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(f-i.top-i.bottom)/2+")");D.append("text").attr("class","nv-title").text(function(e){return e.title}),D.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(e){return e.subtitle}),t.width(y).height(b);var P=L.select(".nv-bulletWrap");d3.transition(P).call(t);var H=l||A.tickFormat(y/100),B=L.selectAll("g.nv-tick").data(A.ticks(y/50),function(e){return this.textContent||H(e)}),j=B.enter().append("g").attr("class","nv-tick").attr("transform",function(e){return"translate("+O(e)+",0)"}).style("opacity",1e-6);j.append("line").attr("y1",b).attr("y2",b*7/6),j.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",b*7/6).text(H);var F=d3.transition(B).attr("transform",function(e){return"translate("+A(e)+",0)"}).style("opacity",1);F.select("line").attr("y1",b).attr("y2",b*7/6),F.select("text").attr("y",b*7/6),d3.transition(B.exit()).attr("transform",function(e){return"translate("+A(e)+",0)"}).style("opacity",1e-6).remove(),d.on("tooltipShow",function(e){e.key=n.title,c&&v(e,w.parentNode)})}),d3.timer.flush(),m}var t=e.models.bullet(),n="left",r=!1,i={top:5,right:40,bottom:20,left:120},s=function(e){return e.ranges},o=function(e){return e.markers},u=function(e){return e.measures},a=null,f=55,l=null,c=!0,h=function(e,t,n,r,i){return"<h3>"+t+"</h3>"+"<p>"+n+"</p>"},p="No Data Available.",d=d3.dispatch("tooltipShow","tooltipHide"),v=function(t,n){var r=t.pos[0]+(n.offsetLeft||0)+i.left,s=t.pos[1]+(n.offsetTop||0)+i.top,o=h(t.key,t.label,t.value,t,m);e.tooltip.show([r,s],o,t.value<0?"e":"w",null,n)};return t.dispatch.on("elementMouseover.tooltip",function(e){d.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){d.tooltipHide(e)}),d.on("tooltipHide",function(){c&&e.tooltip.cleanup()}),m.dispatch=d,m.bullet=t,d3.rebind(m,t,"color"),m.options=e.utils.optionsFunc.bind(m),m.orient=function(e){return arguments.length?(n=e,r=n=="right"||n=="bottom",m):n},m.ranges=function(e){return arguments.length?(s=e,m):s},m.markers=function(e){return arguments.length?(o=e,m):o},m.measures=function(e){return arguments.length?(u=e,m):u},m.width=function(e){return arguments.length?(a=e,m):a},m.height=function(e){return arguments.length?(f=e,m):f},m.margin=function(e){return arguments.length?(i.top=typeof e.top!="undefined"?e.top:i.top,i.right=typeof e.right!="undefined"?e.right:i.right,i.bottom=typeof e.bottom!="undefined"?e.bottom:i.bottom,i.left=typeof e.left!="undefined"?e.left:i.left,m):i},m.tickFormat=function(e){return arguments.length?(l=e,m):l},m.tooltips=function(e){return arguments.length?(c=e,m):c},m.tooltipContent=function(e){return arguments.length?(h=e,m):h},m.noData=function(e){return arguments.length?(p=e,m):p},m},e.models.cumulativeLineChart=function(){"use strict";function D(b){return b.each(function(b){function q(e,t){d3.select(D.container).style("cursor","ew-resize")}function R(e,t){M.x=d3.event.x,M.i=Math.round(O.invert(M.x)),rt()}function U(e,t){d3.select(D.container).style("cursor","auto"),x.index=M.i,k.stateChange(x)}function rt(){nt.data([M]);var e=D.transitionDuration();D.transitionDuration(0),D.update(),D.transitionDuration(e)}var A=d3.select(this).classed("nv-chart-"+S,!0),H=this,B=(f||parseInt(A.style("width"))||960)-u.left-u.right,j=(l||parseInt(A.style("height"))||400)-u.top-u.bottom;D.update=function(){A.transition().duration(L).call(D)},D.container=this,x.disabled=b.map(function(e){return!!e.disabled});if(!T){var F;T={};for(F in x)x[F]instanceof Array?T[F]=x[F].slice(0):T[F]=x[F]}var I=d3.behavior.drag().on("dragstart",q).on("drag",R).on("dragend",U);if(!b||!b.length||!b.filter(function(e){return e.values.length}).length){var z=A.selectAll(".nv-noData").data([N]);return z.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),z.attr("x",u.left+B/2).attr("y",u.top+j/2).text(function(e){return e}),D}A.selectAll(".nv-noData").remove(),w=t.xScale(),E=t.yScale();if(!y){var W=b.filter(function(e){return!e.disabled}).map(function(e,n){var r=d3.extent(e.values,t.y());return r[0]<-0.95&&(r[0]=-0.95),[(r[0]-r[1])/(1+r[1]),(r[1]-r[0])/(1+r[0])]}),X=[d3.min(W,function(e){return e[0]}),d3.max(W,function(e){return e[1]})];t.yDomain(X)}else t.yDomain(null);O.domain([0,b[0].values.length-1]).range([0,B]).clamp(!0);var b=P(M.i,b),V=g?"none":"all",$=A.selectAll("g.nv-wrap.nv-cumulativeLine").data([b]),J=$.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),K=$.select("g");J.append("g").attr("class","nv-interactive"),J.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),J.append("g").attr("class","nv-y nv-axis"),J.append("g").attr("class","nv-background"),J.append("g").attr("class","nv-linesWrap").style("pointer-events",V),J.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),J.append("g").attr("class","nv-legendWrap"),J.append("g").attr("class","nv-controlsWrap"),c&&(i.width(B),K.select(".nv-legendWrap").datum(b).call(i),u.top!=i.height()&&(u.top=i.height(),j=(l||parseInt(A.style("height"))||400)-u.top-u.bottom),K.select(".nv-legendWrap").attr("transform","translate(0,"+ -u.top+")"));if(m){var Q=[{key:"Re-scale y-axis",disabled:!y}];s.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),K.select(".nv-controlsWrap").datum(Q).attr("transform","translate(0,"+ -u.top+")").call(s)}$.attr("transform","translate("+u.left+","+u.top+")"),d&&K.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)");var G=b.filter(function(e){return e.tempDisabled});$.select(".tempDisabled").remove(),G.length&&$.append("text").attr("class","tempDisabled").attr("x",B/2).attr("y","-.71em").style("text-anchor","end").text(G.map(function(e){return e.key}).join(", ")+" values cannot be calculated for this time period."),g&&(o.width(B).height(j).margin({left:u.left,top:u.top}).svgContainer(A).xScale(w),$.select(".nv-interactive").call(o)),J.select(".nv-background").append("rect"),K.select(".nv-background rect").attr("width",B).attr("height",j),t.y(function(e){return e.display.y}).width(B).height(j).color(b.map(function(e,t){return e.color||a(e,t)}).filter(function(e,t){return!b[t].disabled&&!b[t].tempDisabled}));var Y=K.select(".nv-linesWrap").datum(b.filter(function(e){return!e.disabled&&!e.tempDisabled}));Y.call(t),b.forEach(function(e,t){e.seriesIndex=t});var Z=b.filter(function(e){return!e.disabled&&!!C(e)}),et=K.select(".nv-avgLinesWrap").selectAll("line").data(Z,function(e){return e.key}),tt=function(e){var t=E(C(e));return t<0?0:t>j?j:t};et.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(e,n){return t.color()(e,e.seriesIndex)}).attr("x1",0).attr("x2",B).attr("y1",tt).attr("y2",tt),et.style("stroke-opacity",function(e){var t=E(C(e));return t<0||t>j?0:1}).attr("x1",0).attr("x2",B).attr("y1",tt).attr("y2",tt),et.exit().remove();var nt=Y.selectAll(".nv-indexLine").data([M]);nt.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(I),nt.attr("transform",function(e){return"translate("+O(e.i)+",0)"}).attr("height",j),h&&(n.scale(w).ticks(Math.min(b[0].values.length,B/70)).tickSize(-j,0),K.select(".nv-x.nv-axis").attr("transform","translate(0,"+E.range()[0]+")"),d3.transition(K.select(".nv-x.nv-axis")).call(n)),p&&(r.scale(E).ticks(j/36).tickSize(-B,0),d3.transition(K.select(".nv-y.nv-axis")).call(r)),K.select(".nv-background rect").on("click",function(){M.x=d3.mouse(this)[0],M.i=Math.round(O.invert(M.x)),x.index=M.i,k.stateChange(x),rt()}),t.dispatch.on("elementClick",function(e){M.i=e.pointIndex,M.x=O(M.i),x.index=M.i,k.stateChange(x),rt()}),s.dispatch.on("legendClick",function(e,t){e.disabled=!e.disabled,y=!e.disabled,x.rescaleY=y,k.stateChange(x),D.update()}),i.dispatch.on("stateChange",function(e){x.disabled=e.disabled,k.stateChange(x),D.update()}),o.dispatch.on("elementMousemove",function(i){t.clearHighlights();var s,f,l,c=[];b.filter(function(e,t){return e.seriesIndex=t,!e.disabled}).forEach(function(n,r){f=e.interactiveBisect(n.values,i.pointXValue,D.x()),t.highlightPoint(r,f,!0);var o=n.values[f];if(typeof o=="undefined")return;typeof s=="undefined"&&(s=o),typeof l=="undefined"&&(l=D.xScale()(D.x()(o,f))),c.push({key:n.key,value:D.y()(o,f),color:a(n,n.seriesIndex)})});if(c.length>2){var h=D.yScale().invert(i.mouseY),p=Math.abs(D.yScale().domain()[0]-D.yScale().domain()[1]),d=.03*p,m=e.nearestValueIndex(c.map(function(e){return e.value}),h,d);m!==null&&(c[m].highlight=!0)}var g=n.tickFormat()(D.x()(s,f),f);o.tooltip.position({left:l+u.left,top:i.mouseY+u.top}).chartContainer(H.parentNode).enabled(v).valueFormatter(function(e,t){return r.tickFormat()(e)}).data({value:g,series:c})(),o.renderGuideLine(l)}),o.dispatch.on("elementMouseout",function(e){k.tooltipHide(),t.clearHighlights()}),k.on("tooltipShow",function(e){v&&_(e,H.parentNode)}),k.on("changeState",function(e){typeof e.disabled!="undefined"&&(b.forEach(function(t,n){t.disabled=e.disabled[n]}),x.disabled=e.disabled),typeof e.index!="undefined"&&(M.i=e.index,M.x=O(M.i),x.index=e.index,nt.data([M])),typeof e.rescaleY!="undefined"&&(y=e.rescaleY),D.update()})}),D}function P(e,n){return n.map(function(n,r){if(!n.values)return n;var i=t.y()(n.values[e],e);return i<-0.95&&!A?(n.tempDisabled=!0,n):(n.tempDisabled=!1,n.values=
|
2 |
+
n.values.map(function(e,n){return e.display={y:(t.y()(e,n)-i)/(1+i)},e}),n)})}var t=e.models.line(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.interactiveGuideline(),u={top:30,right:30,bottom:50,left:60},a=e.utils.defaultColor(),f=null,l=null,c=!0,h=!0,p=!0,d=!1,v=!0,m=!0,g=!1,y=!0,b=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},w,E,S=t.id(),x={index:0,rescaleY:y},T=null,N="No Data Available.",C=function(e){return e.average},k=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),L=250,A=!1;n.orient("bottom").tickPadding(7),r.orient(d?"right":"left"),s.updateState(!1);var O=d3.scale.linear(),M={i:0,x:0},_=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=b(i.series.key,a,f,i,D);e.tooltip.show([o,u],l,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],k.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){k.tooltipHide(e)}),k.on("tooltipHide",function(){v&&e.tooltip.cleanup()}),D.dispatch=k,D.lines=t,D.legend=i,D.xAxis=n,D.yAxis=r,D.interactiveLayer=o,d3.rebind(D,t,"defined","isArea","x","y","xScale","yScale","size","xDomain","yDomain","xRange","yRange","forceX","forceY","interactive","clipEdge","clipVoronoi","useVoronoi","id"),D.options=e.utils.optionsFunc.bind(D),D.margin=function(e){return arguments.length?(u.top=typeof e.top!="undefined"?e.top:u.top,u.right=typeof e.right!="undefined"?e.right:u.right,u.bottom=typeof e.bottom!="undefined"?e.bottom:u.bottom,u.left=typeof e.left!="undefined"?e.left:u.left,D):u},D.width=function(e){return arguments.length?(f=e,D):f},D.height=function(e){return arguments.length?(l=e,D):l},D.color=function(t){return arguments.length?(a=e.utils.getColor(t),i.color(a),D):a},D.rescaleY=function(e){return arguments.length?(y=e,D):y},D.showControls=function(e){return arguments.length?(m=e,D):m},D.useInteractiveGuideline=function(e){return arguments.length?(g=e,e===!0&&(D.interactive(!1),D.useVoronoi(!1)),D):g},D.showLegend=function(e){return arguments.length?(c=e,D):c},D.showXAxis=function(e){return arguments.length?(h=e,D):h},D.showYAxis=function(e){return arguments.length?(p=e,D):p},D.rightAlignYAxis=function(e){return arguments.length?(d=e,r.orient(e?"right":"left"),D):d},D.tooltips=function(e){return arguments.length?(v=e,D):v},D.tooltipContent=function(e){return arguments.length?(b=e,D):b},D.state=function(e){return arguments.length?(x=e,D):x},D.defaultState=function(e){return arguments.length?(T=e,D):T},D.noData=function(e){return arguments.length?(N=e,D):N},D.average=function(e){return arguments.length?(C=e,D):C},D.transitionDuration=function(e){return arguments.length?(L=e,D):L},D.noErrorCheck=function(e){return arguments.length?(A=e,D):A},D},e.models.discreteBar=function(){"use strict";function E(e){return e.each(function(e){var i=n-t.left-t.right,E=r-t.top-t.bottom,S=d3.select(this);e.forEach(function(e,t){e.values.forEach(function(e){e.series=t})});var T=p&&d?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0}})});s.domain(p||d3.merge(T).map(function(e){return e.x})).rangeBands(v||[0,i],.1),o.domain(d||d3.extent(d3.merge(T).map(function(e){return e.y}).concat(f))),c?o.range(m||[E-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]):o.range(m||[E,0]),b=b||s,w=w||o.copy().range([o(0),o(0)]);var N=S.selectAll("g.nv-wrap.nv-discretebar").data([e]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),k=C.append("g"),L=N.select("g");k.append("g").attr("class","nv-groups"),N.attr("transform","translate("+t.left+","+t.top+")");var A=N.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),A.exit().transition().style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),A.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}),A.transition().style("stroke-opacity",1).style("fill-opacity",.75);var O=A.selectAll("g.nv-bar").data(function(e){return e.values});O.exit().remove();var M=O.enter().append("g").attr("transform",function(e,t,n){return"translate("+(s(u(e,t))+s.rangeBand()*.05)+", "+o(0)+")"}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),g.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),g.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){g.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){g.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(t.series+.5)/e.length,o(a(t,n))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()});M.append("rect").attr("height",0).attr("width",s.rangeBand()*.9/e.length),c?(M.append("text").attr("text-anchor","middle"),O.select("text").text(function(e,t){return h(a(e,t))}).transition().attr("x",s.rangeBand()*.9/2).attr("y",function(e,t){return a(e,t)<0?o(a(e,t))-o(0)+12:-4})):O.selectAll("text").remove(),O.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(e,t){return e.color||l(e,t)}).style("stroke",function(e,t){return e.color||l(e,t)}).select("rect").attr("class",y).transition().attr("width",s.rangeBand()*.9/e.length),O.transition().attr("transform",function(e,t){var n=s(u(e,t))+s.rangeBand()*.05,r=a(e,t)<0?o(0):o(0)-o(a(e,t))<1?o(0)-1:o(a(e,t));return"translate("+n+", "+r+")"}).select("rect").attr("height",function(e,t){return Math.max(Math.abs(o(a(e,t))-o(d&&d[0]||0))||1)}),b=s.copy(),w=o.copy()}),E}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.ordinal(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=e.utils.defaultColor(),c=!1,h=d3.format(",.2f"),p,d,v,m,g=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),y="discreteBar",b,w;return E.dispatch=g,E.options=e.utils.optionsFunc.bind(E),E.x=function(e){return arguments.length?(u=e,E):u},E.y=function(e){return arguments.length?(a=e,E):a},E.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,E):t},E.width=function(e){return arguments.length?(n=e,E):n},E.height=function(e){return arguments.length?(r=e,E):r},E.xScale=function(e){return arguments.length?(s=e,E):s},E.yScale=function(e){return arguments.length?(o=e,E):o},E.xDomain=function(e){return arguments.length?(p=e,E):p},E.yDomain=function(e){return arguments.length?(d=e,E):d},E.xRange=function(e){return arguments.length?(v=e,E):v},E.yRange=function(e){return arguments.length?(m=e,E):m},E.forceY=function(e){return arguments.length?(f=e,E):f},E.color=function(t){return arguments.length?(l=e.utils.getColor(t),E):l},E.id=function(e){return arguments.length?(i=e,E):i},E.showValues=function(e){return arguments.length?(c=e,E):c},E.valueFormat=function(e){return arguments.length?(h=e,E):h},E.rectClass=function(e){return arguments.length?(y=e,E):y},E},e.models.discreteBarChart=function(){"use strict";function w(e){return e.each(function(e){var u=d3.select(this),p=this,E=(s||parseInt(u.style("width"))||960)-i.left-i.right,S=(o||parseInt(u.style("height"))||400)-i.top-i.bottom;w.update=function(){g.beforeUpdate(),u.transition().duration(y).call(w)},w.container=this;if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var T=u.selectAll(".nv-noData").data([m]);return T.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),T.attr("x",i.left+E/2).attr("y",i.top+S/2).text(function(e){return e}),w}u.selectAll(".nv-noData").remove(),d=t.xScale(),v=t.yScale().clamp(!0);var N=u.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([e]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),k=C.append("defs"),L=N.select("g");C.append("g").attr("class","nv-x nv-axis"),C.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),C.append("g").attr("class","nv-barsWrap"),L.attr("transform","translate("+i.left+","+i.top+")"),l&&L.select(".nv-y.nv-axis").attr("transform","translate("+E+",0)"),t.width(E).height(S);var A=L.select(".nv-barsWrap").datum(e.filter(function(e){return!e.disabled}));A.transition().call(t),k.append("clipPath").attr("id","nv-x-label-clip-"+t.id()).append("rect"),L.select("#nv-x-label-clip-"+t.id()+" rect").attr("width",d.rangeBand()*(c?2:1)).attr("height",16).attr("x",-d.rangeBand()/(c?1:2));if(a){n.scale(d).ticks(E/100).tickSize(-S,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+(v.range()[0]+(t.showValues()&&v.domain()[0]<0?16:0))+")"),L.select(".nv-x.nv-axis").transition().call(n);var O=L.select(".nv-x.nv-axis").selectAll("g");c&&O.selectAll("text").attr("transform",function(e,t,n){return"translate(0,"+(n%2==0?"5":"17")+")"})}f&&(r.scale(v).ticks(S/36).tickSize(-E,0),L.select(".nv-y.nv-axis").transition().call(r)),L.select(".nv-zeroLine line").attr("x1",0).attr("x2",E).attr("y1",v(0)).attr("y2",v(0)),g.on("tooltipShow",function(e){h&&b(e,p.parentNode)})}),w}var t=e.models.discreteBar(),n=e.models.axis(),r=e.models.axis(),i={top:15,right:10,bottom:50,left:60},s=null,o=null,u=e.utils.getColor(),a=!0,f=!0,l=!1,c=!1,h=!0,p=function(e,t,n,r,i){return"<h3>"+t+"</h3>"+"<p>"+n+"</p>"},d,v,m="No Data Available.",g=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate"),y=250;n.orient("bottom").highlightZero(!1).showMaxMin(!1).tickFormat(function(e){return e}),r.orient(l?"right":"left").tickFormat(d3.format(",.1f"));var b=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=p(i.series.key,a,f,i,w);e.tooltip.show([o,u],l,i.value<0?"n":"s",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+i.left,e.pos[1]+i.top],g.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){g.tooltipHide(e)}),g.on("tooltipHide",function(){h&&e.tooltip.cleanup()}),w.dispatch=g,w.discretebar=t,w.xAxis=n,w.yAxis=r,d3.rebind(w,t,"x","y","xDomain","yDomain","xRange","yRange","forceX","forceY","id","showValues","valueFormat"),w.options=e.utils.optionsFunc.bind(w),w.margin=function(e){return arguments.length?(i.top=typeof e.top!="undefined"?e.top:i.top,i.right=typeof e.right!="undefined"?e.right:i.right,i.bottom=typeof e.bottom!="undefined"?e.bottom:i.bottom,i.left=typeof e.left!="undefined"?e.left:i.left,w):i},w.width=function(e){return arguments.length?(s=e,w):s},w.height=function(e){return arguments.length?(o=e,w):o},w.color=function(n){return arguments.length?(u=e.utils.getColor(n),t.color(u),w):u},w.showXAxis=function(e){return arguments.length?(a=e,w):a},w.showYAxis=function(e){return arguments.length?(f=e,w):f},w.rightAlignYAxis=function(e){return arguments.length?(l=e,r.orient(e?"right":"left"),w):l},w.staggerLabels=function(e){return arguments.length?(c=e,w):c},w.tooltips=function(e){return arguments.length?(h=e,w):h},w.tooltipContent=function(e){return arguments.length?(p=e,w):p},w.noData=function(e){return arguments.length?(m=e,w):m},w.transitionDuration=function(e){return arguments.length?(y=e,w):y},w},e.models.distribution=function(){"use strict";function l(e){return e.each(function(e){var a=n-(i==="x"?t.left+t.right:t.top+t.bottom),l=i=="x"?"y":"x",c=d3.select(this);f=f||u;var h=c.selectAll("g.nv-distribution").data([e]),p=h.enter().append("g").attr("class","nvd3 nv-distribution"),d=p.append("g"),v=h.select("g");h.attr("transform","translate("+t.left+","+t.top+")");var m=v.selectAll("g.nv-dist").data(function(e){return e},function(e){return e.key});m.enter().append("g"),m.attr("class",function(e,t){return"nv-dist nv-series-"+t}).style("stroke",function(e,t){return o(e,t)});var g=m.selectAll("line.nv-dist"+i).data(function(e){return e.values});g.enter().append("line").attr(i+"1",function(e,t){return f(s(e,t))}).attr(i+"2",function(e,t){return f(s(e,t))}),m.exit().selectAll("line.nv-dist"+i).transition().attr(i+"1",function(e,t){return u(s(e,t))}).attr(i+"2",function(e,t){return u(s(e,t))}).style("stroke-opacity",0).remove(),g.attr("class",function(e,t){return"nv-dist"+i+" nv-dist"+i+"-"+t}).attr(l+"1",0).attr(l+"2",r),g.transition().attr(i+"1",function(e,t){return u(s(e,t))}).attr(i+"2",function(e,t){return u(s(e,t))}),f=u.copy()}),l}var t={top:0,right:0,bottom:0,left:0},n=400,r=8,i="x",s=function(e){return e[i]},o=e.utils.defaultColor(),u=d3.scale.linear(),a,f;return l.options=e.utils.optionsFunc.bind(l),l.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,l):t},l.width=function(e){return arguments.length?(n=e,l):n},l.axis=function(e){return arguments.length?(i=e,l):i},l.size=function(e){return arguments.length?(r=e,l):r},l.getData=function(e){return arguments.length?(s=d3.functor(e),l):s},l.scale=function(e){return arguments.length?(u=e,l):u},l.color=function(t){return arguments.length?(o=e.utils.getColor(t),l):o},l},e.models.historicalBar=function(){"use strict";function w(E){return E.each(function(w){var E=n-t.left-t.right,S=r-t.top-t.bottom,T=d3.select(this);s.domain(d||d3.extent(w[0].values.map(u).concat(f))),c?s.range(m||[E*.5/w[0].values.length,E*(w[0].values.length-.5)/w[0].values.length]):s.range(m||[0,E]),o.domain(v||d3.extent(w[0].values.map(a).concat(l))).range(g||[S,0]),s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]-s.domain()[0]*.01,s.domain()[1]+s.domain()[1]*.01]):s.domain([-1,1])),o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]+o.domain()[0]*.01,o.domain()[1]-o.domain()[1]*.01]):o.domain([-1,1]));var N=T.selectAll("g.nv-wrap.nv-historicalBar-"+i).data([w[0].values]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+i),k=C.append("defs"),L=C.append("g"),A=N.select("g");L.append("g").attr("class","nv-bars"),N.attr("transform","translate("+t.left+","+t.top+")"),T.on("click",function(e,t){y.chartClick({data:e,index:t,pos:d3.event,id:i})}),k.append("clipPath").attr("id","nv-chart-clip-path-"+i).append("rect"),N.select("#nv-chart-clip-path-"+i+" rect").attr("width",E).attr("height",S),A.attr("clip-path",h?"url(#nv-chart-clip-path-"+i+")":"");var O=N.select(".nv-bars").selectAll(".nv-bar").data(function(e){return e},function(e,t){return u(e,t)});O.exit().remove();var M=O.enter().append("rect").attr("x",0).attr("y",function(t,n){return e.utils.NaNtoZero(o(Math.max(0,a(t,n))))}).attr("height",function(t,n){return e.utils.NaNtoZero(Math.abs(o(a(t,n))-o(0)))}).attr("transform",function(e,t){return"translate("+(s(u(e,t))-E/w[0].values.length*.45)+",0)"}).on("mouseover",function(e,t){if(!b)return;d3.select(this).classed("hover",!0),y.elementMouseover({point:e,series:w[0],pos:[s(u(e,t)),o(a(e,t))],pointIndex:t,seriesIndex:0,e:d3.event})}).on("mouseout",function(e,t){if(!b)return;d3.select(this).classed("hover",!1),y.elementMouseout({point:e,series:w[0],pointIndex:t,seriesIndex:0,e:d3.event})}).on("click",function(e,t){if(!b)return;y.elementClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()}).on("dblclick",function(e,t){if(!b)return;y.elementDblClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()});O.attr("fill",function(e,t){return p(e,t)}).attr("class",function(e,t,n){return(a(e,t)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+n+"-"+t}).transition().attr("transform",function(e,t){return"translate("+(s(u(e,t))-E/w[0].values.length*.45)+",0)"}).attr("width",E/w[0].values.length*.9),O.transition().attr("y",function(t,n){var r=a(t,n)<0?o(0):o(0)-o(a(t,n))<1?o(0)-1:o(a(t,n));return e.utils.NaNtoZero(r)}).attr("height",function(t,n){return e.utils.NaNtoZero(Math.max(Math.abs(o(a(t,n))-o(0)),1))})}),w}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[],l=[0],c=!1,h=!0,p=e.utils.defaultColor(),d,v,m,g,y=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),b=!0;return w.highlightPoint=function(e,t){d3.select(".nv-historicalBar-"+i).select(".nv-bars .nv-bar-0-"+e).classed("hover",t)},w.clearHighlights=function(){d3.select(".nv-historicalBar-"+i).select(".nv-bars .nv-bar.hover").classed("hover",!1)},w.dispatch=y,w.options=e.utils.optionsFunc.bind(w),w.x=function(e){return arguments.length?(u=e,w):u},w.y=function(e){return arguments.length?(a=e,w):a},w.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,w):t},w.width=function(e){return arguments.length?(n=e,w):n},w.height=function(e){return arguments.length?(r=e,w):r},w.xScale=function(e){return arguments.length?(s=e,w):s},w.yScale=function(e){return arguments.length?(o=e,w):o},w.xDomain=function(e){return arguments.length?(d=e,w):d},w.yDomain=function(e){return arguments.length?(v=e,w):v},w.xRange=function(e){return arguments.length?(m=e,w):m},w.yRange=function(e){return arguments.length?(g=e,w):g},w.forceX=function(e){return arguments.length?(f=e,w):f},w.forceY=function(e){return arguments.length?(l=e,w):l},w.padData=function(e){return arguments.length?(c=e,w):c},w.clipEdge=function(e){return arguments.length?(h=e,w):h},w.color=function(t){return arguments.length?(p=e.utils.getColor(t),w):p},w.id=function(e){return arguments.length?(i=e,w):i},w.interactive=function(e){return arguments.length?(b=!1,w):b},w},e.models.historicalBarChart=function(){"use strict";function x(e){return e.each(function(d){var T=d3.select(this),N=this,C=(u||parseInt(T.style("width"))||960)-s.left-s.right,k=(a||parseInt(T.style("height"))||400)-s.top-s.bottom;x.update=function(){T.transition().duration(E).call(x)},x.container=this,g.disabled=d.map(function(e){return!!e.disabled});if(!y){var L;y={};for(L in g)g[L]instanceof Array?y[L]=g[L].slice(0):y[L]=g[L]}if(!d||!d.length||!d.filter(function(e){return e.values.length}).length){var A=T.selectAll(".nv-noData").data([b]);return A.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),A.attr("x",s.left+C/2).attr("y",s.top+k/2).text(function(e){return e}),x}T.selectAll(".nv-noData").remove(),v=t.xScale(),m=t.yScale();var O=T.selectAll("g.nv-wrap.nv-historicalBarChart").data([d]),M=O.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),_=O.select("g");M.append("g").attr("class","nv-x nv-axis"),M.append("g").attr("class","nv-y nv-axis"),M.append("g").attr("class","nv-barsWrap"),M.append("g").attr("class","nv-legendWrap"),f&&(i.width(C),_.select(".nv-legendWrap").datum(d).call(i),s.top!=i.height()&&(s.top=i.height(),k=(a||parseInt(T.style("height"))||400)-s.top-s.bottom),O.select(".nv-legendWrap").attr("transform","translate(0,"+ -s.top+")")),O.attr("transform","translate("+s.left+","+s.top+")"),h&&_.select(".nv-y.nv-axis").attr("transform","translate("+C+",0)"),t.width(C).height(k).color(d.map(function(e,t){return e.color||o(e,t)}).filter(function(e,t){return!d[t].disabled}));var D=_.select(".nv-barsWrap").datum(d.filter(function(e){return!e.disabled}));D.transition().call(t),l&&(n.scale(v).tickSize(-k,0),_.select(".nv-x.nv-axis").attr("transform","translate(0,"+m.range()[0]+")"),_.select(".nv-x.nv-axis").transition().call(n)),c&&(r.scale(m).ticks(k/36).tickSize(-C,0),_.select(".nv-y.nv-axis").transition().call(r)),i.dispatch.on("legendClick",function(t,n){t.disabled=!t.disabled,d.filter(function(e){return!e.disabled}).length||d.map(function(e){return e.disabled=!1,O.selectAll(".nv-series").classed("disabled",!1),e}),g.disabled=d.map(function(e){return!!e.disabled}),w.stateChange(g),e.transition().call(x)}),i.dispatch.on("legendDblclick",function(e){d.forEach(function(e){e.disabled=!0}),e.disabled=!1,g.disabled=d.map(function(e){return!!e.disabled}),w.stateChange(g),x.update()}),w.on("tooltipShow",function(e){p&&S(e,N.parentNode)}),w.on("changeState",function(e){typeof e.disabled!="undefined"&&(d.forEach(function(t,n){t.disabled=e.disabled[n]}),g.disabled=e.disabled),x.update()})}),x}var t=e.models.historicalBar(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s={top:30,right:90,bottom:50,left:90},o=e.utils.defaultColor(),u=null,a=null,f=!1,l=!0,c=!0,h=!1,p=!0,d=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},v,m,g={},y=null,b="No Data Available.",w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),E=250;n.orient("bottom").tickPadding(7),r.orient(h?"right":"left");var S=function(i,s){if(s){var o=d3.select(s).select("svg"),u=o.node()?o.attr("viewBox"):null;if(u){u=u.split(" ");var a=parseInt(o.style("width"))/u[2];i.pos[0]=i.pos[0]*a,i.pos[1]=i.pos[1]*a}}var f=i.pos[0]+(s.offsetLeft||0),l=i.pos[1]+(s.offsetTop||0),c=n.tickFormat()(t.x()(i.point,i.pointIndex)),h=r.tickFormat()(t.y()(i.point,i.pointIndex)),p=d(i.series.key,c,h,i,x);e.tooltip.show([f,l],p,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+s.left,e.pos[1]+s.top],w.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){w.tooltipHide(e)}),w.on("tooltipHide",function(){p&&e.tooltip.cleanup()}),x.dispatch=w,x.bars=t,x.legend=i,x.xAxis=n,x.yAxis=r,d3.rebind(x,t,"defined","isArea","x","y","size","xScale","yScale","xDomain","yDomain","xRange","yRange","forceX","forceY","interactive","clipEdge","clipVoronoi","id","interpolate","highlightPoint","clearHighlights","interactive"),x.options=e.utils.optionsFunc.bind(x),x.margin=function(e){return arguments.length?(s.top=typeof e.top!="undefined"?e.top:s.top,s.right=typeof e.right!="undefined"?e.right:s.right,s.bottom=typeof e.bottom!="undefined"?e.bottom:s.bottom,s.left=typeof e.left!="undefined"?e.left:s.left,x):s},x.width=function(e){return arguments.length?(u=e,x):u},x.height=function(e){return arguments.length?(a=e,x):a},x.color=function(t){return arguments.length?(o=e.utils.getColor(t),i.color(o),x):o},x.showLegend=function(e){return arguments.length?(f=e,x):f},x.showXAxis=function(e){return arguments.length?(l=e,x):l},x.showYAxis=function(e){return arguments.length?(c=e,x):c},x.rightAlignYAxis=function(e){return arguments.length?(h=e,r.orient(e?"right":"left"),x):h},x.tooltips=function(e){return arguments.length?(p=e,x):p},x.tooltipContent=function(e){return arguments.length?(d=e,x):d},x.state=function(e){return arguments.length?(g=e,x):g},x.defaultState=function(e){return arguments.length?(y=e,x):y},x.noData=function(e){return arguments.length?(b=e,x):b},x.transitionDuration=function(e){return arguments.length?(E=e,x):E},x},e.models.indentedTree=function(){"use strict";function g(e){return e.each(function(e){function k(e,t,n){d3.event.stopPropagation();if(d3.event.shiftKey&&!n)return d3.event.shiftKey=!1,e.values&&e.values.forEach(function(e){(e.values||e._values)&&k(e,0,!0)}),!0;if(!O(e))return!0;e.values?(e._values=e.values,e.values=null):(e.values=e._values,e._values=null),g.update()}function L(e){return e._values&&e._values.length?h:e.values&&e.values.length?p:""}function A(e){return e._values&&e._values.length}function O(e){var t=e.values||e._values;return t&&t.length}var t=1,n=d3.select(this),i=d3.layout.tree().children(function(e){return e.values}).size([r,f]);g.update=function(){n.transition().duration(600).call(g)},e[0]||(e[0]={key:a});var s=i.nodes(e[0]),y=d3.select(this).selectAll("div").data([[s]]),b=y.enter().append("div").attr("class","nvd3 nv-wrap nv-indentedtree"),w=b.append("table"),E=y.select("table").attr("width","100%").attr("class",c);if(o){var S=w.append("thead"),x=S.append("tr");l.forEach(function(e){x.append("th").attr("width",e.width?e.width:"10%").style("text-align",e.type=="numeric"?"right":"left").append("span").text(e.label)})}var T=E.selectAll("tbody").data(function(e){return e});T.enter().append("tbody"),t=d3.max(s,function(e){return e.depth}),i.size([r,t*f]);var N=T.selectAll("tr").data(function(e){return e.filter(function(e){return u&&!e.children?u(e):!0})},function(e,t){return e.id||e.id||++m});N.exit().remove(),N.select("img.nv-treeicon").attr("src",L).classed("folded",A);var C=N.enter().append("tr");l.forEach(function(e,t){var n=C.append("td").style("padding-left",function(e){return(t?0:e.depth*f+12+(L(e)?0:16))+"px"},"important").style("text-align",e.type=="numeric"?"right":"left");t==0&&n.append("img").classed("nv-treeicon",!0).classed("nv-folded",A).attr("src",L).style("width","14px").style("height","14px").style("padding","0 1px").style("display",function(e){return L(e)?"inline-block":"none"}).on("click",k),n.each(function(n){!t&&v(n)?d3.select(this).append("a").attr("href",v).attr("class",d3.functor(e.classes)).append("span"):d3.select(this).append("span"),d3.select(this).select("span").attr("class",d3.functor(e.classes)).text(function(t){return e.format?e.format(t):t[e.key]||"-"})}),e.showCount&&(n.append("span").attr("class","nv-childrenCount"),N.selectAll("span.nv-childrenCount").text(function(e){return e.values&&e.values.length||e._values&&e._values.length?"("+(e.values&&e.values.filter(function(e){return u?u(e):!0}).length||e._values&&e._values.filter(function(e){return u?u(e):!0}).length||0)+")":""}))}),N.order().on("click",function(e){d.elementClick({row:this,data:e,pos:[e.x,e.y]})}).on("dblclick",function(e){d.elementDblclick({row:this,data:e,pos:[e.x,e.y]})}).on("mouseover",function(e){d.elementMouseover({row:this,data:e,pos:[e.x,e.y]})}).on("mouseout",function(e){d.elementMouseout({row:this,data:e,pos:[e.x,e.y]})})}),g}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=e.utils.defaultColor(),s=Math.floor(Math.random()*1e4),o=!0,u=!1,a="No Data Available.",f=20,l=[{key:"key",label:"Name",type:"text"}],c=null,h="images/grey-plus.png",p="images/grey-minus.png",d=d3.dispatch("elementClick","elementDblclick","elementMouseover","elementMouseout"),v=function(e){return e.url},m=0;return g.options=e.utils.optionsFunc.bind(g),g.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,g):t},g.width=function(e){return arguments.length?(n=e,g):n},g.height=function(e){return arguments.length?(r=e,g):r},g.color=function(t){return arguments.length?(i=e.utils.getColor(t),scatter.color(i),g):i},g.id=function(e){return arguments.length?(s=e,g):s},g.header=function(e){return arguments.length?(o=e,g):o},g.noData=function(e){return arguments.length?(a=e,g):a},g.filterZero=function(e){return arguments.length?(u=e,g):u},g.columns=function(e){return arguments.length?(l=e,g):l},g.tableClass=function(e){return arguments.length?(c=e,g):c},g.iconOpen=function(e){return arguments.length?(h=e,g):h},g.iconClose=function(e){return arguments.length?(p=e,g):p},g.getUrl=function(e){return arguments.length?(v=e,g):v},g},e.models.legend=function(){"use strict";function c(h){return h.each(function(c){var h=n-t.left-t.right,p=d3.select(this),d=p.selectAll("g.nv-legend").data([c]),v=d.enter().append("g").attr("class","nvd3 nv-legend").append("g"),m=d.select("g");d.attr("transform","translate("+t.left+","+t.top+")");var g=m.selectAll(".nv-series").data(function(e){return e}),y=g.enter().append("g").attr("class","nv-series").on("mouseover",function(e,t){l.legendMouseover(e,t)}).on("mouseout",function(e,t){l.legendMouseout(e,t)}).on("click",function(e,t){l.legendClick(e,t),a&&(f?(c.forEach(function(e){e.disabled=!0}),e.disabled=!1):(e.disabled=!e.disabled,c.every(function(e){return e.disabled})&&c.forEach(function(e){e.disabled=!1})),l.stateChange({disabled:c.map(function(e){return!!e.disabled})}))}).on("dblclick",function(e,t){l.legendDblclick(e,t),a&&(c.forEach(function(e){e.disabled=!0}),e.disabled=!1,l.stateChange({disabled:c.map(function(e){return!!e.disabled})}))});y.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),y.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8"),g.classed("disabled",function(e){return e.disabled}),g.exit().remove(),g.select("circle").style("fill",function(e,t){return e.color||s(e,t)}).style("stroke",function(e,t){return e.color||s(e,t)}),g.select("text").text(i);if(o){var b=[];g.each(function(t,n){var r=d3.select(this).select("text"),i;try{i=r.getComputedTextLength();if(i<=0)throw Error()}catch(s){i=e.utils.calcApproxTextWidth(r)}b.push(i+28)});var w=0,E=0,S=[];while(E<h&&w<b.length)S[w]=b[w],E+=b[w++];w===0&&(w=1);while(E>h&&w>1){S=[],w--;for(var x=0;x<b.length;x++)b[x]>(S[x%w]||0)&&(S[x%w]=b[x]);E=S.reduce(function(e,t,n,r){return e+t})}var T=[];for(var N=0,C=0;N<w;N++)T[N]=C,C+=S[N];g.attr("transform",function(e,t){return"translate("+T[t%w]+","+(5+Math.floor(t/w)*20)+")"}),u?m.attr("transform","translate("+(n-t.right-E)+","+t.top+")"):m.attr("transform","translate(0,"+t.top+")"),r=t.top+t.bottom+Math.ceil(b.length/w)*20}else{var k=5,L=5,A=0,O;g.attr("transform",function(e,r){var i=d3.select(this).select("text").node().getComputedTextLength()+28;return O=L,n<t.left+t.right+O+i&&(L=O=5,k+=20),L+=i,L>A&&(A=L),"translate("+O+","+k+")"}),m.attr("transform","translate("+(n-t.right-A)+","+t.top+")"),r=t.top+t.bottom+k+15}}),c}var t={top:5,right:0,bottom:5,left:0},n=400,r=20,i=function(e){return e.key},s=e.utils.defaultColor(),o=!0,u=!0,a=!0,f=!1,l=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange");return c.dispatch=l,c.options=e.utils.optionsFunc.bind(c),c.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,c):t},c.width=function(e){return arguments.length?(n=e,c):n},c.height=function(e){return arguments.length?(r=e,c):r},c.key=function(e){return arguments.length?(i=e,c):i},c.color=function(t){return arguments.length?(s=e.utils.getColor(t),c):s},c.align=function(e){return arguments.length?(o=e,c):o},c.rightAlign=function(e){return arguments.length?(u=e,c):u},c.updateState=function(e){return arguments.length?(a=e,c):a},c.radioButtonMode=function(e){return arguments.length?(f=e,c):f},c},e.models.line=function(){"use strict";function m(g){return g.each(function(m){var g=r-n.left-n.right,b=i-n.top-n.bottom,w=d3.select(this);c=t.xScale(),h=t.yScale(),d=d||c,v=v||h;var E=w.selectAll("g.nv-wrap.nv-line").data([m]),S=E.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),T=S.append("defs"),N=S.append("g"),C=E.select("g");N.append("g").attr("class","nv-groups"),N.append("g").attr("class","nv-scatterWrap"),E.attr("transform","translate("+n.left+","+n.top+")"),t.width(g).height(b);var k=E.select(".nv-scatterWrap");k.transition().call(t),T.append("clipPath").attr("id","nv-edge-clip-"+t.id()).append("rect"),E.select("#nv-edge-clip-"+t.id()+" rect").attr("width",g).attr("height",b),C.attr("clip-path",l?"url(#nv-edge-clip-"+t.id()+")":""),k.attr("clip-path",l?"url(#nv-edge-clip-"+t.id()+")":"");var L=E.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});L.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),L.exit().remove(),L.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return s(e,t)}).style("stroke",function(e,t){return s(e,t)}),L.transition().style("stroke-opacity",1).style("fill-opacity",.5);var A=L.selectAll("path.nv-area").data(function(e){return f(e)?[e]:[]});A.enter().append("path").attr("class","nv-area").attr("d",function(t){return d3.svg.area().interpolate(p).defined(a).x(function(t,n){return e.
|
3 |
+
utils.NaNtoZero(d(o(t,n)))}).y0(function(t,n){return e.utils.NaNtoZero(v(u(t,n)))}).y1(function(e,t){return v(h.domain()[0]<=0?h.domain()[1]>=0?0:h.domain()[1]:h.domain()[0])}).apply(this,[t.values])}),L.exit().selectAll("path.nv-area").remove(),A.transition().attr("d",function(t){return d3.svg.area().interpolate(p).defined(a).x(function(t,n){return e.utils.NaNtoZero(c(o(t,n)))}).y0(function(t,n){return e.utils.NaNtoZero(h(u(t,n)))}).y1(function(e,t){return h(h.domain()[0]<=0?h.domain()[1]>=0?0:h.domain()[1]:h.domain()[0])}).apply(this,[t.values])});var O=L.selectAll("path.nv-line").data(function(e){return[e.values]});O.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(p).defined(a).x(function(t,n){return e.utils.NaNtoZero(d(o(t,n)))}).y(function(t,n){return e.utils.NaNtoZero(v(u(t,n)))})),O.transition().attr("d",d3.svg.line().interpolate(p).defined(a).x(function(t,n){return e.utils.NaNtoZero(c(o(t,n)))}).y(function(t,n){return e.utils.NaNtoZero(h(u(t,n)))})),d=c.copy(),v=h.copy()}),m}var t=e.models.scatter(),n={top:0,right:0,bottom:0,left:0},r=960,i=500,s=e.utils.defaultColor(),o=function(e){return e.x},u=function(e){return e.y},a=function(e,t){return!isNaN(u(e,t))&&u(e,t)!==null},f=function(e){return e.area},l=!1,c,h,p="linear";t.size(16).sizeDomain([16,256]);var d,v;return m.dispatch=t.dispatch,m.scatter=t,d3.rebind(m,t,"id","interactive","size","xScale","yScale","zScale","xDomain","yDomain","xRange","yRange","sizeDomain","forceX","forceY","forceSize","clipVoronoi","useVoronoi","clipRadius","padData","highlightPoint","clearHighlights"),m.options=e.utils.optionsFunc.bind(m),m.margin=function(e){return arguments.length?(n.top=typeof e.top!="undefined"?e.top:n.top,n.right=typeof e.right!="undefined"?e.right:n.right,n.bottom=typeof e.bottom!="undefined"?e.bottom:n.bottom,n.left=typeof e.left!="undefined"?e.left:n.left,m):n},m.width=function(e){return arguments.length?(r=e,m):r},m.height=function(e){return arguments.length?(i=e,m):i},m.x=function(e){return arguments.length?(o=e,t.x(e),m):o},m.y=function(e){return arguments.length?(u=e,t.y(e),m):u},m.clipEdge=function(e){return arguments.length?(l=e,m):l},m.color=function(n){return arguments.length?(s=e.utils.getColor(n),t.color(s),m):s},m.interpolate=function(e){return arguments.length?(p=e,m):p},m.defined=function(e){return arguments.length?(a=e,m):a},m.isArea=function(e){return arguments.length?(f=d3.functor(e),m):f},m},e.models.lineChart=function(){"use strict";function N(m){return m.each(function(m){var C=d3.select(this),k=this,L=(a||parseInt(C.style("width"))||960)-o.left-o.right,A=(f||parseInt(C.style("height"))||400)-o.top-o.bottom;N.update=function(){C.transition().duration(x).call(N)},N.container=this,b.disabled=m.map(function(e){return!!e.disabled});if(!w){var O;w={};for(O in b)b[O]instanceof Array?w[O]=b[O].slice(0):w[O]=b[O]}if(!m||!m.length||!m.filter(function(e){return e.values.length}).length){var M=C.selectAll(".nv-noData").data([E]);return M.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),M.attr("x",o.left+L/2).attr("y",o.top+A/2).text(function(e){return e}),N}C.selectAll(".nv-noData").remove(),g=t.xScale(),y=t.yScale();var _=C.selectAll("g.nv-wrap.nv-lineChart").data([m]),D=_.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),P=_.select("g");D.append("rect").style("opacity",0),D.append("g").attr("class","nv-x nv-axis"),D.append("g").attr("class","nv-y nv-axis"),D.append("g").attr("class","nv-linesWrap"),D.append("g").attr("class","nv-legendWrap"),D.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",L).attr("height",A>0?A:0),l&&(i.width(L),P.select(".nv-legendWrap").datum(m).call(i),o.top!=i.height()&&(o.top=i.height(),A=(f||parseInt(C.style("height"))||400)-o.top-o.bottom),_.select(".nv-legendWrap").attr("transform","translate(0,"+ -o.top+")")),_.attr("transform","translate("+o.left+","+o.top+")"),p&&P.select(".nv-y.nv-axis").attr("transform","translate("+L+",0)"),d&&(s.width(L).height(A).margin({left:o.left,top:o.top}).svgContainer(C).xScale(g),_.select(".nv-interactive").call(s)),t.width(L).height(A).color(m.map(function(e,t){return e.color||u(e,t)}).filter(function(e,t){return!m[t].disabled}));var H=P.select(".nv-linesWrap").datum(m.filter(function(e){return!e.disabled}));H.transition().call(t),c&&(n.scale(g).ticks(L/100).tickSize(-A,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+y.range()[0]+")"),P.select(".nv-x.nv-axis").transition().call(n)),h&&(r.scale(y).ticks(A/36).tickSize(-L,0),P.select(".nv-y.nv-axis").transition().call(r)),i.dispatch.on("stateChange",function(e){b=e,S.stateChange(b),N.update()}),s.dispatch.on("elementMousemove",function(i){t.clearHighlights();var a,f,l,c=[];m.filter(function(e,t){return e.seriesIndex=t,!e.disabled}).forEach(function(n,r){f=e.interactiveBisect(n.values,i.pointXValue,N.x()),t.highlightPoint(r,f,!0);var s=n.values[f];if(typeof s=="undefined")return;typeof a=="undefined"&&(a=s),typeof l=="undefined"&&(l=N.xScale()(N.x()(s,f))),c.push({key:n.key,value:N.y()(s,f),color:u(n,n.seriesIndex)})});if(c.length>2){var h=N.yScale().invert(i.mouseY),p=Math.abs(N.yScale().domain()[0]-N.yScale().domain()[1]),d=.03*p,g=e.nearestValueIndex(c.map(function(e){return e.value}),h,d);g!==null&&(c[g].highlight=!0)}var y=n.tickFormat()(N.x()(a,f));s.tooltip.position({left:l+o.left,top:i.mouseY+o.top}).chartContainer(k.parentNode).enabled(v).valueFormatter(function(e,t){return r.tickFormat()(e)}).data({value:y,series:c})(),s.renderGuideLine(l)}),s.dispatch.on("elementMouseout",function(e){S.tooltipHide(),t.clearHighlights()}),S.on("tooltipShow",function(e){v&&T(e,k.parentNode)}),S.on("changeState",function(e){typeof e.disabled!="undefined"&&m.length===e.disabled.length&&(m.forEach(function(t,n){t.disabled=e.disabled[n]}),b.disabled=e.disabled),N.update()})}),N}var t=e.models.line(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.interactiveGuideline(),o={top:30,right:20,bottom:50,left:60},u=e.utils.defaultColor(),a=null,f=null,l=!0,c=!0,h=!0,p=!1,d=!1,v=!0,m=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},g,y,b={},w=null,E="No Data Available.",S=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),x=250;n.orient("bottom").tickPadding(7),r.orient(p?"right":"left");var T=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=m(i.series.key,a,f,i,N);e.tooltip.show([o,u],l,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],S.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),S.on("tooltipHide",function(){v&&e.tooltip.cleanup()}),N.dispatch=S,N.lines=t,N.legend=i,N.xAxis=n,N.yAxis=r,N.interactiveLayer=s,d3.rebind(N,t,"defined","isArea","x","y","size","xScale","yScale","xDomain","yDomain","xRange","yRange","forceX","forceY","interactive","clipEdge","clipVoronoi","useVoronoi","id","interpolate"),N.options=e.utils.optionsFunc.bind(N),N.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,N):o},N.width=function(e){return arguments.length?(a=e,N):a},N.height=function(e){return arguments.length?(f=e,N):f},N.color=function(t){return arguments.length?(u=e.utils.getColor(t),i.color(u),N):u},N.showLegend=function(e){return arguments.length?(l=e,N):l},N.showXAxis=function(e){return arguments.length?(c=e,N):c},N.showYAxis=function(e){return arguments.length?(h=e,N):h},N.rightAlignYAxis=function(e){return arguments.length?(p=e,r.orient(e?"right":"left"),N):p},N.useInteractiveGuideline=function(e){return arguments.length?(d=e,e===!0&&(N.interactive(!1),N.useVoronoi(!1)),N):d},N.tooltips=function(e){return arguments.length?(v=e,N):v},N.tooltipContent=function(e){return arguments.length?(m=e,N):m},N.state=function(e){return arguments.length?(b=e,N):b},N.defaultState=function(e){return arguments.length?(w=e,N):w},N.noData=function(e){return arguments.length?(E=e,N):E},N.transitionDuration=function(e){return arguments.length?(x=e,N):x},N},e.models.linePlusBarChart=function(){"use strict";function T(e){return e.each(function(e){var l=d3.select(this),c=this,v=(a||parseInt(l.style("width"))||960)-u.left-u.right,N=(f||parseInt(l.style("height"))||400)-u.top-u.bottom;T.update=function(){l.transition().call(T)},b.disabled=e.map(function(e){return!!e.disabled});if(!w){var C;w={};for(C in b)b[C]instanceof Array?w[C]=b[C].slice(0):w[C]=b[C]}if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var k=l.selectAll(".nv-noData").data([E]);return k.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),k.attr("x",u.left+v/2).attr("y",u.top+N/2).text(function(e){return e}),T}l.selectAll(".nv-noData").remove();var L=e.filter(function(e){return!e.disabled&&e.bar}),A=e.filter(function(e){return!e.bar});m=A.filter(function(e){return!e.disabled}).length&&A.filter(function(e){return!e.disabled})[0].values.length?t.xScale():n.xScale(),g=n.yScale(),y=t.yScale();var O=d3.select(this).selectAll("g.nv-wrap.nv-linePlusBar").data([e]),M=O.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),_=O.select("g");M.append("g").attr("class","nv-x nv-axis"),M.append("g").attr("class","nv-y1 nv-axis"),M.append("g").attr("class","nv-y2 nv-axis"),M.append("g").attr("class","nv-barsWrap"),M.append("g").attr("class","nv-linesWrap"),M.append("g").attr("class","nv-legendWrap"),p&&(o.width(v/2),_.select(".nv-legendWrap").datum(e.map(function(e){return e.originalKey=e.originalKey===undefined?e.key:e.originalKey,e.key=e.originalKey+(e.bar?" (left axis)":" (right axis)"),e})).call(o),u.top!=o.height()&&(u.top=o.height(),N=(f||parseInt(l.style("height"))||400)-u.top-u.bottom),_.select(".nv-legendWrap").attr("transform","translate("+v/2+","+ -u.top+")")),O.attr("transform","translate("+u.left+","+u.top+")"),t.width(v).height(N).color(e.map(function(e,t){return e.color||h(e,t)}).filter(function(t,n){return!e[n].disabled&&!e[n].bar})),n.width(v).height(N).color(e.map(function(e,t){return e.color||h(e,t)}).filter(function(t,n){return!e[n].disabled&&e[n].bar}));var D=_.select(".nv-barsWrap").datum(L.length?L:[{values:[]}]),P=_.select(".nv-linesWrap").datum(A[0]&&!A[0].disabled?A:[{values:[]}]);d3.transition(D).call(n),d3.transition(P).call(t),r.scale(m).ticks(v/100).tickSize(-N,0),_.select(".nv-x.nv-axis").attr("transform","translate(0,"+g.range()[0]+")"),d3.transition(_.select(".nv-x.nv-axis")).call(r),i.scale(g).ticks(N/36).tickSize(-v,0),d3.transition(_.select(".nv-y1.nv-axis")).style("opacity",L.length?1:0).call(i),s.scale(y).ticks(N/36).tickSize(L.length?0:-v,0),_.select(".nv-y2.nv-axis").style("opacity",A.length?1:0).attr("transform","translate("+v+",0)"),d3.transition(_.select(".nv-y2.nv-axis")).call(s),o.dispatch.on("stateChange",function(e){b=e,S.stateChange(b),T.update()}),S.on("tooltipShow",function(e){d&&x(e,c.parentNode)}),S.on("changeState",function(t){typeof t.disabled!="undefined"&&(e.forEach(function(e,n){e.disabled=t.disabled[n]}),b.disabled=t.disabled),T.update()})}),T}var t=e.models.line(),n=e.models.historicalBar(),r=e.models.axis(),i=e.models.axis(),s=e.models.axis(),o=e.models.legend(),u={top:30,right:60,bottom:50,left:60},a=null,f=null,l=function(e){return e.x},c=function(e){return e.y},h=e.utils.defaultColor(),p=!0,d=!0,v=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},m,g,y,b={},w=null,E="No Data Available.",S=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState");n.padData(!0),t.clipEdge(!1).padData(!0),r.orient("bottom").tickPadding(7).highlightZero(!1),i.orient("left"),s.orient("right");var x=function(n,o){var u=n.pos[0]+(o.offsetLeft||0),a=n.pos[1]+(o.offsetTop||0),f=r.tickFormat()(t.x()(n.point,n.pointIndex)),l=(n.series.bar?i:s).tickFormat()(t.y()(n.point,n.pointIndex)),c=v(n.series.key,f,l,n,T);e.tooltip.show([u,a],c,n.value<0?"n":"s",null,o)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],S.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),n.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],S.tooltipShow(e)}),n.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),S.on("tooltipHide",function(){d&&e.tooltip.cleanup()}),T.dispatch=S,T.legend=o,T.lines=t,T.bars=n,T.xAxis=r,T.y1Axis=i,T.y2Axis=s,d3.rebind(T,t,"defined","size","clipVoronoi","interpolate"),T.options=e.utils.optionsFunc.bind(T),T.x=function(e){return arguments.length?(l=e,t.x(e),n.x(e),T):l},T.y=function(e){return arguments.length?(c=e,t.y(e),n.y(e),T):c},T.margin=function(e){return arguments.length?(u.top=typeof e.top!="undefined"?e.top:u.top,u.right=typeof e.right!="undefined"?e.right:u.right,u.bottom=typeof e.bottom!="undefined"?e.bottom:u.bottom,u.left=typeof e.left!="undefined"?e.left:u.left,T):u},T.width=function(e){return arguments.length?(a=e,T):a},T.height=function(e){return arguments.length?(f=e,T):f},T.color=function(t){return arguments.length?(h=e.utils.getColor(t),o.color(h),T):h},T.showLegend=function(e){return arguments.length?(p=e,T):p},T.tooltips=function(e){return arguments.length?(d=e,T):d},T.tooltipContent=function(e){return arguments.length?(v=e,T):v},T.state=function(e){return arguments.length?(b=e,T):b},T.defaultState=function(e){return arguments.length?(w=e,T):w},T.noData=function(e){return arguments.length?(E=e,T):E},T},e.models.lineWithFocusChart=function(){"use strict";function k(e){return e.each(function(e){function U(e){var t=+(e=="e"),n=t?1:-1,r=M/3;return"M"+.5*n+","+r+"A6,6 0 0 "+t+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+t+" "+.5*n+","+2*r+"Z"+"M"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}function z(){a.empty()||a.extent(w),I.data([a.empty()?g.domain():w]).each(function(e,t){var n=g(e[0])-v.range()[0],r=v.range()[1]-g(e[1]);d3.select(this).select(".left").attr("width",n<0?0:n),d3.select(this).select(".right").attr("x",g(e[1])).attr("width",r<0?0:r)})}function W(){w=a.empty()?null:a.extent();var n=a.empty()?g.domain():a.extent();if(Math.abs(n[0]-n[1])<=1)return;T.brush({extent:n,brush:a}),z();var s=H.select(".nv-focus .nv-linesWrap").datum(e.filter(function(e){return!e.disabled}).map(function(e,r){return{key:e.key,values:e.values.filter(function(e,r){return t.x()(e,r)>=n[0]&&t.x()(e,r)<=n[1]})}}));s.transition().duration(N).call(t),H.select(".nv-focus .nv-x.nv-axis").transition().duration(N).call(r),H.select(".nv-focus .nv-y.nv-axis").transition().duration(N).call(i)}var S=d3.select(this),L=this,A=(h||parseInt(S.style("width"))||960)-f.left-f.right,O=(p||parseInt(S.style("height"))||400)-f.top-f.bottom-d,M=d-l.top-l.bottom;k.update=function(){S.transition().duration(N).call(k)},k.container=this;if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var _=S.selectAll(".nv-noData").data([x]);return _.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),_.attr("x",f.left+A/2).attr("y",f.top+O/2).text(function(e){return e}),k}S.selectAll(".nv-noData").remove(),v=t.xScale(),m=t.yScale(),g=n.xScale(),y=n.yScale();var D=S.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([e]),P=D.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),H=D.select("g");P.append("g").attr("class","nv-legendWrap");var B=P.append("g").attr("class","nv-focus");B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis"),B.append("g").attr("class","nv-linesWrap");var j=P.append("g").attr("class","nv-context");j.append("g").attr("class","nv-x nv-axis"),j.append("g").attr("class","nv-y nv-axis"),j.append("g").attr("class","nv-linesWrap"),j.append("g").attr("class","nv-brushBackground"),j.append("g").attr("class","nv-x nv-brush"),b&&(u.width(A),H.select(".nv-legendWrap").datum(e).call(u),f.top!=u.height()&&(f.top=u.height(),O=(p||parseInt(S.style("height"))||400)-f.top-f.bottom-d),H.select(".nv-legendWrap").attr("transform","translate(0,"+ -f.top+")")),D.attr("transform","translate("+f.left+","+f.top+")"),t.width(A).height(O).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),n.defined(t.defined()).width(A).height(M).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),H.select(".nv-context").attr("transform","translate(0,"+(O+f.bottom+l.top)+")");var F=H.select(".nv-context .nv-linesWrap").datum(e.filter(function(e){return!e.disabled}));d3.transition(F).call(n),r.scale(v).ticks(A/100).tickSize(-O,0),i.scale(m).ticks(O/36).tickSize(-A,0),H.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+O+")"),a.x(g).on("brush",function(){var e=k.transitionDuration();k.transitionDuration(0),W(),k.transitionDuration(e)}),w&&a.extent(w);var I=H.select(".nv-brushBackground").selectAll("g").data([w||a.extent()]),q=I.enter().append("g");q.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),q.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var R=H.select(".nv-x.nv-brush").call(a);R.selectAll("rect").attr("height",M),R.selectAll(".resize").append("path").attr("d",U),W(),s.scale(g).ticks(A/100).tickSize(-M,0),H.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+y.range()[0]+")"),d3.transition(H.select(".nv-context .nv-x.nv-axis")).call(s),o.scale(y).ticks(M/36).tickSize(-A,0),d3.transition(H.select(".nv-context .nv-y.nv-axis")).call(o),H.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+y.range()[0]+")"),u.dispatch.on("stateChange",function(e){k.update()}),T.on("tooltipShow",function(e){E&&C(e,L.parentNode)})}),k}var t=e.models.line(),n=e.models.line(),r=e.models.axis(),i=e.models.axis(),s=e.models.axis(),o=e.models.axis(),u=e.models.legend(),a=d3.svg.brush(),f={top:30,right:30,bottom:30,left:60},l={top:0,right:30,bottom:20,left:60},c=e.utils.defaultColor(),h=null,p=null,d=100,v,m,g,y,b=!0,w=null,E=!0,S=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},x="No Data Available.",T=d3.dispatch("tooltipShow","tooltipHide","brush"),N=250;t.clipEdge(!0),n.interactive(!1),r.orient("bottom").tickPadding(5),i.orient("left"),s.orient("bottom").tickPadding(5),o.orient("left");var C=function(n,s){var o=n.pos[0]+(s.offsetLeft||0),u=n.pos[1]+(s.offsetTop||0),a=r.tickFormat()(t.x()(n.point,n.pointIndex)),f=i.tickFormat()(t.y()(n.point,n.pointIndex)),l=S(n.series.key,a,f,n,k);e.tooltip.show([o,u],l,null,null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+f.left,e.pos[1]+f.top],T.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),T.on("tooltipHide",function(){E&&e.tooltip.cleanup()}),k.dispatch=T,k.legend=u,k.lines=t,k.lines2=n,k.xAxis=r,k.yAxis=i,k.x2Axis=s,k.y2Axis=o,d3.rebind(k,t,"defined","isArea","size","xDomain","yDomain","xRange","yRange","forceX","forceY","interactive","clipEdge","clipVoronoi","id"),k.options=e.utils.optionsFunc.bind(k),k.x=function(e){return arguments.length?(t.x(e),n.x(e),k):t.x},k.y=function(e){return arguments.length?(t.y(e),n.y(e),k):t.y},k.margin=function(e){return arguments.length?(f.top=typeof e.top!="undefined"?e.top:f.top,f.right=typeof e.right!="undefined"?e.right:f.right,f.bottom=typeof e.bottom!="undefined"?e.bottom:f.bottom,f.left=typeof e.left!="undefined"?e.left:f.left,k):f},k.margin2=function(e){return arguments.length?(l=e,k):l},k.width=function(e){return arguments.length?(h=e,k):h},k.height=function(e){return arguments.length?(p=e,k):p},k.height2=function(e){return arguments.length?(d=e,k):d},k.color=function(t){return arguments.length?(c=e.utils.getColor(t),u.color(c),k):c},k.showLegend=function(e){return arguments.length?(b=e,k):b},k.tooltips=function(e){return arguments.length?(E=e,k):E},k.tooltipContent=function(e){return arguments.length?(S=e,k):S},k.interpolate=function(e){return arguments.length?(t.interpolate(e),n.interpolate(e),k):t.interpolate()},k.noData=function(e){return arguments.length?(x=e,k):x},k.xTickFormat=function(e){return arguments.length?(r.tickFormat(e),s.tickFormat(e),k):r.tickFormat()},k.yTickFormat=function(e){return arguments.length?(i.tickFormat(e),o.tickFormat(e),k):i.tickFormat()},k.brushExtent=function(e){return arguments.length?(w=e,k):w},k.transitionDuration=function(e){return arguments.length?(N=e,k):N},k},e.models.linePlusBarWithFocusChart=function(){"use strict";function B(e){return e.each(function(e){function nt(e){var t=+(e=="e"),n=t?1:-1,r=q/3;return"M"+.5*n+","+r+"A6,6 0 0 "+t+" "+6.5*n+","+(r+6)+"V"+(2*r-6)+"A6,6 0 0 "+t+" "+.5*n+","+2*r+"Z"+"M"+2.5*n+","+(r+8)+"V"+(2*r-8)+"M"+4.5*n+","+(r+8)+"V"+(2*r-8)}function rt(){h.empty()||h.extent(x),Z.data([h.empty()?k.domain():x]).each(function(e,t){var n=k(e[0])-k.range()[0],r=k.range()[1]-k(e[1]);d3.select(this).select(".left").attr("width",n<0?0:n),d3.select(this).select(".right").attr("x",k(e[1])).attr("width",r<0?0:r)})}function it(){x=h.empty()?null:h.extent(),S=h.empty()?k.domain():h.extent(),D.brush({extent:S,brush:h}),rt(),r.width(F).height(I).color(e.map(function(e,t){return e.color||w(e,t)}).filter(function(t,n){return!e[n].disabled&&e[n].bar})),t.width(F).height(I).color(e.map(function(e,t){return e.color||w(e,t)}).filter(function(t,n){return!e[n].disabled&&!e[n].bar}));var n=J.select(".nv-focus .nv-barsWrap").datum(U.length?U.map(function(e,t){return{key:e.key,values:e.values.filter(function(e,t){return r.x()(e,t)>=S[0]&&r.x()(e,t)<=S[1]})}}):[{values:[]}]),i=J.select(".nv-focus .nv-linesWrap").datum(z[0].disabled?[{values:[]}]:z.map(function(e,n){return{key:e.key,values:e.values.filter(function(e,n){return t.x()(e,n)>=S[0]&&t.x()(e,n)<=S[1]})}}));U.length?C=r.xScale():C=t.xScale(),s.scale(C).ticks(F/100).tickSize(-I,0),s.domain([Math.ceil(S[0]),Math.floor(S[1])]),J.select(".nv-x.nv-axis").transition().duration(P).call(s),n.transition().duration(P).call(r),i.transition().duration(P).call(t),J.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L.range()[0]+")"),u.scale(L).ticks(I/36).tickSize(-F,0),J.select(".nv-focus .nv-y1.nv-axis").style("opacity",U.length?1:0),a.scale(A).ticks(I/36).tickSize(U.length?0:-F,0),J.select(".nv-focus .nv-y2.nv-axis").style("opacity",z.length?1:0).attr("transform","translate("+C.range()[1]+",0)"),J.select(".nv-focus .nv-y1.nv-axis").transition().duration(P).call(u),J.select(".nv-focus .nv-y2.nv-axis").transition().duration(P).call(a)}var N=d3.select(this),j=this,F=(v||parseInt(N.style("width"))||960)-p.left-p.right,I=(m||parseInt(N.style("height"))||400)-p.top-p.bottom-g,q=g-d.top-d.bottom;B.update=function(){N.transition().duration(P).call(B)},B.container=this;if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var R=N.selectAll(".nv-noData").data([_]);return R.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),R.attr("x",p.left+F/2).attr("y",p.top+I/2).text(function(e){return e}),B}N.selectAll(".nv-noData").remove();var U=e.filter(function(e){return!e.disabled&&e.bar}),z=e.filter(function(e){return!e.bar});C=r.xScale(),k=o.scale(),L=r.yScale(),A=t.yScale(),O=i.yScale(),M=n.yScale();var W=e.filter(function(e){return!e.disabled&&e.bar}).map(function(e){return e.values.map(function(e,t){return{x:y(e,t),y:b(e,t)}})}),X=e.filter(function(e){return!e.disabled&&!e.bar}).map(function(e){return e.values.map(function(e,t){return{x:y(e,t),y:b(e,t)}})});C.range([0,F]),k.domain(d3.extent(d3.merge(W.concat(X)),function(e){return e.x})).range([0,F]);var V=N.selectAll("g.nv-wrap.nv-linePlusBar").data([e]),$=V.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),J=V.select("g");$.append("g").attr("class","nv-legendWrap");var K=$.append("g").attr("class","nv-focus");K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y1 nv-axis"),K.append("g").attr("class","nv-y2 nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-linesWrap");var Q=$.append("g").attr("class","nv-context");Q.append("g").attr("class","nv-x nv-axis"),Q.append("g").attr("class","nv-y1 nv-axis"),Q.append("g").attr("class","nv-y2 nv-axis"),Q.append("g").attr("class","nv-barsWrap"),Q.append("g").attr("class","nv-linesWrap"),Q.append("g").attr("class","nv-brushBackground"),Q.append("g").attr("class","nv-x nv-brush"),E&&(c.width(F/2),J.select(".nv-legendWrap").datum(e.map(function(e){return e.originalKey=e.originalKey===undefined?e.key:e.originalKey,e.key=e.originalKey+(e.bar?" (left axis)":" (right axis)"),e})).call(c),p.top!=c.height()&&(p.top=c.height(),I=(m||parseInt(N.style("height"))||400)-p.top-p.bottom-g),J.select(".nv-legendWrap").attr("transform","translate("+F/2+","+ -p.top+")")),V.attr("transform","translate("+p.left+","+p.top+")"),i.width(F).height(q).color(e.map(function(e,t){return e.color||w(e,t)}).filter(function(t,n){return!e[n].disabled&&e[n].bar})),n.width(F).height(q).color(e.map(function(e,t){return e.color||w(e,t)}).filter(function(t,n){return!e[n].disabled&&!e[n].bar}));var G=J.select(".nv-context .nv-barsWrap").datum(U.length?U:[{values:[]}]),Y=J.select(".nv-context .nv-linesWrap").datum(z[0].disabled?[{values:[]}]:z);J.select(".nv-context").attr("transform","translate(0,"+(I+p.bottom+d.top)+")"),G.transition().call(i),Y.transition().call(n),h.x(k).on("brush",it),x&&h.extent(x);var Z=J.select(".nv-brushBackground").selectAll("g").data([x||h.extent()]),et=Z.enter().append("g");et.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",q),et.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",q);var tt=J.select(".nv-x.nv-brush").call(h);tt.selectAll("rect").attr("height",q),tt.selectAll(".resize").append("path").attr("d",nt),o.ticks(F/100).tickSize(-q,0),J.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+O.range()[0]+")"),J.select(".nv-context .nv-x.nv-axis").transition().call(o),f.scale(O).ticks(q/36).tickSize(-F,0),J.select(".nv-context .nv-y1.nv-axis").style("opacity",U.length?1:0).attr("transform","translate(0,"+k.range()[0]+")"),J.select(".nv-context .nv-y1.nv-axis").transition().call(f),l.scale(M).ticks(q/36).tickSize(U.length?0:-F,0),J.select(".nv-context .nv-y2.nv-axis").style("opacity",z.length?1:0).attr("transform","translate("+k.range()[1]+",0)"),J.select(".nv-context .nv-y2.nv-axis").transition().call(l),c.dispatch.on("stateChange",function(e){B.update()}),D.on("tooltipShow",function(e){T&&H(e,j.parentNode)}),it()}),B}var t=e.models.line(),n=e.models.line(),r=e.models.historicalBar(),i=e.models.historicalBar(),s=e.models.axis(),o=e.models.axis(),u=e.models.axis(),a=e.models.axis(),f=e.models.axis(),l=e.models.axis(),c=e.models.legend(),h=d3.svg.brush(),p={top:30,right:30,bottom:30,left:60},d={top:0,right:30,bottom:20,left:60},v=null,m=null,g=100,y=function(e){return e.x},b=function(e){return e.y},w=e.utils.defaultColor(),E=!0,S,x=null,T=!0,N=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},C,k,L,A,O,M,_="No Data Available.",D=d3.dispatch("tooltipShow","tooltipHide","brush"),P=0;t.clipEdge(!0),n.interactive(!1),s.orient("bottom").tickPadding(5),u.orient("left"),a.orient("right"),o.orient("bottom").tickPadding(5),f.orient("left"),l.orient("right");var H=function(n,r){S&&(n.pointIndex+=Math.ceil(S[0]));var i=n.pos[0]+(r.offsetLeft||0),o=n.pos[1]+(r.offsetTop||0),f=s.tickFormat()(t.x()(n.point,n.pointIndex)),l=(n.series.bar?u:a).tickFormat()(t.y()(n.point,n.pointIndex)),c=N(n.series.key,f,l,n,B);e.tooltip.show([i,o],c,n.value<0?"n":"s",null,r)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+p.left,e.pos[1]+p.top],D.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){D.tooltipHide(e)}),r.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+p.left,e.pos[1]+p.top],D.tooltipShow(e)}),r.dispatch.on("elementMouseout.tooltip",function(e){D.tooltipHide(e)}),D.on("tooltipHide",function(){T&&e.tooltip.cleanup()}),B.dispatch=D,B.legend=c,B.lines=t,B.lines2=n,B.bars=r,B.bars2=i,B.xAxis=s,B.x2Axis=o,B.y1Axis=u,B.y2Axis=a,B.y3Axis=f,B.y4Axis=l,d3.rebind(B,t,"defined","size","clipVoronoi","interpolate"),B.options=e.utils.optionsFunc.bind(B),B.x=function(e){return arguments.length?(y=e,t.x(e),r.x(e),B):y},B.y=function(e){return arguments.length?(b=e,t.y(e),r.y(e),B):b},B.margin=function(e){return arguments.length?(p.top=typeof e.top!="undefined"?e.top:p.top,p.right=typeof e.right!="undefined"?e.right:p.right,p.bottom=typeof e.bottom!="undefined"?e.bottom:p.bottom,p.left=typeof e.left!="undefined"?e.left:p.left,B):p},B.width=function(e){return arguments.length?(v=e,B):v},B.height=function(e){return arguments.length?(m=e,B):m},B.color=function(t){return arguments.length?(w=e.utils.getColor(t),c.color(w),B):w},B.showLegend=function(e){return arguments.length?(E=e,B):E},B.tooltips=function(e){return arguments.length?(T=e,B):T},B.tooltipContent=function(e){return arguments.length?(N=e,B):N},B.noData=function(e){return arguments.length?(_=e,B):_},B.brushExtent=function(e){return arguments.length?(x=e,B):x},B},e.models.multiBar=function(){"use strict";function C(e){return e.each(function(e){var C=n-t.left-t.right,k=r-t.top-t.bottom,L=d3.select(this);d&&e.length&&(d=[{values:e[0].values.map(function(e){return{x:e.x,y:0,series:e.series,size:.01}})}]),c&&(e=d3.layout.stack().offset(h).values(function(e){return e.values}).y(a)(!e.length&&d?d:e)),e.forEach(function(e,t){e.values.forEach(function(e){e.series=t})}),c&&e[0].values.map(function(t,n){var r=0,i=0;e.map(function(e){var t=e.values[n];t.size=Math.abs(t.y),t.y<0?(t.y1=i,i-=t.size):(t.y1=t.size+r,r+=t.size)})});var A=y&&b?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0,y1:e.y1}})});i.domain(y||d3.merge(A).map(function(e){return e.x})).rangeBands(w||[0,C],S),s.domain(b||d3.extent(d3.merge(A).map(function(e){return c?e.y>0?e.y1:e.y1+e.y:e.y}).concat(f))).range(E||[k,0]),i.domain()[0]===i.domain()[1]&&(i.domain()[0]?i.domain([i.domain()[0]-i.domain()[0]*.01,i.domain()[1]+i.domain()[1]*.01]):i.domain([-1,1])),s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]+s.domain()[0]*.01,s.domain()[1]-s.domain()[1]*.01]):s.domain([-1,1])),T=T||i,N=N||s;var O=L.selectAll("g.nv-wrap.nv-multibar").data([e]),M=O.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),_=M.append("defs"),D=M.append("g"),P=O.select("g");D.append("g").attr("class","nv-groups"),O.attr("transform","translate("+t.left+","+t.top+")"),_.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),O.select("#nv-edge-clip-"+o+" rect").attr("width",C).attr("height",k),P.attr("clip-path",l?"url(#nv-edge-clip-"+o+")":"");var H=O.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e,t){return t});H.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),H.exit().transition().selectAll("rect.nv-bar").delay(function(t,n){return n*g/e[0].values.length}).attr("y",function(e){return c?N(e.y0):N(0)}).attr("height",0).remove(),H.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return p(e,t)}).style("stroke",function(e,t){return p(e,t)}),H.transition().style("stroke-opacity",1).style("fill-opacity",.75);var B=H.selectAll("rect.nv-bar").data(function(t){return d&&!e.length?d.values:t.values});B.exit().remove();var j=B.enter().append("rect").attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(t,n,r){return c?0:r*i.rangeBand()/e.length}).attr("y",function(e){return N(c?e.y0:0)}).attr("height",0).attr("width",i.rangeBand()/(c?1:e.length)).attr("transform",function(e,t){return"translate("+i(u(e,t))+",0)"});B.style("fill",function(e,t,n){return p(e,n,t)}).style("stroke",function(e,t,n){return p(e,n,t)}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),x.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),x.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){x.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length
|
4 |
+
,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){x.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[i(u(t,n))+i.rangeBand()*(c?e.length/2:t.series+.5)/e.length,s(a(t,n)+(c?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}),B.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}).transition().attr("transform",function(e,t){return"translate("+i(u(e,t))+",0)"}),v&&(m||(m=e.map(function(){return!0})),B.style("fill",function(e,t,n){return d3.rgb(v(e,t)).darker(m.map(function(e,t){return t}).filter(function(e,t){return!m[t]})[n]).toString()}).style("stroke",function(e,t,n){return d3.rgb(v(e,t)).darker(m.map(function(e,t){return t}).filter(function(e,t){return!m[t]})[n]).toString()})),c?B.transition().delay(function(t,n){return n*g/e[0].values.length}).attr("y",function(e,t){return s(c?e.y1:0)}).attr("height",function(e,t){return Math.max(Math.abs(s(e.y+(c?e.y0:0))-s(c?e.y0:0)),1)}).attr("x",function(t,n){return c?0:t.series*i.rangeBand()/e.length}).attr("width",i.rangeBand()/(c?1:e.length)):B.transition().delay(function(t,n){return n*g/e[0].values.length}).attr("x",function(t,n){return t.series*i.rangeBand()/e.length}).attr("width",i.rangeBand()/e.length).attr("y",function(e,t){return a(e,t)<0?s(0):s(0)-s(a(e,t))<1?s(0)-1:s(a(e,t))||0}).attr("height",function(e,t){return Math.max(Math.abs(s(a(e,t))-s(0)),1)||0}),T=i.copy(),N=s.copy()}),C}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=d3.scale.ordinal(),s=d3.scale.linear(),o=Math.floor(Math.random()*1e4),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=!0,c=!1,h="zero",p=e.utils.defaultColor(),d=!1,v=null,m,g=1200,y,b,w,E,S=.1,x=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),T,N;return C.dispatch=x,C.options=e.utils.optionsFunc.bind(C),C.x=function(e){return arguments.length?(u=e,C):u},C.y=function(e){return arguments.length?(a=e,C):a},C.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,C):t},C.width=function(e){return arguments.length?(n=e,C):n},C.height=function(e){return arguments.length?(r=e,C):r},C.xScale=function(e){return arguments.length?(i=e,C):i},C.yScale=function(e){return arguments.length?(s=e,C):s},C.xDomain=function(e){return arguments.length?(y=e,C):y},C.yDomain=function(e){return arguments.length?(b=e,C):b},C.xRange=function(e){return arguments.length?(w=e,C):w},C.yRange=function(e){return arguments.length?(E=e,C):E},C.forceY=function(e){return arguments.length?(f=e,C):f},C.stacked=function(e){return arguments.length?(c=e,C):c},C.stackOffset=function(e){return arguments.length?(h=e,C):h},C.clipEdge=function(e){return arguments.length?(l=e,C):l},C.color=function(t){return arguments.length?(p=e.utils.getColor(t),C):p},C.barColor=function(t){return arguments.length?(v=e.utils.getColor(t),C):v},C.disabled=function(e){return arguments.length?(m=e,C):m},C.id=function(e){return arguments.length?(o=e,C):o},C.hideable=function(e){return arguments.length?(d=e,C):d},C.delay=function(e){return arguments.length?(g=e,C):g},C.groupSpacing=function(e){return arguments.length?(S=e,C):S},C},e.models.multiBarChart=function(){"use strict";function A(e){return e.each(function(e){var b=d3.select(this),O=this,M=(u||parseInt(b.style("width"))||960)-o.left-o.right,_=(a||parseInt(b.style("height"))||400)-o.top-o.bottom;A.update=function(){b.transition().duration(k).call(A)},A.container=this,S.disabled=e.map(function(e){return!!e.disabled});if(!x){var D;x={};for(D in S)S[D]instanceof Array?x[D]=S[D].slice(0):x[D]=S[D]}if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var P=b.selectAll(".nv-noData").data([T]);return P.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),P.attr("x",o.left+M/2).attr("y",o.top+_/2).text(function(e){return e}),A}b.selectAll(".nv-noData").remove(),w=t.xScale(),E=t.yScale();var H=b.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([e]),B=H.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),j=H.select("g");B.append("g").attr("class","nv-x nv-axis"),B.append("g").attr("class","nv-y nv-axis"),B.append("g").attr("class","nv-barsWrap"),B.append("g").attr("class","nv-legendWrap"),B.append("g").attr("class","nv-controlsWrap"),c&&(i.width(M-C()),t.barColor()&&e.forEach(function(e,t){e.color=d3.rgb("#ccc").darker(t*1.5).toString()}),j.select(".nv-legendWrap").datum(e).call(i),o.top!=i.height()&&(o.top=i.height(),_=(a||parseInt(b.style("height"))||400)-o.top-o.bottom),j.select(".nv-legendWrap").attr("transform","translate("+C()+","+ -o.top+")"));if(l){var F=[{key:"Grouped",disabled:t.stacked()},{key:"Stacked",disabled:!t.stacked()}];s.width(C()).color(["#444","#444","#444"]),j.select(".nv-controlsWrap").datum(F).attr("transform","translate(0,"+ -o.top+")").call(s)}H.attr("transform","translate("+o.left+","+o.top+")"),d&&j.select(".nv-y.nv-axis").attr("transform","translate("+M+",0)"),t.disabled(e.map(function(e){return e.disabled})).width(M).height(_).color(e.map(function(e,t){return e.color||f(e,t)}).filter(function(t,n){return!e[n].disabled}));var I=j.select(".nv-barsWrap").datum(e.filter(function(e){return!e.disabled}));I.transition().call(t);if(h){n.scale(w).ticks(M/100).tickSize(-_,0),j.select(".nv-x.nv-axis").attr("transform","translate(0,"+E.range()[0]+")"),j.select(".nv-x.nv-axis").transition().call(n);var q=j.select(".nv-x.nv-axis > g").selectAll("g");q.selectAll("line, text").style("opacity",1);if(m){var R=function(e,t){return"translate("+e+","+t+")"},U=5,z=17;q.selectAll("text").attr("transform",function(e,t,n){return R(0,n%2==0?U:z)});var W=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;j.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(e,t){return R(0,t===0||W%2!==0?z:U)})}v&&q.filter(function(t,n){return n%Math.ceil(e[0].values.length/(M/100))!==0}).selectAll("text, line").style("opacity",0),g&&q.selectAll(".tick text").attr("transform","rotate("+g+" 0,0)").style("text-anchor",g>0?"start":"end"),j.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}p&&(r.scale(E).ticks(_/36).tickSize(-M,0),j.select(".nv-y.nv-axis").transition().call(r)),i.dispatch.on("stateChange",function(e){S=e,N.stateChange(S),A.update()}),s.dispatch.on("legendClick",function(e,n){if(!e.disabled)return;F=F.map(function(e){return e.disabled=!0,e}),e.disabled=!1;switch(e.key){case"Grouped":t.stacked(!1);break;case"Stacked":t.stacked(!0)}S.stacked=t.stacked(),N.stateChange(S),A.update()}),N.on("tooltipShow",function(e){y&&L(e,O.parentNode)}),N.on("changeState",function(n){typeof n.disabled!="undefined"&&(e.forEach(function(e,t){e.disabled=n.disabled[t]}),S.disabled=n.disabled),typeof n.stacked!="undefined"&&(t.stacked(n.stacked),S.stacked=n.stacked),A.update()})}),A}var t=e.models.multiBar(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o={top:30,right:20,bottom:50,left:60},u=null,a=null,f=e.utils.defaultColor(),l=!0,c=!0,h=!0,p=!0,d=!1,v=!0,m=!1,g=0,y=!0,b=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" on "+t+"</p>"},w,E,S={stacked:!1},x=null,T="No Data Available.",N=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),C=function(){return l?180:0},k=250;t.stacked(!1),n.orient("bottom").tickPadding(7).highlightZero(!0).showMaxMin(!1).tickFormat(function(e){return e}),r.orient(d?"right":"left").tickFormat(d3.format(",.1f")),s.updateState(!1);var L=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=b(i.series.key,a,f,i,A);e.tooltip.show([o,u],l,i.value<0?"n":"s",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],N.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){N.tooltipHide(e)}),N.on("tooltipHide",function(){y&&e.tooltip.cleanup()}),A.dispatch=N,A.multibar=t,A.legend=i,A.xAxis=n,A.yAxis=r,d3.rebind(A,t,"x","y","xDomain","yDomain","xRange","yRange","forceX","forceY","clipEdge","id","stacked","stackOffset","delay","barColor","groupSpacing"),A.options=e.utils.optionsFunc.bind(A),A.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,A):o},A.width=function(e){return arguments.length?(u=e,A):u},A.height=function(e){return arguments.length?(a=e,A):a},A.color=function(t){return arguments.length?(f=e.utils.getColor(t),i.color(f),A):f},A.showControls=function(e){return arguments.length?(l=e,A):l},A.showLegend=function(e){return arguments.length?(c=e,A):c},A.showXAxis=function(e){return arguments.length?(h=e,A):h},A.showYAxis=function(e){return arguments.length?(p=e,A):p},A.rightAlignYAxis=function(e){return arguments.length?(d=e,r.orient(e?"right":"left"),A):d},A.reduceXTicks=function(e){return arguments.length?(v=e,A):v},A.rotateLabels=function(e){return arguments.length?(g=e,A):g},A.staggerLabels=function(e){return arguments.length?(m=e,A):m},A.tooltip=function(e){return arguments.length?(b=e,A):b},A.tooltips=function(e){return arguments.length?(y=e,A):y},A.tooltipContent=function(e){return arguments.length?(b=e,A):b},A.state=function(e){return arguments.length?(S=e,A):S},A.defaultState=function(e){return arguments.length?(x=e,A):x},A.noData=function(e){return arguments.length?(T=e,A):T},A.transitionDuration=function(e){return arguments.length?(k=e,A):k},A},e.models.multiBarHorizontal=function(){"use strict";function C(e){return e.each(function(e){var i=n-t.left-t.right,y=r-t.top-t.bottom,C=d3.select(this);p&&(e=d3.layout.stack().offset("zero").values(function(e){return e.values}).y(a)(e)),e.forEach(function(e,t){e.values.forEach(function(e){e.series=t})}),p&&e[0].values.map(function(t,n){var r=0,i=0;e.map(function(e){var t=e.values[n];t.size=Math.abs(t.y),t.y<0?(t.y1=i-t.size,i-=t.size):(t.y1=r,r+=t.size)})});var k=b&&w?[]:e.map(function(e){return e.values.map(function(e,t){return{x:u(e,t),y:a(e,t),y0:e.y0,y1:e.y1}})});s.domain(b||d3.merge(k).map(function(e){return e.x})).rangeBands(E||[0,y],.1),o.domain(w||d3.extent(d3.merge(k).map(function(e){return p?e.y>0?e.y1+e.y:e.y1:e.y}).concat(f))),d&&!p?o.range(S||[o.domain()[0]<0?m:0,i-(o.domain()[1]>0?m:0)]):o.range(S||[0,i]),T=T||s,N=N||d3.scale.linear().domain(o.domain()).range([o(0),o(0)]);var L=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([e]),A=L.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),O=A.append("defs"),M=A.append("g"),_=L.select("g");M.append("g").attr("class","nv-groups"),L.attr("transform","translate("+t.left+","+t.top+")");var D=L.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e,t){return t});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().transition().style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}).style("fill",function(e,t){return l(e,t)}).style("stroke",function(e,t){return l(e,t)}),D.transition().style("stroke-opacity",1).style("fill-opacity",.75);var P=D.selectAll("g.nv-bar").data(function(e){return e.values});P.exit().remove();var H=P.enter().append("g").attr("transform",function(t,n,r){return"translate("+N(p?t.y0:0)+","+(p?0:r*s.rangeBand()/e.length+s(u(t,n)))+")"});H.append("rect").attr("width",0).attr("height",s.rangeBand()/(p?1:e.length)),P.on("mouseover",function(t,n){d3.select(this).classed("hover",!0),x.elementMouseover({value:a(t,n),point:t,series:e[t.series],pos:[o(a(t,n)+(p?t.y0:0)),s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),x.elementMouseout({value:a(t,n),point:t,series:e[t.series],pointIndex:n,seriesIndex:t.series,e:d3.event})}).on("click",function(t,n){x.elementClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length,o(a(t,n)+(p?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}).on("dblclick",function(t,n){x.elementDblClick({value:a(t,n),point:t,series:e[t.series],pos:[s(u(t,n))+s.rangeBand()*(p?e.length/2:t.series+.5)/e.length,o(a(t,n)+(p?t.y0:0))],pointIndex:n,seriesIndex:t.series,e:d3.event}),d3.event.stopPropagation()}),H.append("text"),d&&!p?(P.select("text").attr("text-anchor",function(e,t){return a(e,t)<0?"end":"start"}).attr("y",s.rangeBand()/(e.length*2)).attr("dy",".32em").text(function(e,t){return g(a(e,t))}),P.transition().select("text").attr("x",function(e,t){return a(e,t)<0?-4:o(a(e,t))-o(0)+4})):P.selectAll("text").text(""),v&&!p?(H.append("text").classed("nv-bar-label",!0),P.select("text.nv-bar-label").attr("text-anchor",function(e,t){return a(e,t)<0?"start":"end"}).attr("y",s.rangeBand()/(e.length*2)).attr("dy",".32em").text(function(e,t){return u(e,t)}),P.transition().select("text.nv-bar-label").attr("x",function(e,t){return a(e,t)<0?o(0)-o(a(e,t))+4:-4})):P.selectAll("text.nv-bar-label").text(""),P.attr("class",function(e,t){return a(e,t)<0?"nv-bar negative":"nv-bar positive"}),c&&(h||(h=e.map(function(){return!0})),P.style("fill",function(e,t,n){return d3.rgb(c(e,t)).darker(h.map(function(e,t){return t}).filter(function(e,t){return!h[t]})[n]).toString()}).style("stroke",function(e,t,n){return d3.rgb(c(e,t)).darker(h.map(function(e,t){return t}).filter(function(e,t){return!h[t]})[n]).toString()})),p?P.transition().attr("transform",function(e,t){return"translate("+o(e.y1)+","+s(u(e,t))+")"}).select("rect").attr("width",function(e,t){return Math.abs(o(a(e,t)+e.y0)-o(e.y0))}).attr("height",s.rangeBand()):P.transition().attr("transform",function(t,n){return"translate("+(a(t,n)<0?o(a(t,n)):o(0))+","+(t.series*s.rangeBand()/e.length+s(u(t,n)))+")"}).select("rect").attr("height",s.rangeBand()/e.length).attr("width",function(e,t){return Math.max(Math.abs(o(a(e,t))-o(0)),1)}),T=s.copy(),N=o.copy()}),C}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.ordinal(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=[0],l=e.utils.defaultColor(),c=null,h,p=!1,d=!1,v=!1,m=60,g=d3.format(",.2f"),y=1200,b,w,E,S,x=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout"),T,N;return C.dispatch=x,C.options=e.utils.optionsFunc.bind(C),C.x=function(e){return arguments.length?(u=e,C):u},C.y=function(e){return arguments.length?(a=e,C):a},C.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,C):t},C.width=function(e){return arguments.length?(n=e,C):n},C.height=function(e){return arguments.length?(r=e,C):r},C.xScale=function(e){return arguments.length?(s=e,C):s},C.yScale=function(e){return arguments.length?(o=e,C):o},C.xDomain=function(e){return arguments.length?(b=e,C):b},C.yDomain=function(e){return arguments.length?(w=e,C):w},C.xRange=function(e){return arguments.length?(E=e,C):E},C.yRange=function(e){return arguments.length?(S=e,C):S},C.forceY=function(e){return arguments.length?(f=e,C):f},C.stacked=function(e){return arguments.length?(p=e,C):p},C.color=function(t){return arguments.length?(l=e.utils.getColor(t),C):l},C.barColor=function(t){return arguments.length?(c=e.utils.getColor(t),C):c},C.disabled=function(e){return arguments.length?(h=e,C):h},C.id=function(e){return arguments.length?(i=e,C):i},C.delay=function(e){return arguments.length?(y=e,C):y},C.showValues=function(e){return arguments.length?(d=e,C):d},C.showBarLabels=function(e){return arguments.length?(v=e,C):v},C.valueFormat=function(e){return arguments.length?(g=e,C):g},C.valuePadding=function(e){return arguments.length?(m=e,C):m},C},e.models.multiBarHorizontalChart=function(){"use strict";function C(e){return e.each(function(e){var d=d3.select(this),m=this,k=(u||parseInt(d.style("width"))||960)-o.left-o.right,L=(a||parseInt(d.style("height"))||400)-o.top-o.bottom;C.update=function(){d.transition().duration(T).call(C)},C.container=this,b.disabled=e.map(function(e){return!!e.disabled});if(!w){var A;w={};for(A in b)b[A]instanceof Array?w[A]=b[A].slice(0):w[A]=b[A]}if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var O=d.selectAll(".nv-noData").data([E]);return O.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),O.attr("x",o.left+k/2).attr("y",o.top+L/2).text(function(e){return e}),C}d.selectAll(".nv-noData").remove(),g=t.xScale(),y=t.yScale();var M=d.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([e]),_=M.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),D=M.select("g");_.append("g").attr("class","nv-x nv-axis"),_.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),_.append("g").attr("class","nv-barsWrap"),_.append("g").attr("class","nv-legendWrap"),_.append("g").attr("class","nv-controlsWrap"),c&&(i.width(k-x()),t.barColor()&&e.forEach(function(e,t){e.color=d3.rgb("#ccc").darker(t*1.5).toString()}),D.select(".nv-legendWrap").datum(e).call(i),o.top!=i.height()&&(o.top=i.height(),L=(a||parseInt(d.style("height"))||400)-o.top-o.bottom),D.select(".nv-legendWrap").attr("transform","translate("+x()+","+ -o.top+")"));if(l){var P=[{key:"Grouped",disabled:t.stacked()},{key:"Stacked",disabled:!t.stacked()}];s.width(x()).color(["#444","#444","#444"]),D.select(".nv-controlsWrap").datum(P).attr("transform","translate(0,"+ -o.top+")").call(s)}M.attr("transform","translate("+o.left+","+o.top+")"),t.disabled(e.map(function(e){return e.disabled})).width(k).height(L).color(e.map(function(e,t){return e.color||f(e,t)}).filter(function(t,n){return!e[n].disabled}));var H=D.select(".nv-barsWrap").datum(e.filter(function(e){return!e.disabled}));H.transition().call(t);if(h){n.scale(g).ticks(L/24).tickSize(-k,0),D.select(".nv-x.nv-axis").transition().call(n);var B=D.select(".nv-x.nv-axis").selectAll("g");B.selectAll("line, text")}p&&(r.scale(y).ticks(k/100).tickSize(-L,0),D.select(".nv-y.nv-axis").attr("transform","translate(0,"+L+")"),D.select(".nv-y.nv-axis").transition().call(r)),D.select(".nv-zeroLine line").attr("x1",y(0)).attr("x2",y(0)).attr("y1",0).attr("y2",-L),i.dispatch.on("stateChange",function(e){b=e,S.stateChange(b),C.update()}),s.dispatch.on("legendClick",function(e,n){if(!e.disabled)return;P=P.map(function(e){return e.disabled=!0,e}),e.disabled=!1;switch(e.key){case"Grouped":t.stacked(!1);break;case"Stacked":t.stacked(!0)}b.stacked=t.stacked(),S.stateChange(b),C.update()}),S.on("tooltipShow",function(e){v&&N(e,m.parentNode)}),S.on("changeState",function(n){typeof n.disabled!="undefined"&&(e.forEach(function(e,t){e.disabled=n.disabled[t]}),b.disabled=n.disabled),typeof n.stacked!="undefined"&&(t.stacked(n.stacked),b.stacked=n.stacked),C.update()})}),C}var t=e.models.multiBarHorizontal(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend().height(30),s=e.models.legend().height(30),o={top:30,right:20,bottom:50,left:60},u=null,a=null,f=e.utils.defaultColor(),l=!0,c=!0,h=!0,p=!0,d=!1,v=!0,m=function(e,t,n,r,i){return"<h3>"+e+" - "+t+"</h3>"+"<p>"+n+"</p>"},g,y,b={stacked:d},w=null,E="No Data Available.",S=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),x=function(){return l?180:0},T=250;t.stacked(d),n.orient("left").tickPadding(5).highlightZero(!1).showMaxMin(!1).tickFormat(function(e){return e}),r.orient("bottom").tickFormat(d3.format(",.1f")),s.updateState(!1);var N=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=m(i.series.key,a,f,i,C);e.tooltip.show([o,u],l,i.value<0?"e":"w",null,s)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+o.left,e.pos[1]+o.top],S.tooltipShow(e)}),t.dispatch.on("elementMouseout.tooltip",function(e){S.tooltipHide(e)}),S.on("tooltipHide",function(){v&&e.tooltip.cleanup()}),C.dispatch=S,C.multibar=t,C.legend=i,C.xAxis=n,C.yAxis=r,d3.rebind(C,t,"x","y","xDomain","yDomain","xRange","yRange","forceX","forceY","clipEdge","id","delay","showValues","showBarLabels","valueFormat","stacked","barColor"),C.options=e.utils.optionsFunc.bind(C),C.margin=function(e){return arguments.length?(o.top=typeof e.top!="undefined"?e.top:o.top,o.right=typeof e.right!="undefined"?e.right:o.right,o.bottom=typeof e.bottom!="undefined"?e.bottom:o.bottom,o.left=typeof e.left!="undefined"?e.left:o.left,C):o},C.width=function(e){return arguments.length?(u=e,C):u},C.height=function(e){return arguments.length?(a=e,C):a},C.color=function(t){return arguments.length?(f=e.utils.getColor(t),i.color(f),C):f},C.showControls=function(e){return arguments.length?(l=e,C):l},C.showLegend=function(e){return arguments.length?(c=e,C):c},C.showXAxis=function(e){return arguments.length?(h=e,C):h},C.showYAxis=function(e){return arguments.length?(p=e,C):p},C.tooltip=function(e){return arguments.length?(m=e,C):m},C.tooltips=function(e){return arguments.length?(v=e,C):v},C.tooltipContent=function(e){return arguments.length?(m=e,C):m},C.state=function(e){return arguments.length?(b=e,C):b},C.defaultState=function(e){return arguments.length?(w=e,C):w},C.noData=function(e){return arguments.length?(E=e,C):E},C.transitionDuration=function(e){return arguments.length?(T=e,C):T},C},e.models.multiChart=function(){"use strict";function C(e){return e.each(function(e){var u=d3.select(this),f=this;C.update=function(){u.transition().call(C)},C.container=this;var k=(r||parseInt(u.style("width"))||960)-t.left-t.right,L=(i||parseInt(u.style("height"))||400)-t.top-t.bottom,A=e.filter(function(e){return!e.disabled&&e.type=="line"&&e.yAxis==1}),O=e.filter(function(e){return!e.disabled&&e.type=="line"&&e.yAxis==2}),M=e.filter(function(e){return!e.disabled&&e.type=="bar"&&e.yAxis==1}),_=e.filter(function(e){return!e.disabled&&e.type=="bar"&&e.yAxis==2}),D=e.filter(function(e){return!e.disabled&&e.type=="area"&&e.yAxis==1}),P=e.filter(function(e){return!e.disabled&&e.type=="area"&&e.yAxis==2}),H=e.filter(function(e){return!e.disabled&&e.yAxis==1}).map(function(e){return e.values.map(function(e,t){return{x:e.x,y:e.y}})}),B=e.filter(function(e){return!e.disabled&&e.yAxis==2}).map(function(e){return e.values.map(function(e,t){return{x:e.x,y:e.y}})});a.domain(d3.extent(d3.merge(H.concat(B)),function(e){return e.x})).range([0,k]);var j=u.selectAll("g.wrap.multiChart").data([e]),F=j.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");F.append("g").attr("class","x axis"),F.append("g").attr("class","y1 axis"),F.append("g").attr("class","y2 axis"),F.append("g").attr("class","lines1Wrap"),F.append("g").attr("class","lines2Wrap"),F.append("g").attr("class","bars1Wrap"),F.append("g").attr("class","bars2Wrap"),F.append("g").attr("class","stack1Wrap"),F.append("g").attr("class","stack2Wrap"),F.append("g").attr("class","legendWrap");var I=j.select("g");s&&(x.width(k/2),I.select(".legendWrap").datum(e.map(function(e){return e.originalKey=e.originalKey===undefined?e.key:e.originalKey,e.key=e.originalKey+(e.yAxis==1?"":" (right axis)"),e})).call(x),t.top!=x.height()&&(t.top=x.height(),L=(i||parseInt(u.style("height"))||400)-t.top-t.bottom),I.select(".legendWrap").attr("transform","translate("+k/2+","+ -t.top+")")),d.width(k).height(L).interpolate("monotone").color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==1&&e[n].type=="line"})),v.width(k).height(L).interpolate("monotone").color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==2&&e[n].type=="line"})),m.width(k).height(L).color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==1&&e[n].type=="bar"})),g.width(k).height(L).color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==2&&e[n].type=="bar"})),y.width(k).height(L).color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==1&&e[n].type=="area"})),b.width(k).height(L).color(e.map(function(e,t){return e.color||n[t%n.length]}).filter(function(t,n){return!e[n].disabled&&e[n].yAxis==2&&e[n].type=="area"})),I.attr("transform","translate("+t.left+","+t.top+")");var q=I.select(".lines1Wrap").datum(A),R=I.select(".bars1Wrap").datum(M),U=I.select(".stack1Wrap").datum(D),z=I.select(".lines2Wrap").datum(O),W=I.select(".bars2Wrap").datum(_),X=I.select(".stack2Wrap").datum(P),V=D.length?D.map(function(e){return e.values}).reduce(function(e,t){return e.map(function(e,n){return{x:e.x,y:e.y+t[n].y}})}).concat([{x:0,y:0}]):[],$=P.length?P.map(function(e){return e.values}).reduce(function(e,t){return e.map(function(e,n){return{x:e.x,y:e.y+t[n].y}})}).concat([{x:0,y:0}]):[];h.domain(l||d3.extent(d3.merge(H).concat(V),function(e){return e.y})).range([0,L]),p.domain(c||d3.extent(d3.merge(B).concat($),function(e){return e.y})).range([0,L]),d.yDomain(h.domain()),m.yDomain(h.domain()),y.yDomain(h.domain()),v.yDomain(p.domain()),g.yDomain(p.domain()),b.yDomain(p.domain()),D.length&&d3.transition(U).call(y),P.length&&d3.transition(X).call(b),M.length&&d3.transition(R).call(m),_.length&&d3.transition(W).call(g),A.length&&d3.transition(q).call(d),O.length&&d3.transition(z).call(v),w.ticks(k/100).tickSize(-L,0),I.select(".x.axis").attr("transform","translate(0,"+L+")"),d3.transition(I.select(".x.axis")).call(w),E.ticks(L/36).tickSize(-k,0),d3.transition(I.select(".y1.axis")).call(E),S.ticks(L/36).tickSize(-k,0),d3.transition(I.select(".y2.axis")).call(S),I.select(".y2.axis").style("opacity",B.length?1:0).attr("transform","translate("+a.range()[1]+",0)"),x.dispatch.on("stateChange",function(e){C.update()}),T.on("tooltipShow",function(e){o&&N(e,f.parentNode)})}),C}var t={top:30,right:20,bottom:50,left:60},n=d3.scale.category20().range(),r=null,i=null,s=!0,o=!0,u=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" at "+t+"</p>"},a,f,l,c,a=d3.scale.linear(),h=d3.scale.linear(),p=d3.scale.linear(),d=e.models.line().yScale(h),v=e.models.line().yScale(p),m=e.models.multiBar().stacked(!1).yScale(h),g=e.models.multiBar().stacked(!1).yScale(p),y=e.models.stackedArea().yScale(h),b=e.models.stackedArea().yScale(p),w=e.models.axis().scale(a).orient("bottom").tickPadding(5),E=e.models.axis().scale(h).orient("left"),S=e.models.axis().scale(p).orient("right"),x=e.models.legend().height(30),T=d3.dispatch("tooltipShow","tooltipHide"),N=function(t,n){var r=t.pos[0]+(n.offsetLeft||0),i=t.pos[1]+(n.offsetTop||0),s=w.tickFormat()(d.x()(t.point,t.pointIndex)),o=(t.series.yAxis==2?S:E).tickFormat()(d.y()(t.point,t.pointIndex)),a=u(t.series.key,s,o,t,C);e.tooltip.show([r,i],a,undefined,undefined,n.offsetParent)};return d.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),d.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),v.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),v.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),m.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),m.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),g.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),g.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),y.dispatch.on("tooltipShow",function(e){if(!Math.round(y.y()(e.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),y.dispatch.on("tooltipHide",function(e){T.tooltipHide(e)}),b.dispatch.on("tooltipShow",function(e){if(!Math.round(b.y()(e.point)*100))return setTimeout(function(){d3.selectAll(".point.hover").classed("hover",!1)},0),!1;e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),b.dispatch.on("tooltipHide",function(e){T.tooltipHide(e)}),d.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),d.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),v.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],T.tooltipShow(e)}),v.dispatch.on("elementMouseout.tooltip",function(e){T.tooltipHide(e)}),T.on("tooltipHide",function(){o&&e.tooltip.cleanup()}),C.dispatch=T,C.lines1=d,C.lines2=v,C.bars1=m,C.bars2=g,C.stack1=y,C.stack2=b,C.xAxis=w,C.yAxis1=E,C.yAxis2=S,C.options=e.utils.optionsFunc.bind(C),C.x=function(e){return arguments.length?(getX=e,d.x(e),m.x(e),C):getX},C.y=function(e){return arguments.length?(getY=e,d.y(e),m.y(e),C):getY},C.yDomain1=function(e){return arguments.length?(l=e,C):l},C.yDomain2=function(e){return arguments.length?(c=e,C):c},C.margin=function(e){return arguments.length?(t=e,C):t},C.width=function(e){return arguments.length?(r=e,C):r},C.height=function(e){return arguments.length?(i=e,C):i},C.color=function(e){return arguments.length?(n=e,x.color(e),C):n},C.showLegend=function(e){return arguments.length?(s=e,C):s},C.tooltips=function(e){return arguments.length?(o=e,C):o},C.tooltipContent=function(e){return arguments.length?(u=e,C):u},C},e.models.ohlcBar=function(){"use strict";function x(e){return e.each(function(e){var g=n-t.left-t.right,x=r-t.top-t.bottom,T=d3.select(this);s.domain(y||d3.extent(e[0].values.map(u).concat(p))),v?s.range(w||[g*.5/e[0].values.length,g*(e[0].values.length-.5)/e[0].values.length]):s.range(w||[0,g]),o.domain(b||[d3.min(e[0].values.map(h).concat(d)),d3.max(e[0].values.map(c).concat(d))]).range(E||[x,0]),s.domain()[0]===s.domain()[1]&&(s.domain()[0]?s.domain([s.domain()[0]-s.domain()[0]*.01,s.domain()[1]+s.domain()[1]*.01]):s.domain([-1,1])),o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]+o.domain()[0]*.01,o.domain()[1]-o.domain()[1]*.01]):o.domain([-1,1]));var N=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([e[0].values]),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),k=C.append("defs"),L=C.append("g"),A=N.select("g");L.append("g").attr("class","nv-ticks"),N.attr("transform","translate("+t.left+","+t.top+")"),T.on("click",function(e,t){S.chartClick({data:e,index:t,pos:d3.event,id:i})}),k.append("clipPath").attr("id","nv-chart-clip-path-"+i).append("rect"),N.select("#nv-chart-clip-path-"+i+" rect").attr("width",g).attr("height",x),A.attr("clip-path",m?"url(#nv-chart-clip-path-"+i+")":"");var O=N.select(".nv-ticks").selectAll(".nv-tick").data(function(e){return e});O.exit().remove();var M=O.enter().append("path").attr("class",function(e,t,n){return(f(e,t)>l(e,t)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+t}).attr("d",function(t,n){var r=g/e[0].values.length*.9;return"m0,0l0,"+(o(f(t,n))-o(c(t,n)))+"l"+ -r/2+",0l"+r/2+",0l0,"+(o(h(t,n))-o(f(t,n)))+"l0,"+(o(l(t,n))-o(h(t,n)))+"l"+r/2+",0l"+ -r/2+",0z"}).attr("transform",function(e,t){return"translate("+s(u(e,t))+","+o(c(e,t))+")"}).on("mouseover",function(t,n){d3.select(this).classed("hover",!0),S.elementMouseover({point:t,series:e[0],pos:[s(u(t,n)),o(a(t,n))],pointIndex:n,seriesIndex:0,e:d3.event})}).on("mouseout",function(t,n){d3.select(this).classed("hover",!1),S.elementMouseout({point:t,series:e[0],pointIndex:n,seriesIndex:0,e:d3.event})}).on("click",function(e,t){S.elementClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()}).on("dblclick",function(e,t){S.elementDblClick({value:a(e,t),data:e,index:t,pos:[s(u(e,t)),o(a(e,t))],e:d3.event,id:i}),d3.event.stopPropagation()});O.attr("class",function(e,t,n){return(f(e,t)>l(e,t)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+n+"-"+t}),d3.transition(O).attr("transform",function(e,t){return"translate("+s(u(e,t))+","+o(c(e,t))+")"}).attr("d",function(t,n){var r=g/e[0].values.length*.9;return"m0,0l0,"+(o(f(t,n))-o(c(t,n)))+"l"+ -r/2+",0l"+r/2+",0l0,"+(o(h(t,n))-o(f(t,n)))+"l0,"+(o(l(t,n))-o(h(t,n)))+"l"+r/2+",0l"+ -r/2+",0z"})}),x}var t={top:0
|
5 |
+
,right:0,bottom:0,left:0},n=960,r=500,i=Math.floor(Math.random()*1e4),s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=function(e){return e.open},l=function(e){return e.close},c=function(e){return e.high},h=function(e){return e.low},p=[],d=[],v=!1,m=!0,g=e.utils.defaultColor(),y,b,w,E,S=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return x.dispatch=S,x.options=e.utils.optionsFunc.bind(x),x.x=function(e){return arguments.length?(u=e,x):u},x.y=function(e){return arguments.length?(a=e,x):a},x.open=function(e){return arguments.length?(f=e,x):f},x.close=function(e){return arguments.length?(l=e,x):l},x.high=function(e){return arguments.length?(c=e,x):c},x.low=function(e){return arguments.length?(h=e,x):h},x.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,x):t},x.width=function(e){return arguments.length?(n=e,x):n},x.height=function(e){return arguments.length?(r=e,x):r},x.xScale=function(e){return arguments.length?(s=e,x):s},x.yScale=function(e){return arguments.length?(o=e,x):o},x.xDomain=function(e){return arguments.length?(y=e,x):y},x.yDomain=function(e){return arguments.length?(b=e,x):b},x.xRange=function(e){return arguments.length?(w=e,x):w},x.yRange=function(e){return arguments.length?(E=e,x):E},x.forceX=function(e){return arguments.length?(p=e,x):p},x.forceY=function(e){return arguments.length?(d=e,x):d},x.padData=function(e){return arguments.length?(v=e,x):v},x.clipEdge=function(e){return arguments.length?(m=e,x):m},x.color=function(t){return arguments.length?(g=e.utils.getColor(t),x):g},x.id=function(e){return arguments.length?(i=e,x):i},x},e.models.pie=function(){"use strict";function S(e){return e.each(function(e){function q(e){var t=(e.startAngle+e.endAngle)*90/Math.PI-90;return t>90?t-180:t}function R(e){e.endAngle=isNaN(e.endAngle)?0:e.endAngle,e.startAngle=isNaN(e.startAngle)?0:e.startAngle,m||(e.innerRadius=0);var t=d3.interpolate(this._current,e);return this._current=t(0),function(e){return A(t(e))}}function U(e){e.innerRadius=0;var t=d3.interpolate({startAngle:0,endAngle:0},e);return function(e){return A(t(e))}}var o=n-t.left-t.right,f=r-t.top-t.bottom,S=Math.min(o,f)/2,x=S-S/5,T=d3.select(this),N=T.selectAll(".nv-wrap.nv-pie").data(e),C=N.enter().append("g").attr("class","nvd3 nv-wrap nv-pie nv-chart-"+u),k=C.append("g"),L=N.select("g");k.append("g").attr("class","nv-pie"),k.append("g").attr("class","nv-pieLabels"),N.attr("transform","translate("+t.left+","+t.top+")"),L.select(".nv-pie").attr("transform","translate("+o/2+","+f/2+")"),L.select(".nv-pieLabels").attr("transform","translate("+o/2+","+f/2+")"),T.on("click",function(e,t){E.chartClick({data:e,index:t,pos:d3.event,id:u})});var A=d3.svg.arc().outerRadius(x);y&&A.startAngle(y),b&&A.endAngle(b),m&&A.innerRadius(S*w);var O=d3.layout.pie().sort(null).value(function(e){return e.disabled?0:s(e)}),M=N.select(".nv-pie").selectAll(".nv-slice").data(O),_=N.select(".nv-pieLabels").selectAll(".nv-label").data(O);M.exit().remove(),_.exit().remove();var D=M.enter().append("g").attr("class","nv-slice").on("mouseover",function(e,t){d3.select(this).classed("hover",!0),E.elementMouseover({label:i(e.data),value:s(e.data),point:e.data,pointIndex:t,pos:[d3.event.pageX,d3.event.pageY],id:u})}).on("mouseout",function(e,t){d3.select(this).classed("hover",!1),E.elementMouseout({label:i(e.data),value:s(e.data),point:e.data,index:t,id:u})}).on("click",function(e,t){E.elementClick({label:i(e.data),value:s(e.data),point:e.data,index:t,pos:d3.event,id:u}),d3.event.stopPropagation()}).on("dblclick",function(e,t){E.elementDblClick({label:i(e.data),value:s(e.data),point:e.data,index:t,pos:d3.event,id:u}),d3.event.stopPropagation()});M.attr("fill",function(e,t){return a(e,t)}).attr("stroke",function(e,t){return a(e,t)});var P=D.append("path").each(function(e){this._current=e});M.select("path").transition().attr("d",A).attrTween("d",R);if(l){var H=d3.svg.arc().innerRadius(0);c&&(H=A),h&&(H=d3.svg.arc().outerRadius(A.outerRadius())),_.enter().append("g").classed("nv-label",!0).each(function(e,t){var n=d3.select(this);n.attr("transform",function(e){if(g){e.outerRadius=x+10,e.innerRadius=x+15;var t=(e.startAngle+e.endAngle)/2*(180/Math.PI);return(e.startAngle+e.endAngle)/2<Math.PI?t-=90:t+=90,"translate("+H.centroid(e)+") rotate("+t+")"}return e.outerRadius=S+10,e.innerRadius=S+15,"translate("+H.centroid(e)+")"}),n.append("rect").style("stroke","#fff").style("fill","#fff").attr("rx",3).attr("ry",3),n.append("text").style("text-anchor",g?(e.startAngle+e.endAngle)/2<Math.PI?"start":"end":"middle").style("fill","#000")});var B={},j=14,F=140,I=function(e){return Math.floor(e[0]/F)*F+","+Math.floor(e[1]/j)*j};_.transition().attr("transform",function(e){if(g){e.outerRadius=x+10,e.innerRadius=x+15;var t=(e.startAngle+e.endAngle)/2*(180/Math.PI);return(e.startAngle+e.endAngle)/2<Math.PI?t-=90:t+=90,"translate("+H.centroid(e)+") rotate("+t+")"}e.outerRadius=S+10,e.innerRadius=S+15;var n=H.centroid(e),r=I(n);return B[r]&&(n[1]-=j),B[I(n)]=!0,"translate("+n+")"}),_.select(".nv-label text").style("text-anchor",g?(d.startAngle+d.endAngle)/2<Math.PI?"start":"end":"middle").text(function(e,t){var n=(e.endAngle-e.startAngle)/(2*Math.PI),r={key:i(e.data),value:s(e.data),percent:d3.format("%")(n)};return e.value&&n>v?r[p]:""})}}),S}var t={top:0,right:0,bottom:0,left:0},n=500,r=500,i=function(e){return e.x},s=function(e){return e.y},o=function(e){return e.description},u=Math.floor(Math.random()*1e4),a=e.utils.defaultColor(),f=d3.format(",.2f"),l=!0,c=!0,h=!1,p="key",v=.02,m=!1,g=!1,y=!1,b=!1,w=.5,E=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout");return S.dispatch=E,S.options=e.utils.optionsFunc.bind(S),S.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,S):t},S.width=function(e){return arguments.length?(n=e,S):n},S.height=function(e){return arguments.length?(r=e,S):r},S.values=function(t){return e.log("pie.values() is no longer supported."),S},S.x=function(e){return arguments.length?(i=e,S):i},S.y=function(e){return arguments.length?(s=d3.functor(e),S):s},S.description=function(e){return arguments.length?(o=e,S):o},S.showLabels=function(e){return arguments.length?(l=e,S):l},S.labelSunbeamLayout=function(e){return arguments.length?(g=e,S):g},S.donutLabelsOutside=function(e){return arguments.length?(h=e,S):h},S.pieLabelsOutside=function(e){return arguments.length?(c=e,S):c},S.labelType=function(e){return arguments.length?(p=e,p=p||"key",S):p},S.donut=function(e){return arguments.length?(m=e,S):m},S.donutRatio=function(e){return arguments.length?(w=e,S):w},S.startAngle=function(e){return arguments.length?(y=e,S):y},S.endAngle=function(e){return arguments.length?(b=e,S):b},S.id=function(e){return arguments.length?(u=e,S):u},S.color=function(t){return arguments.length?(a=e.utils.getColor(t),S):a},S.valueFormat=function(e){return arguments.length?(f=e,S):f},S.labelThreshold=function(e){return arguments.length?(v=e,S):v},S},e.models.pieChart=function(){"use strict";function v(e){return e.each(function(e){var u=d3.select(this),a=this,f=(i||parseInt(u.style("width"))||960)-r.left-r.right,d=(s||parseInt(u.style("height"))||400)-r.top-r.bottom;v.update=function(){u.transition().call(v)},v.container=this,l.disabled=e.map(function(e){return!!e.disabled});if(!c){var m;c={};for(m in l)l[m]instanceof Array?c[m]=l[m].slice(0):c[m]=l[m]}if(!e||!e.length){var g=u.selectAll(".nv-noData").data([h]);return g.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),g.attr("x",r.left+f/2).attr("y",r.top+d/2).text(function(e){return e}),v}u.selectAll(".nv-noData").remove();var y=u.selectAll("g.nv-wrap.nv-pieChart").data([e]),b=y.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),w=y.select("g");b.append("g").attr("class","nv-pieWrap"),b.append("g").attr("class","nv-legendWrap"),o&&(n.width(f).key(t.x()),y.select(".nv-legendWrap").datum(e).call(n),r.top!=n.height()&&(r.top=n.height(),d=(s||parseInt(u.style("height"))||400)-r.top-r.bottom),y.select(".nv-legendWrap").attr("transform","translate(0,"+ -r.top+")")),y.attr("transform","translate("+r.left+","+r.top+")"),t.width(f).height(d);var E=w.select(".nv-pieWrap").datum([e]);d3.transition(E).call(t),n.dispatch.on("stateChange",function(e){l=e,p.stateChange(l),v.update()}),t.dispatch.on("elementMouseout.tooltip",function(e){p.tooltipHide(e)}),p.on("changeState",function(t){typeof t.disabled!="undefined"&&(e.forEach(function(e,n){e.disabled=t.disabled[n]}),l.disabled=t.disabled),v.update()})}),v}var t=e.models.pie(),n=e.models.legend(),r={top:30,right:20,bottom:20,left:20},i=null,s=null,o=!0,u=e.utils.defaultColor(),a=!0,f=function(e,t,n,r){return"<h3>"+e+"</h3>"+"<p>"+t+"</p>"},l={},c=null,h="No Data Available.",p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),d=function(n,r){var i=t.description()(n.point)||t.x()(n.point),s=n.pos[0]+(r&&r.offsetLeft||0),o=n.pos[1]+(r&&r.offsetTop||0),u=t.valueFormat()(t.y()(n.point)),a=f(i,u,n,v);e.tooltip.show([s,o],a,n.value<0?"n":"s",null,r)};return t.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+r.left,e.pos[1]+r.top],p.tooltipShow(e)}),p.on("tooltipShow",function(e){a&&d(e)}),p.on("tooltipHide",function(){a&&e.tooltip.cleanup()}),v.legend=n,v.dispatch=p,v.pie=t,d3.rebind(v,t,"valueFormat","values","x","y","description","id","showLabels","donutLabelsOutside","pieLabelsOutside","labelType","donut","donutRatio","labelThreshold"),v.options=e.utils.optionsFunc.bind(v),v.margin=function(e){return arguments.length?(r.top=typeof e.top!="undefined"?e.top:r.top,r.right=typeof e.right!="undefined"?e.right:r.right,r.bottom=typeof e.bottom!="undefined"?e.bottom:r.bottom,r.left=typeof e.left!="undefined"?e.left:r.left,v):r},v.width=function(e){return arguments.length?(i=e,v):i},v.height=function(e){return arguments.length?(s=e,v):s},v.color=function(r){return arguments.length?(u=e.utils.getColor(r),n.color(u),t.color(u),v):u},v.showLegend=function(e){return arguments.length?(o=e,v):o},v.tooltips=function(e){return arguments.length?(a=e,v):a},v.tooltipContent=function(e){return arguments.length?(f=e,v):f},v.state=function(e){return arguments.length?(l=e,v):l},v.defaultState=function(e){return arguments.length?(c=e,v):c},v.noData=function(e){return arguments.length?(h=e,v):h},v},e.models.scatter=function(){"use strict";function I(q){return q.each(function(I){function Q(){if(!g)return!1;var e,i=d3.merge(I.map(function(e,t){return e.values.map(function(e,n){var r=f(e,n),i=l(e,n);return[o(r)+Math.random()*1e-7,u(i)+Math.random()*1e-7,t,n,e]}).filter(function(e,t){return b(e[4],t)})}));if(D===!0){if(x){var a=X.select("defs").selectAll(".nv-point-clips").data([s]).enter();a.append("clipPath").attr("class","nv-point-clips").attr("id","nv-points-clip-"+s);var c=X.select("#nv-points-clip-"+s).selectAll("circle").data(i);c.enter().append("circle").attr("r",T),c.exit().remove(),c.attr("cx",function(e){return e[0]}).attr("cy",function(e){return e[1]}),X.select(".nv-point-paths").attr("clip-path","url(#nv-points-clip-"+s+")")}i.length&&(i.push([o.range()[0]-20,u.range()[0]-20,null,null]),i.push([o.range()[1]+20,u.range()[1]+20,null,null]),i.push([o.range()[0]-20,u.range()[0]+20,null,null]),i.push([o.range()[1]+20,u.range()[1]-20,null,null]));var h=d3.geom.polygon([[-10,-10],[-10,r+10],[n+10,r+10],[n+10,-10]]),p=d3.geom.voronoi(i).map(function(e,t){return{data:h.clip(e),series:i[t][2],point:i[t][3]}}),d=X.select(".nv-point-paths").selectAll("path").data(p);d.enter().append("path").attr("class",function(e,t){return"nv-path-"+t}),d.exit().remove(),d.attr("d",function(e){return e.data.length===0?"M 0 0":"M"+e.data.join("L")+"Z"});var v=function(e,n){if(F)return 0;var r=I[e.series];if(typeof r=="undefined")return;var i=r.values[e.point];n({point:i,series:r,pos:[o(f(i,e.point))+t.left,u(l(i,e.point))+t.top],seriesIndex:e.series,pointIndex:e.point})};d.on("click",function(e){v(e,_.elementClick)}).on("mouseover",function(e){v(e,_.elementMouseover)}).on("mouseout",function(e,t){v(e,_.elementMouseout)})}else X.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(e,n){if(F||!I[e.series])return 0;var r=I[e.series],i=r.values[n];_.elementClick({point:i,series:r,pos:[o(f(i,n))+t.left,u(l(i,n))+t.top],seriesIndex:e.series,pointIndex:n})}).on("mouseover",function(e,n){if(F||!I[e.series])return 0;var r=I[e.series],i=r.values[n];_.elementMouseover({point:i,series:r,pos:[o(f(i,n))+t.left,u(l(i,n))+t.top],seriesIndex:e.series,pointIndex:n})}).on("mouseout",function(e,t){if(F||!I[e.series])return 0;var n=I[e.series],r=n.values[t];_.elementMouseout({point:r,series:n,seriesIndex:e.series,pointIndex:t})});F=!1}var q=n-t.left-t.right,R=r-t.top-t.bottom,U=d3.select(this);I.forEach(function(e,t){e.values.forEach(function(e){e.series=t})});var W=N&&C&&A?[]:d3.merge(I.map(function(e){return e.values.map(function(e,t){return{x:f(e,t),y:l(e,t),size:c(e,t)}})}));o.domain(N||d3.extent(W.map(function(e){return e.x}).concat(d))),w&&I[0]?o.range(k||[(q*E+q)/(2*I[0].values.length),q-q*(1+E)/(2*I[0].values.length)]):o.range(k||[0,q]),u.domain(C||d3.extent(W.map(function(e){return e.y}).concat(v))).range(L||[R,0]),a.domain(A||d3.extent(W.map(function(e){return e.size}).concat(m))).range(O||[16,256]);if(o.domain()[0]===o.domain()[1]||u.domain()[0]===u.domain()[1])M=!0;o.domain()[0]===o.domain()[1]&&(o.domain()[0]?o.domain([o.domain()[0]-o.domain()[0]*.01,o.domain()[1]+o.domain()[1]*.01]):o.domain([-1,1])),u.domain()[0]===u.domain()[1]&&(u.domain()[0]?u.domain([u.domain()[0]-u.domain()[0]*.01,u.domain()[1]+u.domain()[1]*.01]):u.domain([-1,1])),isNaN(o.domain()[0])&&o.domain([-1,1]),isNaN(u.domain()[0])&&u.domain([-1,1]),P=P||o,H=H||u,B=B||a;var X=U.selectAll("g.nv-wrap.nv-scatter").data([I]),V=X.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+s+(M?" nv-single-point":"")),$=V.append("defs"),J=V.append("g"),K=X.select("g");J.append("g").attr("class","nv-groups"),J.append("g").attr("class","nv-point-paths"),X.attr("transform","translate("+t.left+","+t.top+")"),$.append("clipPath").attr("id","nv-edge-clip-"+s).append("rect"),X.select("#nv-edge-clip-"+s+" rect").attr("width",q).attr("height",R>0?R:0),K.attr("clip-path",S?"url(#nv-edge-clip-"+s+")":""),F=!0;var G=X.select(".nv-groups").selectAll(".nv-group").data(function(e){return e},function(e){return e.key});G.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),G.exit().remove(),G.attr("class",function(e,t){return"nv-group nv-series-"+t}).classed("hover",function(e){return e.hover}),G.transition().style("fill",function(e,t){return i(e,t)}).style("stroke",function(e,t){return i(e,t)}).style("stroke-opacity",1).style("fill-opacity",.5);if(p){var Y=G.selectAll("circle.nv-point").data(function(e){return e.values},y);Y.enter().append("circle").style("fill",function(e,t){return e.color}).style("stroke",function(e,t){return e.color}).attr("cx",function(t,n){return e.utils.NaNtoZero(P(f(t,n)))}).attr("cy",function(t,n){return e.utils.NaNtoZero(H(l(t,n)))}).attr("r",function(e,t){return Math.sqrt(a(c(e,t))/Math.PI)}),Y.exit().remove(),G.exit().selectAll("path.nv-point").transition().attr("cx",function(t,n){return e.utils.NaNtoZero(o(f(t,n)))}).attr("cy",function(t,n){return e.utils.NaNtoZero(u(l(t,n)))}).remove(),Y.each(function(e,t){d3.select(this).classed("nv-point",!0).classed("nv-point-"+t,!0).classed("hover",!1)}),Y.transition().attr("cx",function(t,n){return e.utils.NaNtoZero(o(f(t,n)))}).attr("cy",function(t,n){return e.utils.NaNtoZero(u(l(t,n)))}).attr("r",function(e,t){return Math.sqrt(a(c(e,t))/Math.PI)})}else{var Y=G.selectAll("path.nv-point").data(function(e){return e.values});Y.enter().append("path").style("fill",function(e,t){return e.color}).style("stroke",function(e,t){return e.color}).attr("transform",function(e,t){return"translate("+P(f(e,t))+","+H(l(e,t))+")"}).attr("d",d3.svg.symbol().type(h).size(function(e,t){return a(c(e,t))})),Y.exit().remove(),G.exit().selectAll("path.nv-point").transition().attr("transform",function(e,t){return"translate("+o(f(e,t))+","+u(l(e,t))+")"}).remove(),Y.each(function(e,t){d3.select(this).classed("nv-point",!0).classed("nv-point-"+t,!0).classed("hover",!1)}),Y.transition().attr("transform",function(e,t){return"translate("+o(f(e,t))+","+u(l(e,t))+")"}).attr("d",d3.svg.symbol().type(h).size(function(e,t){return a(c(e,t))}))}clearTimeout(j),j=setTimeout(Q,300),P=o.copy(),H=u.copy(),B=a.copy()}),I}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=e.utils.defaultColor(),s=Math.floor(Math.random()*1e5),o=d3.scale.linear(),u=d3.scale.linear(),a=d3.scale.linear(),f=function(e){return e.x},l=function(e){return e.y},c=function(e){return e.size||1},h=function(e){return e.shape||"circle"},p=!0,d=[],v=[],m=[],g=!0,y=null,b=function(e){return!e.notActive},w=!1,E=.1,S=!1,x=!0,T=function(){return 25},N=null,C=null,k=null,L=null,A=null,O=null,M=!1,_=d3.dispatch("elementClick","elementMouseover","elementMouseout"),D=!0,P,H,B,j,F=!1;return I.clearHighlights=function(){d3.selectAll(".nv-chart-"+s+" .nv-point.hover").classed("hover",!1)},I.highlightPoint=function(e,t,n){d3.select(".nv-chart-"+s+" .nv-series-"+e+" .nv-point-"+t).classed("hover",n)},_.on("elementMouseover.point",function(e){g&&I.highlightPoint(e.seriesIndex,e.pointIndex,!0)}),_.on("elementMouseout.point",function(e){g&&I.highlightPoint(e.seriesIndex,e.pointIndex,!1)}),I.dispatch=_,I.options=e.utils.optionsFunc.bind(I),I.x=function(e){return arguments.length?(f=d3.functor(e),I):f},I.y=function(e){return arguments.length?(l=d3.functor(e),I):l},I.size=function(e){return arguments.length?(c=d3.functor(e),I):c},I.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,I):t},I.width=function(e){return arguments.length?(n=e,I):n},I.height=function(e){return arguments.length?(r=e,I):r},I.xScale=function(e){return arguments.length?(o=e,I):o},I.yScale=function(e){return arguments.length?(u=e,I):u},I.zScale=function(e){return arguments.length?(a=e,I):a},I.xDomain=function(e){return arguments.length?(N=e,I):N},I.yDomain=function(e){return arguments.length?(C=e,I):C},I.sizeDomain=function(e){return arguments.length?(A=e,I):A},I.xRange=function(e){return arguments.length?(k=e,I):k},I.yRange=function(e){return arguments.length?(L=e,I):L},I.sizeRange=function(e){return arguments.length?(O=e,I):O},I.forceX=function(e){return arguments.length?(d=e,I):d},I.forceY=function(e){return arguments.length?(v=e,I):v},I.forceSize=function(e){return arguments.length?(m=e,I):m},I.interactive=function(e){return arguments.length?(g=e,I):g},I.pointKey=function(e){return arguments.length?(y=e,I):y},I.pointActive=function(e){return arguments.length?(b=e,I):b},I.padData=function(e){return arguments.length?(w=e,I):w},I.padDataOuter=function(e){return arguments.length?(E=e,I):E},I.clipEdge=function(e){return arguments.length?(S=e,I):S},I.clipVoronoi=function(e){return arguments.length?(x=e,I):x},I.useVoronoi=function(e){return arguments.length?(D=e,D===!1&&(x=!1),I):D},I.clipRadius=function(e){return arguments.length?(T=e,I):T},I.color=function(t){return arguments.length?(i=e.utils.getColor(t),I):i},I.shape=function(e){return arguments.length?(h=e,I):h},I.onlyCircles=function(e){return arguments.length?(p=e,I):p},I.id=function(e){return arguments.length?(s=e,I):s},I.singlePoint=function(e){return arguments.length?(M=e,I):M},I},e.models.scatterChart=function(){"use strict";function F(e){return e.each(function(e){function K(){if(T)return X.select(".nv-point-paths").style("pointer-events","all"),!1;X.select(".nv-point-paths").style("pointer-events","none");var i=d3.mouse(this);h.distortion(x).focus(i[0]),p.distortion(x).focus(i[1]),X.select(".nv-scatterWrap").call(t),b&&X.select(".nv-x.nv-axis").call(n),w&&X.select(".nv-y.nv-axis").call(r),X.select(".nv-distributionX").datum(e.filter(function(e){return!e.disabled})).call(o),X.select(".nv-distributionY").datum(e.filter(function(e){return!e.disabled})).call(u)}var C=d3.select(this),k=this,L=(f||parseInt(C.style("width"))||960)-a.left-a.right,I=(l||parseInt(C.style("height"))||400)-a.top-a.bottom;F.update=function(){C.transition().duration(D).call(F)},F.container=this,A.disabled=e.map(function(e){return!!e.disabled});if(!O){var q;O={};for(q in A)A[q]instanceof Array?O[q]=A[q].slice(0):O[q]=A[q]}if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var R=C.selectAll(".nv-noData").data([_]);return R.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),R.attr("x",a.left+L/2).attr("y",a.top+I/2).text(function(e){return e}),F}C.selectAll(".nv-noData").remove(),P=P||h,H=H||p;var U=C.selectAll("g.nv-wrap.nv-scatterChart").data([e]),z=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+t.id()),W=z.append("g"),X=U.select("g");W.append("rect").attr("class","nvd3 nv-background"),W.append("g").attr("class","nv-x nv-axis"),W.append("g").attr("class","nv-y nv-axis"),W.append("g").attr("class","nv-scatterWrap"),W.append("g").attr("class","nv-distWrap"),W.append("g").attr("class","nv-legendWrap"),W.append("g").attr("class","nv-controlsWrap");if(y){var V=S?L/2:L;i.width(V),U.select(".nv-legendWrap").datum(e).call(i),a.top!=i.height()&&(a.top=i.height(),I=(l||parseInt(C.style("height"))||400)-a.top-a.bottom),U.select(".nv-legendWrap").attr("transform","translate("+(L-V)+","+ -a.top+")")}S&&(s.width(180).color(["#444"]),X.select(".nv-controlsWrap").datum(j).attr("transform","translate(0,"+ -a.top+")").call(s)),U.attr("transform","translate("+a.left+","+a.top+")"),E&&X.select(".nv-y.nv-axis").attr("transform","translate("+L+",0)"),t.width(L).height(I).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),d!==0&&t.xDomain(null),v!==0&&t.yDomain(null),U.select(".nv-scatterWrap").datum(e.filter(function(e){return!e.disabled})).call(t);if(d!==0){var $=h.domain()[1]-h.domain()[0];t.xDomain([h.domain()[0]-d*$,h.domain()[1]+d*$])}if(v!==0){var J=p.domain()[1]-p.domain()[0];t.yDomain([p.domain()[0]-v*J,p.domain()[1]+v*J])}(v!==0||d!==0)&&U.select(".nv-scatterWrap").datum(e.filter(function(e){return!e.disabled})).call(t),b&&(n.scale(h).ticks(n.ticks()&&n.ticks().length?n.ticks():L/100).tickSize(-I,0),X.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(n)),w&&(r.scale(p).ticks(r.ticks()&&r.ticks().length?r.ticks():I/36).tickSize(-L,0),X.select(".nv-y.nv-axis").call(r)),m&&(o.getData(t.x()).scale(h).width(L).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),W.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),X.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(e.filter(function(e){return!e.disabled})).call(o)),g&&(u.getData(t.y()).scale(p).width(I).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),W.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),X.select(".nv-distributionY").attr("transform","translate("+(E?L:-u.size())+",0)").datum(e.filter(function(e){return!e.disabled})).call(u)),d3.fisheye&&(X.select(".nv-background").attr("width",L).attr("height",I),X.select(".nv-background").on("mousemove",K),X.select(".nv-background").on("click",function(){T=!T}),t.dispatch.on("elementClick.freezeFisheye",function(){T=!T})),s.dispatch.on("legendClick",function(e,i){e.disabled=!e.disabled,x=e.disabled?0:2.5,X.select(".nv-background").style("pointer-events",e.disabled?"none":"all"),X.select(".nv-point-paths").style("pointer-events",e.disabled?"all":"none"),e.disabled?(h.distortion(x).focus(0),p.distortion(x).focus(0),X.select(".nv-scatterWrap").call(t),X.select(".nv-x.nv-axis").call(n),X.select(".nv-y.nv-axis").call(r)):T=!1,F.update()}),i.dispatch.on("stateChange",function(e){A.disabled=e.disabled,M.stateChange(A),F.update()}),t.dispatch.on("elementMouseover.tooltip",function(e){d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",function(t,n){return e.pos[1]-I}),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",e.pos[0]+o.size()),e.pos=[e.pos[0]+a.left,e.pos[1]+a.top],M.tooltipShow(e)}),M.on("tooltipShow",function(e){N&&B(e,k.parentNode)}),M.on("changeState",function(t){typeof t.disabled!="undefined"&&(e.forEach(function(e,n){e.disabled=t.disabled[n]}),A.disabled=t.disabled),F.update()}),P=h.copy(),H=p.copy()}),F}var t=e.models.scatter(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.models.distribution(),u=e.models.distribution(),a={top:30,right:20,bottom:50,left:75},f=null,l=null,c=e.utils.defaultColor(),h=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.xScale(),p=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.yScale(),d=0,v=0,m=!1,g=!1,y=!0,b=!0,w=!0,E=!1,S=!!d3.fisheye,x=0,T=!1,N=!0,C=function(e,t,n){return"<strong>"+t+"</strong>"},k=function(e,t,n){return"<strong>"+n+"</strong>"},L=null,A={},O=null,M=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),_="No Data Available.",D=250;t.xScale(h).yScale(p),n.orient("bottom").tickPadding(10),r.orient(E?"right":"left").tickPadding(10),o.axis("x"),u.axis("y"),s.updateState(!1);var P,H,B=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),f=i.pos[0]+(s.offsetLeft||0),l=p.range()[0]+a.top+(s.offsetTop||0),c=h.range()[0]+a.left+(s.offsetLeft||0),d=i.pos[1]+(s.offsetTop||0),v=n.tickFormat()(t.x()(i.point,i.pointIndex)),m=r.tickFormat()(t.y()(i.point,i.pointIndex));C!=null&&e.tooltip.show([f,l],C(i.series.key,v,m,i,F),"n",1,s,"x-nvtooltip"),k!=null&&e.tooltip.show([c,d],k(i.series.key,v,m,i,F),"e",1,s,"y-nvtooltip"),L!=null&&e.tooltip.show([o,u],L(i.series.key,v,m,i,F),i.value<0?"n":"s",null,s)},j=[{key:"Magnify",disabled:!0}];return t.dispatch.on("elementMouseout.tooltip",function(e){M.tooltipHide(e),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",0),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",u.size())}),M.on("tooltipHide",function(){N&&e.tooltip.cleanup()}),F.dispatch=M,F.scatter=t,F.legend=i,F.controls=s,F.xAxis=n,F.yAxis=r,F.distX=o,F.distY=u,d3.rebind(F,t,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","xRange","yRange","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),F.options=e.utils.optionsFunc.bind(F),F.margin=function(e){return arguments.length?(a.top=typeof e.top!="undefined"?e.top:a.top,a.right=typeof e.right!="undefined"?e.right:a.right,a.bottom=typeof e.bottom!="undefined"?e.bottom:a.bottom,a.left=typeof e.left!="undefined"?e.left:a.left,F):a},F.width=function(e){return arguments.length?(f=e,F):f},F.height=function(e){return arguments.length?(l=e,F):l},F.color=function(t){return arguments.length?(c=e.utils.getColor(t),i.color(c),o.color(c),u.color(c),F):c},F.showDistX=function(e){return arguments.length?(m=e,F):m},F.showDistY=function(e){return arguments.length?(g=e,F):g},F.showControls=function(e){return arguments.length?(S=e,F):S},F.showLegend=function(e){return arguments.length?(y=e,F):y},F.showXAxis=function(e){return arguments.length?(b=e,F):b},F.showYAxis=function(e){return arguments.length?(w=e,F):w},F.rightAlignYAxis=function(e){return arguments.length?(E=e,r.orient(e?"right":"left"),F):E},F.fisheye=function(e){return arguments.length?(x=e,F):x},F.xPadding=function(e){return arguments.length?(d=e,F):d},F.yPadding=function(e){return arguments.length?(v=e,F):v},F.tooltips=function(e){return arguments.length?(N=e,F):N},F.tooltipContent=function(e){return arguments.length?(L=e,F):L},F.tooltipXContent=function(e){return arguments.length?(C=e,F):C},F.tooltipYContent=function(e){return arguments.length?(k=e,F):k},F.state=function(e){return arguments.length?(A=e,F):A},F.defaultState=function(e){return arguments.length?(O=e,F):O},F.noData=function(e){return arguments.length?(_=e,F):_},F.transitionDuration=function(e){return arguments.length?(D=e,F):D},F},e.models.scatterPlusLineChart=function(){"use strict";function B(e){return e.each(function(e){function $(){if(S)return z.select(".nv-point-paths").style("pointer-events","all"),!1;z.select(".nv-point-paths").style("pointer-events","none");var i=d3.mouse(this);h.distortion(E).focus(i[0]),p.distortion(E).focus(i[1]),z.select(".nv-scatterWrap").datum(e.filter(function(e){return!e.disabled})).call(t),g&&z.select(".nv-x.nv-axis").call(n),y&&z.select(".nv-y.nv-axis").call(r),z.select(".nv-distributionX").datum(e.filter(function(e){return!e.disabled})).call(o),z.select(".nv-distributionY").datum(e.filter(function(e){return!e.disabled})).call(u)}var T=d3.select(this),N=this,C=(f||parseInt(T.style("width"))||960)-a.left-a.right,j=(l||parseInt(T.style("height"))||400)-a.top-a.bottom;B.update=function(){T.transition().duration(M).call(B)},B.container=this,k.disabled=e.map(function(e){return!!e.disabled});if(!L){var F;L={};for(F in k)k[F]instanceof Array?L[F]=k[F].slice(0):L[F]=k[F]}if(!e||!e.length||!e.filter(function(e){return e.values.length}).length){var I=T.selectAll(".nv-noData").data([O]);return I.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),I.attr("x",a.left+C/2).attr("y",a.top+j/2).text(function(e){return e}),B}T.selectAll(".nv-noData").remove(),h=t.xScale(),p=t.yScale(),_=_||h,D=D||p;var q=T.selectAll("g.nv-wrap.nv-scatterChart").data([e]),R=q.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+t.id()),U=R.append("g"),z=q.select("g");U.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),U.append("g").attr("class","nv-x nv-axis"),U.append("g").attr("class","nv-y nv-axis"),U.append("g").attr("class","nv-scatterWrap"),U.append("g").attr("class","nv-regressionLinesWrap"),U.append("g").attr("class","nv-distWrap"),U.append("g").attr("class","nv-legendWrap"),U.append("g").attr("class","nv-controlsWrap"),q.attr("transform","translate("+a.left+","+a.top+")"),b&&z.select(".nv-y.nv-axis").attr("transform","translate("+C+",0)"),m&&(i.width(C/2),q.select(".nv-legendWrap").datum(e).call(i),a.top!=i.height()&&(a.top=i.height(),j=(l||parseInt(T.style("height"))||400)-a.top-a.bottom),q.select(".nv-legendWrap").attr("transform","translate("+C/2+","+ -a.top+")")),w&&(s.width(180).color(["#444"]),z.select(".nv-controlsWrap").datum(H).attr("transform","translate(0,"+ -a.top+")").call(s)),t.width(C).height(j).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),q.select(".nv-scatterWrap").datum(e.filter(function(e){return!e.disabled})).call(t),q.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+t.id()+")");var W=q.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(e){return e});W.enter().append("g").attr("class","nv-regLines");var X=W.selectAll(".nv-regLine").data(function(e){return[e]}),V=X.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0);X.transition().attr("x1",h.range()[0]).attr("x2",h.range()[1]).attr("y1",function(e,t){return p(h.domain()[0]*e.slope+e.intercept)}).attr("y2",function(e,t){return p(h.domain()[1]*e.slope+e.intercept)}).style("stroke",function(e,t,n){return c(e,n)}).style("stroke-opacity",function(e,t){return e.disabled||typeof e.slope=="undefined"||typeof e.intercept=="undefined"?0:1}),g&&(n.scale(h).ticks(n.ticks()?n.ticks():C/100).tickSize(-j,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(n)),y&&(r.scale(p).ticks(r.ticks()?r.ticks():j/36).tickSize(-C,0),z.select(".nv-y.nv-axis").call(r)),d&&(o.getData(t.x()).scale(h).width(C).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),U.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),z.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(e.filter(function(e){return!e.disabled})).call(o)),v&&(u.getData(t.y()).scale(p).width(
|
6 |
+
j).color(e.map(function(e,t){return e.color||c(e,t)}).filter(function(t,n){return!e[n].disabled})),U.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),z.select(".nv-distributionY").attr("transform","translate("+(b?C:-u.size())+",0)").datum(e.filter(function(e){return!e.disabled})).call(u)),d3.fisheye&&(z.select(".nv-background").attr("width",C).attr("height",j),z.select(".nv-background").on("mousemove",$),z.select(".nv-background").on("click",function(){S=!S}),t.dispatch.on("elementClick.freezeFisheye",function(){S=!S})),s.dispatch.on("legendClick",function(e,i){e.disabled=!e.disabled,E=e.disabled?0:2.5,z.select(".nv-background").style("pointer-events",e.disabled?"none":"all"),z.select(".nv-point-paths").style("pointer-events",e.disabled?"all":"none"),e.disabled?(h.distortion(E).focus(0),p.distortion(E).focus(0),z.select(".nv-scatterWrap").call(t),z.select(".nv-x.nv-axis").call(n),z.select(".nv-y.nv-axis").call(r)):S=!1,B.update()}),i.dispatch.on("stateChange",function(e){k=e,A.stateChange(k),B.update()}),t.dispatch.on("elementMouseover.tooltip",function(e){d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",e.pos[1]-j),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",e.pos[0]+o.size()),e.pos=[e.pos[0]+a.left,e.pos[1]+a.top],A.tooltipShow(e)}),A.on("tooltipShow",function(e){x&&P(e,N.parentNode)}),A.on("changeState",function(t){typeof t.disabled!="undefined"&&(e.forEach(function(e,n){e.disabled=t.disabled[n]}),k.disabled=t.disabled),B.update()}),_=h.copy(),D=p.copy()}),B}var t=e.models.scatter(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.models.distribution(),u=e.models.distribution(),a={top:30,right:20,bottom:50,left:75},f=null,l=null,c=e.utils.defaultColor(),h=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.xScale(),p=d3.fisheye?d3.fisheye.scale(d3.scale.linear).distortion(0):t.yScale(),d=!1,v=!1,m=!0,g=!0,y=!0,b=!1,w=!!d3.fisheye,E=0,S=!1,x=!0,T=function(e,t,n){return"<strong>"+t+"</strong>"},N=function(e,t,n){return"<strong>"+n+"</strong>"},C=function(e,t,n,r){return"<h3>"+e+"</h3>"+"<p>"+r+"</p>"},k={},L=null,A=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),O="No Data Available.",M=250;t.xScale(h).yScale(p),n.orient("bottom").tickPadding(10),r.orient(b?"right":"left").tickPadding(10),o.axis("x"),u.axis("y"),s.updateState(!1);var _,D,P=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),f=i.pos[0]+(s.offsetLeft||0),l=p.range()[0]+a.top+(s.offsetTop||0),c=h.range()[0]+a.left+(s.offsetLeft||0),d=i.pos[1]+(s.offsetTop||0),v=n.tickFormat()(t.x()(i.point,i.pointIndex)),m=r.tickFormat()(t.y()(i.point,i.pointIndex));T!=null&&e.tooltip.show([f,l],T(i.series.key,v,m,i,B),"n",1,s,"x-nvtooltip"),N!=null&&e.tooltip.show([c,d],N(i.series.key,v,m,i,B),"e",1,s,"y-nvtooltip"),C!=null&&e.tooltip.show([o,u],C(i.series.key,v,m,i.point.tooltip,i,B),i.value<0?"n":"s",null,s)},H=[{key:"Magnify",disabled:!0}];return t.dispatch.on("elementMouseout.tooltip",function(e){A.tooltipHide(e),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-distx-"+e.pointIndex).attr("y1",0),d3.select(".nv-chart-"+t.id()+" .nv-series-"+e.seriesIndex+" .nv-disty-"+e.pointIndex).attr("x2",u.size())}),A.on("tooltipHide",function(){x&&e.tooltip.cleanup()}),B.dispatch=A,B.scatter=t,B.legend=i,B.controls=s,B.xAxis=n,B.yAxis=r,B.distX=o,B.distY=u,d3.rebind(B,t,"id","interactive","pointActive","x","y","shape","size","xScale","yScale","zScale","xDomain","yDomain","xRange","yRange","sizeDomain","sizeRange","forceX","forceY","forceSize","clipVoronoi","clipRadius","useVoronoi"),B.options=e.utils.optionsFunc.bind(B),B.margin=function(e){return arguments.length?(a.top=typeof e.top!="undefined"?e.top:a.top,a.right=typeof e.right!="undefined"?e.right:a.right,a.bottom=typeof e.bottom!="undefined"?e.bottom:a.bottom,a.left=typeof e.left!="undefined"?e.left:a.left,B):a},B.width=function(e){return arguments.length?(f=e,B):f},B.height=function(e){return arguments.length?(l=e,B):l},B.color=function(t){return arguments.length?(c=e.utils.getColor(t),i.color(c),o.color(c),u.color(c),B):c},B.showDistX=function(e){return arguments.length?(d=e,B):d},B.showDistY=function(e){return arguments.length?(v=e,B):v},B.showControls=function(e){return arguments.length?(w=e,B):w},B.showLegend=function(e){return arguments.length?(m=e,B):m},B.showXAxis=function(e){return arguments.length?(g=e,B):g},B.showYAxis=function(e){return arguments.length?(y=e,B):y},B.rightAlignYAxis=function(e){return arguments.length?(b=e,r.orient(e?"right":"left"),B):b},B.fisheye=function(e){return arguments.length?(E=e,B):E},B.tooltips=function(e){return arguments.length?(x=e,B):x},B.tooltipContent=function(e){return arguments.length?(C=e,B):C},B.tooltipXContent=function(e){return arguments.length?(T=e,B):T},B.tooltipYContent=function(e){return arguments.length?(N=e,B):N},B.state=function(e){return arguments.length?(k=e,B):k},B.defaultState=function(e){return arguments.length?(L=e,B):L},B.noData=function(e){return arguments.length?(O=e,B):O},B.transitionDuration=function(e){return arguments.length?(M=e,B):M},B},e.models.sparkline=function(){"use strict";function d(e){return e.each(function(e){var i=n-t.left-t.right,d=r-t.top-t.bottom,v=d3.select(this);s.domain(l||d3.extent(e,u)).range(h||[0,i]),o.domain(c||d3.extent(e,a)).range(p||[d,0]);var m=v.selectAll("g.nv-wrap.nv-sparkline").data([e]),g=m.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline"),b=g.append("g"),w=m.select("g");m.attr("transform","translate("+t.left+","+t.top+")");var E=m.selectAll("path").data(function(e){return[e]});E.enter().append("path"),E.exit().remove(),E.style("stroke",function(e,t){return e.color||f(e,t)}).attr("d",d3.svg.line().x(function(e,t){return s(u(e,t))}).y(function(e,t){return o(a(e,t))}));var S=m.selectAll("circle.nv-point").data(function(e){function n(t){if(t!=-1){var n=e[t];return n.pointIndex=t,n}return null}var t=e.map(function(e,t){return a(e,t)}),r=n(t.lastIndexOf(o.domain()[1])),i=n(t.indexOf(o.domain()[0])),s=n(t.length-1);return[i,r,s].filter(function(e){return e!=null})});S.enter().append("circle"),S.exit().remove(),S.attr("cx",function(e,t){return s(u(e,e.pointIndex))}).attr("cy",function(e,t){return o(a(e,e.pointIndex))}).attr("r",2).attr("class",function(e,t){return u(e,e.pointIndex)==s.domain()[1]?"nv-point nv-currentValue":a(e,e.pointIndex)==o.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),d}var t={top:2,right:0,bottom:2,left:0},n=400,r=32,i=!0,s=d3.scale.linear(),o=d3.scale.linear(),u=function(e){return e.x},a=function(e){return e.y},f=e.utils.getColor(["#000"]),l,c,h,p;return d.options=e.utils.optionsFunc.bind(d),d.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,d):t},d.width=function(e){return arguments.length?(n=e,d):n},d.height=function(e){return arguments.length?(r=e,d):r},d.x=function(e){return arguments.length?(u=d3.functor(e),d):u},d.y=function(e){return arguments.length?(a=d3.functor(e),d):a},d.xScale=function(e){return arguments.length?(s=e,d):s},d.yScale=function(e){return arguments.length?(o=e,d):o},d.xDomain=function(e){return arguments.length?(l=e,d):l},d.yDomain=function(e){return arguments.length?(c=e,d):c},d.xRange=function(e){return arguments.length?(h=e,d):h},d.yRange=function(e){return arguments.length?(p=e,d):p},d.animate=function(e){return arguments.length?(i=e,d):i},d.color=function(t){return arguments.length?(f=e.utils.getColor(t),d):f},d},e.models.sparklinePlus=function(){"use strict";function v(e){return e.each(function(c){function O(){if(a)return;var e=C.selectAll(".nv-hoverValue").data(u),r=e.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);e.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),e.attr("transform",function(e){return"translate("+s(t.x()(c[e],e))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1);if(!u.length)return;r.append("line").attr("x1",0).attr("y1",-n.top).attr("x2",0).attr("y2",b),r.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-n.top).attr("text-anchor","end").attr("dy",".9em"),C.select(".nv-hoverValue .nv-xValue").text(f(t.x()(c[u[0]],u[0]))),r.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-n.top).attr("text-anchor","start").attr("dy",".9em"),C.select(".nv-hoverValue .nv-yValue").text(l(t.y()(c[u[0]],u[0])))}function M(){function r(e,n){var r=Math.abs(t.x()(e[0],0)-n),i=0;for(var s=0;s<e.length;s++)Math.abs(t.x()(e[s],s)-n)<r&&(r=Math.abs(t.x()(e[s],s)-n),i=s);return i}if(a)return;var e=d3.mouse(this)[0]-n.left;u=[r(c,Math.round(s.invert(e)))],O()}var m=d3.select(this),g=(r||parseInt(m.style("width"))||960)-n.left-n.right,b=(i||parseInt(m.style("height"))||400)-n.top-n.bottom;v.update=function(){v(e)},v.container=this;if(!c||!c.length){var w=m.selectAll(".nv-noData").data([d]);return w.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),w.attr("x",n.left+g/2).attr("y",n.top+b/2).text(function(e){return e}),v}m.selectAll(".nv-noData").remove();var E=t.y()(c[c.length-1],c.length-1);s=t.xScale(),o=t.yScale();var S=m.selectAll("g.nv-wrap.nv-sparklineplus").data([c]),T=S.enter().append("g").attr("class","nvd3 nv-wrap nv-sparklineplus"),N=T.append("g"),C=S.select("g");N.append("g").attr("class","nv-sparklineWrap"),N.append("g").attr("class","nv-valueWrap"),N.append("g").attr("class","nv-hoverArea"),S.attr("transform","translate("+n.left+","+n.top+")");var k=C.select(".nv-sparklineWrap");t.width(g).height(b),k.call(t);var L=C.select(".nv-valueWrap"),A=L.selectAll(".nv-currentValue").data([E]);A.enter().append("text").attr("class","nv-currentValue").attr("dx",p?-8:8).attr("dy",".9em").style("text-anchor",p?"end":"start"),A.attr("x",g+(p?n.right:0)).attr("y",h?function(e){return o(e)}:0).style("fill",t.color()(c[c.length-1],c.length-1)).text(l(E)),N.select(".nv-hoverArea").append("rect").on("mousemove",M).on("click",function(){a=!a}).on("mouseout",function(){u=[],O()}),C.select(".nv-hoverArea rect").attr("transform",function(e){return"translate("+ -n.left+","+ -n.top+")"}).attr("width",g+n.left+n.right).attr("height",b+n.top)}),v}var t=e.models.sparkline(),n={top:15,right:100,bottom:10,left:50},r=null,i=null,s,o,u=[],a=!1,f=d3.format(",r"),l=d3.format(",.2f"),c=!0,h=!0,p=!1,d="No Data Available.";return v.sparkline=t,d3.rebind(v,t,"x","y","xScale","yScale","color"),v.options=e.utils.optionsFunc.bind(v),v.margin=function(e){return arguments.length?(n.top=typeof e.top!="undefined"?e.top:n.top,n.right=typeof e.right!="undefined"?e.right:n.right,n.bottom=typeof e.bottom!="undefined"?e.bottom:n.bottom,n.left=typeof e.left!="undefined"?e.left:n.left,v):n},v.width=function(e){return arguments.length?(r=e,v):r},v.height=function(e){return arguments.length?(i=e,v):i},v.xTickFormat=function(e){return arguments.length?(f=e,v):f},v.yTickFormat=function(e){return arguments.length?(l=e,v):l},v.showValue=function(e){return arguments.length?(c=e,v):c},v.alignValue=function(e){return arguments.length?(h=e,v):h},v.rightAlignValue=function(e){return arguments.length?(p=e,v):p},v.noData=function(e){return arguments.length?(d=e,v):d},v},e.models.stackedArea=function(){"use strict";function g(e){return e.each(function(e){var a=n-t.left-t.right,b=r-t.top-t.bottom,w=d3.select(this);p=v.xScale(),d=v.yScale();var E=e;e.forEach(function(e,t){e.seriesIndex=t,e.values=e.values.map(function(e,n){return e.index=n,e.seriesIndex=t,e})});var S=e.filter(function(e){return!e.disabled});e=d3.layout.stack().order(l).offset(f).values(function(e){return e.values}).x(o).y(u).out(function(e,t,n){var r=u(e)===0?0:n;e.display={y:r,y0:t}})(S);var T=w.selectAll("g.nv-wrap.nv-stackedarea").data([e]),N=T.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedarea"),C=N.append("defs"),k=N.append("g"),L=T.select("g");k.append("g").attr("class","nv-areaWrap"),k.append("g").attr("class","nv-scatterWrap"),T.attr("transform","translate("+t.left+","+t.top+")"),v.width(a).height(b).x(o).y(function(e){return e.display.y+e.display.y0}).forceY([0]).color(e.map(function(e,t){return e.color||i(e,e.seriesIndex)}));var A=L.select(".nv-scatterWrap").datum(e);A.call(v),C.append("clipPath").attr("id","nv-edge-clip-"+s).append("rect"),T.select("#nv-edge-clip-"+s+" rect").attr("width",a).attr("height",b),L.attr("clip-path",h?"url(#nv-edge-clip-"+s+")":"");var O=d3.svg.area().x(function(e,t){return p(o(e,t))}).y0(function(e){return d(e.display.y0)}).y1(function(e){return d(e.display.y+e.display.y0)}).interpolate(c),M=d3.svg.area().x(function(e,t){return p(o(e,t))}).y0(function(e){return d(e.display.y0)}).y1(function(e){return d(e.display.y0)}),_=L.select(".nv-areaWrap").selectAll("path.nv-area").data(function(e){return e});_.enter().append("path").attr("class",function(e,t){return"nv-area nv-area-"+t}).attr("d",function(e,t){return M(e.values,e.seriesIndex)}).on("mouseover",function(e,t){d3.select(this).classed("hover",!0),m.areaMouseover({point:e,series:e.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:e.seriesIndex})}).on("mouseout",function(e,t){d3.select(this).classed("hover",!1),m.areaMouseout({point:e,series:e.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:e.seriesIndex})}).on("click",function(e,t){d3.select(this).classed("hover",!1),m.areaClick({point:e,series:e.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:e.seriesIndex})}),_.exit().remove(),_.style("fill",function(e,t){return e.color||i(e,e.seriesIndex)}).style("stroke",function(e,t){return e.color||i(e,e.seriesIndex)}),_.transition().attr("d",function(e,t){return O(e.values,t)}),v.dispatch.on("elementMouseover.area",function(e){L.select(".nv-chart-"+s+" .nv-area-"+e.seriesIndex).classed("hover",!0)}),v.dispatch.on("elementMouseout.area",function(e){L.select(".nv-chart-"+s+" .nv-area-"+e.seriesIndex).classed("hover",!1)}),g.d3_stackedOffset_stackPercent=function(e){var t=e.length,n=e[0].length,r=1/t,i,s,o,a=[];for(s=0;s<n;++s){for(i=0,o=0;i<E.length;i++)o+=u(E[i].values[s]);if(o)for(i=0;i<t;i++)e[i][s][1]/=o;else for(i=0;i<t;i++)e[i][s][1]=r}for(s=0;s<n;++s)a[s]=0;return a}}),g}var t={top:0,right:0,bottom:0,left:0},n=960,r=500,i=e.utils.defaultColor(),s=Math.floor(Math.random()*1e5),o=function(e){return e.x},u=function(e){return e.y},a="stack",f="zero",l="default",c="linear",h=!1,p,d,v=e.models.scatter(),m=d3.dispatch("tooltipShow","tooltipHide","areaClick","areaMouseover","areaMouseout");return v.size(2.2).sizeDomain([2.2,2.2]),v.dispatch.on("elementClick.area",function(e){m.areaClick(e)}),v.dispatch.on("elementMouseover.tooltip",function(e){e.pos=[e.pos[0]+t.left,e.pos[1]+t.top],m.tooltipShow(e)}),v.dispatch.on("elementMouseout.tooltip",function(e){m.tooltipHide(e)}),g.dispatch=m,g.scatter=v,d3.rebind(g,v,"interactive","size","xScale","yScale","zScale","xDomain","yDomain","xRange","yRange","sizeDomain","forceX","forceY","forceSize","clipVoronoi","useVoronoi","clipRadius","highlightPoint","clearHighlights"),g.options=e.utils.optionsFunc.bind(g),g.x=function(e){return arguments.length?(o=d3.functor(e),g):o},g.y=function(e){return arguments.length?(u=d3.functor(e),g):u},g.margin=function(e){return arguments.length?(t.top=typeof e.top!="undefined"?e.top:t.top,t.right=typeof e.right!="undefined"?e.right:t.right,t.bottom=typeof e.bottom!="undefined"?e.bottom:t.bottom,t.left=typeof e.left!="undefined"?e.left:t.left,g):t},g.width=function(e){return arguments.length?(n=e,g):n},g.height=function(e){return arguments.length?(r=e,g):r},g.clipEdge=function(e){return arguments.length?(h=e,g):h},g.color=function(t){return arguments.length?(i=e.utils.getColor(t),g):i},g.offset=function(e){return arguments.length?(f=e,g):f},g.order=function(e){return arguments.length?(l=e,g):l},g.style=function(e){if(!arguments.length)return a;a=e;switch(a){case"stack":g.offset("zero"),g.order("default");break;case"stream":g.offset("wiggle"),g.order("inside-out");break;case"stream-center":g.offset("silhouette"),g.order("inside-out");break;case"expand":g.offset("expand"),g.order("default");break;case"stack_percent":g.offset(g.d3_stackedOffset_stackPercent),g.order("default")}return g},g.interpolate=function(e){return arguments.length?(c=e,g):c},g},e.models.stackedAreaChart=function(){"use strict";function M(y){return y.each(function(y){var _=d3.select(this),D=this,P=(a||parseInt(_.style("width"))||960)-u.left-u.right,H=(f||parseInt(_.style("height"))||400)-u.top-u.bottom;M.update=function(){_.transition().duration(A).call(M)},M.container=this,S.disabled=y.map(function(e){return!!e.disabled});if(!x){var B;x={};for(B in S)S[B]instanceof Array?x[B]=S[B].slice(0):x[B]=S[B]}if(!y||!y.length||!y.filter(function(e){return e.values.length}).length){var j=_.selectAll(".nv-noData").data([T]);return j.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),j.attr("x",u.left+P/2).attr("y",u.top+H/2).text(function(e){return e}),M}_.selectAll(".nv-noData").remove(),b=t.xScale(),w=t.yScale();var F=_.selectAll("g.nv-wrap.nv-stackedAreaChart").data([y]),I=F.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),q=F.select("g");I.append("rect").style("opacity",0),I.append("g").attr("class","nv-x nv-axis"),I.append("g").attr("class","nv-y nv-axis"),I.append("g").attr("class","nv-stackedWrap"),I.append("g").attr("class","nv-legendWrap"),I.append("g").attr("class","nv-controlsWrap"),I.append("g").attr("class","nv-interactive"),q.select("rect").attr("width",P).attr("height",H);if(h){var R=c?P-C:P;i.width(R),q.select(".nv-legendWrap").datum(y).call(i),u.top!=i.height()&&(u.top=i.height(),H=(f||parseInt(_.style("height"))||400)-u.top-u.bottom),q.select(".nv-legendWrap").attr("transform","translate("+(P-R)+","+ -u.top+")")}if(c){var U=[{key:L.stacked||"Stacked",metaKey:"Stacked",disabled:t.style()!="stack",style:"stack"},{key:L.stream||"Stream",metaKey:"Stream",disabled:t.style()!="stream",style:"stream"},{key:L.expanded||"Expanded",metaKey:"Expanded",disabled:t.style()!="expand",style:"expand"},{key:L.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:t.style()!="stack_percent",style:"stack_percent"}];C=k.length/3*260,U=U.filter(function(e){return k.indexOf(e.metaKey)!==-1}),s.width(C).color(["#444","#444","#444"]),q.select(".nv-controlsWrap").datum(U).call(s),u.top!=Math.max(s.height(),i.height())&&(u.top=Math.max(s.height(),i.height()),H=(f||parseInt(_.style("height"))||400)-u.top-u.bottom),q.select(".nv-controlsWrap").attr("transform","translate(0,"+ -u.top+")")}F.attr("transform","translate("+u.left+","+u.top+")"),v&&q.select(".nv-y.nv-axis").attr("transform","translate("+P+",0)"),m&&(o.width(P).height(H).margin({left:u.left,top:u.top}).svgContainer(_).xScale(b),F.select(".nv-interactive").call(o)),t.width(P).height(H);var z=q.select(".nv-stackedWrap").datum(y);z.transition().call(t),p&&(n.scale(b).ticks(P/100).tickSize(-H,0),q.select(".nv-x.nv-axis").attr("transform","translate(0,"+H+")"),q.select(".nv-x.nv-axis").transition().duration(0).call(n)),d&&(r.scale(w).ticks(t.offset()=="wiggle"?0:H/36).tickSize(-P,0).setTickFormat(t.style()=="expand"||t.style()=="stack_percent"?d3.format("%"):E),q.select(".nv-y.nv-axis").transition().duration(0).call(r)),t.dispatch.on("areaClick.toggle",function(e){y.filter(function(e){return!e.disabled}).length===1?y.forEach(function(e){e.disabled=!1}):y.forEach(function(t,n){t.disabled=n!=e.seriesIndex}),S.disabled=y.map(function(e){return!!e.disabled}),N.stateChange(S),M.update()}),i.dispatch.on("stateChange",function(e){S.disabled=e.disabled,N.stateChange(S),M.update()}),s.dispatch.on("legendClick",function(e,n){if(!e.disabled)return;U=U.map(function(e){return e.disabled=!0,e}),e.disabled=!1,t.style(e.style),S.style=t.style(),N.stateChange(S),M.update()}),o.dispatch.on("elementMousemove",function(i){t.clearHighlights();var s,a,f,c=[];y.filter(function(e,t){return e.seriesIndex=t,!e.disabled}).forEach(function(n,r){a=e.interactiveBisect(n.values,i.pointXValue,M.x()),t.highlightPoint(r,a,!0);var o=n.values[a];if(typeof o=="undefined")return;typeof s=="undefined"&&(s=o),typeof f=="undefined"&&(f=M.xScale()(M.x()(o,a)));var u=t.style()=="expand"?o.display.y:M.y()(o,a);c.push({key:n.key,value:u,color:l(n,n.seriesIndex),stackedValue:o.display})}),c.reverse();if(c.length>2){var h=M.yScale().invert(i.mouseY),p=Infinity,d=null;c.forEach(function(e,t){h=Math.abs(h);var n=Math.abs(e.stackedValue.y0),r=Math.abs(e.stackedValue.y);if(h>=n&&h<=r+n){d=t;return}}),d!=null&&(c[d].highlight=!0)}var v=n.tickFormat()(M.x()(s,a)),m=t.style()=="expand"?function(e,t){return d3.format(".1%")(e)}:function(e,t){return r.tickFormat()(e)};o.tooltip.position({left:f+u.left,top:i.mouseY+u.top}).chartContainer(D.parentNode).enabled(g).valueFormatter(m).data({value:v,series:c})(),o.renderGuideLine(f)}),o.dispatch.on("elementMouseout",function(e){N.tooltipHide(),t.clearHighlights()}),N.on("tooltipShow",function(e){g&&O(e,D.parentNode)}),N.on("changeState",function(e){typeof e.disabled!="undefined"&&y.length===e.disabled.length&&(y.forEach(function(t,n){t.disabled=e.disabled[n]}),S.disabled=e.disabled),typeof e.style!="undefined"&&t.style(e.style),M.update()})}),M}var t=e.models.stackedArea(),n=e.models.axis(),r=e.models.axis(),i=e.models.legend(),s=e.models.legend(),o=e.interactiveGuideline(),u={top:30,right:25,bottom:50,left:60},a=null,f=null,l=e.utils.defaultColor(),c=!0,h=!0,p=!0,d=!0,v=!1,m=!1,g=!0,y=function(e,t,n,r,i){return"<h3>"+e+"</h3>"+"<p>"+n+" on "+t+"</p>"},b,w,E=d3.format(",.2f"),S={style:t.style()},x=null,T="No Data Available.",N=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState"),C=250,k=["Stacked","Stream","Expanded"],L={},A=250;n.orient("bottom").tickPadding(7),r.orient(v?"right":"left"),s.updateState(!1);var O=function(i,s){var o=i.pos[0]+(s.offsetLeft||0),u=i.pos[1]+(s.offsetTop||0),a=n.tickFormat()(t.x()(i.point,i.pointIndex)),f=r.tickFormat()(t.y()(i.point,i.pointIndex)),l=y(i.series.key,a,f,i,M);e.tooltip.show([o,u],l,i.value<0?"n":"s",null,s)};return t.dispatch.on("tooltipShow",function(e){e.pos=[e.pos[0]+u.left,e.pos[1]+u.top],N.tooltipShow(e)}),t.dispatch.on("tooltipHide",function(e){N.tooltipHide(e)}),N.on("tooltipHide",function(){g&&e.tooltip.cleanup()}),M.dispatch=N,M.stacked=t,M.legend=i,M.controls=s,M.xAxis=n,M.yAxis=r,M.interactiveLayer=o,d3.rebind(M,t,"x","y","size","xScale","yScale","xDomain","yDomain","xRange","yRange","sizeDomain","interactive","useVoronoi","offset","order","style","clipEdge","forceX","forceY","forceSize","interpolate"),M.options=e.utils.optionsFunc.bind(M),M.margin=function(e){return arguments.length?(u.top=typeof e.top!="undefined"?e.top:u.top,u.right=typeof e.right!="undefined"?e.right:u.right,u.bottom=typeof e.bottom!="undefined"?e.bottom:u.bottom,u.left=typeof e.left!="undefined"?e.left:u.left,M):u},M.width=function(e){return arguments.length?(a=e,M):a},M.height=function(e){return arguments.length?(f=e,M):f},M.color=function(n){return arguments.length?(l=e.utils.getColor(n),i.color(l),t.color(l),M):l},M.showControls=function(e){return arguments.length?(c=e,M):c},M.showLegend=function(e){return arguments.length?(h=e,M):h},M.showXAxis=function(e){return arguments.length?(p=e,M):p},M.showYAxis=function(e){return arguments.length?(d=e,M):d},M.rightAlignYAxis=function(e){return arguments.length?(v=e,r.orient(e?"right":"left"),M):v},M.useInteractiveGuideline=function(e){return arguments.length?(m=e,e===!0&&(M.interactive(!1),M.useVoronoi(!1)),M):m},M.tooltip=function(e){return arguments.length?(y=e,M):y},M.tooltips=function(e){return arguments.length?(g=e,M):g},M.tooltipContent=function(e){return arguments.length?(y=e,M):y},M.state=function(e){return arguments.length?(S=e,M):S},M.defaultState=function(e){return arguments.length?(x=e,M):x},M.noData=function(e){return arguments.length?(T=e,M)
|