632636fb by Jeff Balicki

home content

1 parent e4dc7cb8
Showing 104 changed files with 14348 additions and 65 deletions
1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
8
9 Preamble
10
11 The licenses for most software are designed to take away your
12 freedom to share and change it. By contrast, the GNU General Public
13 License is intended to guarantee your freedom to share and change free
14 software--to make sure the software is free for all its users. This
15 General Public License applies to most of the Free Software
16 Foundation's software and to any other program whose authors commit to
17 using it. (Some other Free Software Foundation software is covered by
18 the GNU Lesser General Public License instead.) You can apply it to
19 your programs, too.
20
21 When we speak of free software, we are referring to freedom, not
22 price. Our General Public Licenses are designed to make sure that you
23 have the freedom to distribute copies of free software (and charge for
24 this service if you wish), that you receive source code or can get it
25 if you want it, that you can change the software or use pieces of it
26 in new free programs; and that you know you can do these things.
27
28 To protect your rights, we need to make restrictions that forbid
29 anyone to deny you these rights or to ask you to surrender the rights.
30 These restrictions translate to certain responsibilities for you if you
31 distribute copies of the software, or if you modify it.
32
33 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must give the recipients all the rights that
35 you have. You must make sure that they, too, receive or can get the
36 source code. And you must show them these terms so they know their
37 rights.
38
39 We protect your rights with two steps: (1) copyright the software, and
40 (2) offer you this license which gives you legal permission to copy,
41 distribute and/or modify the software.
42
43 Also, for each author's protection and ours, we want to make certain
44 that everyone understands that there is no warranty for this free
45 software. If the software is modified by someone else and passed on, we
46 want its recipients to know that what they have is not the original, so
47 that any problems introduced by others will not reflect on the original
48 authors' reputations.
49
50 Finally, any free program is threatened constantly by software
51 patents. We wish to avoid the danger that redistributors of a free
52 program will individually obtain patent licenses, in effect making the
53 program proprietary. To prevent this, we have made it clear that any
54 patent must be licensed for everyone's free use or not licensed at all.
55
56 The precise terms and conditions for copying, distribution and
57 modification follow.
58
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
62 0. This License applies to any program or other work which contains
63 a notice placed by the copyright holder saying it may be distributed
64 under the terms of this General Public License. The "Program", below,
65 refers to any such program or work, and a "work based on the Program"
66 means either the Program or any derivative work under copyright law:
67 that is to say, a work containing the Program or a portion of it,
68 either verbatim or with modifications and/or translated into another
69 language. (Hereinafter, translation is included without limitation in
70 the term "modification".) Each licensee is addressed as "you".
71
72 Activities other than copying, distribution and modification are not
73 covered by this License; they are outside its scope. The act of
74 running the Program is not restricted, and the output from the Program
75 is covered only if its contents constitute a work based on the
76 Program (independent of having been made by running the Program).
77 Whether that is true depends on what the Program does.
78
79 1. You may copy and distribute verbatim copies of the Program's
80 source code as you receive it, in any medium, provided that you
81 conspicuously and appropriately publish on each copy an appropriate
82 copyright notice and disclaimer of warranty; keep intact all the
83 notices that refer to this License and to the absence of any warranty;
84 and give any other recipients of the Program a copy of this License
85 along with the Program.
86
87 You may charge a fee for the physical act of transferring a copy, and
88 you may at your option offer warranty protection in exchange for a fee.
89
90 2. You may modify your copy or copies of the Program or any portion
91 of it, thus forming a work based on the Program, and copy and
92 distribute such modifications or work under the terms of Section 1
93 above, provided that you also meet all of these conditions:
94
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
97
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
102
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
113
114 These requirements apply to the modified work as a whole. If
115 identifiable sections of that work are not derived from the Program,
116 and can be reasonably considered independent and separate works in
117 themselves, then this License, and its terms, do not apply to those
118 sections when you distribute them as separate works. But when you
119 distribute the same sections as part of a whole which is a work based
120 on the Program, the distribution of the whole must be on the terms of
121 this License, whose permissions for other licensees extend to the
122 entire whole, and thus to each and every part regardless of who wrote it.
123
124 Thus, it is not the intent of this section to claim rights or contest
125 your rights to work written entirely by you; rather, the intent is to
126 exercise the right to control the distribution of derivative or
127 collective works based on the Program.
128
129 In addition, mere aggregation of another work not based on the Program
130 with the Program (or with a work based on the Program) on a volume of
131 a storage or distribution medium does not bring the other work under
132 the scope of this License.
133
134 3. You may copy and distribute the Program (or a work based on it,
135 under Section 2) in object code or executable form under the terms of
136 Sections 1 and 2 above provided that you also do one of the following:
137
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
141
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
148
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
154
155 The source code for a work means the preferred form of the work for
156 making modifications to it. For an executable work, complete source
157 code means all the source code for all modules it contains, plus any
158 associated interface definition files, plus the scripts used to
159 control compilation and installation of the executable. However, as a
160 special exception, the source code distributed need not include
161 anything that is normally distributed (in either source or binary
162 form) with the major components (compiler, kernel, and so on) of the
163 operating system on which the executable runs, unless that component
164 itself accompanies the executable.
165
166 If distribution of executable or object code is made by offering
167 access to copy from a designated place, then offering equivalent
168 access to copy the source code from the same place counts as
169 distribution of the source code, even though third parties are not
170 compelled to copy the source along with the object code.
171
172 4. You may not copy, modify, sublicense, or distribute the Program
173 except as expressly provided under this License. Any attempt
174 otherwise to copy, modify, sublicense or distribute the Program is
175 void, and will automatically terminate your rights under this License.
176 However, parties who have received copies, or rights, from you under
177 this License will not have their licenses terminated so long as such
178 parties remain in full compliance.
179
180 5. You are not required to accept this License, since you have not
181 signed it. However, nothing else grants you permission to modify or
182 distribute the Program or its derivative works. These actions are
183 prohibited by law if you do not accept this License. Therefore, by
184 modifying or distributing the Program (or any work based on the
185 Program), you indicate your acceptance of this License to do so, and
186 all its terms and conditions for copying, distributing or modifying
187 the Program or works based on it.
188
189 6. Each time you redistribute the Program (or any work based on the
190 Program), the recipient automatically receives a license from the
191 original licensor to copy, distribute or modify the Program subject to
192 these terms and conditions. You may not impose any further
193 restrictions on the recipients' exercise of the rights granted herein.
194 You are not responsible for enforcing compliance by third parties to
195 this License.
196
197 7. If, as a consequence of a court judgment or allegation of patent
198 infringement or for any other reason (not limited to patent issues),
199 conditions are imposed on you (whether by court order, agreement or
200 otherwise) that contradict the conditions of this License, they do not
201 excuse you from the conditions of this License. If you cannot
202 distribute so as to satisfy simultaneously your obligations under this
203 License and any other pertinent obligations, then as a consequence you
204 may not distribute the Program at all. For example, if a patent
205 license would not permit royalty-free redistribution of the Program by
206 all those who receive copies directly or indirectly through you, then
207 the only way you could satisfy both it and this License would be to
208 refrain entirely from distribution of the Program.
209
210 If any portion of this section is held invalid or unenforceable under
211 any particular circumstance, the balance of the section is intended to
212 apply and the section as a whole is intended to apply in other
213 circumstances.
214
215 It is not the purpose of this section to induce you to infringe any
216 patents or other property right claims or to contest validity of any
217 such claims; this section has the sole purpose of protecting the
218 integrity of the free software distribution system, which is
219 implemented by public license practices. Many people have made
220 generous contributions to the wide range of software distributed
221 through that system in reliance on consistent application of that
222 system; it is up to the author/donor to decide if he or she is willing
223 to distribute software through any other system and a licensee cannot
224 impose that choice.
225
226 This section is intended to make thoroughly clear what is believed to
227 be a consequence of the rest of this License.
228
229 8. If the distribution and/or use of the Program is restricted in
230 certain countries either by patents or by copyrighted interfaces, the
231 original copyright holder who places the Program under this License
232 may add an explicit geographical distribution limitation excluding
233 those countries, so that distribution is permitted only in or among
234 countries not thus excluded. In such case, this License incorporates
235 the limitation as if written in the body of this License.
236
237 9. The Free Software Foundation may publish revised and/or new versions
238 of the General Public License from time to time. Such new versions will
239 be similar in spirit to the present version, but may differ in detail to
240 address new problems or concerns.
241
242 Each version is given a distinguishing version number. If the Program
243 specifies a version number of this License which applies to it and "any
244 later version", you have the option of following the terms and conditions
245 either of that version or of any later version published by the Free
246 Software Foundation. If the Program does not specify a version number of
247 this License, you may choose any version ever published by the Free Software
248 Foundation.
249
250 10. If you wish to incorporate parts of the Program into other free
251 programs whose distribution conditions are different, write to the author
252 to ask for permission. For software which is copyrighted by the Free
253 Software Foundation, write to the Free Software Foundation; we sometimes
254 make exceptions for this. Our decision will be guided by the two goals
255 of preserving the free status of all derivatives of our free software and
256 of promoting the sharing and reuse of software generally.
257
258 NO WARRANTY
259
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 REPAIR OR CORRECTION.
269
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 POSSIBILITY OF SUCH DAMAGES.
279
280 END OF TERMS AND CONDITIONS
281
282 How to Apply These Terms to Your New Programs
283
284 If you develop a new program, and you want it to be of the greatest
285 possible use to the public, the best way to achieve this is to make it
286 free software which everyone can redistribute and change under these terms.
287
288 To do so, attach the following notices to the program. It is safest
289 to attach them to the start of each source file to most effectively
290 convey the exclusion of warranty; and each file should have at least
291 the "copyright" line and a pointer to where the full notice is found.
292
293 <one line to give the program's name and a brief idea of what it does.>
294 Copyright (C) <year> <name of author>
295
296 This program is free software; you can redistribute it and/or modify
297 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
299 (at your option) any later version.
300
301 This program is distributed in the hope that it will be useful,
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 GNU General Public License for more details.
305
306 You should have received a copy of the GNU General Public License along
307 with this program; if not, write to the Free Software Foundation, Inc.,
308 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309
310 Also add information on how to contact you by electronic and paper mail.
311
312 If the program is interactive, make it output a short notice like this
313 when it starts in an interactive mode:
314
315 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 This is free software, and you are welcome to redistribute it
318 under certain conditions; type `show c' for details.
319
320 The hypothetical commands `show w' and `show c' should show the appropriate
321 parts of the General Public License. Of course, the commands you use may
322 be called something other than `show w' and `show c'; they could even be
323 mouse-clicks or menu items--whatever suits your program.
324
325 You should also get your employer (if you work as a programmer) or your
326 school, if any, to sign a "copyright disclaimer" for the program, if
327 necessary. Here is a sample; alter the names:
328
329 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
332 <signature of Ty Coon>, 1 April 1989
333 Ty Coon, President of Vice
334
335 This General Public License does not permit incorporating your program into
336 proprietary programs. If your program is a subroutine library, you may
337 consider it more useful to permit linking proprietary applications with the
338 library. If this is what you want to do, use the GNU Lesser General
339 Public License instead of this License.
1 {
2 "block_lab": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M3.823,5.688c0.034,-0.045 0.075,-0.085 0.126,-0.118l5.211,-3.008c0.085,-0.044 0.178,-0.067 0.278,-0.061c0.057,0.007 0.081,0.002 0.194,0.061l5.212,3.008l5.212,3.01l0.027,0.017l0.028,0.02l0.024,0.021l0.024,0.022l0.022,0.024l0.02,0.027l0.018,0.027l0.017,0.029l0.015,0.029l0.012,0.031l0.01,0.032l0.009,0.031l0.005,0.033l0.003,0.033l0.001,0.032l0,6.018c-0.012,0.248 -0.037,0.28 -0.235,0.409l-10.384,5.994c-0.137,0.105 -0.317,0.127 -0.512,0.024l-5.211,-3.009c-0.027,-0.017 -0.049,-0.032 -0.069,-0.046c-0.125,-0.088 -0.151,-0.139 -0.163,-0.286c-0.009,-0.051 -0.011,-0.101 -0.004,-0.149l0,-11.964c0.006,-0.11 0.046,-0.211 0.11,-0.291Zm5.102,14.518l0,-10.945l-4.268,-2.464l0,10.944l4.268,2.465Zm6.155,-9.026l3.796,-2.192l-3.796,-2.191l0,4.383Z\"/></svg>",
3 "account_balance": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M6.5 10h-2v7h2v-7zm6 0h-2v7h2v-7zm8.5 9H2v2h19v-2zm-2.5-9h-2v7h2v-7zm-7-6.74L16.71 6H6.29l5.21-2.74m0-2.26L2 6v2h19V6l-9.5-5z\"/></svg>",
4 "account_circle": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM7.07 18.28c.43-.9 3.05-1.78 4.93-1.78s4.51.88 4.93 1.78C15.57 19.36 13.86 20 12 20s-3.57-.64-4.93-1.72zm11.29-1.45c-1.43-1.74-4.9-2.33-6.36-2.33s-4.93.59-6.36 2.33C4.62 15.49 4 13.82 4 12c0-4.41 3.59-8 8-8s8 3.59 8 8c0 1.82-.62 3.49-1.64 4.83zM12 6c-1.94 0-3.5 1.56-3.5 3.5S10.06 13 12 13s3.5-1.56 3.5-3.5S13.94 6 12 6zm0 5c-.83 0-1.5-.67-1.5-1.5S11.17 8 12 8s1.5.67 1.5 1.5S12.83 11 12 11z\"/></svg>",
5 "alarm": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12.5 8H11v6l4.75 2.85.75-1.23-4-2.37zm4.837-6.19l4.607 3.845-1.28 1.535-4.61-3.843zm-10.674 0l1.282 1.536L3.337 7.19l-1.28-1.536zM12 4c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7z\"/></svg>",
6 "inbox": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5v-3h3.56c.69 1.19 1.97 2 3.45 2s2.75-.81 3.45-2H19v3zm0-5h-4.99c0 1.1-.9 2-2 2s-2-.9-2-2H5V5h14v9z\"/></svg>",
7 "announcement": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12zM11 5h2v6h-2zm0 8h2v2h-2z\"/></svg>",
8 "assessment": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7 10h2v7H7zm4-3h2v10h-2zm4 6h2v4h-2z\"/></svg>",
9 "assignment_ind": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3h-4.18C14.4 1.84 13.3 1 12 1s-2.4.84-2.82 2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7-.25c.22 0 .41.1.55.25.12.13.2.31.2.5 0 .41-.34.75-.75.75s-.75-.34-.75-.75c0-.19.08-.37.2-.5.14-.15.33-.25.55-.25zM19 19H5V5h14v14zM12 6c-1.65 0-3 1.35-3 3s1.35 3 3 3 3-1.35 3-3-1.35-3-3-3zm0 4c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-6 6.47V18h12v-1.53c0-2.5-3.97-3.58-6-3.58s-6 1.07-6 3.58zM8.31 16c.69-.56 2.38-1.12 3.69-1.12s3.01.56 3.69 1.12H8.31z\"/></svg>",
10 "book": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M18 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM9 4h2v5l-1-.75L9 9V4zm9 16H6V4h1v9l3-2.25L13 13V4h5v16z\"/></svg>",
11 "bookmark_border": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17 3H7c-1.1 0-2 .9-2 2v16l7-3 7 3V5c0-1.1-.9-2-2-2zm0 15l-5-2.18L7 18V5h10v13z\"/></svg>",
12 "build": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M22.61 18.99l-9.08-9.08c.93-2.34.45-5.1-1.44-7C9.79.61 6.21.4 3.66 2.26L7.5 6.11 6.08 7.52 2.25 3.69C.39 6.23.6 9.82 2.9 12.11c1.86 1.86 4.57 2.35 6.89 1.48l9.11 9.11c.39.39 1.02.39 1.41 0l2.3-2.3c.4-.38.4-1.01 0-1.41zm-3 1.6l-9.46-9.46c-.61.45-1.29.72-2 .82-1.36.2-2.79-.21-3.83-1.25C3.37 9.76 2.93 8.5 3 7.26l3.09 3.09 4.24-4.24-3.09-3.09c1.24-.07 2.49.37 3.44 1.31 1.08 1.08 1.49 2.57 1.24 3.96-.12.71-.42 1.37-.88 1.96l9.45 9.45-.88.89z\"/></svg>",
13 "today": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V9h14v10zm0-12H5V5h14v2zM7 11h5v5H7z\"/></svg>",
14 "card_giftcard": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 6h-2.18c.11-.31.18-.65.18-1 0-1.66-1.34-3-3-3-1.05 0-1.96.54-2.5 1.35l-.5.67-.5-.68C10.96 2.54 10.05 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm11 15H4v-2h16v2zm0-5H4V8h5.08L7 10.83 8.62 12 12 7.4l3.38 4.6L17 10.83 14.92 8H20v6z\"/></svg>",
15 "check_circle": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm4.59-12.42L10 14.17l-2.59-2.58L6 13l4 4 8-8z\"/></svg>",
16 "code": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z\"/></svg>",
17 "credit_card": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z\"/></svg>",
18 "dashboard": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 5v2h-4V5h4M9 5v6H5V5h4m10 8v6h-4v-6h4M9 17v2H5v-2h4M21 3h-8v6h8V3zM11 3H3v10h8V3zm10 8h-8v10h8V11zm-10 4H3v6h8v-6z\"/></svg>",
19 "description": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M8 16h8v2H8zm0-4h8v2H8zm6-10H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z\"/></svg>",
20 "donut_small": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M14.82 11h7.13c-.47-4.72-4.23-8.48-8.95-8.95v7.13c.85.31 1.51.97 1.82 1.82zM15 4.58C17 5.4 18.6 7 19.42 9h-3.43c-.28-.37-.62-.71-.99-.99V4.58zM2 12c0 5.19 3.95 9.45 9 9.95v-7.13C9.84 14.4 9 13.3 9 12c0-1.3.84-2.4 2-2.82V2.05c-5.05.5-9 4.76-9 9.95zm7-7.42v3.44c-1.23.92-2 2.39-2 3.98 0 1.59.77 3.06 2 3.99v3.44C6.04 18.24 4 15.35 4 12c0-3.35 2.04-6.24 5-7.42zm4 10.24v7.13c4.72-.47 8.48-4.23 8.95-8.95h-7.13c-.31.85-.97 1.51-1.82 1.82zm2 1.17c.37-.28.71-.61.99-.99h3.43C18.6 17 17 18.6 15 19.42v-3.43z\"/></svg>",
21 "explore": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-5.5-2.5l7.51-3.49L17.5 6.5 9.99 9.99 6.5 17.5zm5.5-6.6c.61 0 1.1.49 1.1 1.1s-.49 1.1-1.1 1.1-1.1-.49-1.1-1.1.49-1.1 1.1-1.1z\"/></svg>",
22 "extension": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M10.5 4.5c.28 0 .5.22.5.5v2h6v6h2c.28 0 .5.22.5.5s-.22.5-.5.5h-2v6h-2.12c-.68-1.75-2.39-3-4.38-3s-3.7 1.25-4.38 3H4v-2.12c1.75-.68 3-2.39 3-4.38 0-1.99-1.24-3.7-2.99-4.38L4 7h6V5c0-.28.22-.5.5-.5m0-2C9.12 2.5 8 3.62 8 5H4c-1.1 0-1.99.9-1.99 2v3.8h.29c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-.3c0-1.49 1.21-2.7 2.7-2.7s2.7 1.21 2.7 2.7v.3H17c1.1 0 2-.9 2-2v-4c1.38 0 2.5-1.12 2.5-2.5S20.38 11 19 11V7c0-1.1-.9-2-2-2h-4c0-1.38-1.12-2.5-2.5-2.5z\"/></svg>",
23 "face": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M10.25 13c0 .69-.56 1.25-1.25 1.25S7.75 13.69 7.75 13s.56-1.25 1.25-1.25 1.25.56 1.25 1.25zM15 11.75c-.69 0-1.25.56-1.25 1.25s.56 1.25 1.25 1.25 1.25-.56 1.25-1.25-.56-1.25-1.25-1.25zm7 .25c0 5.52-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2s10 4.48 10 10zM10.66 4.12C12.06 6.44 14.6 8 17.5 8c.46 0 .91-.05 1.34-.12C17.44 5.56 14.9 4 12 4c-.46 0-.91.05-1.34.12zM4.42 9.47c1.71-.97 3.03-2.55 3.66-4.44C6.37 6 5.05 7.58 4.42 9.47zM20 12c0-.78-.12-1.53-.33-2.24-.7.15-1.42.24-2.17.24-3.13 0-5.92-1.44-7.76-3.69C8.69 8.87 6.6 10.88 4 11.86c.01.04 0 .09 0 .14 0 4.41 3.59 8 8 8s8-3.59 8-8z\"/></svg>",
24 "favorite_border": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z\"/></svg>",
25 "fingerprint": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39s-4.66 1.97-4.66 4.39c0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94s3.08 1.32 3.08 2.94c0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z\"/></svg>",
26 "star_border": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z\"/></svg>",
27 "help_outline": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z\"/></svg>",
28 "home": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 5.69l5 4.5V18h-2v-6H9v6H7v-7.81l5-4.5M12 3L2 12h3v8h6v-6h2v6h6v-8h3L12 3z\"/></svg>",
29 "hourglass_empty": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6zm10 14.5V20H8v-3.5l4-4 4 4zm-4-5l-4-4V4h8v3.5l-4 4z\"/></svg>",
30 "lock": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><g fill=\"none\"><path d=\"M0 0h24v24H0V0z\"/><path opacity=\".87\" d=\"M0 0h24v24H0V0z\"/></g><path d=\"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z\"/></svg>",
31 "info": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\"/></svg>",
32 "label": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16zM16 17H5V7h11l3.55 5L16 17z\"/></svg>",
33 "language": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2s.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2s.07-1.35.16-2h4.68c.09.65.16 1.32.16 2s-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2s-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z\"/></svg>",
34 "list": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><g fill=\"none\"><path d=\"M0 0h24v24H0V0z\"/><path opacity=\".87\" d=\"M0 0h24v24H0V0z\"/></g><path d=\"M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7zm-4 6h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z\"/></svg>",
35 "note_add": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M13 11h-2v3H8v2h3v3h2v-3h3v-2h-3zm1-9H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z\"/></svg>",
36 "perm_identity": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 6c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2m0 9c2.7 0 5.8 1.29 6 2v1H6v-.99c.2-.72 3.3-2.01 6-2.01m0-11C9.79 4 8 5.79 8 8s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 9c-2.67 0-8 1.34-8 4v3h16v-3c0-2.66-5.33-4-8-4z\"/></svg>",
37 "pets": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><circle cx=\"4.5\" cy=\"9.5\" r=\"2.5\"/><circle cx=\"9\" cy=\"5.5\" r=\"2.5\"/><circle cx=\"15\" cy=\"5.5\" r=\"2.5\"/><circle cx=\"19.5\" cy=\"9.5\" r=\"2.5\"/><path d=\"M17.34 14.86c-.87-1.02-1.6-1.89-2.48-2.91-.46-.54-1.05-1.08-1.75-1.32-.11-.04-.22-.07-.33-.09-.25-.04-.52-.04-.78-.04s-.53 0-.79.05c-.11.02-.22.05-.33.09-.7.24-1.28.78-1.75 1.32-.87 1.02-1.6 1.89-2.48 2.91-1.31 1.31-2.92 2.76-2.62 4.79.29 1.02 1.02 2.03 2.33 2.32.73.15 3.06-.44 5.54-.44h.18c2.48 0 4.81.58 5.54.44 1.31-.29 2.04-1.31 2.33-2.32.31-2.04-1.3-3.49-2.61-4.8z\"/></svg>",
38 "schedule": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z\"/></svg>",
39 "search": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/></svg>",
40 "settings": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73 0 .21-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z\"/></svg>",
41 "shopping_cart": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M15.55 13c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.37-.66-.11-1.48-.87-1.48H5.21l-.94-2H1v2h2l3.6 7.59-1.35 2.44C4.52 15.37 5.48 17 7 17h12v-2H7l1.1-2h7.45zM6.16 6h12.15l-2.76 5H8.53L6.16 6zM7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z\"/></svg>",
42 "theaters": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm6 10h-4V5h4v14zm4-2h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z\"/></svg>",
43 "translate": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z\"/></svg>",
44 "view_column": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 5v13h17V5H4zm10 2v9h-3V7h3zM6 7h3v9H6V7zm13 9h-3V7h3v9z\"/></svg>",
45 "view_carousel": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M2 6h4v11H2zm5 13h10V4H7v15zM9 6h6v11H9V6zm9 0h4v11h-4z\"/></svg>",
46 "view_day": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 18H2v2h19v-2zm-2-8v4H4v-4h15m1-2H3c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h17c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1zm1-4H2v2h19V4z\"/></svg>",
47 "view_module": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 5v13h17V5H4zm10 2v3.5h-3V7h3zM6 7h3v3.5H6V7zm0 9v-3.5h3V16H6zm5 0v-3.5h3V16h-3zm8 0h-3v-3.5h3V16zm-3-5.5V7h3v3.5h-3z\"/></svg>",
48 "view_quilt": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 5v13h17V5H4zm2 11V7h3v9H6zm5 0v-3.5h3V16h-3zm8 0h-3v-3.5h3V16zm-8-5.5V7h8v3.5h-8z\"/></svg>",
49 "work_outline": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M14 6V4h-4v2h4zM4 8v11h16V8H4zm16-2c1.11 0 2 .89 2 2v11c0 1.11-.89 2-2 2H4c-1.11 0-2-.89-2-2l.01-11c0-1.11.88-2 1.99-2h4V4c0-1.11.89-2 2-2h4c1.11 0 2 .89 2 2v2h4z\"/></svg>",
50 "play_circle_outline": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M10 16.5l6-4.5-6-4.5zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\"/></svg>",
51 "mic_none": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z\"/></svg>",
52 "movie": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 6.47L5.76 10H20v8H4V6.47M22 4h-4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4z\"/></svg>",
53 "chat_bubble_outline": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z\"/></svg>",
54 "email": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0l-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z\"/></svg>",
55 "forum": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M15 4v7H5.17L4 12.17V4h11m1-2H3c-.55 0-1 .45-1 1v14l4-4h10c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm5 4h-2v9H6v2c0 .55.45 1 1 1h11l4 4V7c0-.55-.45-1-1-1z\"/></svg>",
56 "sentiment_satisfied_alt": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><circle cx=\"15.5\" cy=\"9.5\" r=\"1.5\"/><circle cx=\"8.5\" cy=\"9.5\" r=\"1.5\"/><path d=\"M12 16c-1.48 0-2.75-.81-3.45-2H6.88c.8 2.05 2.79 3.5 5.12 3.5s4.32-1.45 5.12-3.5h-1.67c-.69 1.19-1.97 2-3.45 2zm-.01-14C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z\"/></svg>",
57 "vpn_key": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M22 19h-6v-4h-2.68c-1.14 2.42-3.6 4-6.32 4-3.86 0-7-3.14-7-7s3.14-7 7-7c2.72 0 5.17 1.58 6.32 4H24v6h-2v4zm-4-2h2v-4h2v-2H11.94l-.23-.67C11.01 8.34 9.11 7 7 7c-2.76 0-5 2.24-5 5s2.24 5 5 5c2.11 0 4.01-1.34 4.71-3.33l.23-.67H18v4zM7 15c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z\"/></svg>",
58 "create": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM5.92 19H5v-.92l9.06-9.06.92.92L5.92 19zM20.71 5.63l-2.34-2.34c-.2-.2-.45-.29-.71-.29s-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41z\"/></svg>",
59 "add_circle_outline": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z\"/></svg>",
60 "font_download": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M9.17 15.5h5.64l1.14 3h2.09l-5.11-13h-1.86l-5.11 13h2.09l1.12-3zM12 7.98l2.07 5.52H9.93L12 7.98zM20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 18H4V4h16v16z\"/></svg>",
61 "link": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z\"/></svg>",
62 "save": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm2 16H5V5h11.17L19 7.83V19zm-7-7c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zM6 6h9v4H6z\"/></svg>",
63 "waves": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M17 16.99c-1.35 0-2.2.42-2.95.8-.65.33-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.38-1.57-.8-2.95-.8s-2.2.42-2.95.8c-.65.33-1.17.6-2.05.6v1.95c1.35 0 2.2-.42 2.95-.8.65-.33 1.17-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.42 2.95-.8c.65-.33 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8v-1.95c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8zm0-4.45c-1.35 0-2.2.43-2.95.8-.65.32-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.38-1.57-.8-2.95-.8s-2.2.43-2.95.8c-.65.32-1.17.6-2.05.6v1.95c1.35 0 2.2-.43 2.95-.8.65-.35 1.15-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.35 1.15-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.58.8 2.95.8v-1.95c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8zm2.95-8.08c-.75-.38-1.58-.8-2.95-.8s-2.2.42-2.95.8c-.65.32-1.18.6-2.05.6-.9 0-1.4-.25-2.05-.6-.75-.37-1.57-.8-2.95-.8s-2.2.42-2.95.8c-.65.33-1.17.6-2.05.6v1.93c1.35 0 2.2-.43 2.95-.8.65-.33 1.17-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.32 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8V5.04c-.9 0-1.4-.25-2.05-.58zM17 8.09c-1.35 0-2.2.43-2.95.8-.65.35-1.15.6-2.05.6s-1.4-.25-2.05-.6c-.75-.38-1.57-.8-2.95-.8s-2.2.43-2.95.8c-.65.35-1.15.6-2.05.6v1.95c1.35 0 2.2-.43 2.95-.8.65-.32 1.18-.6 2.05-.6s1.4.25 2.05.6c.75.38 1.57.8 2.95.8s2.2-.43 2.95-.8c.65-.32 1.18-.6 2.05-.6.9 0 1.4.25 2.05.6.75.38 1.58.8 2.95.8V9.49c-.9 0-1.4-.25-2.05-.6-.75-.38-1.6-.8-2.95-.8z\"/></svg>",
64 "battery_charging_full": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V5.33C17 4.6 16.4 4 15.67 4zM11 20v-5.5H9L13 7v5.5h2L11 20z\"/></svg>",
65 "brightness_low": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69zm-2 5.79V18h-3.52L12 20.48 9.52 18H6v-3.52L3.52 12 6 9.52V6h3.52L12 3.52 14.48 6H18v3.52L20.48 12 18 14.48zM12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm0 10c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z\"/></svg>",
66 "devices": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 6h18V4H4c-1.1 0-2 .9-2 2v11H0v3h14v-3H4V6zm19 2h-6c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h6c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1zm-1 9h-4v-7h4v7z\"/></svg>",
67 "location_searching": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20.94 11c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z\"/></svg>",
68 "wallpaper": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M4 4h7V2H4c-1.1 0-2 .9-2 2v7h2V4zm6 9l-4 5h12l-3-4-2.03 2.71L10 13zm7-4.5c0-.83-.67-1.5-1.5-1.5S14 7.67 14 8.5s.67 1.5 1.5 1.5S17 9.33 17 8.5zM20 2h-7v2h7v7h2V4c0-1.1-.9-2-2-2zm0 18h-7v2h7c1.1 0 2-.9 2-2v-7h-2v7zM4 13H2v7c0 1.1.9 2 2 2h7v-2H4v-7z\"/></svg>",
69 "widgets": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16.66 4.52l2.83 2.83-2.83 2.83-2.83-2.83 2.83-2.83M9 5v4H5V5h4m10 10v4h-4v-4h4M9 15v4H5v-4h4m7.66-13.31L11 7.34 16.66 13l5.66-5.66-5.66-5.65zM11 3H3v8h8V3zm10 10h-8v8h8v-8zm-10 0H3v8h8v-8z\"/></svg>",
70 "attach_file": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z\"/></svg>",
71 "attach_money": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z\"/></svg>",
72 "insert_chart": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM7 10h2v7H7zm4-3h2v10h-2zm4 6h2v4h-2z\"/></svg>",
73 "format_quote": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M18.62 18h-5.24l2-4H13V6h8v7.24L18.62 18zm-2-2h.76L19 12.76V8h-4v4h3.62l-2 4zm-8 2H3.38l2-4H3V6h8v7.24L8.62 18zm-2-2h.76L9 12.76V8H5v4h3.62l-2 4z\"/></svg>",
74 "text_fields": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M2.5 4v3h5v12h3V7h5V4h-13zm19 5h-9v3h3v7h3v-7h3V9z\"/></svg>",
75 "title": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M5 4v3h5.5v12h3V7H19V4H5z\"/></svg>",
76 "insert_photo": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z\"/></svg>",
77 "linear_scale": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M19.5 9.5c-1.03 0-1.9.62-2.29 1.5h-2.92c-.39-.88-1.26-1.5-2.29-1.5s-1.9.62-2.29 1.5H6.79c-.39-.88-1.26-1.5-2.29-1.5C3.12 9.5 2 10.62 2 12s1.12 2.5 2.5 2.5c1.03 0 1.9-.62 2.29-1.5h2.92c.39.88 1.26 1.5 2.29 1.5s1.9-.62 2.29-1.5h2.92c.39.88 1.26 1.5 2.29 1.5 1.38 0 2.5-1.12 2.5-2.5s-1.12-2.5-2.5-2.5z\"/></svg>",
78 "notes": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 11.01L3 11v2h18zM3 16h12v2H3zM21 6H3v2.01L21 8z\"/></svg>",
79 "pie_chart": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm7.93 9H13V4.07c3.61.45 6.48 3.32 6.93 6.93zM4 12c0-4.07 3.06-7.44 7-7.93v15.86c-3.94-.49-7-3.86-7-7.93zm9 7.93V13h6.93c-.45 3.61-3.32 6.48-6.93 6.93z\"/></svg>",
80 "scatter_plot": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M7 18c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm4-2c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm5.6 17.6c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>",
81 "space_bar": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M18 9v4H6V9H4v6h16V9h-2z\"/></svg>",
82 "cloud": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6m0-2C9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96C18.67 6.59 15.64 4 12 4z\"/></svg>",
83 "folder": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z\"/></svg>",
84 "desktop_mac": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z\"/></svg>",
85 "gamepad": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M13 4v2.67l-1 1-1-1V4h2m7 7v2h-2.67l-1-1 1-1H20M6.67 11l1 1-1 1H4v-2h2.67M12 16.33l1 1V20h-2v-2.67l1-1M15 2H9v5.5l3 3 3-3V2zm7 7h-5.5l-3 3 3 3H22V9zM7.5 9H2v6h5.5l3-3-3-3zm4.5 4.5l-3 3V22h6v-5.5l-3-3z\"/></svg>",
86 "toys": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0zm0 0h24v24H0V0z\"/><path d=\"M12 23h-1v-6.57C9.93 17.4 8.52 18 7 18c-3.25 0-6-2.75-6-6v-1h6.57C6.6 9.93 6 8.52 6 7c0-3.25 2.75-6 6-6h1v6.57C14.07 6.6 15.48 6 17 6c3.25 0 6 2.75 6 6v1h-6.57c.97 1.07 1.57 2.48 1.57 4 0 3.25-2.75 6-6 6zm1-9.87v7.74c1.7-.46 3-2.04 3-3.87s-1.3-3.41-3-3.87zM3.13 13c.46 1.7 2.04 3 3.87 3s3.41-1.3 3.87-3H3.13zm10-2h7.74c-.46-1.7-2.05-3-3.87-3s-3.41 1.3-3.87 3zM11 3.13C9.3 3.59 8 5.18 8 7s1.3 3.41 3 3.87V3.13z\"/></svg>",
87 "tv": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z\"/></svg>",
88 "videogame_asset": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 6H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H3V8h18v8zM6 15h2v-2h2v-2H8V9H6v2H4v2h2z\"/><circle cx=\"14.5\" cy=\"13.5\" r=\"1.5\"/><circle cx=\"18.5\" cy=\"10.5\" r=\"1.5\"/></svg>",
89 "watch": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M14.31 2l.41 2.48C13.87 4.17 12.96 4 12 4c-.95 0-1.87.17-2.71.47L9.7 2h4.61m.41 17.52L14.31 22H9.7l-.41-2.47c.84.3 1.76.47 2.71.47.96 0 1.87-.17 2.72-.48M16 0H8l-.95 5.73C5.19 7.19 4 9.45 4 12s1.19 4.81 3.05 6.27L8 24h8l.96-5.73C18.81 16.81 20 14.54 20 12s-1.19-4.81-3.04-6.27L16 0zm-4 18c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z\"/></svg>",
90 "add_a_photo": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M21 6h-3.17L16 4h-6v2h5.12l1.83 2H21v12H5v-9H3v9c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zM8 14c0 2.76 2.24 5 5 5s5-2.24 5-5-2.24-5-5-5-5 2.24-5 5zm5-3c1.65 0 3 1.35 3 3s-1.35 3-3 3-3-1.35-3-3 1.35-3 3-3zM5 6h3V4H5V1H3v3H0v2h3v3h2z\"/></svg>",
91 "audiotrack": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6zm-2 16c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z\"/></svg>",
92 "brightness_2": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M10 4c4.41 0 8 3.59 8 8s-3.59 8-8 8c-.34 0-.68-.02-1.01-.07C10.9 17.77 12 14.95 12 12s-1.1-5.77-3.01-7.93C9.32 4.02 9.66 4 10 4m0-2c-1.82 0-3.53.5-5 1.35C7.99 5.08 10 8.3 10 12s-2.01 6.92-5 8.65C6.47 21.5 8.18 22 10 22c5.52 0 10-4.48 10-10S15.52 2 10 2z\"/></svg>",
93 "brush": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M7 16c.55 0 1 .45 1 1 0 1.1-.9 2-2 2-.17 0-.33-.02-.5-.05.31-.55.5-1.21.5-1.95 0-.55.45-1 1-1M18.67 3c-.26 0-.51.1-.71.29L9 12.25 11.75 15l8.96-8.96c.39-.39.39-1.02 0-1.41l-1.34-1.34c-.2-.2-.45-.29-.7-.29zM7 14c-1.66 0-3 1.34-3 3 0 1.31-1.16 2-2 2 .92 1.22 2.49 2 4 2 2.21 0 4-1.79 4-4 0-1.66-1.34-3-3-3z\"/></svg>",
94 "camera": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M14.25 2.26l-.08-.04-.01.02C13.46 2.09 12.74 2 12 2 6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10c0-4.75-3.31-8.72-7.75-9.74zM19.41 9h-7.99l2.71-4.7c2.4.66 4.35 2.42 5.28 4.7zM13.1 4.08L10.27 9l-1.15 2L6.4 6.3C7.84 4.88 9.82 4 12 4c.37 0 .74.03 1.1.08zM5.7 7.09L8.54 12l1.15 2H4.26C4.1 13.36 4 12.69 4 12c0-1.85.64-3.55 1.7-4.91zM4.59 15h7.98l-2.71 4.7c-2.4-.67-4.34-2.42-5.27-4.7zm6.31 4.91L14.89 13l2.72 4.7C16.16 19.12 14.18 20 12 20c-.38 0-.74-.04-1.1-.09zm7.4-3l-4-6.91h5.43c.17.64.27 1.31.27 2 0 1.85-.64 3.55-1.7 4.91z\"/></svg>",
95 "camera_alt": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 4h-3.17L15 2H9L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V6h4.05l1.83-2h4.24l1.83 2H20v12zM12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0 8c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z\"/></svg>",
96 "color_lens": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M12 22C6.49 22 2 17.51 2 12S6.49 2 12 2s10 4.04 10 9c0 3.31-2.69 6-6 6h-1.77c-.28 0-.5.22-.5.5 0 .12.05.23.13.33.41.47.64 1.06.64 1.67 0 1.38-1.12 2.5-2.5 2.5zm0-18c-4.41 0-8 3.59-8 8s3.59 8 8 8c.28 0 .5-.22.5-.5 0-.16-.08-.28-.14-.35-.41-.46-.63-1.05-.63-1.65 0-1.38 1.12-2.5 2.5-2.5H16c2.21 0 4-1.79 4-4 0-3.86-3.59-7-8-7z\"/><circle cx=\"6.5\" cy=\"11.5\" r=\"1.5\"/><circle cx=\"9.5\" cy=\"7.5\" r=\"1.5\"/><circle cx=\"14.5\" cy=\"7.5\" r=\"1.5\"/><circle cx=\"17.5\" cy=\"11.5\" r=\"1.5\"/></svg>",
97 "panorama_horizontal": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20 6.54v10.91c-2.6-.77-5.28-1.16-8-1.16s-5.4.39-8 1.16V6.54c2.6.77 5.28 1.16 8 1.16 2.72.01 5.4-.38 8-1.16M21.43 4c-.1 0-.2.02-.31.06C18.18 5.16 15.09 5.7 12 5.7s-6.18-.55-9.12-1.64C2.77 4.02 2.66 4 2.57 4c-.34 0-.57.23-.57.63v14.75c0 .39.23.62.57.62.1 0 .2-.02.31-.06 2.94-1.1 6.03-1.64 9.12-1.64s6.18.55 9.12 1.64c.11.04.21.06.31.06.33 0 .57-.23.57-.63V4.63c0-.4-.24-.63-.57-.63z\"/></svg>",
98 "directions_bike": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M15.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM5 12c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 8.5c-1.9 0-3.5-1.6-3.5-3.5s1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5-1.6 3.5-3.5 3.5zm5.8-10l2.4-2.4.8.8c1.3 1.3 3 2.1 5.1 2.1V9c-1.5 0-2.7-.6-3.6-1.5l-1.9-1.9c-.5-.4-1-.6-1.6-.6s-1.1.2-1.4.6L7.8 8.4c-.4.4-.6.9-.6 1.4 0 .6.2 1.1.6 1.4L11 14v5h2v-6.2l-2.2-2.3zM19 12c-2.8 0-5 2.2-5 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 8.5c-1.9 0-3.5-1.6-3.5-3.5s1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5-1.6 3.5-3.5 3.5z\"/></svg>",
99 "directions_boat": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M13 3v1h-2V3h2m-1 7.11l5.38 1.77 2.39.78-1.12 3.97c-.54-.3-.94-.71-1.14-.94L16 13.96l-1.51 1.72c-.34.4-1.28 1.32-2.49 1.32s-2.15-.92-2.49-1.32L8 13.96l-1.51 1.72c-.2.23-.6.63-1.14.93l-1.13-3.96 2.4-.79L12 10.11M15 1H9v3H6c-1.1 0-2 .9-2 2v4.62l-1.29.42c-.26.08-.48.26-.6.5s-.15.52-.06.78L3.95 19H4c1.6 0 3.02-.88 4-2 .98 1.12 2.4 2 4 2s3.02-.88 4-2c.98 1.12 2.4 2 4 2h.05l1.89-6.68c.08-.26.06-.54-.06-.78s-.34-.42-.6-.5L20 10.62V6c0-1.1-.9-2-2-2h-3V1zM6 9.97V6h12v3.97L12 8 6 9.97zm10 9.71c-1.22.85-2.61 1.28-4 1.28s-2.78-.43-4-1.28C6.78 20.53 5.39 21 4 21H2v2h2c1.38 0 2.74-.35 4-.99 1.26.64 2.63.97 4 .97s2.74-.32 4-.97c1.26.65 2.62.99 4 .99h2v-2h-2c-1.39 0-2.78-.47-4-1.32z\"/></svg>",
100 "directions_bus": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2c-4.42 0-8 .5-8 4v10c0 .88.39 1.67 1 2.22V20c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h8v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1.78c.61-.55 1-1.34 1-2.22V6c0-3.5-3.58-4-8-4zm5.66 2.99H6.34C6.89 4.46 8.31 4 12 4s5.11.46 5.66.99zm.34 2V10H6V6.99h12zm-.34 9.74l-.29.27H6.63l-.29-.27C6.21 16.62 6 16.37 6 16v-4h12v4c0 .37-.21.62-.34.73z\"/><circle cx=\"8.5\" cy=\"14.5\" r=\"1.5\"/><circle cx=\"15.5\" cy=\"14.5\" r=\"1.5\"/></svg>",
101 "directions_car": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.85 7h10.29l1.08 3.11H5.77L6.85 7zM19 17H5v-5h14v5z\"/><circle cx=\"7.5\" cy=\"14.5\" r=\"1.5\"/><circle cx=\"16.5\" cy=\"14.5\" r=\"1.5\"/></svg>",
102 "directions_railway": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 1c-4.42 0-8 .5-8 4v10.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h12v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V5c0-3.5-3.58-4-8-4zm0 2c6 0 6 1.2 6 2H6c0-.8 0-2 6-2zm6 4v3H6V7h12zm-1.5 10h-9c-.83 0-1.5-.67-1.5-1.5V12h12v3.5c0 .83-.67 1.5-1.5 1.5zM12 12.5c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\"/></svg>",
103 "directions_run": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M13.49 5.48c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 .6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1l-5.2 2.2v4.7h2v-3.4l1.8-.7-1.6 8.1-4.9-1-.4 2 7 1.4z\"/></svg>",
104 "fastfood": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M1 21.98c0 .56.45 1.01 1.01 1.01H15c.56 0 1.01-.45 1.01-1.01V21H1v.98zM8.5 8.99C4.75 8.99 1 11 1 15h15c0-4-3.75-6.01-7.5-6.01zM3.62 13c1.11-1.55 3.47-2.01 4.88-2.01s3.77.46 4.88 2.01H3.62zM1 17h15v2H1zM18 5V1h-2v4h-5l.23 2h9.56l-1.4 14H18v2h1.72c.84 0 1.53-.65 1.63-1.47L23 5h-5z\"/></svg>",
105 "flight": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M21 16v-2l-8-5V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5l8 2.5z\"/></svg>",
106 "hotel": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M7 14c1.66 0 3-1.34 3-3S8.66 8 7 8s-3 1.34-3 3 1.34 3 3 3zm0-4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm12-3h-8v8H3V5H1v15h2v-3h18v3h2v-9c0-2.21-1.79-4-4-4zm2 8h-8V9h6c1.1 0 2 .9 2 2v4z\"/></svg>",
107 "local_activity": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M22 10V6c0-1.1-.9-2-2-2H4c-1.1 0-1.99.9-1.99 2v4c1.1 0 1.99.9 1.99 2s-.89 2-2 2v4c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2v-4c-1.1 0-2-.9-2-2s.9-2 2-2zm-2-1.46c-1.19.69-2 1.99-2 3.46s.81 2.77 2 3.46V18H4v-2.54c1.19-.69 2-1.99 2-3.46 0-1.48-.8-2.77-1.99-3.46L4 6h16v2.54zM9.07 16L12 14.12 14.93 16l-.89-3.36 2.69-2.2-3.47-.21L12 7l-1.27 3.22-3.47.21 2.69 2.2z\"/></svg>",
108 "local_bar": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M14.77 9L12 12.11 9.23 9h5.54M21 3H3v2l8 9v5H6v2h12v-2h-5v-5l8-9V3zM7.43 7L5.66 5h12.69l-1.78 2H7.43z\"/></svg>",
109 "local_cafe": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M16 5v8c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2V5h10m4-2H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm-2 5V5h2v3h-2zm2 11H2v2h18v-2z\"/></svg>",
110 "local_dining": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M8.1 13.34l2.83-2.83L3.91 3.5c-1.56 1.56-1.56 4.09 0 5.66l4.19 4.18zm6.78-1.81c1.53.71 3.68.21 5.27-1.38 1.91-1.91 2.28-4.65.81-6.12-1.46-1.46-4.2-1.1-6.12.81-1.59 1.59-2.09 3.74-1.38 5.27L3.7 19.87l1.41 1.41L12 14.41l6.88 6.88 1.41-1.41L13.41 13l1.47-1.47z\"/></svg>",
111 "local_florist": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M8.66 13.07c.15 0 .29-.01.43-.03C9.56 14.19 10.69 15 12 15s2.44-.81 2.91-1.96c.14.02.29.03.43.03 1.73 0 3.14-1.41 3.14-3.14 0-.71-.25-1.39-.67-1.93.43-.54.67-1.22.67-1.93 0-1.73-1.41-3.14-3.14-3.14-.15 0-.29.01-.43.03C14.44 1.81 13.31 1 12 1s-2.44.81-2.91 1.96c-.14-.02-.29-.03-.43-.03-1.73 0-3.14 1.41-3.14 3.14 0 .71.25 1.39.67 1.93-.43.54-.68 1.22-.68 1.93 0 1.73 1.41 3.14 3.15 3.14zM12 13c-.62 0-1.12-.49-1.14-1.1l.12-1.09c.32.12.66.19 1.02.19s.71-.07 1.03-.19l.11 1.09c-.02.61-.52 1.1-1.14 1.1zm3.34-1.93c-.24 0-.46-.07-.64-.2l-.81-.57c.55-.45.94-1.09 1.06-1.83l.88.42c.4.19.66.59.66 1.03 0 .64-.52 1.15-1.15 1.15zm-.65-5.94c.2-.13.42-.2.65-.2.63 0 1.14.51 1.14 1.14 0 .44-.25.83-.66 1.03l-.88.42c-.12-.74-.51-1.38-1.07-1.83l.82-.56zM12 3c.62 0 1.12.49 1.14 1.1l-.11 1.09C12.71 5.07 12.36 5 12 5s-.7.07-1.02.19l-.12-1.09c.02-.61.52-1.1 1.14-1.1zM8.66 4.93c.24 0 .46.07.64.2l.81.56c-.55.45-.94 1.09-1.06 1.83l-.88-.42c-.4-.2-.66-.59-.66-1.03 0-.63.52-1.14 1.15-1.14zM8.17 8.9l.88-.42c.12.74.51 1.38 1.07 1.83l-.81.55c-.2.13-.42.2-.65.2-.63 0-1.14-.51-1.14-1.14-.01-.43.25-.82.65-1.02zM12 22c4.97 0 9-4.03 9-9-4.97 0-9 4.03-9 9zm2.44-2.44c.71-1.9 2.22-3.42 4.12-4.12-.71 1.9-2.22 3.41-4.12 4.12zM3 13c0 4.97 4.03 9 9 9 0-4.97-4.03-9-9-9zm2.44 2.44c1.9.71 3.42 2.22 4.12 4.12-1.9-.71-3.41-2.22-4.12-4.12z\"/></svg>",
112 "map": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM10 5.47l4 1.4v11.66l-4-1.4V5.47zm-5 .99l3-1.01v11.7l-3 1.16V6.46zm14 11.08l-3 1.01V6.86l3-1.16v11.84z\"/></svg>",
113 "place": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zM7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 2.88-2.88 7.19-5 9.88C9.92 16.21 7 11.85 7 9z\"/><circle cx=\"12\" cy=\"9\" r=\"2.5\"/></svg>",
114 "group": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M9 13.75c-2.34 0-7 1.17-7 3.5V19h14v-1.75c0-2.33-4.66-3.5-7-3.5zM4.34 17c.84-.58 2.87-1.25 4.66-1.25s3.82.67 4.66 1.25H4.34zM9 12c1.93 0 3.5-1.57 3.5-3.5S10.93 5 9 5 5.5 6.57 5.5 8.5 7.07 12 9 12zm0-5c.83 0 1.5.67 1.5 1.5S9.83 10 9 10s-1.5-.67-1.5-1.5S8.17 7 9 7zm7.04 6.81c1.16.84 1.96 1.96 1.96 3.44V19h4v-1.75c0-2.02-3.5-3.17-5.96-3.44zM15 12c1.93 0 3.5-1.57 3.5-3.5S16.93 5 15 5c-.54 0-1.04.13-1.5.35.63.89 1 1.98 1 3.15s-.37 2.26-1 3.15c.46.22.96.35 1.5.35z\"/></svg>",
115 "notifications": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z\"/></svg>",
116 "public": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-.61.08-1.21.21-1.78L8.99 15v1c0 1.1.9 2 2 2v1.93C7.06 19.43 4 16.07 4 12zm13.89 5.4c-.26-.81-1-1.4-1.9-1.4h-1v-3c0-.55-.45-1-1-1h-6v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41C17.92 5.77 20 8.65 20 12c0 2.08-.81 3.98-2.11 5.4z\"/></svg>",
117 "whatshot": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M11.57 13.16c-1.36.28-2.17 1.16-2.17 2.41 0 1.34 1.11 2.42 2.49 2.42 2.05 0 3.71-1.66 3.71-3.71 0-1.07-.15-2.12-.46-3.12-.79 1.07-2.2 1.72-3.57 2zM13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM12 20c-3.31 0-6-2.69-6-6 0-1.53.3-3.04.86-4.43 1.01 1.01 2.41 1.63 3.97 1.63 2.66 0 4.75-1.83 5.28-4.43C17.34 8.97 18 11.44 18 14c0 3.31-2.69 6-6 6z\"/></svg>",
118 "check_box": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM17.99 9l-1.41-1.42-6.59 6.59-2.58-2.57-1.42 1.41 4 3.99z\"/></svg>",
119 "radio_button_checked": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0V0z\"/><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z\"/><circle cx=\"12\" cy=\"12\" r=\"5\"/></svg>",
120 "toggle_on": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M17 6H7c-3.31 0-6 2.69-6 6s2.69 6 6 6h10c3.31 0 6-2.69 6-6s-2.69-6-6-6zm0 10H7c-2.21 0-4-1.79-4-4s1.79-4 4-4h10c2.21 0 4 1.79 4 4s-1.79 4-4 4zm0-7c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/></svg>"
121 }
1 <svg fillRule="evenodd" clipRule="evenodd" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="1.5"
2 width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
3 <path
4 id="Block_Lab_Icon"
5 d="M2.255,4.02c0.032,-0.042 0.071,-0.08 0.119,-0.111l4.937,-2.85c0.08,-0.042 0.169,-0.063 0.263,-0.058c0.054,0.007 0.077,0.002 0.184,0.058l4.938,2.85l0,0l4.937,2.851l0.026,0.016l0.026,0.019l0.023,0.02l0.023,0.021l0.021,0.023l0.019,0.025l0.017,0.026l0.016,0.027l0.014,0.028l0.011,0.029l0.01,0.03l0.008,0.03l0.005,0.031l0.003,0.031l0.001,0.031l0,0l0,5.701c-0.012,0.235 -0.035,0.265 -0.223,0.387l-9.837,5.679c-0.13,0.099 -0.3,0.12 -0.485,0.022l0,0l-4.937,-2.85l0,0c-0.025,-0.016 -0.046,-0.031 -0.065,-0.044c-0.118,-0.083 -0.143,-0.132 -0.154,-0.271c-0.009,-0.048 -0.011,-0.095 -0.004,-0.141l0,-11.334c0.005,-0.104 0.043,-0.2 0.104,-0.276Zm4.833,13.754l0,-10.369l-4.043,-2.334l0,10.368l4.043,2.335Zm5.831,-8.551l3.596,-2.076l-3.596,-2.076l0,4.152Z"
6 fill="#82878c"
7 />
8 </svg>
1 <?php
2 /**
3 * Block Lab
4 *
5 * @package Block_Lab_Custom_Pro
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 *
9 * Plugin Name: Block Lab Custom Pro
10 * Plugin URI:
11 * Description: The easy way to build custom blocks for Gutenberg.
12 * Version: 200
13 * Author:
14 * Author URI:
15 * License: GPL2
16 * License URI:
17 * Text Domain: block-lab
18 * Domain Path: languages
19 */
20
21 // Exit if accessed directly.
22 if ( ! defined( 'ABSPATH' ) ) {
23 exit;
24 }
25
26 // Setup the plugin auto loader.
27 require_once 'php/autoloader.php';
28
29 /**
30 * Admin notice for incompatible versions of PHP.
31 */
32 function block_lab_php_version_error() {
33 printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_php_version_text() ) );
34 }
35
36 /**
37 * String describing the minimum PHP version.
38 *
39 * "Namespace" is a PHP 5.3 introduced feature. This is a hard requirement
40 * for the plugin structure.
41 *
42 * "Traits" is a PHP 5.4 introduced feature. Remove "Traits" support from
43 * php/autoloader if you want to support a lower PHP version.
44 * Remember to update the checked version below if you do.
45 *
46 * @return string
47 */
48 function block_lab_php_version_text() {
49 return __( 'Block Lab plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.4 or higher.', 'block-lab' );
50 }
51
52 // If the PHP version is too low, show warning and return.
53 if ( version_compare( phpversion(), '5.4', '<' ) ) {
54 if ( defined( 'WP_CLI' ) ) {
55 WP_CLI::warning( block_lab_php_version_text() );
56 } else {
57 add_action( 'admin_notices', 'block_lab_php_version_error' );
58 }
59
60 return;
61 }
62
63 /**
64 * Admin notice for incompatible versions of WordPress or missing Gutenberg Plugin.
65 */
66 function block_lab_wp_version_error() {
67 printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_wp_version_text() ) );
68 }
69
70 /**
71 * String describing the minimum WP version or Gutenberg Plugin requirement.
72 *
73 * "Blocks" are a feature of WordPress 5.0+ or require the Gutenberg plugin.
74 *
75 * @return string
76 */
77 function block_lab_wp_version_text() {
78 return __( 'Block Lab plugin error: Your version of WordPress is too old. You must be running WordPress 5.0 to use Block Lab.', 'block-lab' );
79 }
80
81 // If the WordPress version is too low, show warning and return.
82 if ( ! function_exists( 'register_block_type' ) ) {
83 if ( defined( 'WP_CLI' ) ) {
84 WP_CLI::warning( block_lab_wp_version_text() );
85 } else {
86 add_action( 'admin_notices', 'block_lab_wp_version_error' );
87 }
88 }
89
90 /**
91 * Get the plugin object.
92 *
93 * @return \Block_Lab\Plugin
94 */
95 function block_lab() {
96 static $instance;
97
98 if ( null === $instance ) {
99 $instance = new \Block_Lab\Plugin();
100 }
101
102 return $instance;
103 }
104
105 /**
106 * Setup the plugin instance.
107 */
108 block_lab()
109 ->set_basename( plugin_basename( __FILE__ ) )
110 ->set_directory( plugin_dir_path( __FILE__ ) )
111 ->set_file( __FILE__ )
112 ->set_slug( 'block-lab' )
113 ->set_url( plugin_dir_url( __FILE__ ) )
114 ->set_version( __FILE__ )
115 ->init();
116
117 // Sometimes we need to do some things after the plugin is loaded, so call the Plugin_Interface::plugin_loaded().
118 add_action( 'plugins_loaded', [ block_lab(), 'plugin_loaded' ] );
119
120 // Require helpers at 11, so if GCB is active, its helpers will be required first and prevent a PHP error.
121 add_action( 'plugins_loaded', [ block_lab(), 'require_helpers' ], 11 );
122
123
1 .post-type-block_lab .tablenav.top,
2 .post-type-block_lab .search-box,
3 .post-type-block_lab .inline-edit-date,
4 .post-type-block_lab .inline-edit-group {
5 display: none;
6 }
7 .post-type-block_lab .column-template code {
8 background: none;
9 font-size: 12px;
10 padding-left: 0;
11 }
12 .post-type-block_lab .fixed .column-icon {
13 width: 10%;
14 }
15 .post-type-block_lab .fixed td.column-icon {
16 color: #72777c;
17 }
18 .post-type-block_lab .fixed td.column-icon .icon {
19 background: #ffffff;
20 border: 1px solid #aaa;
21 border-radius: 4px;
22 padding: 4px;
23 width: 24px;
24 height: 24px;
25 display: inline-block;
26 }
27 .post-type-block_lab .fixed .column-fields {
28 width: 10%;
29 }
1 #minor-publishing-actions,
2 #misc-publishing-actions #visibility,
3 #misc-publishing-actions .curtime {
4 display: none;
5 }
6 #misc-publishing-actions .block-lab-pub-section {
7 padding: 6px 10px 14px;
8 }
9 #misc-publishing-actions .block-lab-pub-section:before {
10 content: "\f537";
11 color: #82878c;
12 font: normal 20px/1 dashicons;
13 speak: none;
14 display: inline-block;
15 margin-left: -1px;
16 padding-right: 3px;
17 vertical-align: top;
18 -webkit-font-smoothing: antialiased;
19 -moz-osx-font-smoothing: grayscale;
20 }
21 #misc-publishing-actions .block-lab-pub-section .post-types-display {
22 font-weight: bold;
23 }
24 #misc-publishing-actions .block-lab-pub-section .post-types-select {
25 display: none;
26 }
27 #misc-publishing-actions .block-lab-pub-section .post-types-select .post-types-select-items {
28 margin: 3px 0;
29 border: 1px solid #eeeeee;
30 padding: 6px 10px;
31 margin-bottom: 6px;
32 }
33 .handle-order-higher,
34 .handle-order-lower {
35 display: none;
36 }
37 #block_fields a,
38 #block_fields a:focus {
39 outline: none;
40 box-shadow: none;
41 }
42 #block_fields .handlediv,
43 #block_fields .hndle {
44 display: none;
45 }
46 #block_fields .inside {
47 padding: 0;
48 margin: 0;
49 }
50 #block_fields .block-fields-list .widefat {
51 border: none;
52 }
53 #block_fields .block-fields-list td {
54 padding: 0;
55 line-height: 0;
56 }
57 #block_fields .block-fields-list thead th {
58 font-weight: 600;
59 width: 32%;
60 border-bottom: 1px solid #eee;
61 }
62 #block_fields .block-fields-list thead th.block-fields-sort {
63 width: 4%;
64 min-width: 32px;
65 }
66 #block_fields .block-fields-list tbody {
67 background: #f5f5f5;
68 }
69 #block_fields button {
70 border: 0;
71 background: transparent;
72 padding: 0;
73 margin: 0;
74 cursor: pointer;
75 color: #0073aa;
76 font-weight: bold;
77 transition: none;
78 line-height: 20px;
79 }
80 #block_fields button:hover {
81 color: #00a0d2;
82 }
83 #block_fields button:active,
84 #block_fields button:focus {
85 outline: 0;
86 }
87 #block_fields button > .dashicons {
88 color: #0073aa;
89 font-size: 12px;
90 border: 2px solid #0073aa;
91 border-radius: 10px;
92 padding: 0;
93 margin: 1px 4px 0 0;
94 width: 14px;
95 height: 14px;
96 text-align: center;
97 line-height: 16px;
98 transition: none;
99 }
100 #block_fields button:hover > .dashicons {
101 color: #00a0d2;
102 border-color: #00a0d2;
103 }
104 #block_fields .block-fields-rows {
105 width: 100%;
106 line-height: 1.5em;
107 }
108 #block_fields .block-fields-rows .block-no-fields {
109 display: none;
110 padding: 20px 10px 10px;
111 margin: 0;
112 text-align: center;
113 }
114 #block_fields .block-fields-rows .block-fields-row {
115 background: #fff;
116 border: 1px solid #eee;
117 box-shadow: 0 1px 1px rgba(0,0,0,.04);
118 margin: 10px 10px 0;
119 }
120 #block_fields .block-fields-rows .block-fields-row-columns {
121 display: grid;
122 grid-template-columns: [handle] minmax( 20px, calc(4% - 10px) ) calc(32% + 14px) calc(32% + 14px) calc(32% - 18px);
123 }
124 #block_fields .block-fields-rows .block-fields-row > div {
125 padding: 8px 10px;
126 }
127 #block_fields .block-fields-sort-handle:before {
128 content: "\f545";
129 }
130 #block_fields .block-fields-sort-handle {
131 display: none;
132 width: 15px;
133 height: 15px;
134 font-family: 'Dashicons', monospace;
135 font-size: 15px;
136 line-height: 15px;
137 cursor: grab;
138 padding-top: 2px;
139 }
140 #block_fields .block-fields-rows .row-title {
141 display: block;
142 padding-bottom: 6px;
143 }
144 #block_fields .block-fields-actions-add-field {
145 padding: 20px 10px;
146 clear: both;
147 background: #f5f5f5;
148 text-align: center;
149 }
150 #block_fields .block-fields-actions {
151 font-size: 12px;
152 line-height: 12px;
153 visibility: hidden;
154 }
155 #block_fields .block-fields-row-columns:hover > .block-fields-label .block-fields-actions {
156 visibility: visible;
157 }
158 #block_fields .block-fields-row-columns:hover > .block-fields-sort .block-fields-sort-handle {
159 display: block;
160 }
161 #block_fields .block-fields-rows .block-fields-row .block-fields-edit {
162 display: none;
163 grid-column: 1 / span 4;
164 padding: 0;
165 border-bottom: 1px solid #e1e1e1;
166 border-top: 1px solid #e1e1e1;
167 }
168 #block_fields .block-fields-rows .block-fields-row .block-fields-edit tbody {
169 background: #f7f7f7;
170 }
171 #block_fields .block-fields-rows .block-fields-edit td,
172 #block_fields .block-fields-rows .block-fields-edit th {
173 padding: 8px 10px;
174 }
175 #block_fields .block-fields-rows .block-fields-edit td.spacer {
176 width: 4%;
177 }
178 #block_fields .block-fields-rows .block-fields-edit th {
179 font-size: 12px;
180 width: 32%;
181 border-right: 1px solid #e1e1e1;
182 }
183 #block_fields .block-fields-rows .block-fields-edit th label {
184 font-weight: 600;
185 }
186 #block_fields .block-fields-rows .block-fields-control {
187 display: flex;
188 }
189 #block_fields .block-fields-rows .block-fields-control span.pro-required {
190 margin-left: 0.5rem;
191 }
192 #block_fields .block-fields-rows input[readonly="readonly"],
193 #block_fields .block-fields-rows textarea[readonly="readonly"] {
194 color: #999;
195 }
196 #block_fields .block-fields-rows .button-group > .button {
197 width: 5em;
198 position: relative;
199 z-index: 10;
200 }
201 #block_fields .block-fields-rows .button-group > .button:last-of-type {
202 border-radius: 0 3px 3px 0;
203 }
204 #block_fields .block-fields-rows .button-group > .button:hover {
205 z-index: 30;
206 }
207 #block_fields .block-fields-rows .button-group > .button:checked {
208 background: #eee;
209 border-color: #999;
210 box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5), 0 1px 0 #cccccc;
211 z-index: 20;
212 }
213 #block_fields .block-fields-rows .button-group > .button:checked:before {
214 width: 0;
215 height: 0;
216 background-color: transparent;
217 }
218 #block_fields .block-fields-rows .button-group > label {
219 font-size: 13px;
220 position: absolute;
221 margin-left: -5em;
222 width: 5em;
223 top: 13px;
224 text-align: center;
225 pointer-events: none;
226 z-index: 40;
227 }
228 #block_fields .block-fields-rows .block-fields-edit-loading .loading:before {
229 content: '';
230 box-sizing: border-box;
231 display: block;
232 width: 20px;
233 height: 20px;
234 border-radius: 50%;
235 border: 2px solid #e1e1e1;
236 border-top-color: #444;
237 animation: spinner .6s linear infinite;
238 }
239 @media only screen and (max-width: 850px) {
240 #block_fields .block-fields-rows {
241 max-height: none;
242 overflow-x: auto;
243 overflow-y: auto;
244 line-height: 1.5em;
245 }
246 }
247 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows {
248 grid-column: 1 / span 4;
249 margin: 0 10px;
250 padding: 0 0 0 32px;
251 overflow: hidden;
252 }
253 #block_fields .block-fields-sub-rows .block-fields-row-columns {
254 grid-template-columns: [handle] minmax( 20px, 3% ) calc(33% - 22px) calc(32% + 38px) calc(32% - 16px);
255 }
256 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows .block-fields-row {
257 border: none;
258 background: #f7f7f7;
259 }
260 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions {
261 grid-column: 1 / span 4;
262 padding: 0;
263 margin: 0 10px 0 42px;
264 text-align: center;
265 background: #fff;
266 }
267 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions p {
268 padding: 10px 0;
269 margin: 10px 0;
270 }
271 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions p.repeater-no-fields {
272 background: #f7f7f7;
273 }
274 #block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions button {
275 margin-left: -32px;
276 }
277
278 #block_properties input,
279 #block_properties select,
280 #block_properties label {
281 width: 100%;
282 display: block;
283 }
284 #block_properties #block-properties-icon-current {
285 background: #ffffff;
286 border: 1px solid #cccccc;
287 border-right: 0;
288 border-radius: 3px 0 0 3px;
289 padding: 3px 6px;
290 margin-right: 0;
291 width: 24px;
292 height: 24px;
293 display: inline-block;
294 box-shadow: 0 1px 0 #cccccc;
295 }
296 #block_properties #block-properties-icon-current svg {
297 color: #23282d;
298 fill: #23282d;
299 }
300 #block_properties .block-properties-icon-button {
301 width: auto;
302 display: inline-block;
303 vertical-align: top;
304 margin-left: -4px;
305 border-radius: 0 3px 3px 0;
306 height: 32px;
307 line-height: 28px;
308 padding: 0 10px 1px;
309 }
310 #block_properties .block-properties-icon-button:active {
311 transform: translateY(0);
312 box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ), 0 1px 0 #cccccc;
313 }
314 #block_properties #block-properties-icon-select,
315 #block_properties #block-properties-icon-close,
316 #block_properties #block-properties-icon-choose:target {
317 display: none;
318 }
319 #block-properties-icon-choose:target + #block-properties-icon-close {
320 display: inline-block;
321 }
322 #block-properties-icon-choose:target ~ #block-properties-icon-select {
323 display: block;
324 }
325 #block_properties .block-properties-icon-select {
326 background: #f9f9f9;
327 overflow-x: hidden;
328 overflow-y: scroll;
329 padding: 4px;
330 margin: 5px 0 0;
331 height: 110px;
332 border: 1px solid #cccccc;
333 border-radius: 3px;
334 }
335 #block_properties .block-properties-icon-select .icon {
336 display: inline-block;
337 padding: 4px;
338 margin: 1px;
339 border: 1px solid transparent;
340 border-radius: 4px;
341 cursor: pointer;
342 color: #72777c;
343 font-size: 25px;
344 width: 1em;
345 height: 1em;
346 }
347 #block_properties .block-properties-icon-select .icon:hover {
348 background: #fff;
349 border: 1px solid #aaa;
350 color: #444;
351 }
352 #block_properties .block-properties-icon-select .icon.selected,
353 #block_properties .block-properties-icon-select .icon.selected:hover {
354 border: 1px solid rgba(0, 160, 210, 1);
355 color: rgba(0, 160, 210, 1);
356 fill: rgba(0, 160, 210, 1);
357 }
358 #block_properties .block-properties-icon-select .icon svg {
359 vertical-align: top;
360 }
361 #block_properties .block-properties-category-custom {
362 display: none;
363 margin: 12px 0;
364 }
365 #block_template > h2 {
366 display: none;
367 }
368 #block_template .inside {
369 padding: 0;
370 margin: 0;
371 }
372 #block_template .template-notice {
373 background: #fff;
374 border-left: 4px solid #ffffff;
375 padding: 1px 12px;
376 border-left-color: #7D5DEC;
377 }
378 #block_template .template-success {
379 border-left-color: #46b450;
380 }
381 #block_template .template-location {
382 margin: 0 0 6px;
383 padding: 5px 8px;
384 background: rgba(0, 0, 0, 0.07);
385 display: inline-block;
386 font-size: 0;
387 }
388 #block_template .template-location * {
389 font-size: 12px;
390 line-height: 23px;
391 }
392 #block_template .template-location a.filename {
393 color: #444;
394 text-decoration: none;
395 border-bottom: 1px dotted #444;
396 }
397 #block_template .template-location a.filename:focus {
398 outline: none;
399 box-shadow: none;
400 }
401 #block_template .template-location .click-to-copy {
402 display: none;
403 }
404 #block_template .template-location .click-to-copy input {
405 color: #444;
406 margin: 0;
407 font-size: 12px;
408 line-height: 15px;
409 }
410
411 /**
412 * Tooltip Styles
413 */
414
415 /* Add this attribute to the element that needs a tooltip */
416 [data-tooltip] {
417 position: relative;
418 z-index: 2;
419 cursor: pointer;
420 }
421 /* Hide the tooltip content by default */
422 [data-tooltip]:before,
423 [data-tooltip]:after {
424 visibility: hidden;
425 opacity: 0;
426 pointer-events: none;
427 }
428 /* Position tooltip above the element */
429 [data-tooltip]:before {
430 position: absolute;
431 bottom: 150%;
432 left: 50%;
433 margin-bottom: 5px;
434 margin-left: -60px;
435 padding: 7px;
436 width: 120px;
437 border-radius: 3px;
438 background-color: #000;
439 background-color: rgba(40, 40, 40, 0.95);
440 color: #fff;
441 content: attr(data-tooltip);
442 text-align: center;
443 font-size: 12px;
444 line-height: 1.2;
445 }
446 /* Triangle hack to make tooltip look like a speech bubble */
447 [data-tooltip]:after {
448 position: absolute;
449 bottom: 150%;
450 left: 50%;
451 margin-left: 3px;
452 width: 0;
453 border-top: 5px solid #000;
454 border-top: 5px solid rgba(40, 40, 40, 0.95);
455 border-right: 5px solid transparent;
456 border-left: 5px solid transparent;
457 content: " ";
458 font-size: 0;
459 line-height: 0;
460 }
461 /* Show tooltip content on hover */
462 [data-tooltip]:hover:before,
463 [data-tooltip]:hover:after {
464 visibility: visible;
465 opacity: 1;
466 }
467
468 @keyframes spinner {
469 to {
470 transform: rotate( 360deg );
471 }
472 }
1 .bl-notice-conflict {
2 display: flex;
3 padding-top: 0.2rem;
4 padding-bottom: 0.2rem;
5 }
6
7 .bl-notice-conflict .bl-conflict-copy {
8 margin-right: auto;
9 }
10
11 .bl-notice-conflict .bl-link-deactivate {
12 margin: auto 0.2rem;
13 }
1 #adminmenu ul > li > a[href="edit.php?post_type=block_lab&page=block-lab-pro"] {
2 color: #00b9eb;
3 }
...\ No newline at end of file ...\ No newline at end of file
1 .bl-notice-migration {
2 display: flex;
3 }
4
5 .bl-notice-migration .bl-migration-copy {
6 margin-right: auto;
7 }
8
9 .bl-notice-migration__learn-more {
10 display: block;
11 }
12
13 .bl-notice-migration .bl-notice-option {
14 margin: auto 0.2rem;
15 }
16
17 .bl-notice-migration.bl-hidden {
18 display: none;
19 }
20
21 #bl-notice-not-now {
22 margin-left: 16px;
23 }
1 :root {
2 --color-brand: #5C34E8;
3 --color-pink: #FF227E;
4 --color-orange: #FF7C53;
5 --color-heading: #000;
6 --color-body: rgb(51, 51, 51);
7 --color-white: #fff;
8 --color-black: #000;
9 --color-gray: #edf2f7;
10 --color-green: #48bb78;
11
12 --color-purple: #422E62;
13 --color-purple-100: #FAF5FF;
14 --color-purple-200: #E9D8FD;
15 --color-purple-300: #D6BCFA;
16 --color-purple-400: #B794F4;
17 --color-purple-500: #9F7AEA;
18 --color-purple-600: #805AD5;
19 --color-purple-700: #6B46C1;
20 --color-purple-800: #553C9A;
21 --color-purple-900: #44337A;
22
23 --color-gray-100: #F7FAFC;
24 --color-gray-200: #EDF2F7;
25 --color-gray-300: #E2E8F0;
26 --color-gray-400: #CBD5E0;
27 --color-gray-500: #A0AEC0;
28 --color-gray-600: #718096;
29 --color-gray-700: #4A5568;
30 --color-gray-800: #2D3748;
31 --color-gray-900: #1A202C;
32
33 --box-shadow: 10px 10px 20px 0px rgba(121,110,255,0.1);
34 --box-shadow-small: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06);
35 }
36
37 #wpcontent {
38 padding-left: 0;
39 }
40
41 .bl-migration__content {
42 font-family: system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
43 line-height: 1.5;
44 }
45
46 .bl-migration__content {
47 color: var(--color-body);
48 font-size: 16px;
49 }
50
51 .bl-migration__content .get-genesis-pro {
52 border-top: solid 1px var(--color-gray-300);
53 padding-top: 2rem;
54 font-size: 18px;
55 }
56
57 .bl-migration__content p {
58 margin-bottom: 1em;
59 font-size: 16px;
60 }
61
62 .bl-migration__content h1,
63 .bl-migration__content h2,
64 .bl-migration__content h3,
65 .bl-migration__content h4,
66 .bl-migration__content h5,
67 .bl-migration__content h6 {
68 font-weight: 500;
69 margin-bottom: 1em;
70 }
71
72 .bl-migration__content h1 {
73 font-size: 1.65rem;
74 }
75
76 .bl-migration__content a {
77 color: var(--color-brand);
78 text-decoration: underline;
79 }
80
81 .bl-migration__content {
82 display: flex;
83 flex-grow: 1;
84 flex-direction: column;
85 background-color: var(--color-gray);
86 }
87
88 .bl-migration__content ul {
89 list-style-type: disc;
90 padding-left: 2rem;
91 margin-bottom: 2rem;
92 }
93
94 .bl-migration__content-wrapper {
95 padding: 1rem;
96 overflow: auto;
97 flex-grow: 1;
98 }
99
100 .bl-migration__content-container {
101 box-shadow: var(--box-shadow);
102 padding: 2.5rem;
103 background-color: var(--color-white);
104
105 }
106
107 .bl-migration__content .container {
108 max-width: 1200px;
109 margin-left: auto;
110 margin-right: auto;
111 }
112
113 .bl-migration__content .btn {
114 appearance: none;
115 border: none;
116 display: flex;
117 color: var(--color-purple-100);
118 cursor: pointer;
119 box-shadow: var(--box-shadow-small);
120 padding-left: 1.25rem;
121 padding-right: 1.25rem;
122 font-size: .875rem;
123 height: 2.25rem;
124 line-height: 1;
125 font-weight: 500;
126 align-items: center;
127 border-radius: .25rem;
128 background-color: var(--color-purple-700);
129 text-decoration: none;
130 }
131
132 .bl-migration__content .btn:hover {
133 cursor: pointer;
134 background-color: var(--color-purple-800);
135 }
136
137 .bl-migration__content .btn[disabled],
138 .genesis-pro-form button[disabled] {
139 color: var(--color-gray-500);
140 background-color: var(--color-gray-200);
141 box-shadow: none;
142 cursor: not-allowed;
143 }
144
145 .bl-migration__content .get-genesis-pro .btn {
146 display: inline-flex;
147 margin-right: 1rem;
148 }
149
150 .bl-migration__content .btn-secondary {
151 color: var(--color-purple-600);
152 border-color: var(--color-purple-200);
153 background-color: var(--color-purple-100);
154 box-shadow: none;
155 border-width: 1px;
156 border-style: solid;
157 }
158
159 .bl-migration__content .btn-secondary:hover {
160 color: var(--color-purple-700);
161 background-color: var(--color-purple-200);
162 }
163
164 .help-text {
165 opacity: 0.7;
166 font-style: italic;
167 margin-top: .5rem;
168 font-size: .875rem;
169 }
170
171 .dev-notice {
172 display: flex;
173 padding-left: .5rem;
174 padding-right: .5rem;
175 height: 3rem;
176 justify-content: flex-start;
177 align-items: center;
178 border-width: 1px;
179 border-radius: .25rem;
180 border-color: var(--color-brand);
181 background-color: var(--color-purple-100);
182 }
183
184 .dev-notice svg {
185 color: var(--color-purple-600);
186 fill: currentColor;
187 height: 1.25rem;
188 width: 1.25rem;
189 margin-left: 0.25rem;
190 }
191 .dev-notice>span {
192 color: var(--color-purple-700);
193 font-size: .875rem;
194 margin-left: .5rem;
195 line-height: 1;
196 font-weight: 500;
197 }
198
199 .dev-notice .btn {
200 color: var(--color-purple-700);
201 background-color: var(--color-purple-200);
202 height: 2rem;
203 padding-right: 0.75rem;
204 padding-left: 0.75rem;
205 margin-left: auto;
206 }
207
208 .dev-notice .btn:hover,
209 .genesis-pro-form button:hover {
210 color: var(--color-purple-800);
211 background-color: var(--color-purple-300);
212 }
213
214 .step {
215 background-color: var(--color-gray-100);
216 display: flex;
217 justify-content: flex-start;
218 padding-top: 1.1rem;
219 padding-bottom: 1rem;
220 padding-left: 1rem;
221 padding-right: 1rem;
222 border-top-width: 1px;
223 border-top-style: solid;
224 border-top-color: var(--color-gray-400);
225 max-height: 2.4rem;
226 overflow: hidden;
227 transition-duration: 200ms;
228 }
229
230 .step:last-child {
231 border-bottom: 1px solid var(--color-gray-400);
232 }
233
234 .step-icon {
235 display: flex;
236 flex-shrink: 0;
237 align-items: center;
238 justify-content: center;
239 height: 2.25rem;
240 width: 2.25rem;
241 border-radius: 99999px;
242 border-color: var(--color-gray-300);
243 border-width: 2px;
244 border-style: solid;
245 color: var(--color-gray-500)
246 }
247
248 .step-icon svg {
249 width: 1.5rem;
250 height: 1.5rem;
251 fill: currentColor;
252 }
253
254 .step-icon span {
255 font-size: 1.25rem;
256 font-weight: 600;
257 }
258
259 .step h3 {
260 color: var(--color-gray-500);
261 margin-top: 6px;
262 }
263
264 .step--active {
265 overflow: hidden;
266 transition: max-height 1s ease-in-out;
267 max-height: 200rem;
268 background-color: var(--color-white);
269 padding-top: 2rem;
270 padding-bottom: 2rem;
271 }
272
273 .step--active h3 {
274 color: var(--color-body);
275 }
276
277 .step--active .step-icon {
278 border-color: var(--color-green);
279 color: var(--color-green);
280 }
281
282 .step--complete .step-icon {
283 background-color: var(--color-green);
284 border-color: var(--color-green);
285 color: var(--color-white);
286 }
287
288 .step--complete h3 {
289 color: var(--color-body);
290 }
291
292 .step-content {
293 flex-grow: 1;
294 margin-left: 2rem;
295 }
296
297 .step-footer {
298 display: flex;
299 align-items: center;
300 justify-content: space-between;
301 margin-top: 2rem;
302 border-top: solid 1px var(--color-gray-300);
303 padding-top: 1rem;
304 }
305
306 .step-footer form {
307 display: flex;
308 align-items: center;
309 margin-left: auto;
310 margin-bottom: 0;
311 margin-right: 1rem;
312 }
313
314 .step-footer button:only-child {
315 margin-left: auto;
316 }
317
318 .step-footer input[type="checkbox"] {
319 margin: 0;
320 }
321
322 label {
323 font-size: .875rem;
324 margin-left: .25rem;
325 font-weight: 500;
326 color: var(--color-gray-700);
327 }
328
329 .genesis-pro-form {
330 display: flex;
331 align-items: center;
332 }
333
334 .genesis-pro-form form {
335 display: flex;
336 margin-bottom: 0;
337 }
338
339 .genesis-pro-form input {
340 appearance: none;
341 width: 20rem;
342 box-shadow: var(--box-shadow-small);
343 padding-left: 1rem;
344 padding-right: 1rem;
345 font-size: .875rem;
346 height: 2.5rem;
347 border-style: solid;
348 border-width: 1px;
349 border-radius: 0.25rem 0 0 0.25rem;
350 border-color: var(--color-gray-400)
351 }
352
353 .genesis-pro-form button {
354 color: var(--color-purple-600);
355 box-shadow: var(--box-shadow-small);
356 padding-left: 1rem;
357 padding-right: 1rem;
358 font-size: .875rem;
359 height: 2.5rem;
360 margin-left: -1px;
361 font-weight: 500;
362 border-style: solid;
363 border-width: 1px;
364 border-bottom-right-radius: .25rem;
365 border-top-right-radius: .25rem;
366 border-color: var(--color-purple-300);
367 background-color: var(--color-purple-200);
368 cursor: pointer;
369 }
370
371 .genesis-pro-form p {
372 margin-left: 1rem;
373 margin-right: 1rem;
374 margin-bottom: 0;
375 margin-top: 0;
376 font-weight: 500;
377 }
378
379 .pro-submission-message {
380 font-style: italic;
381 color: var(--color-purple-600);
382 margin-top: 0.5rem;
383 }
384
385 .pro-box {
386 background-color: var(--color-gray-800);
387 color: var(--color-gray-200);
388 padding: 2.5rem 2.5rem 1rem 2.5rem;
389 margin-top: 2.5rem;
390 border-radius: .25rem;
391 box-shadow: var(--box-shadow);
392 margin-bottom: 2rem;;
393 }
394
395 .pro-box h3 {
396 color: var(--color-gray-200);
397 }
398
399 .pro-box-tiles {
400 display: flex;
401 margin-top: 1rem;
402 margin-left: -.5rem;
403 margin-right: -.5rem;
404 }
405
406 .pro-box-tile {
407 background-color: var(--color-gray-900);
408 padding: 2rem;
409 border-radius: .25rem;
410 margin-left: .5rem;
411 margin-right: .5rem;
412 width: 0;
413 flex-grow: 1;
414 }
415
416 .pro-box-tile__icon {
417 display: flex;
418 align-items: center;
419 justify-content: center;
420 border-radius: .25rem;
421 background-color: var(--color-purple-700);
422 height: 3rem;
423 width: 3rem;
424 margin-bottom: .75rem;
425 }
426
427 .pro-box-tile__icon svg {
428 width: 2rem;
429 height: 2rem;
430 color: var(--color-purple-300);
431 fill: currentColor;
432 }
433
434 .bl-migration__error {
435 background-color: var(--color-purple-100);
436 padding: 0.1rem 1rem;
437 margin-bottom: 1rem;
438 }
439
440 .bl-migration__error p:first-child {
441 color: var(--color-purple-700);
442 font-weight: 500;
443 }
444
445 .message-future {
446 font-size: 12px;
447 }
448
449 /*
450 * Copied from Gutenberg.
451 * https://github.com/WordPress/gutenberg/blob/eb856ef0892d6b9d15f39483ac2f45673d68f2d4/packages/components/src/spinner/style.scss
452 */
453 .components-spinner {
454 display: inline-block;
455 background-color: #7e8993;
456 width: 18px;
457 height: 18px;
458 opacity: 0.7;
459 margin: 5px 11px 0;
460 border-radius: 100%;
461 position: relative;
462 }
463
464 .components-spinner::before {
465 content: "";
466 position: absolute;
467 background-color: #fff;
468 top: 3px;
469 left: 3px;
470 width: 4px;
471 height: 4px;
472 border-radius: 100%;
473 transform-origin: 6px 6px;
474 animation: components-spinner__animation 1s infinite linear;
475 }
476
477 @keyframes components-spinner__animation {
478 from {
479 transform: rotate(0deg);
480 }
481 to {
482 transform: rotate(360deg);
483 }
484 }
485
486 @media (max-width: 1000px) {
487 .step .pro-box-tiles {
488 flex-direction: column;
489 }
490
491 .step .pro-box-tile {
492 width: auto;
493 }
494 }
1
2 .block-lab-notice {
3 border-left: 4px solid #7D5DEC;
4 padding: 10px 20px;
5 background: #fff;
6 box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
7 margin: 5px 0 15px;
8 }
9 .block-lab-notice h2 {
10 margin: 0.5em 0 1em;
11 }
12 .block-lab-notice p {
13 font-size: 1em;
14 }
15 .block-lab-notice .intro {
16 font-size: 1.2em;
17 line-height: 1.5em;
18 }
19 .block-lab-notice p.ps {
20 font-size: smaller;
21 }
22 .block-lab-notice .button {
23 display: inline-block;
24 position: relative;
25 background-color: #7D5DEC;
26 padding: 10px 12px;
27 margin: 1em 0;
28 height: auto;
29 border-radius: 4px;
30 border: 2px solid #7D5DEC;
31 color: #fff;
32 font-size: 13px;
33 text-decoration: none;
34 line-height: 13px;
35 cursor: pointer;
36 box-shadow: none;
37 }
38 .block-lab-notice .button:active,
39 .block-lab-notice .button:focus,
40 .block-lab-notice .button:visited {
41 color: #fff;
42 }
43 .block-lab-notice .button:hover {
44 color: #fff;
45 background-color: #6444d3;
46 border-color: #6444d3;
47 }
48 .block-lab-notice .button:nth-of-type( 2 ) {
49 margin-left: 10px;
50 }
51 .block-lab-notice .button--white {
52 background-color: #fff;
53 border-color: #fff;
54 color: #7D5DEC;
55 }
56 .block-lab-notice .button--white:active,
57 .block-lab-notice .button--white:focus,
58 .block-lab-notice .button--white:visited {
59 color: #7D5DEC;
60 }
61 .block-lab-notice .button--white:hover {
62 color: #7D5DEC;
63 background-color: #f4f4f4;
64 border-color: #f4f4f4;
65 }
66 .block-lab-notice .button_cta {
67 font-weight: 600;
68 padding: 16px 15px 15px;
69 box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
70 }
71 .block-lab-welcome {
72 background-color: #7D5DEC;
73 background-image: url('https://getblocklab.com/wp-content/uploads/2019/02/Block-Lab-Pro-Hero-Background-1.svg');
74 background-size: cover;
75 background-position: center;
76 color: #fff;
77 padding: 32px 32px 6px;
78 border: none;
79 box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
80 border-radius: 3px;
81 }
82 .block-lab-welcome h2 {
83 color: #fff;
84 line-height: 1em;
85 font-size: 2em;
86 font-weight: 700;
87 margin-bottom: 0.5em;
88 margin-top: 0;
89 font-style: italic;
90 }
91 .block-lab-welcome p {
92 font-size: 1.1em;
93 }
94 .block-lab-welcome .intro {
95 font-size: 1.3em;
96 }
97 .block-lab-notice p.ps {
98 opacity: 0.5;
99 }
100 .block-lab-welcome .notice-dismiss:before {
101 color: #372182;
102 }
103 .block-lab-welcome .notice-dismiss:hover:before {
104 color: #f4f4f4;
105 }
106 .block-lab-edit-block a.trash {
107 color: #444;
108 }
109 .block-lab-edit-block a.trash:hover {
110 color: #dc3232;
111 }
112 .block-lab-add-fields {
113 position: relative;
114 }
115 .block-lab-add-fields h2 {
116 font-size: 1.3em !important;
117 padding: 0 !important;
118 margin: 0.5em 0 1em !important;
119 font-weight: 600 !important;
120 }
121 .block-lab-publish {
122 margin: 1em 0 0;
123 }
124 .block-lab-publish h2 {
125 font-size: 1.3em !important;
126 padding: 0 !important;
127 margin: 0.5em 0 1em !important;
128 }
129
130 @keyframes block-lab-edit-block-point {
131 from {
132 transform: rotate(230deg) translateX(0px) translateY(0px);
133 }
134 to {
135 transform: rotate(220deg) translateX(10px) translateY(-40px);
136 }
137 }
138 @keyframes block-lab-add-fields-point {
139 from {
140 transform: rotate(30deg) translateX(0px) translateY(0px);
141 }
142 to {
143 transform: rotate(40deg) translateX(40px) translateY(20px);
144 }
145 }
1 .block-lab-settings .nav-tab-wrapper .dashicons-before:before {
2 padding: 2px 3px 0 0;
3 }
...\ No newline at end of file ...\ No newline at end of file
1 .block-lab-pro {
2 padding: 20px 20px 0 0;
3 margin: 0 auto;
4 max-width: 1280px;
5 }
6 .block-lab-pro .container {
7 margin-top: 20px;
8 display:grid;
9 grid-template-columns: repeat( 6, 1fr );
10 grid-gap: 20px;
11 }
12 .block-lab-pro .tile {
13 box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
14 border-radius: 3px;
15 background-color: #fff;
16 }
17 .block-lab-pro .tile p {
18 font-size: 16px;
19 }
20 .block-lab-pro .tile_header {
21 padding: 40px 40px 0;
22 font-weight: 600;
23 display: flex;
24 justify-content: space-between;
25 align-items: center;
26 }
27 .block-lab-pro .tile_header span {
28 font-size: 16px;
29 line-height: 16px;
30 }
31 .block-lab-pro .tile_body {
32 padding: 0 40px 20px;
33 }
34 .block-lab-pro .tile_body h4 {
35 font-size: 24px;
36 line-height: 24px;
37 }
38 .block-lab-pro .tile_footer {
39 border-top: 2px solid #F7F9FC;
40 padding: 30px 40px 30px;
41 display: flex;
42 justify-content: space-between;
43 align-items: center;
44 }
45 .block-lab-pro .tile_footer.tile_footer_email {
46 background-color: #F7F9FC;
47 }
48 .block-lab-pro .tile_icon {
49 width: 180px;
50 display: block;
51 margin: auto;
52 max-width: 100%;
53 }
54 .block-lab-pro .tile_icon_wrapper {
55 width: 180px;
56 height: 180px;
57 display: block;
58 background-size: contain;
59 background-position: center;
60 background-repeat: no-repeat;
61 margin: auto;
62 }
63 .block-lab-pro .button {
64 display: inline-block;
65 position: relative;
66 background-color: #5c34e8;
67 padding: 10px 12px;
68 height: auto;
69 border-radius: 4px;
70 border: 2px solid #5c34e8;
71 color: #fff;
72 font-size: 13px;
73 text-decoration: none;
74 line-height: 13px;
75 cursor: pointer;
76 box-shadow: none;
77 }
78 .block-lab-pro .button:active,
79 .block-lab-pro .button:focus,
80 .block-lab-pro .button:visited {
81 color: #fff;
82 background-color: #5c34e8;
83 }
84 .block-lab-pro .button:hover {
85 color: #fff;
86 background-color: #5c34e8;
87 border-color: #5c34e8;
88 opacity: 0.9;
89 }
90 .block-lab-pro .button:nth-of-type( 2 ) {
91 margin-left: 10px;
92 }
93 .block-lab-pro .button--secondary {
94 background-color: #ff237e;
95 border-color: #ff237e;
96 color: #fff;
97 }
98 .block-lab-pro .button--secondary:active,
99 .block-lab-pro .button--secondary:focus,
100 .block-lab-pro .button--secondary:visited {
101 color: #fff;
102 background-color: #ff237e;
103 }
104 .block-lab-pro .button--secondary:hover {
105 background-color: #ff237e;
106 border-color: #ff237e;
107 opacity: 0.9;
108 }
109 .block-lab-pro .button--secondary-stroke {
110 border-color: #fff;
111 background-color: transparent;
112 }
113 .block-lab-pro .button--secondary-stroke:active,
114 .block-lab-pro .button--secondary-stroke:focus,
115 .block-lab-pro .button--secondary-stroke:visited {
116 color: #fff;
117 }
118 .block-lab-pro .button--secondary-stroke:hover {
119 color: #5c34e8;
120 background-color: #fff;
121 border-color: #fff;
122 }
123 .block-lab-pro .button_close {
124 background-color: rgba( 0, 0, 0, 0.1 );
125 border-radius: 50%;
126 line-height: 1.6em;
127 display: flex;
128 justify-content: center;
129 border: none;
130 color: red;
131 font-weight: 700;
132 }
133 .block-lab-pro .section_heading {
134 grid-column-start: 1;
135 grid-column-end: 7;
136 }
137 .block-lab-pro .dashboard_welcome {
138 grid-column-start: 1;
139 grid-column-end: 7;
140 background-color: #fff;
141 background-image: url('http://getblocklab.com/wp-content/uploads/2019/08/block_lab_hero_bg.svg');
142 background-size: cover;
143 background-position: top;
144 color: #000;
145 padding: 32px;
146 }
147 .block-lab-pro .dashboard_welcome .notice p {
148 color: #444;
149 font-size: 13px;
150 margin: 0.5em 0;
151 padding: 2px;
152 }
153 .block-lab-pro .dashboard_welcome h1 {
154 color: #000;
155 font-weight: 700;
156 font-size: 52px;
157 line-height: 52px;
158 margin-top: 10px;
159 text-shadow: 0 0 10px rgba(255,255,255,0.6);
160 }
161 .block-lab-pro .dashboard_welcome h1 .pro-pill {
162 font-size: .6em;
163 line-height: 1.4;
164 color: #fff;
165 background-color: #5c34e8;
166 display: inline-block;
167 border-radius: 8px;
168 padding: 0 11px;
169 top: -5px;
170 position: relative;
171 box-shadow: 0 0 10px rgba(255,255,255,0.6);
172 text-shadow: none;
173 }
174 .block-lab-pro .dashboard_welcome .tile_body {
175 display: flex;
176 justify-content: left;
177 align-items: center;
178 }
179 .block-lab-pro .dashboard_welcome p.description {
180 font-size: 18px;
181 margin-bottom: 30px;
182 max-width: 480px;
183 color: #000;
184 padding-top: 12px;
185 text-shadow: 0 0 4px #fff;
186 font-style: normal;
187 }
188 /* Tile sizing */
189 .block-lab-pro .tile_2 {
190 grid-column: span 2;
191 }
192 .block-lab-pro .tile_3 {
193 grid-column: span 3;
194 }
195 .block-lab-pro .tile_4 {
196 grid-column: span 4;
197 }
198 .block-lab-pro .tile_5 {
199 grid-column: span 5;
200 }
201 .block-lab-pro .tile_6 {
202 grid-column: span 6;
203 }
204 .block-lab-pro .ul_pills {
205 display: flex;
206 flex-wrap: wrap;
207 justify-content: flex-start;
208 }
209 .block-lab-pro .ul_pills li {
210 display: block;
211 padding: 3px 6px;
212 margin-right: 16px;
213 margin-bottom: 16px;
214 border-radius: 3px;
215 box-shadow: 0 0 0 4px rgba( 121, 110, 255, 0.1 );
216 font-weight: 600;
217 }
218 .block-lab-pro .align_center {
219 text-align: center;
220 }
221 /* Mailchimp Styling */
222 #mc_embed_signup {
223 width: 100%;
224 }
225 #mc_embed_signup #mc_embed_signup_scroll {
226 display: flex;
227 }
228 .block-lab-pro input[type=email].input {
229 background-color: #fff;
230 border: 1px solid rgba( 0, 0, 0, 0.1 );
231 border-radius: 3px;
232 padding: 9px 10px;
233 width: 100%;
234 }
235 .block-lab-pro label.input_label {
236 display: none;
237 }
238 .mc-field-group {
239 flex-grow: 1;
240 padding-right: 10px
241 }
242 .block-lab-pro .cta_license_form_wrapper {
243 display: flex;
244 align-items: center;
245 }
246 .block-lab-pro .button_cta {
247 padding: 16px 15px 15px;
248 box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
249 }
250 .block-lab-pro .license_key_form {
251 display: flex;
252 background-color: rgba( 0, 0, 0, 0.06 );
253 border-radius: 3px;
254 padding: 4px;
255 }
256 .block-lab-pro .license_key_text {
257 font-size: 14px !important;
258 font-style: italic !important;
259 margin-bottom: 14px !important;
260 margin-right: 10px;
261 margin-left: 10px;
262 }
263 .block-lab-pro input[type="text"].input_text {
264 border-radius: 3px;
265 padding: 11px 10px 10px;
266 width: 100%;
267 box-shadow: none;
268 border: none;
269 background-color: rgba( 255,255,255,1 );
270 margin-right: 4px;
271 }
272 @media ( max-width: 1200px ) {
273 .block-lab-pro .cta_license_form_wrapper {
274 display: block;
275 }
276 }
277 @media ( max-width: 720px ) {
278 .block-lab-pro .container {
279 display: block;
280 }
281 .block-lab-pro .tile {
282 margin-bottom: 20px;
283 }
284 .block-lab-pro .dashboard_welcome {
285 display: block;
286 }
287 .block-lab-pro .tile_body {
288 padding-top: 40px;
289 }
290 }
1 <?php return array('dependencies' => array(), 'version' => '734085aee55b820c6613aebadcd335c2');
...\ No newline at end of file ...\ No newline at end of file
1 div[class^="wp-block-block-lab-"] :required:invalid,.edit-post-settings-sidebar__panel-block .components-panel__body :required:invalid{border-color:#C00000}div[class^="wp-block-block-lab-"] .text-control__error,.edit-post-settings-sidebar__panel-block .components-panel__body .text-control__error{border-color:#d94f4f;box-shadow:0 0 0 1px #d94f4f}div[class^="wp-block-block-lab-"] .block-lab-color-control .components-base-control,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control{display:inline-block;margin-bottom:0 !important}div[class^="wp-block-block-lab-"] .block-lab-color-control .components-base-control .components-base-control__field,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control .components-base-control__field{margin:0 !important;width:100%;height:100%}div[class^="wp-block-block-lab-"] .block-lab-color-control .components-base-control.block-lab-color-popover,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control.block-lab-color-popover{width:28px;height:28px;border-radius:50%;margin:1px 1em !important;background-image:linear-gradient(45deg, #ddd 25%, transparent 25%),linear-gradient(-45deg, #ddd 25%, transparent 25%),linear-gradient(45deg, transparent 75%, #ddd 75%),linear-gradient(-45deg, transparent 75%, #ddd 75%);background-size:10px 10px;background-position:0 0, 0 5px, 5px -5px, -5px 0;display:inline-block;vertical-align:top;border:none;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.2);transition:100ms transform ease;cursor:pointer}div[class^="wp-block-block-lab-"] .block-lab-color-control .components-base-control.block-lab-color-popover:hover,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control.block-lab-color-popover:hover{transform:scale(1.2)}div[class^="wp-block-block-lab-"] .block-lab-color-control .components-base-control.block-lab-color-popover .component-color-indicator,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control.block-lab-color-popover .component-color-indicator{width:100%;height:100%;margin:0;border:none;border-radius:50%}div[class^="wp-block-block-lab-"] .block-lab-media-controls .bl-image__img,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .bl-image__img{max-height:200px;display:block;margin-bottom:8px}div[class^="wp-block-block-lab-"] .block-lab-media-controls .components-placeholder,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .components-placeholder{position:relative}div[class^="wp-block-block-lab-"] .block-lab-media-controls .bl-image__placeholder .components-button.is-button,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .bl-image__placeholder .components-button.is-button{white-space:normal}div[class^="wp-block-block-lab-"] .block-lab-media-controls .components-form-file-upload,div[class^="wp-block-block-lab-"] .block-lab-media-controls .components-media-library-button,div[class^="wp-block-block-lab-"] .block-lab-media-controls .bl-image__remove,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .components-form-file-upload,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .components-media-library-button,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .bl-image__remove{margin:0 4px 4px 0;display:inline-block;vertical-align:top}div[class^="wp-block-block-lab-"] .block-lab-media-controls .components-base-control__field .components-base-control__help,.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .components-base-control__field .components-base-control__help{margin-bottom:4px;margin-top:0}div[class^="wp-block-block-lab-"]{margin:0}div[class^="wp-block-block-lab-"] .block-form{border-left:none;background:rgba(139,139,150,0.1);padding:22px 0 22px 22px;font-size:0.8125rem;display:flex;flex-wrap:wrap}div[class^="wp-block-block-lab-"] .block-form h3{color:#111;font-size:1rem;margin:0;flex:1 1 100%}div[class^="wp-block-block-lab-"] .block-form h3 svg{position:relative;top:6px;margin-right:4px}div[class^="wp-block-block-lab-"] .block-form p{font-size:1rem;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}div[class^="wp-block-block-lab-"] .block-form .block-lab-control{flex-basis:100%;padding-right:22px}div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-25{flex-basis:25%}div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-50{flex-basis:50%}div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-75{flex-basis:75%}@media screen and (max-width: 782px){div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-25,div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-50,div[class^="wp-block-block-lab-"] .block-form .block-lab-control.width-75{flex-basis:100%}}div[class^="wp-block-block-lab-"] .block-form .components-base-control{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}div[class^="wp-block-block-lab-"] .block-form .components-base-control__field{margin:1em 0 0}div[class^="wp-block-block-lab-"] .block-form .components-base-control__field .components-base-control__label{display:block;font-weight:600}div[class^="wp-block-block-lab-"] .block-form .components-base-control__help{margin:0 0 1em 0.5em;font-size:1em;color:rgba(0,0,0,0.8)}div[class^="wp-block-block-lab-"] .block-form .components-select-control__input{max-width:unset}div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .components-base-control__field{margin-top:20px}div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .input-control{background:#fff;min-height:7em;line-height:1.4rem;font-size:13px}div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .input-control p{font-size:13px;margin-top:0.67rem;margin-bottom:0.67rem}div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .input-control p:first-child,div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .input-control p:first-child>p{margin-top:0}div[class^="wp-block-block-lab-"] .block-lab-rich-text-control .editor-rich-text__inline-toolbar .components-toolbar>.components-toolbar{border:none;border-right:1px solid #e2e4e7}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__toolbar{position:relative;z-index:10;top:1px}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__toolbar>.mce-container{width:100% !important;border:1px solid #c5c5c5;box-shadow:none;box-sizing:border-box}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__toolbar .mce-container-body{width:100% !important}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__toolbar .mce-container-body>.mce-container{width:100% !important}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__edit{background:#fff;padding:0.2rem 1rem;border:1px solid #8d96a0;outline:none;min-height:6rem}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__edit:after{content:"";display:table;clear:both}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__edit p{font-size:13px;margin-top:0.67rem;margin-bottom:0.67rem}div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__edit ul,div[class^="wp-block-block-lab-"] .block-lab-classic-text-control .classic-text__edit ol{margin-left:1rem}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows{width:100%}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row{min-width:100%;border:1px dashed #8d96a0;border-top:0;margin-bottom:0;padding:10px 14px 0;position:relative;transition:background 1s ease-in-out}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:first-child{border-top:1px dashed #8d96a0}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:first-child .block-lab-repeater--row-actions .button-move-up{display:none}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:last-child .block-lab-repeater--row-actions .button-move-down{display:none}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .components-base-control__field{margin:0 0 14px}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-actions{position:absolute;display:none;top:-1px;left:-30px}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-actions .components-icon-button{width:28px;padding:0;background:#fff;border-color:#8d96a0;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-actions .components-icon-button svg{width:100%}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete{display:none;position:absolute;right:2px;top:4px}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete .components-icon-button{width:28px;padding:0;border-color:transparent;box-shadow:none;background:transparent;color:#8d96a0}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete .components-icon-button svg{width:100%}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete .components-icon-button:hover:not(:disabled){color:#d94f4f}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete .components-icon-button:active:not(:disabled){color:#40464d}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row .block-lab-repeater--row-delete .components-icon-button:disabled{opacity:0}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-to{transition:background 0.2s ease-in-out;background:#fef8ee}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-from{transition:none;background:transparent}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:hover:not(.row-from) .block-lab-repeater--row-actions,div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-to .block-lab-repeater--row-actions{display:block;background:#fff;box-sizing:border-box;border:1px solid #8d96a0;border-radius:3px;border-top-right-radius:0;border-bottom-right-radius:0;border-right:none}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:hover:not(.row-from) .block-lab-repeater--row-actions .components-icon-button,div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-to .block-lab-repeater--row-actions .components-icon-button{border-color:transparent;box-shadow:none;background:transparent}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:hover:not(.row-from) .block-lab-repeater--row-actions .components-icon-button:hover,div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-to .block-lab-repeater--row-actions .components-icon-button:hover{background-color:rgba(139,139,150,0.1)}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:hover:not(.row-from) .block-lab-repeater--row-delete,div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row.row-to .block-lab-repeater--row-delete{display:block}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater__rows .block-lab-repeater--row:before{content:'';width:29px;height:100%;position:absolute;left:-30px;top:0}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater--row-add{margin-top:4px;display:flex;justify-content:center}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater--row-add .components-icon-button:hover:not(:disabled){border-color:transparent;box-shadow:none;background:transparent;color:#007cba}div[class^="wp-block-block-lab-"] .block-lab-repeater .block-lab-repeater--row-add .components-icon-button:active:disabled{color:#555d66}.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-media-controls .components-spinner{float:none}.edit-post-settings-sidebar__panel-block .components-panel__body .block-lab-color-control .components-base-control__label{display:block}.edit-post-settings-sidebar__panel-block .components-panel__body .bl-fetch--input input[type="text"]{width:100%}.edit-post-layout .components-popover:not(.is-mobile):not(.bl-fetch__popover) .components-popover__content .components-color-picker{min-width:340px}.bl-fetch__input{width:100%}.bl-fetch__popover.editor:not(.is-mobile) .components-popover__content{width:300px}.bl-fetch__popover.inspector:not(.is-mobile) .components-popover__content{min-width:247px}.bl-fetch-input__suggestions{max-height:200px;transition:all 0.15s ease-in-out;padding:4px 0;overflow-y:auto}.bl-fetch-input__suggestions,.bl-fetch-input .components-spinner{display:none}@media (min-width: 600px){.bl-fetch-input__suggestions,.bl-fetch-input .components-spinner{display:inherit}}.bl-fetch-input>.components-base-control__field{display:flex;flex-wrap:wrap}.bl-fetch-input>.components-base-control__field>.components-base-control__label{flex:3 3 100%}.bl-fetch-input>.components-base-control__field>.bl-fetch__input{flex:1 1 0}.bl-fetch-input>.components-base-control__field>.components-spinner{flex:0 0 auto}.bl-fetch-input__suggestion{padding:4px 8px;color:#6c7781;display:block;font-size:13px;cursor:pointer;background:#fff;width:100%;border:none;text-align:left;border:none;box-shadow:none}.bl-fetch-input__suggestion:hover{background:#e2e4e7}.bl-fetch-input__suggestion:focus,.bl-fetch-input__suggestion.is-selected{background:#00719e;color:#fff;outline:none}.bl-dot-tip.read-more{float:left;margin:0;padding-right:1em}.editor-styles-wrapper .block-lab-editor__ssr ul,.editor-styles-wrapper .block-lab-editor__ssr ol{margin-left:1rem}
2
1 /**
2 * Colors
3 *
4 * The variables below are taken from Gutenberg's styling in _colors.scss.
5 *
6 * @see https://github.com/WordPress/gutenberg/blob/master/assets/stylesheets/_colors.scss
7 */
8
9 // Hugo's new WordPress shades of gray, from http://codepen.io/hugobaeta/pen/grJjVp.
10 $black: #000;
11 $dark-gray-900: #191e23;
12 $dark-gray-800: #23282d;
13 $dark-gray-700: #32373c;
14 $dark-gray-600: #40464d;
15 $dark-gray-500: #555d66; // Use this most of the time for dark items.
16 $dark-gray-400: #606a73;
17 $dark-gray-300: #6c7781; // Lightest gray that can be used for AA text contrast.
18 $dark-gray-200: #7e8993;
19 $dark-gray-150: #8d96a0; // Lightest gray that can be used for AA non-text contrast.
20 $dark-gray-100: #8f98a1;
21 $light-gray-900: #a2aab2;
22 $light-gray-800: #b5bcc2;
23 $light-gray-700: #ccd0d4;
24 $light-gray-600: #d7dade;
25 $light-gray-500: #e2e4e7; // Good for "grayed" items and borders.
26 $light-gray-400: #e8eaeb; // Good for "readonly" input fields and special text selection.
27 $light-gray-300: #edeff0;
28 $light-gray-200: #f3f4f5;
29 $light-gray-100: #f8f9f9;
30 $white: #fff;
31
32 // Dark opacities, for use with light themes.
33 $dark-opacity-900: rgba(#000510, 0.9);
34 $dark-opacity-800: rgba(#00000a, 0.85);
35 $dark-opacity-700: rgba(#06060b, 0.8);
36 $dark-opacity-600: rgba(#000913, 0.75);
37 $dark-opacity-500: rgba(#0a1829, 0.7);
38 $dark-opacity-400: rgba(#0a1829, 0.65);
39 $dark-opacity-300: rgba(#0e1c2e, 0.62);
40 $dark-opacity-200: rgba(#162435, 0.55);
41 $dark-opacity-100: rgba(#223443, 0.5);
42 $dark-opacity-light-900: rgba(#304455, 0.45);
43 $dark-opacity-light-800: rgba(#425863, 0.4);
44 $dark-opacity-light-700: rgba(#667886, 0.35);
45 $dark-opacity-light-600: rgba(#7b86a2, 0.3);
46 $dark-opacity-light-500: rgba(#9197a2, 0.25);
47 $dark-opacity-light-400: rgba(#95959c, 0.2);
48 $dark-opacity-light-300: rgba(#829493, 0.15);
49 $dark-opacity-light-200: rgba(#8b8b96, 0.1);
50 $dark-opacity-light-100: rgba(#747474, 0.05);
51 $dark-opacity-background-fill: rgba($dark-gray-700, 0.7); // Similar to $dark-opacity-light-200, but more opaque.
52
53 // Light opacities, for use with dark themes.
54 $light-opacity-900: rgba($white, 1);
55 $light-opacity-800: rgba($white, 0.9);
56 $light-opacity-700: rgba($white, 0.85);
57 $light-opacity-600: rgba($white, 0.8);
58 $light-opacity-500: rgba($white, 0.75);
59 $light-opacity-400: rgba($white, 0.7);
60 $light-opacity-300: rgba($white, 0.65);
61 $light-opacity-200: rgba($white, 0.6);
62 $light-opacity-100: rgba($white, 0.55);
63 $light-opacity-light-900: rgba($white, 0.5);
64 $light-opacity-light-800: rgba($white, 0.45);
65 $light-opacity-light-700: rgba($white, 0.4);
66 $light-opacity-light-600: rgba($white, 0.35);
67 $light-opacity-light-500: rgba($white, 0.3);
68 $light-opacity-light-400: rgba($white, 0.25);
69 $light-opacity-light-300: rgba($white, 0.2);
70 $light-opacity-light-200: rgba($white, 0.15);
71 $light-opacity-light-100: rgba($white, 0.1);
72 $light-opacity-background-fill: rgba($light-gray-300, 0.8); // Similar to $light-opacity-light-200, but more opaque.
73
74 // Additional colors.
75 // Some are from https://make.wordpress.org/design/handbook/foundations/colors/.
76 $blue-wordpress-700: #00669b;
77 $blue-dark-900: #0071a1;
78
79 $blue-medium-900: #006589;
80 $blue-medium-800: #00739c;
81 $blue-medium-700: #007fac;
82 $blue-medium-600: #008dbe;
83 $blue-medium-500: #00a0d2;
84 $blue-medium-400: #33b3db;
85 $blue-medium-300: #66c6e4;
86 $blue-medium-200: #bfe7f3;
87 $blue-medium-100: #e5f5fa;
88 $blue-medium-highlight: #b3e7fe;
89 $blue-medium-focus: #007cba;
90
91 // Alert colors.
92 $alert-yellow: #f0b849;
93 $alert-red: #d94f4f;
94 $alert-green: #4ab866;
...\ No newline at end of file ...\ No newline at end of file
1 @import 'color';
2
3 $input-padding: 8px;
4 $input-width: 300px;
5 $default-font-size: 13px;
6 $break-small: 600px;
7 $font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
8
9 @mixin break-small() {
10 @media (min-width: #{ ($break-small) }) {
11 @content;
12 }
13 }
14
15 /* Menu items. */
16 @mixin menu-style__neutral() {
17 border: none;
18 box-shadow: none;
19 }
20
21 /* Block form in editor & sidebar */
22 div[class^="wp-block-block-lab-"],
23 .edit-post-settings-sidebar__panel-block .components-panel__body {
24
25 :required:invalid {
26 border-color: #C00000;
27 }
28
29 .text-control__error {
30 border-color: #d94f4f;
31 box-shadow: 0 0 0 1px #d94f4f;
32 }
33
34 /* Color Control Component */
35 .block-lab-color-control {
36
37 .components-base-control {
38 display: inline-block;
39 margin-bottom: 0 !important;
40
41 .components-base-control__field {
42 margin: 0 !important;
43 width: 100%;
44 height: 100%;
45 }
46
47 &.block-lab-color-popover {
48 width: 28px;
49 height: 28px;
50 border-radius: 50%;
51 margin: 1px 1em !important;
52 background-image: linear-gradient(45deg, #ddd 25%, transparent 25%), linear-gradient(-45deg, #ddd 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ddd 75%), linear-gradient(-45deg, transparent 75%, #ddd 75%);
53 background-size: 10px 10px;
54 background-position: 0 0, 0 5px, 5px -5px, -5px 0;
55 display: inline-block;
56 vertical-align: top;
57 border: none;
58 box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);
59 transition: 100ms transform ease;
60 cursor: pointer;
61
62 &:hover {
63 transform: scale(1.2)
64 }
65
66 .component-color-indicator {
67 width: 100%;
68 height: 100%;
69 margin: 0;
70 border: none;
71 border-radius: 50%;
72 }
73 }
74 }
75 }
76
77 /* Media Upload Component */
78 .block-lab-media-controls {
79 .bl-image__img {
80 max-height: 200px;
81 display: block;
82 margin-bottom: 8px;
83 }
84
85 .components-placeholder {
86 position: relative;
87 }
88
89 .bl-image__placeholder .components-button.is-button {
90 white-space: normal;
91 }
92
93 .components-form-file-upload,
94 .components-media-library-button,
95 .bl-image__remove {
96 margin: 0 4px 4px 0;
97 display: inline-block;
98 vertical-align: top;
99 }
100
101 .components-base-control__field {
102 .components-base-control__help {
103 margin-bottom: 4px;
104 margin-top: 0;
105 }
106 }
107 }
108 }
109
110 /* Block form in editor */
111 div[class^="wp-block-block-lab-"] {
112 margin: 0;
113
114 .block-form {
115 border-left: none;
116 background: $dark-opacity-light-200;
117 padding: 22px 0 22px 22px;
118 font-size: 0.8125rem;
119 display: flex;
120 flex-wrap: wrap;
121
122 h3 {
123 color: #111;
124 font-size: 1rem;
125 margin: 0;
126 flex: 1 1 100%;
127
128 svg {
129 position: relative;
130 top: 6px;
131 margin-right: 4px;
132 }
133 }
134
135 p {
136 font-size: 1rem;
137 font-family: $font-family;
138 }
139
140 .block-lab-control {
141 flex-basis: 100%;
142 padding-right: 22px;
143
144 &.width-25 {
145 flex-basis: 25%;
146 }
147 &.width-50 {
148 flex-basis: 50%;
149 }
150 &.width-75 {
151 flex-basis: 75%;
152 }
153 }
154
155 @media screen and (max-width: 782px) {
156 .block-lab-control.width-25,
157 .block-lab-control.width-50,
158 .block-lab-control.width-75 {
159 flex-basis: 100%;
160 }
161 }
162
163 .components-base-control {
164 font-family: $font-family;
165 }
166
167 .components-base-control__field {
168 margin: 1em 0 0;
169
170 .components-base-control__label {
171 display: block;
172 font-weight: 600;
173 }
174 }
175
176 .components-base-control__help {
177 margin: 0 0 1em 0.5em;
178 font-size: 1em;
179 color: rgba(0, 0, 0, 0.8);
180 }
181
182 /* Override a max-width: 25rem style rule in Core that can prevent displaying at the selected with, like 75% */
183 .components-select-control__input {
184 max-width: unset;
185 }
186 }
187
188 /* Rich Text Control Component */
189 .block-lab-rich-text-control {
190 .components-base-control__field {
191 margin-top: 20px;
192 }
193
194 .input-control {
195 background: #fff;
196 min-height: 7em;
197 line-height: 1.4rem;
198 font-size: 13px;
199
200 p {
201 font-size: 13px;
202 margin-top: 0.67rem;
203 margin-bottom: 0.67rem;
204 }
205
206 p:first-child,
207 p:first-child > p {
208 margin-top: 0;
209 }
210 }
211
212 /* Override native styling that created a double border */
213 .editor-rich-text__inline-toolbar .components-toolbar > .components-toolbar {
214 border: none;
215 border-right: 1px solid $light-gray-500;
216 }
217 }
218
219 /* Classic Text Control Component */
220 .block-lab-classic-text-control {
221
222 .classic-text__toolbar {
223 position: relative;
224 z-index: 10;
225 top: 1px;
226
227 > .mce-container {
228 width: 100% !important;
229 border: 1px solid #c5c5c5;
230 box-shadow: none;
231 box-sizing: border-box;
232 }
233
234 .mce-container-body {
235 width: 100% !important;
236 > .mce-container {
237 width: 100% !important;
238 }
239 }
240 }
241
242 /* The editing area, not the toolbar. */
243 .classic-text__edit {
244
245 background: $white;
246 padding: 0.2rem 1rem;
247 border: 1px solid $dark-gray-150;
248 outline: none;
249 min-height: 6rem;
250
251 /* Clear the float in case an image is floated. */
252 &:after {
253 content: "";
254 display: table;
255 clear: both;
256 }
257
258 p {
259 font-size: 13px;
260 margin-top: 0.67rem;
261 margin-bottom: 0.67rem;
262 }
263
264 ul, ol {
265 margin-left: 1rem;
266 }
267 }
268 }
269
270 .block-lab-repeater {
271
272 .block-lab-repeater__rows {
273 width: 100%;
274
275 .block-lab-repeater--row {
276 min-width: 100%;
277 border: 1px dashed $dark-gray-150;
278 border-top: 0;
279 margin-bottom: 0;
280 padding: 10px 14px 0;
281 position: relative;
282 transition: background 1s ease-in-out;
283
284 &:first-child {
285 border-top: 1px dashed $dark-gray-150;
286
287 .block-lab-repeater--row-actions .button-move-up {
288 display: none;
289 }
290 }
291
292 &:last-child {
293 .block-lab-repeater--row-actions .button-move-down {
294 display: none;
295 }
296 }
297
298 .components-base-control__field {
299 margin: 0 0 14px;
300 }
301
302 .block-lab-repeater--row-actions {
303 position: absolute;
304 display: none;
305 top: -1px;
306 left: -30px;
307
308 .components-icon-button {
309 width: 28px;
310 padding: 0;
311 background: $white;
312 border-color: $dark-gray-150;
313 border-top-right-radius: 0;
314 border-bottom-right-radius: 0;
315 border-right: none;
316
317 svg {
318 width: 100%;
319 }
320 }
321 }
322
323 .block-lab-repeater--row-delete {
324 display: none;
325 position: absolute;
326 right: 2px;
327 top: 4px;
328
329 .components-icon-button {
330 width: 28px;
331 padding: 0;
332 border-color: transparent;
333 box-shadow: none;
334 background: transparent;
335 color: $dark-gray-150;
336
337 svg {
338 width: 100%;
339 }
340
341 &:hover:not(:disabled) {
342 color: $alert-red;
343 }
344
345 &:active:not(:disabled) {
346 color: $dark-gray-600;
347 }
348
349 &:disabled {
350 opacity: 0;
351 }
352 }
353 }
354
355 &.row-to {
356 transition: background 0.2s ease-in-out;
357 background: #fef8ee;
358 }
359
360 &.row-from {
361 transition: none;
362 background: transparent;
363 }
364
365 &:hover:not(.row-from),
366 &.row-to {
367 .block-lab-repeater--row-actions {
368 display: block;
369 background: #fff;
370 box-sizing: border-box;
371 border: 1px solid $dark-gray-150;
372 border-radius: 3px;
373 border-top-right-radius: 0;
374 border-bottom-right-radius: 0;
375 border-right: none;
376
377 .components-icon-button {
378 border-color: transparent;
379 box-shadow: none;
380 background: transparent;
381
382 &:hover {
383 background-color: $dark-opacity-light-200;
384 }
385 }
386 }
387
388 .block-lab-repeater--row-delete {
389 display: block;
390 }
391 }
392
393 &:before {
394 content: '';
395 width: 29px;
396 height: 100%;
397 position: absolute;
398 left: -30px;
399 top: 0;
400 }
401 }
402 }
403
404 .block-lab-repeater--row-add {
405 margin-top: 4px;
406 display: flex;
407 justify-content: center;
408
409 .components-icon-button:hover:not(:disabled) {
410 border-color: transparent;
411 box-shadow: none;
412 background: transparent;
413 color: $blue-medium-focus;
414 }
415 .components-icon-button:active:disabled {
416 color: #555d66;
417 }
418 }
419 }
420 }
421
422 /* Block form in sidebar */
423 .edit-post-settings-sidebar__panel-block .components-panel__body {
424 /* Media Upload Component */
425 .block-lab-media-controls {
426 .components-spinner {
427 float: none;
428 }
429 }
430
431 .block-lab-color-control .components-base-control__label {
432 display: block;
433 }
434
435 /* The FetchInput component */
436 .bl-fetch--input input[type="text"] {
437 width: 100%;
438 }
439 }
440
441 /* Miscellaneous global styles */
442 .edit-post-layout {
443 .components-popover:not(.is-mobile):not(.bl-fetch__popover) .components-popover__content .components-color-picker {
444 min-width: 340px;
445 }
446 }
447
448 /*
449 * FetchInput styling forked from URLInput styling in Gutenberg, with different class names.
450 * This uses a Popover, which often appears outside of its container.
451 * So this doesn't work inside the "Block form in editor" section above.
452 * @see https://github.com/WordPress/gutenberg/blob/fd2468d6c486220f7f1fa5bfa2366c510b46af27/packages/editor/src/components/url-input/style.scss#L42
453 */
454 .bl-fetch__input {
455 width: 100%;
456 }
457
458 .bl-fetch__popover.editor:not(.is-mobile) .components-popover__content {
459 width: $input-width;
460 }
461
462 .bl-fetch__popover.inspector:not(.is-mobile) .components-popover__content {
463 min-width: 247px;
464 }
465
466 .bl-fetch-input__suggestions {
467 max-height: 200px;
468 transition: all 0.15s ease-in-out;
469 padding: 4px 0;
470 overflow-y: auto;
471 }
472
473 // Hide suggestions on mobile until we @todo find a better way to show them.
474 .bl-fetch-input__suggestions,
475 .bl-fetch-input .components-spinner {
476 display: none;
477 @include break-small() {
478 display: inherit;
479 }
480 }
481
482 .bl-fetch-input {
483 > .components-base-control__field {
484 display: flex;
485 flex-wrap: wrap;
486
487 > .components-base-control__label {
488 flex: 3 3 100%;
489 }
490
491 > .bl-fetch__input {
492 flex: 1 1 0;
493 }
494 > .components-spinner {
495 flex: 0 0 auto;
496 }
497 }
498 }
499
500 .bl-fetch-input__suggestion {
501 padding: 4px $input-padding;
502 color: $dark-gray-300; // Lightest we can use for contrast.
503 display: block;
504 font-size: $default-font-size;
505 cursor: pointer;
506 background: $white;
507 width: 100%;
508 border: none;
509 text-align: left;
510 @include menu-style__neutral();
511
512 &:hover {
513 background: $light-gray-500;
514 }
515
516 &:focus,
517 &.is-selected {
518 background: rgb(0, 113, 158);
519 color: $white;
520 outline: none;
521 }
522 }
523
524 .bl-dot-tip.read-more {
525 float: left;
526 margin: 0;
527 padding-right: 1em;
528 }
529
530 // In the <ServerSideRender> display, correct an issue where <ul> and <ol> can appear outside (to the left) of their container.
531 .editor-styles-wrapper .block-lab-editor__ssr {
532 ul, ol {
533 margin-left: 1rem;
534 }
535 }
1 /**
2 * Used for editing Blocks.
3 *
4 * @copyright Copyright(c) 2020, Block Lab
5 * @license GPL-2.0-only
6 */
7
8 /* global blockLab, jQuery */
9
10 ( function( $ ) {
11 $( function() {
12 blockTitleInit();
13 blockIconInit();
14 blockFieldInit();
15 blockPostTypesInit();
16
17 $( '#block-add-field' ).on( 'click', function() {
18 const template = wp.template( 'field-repeater' ),
19 data = { uid: new Date().getTime() },
20 row = $( template( data ) ),
21 edit = row.find( '.block-fields-actions-edit' ),
22 label = row.find( '.block-fields-edit-label input' );
23
24 incrementRow( row );
25
26 $( '.block-fields-rows' ).append( row );
27 $( '.block-no-fields' ).hide();
28 $( '.block-lab-add-fields' ).hide();
29
30 edit.trigger( 'click' );
31 label.data( 'defaultValue', label.val() );
32 label.trigger( 'change' );
33 label.select();
34 } );
35
36 $( '#block_fields' ).on( 'click', '#block-add-sub-field', function() {
37 const template = wp.template( 'field-repeater' ),
38 data = { uid: new Date().getTime() },
39 row = $( template( data ) ),
40 parent = $( this ).closest( '.block-fields-row' ),
41 edit = row.find( '.block-fields-actions-edit' ),
42 label = row.find( '.block-fields-edit-label input' );
43
44 incrementRow( row );
45
46 // Prevents adding a repeater, in a repeater, in a repeater…
47 row.find( '.block-fields-edit-control option[value="repeater"]' ).remove();
48
49 // Don't render the location or width settings for sub-fields.
50 row.find( '.block-fields-edit-location-settings' ).remove();
51 row.find( '.block-fields-edit-width-settings' ).remove();
52
53 // Add parent UID as a hidden input
54 const parentInput = $( '<input>' ).attr( {
55 type: 'hidden',
56 name: 'block-fields-parent[' + data.uid + ']',
57 value: parent.data( 'uid' ),
58 } );
59 row.append( parentInput );
60
61 $( '.block-fields-sub-rows', parent ).append( row );
62 $( '.repeater-no-fields', parent ).hide();
63 $( '.repeater-has-fields', parent ).show();
64
65 edit.trigger( 'click' );
66 label.data( 'defaultValue', label.val() );
67 label.trigger( 'change' );
68 label.select();
69 } );
70
71 $( '.block-lab-pub-section .edit-post-types' ).on( 'click', function() {
72 const excludedPostTypes = $( '#block-excluded-post-types' ).val().split( ',' ).filter( Boolean );
73
74 $( '.post-types-select-items input' ).prop( 'checked', true );
75
76 for ( const postType of excludedPostTypes ) {
77 $( '.post-types-select-items input[value="' + postType + '"]' ).prop( 'checked', false );
78 }
79
80 $( '.block-lab-pub-section .post-types-select' ).slideDown( 200 );
81 $( this ).hide();
82 } );
83
84 $( '.block-lab-pub-section .save-post-types' ).on( 'click', function() {
85 const checked = $( '.post-types-select-items input:not(:checked)' ),
86 postTypes = [];
87 for ( const input of checked ) {
88 postTypes.push( $( input ).val() );
89 }
90
91 $( '#block-excluded-post-types' ).val( postTypes.join( ',' ) );
92
93 blockPostTypesInit();
94
95 $( '.block-lab-pub-section .post-types-select' ).slideUp( 200 );
96 $( '.block-lab-pub-section .edit-post-types' ).show();
97 } );
98
99 $( '.block-lab-pub-section .button-cancel' ).on( 'click', function() {
100 $( '.block-lab-pub-section .post-types-select' ).slideUp( 200 );
101 $( '.block-lab-pub-section .edit-post-types' ).show();
102 } );
103
104 $( '#block_properties .block-properties-icon-select span' ).on( 'click', function() {
105 const svg = $( 'svg', this ).clone();
106 $( '#block_properties .block-properties-icon-select span.selected' ).removeClass( 'selected' );
107 $( this ).addClass( 'selected' );
108 $( '#block-properties-icon' ).val( $( this ).data( 'value' ) );
109 $( '#block-properties-icon-current' ).html( svg );
110 } );
111
112 $( '#block_properties .block-properties-category' ).on( 'change', function() {
113 if ( '__custom' === $( this ).val() ) {
114 $( this ).next( '.block-properties-category-custom' ).css( 'display', 'block' );
115 } else {
116 $( this ).next( '.block-properties-category-custom' ).hide();
117 }
118 } );
119
120 $( '#block_template .template-location a.filename' ).on( 'click', function( event ) {
121 event.preventDefault();
122
123 const copy = $( '#block_template .template-location .click-to-copy' ),
124 input = $( 'input', copy ),
125 width = $( this ).width() + input.outerWidth( false ) - input.width();
126
127 copy.show();
128 input.outerWidth( width ).focus().select();
129
130 const copied = document.execCommand( 'copy' );
131
132 if ( copied ) {
133 copy.attr( 'data-tooltip', blockLab.copySuccessMessage );
134 } else {
135 copy.attr( 'data-tooltip', blockLab.copyFailMessage );
136 }
137
138 $( this ).hide();
139 } );
140
141 $( '#block_template .template-location .click-to-copy input' ).on( 'blur', function() {
142 $( '#block_template .template-location a.filename' ).show();
143 $( this ).parent().hide();
144 } );
145
146 $( '.block-fields-rows' )
147 .on( 'click', '.block-fields-actions-delete', function() {
148 const subRows = $( this ).closest( '.block-fields-sub-rows' );
149 $( this ).closest( '.block-fields-row' ).remove();
150 if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) {
151 $( '.block-no-fields' ).show();
152 }
153 if ( 0 !== subRows.length && 0 === $( '.block-fields-row', subRows ).length ) {
154 subRows.parent().find( '.repeater-no-fields' ).show();
155 subRows.parent().find( '.repeater-has-fields' ).hide();
156 }
157 } )
158 .on( 'click', '.block-fields-actions-duplicate', function() {
159 const row = $( this ).closest( '.block-fields-row' ),
160 newRow = cloneRow( row );
161
162 // Expand the duplicated row.
163 if ( newRow.hasClass( 'block-fields-row-active' ) ) {
164 row.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' );
165 } else {
166 newRow.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' );
167 }
168
169 // Select the label field of the new row.
170 const label = newRow.find( '.block-fields-edit-label input' );
171 label.trigger( 'change' );
172 label.select();
173 } )
174 .on( 'click', '.block-fields-actions-edit, a.row-title', function() {
175 const currentRow = $( this ).closest( '.block-fields-row' );
176
177 // If we're expanding this row, first collapse all other rows and scroll this row into view.
178 if ( ! currentRow.hasClass( 'block-fields-row-active' ) ) {
179 const editRow = $( '.block-fields-rows .block-fields-edit' );
180
181 scrollRowIntoView( currentRow );
182 editRow.slideUp();
183
184 $( '.block-fields-rows .block-fields-row-active' ).removeClass( 'block-fields-row-active' );
185 }
186
187 currentRow.toggleClass( 'block-fields-row-active' );
188 currentRow.find( '.block-fields-edit' ).first().slideToggle();
189
190 // Fetch field settings if field is active and there are no settings.
191 if ( $( this ).closest( '.block-fields-row' ).hasClass( 'block-fields-row-active' ) ) {
192 const fieldRow = $( this ).closest( '.block-fields-row' );
193 if ( 0 === fieldRow.find( '.block-fields-edit-settings' ).length ) {
194 const fieldControl = fieldRow.find( '.block-fields-edit-control select' ).val();
195 fetchFieldSettings( fieldRow, fieldControl );
196 }
197 }
198 } )
199 .on( 'click', '.block-fields-edit-actions-close a.button', function() {
200 const fieldRow = $( this ).closest( '.block-fields-row' );
201 fieldRow.removeClass( 'block-fields-row-active' );
202 $( '.block-fields-edit', fieldRow ).slideUp();
203 } )
204 .on( 'change keyup', '.block-fields-edit input', function() {
205 const sync = $( this ).data( 'sync' );
206 $( '#' + sync ).text( $( this ).val() );
207 } )
208 .on( 'change keyup', '.block-fields-edit select', function() {
209 const sync = $( this ).data( 'sync' ),
210 option = $( 'option:selected', $( this ) ).text();
211 $( '#' + sync ).text( option );
212 } )
213 .on( 'change', '.block-fields-edit-control select', function() {
214 const fieldRow = $( this ).closest( '.block-fields-row' );
215 fetchFieldSettings( fieldRow, $( this ).val() );
216
217 if ( 'repeater' === $( this ).val() ) {
218 const subRows = wp.template( 'sub-field-rows' );
219 fieldRow.append( subRows );
220 blockFieldSubRowsInit( $( '.block-fields-sub-rows', fieldRow ) );
221 } else {
222 $( '.block-fields-sub-rows,.block-fields-sub-rows-actions', fieldRow ).remove();
223 }
224 } )
225 .on( 'change', '.block-fields-edit-location-settings select', function() {
226 blockFieldWidthInit( $( this ).closest( '.block-fields-row' ) );
227 } )
228 .on( 'change keyup', '.block-fields-edit-label input', function() {
229 const slug = $( this )
230 .closest( '.block-fields-edit' )
231 .find( '.block-fields-edit-name input' );
232
233 if ( 'false' !== slug.data( 'autoslug' ) ) {
234 slug
235 .val( slugify( $( this ).val() ) )
236 .trigger( 'change' );
237 }
238 } )
239 .on( 'blur', '.block-fields-edit-label input', function() {
240 // If the value hasn't changed from default, don't turn off autoslug.
241 if ( $( this ).data( 'defaultValue' ) === $( this ).val() ) {
242 return;
243 }
244 $( this )
245 .closest( '.block-fields-edit' )
246 .find( '.block-fields-edit-name input' )
247 .data( 'autoslug', 'false' );
248 } )
249 .on( 'mouseenter', '.block-fields-row div:not(.block-fields-edit,.block-fields-sub-rows,.block-fields-sub-rows-actions)', function() {
250 $( this ).parent().addClass( 'hover' );
251 } )
252 .on( 'mouseleave', '.block-fields-row div', function() {
253 $( this ).parent().removeClass( 'hover' );
254 } )
255 .sortable( {
256 axis: 'y',
257 cursor: 'grabbing',
258 handle: '> .block-fields-row-columns .block-fields-sort-handle',
259 containment: 'parent',
260 tolerance: 'pointer',
261 } );
262 } );
263
264 const blockTitleInit = function() {
265 const title = $( '#title' ),
266 slug = $( '#block-properties-slug' );
267
268 // If this is a new block, then enable auto-generated slugs.
269 if ( '' === title.val() && '' === slug.val() ) {
270 // If auto-generated slugs are enabled, set the slug based on the title.
271 title.on( 'change keyup', function() {
272 if ( 'false' !== slug.data( 'autoslug' ) ) {
273 slug.val( slugify( title.val() ) );
274 }
275 } );
276
277 // Turn auto-generated slugs off once a title has been set.
278 title.on( 'blur', function() {
279 if ( '' !== title.val() ) {
280 slug.data( 'autoslug', 'false' );
281 }
282 } );
283 }
284 };
285
286 const blockIconInit = function() {
287 const iconsContainer = $( '.block-properties-icon-select' ),
288 selectedIcon = $( '.selected', iconsContainer );
289 if ( 0 !== iconsContainer.length && 0 !== selectedIcon.length ) {
290 iconsContainer.scrollTop( selectedIcon.position().top );
291 }
292 };
293
294 const blockFieldInit = function() {
295 if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) {
296 $( '.block-no-fields' ).show();
297 }
298 $( '.block-fields-edit-name input' ).data( 'autoslug', 'false' );
299 $( '.block-fields-sub-rows' ).each( function() {
300 blockFieldSubRowsInit( $( this ) );
301 } );
302 };
303
304 const blockFieldSubRowsInit = function( subRows ) {
305 subRows.sortable( {
306 axis: 'y',
307 cursor: 'grabbing',
308 handle: '> .block-fields-row-columns .block-fields-sort-handle',
309 containment: 'parent',
310 tolerance: 'pointer',
311 } );
312 };
313
314 const blockFieldWidthInit = function( fieldRow ) {
315 const widthSettings = fieldRow.find( '.block-fields-edit-width-settings' ),
316 locationSettings = fieldRow.find( '.block-fields-edit-location-settings' );
317
318 if ( 'editor' !== $( 'select', locationSettings ).val() ) {
319 widthSettings.hide();
320 } else {
321 widthSettings.show();
322 }
323 };
324
325 const blockPostTypesInit = function() {
326 if ( 0 === $( '.block-lab-pub-section' ).length ) {
327 return;
328 }
329
330 const display = $( '.post-types-display' );
331
332 const excludedPostTypes = $( '#block-excluded-post-types' )
333 .val()
334 .split( ',' )
335 .filter( Boolean );
336
337 if ( 0 === excludedPostTypes.length ) {
338 display.text( blockLab.postTypes.all );
339 return;
340 }
341
342 const inputs = $( '.post-types-select-items input' );
343
344 if ( excludedPostTypes.length === inputs.length ) {
345 display.text( blockLab.postTypes.none );
346 return;
347 }
348
349 const displayList = [];
350 for ( const input of inputs ) {
351 const postType = $( input ).val();
352 if ( -1 === excludedPostTypes.indexOf( postType ) ) {
353 displayList.push(
354 $( input ).next( 'label' ).text()
355 );
356 }
357 }
358
359 display.text( displayList.join( ', ' ) );
360 };
361
362 const fetchFieldSettings = function( fieldRow, fieldControl ) {
363 if ( ! blockLab.hasOwnProperty( 'fieldSettingsNonce' ) ) {
364 return;
365 }
366
367 const loadingRow = '' +
368 '<tr class="block-fields-edit-loading">' +
369 ' <td class="spacer"></td>' +
370 ' <th></th>' +
371 ' <td><span class="loading"></span></td>' +
372 '</tr>';
373
374 $( '.block-fields-edit-settings', fieldRow ).remove();
375 $( '.block-fields-edit-control', fieldRow ).after( $( loadingRow ) );
376
377 const data = {
378 control: fieldControl,
379 uid: fieldRow.data( 'uid' ),
380 nonce: blockLab.fieldSettingsNonce,
381 };
382
383 // If this is a sub-field, pass along the parent UID as well.
384 if ( fieldRow.parent( '.block-fields-sub-rows' ).length > 0 ) {
385 data.parent = fieldRow.closest( '.block-fields-row' ).data( 'uid' );
386 }
387
388 wp.ajax.send( 'fetch_field_settings', {
389 success( result ) {
390 $( '.block-fields-edit-loading', fieldRow ).remove();
391
392 if ( ! result.hasOwnProperty( 'html' ) ) {
393 return;
394 }
395 const settingsRows = $( result.html );
396 $( '.block-fields-edit-control', fieldRow ).after( settingsRows );
397 blockFieldWidthInit( fieldRow );
398 scrollRowIntoView( fieldRow );
399 },
400 error() {
401 $( '.block-fields-edit-loading', fieldRow ).remove();
402 },
403 data,
404 } );
405 };
406
407 const scrollRowIntoView = function( row ) {
408 let scrollTop = 0;
409
410 $( '.block-fields-rows .block-fields-row' ).each( function() {
411 // Add the height of all previous rows to the target scrollTop position.
412 if ( $( this ).is( row ) ) {
413 return false;
414 }
415
416 const height = $( this ).children().first().outerHeight();
417 scrollTop += height;
418 } );
419
420 $( 'body' ).animate( { scrollTop } );
421 };
422
423 /**
424 * Clone a row, used for row duplication.
425 *
426 * @param {jQuery} row The row to clone.
427 * @param {boolean} append Whether to append the cloned row, or just return it.
428 * @return {jQuery} The cloned row.
429 */
430 const cloneRow = function( row, append = true ) {
431 const uid = row.data( 'uid' ),
432 newUid = Math.floor( Math.random() * 1000000000000 ),
433 newRow = row.clone(),
434 subRows = newRow.find( '.block-fields-sub-rows' ).children();
435
436 // Remove the sub rows (we'll add them back again later).
437 if ( subRows.length > 0 ) {
438 subRows.remove();
439 }
440
441 // Replace all the UIDs.
442 newRow.attr( 'data-uid', newUid );
443 newRow.html( function( index, html ) {
444 return html.replace( new RegExp( uid, 'g' ), newUid );
445 } );
446
447 // Set the values manually. jQuery's clone method doesn't work for dynamic data.
448 row.find( '[name*="[' + uid + ']"]' ).each( function() {
449 const newRowName = $( this ).attr( 'name' ).replace( uid, newUid ),
450 newRowInput = newRow.find( '[name="' + newRowName + '"]' );
451
452 // Radio and Checkbox inputs are unique in that multiple can exist with the same name.
453 if ( $( this ).is( '[type="radio"],[type="checkbox"]' ) ) {
454 newRowInput.parent().find( '[value="' + $( this ).val() + '"]' ).prop( 'checked', $( this ).prop( 'checked' ) );
455 } else {
456 newRowInput.val( $( this ).val() );
457 }
458 } );
459
460 incrementRow( newRow );
461
462 // Insert the new row.
463 if ( append ) {
464 newRow.insertAfter( row );
465 }
466
467 subRows.each( function() {
468 $( this ).find( 'input[name^="block-fields-parent"]' ).val( newUid );
469 newRow.find( '.block-fields-sub-rows' ).append( cloneRow( $( this ), false ) );
470 } );
471
472 return newRow;
473 };
474
475 const incrementRow = function( row ) {
476 const label = row.find( '.block-fields-edit-label input' ).eq( 0 ),
477 name = row.find( '.block-fields-edit-name input' ).eq( 0 ),
478 baseName = name.val().replace( /-\d+$/, '' ),
479 baseLabel = label.val().replace( / \d+$/, '' ),
480 nameMatchRegex = new RegExp( '^' + baseName + '(-\\d+)?$' ),
481 matchedNames = $( 'input[name^="block-fields-name"]' ).filter( function() {
482 // Get all other rows that have the same base name.
483 return $( this ).val().match( nameMatchRegex );
484 } );
485
486 let numbers = [];
487
488 // Get the number of each row, then sort them.
489 matchedNames.each( function() {
490 numbers.push( $( this ).val().match( /\d*$/ )[ 0 ] );
491 } );
492
493 // Filter out duplicate numbers.
494 numbers = numbers.filter( function( value, index, self ) {
495 return self.indexOf( value ) === index;
496 } );
497
498 // Put the numbers in ascending order.
499 numbers = numbers.sort( function( a, b ) {
500 return b - a;
501 } );
502
503 // Assign the new names.
504 if ( numbers.length > 1 ) {
505 const newNumber = parseInt( numbers[ 0 ] ) + 1;
506 name.val( baseName + '-' + newNumber );
507 label.val( baseLabel + ' ' + newNumber );
508 } else if ( 1 === numbers.length ) {
509 name.val( name.val() + '-1' );
510 label.val( label.val() + ' 1' );
511 } else {
512 name.val( name.val() );
513 label.val( label.val() );
514 }
515
516 label.data( 'defaultValue', label.val() );
517 };
518
519 const slugify = function( text ) {
520 return text
521 .toLowerCase()
522 .replace( /[^\w ]+/g, '' )
523 .replace( / +/g, '-' )
524 .replace( /_+/g, '-' );
525 };
526 }( jQuery ) );
1 /* global XMLHttpRequest, FormData, ajaxurl */
2
3 document.addEventListener( 'DOMContentLoaded', function() {
4 const hiddenClass = 'bl-hidden';
5
6 // In the main migration notice, on clicking 'Not Now',
7 // make an AJAX request to store the user meta to not display the notice again.
8 // Also, remove this notice and display another.
9 document.querySelector( '#bl-notice-not-now' ).addEventListener( 'click', function() {
10 const request = new XMLHttpRequest();
11 const data = new FormData();
12 data.append( 'action', 'bl_dismiss_migration_notice' );
13 data.append( 'bl-migration-nonce-name', document.querySelector( '#bl-migration-nonce-name' ).value );
14
15 request.open( 'POST', ajaxurl, true );
16 request.send( data );
17
18 // Remove this notice.
19 const notice = document.querySelector( '#bl-migration-notice' );
20 notice.parentNode.removeChild( notice );
21
22 // Display the 'Not Now' notice.
23 document.querySelector( '#bl-not-now-notice' ).classList.remove( hiddenClass );
24 } );
25
26 // In the 'Not Now' notice, on clicking 'OK', hide the notice.
27 document.querySelector( '#bl-notice-ok' ).addEventListener( 'click', function() {
28 document.querySelector( '#bl-not-now-notice' ).classList.add( hiddenClass );
29 } );
30 } );
1 <?php return array('dependencies' => array('react', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-element', 'wp-polyfill'), 'version' => 'd880838e7a15ba0cee2603309a597941');
...\ No newline at end of file ...\ No newline at end of file
1 !function(e){var t={};function n(r){if(t[r])return t[r].exports;var c=t[r]={i:r,l:!1,exports:{}};return e[r].call(c.exports,c,c.exports,n),c.l=!0,c.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var c in e)n.d(r,c,function(t){return e[t]}.bind(null,c));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=52)}([function(e,t){!function(){e.exports=this.wp.element}()},function(e,t,n){"use strict";n.d(t,"d",(function(){return i})),n.d(t,"c",(function(){return _})),n.d(t,"a",(function(){return C})),n.d(t,"b",(function(){return N}));var r=n(24),c=n.n(r),a=n(15),o=n.n(a),l=c()(console.error);function i(e){try{for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return o.a.sprintf.apply(o.a,[e].concat(n))}catch(t){return l("sprintf error: \n\n"+t.toString()),e}}var s,u,b,p,m=n(12);s={"(":9,"!":8,"*":7,"/":7,"%":7,"+":6,"-":6,"<":5,"<=":5,">":5,">=":5,"==":4,"!=":4,"&&":3,"||":2,"?":1,"?:":1},u=["(","?"],b={")":["("],":":["?","?:"]},p=/<=|>=|==|!=|&&|\|\||\?:|\(|!|\*|\/|%|\+|-|<|>|\?|\)|:/;var f={"!":function(e){return!e},"*":function(e,t){return e*t},"/":function(e,t){return e/t},"%":function(e,t){return e%t},"+":function(e,t){return e+t},"-":function(e,t){return e-t},"<":function(e,t){return e<t},"<=":function(e,t){return e<=t},">":function(e,t){return e>t},">=":function(e,t){return e>=t},"==":function(e,t){return e===t},"!=":function(e,t){return e!==t},"&&":function(e,t){return e&&t},"||":function(e,t){return e||t},"?:":function(e,t,n){if(e)throw t;return n}};function d(e){var t=function(e){for(var t,n,r,c,a=[],o=[];t=e.match(p);){for(n=t[0],(r=e.substr(0,t.index).trim())&&a.push(r);c=o.pop();){if(b[n]){if(b[n][0]===c){n=b[n][1]||n;break}}else if(u.indexOf(c)>=0||s[c]<s[n]){o.push(c);break}a.push(c)}b[n]||o.push(n),e=e.substr(t.index+n.length)}return(e=e.trim())&&a.push(e),a.concat(o.reverse())}(e);return function(e){return function(e,t){var n,r,c,a,o,l,i=[];for(n=0;n<e.length;n++){if(o=e[n],a=f[o]){for(r=a.length,c=Array(r);r--;)c[r]=i.pop();try{l=a.apply(null,c)}catch(e){return e}}else l=t.hasOwnProperty(o)?t[o]:+o;i.push(l)}return i[0]}(t,e)}}var h={contextDelimiter:"",onMissingKey:null};function O(e,t){var n;for(n in this.data=e,this.pluralForms={},this.options={},h)this.options[n]=void 0!==t&&n in t?t[n]:h[n]}function j(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function g(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?j(Object(n),!0).forEach((function(t){Object(m.a)(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):j(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}O.prototype.getPluralForm=function(e,t){var n,r,c,a,o=this.pluralForms[e];return o||("function"!=typeof(c=(n=this.data[e][""])["Plural-Forms"]||n["plural-forms"]||n.plural_forms)&&(r=function(e){var t,n,r;for(t=e.split(";"),n=0;n<t.length;n++)if(0===(r=t[n].trim()).indexOf("plural="))return r.substr(7)}(n["Plural-Forms"]||n["plural-forms"]||n.plural_forms),a=d(r),c=function(e){return+a({n:e})}),o=this.pluralForms[e]=c),o(t)},O.prototype.dcnpgettext=function(e,t,n,r,c){var a,o,l;return a=void 0===c?0:this.getPluralForm(e,c),o=n,t&&(o=t+this.options.contextDelimiter+n),(l=this.data[e][o])&&l[a]?l[a]:(this.options.onMissingKey&&this.options.onMissingKey(n,e),0===a?n:r)};var v,k,y,w,E,x,S={"":{plural_forms:function(e){return 1===e?0:1}}},P=(y=new O({}),w=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"default";y.data[t]=g({},S,{},y.data[t],{},e),y.data[t][""]=g({},S[""],{},y.data[t][""])},E=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"default",t=arguments.length>1?arguments[1]:void 0,n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,c=arguments.length>4?arguments[4]:void 0;return y.data[e]||w(void 0,e),y.dcnpgettext(e,t,n,r,c)},x=function(e,t,n){return E(n,t,e)},v&&w(v,k),{setLocaleData:w,__:function(e,t){return E(t,void 0,e)},_x:x,_n:function(e,t,n,r){return E(r,void 0,e,t,n)},_nx:function(e,t,n,r,c){return E(c,r,e,t,n)},isRTL:function(){return"rtl"===x("ltr","text direction")}}),_=P.setLocaleData.bind(P),C=P.__.bind(P),N=(P._x.bind(P),P._n.bind(P));P._nx.bind(P),P.isRTL.bind(P)},function(e,t){!function(){e.exports=this.wp.components}()},,function(e,t,n){var r=n(47),c=n(48),a=n(28),o=n(49);e.exports=function(e,t){return r(e)||c(e,t)||a(e,t)||o()}},,function(e,t){!function(){e.exports=this.regeneratorRuntime}()},function(e,t,n){var r;!function(){"use strict";var n={}.hasOwnProperty;function c(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var a=typeof r;if("string"===a||"number"===a)e.push(r);else if(Array.isArray(r)&&r.length){var o=c.apply(null,r);o&&e.push(o)}else if("object"===a)for(var l in r)n.call(r,l)&&r[l]&&e.push(l)}}return e.join(" ")}e.exports?(c.default=c,e.exports=c):void 0===(r=function(){return c}.apply(t,[]))||(e.exports=r)}()},function(e,t){!function(){e.exports=this.React}()},function(e,t){!function(){e.exports=this.wp.apiFetch}()},function(e,t){function n(e,t,n,r,c,a,o){try{var l=e[a](o),i=l.value}catch(e){return void n(e)}l.done?t(i):Promise.resolve(i).then(r,c)}e.exports=function(e){return function(){var t=this,r=arguments;return new Promise((function(c,a){var o=e.apply(t,r);function l(e){n(o,c,a,l,i,"next",e)}function i(e){n(o,c,a,l,i,"throw",e)}l(void 0)}))}}},function(e,t){!function(){e.exports=this.wp.a11y}()},function(e,t,n){"use strict";function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}n.d(t,"a",(function(){return r}))},,,function(e,t,n){var r;!function(){"use strict";var c={not_string:/[^s]/,not_bool:/[^t]/,not_type:/[^T]/,not_primitive:/[^v]/,number:/[diefg]/,numeric_arg:/[bcdiefguxX]/,json:/[j]/,not_json:/[^j]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[+-]/};function a(e){return l(s(e),arguments)}function o(e,t){return a.apply(null,[e].concat(t||[]))}function l(e,t){var n,r,o,l,i,s,u,b,p,m=1,f=e.length,d="";for(r=0;r<f;r++)if("string"==typeof e[r])d+=e[r];else if("object"==typeof e[r]){if((l=e[r]).keys)for(n=t[m],o=0;o<l.keys.length;o++){if(null==n)throw new Error(a('[sprintf] Cannot access property "%s" of undefined value "%s"',l.keys[o],l.keys[o-1]));n=n[l.keys[o]]}else n=l.param_no?t[l.param_no]:t[m++];if(c.not_type.test(l.type)&&c.not_primitive.test(l.type)&&n instanceof Function&&(n=n()),c.numeric_arg.test(l.type)&&"number"!=typeof n&&isNaN(n))throw new TypeError(a("[sprintf] expecting number but found %T",n));switch(c.number.test(l.type)&&(b=n>=0),l.type){case"b":n=parseInt(n,10).toString(2);break;case"c":n=String.fromCharCode(parseInt(n,10));break;case"d":case"i":n=parseInt(n,10);break;case"j":n=JSON.stringify(n,null,l.width?parseInt(l.width):0);break;case"e":n=l.precision?parseFloat(n).toExponential(l.precision):parseFloat(n).toExponential();break;case"f":n=l.precision?parseFloat(n).toFixed(l.precision):parseFloat(n);break;case"g":n=l.precision?String(Number(n.toPrecision(l.precision))):parseFloat(n);break;case"o":n=(parseInt(n,10)>>>0).toString(8);break;case"s":n=String(n),n=l.precision?n.substring(0,l.precision):n;break;case"t":n=String(!!n),n=l.precision?n.substring(0,l.precision):n;break;case"T":n=Object.prototype.toString.call(n).slice(8,-1).toLowerCase(),n=l.precision?n.substring(0,l.precision):n;break;case"u":n=parseInt(n,10)>>>0;break;case"v":n=n.valueOf(),n=l.precision?n.substring(0,l.precision):n;break;case"x":n=(parseInt(n,10)>>>0).toString(16);break;case"X":n=(parseInt(n,10)>>>0).toString(16).toUpperCase()}c.json.test(l.type)?d+=n:(!c.number.test(l.type)||b&&!l.sign?p="":(p=b?"+":"-",n=n.toString().replace(c.sign,"")),s=l.pad_char?"0"===l.pad_char?"0":l.pad_char.charAt(1):" ",u=l.width-(p+n).length,i=l.width&&u>0?s.repeat(u):"",d+=l.align?p+n+i:"0"===s?p+i+n:i+p+n)}return d}var i=Object.create(null);function s(e){if(i[e])return i[e];for(var t,n=e,r=[],a=0;n;){if(null!==(t=c.text.exec(n)))r.push(t[0]);else if(null!==(t=c.modulo.exec(n)))r.push("%");else{if(null===(t=c.placeholder.exec(n)))throw new SyntaxError("[sprintf] unexpected placeholder");if(t[2]){a|=1;var o=[],l=t[2],s=[];if(null===(s=c.key.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(o.push(s[1]);""!==(l=l.substring(s[0].length));)if(null!==(s=c.key_access.exec(l)))o.push(s[1]);else{if(null===(s=c.index_access.exec(l)))throw new SyntaxError("[sprintf] failed to parse named argument key");o.push(s[1])}t[2]=o}else a|=2;if(3===a)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");r.push({placeholder:t[0],param_no:t[1],keys:t[2],sign:t[3],pad_char:t[4],align:t[5],width:t[6],precision:t[7],type:t[8]})}n=n.substring(t[0].length)}return i[e]=r}t.sprintf=a,t.vsprintf=o,"undefined"!=typeof window&&(window.sprintf=a,window.vsprintf=o,void 0===(r=function(){return{sprintf:a,vsprintf:o}}.call(t,n,t,e))||(e.exports=r))}()},,,,,,,,,function(e,t,n){e.exports=function(e,t){var n,r,c=0;function a(){var a,o,l=n,i=arguments.length;e:for(;l;){if(l.args.length===arguments.length){for(o=0;o<i;o++)if(l.args[o]!==arguments[o]){l=l.next;continue e}return l!==n&&(l===r&&(r=l.prev),l.prev.next=l.next,l.next&&(l.next.prev=l.prev),l.next=n,l.prev=null,n.prev=l,n=l),l.val}l=l.next}for(a=new Array(i),o=0;o<i;o++)a[o]=arguments[o];return l={args:a,val:e.apply(null,a)},n?(n.prev=l,l.next=n):r=l,c===t.maxSize?(r=r.prev).next=null:c++,n=l,l.val}return t=t||{},a.clear=function(){n=null,r=null,c=0},a}},,function(e,t){e.exports=function(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}},,function(e,t,n){var r=n(26);e.exports=function(e,t){if(e){if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(n):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(e,t):void 0}}},,,,,,,,,,,function(e,t){function n(){return e.exports=n=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},n.apply(this,arguments)}e.exports=n},,,,,,,,function(e,t){e.exports=function(e){if(Array.isArray(e))return e}},function(e,t){e.exports=function(e,t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e)){var n=[],r=!0,c=!1,a=void 0;try{for(var o,l=e[Symbol.iterator]();!(r=(o=l.next()).done)&&(n.push(o.value),!t||n.length!==t);r=!0);}catch(e){c=!0,a=e}finally{try{r||null==l.return||l.return()}finally{if(c)throw a}}return n}}},function(e,t){e.exports=function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}},,,function(e,t,n){"use strict";n.r(t);var r=n(0);var c,a=n(39),o=n.n(a),l=n(4),i=n.n(l),s=(n(8),n(1)),u=function(e){var t=e.isStepActive,n=e.isStepComplete,c=e.goToNext,a=e.goToPrevious,o=e.stepIndex,l=1===o;return Object(r.createElement)(C,{isActive:t,isComplete:n},Object(r.createElement)(A,{index:o,isComplete:n}),Object(r.createElement)(N,{heading:Object(s.a)("Back Up Your Site","block-lab"),isStepActive:t},Object(r.createElement)("p",null,Object(s.a)("Migrating from Block Lab to Genesis Custom Blocks is a one-way action. It can’t be undone. Please back up your site before you begin, just in case you need to roll it back.","block-lab")),Object(r.createElement)(T,null,!l&&Object(r.createElement)(x,{onClick:a}),Object(r.createElement)(E,{checkboxLabel:Object(s.a)("I have backed up my site.","block-lab"),onClick:c,stepIndex:o}))))},b=n(6),p=n.n(b),m=n(10),f=n.n(m),d=n(9),h=n.n(d),O=n(2),j=function(e){var t=e.goToNext,n=e.isStepActive,c=e.isStepComplete,a=e.stepIndex,o=blockLabMigration.genesisProKey,l=Object(r.useState)(!1),u=i()(l,2),b=u[0],m=u[1],d=Object(r.useState)(!1),j=i()(d,2),g=j[0],v=j[1],k=Object(r.useState)(o||""),y=i()(k,2),w=y[0],x=y[1],S=Object(r.useState)(""),P=i()(S,2),_=P[0],I=P[1],L=!!o||g,M=function(){var e=f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return m(!0),e.next=3,h()({path:"/block-lab/update-subscription-key",method:"POST",data:{subscriptionKey:w}}).then((function(){I(Object(s.a)("Thanks! Your key is valid, and has been saved.","block-lab")),v(!0)})).catch((function(e){var t=e.message?e.message:Object(s.a)("There was an error validating the key.","block-lab");I(t),v(!1),x("")}));case 3:m(!1);case 4:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return Object(r.createElement)(C,{isActive:n,isComplete:c},Object(r.createElement)(A,{index:a,isComplete:c}),Object(r.createElement)(N,{heading:Object(s.a)("Get Genesis Pro","block-lab"),isStepActive:n},Object(r.createElement)("p",null),Object(r.createElement)("div",{className:"pro-box"},Object(r.createElement)("h3",null,Object(s.a)("Migrating from Block Lab Pro","block-lab")),Object(r.createElement)("p",null,Object(s.a)("It looks like you’re a Block Lab Pro customer! Thank you so much for your support. We wouldn't be here without you! Rest assured, your Block Lab Pro license will continue to receive security updates and support for the duration of its term.","block-lab"),"*"),Object(r.createElement)("div",{className:"pro-box-tiles"},Object(r.createElement)("div",{className:"pro-box-tile"},Object(r.createElement)("div",{className:"pro-box-tile__icon"},Object(r.createElement)("svg",{width:"100%",height:"100%",viewBox:"0 0 91 75",version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",xmlSpace:"preserve",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:2}},Object(r.createElement)("g",{id:"bl_genesis_icon"},Object(r.createElement)("path",{d:"M43.31,39.843c0.288,0.81 0.687,1.495 1.196,2.053c0.508,0.558 1.111,0.984 1.809,1.276c0.698,0.293 1.46,0.439 2.288,0.439c0.631,0 1.189,-0.055 1.675,-0.163c0.487,-0.108 0.945,-0.252 1.377,-0.432l0,-2.984l-1.944,0c-0.288,0 -0.513,-0.076 -0.675,-0.229c-0.162,-0.153 -0.243,-0.346 -0.243,-0.581l0,-2.512l6.994,0l0,8.306c-0.504,0.369 -1.028,0.686 -1.572,0.95c-0.545,0.267 -1.126,0.485 -1.742,0.656c-0.617,0.171 -1.275,0.297 -1.972,0.379c-0.698,0.081 -1.447,0.12 -2.249,0.12c-1.44,0 -2.772,-0.253 -3.997,-0.762c-1.224,-0.509 -2.284,-1.211 -3.179,-2.107c-0.896,-0.895 -1.599,-1.958 -2.107,-3.186c-0.509,-1.229 -0.763,-2.564 -0.763,-4.005c0,-1.466 0.243,-2.816 0.729,-4.045c0.486,-1.228 1.182,-2.288 2.087,-3.179c0.904,-0.892 1.998,-1.585 3.281,-2.079c1.283,-0.496 2.716,-0.744 4.3,-0.744c0.82,0 1.589,0.069 2.31,0.203c0.72,0.135 1.384,0.32 1.992,0.554c0.608,0.234 1.163,0.513 1.668,0.837c0.503,0.324 0.953,0.675 1.35,1.052l-1.324,2.013c-0.126,0.189 -0.276,0.338 -0.452,0.446c-0.175,0.108 -0.367,0.162 -0.574,0.162c-0.27,0 -0.549,-0.09 -0.837,-0.27c-0.36,-0.216 -0.7,-0.403 -1.019,-0.561c-0.32,-0.158 -0.647,-0.285 -0.98,-0.384c-0.333,-0.099 -0.684,-0.171 -1.053,-0.216c-0.369,-0.046 -0.783,-0.068 -1.243,-0.068c-0.855,0 -1.625,0.151 -2.308,0.453c-0.685,0.3 -1.267,0.727 -1.749,1.275c-0.483,0.55 -0.853,1.209 -1.115,1.978c-0.261,0.77 -0.391,1.628 -0.391,2.573c0,1.045 0.144,1.972 0.432,2.782Zm42.005,0.944c-0.658,1.855 -1.461,3.536 -2.437,4.951c0.979,-3.651 1.411,-7.51 1.208,-11.481c-1.438,-19.116 -17.393,-34.183 -36.878,-34.183c-12.247,0 -23.093,5.958 -29.826,15.127c-0.386,0.552 -0.769,1.11 -1.129,1.69c-2.23,3.589 -3.761,7.411 -4.65,11.311c-0.128,-3.334 0.369,-6.946 1.446,-10.724c-3.39,0.467 -6.246,1.301 -8.413,2.492c0.001,0 0.002,0.001 0.003,0.001c-1.918,1.056 -3.3,2.391 -4.03,4.003c-0.002,0.005 -0.005,0.01 -0.008,0.015c-0.103,0.231 -0.194,0.468 -0.271,0.71c-0.092,0.292 -0.163,0.59 -0.214,0.89c-0.015,0.089 -0.017,0.182 -0.029,0.272c-0.027,0.214 -0.055,0.427 -0.063,0.646c-0.002,0.102 0.007,0.208 0.008,0.311c0.001,0.21 0.001,0.419 0.021,0.632c0.01,0.104 0.032,0.209 0.045,0.313c0.029,0.217 0.057,0.434 0.103,0.655c0.02,0.099 0.053,0.201 0.077,0.301c0.056,0.228 0.113,0.457 0.187,0.688c0.03,0.093 0.069,0.188 0.102,0.281c0.085,0.242 0.173,0.485 0.277,0.73c0.036,0.084 0.08,0.17 0.118,0.254c0.117,0.258 0.237,0.515 0.375,0.775c0.039,0.074 0.084,0.15 0.125,0.224c0.149,0.273 0.306,0.547 0.478,0.821c0.04,0.064 0.084,0.128 0.124,0.191c0.186,0.289 0.379,0.578 0.588,0.87c0.038,0.052 0.079,0.105 0.117,0.157c0.223,0.305 0.455,0.609 0.702,0.914c0.034,0.042 0.069,0.082 0.103,0.124c0.26,0.318 0.532,0.637 0.82,0.957c0.027,0.03 0.056,0.061 0.083,0.091c0.301,0.332 0.613,0.664 0.941,0.996c0.02,0.021 0.04,0.041 0.06,0.06c0.339,0.344 0.692,0.687 1.061,1.031c0.012,0.011 0.025,0.023 0.037,0.034c0.378,0.352 0.77,0.704 1.178,1.057c0.004,0.005 0.01,0.008 0.016,0.014c0.414,0.358 0.843,0.715 1.286,1.073c0.001,0.001 0.001,0.002 0.002,0.003c0.447,0.359 0.909,0.717 1.384,1.076c7.15,5.394 17.488,10.59 29.489,14.386c2.195,0.695 4.369,1.315 6.515,1.87c-13.418,-1.826 -24.644,-4.124 -34.98,-10.802c4.203,15.814 18.606,27.468 35.742,27.468c12.033,0 22.719,-5.747 29.475,-14.642c6.999,-0.911 11.792,-3.35 13.03,-7.263c1.059,-3.348 -0.634,-7.309 -4.398,-11.37Z"})))),Object(r.createElement)("h4",null,Object(s.a)("12 months free","block-lab")),Object(r.createElement)("p",null,Object(s.a)("As part of the migration to Genesis Custom Blocks, we’d like to set you up with a free year of Genesis Pro. This new Genesis subscription will give you access to all the features you’ve loved in Block Lab Pro.","block-lab"))),Object(r.createElement)("div",{className:"pro-box-tile"},Object(r.createElement)("div",{className:"pro-box-tile__icon"},Object(r.createElement)("svg",{width:"100%",height:"100%",viewBox:"0 0 101 50",version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",xmlSpace:"preserve",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:2}},Object(r.createElement)("g",{id:"bl_infinity_icon"},Object(r.createElement)("path",{d:"M50.017,16.489l8.579,-8.58c9.47,-9.47 24.848,-9.47 34.318,0c9.47,9.47 9.47,24.848 0,34.318c-9.47,9.47 -24.848,9.47 -34.318,0l-8.579,-8.58l-8.58,8.58c-9.47,9.47 -24.847,9.47 -34.318,0c-9.47,-9.47 -9.47,-24.848 0,-34.318c9.471,-9.47 24.848,-9.47 34.318,0l8.58,8.58Zm-17.159,0l8.579,8.579l-8.579,8.579c-4.735,4.736 -12.424,4.736 -17.159,0c-4.735,-4.735 -4.735,-12.423 0,-17.158c4.735,-4.736 12.424,-4.736 17.159,0Zm34.318,17.158l-8.58,-8.579l8.58,-8.579c4.735,-4.736 12.423,-4.736 17.158,0c4.736,4.735 4.736,12.423 0,17.158c-4.735,4.736 -12.423,4.736 -17.158,0Z"})))),Object(r.createElement)("h4",null,Object(s.a)("Unlimited Sites","block-lab")),Object(r.createElement)("p",null,Object(s.a)("All Genesis Pro subscriptions are valid on an unlimited number of installs, and come with additional access to the Genesis Framework, Genesis Themes, and Genesis Page Builder.","block-lab"))),Object(r.createElement)("div",{className:"pro-box-tile"},Object(r.createElement)("div",{className:"pro-box-tile__icon"},Object(r.createElement)("svg",{width:"100%",height:"100%",viewBox:"0 0 101 52",version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",xmlSpace:"preserve",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:2}},Object(r.createElement)("g",{id:"bl_key_icon"},Object(r.createElement)("path",{d:"M51.602,31.449c-2.477,11.61 -12.8,20.327 -25.143,20.327c-14.188,0 -25.708,-11.519 -25.708,-25.708c0,-14.189 11.52,-25.708 25.708,-25.708c11.678,0 21.547,7.802 24.675,18.474l49.617,0l0,12.615l-8.995,0l0,15.615l-12.616,0l0,-15.615l-27.538,0Zm-25.143,-15.898c5.805,0 10.517,4.713 10.517,10.517c0,5.804 -4.712,10.517 -10.517,10.517c-5.804,0 -10.517,-4.713 -10.517,-10.517c0,-5.804 4.713,-10.517 10.517,-10.517Z"})))),Object(r.createElement)("h4",null,Object(s.a)("New Subscription Key","block-lab")),Object(r.createElement)("p",null,Object(s.a)("To migrate and maintain your Block Lab Pro feature set, you will need a Genesis Pro subscription key. Step number 1 below will walk you through setting up your account.","block-lab")))),Object(r.createElement)("p",null,Object(s.a)("* Block Lab Pro licenses will not be renewing and Pro updates / support will end when your current license expires.","block-lab"))),Object(r.createElement)("p",null,Object(s.a)("Since you're a Block Lab Pro customer, we've already emailed you regarding setting up a WP Engine account with a free Pro subscription.","block-lab")),Object(r.createElement)("p",null,Object(s.a)("To migrate and maintain your Block Lab Pro feature set with Genesis Custom Blocks, you will need your Genesis Pro subscription key.","block-lab")),Object(r.createElement)("ul",null,Object(r.createElement)("li",null,Object(s.a)("Already have got it? Enter the subscription key below to continue migrating.","block-lab")),Object(r.createElement)("li",null,Object(s.a)("Don’t have one yet? Please opt-in using the link below.","block-lab"))),!g&&Object(r.createElement)(r.Fragment,null,Object(r.createElement)("div",{className:"get-genesis-pro"},Object(r.createElement)("a",{href:"https://forms.gle/26u7NDRUp2A9i2aF8",className:"btn",target:"_blank",rel:"noopener noreferrer"},Object(s.a)("Opt-in for Genesis Pro","block-lab")),Object(r.createElement)("span",null,Object(s.a)("(This may take up to 3 working days)","block-lab"))),Object(r.createElement)("p",null,Object(s.a)("then","block-lab")),Object(r.createElement)("div",{className:"genesis-pro-form"},Object(r.createElement)("input",{type:"text",placeholder:Object(s.a)("Paste your Genesis Pro subscription key","block-lab"),value:w,onChange:function(e){x(e.target.value)}}),Object(r.createElement)("button",{onClick:M,disabled:b},Object(s.a)("Save","block-lab")),b&&Object(r.createElement)(O.Spinner,null))),Object(r.createElement)("p",{className:"pro-submission-message"},_),!g&&Object(r.createElement)("p",{className:"help-text"},Object(s.a)("Want to migrate but not set up Genesis Pro just now?","block-lab")," ",Object(r.createElement)("a",{href:"https://getblocklab.com/migrating-to-genesis-custom-blocks/",target:"_blank",rel:"noopener noreferrer","aria-label":Object(s.a)("More information about migrating but not setting up Genesis Pro","genesis-custom-blocks")},Object(s.a)("Read here for what that means.","block-lab"))),Object(r.createElement)(T,null,Object(r.createElement)(E,{checkboxLabel:L?null:Object(s.a)("Migrate without Genesis Pro.","block-lab"),onClick:t,stepIndex:a}))))},g=n(11),v=function(e){var t=e.goToNext,n=e.isStepActive,c=e.isStepComplete,a=e.stepIndex,o=Object(r.useState)(!1),l=i()(o,2),u=l[0],b=l[1],m=Object(r.useState)(!1),d=i()(m,2),j=d[0],v=d[1],k=Object(r.useState)(""),y=i()(k,2),w=y[0],x=y[1],S=Object(r.useState)(!1),P=i()(S,2),_=P[0],I=P[1],L=function(){var e=f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return Object(g.speak)(Object(s.a)("The installation is now in progress","block-lab")),b(!0),v(!1),x(""),e.next=6,h()({path:"/block-lab/install-activate-gcb",method:"POST"}).then((function(){Object(g.speak)(Object(s.a)("Success! Genesis Custom Blocks is installed and activated.","block-lab")),I(!0)})).catch((function(e){Object(g.speak)(Object(s.a)("The installation and activation failed with the following error:","block-lab")),e.hasOwnProperty("message")&&(Object(g.speak)(e.message),x(e.message)),I(!1),v(!0)}));case 6:b(!1);case 7:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return Object(r.createElement)(C,{isActive:n,isComplete:c},Object(r.createElement)(A,{index:a,isComplete:c}),Object(r.createElement)(N,{heading:Object(s.a)("Install And Activate Genesis Custom Blocks","block-lab"),isStepActive:n},u&&Object(r.createElement)(r.Fragment,null,Object(r.createElement)(O.Spinner,null),Object(r.createElement)("p",null,Object(s.a)("Installing and activating Genesis Custom Blocks…","block-lab"))),!!w&&Object(r.createElement)("div",{className:"bl-migration__error"},Object(r.createElement)("p",null,Object(s.a)("The following error ocurred:","block-lab")),Object(r.createElement)("p",null,w)),!u&&!_&&Object(r.createElement)("button",{className:"btn",onClick:L},j?Object(s.a)("Try Again","block-lab"):Object(s.a)("Install and activate","block-lab")),_&&Object(r.createElement)(r.Fragment,null,Object(r.createElement)("p",null,Object(s.a)("Success! Genesis Custom Blocks is installed and activated.","block-lab")),Object(r.createElement)(T,null,Object(r.createElement)(E,{onClick:t,stepIndex:a})))))},k=function(e){var t=e.isStepActive,n=e.isStepComplete,c=e.stepIndex,a=Object(r.useState)(0),o=i()(a,2),l=o[0],u=o[1],b=Object(r.useState)(!1),m=i()(b,2),d=m[0],j=m[1],v=Object(r.useState)(!1),k=i()(v,2),y=k[0],w=k[1],E=Object(r.useState)(""),x=i()(E,2),S=x[0],P=x[1],_=Object(r.useState)(!1),I=i()(_,2),L=I[0],M=I[1],B=[Object(s.a)("Migrating your blocks…","block-lab"),Object(s.a)("Migrating your post content…","block-lab")],F=function(){var e=f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,h()({path:"/block-lab/migrate-post-type",method:"POST"}).then(f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return u(1),e.next=3,G();case 3:case"end":return e.stop()}}),e)})))).catch((function(e){e.hasOwnProperty("message")&&P(e.message),Object(g.speak)(Object(s.a)("The migration failed in the CPT migration","block-lab")),w(!0),j(!1)}));case 2:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),G=function(){var e=f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return"invalid_json",e.next=3,h()({path:"/block-lab/migrate-post-content",method:"POST"}).then((function(){Object(g.speak)(Object(s.a)("The migration was successful!","block-lab")),M(!0)})).catch(function(){var e=f()(p.a.mark((function e(t){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:if(!t.hasOwnProperty("code")||"invalid_json"!==t.code){e.next=6;break}return e.next=3,G();case 3:return e.abrupt("return");case 6:t.hasOwnProperty("message")&&P(t.message);case 7:Object(g.speak)(Object(s.a)("The migration failed in the post content migration","block-lab")),w(!0);case 9:case"end":return e.stop()}}),e)})));return function(t){return e.apply(this,arguments)}}());case 3:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}(),R=function(){var e=f()(p.a.mark((function e(){return p.a.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return Object(g.speak)(Object(s.a)("The migration is now in progress","block-lab")),P(""),j(!0),e.next=5,F();case 5:j(!1);case 6:case"end":return e.stop()}}),e)})));return function(){return e.apply(this,arguments)}}();return Object(r.createElement)(C,{isActive:t,isComplete:n},Object(r.createElement)(A,{index:c,isComplete:n}),Object(r.createElement)(N,{heading:Object(s.a)("Migrate Your Blocks","block-lab"),isStepActive:t},!L&&Object(r.createElement)("p",null,Object(s.a)("Okay! Everything is ready. Let's do this. While the migration is underway, don't leave this page.","block-lab")),!!S&&Object(r.createElement)("div",{className:"bl-migration__error"},Object(r.createElement)("p",null,Object(s.a)("The following error ocurred:","block-lab")),Object(r.createElement)("p",null,S)),d&&Object(r.createElement)(r.Fragment,null,Object(r.createElement)(O.Spinner,null),Object(r.createElement)("p",null,B[l])),!d&&!L&&Object(r.createElement)("button",{className:"btn",onClick:R},y?Object(s.a)("Try Again","block-lab"):Object(s.a)("Migrate Now","block-lab")),L&&Object(r.createElement)(r.Fragment,null,Object(r.createElement)("p",null,Object(r.createElement)("span",{role:"img","aria-label":Object(s.a)("party emoji","block-lab")},"🎉")," ",Object(s.a)("The migration completed successfully! Time to say goodbye to Block Lab (it’s been fun!) and step into the FUTURE","block-lab")," ",Object(r.createElement)("span",{className:"message-future"},Object(s.a)("FUTURE","block-lab"))," ",Object(r.createElement)("sub",null,Object(s.a)("FUTURE","block-lab")),"."),Object(r.createElement)(T,null,Object(r.createElement)("a",{href:blockLabMigration.gcbUrl,className:"btn"},Object(s.a)("Go To Genesis Custom Blocks","block-lab"))))))},y=function(e){var t=e.isStepActive,n=e.isStepComplete,c=e.stepIndex,a=e.goToNext,o=e.goToPrevious;return Object(r.createElement)(C,{isActive:t,isComplete:n},Object(r.createElement)(A,{index:c,isComplete:n}),Object(r.createElement)(N,{heading:Object(s.a)("Update Hooks & API","block-lab"),isStepActive:t},Object(r.createElement)("p",null,Object(s.a)("In most cases, you won’t have to worry about this step. However, there are some instances that will require manual edits to your custom block related files. These are:","block-lab")),Object(r.createElement)("ul",{className:"list-disc list-inside mt-2"},Object(r.createElement)("li",null,Object(r.createElement)("b",null,Object(s.a)("Hooks","block-lab"))," - ",Object(s.a)("The Block Lab hook names have changed. If you’ve extended Block Lab with custom functionality using these, you’ll need to make some small changes.","block-lab")," ",Object(r.createElement)("a",{href:"https://developer.wpengine.com/genesis-custom-blocks/block-lab-hook-compatibility/",target:"_blank",rel:"noopener noreferrer","aria-label":Object(s.a)("More details on the hooks","genesis-custom-blocks")},Object(s.a)("More details here.","block-lab"))),Object(r.createElement)("li",null,Object(r.createElement)("b",null,Object(s.a)("API","block-lab"))," - ",Object(s.a)("If you use Block Lab’s PHP API or JSON API to register and configure your custom blocks, you’ll need to make some small changes.","block-lab")," ",Object(r.createElement)("a",{href:"https://developer.wpengine.com/genesis-custom-blocks/block-lab-php-api-compatibility/",target:"_blank",rel:"noopener noreferrer","aria-label":Object(s.a)("More details on the PHP API","genesis-custom-blocks")},Object(s.a)("More details here.","block-lab")))),Object(r.createElement)(T,null,Object(r.createElement)(x,{onClick:o}),Object(r.createElement)(E,{checkboxLabel:Object(s.a)("I'm all okay on the hooks and API front.","block-lab"),onClick:a,stepIndex:c}))))},w=function(){var e=Object(r.useState)(1),t=i()(e,2),n=t[0],c=t[1],a=function(){c(n-1)},l=function(){c(n+1)},s=[u,y,v,k];return blockLabMigration.isPro&&s.unshift(j),Object(r.createElement)("div",{className:"bl-migration__content-wrapper"},Object(r.createElement)("div",{className:"container bl-migration__content-container"},Object(r.createElement)(S,null),s.map((function(e,t){var c=1+t,i=n===c,s=n>c;return Object(r.createElement)(e,o()({key:"bl-migration-step-".concat(c)},{currentStepIndex:n,goToNext:l,goToPrevious:a,isStepActive:i,isStepComplete:s,stepIndex:c}))}))))},E=function(e){var t=e.onClick,n=e.checkboxLabel,c=e.stepIndex,a=Object(r.useState)(!1),o=i()(a,2),l=o[0],u=o[1];if(!n)return Object(r.createElement)("button",{className:"btn",onClick:t},Object(s.a)("Next Step","block-lab"));var b="bl-migration-check-".concat(c);return Object(r.createElement)(r.Fragment,null,Object(r.createElement)("form",null,Object(r.createElement)("input",{id:b,type:"checkbox",onClick:function(){u(!l)}}),Object(r.createElement)("label",{htmlFor:b,className:"ml-2 font-medium"},n)),Object(r.createElement)("button",{className:"btn",onClick:t,disabled:!l},Object(s.a)("Next Step","block-lab")))},x=function(e){var t=e.onClick;return Object(r.createElement)("button",{className:"btn btn-secondary",onClick:t},Object(s.a)("Previous","block-lab"))},S=function(){return Object(r.createElement)(r.Fragment,null,Object(r.createElement)("div",null,Object(r.createElement)("h1",null,Object(s.a)("Migrating to Genesis Custom Blocks","block-lab")),Object(r.createElement)("p",null,Object(s.a)("In April, the Block Lab team joined the Genesis team at WP Engine. With our full-time focus, we’re very excited about the future of custom block tooling in WordPress. You can read more about that moment in this","block-lab")," ",Object(r.createElement)("a",{target:"_blank",rel:"noopener noreferrer",className:"text-purple-600 underline hover:text-purple-700",href:"https://getblocklab.com/the-block-lab-team-are-joining-wp-engine/"},Object(s.a)("announcement post","block-lab"),".")),Object(r.createElement)("p",null,Object(s.a)("As part of this move, we have been working on a new plugin that is based on what we developed at Block Lab.","block-lab")," ",Object(s.a)("Genesis Custom Blocks is now the home of all our custom block efforts and what we have planned is very very cool!","block-lab")," ",Object(s.a)("Version 1.0 of this plugin is now released and has full feature parity with Block Lab.","block-lab")),Object(r.createElement)("p",null,Object(s.a)("To continue receiving the best of what our team is building, we encourage you to migrate over. Our migration tool makes this nice and easy, and for the majority of use cases, completely automated.","block-lab")),Object(r.createElement)("div",{className:"dev-notice"},Object(r.createElement)("svg",{fill:"currentColor",viewBox:"0 0 20 20"},Object(r.createElement)("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z",clipRule:"evenodd"})),Object(r.createElement)("span",null,Object(s.a)("Need to let the developer for this site know about this? Send them this link.","block-lab")),Object(r.createElement)("a",{href:"https://getblocklab.com/migrating-to-genesis-custom-blocks/",target:"_blank",rel:"noopener noreferrer",className:"btn"},Object(r.createElement)("span",null,Object(s.a)("Developer Notice","block-lab")),Object(r.createElement)("svg",{fill:"currentColor",viewBox:"0 0 20 20"},Object(r.createElement)("path",{d:"M11 3a1 1 0 100 2h3.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"}),Object(r.createElement)("path",{d:"M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"}))))),Object(r.createElement)("h2",null,Object(s.a)("Let's Migrate","block-lab")))},P=n(7),_=n.n(P),C=function(e){var t=e.isActive,n=e.isComplete,c=e.children;return Object(r.createElement)("div",{className:_()("step",{"step--active":t,"step--complete":n})},c)},N=function(e){var t=e.children,n=e.heading,c=e.isStepActive;return Object(r.createElement)("div",{className:"step-content"},Object(r.createElement)("h3",null,n),c&&t)},T=function(e){var t=e.children;return Object(r.createElement)("div",{className:"step-footer"},t)},A=function(e){var t=e.index,n=e.isComplete,c="bl-migration-icon-".concat(t),a=Object(r.createElement)("svg",{fill:"currentColor",viewBox:"0 0 20 20","aria-labelledby":c},Object(r.createElement)("path",{fillRule:"evenodd",d:"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z",clipRule:"evenodd"}),Object(r.createElement)("title",{id:c},Object(s.a)("Step completed","block-lab")));return Object(r.createElement)("div",{className:"step-icon"},n?a:t)};c=function(){Object(r.render)(Object(r.createElement)(w,null),document.querySelector(".bl-migration__content"))},"complete"!==document.readyState&&"interactive"!==document.readyState?document.addEventListener("DOMContentLoaded",c):c()}]);
...\ No newline at end of file ...\ No newline at end of file
1 <?php return array('dependencies' => array('lodash', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-deprecated', 'wp-dom', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-keycodes'), 'version' => '71acd0fd9d80bf1688a4e97931170ca5');
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
1 /* global blockLabMigration */
2 // @ts-check
3
4 /**
5 * External dependencies
6 */
7 import * as React from 'react';
8
9 /**
10 * WordPress dependencies
11 */
12 import { useState } from '@wordpress/element';
13
14 /**
15 * Internal dependencies
16 */
17 import { Intro } from './';
18 import { BackUpSite, GetGenesisPro, InstallActivateGcb, MigrateBlocks, UpdateHooks } from './steps';
19 import { FIRST_STEP_NUMBER } from '../constants';
20
21 /**
22 * The migration admin page.
23 *
24 * @return {React.ReactElement} The component for the admin page.
25 */
26 const App = () => {
27 const [ currentStepIndex, setStepIndex ] = useState( FIRST_STEP_NUMBER );
28
29 /**
30 * Sets the step index to the previous step.
31 */
32 const goToPrevious = () => {
33 setStepIndex( currentStepIndex - 1 );
34 };
35
36 /**
37 * Sets the step index to the next step.
38 */
39 const goToNext = () => {
40 setStepIndex( currentStepIndex + 1 );
41 };
42
43 const steps = [
44 BackUpSite,
45 UpdateHooks,
46 InstallActivateGcb,
47 MigrateBlocks,
48 ];
49
50 // Conditionally add the step to get Genesis Pro.
51 // @ts-ignore
52 if ( blockLabMigration.isPro ) {
53 steps.unshift( GetGenesisPro );
54 }
55
56 return (
57 <div className="bl-migration__content-wrapper">
58 <div className="container bl-migration__content-container">
59 <Intro />
60 {
61 steps.map( ( MigrationStep, index ) => {
62 const stepIndex = FIRST_STEP_NUMBER + index;
63 const isStepActive = currentStepIndex === stepIndex;
64 const isStepComplete = currentStepIndex > stepIndex;
65
66 return (
67 <MigrationStep
68 key={ `bl-migration-step-${ stepIndex }` }
69 { ...{ currentStepIndex, goToNext, goToPrevious, isStepActive, isStepComplete, stepIndex } }
70 />
71 );
72 } )
73 }
74 </div>
75 </div>
76 );
77 };
78
79 export default App;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12 import { useState } from '@wordpress/element';
13
14 /**
15 * @typedef ButtonNextProps
16 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
17 * @property {string} [checkboxLabel] The label of the checkbox, if there should be one.
18 * @property {number} stepIndex The index of this button's step.
19 */
20
21 /**
22 * The next button.
23 *
24 * @param {ButtonNextProps} props The component props.
25 * @return {React.ReactElement} The component for the step content.
26 */
27 const ButtonNext = ( { onClick, checkboxLabel, stepIndex } ) => {
28 const [ isCheckboxChecked, setCheckboxChecked ] = useState( false );
29
30 // If there's no label for the 'confirmation' checkbox, return a simple button.
31 if ( ! checkboxLabel ) {
32 return <button className="btn" onClick={ onClick }>{ __( 'Next Step', 'block-lab' ) }</button>;
33 }
34
35 const inputId = `bl-migration-check-${ stepIndex }`;
36 return (
37 <>
38 <form>
39 <input
40 id={ inputId }
41 type="checkbox"
42 onClick={ () => {
43 setCheckboxChecked( ! isCheckboxChecked );
44 } }
45 />
46 <label htmlFor={ inputId } className="ml-2 font-medium">{ checkboxLabel }</label>
47 </form>
48 <button
49 className="btn"
50 onClick={ onClick }
51 disabled={ ! isCheckboxChecked }
52 >
53 { __( 'Next Step', 'block-lab' ) }
54 </button>
55 </>
56
57 );
58 };
59
60 export default ButtonNext;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12
13 /**
14 * @typedef ButtonPreviousProps
15 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
16 */
17
18 /**
19 * The previous button.
20 *
21 * @param {ButtonPreviousProps} props The component props.
22 * @return {React.ReactElement} The component for the step content.
23 */
24 const ButtonPrevious = ( { onClick } ) => {
25 return (
26 <button className="btn btn-secondary" onClick={ onClick }>
27 { __( 'Previous', 'block-lab' ) }
28 </button>
29 );
30 };
31
32 export default ButtonPrevious;
1 export { default as App } from './app';
2 export { default as ButtonNext } from './button-next';
3 export { default as ButtonPrevious } from './button-previous';
4 export { default as Intro } from './intro';
5 export { default as Step } from './step';
6 export { default as StepContent } from './step-content';
7 export { default as StepFooter } from './step-footer';
8 export { default as StepIcon } from './step-icon';
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12
13 /**
14 * The introduction to the migration.
15 *
16 * @return {React.ReactElement} The introduction to the migration.
17 */
18 const Intro = () => {
19 const developerNoticeUrl = 'https://getblocklab.com/migrating-to-genesis-custom-blocks/';
20 const announcementUrl = 'https://getblocklab.com/the-block-lab-team-are-joining-wp-engine/';
21
22 return (
23 <>
24 <div>
25 <h1>{ __( 'Migrating to Genesis Custom Blocks', 'block-lab' ) }</h1>
26 <p>{ __( 'In April, the Block Lab team joined the Genesis team at WP Engine. With our full-time focus, we’re very excited about the future of custom block tooling in WordPress. You can read more about that moment in this', 'block-lab' ) } <a target="_blank" rel="noopener noreferrer" className="text-purple-600 underline hover:text-purple-700" href={ announcementUrl }>{ __( 'announcement post', 'block-lab' ) }.</a></p>
27 <p>
28 { __( 'As part of this move, we have been working on a new plugin that is based on what we developed at Block Lab.', 'block-lab' ) }
29 &nbsp;
30 { __( 'Genesis Custom Blocks is now the home of all our custom block efforts and what we have planned is very very cool!', 'block-lab' ) }
31 &nbsp;
32 { __( 'Version 1.0 of this plugin is now released and has full feature parity with Block Lab.', 'block-lab' ) }
33 </p>
34 <p>{ __( 'To continue receiving the best of what our team is building, we encourage you to migrate over. Our migration tool makes this nice and easy, and for the majority of use cases, completely automated.', 'block-lab' ) }</p>
35 <div className="dev-notice">
36 <svg fill="currentColor" viewBox="0 0 20 20">
37 <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd"></path>
38 </svg>
39 <span>{ __( 'Need to let the developer for this site know about this? Send them this link.', 'block-lab' ) }</span>
40 <a href={ developerNoticeUrl } target="_blank" rel="noopener noreferrer" className="btn">
41 <span>{ __( 'Developer Notice', 'block-lab' ) }</span>
42 <svg fill="currentColor" viewBox="0 0 20 20">
43 <path d="M11 3a1 1 0 100 2h3.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path>
44 <path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path>
45 </svg>
46 </a>
47 </div>
48 </div>
49 <h2>{ __( "Let's Migrate", 'block-lab' ) }</h2>
50 </>
51 );
52 };
53
54 export default Intro;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * @typedef StepContentProps
10 * @property {React.ReactNode} children The component's children.
11 * @property {string} heading The step heading.
12 * @property {boolean} isStepActive Whether this step is active.
13 */
14
15 /**
16 * The content of the step.
17 *
18 * @param {StepContentProps} props The component props.
19 * @return {React.ReactElement} The component for the step content.
20 */
21 const StepContent = ( { children, heading, isStepActive } ) => {
22 return (
23 <div className="step-content">
24 <h3>{ heading }</h3>
25 { isStepActive && children }
26 </div>
27 );
28 };
29
30 export default StepContent;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * @typedef StepFooterProps
10 * @property {React.ReactNode} children The component's children.
11 */
12
13 /**
14 * The footer of the step.
15 *
16 * @param {StepFooterProps} props The component props.
17 * @return {React.ReactElement} The component for the step content.
18 */
19 const StepFooter = ( { children } ) => {
20 return (
21 <div className="step-footer">
22 { children }
23 </div>
24 );
25 };
26
27 export default StepFooter;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12
13 /**
14 * @typedef StepIconProps
15 * @property {number} index The index of this icon's step.
16 * @property {boolean} isComplete Whether this icon's step is active.
17 */
18
19 /**
20 * The icon of the step number.
21 *
22 * @param {StepIconProps} props The component props.
23 * @return {React.ReactElement} props The icon component.
24 */
25 const StepIcon = ( { index, isComplete } ) => {
26 const titleId = `bl-migration-icon-${ index }`;
27 const svg = (
28 <svg fill="currentColor" viewBox="0 0 20 20" aria-labelledby={ titleId }>
29 <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd"></path>
30 <title id={ titleId }>{ __( 'Step completed', 'block-lab' ) }</title>
31 </svg>
32 );
33
34 return (
35 <div className="step-icon">
36 { isComplete ? svg : index }
37 </div>
38 );
39 };
40
41 export default StepIcon;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import classNames from 'classnames';
7 import * as React from 'react';
8
9 /**
10 * @typedef StepProps
11 * @property {boolean} isActive Whether this step is active.
12 * @property {boolean} isComplete Whether this step is complete.
13 * @property {React.ReactNode} children The children of the component.
14 */
15
16 /**
17 * Migration step.
18 *
19 * @param {StepProps} props The component props.
20 */
21 const Step = ( { isActive, isComplete, children } ) => {
22 return (
23 <div
24 className={ classNames( 'step', {
25 'step--active': isActive,
26 'step--complete': isComplete,
27 } ) }
28 >
29 { children }
30 </div>
31 );
32 };
33
34 export default Step;
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12
13 /**
14 * Internal dependencies
15 */
16 import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
17 import { FIRST_STEP_NUMBER } from '../../constants';
18
19 /**
20 * @typedef {Object} BackUpSiteProps The component props.
21 * @property {boolean} isStepActive Whether this step is active.
22 * @property {boolean} isStepComplete Whether this step is complete.
23 * @property {number} stepIndex The step index of this step.
24 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
25 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the previous step.
26 */
27
28 /**
29 * The step that prompts to back up the site.
30 *
31 * @param {BackUpSiteProps} Props The component props.
32 * @return {React.ReactElement} The component to prompt to back up the site.
33 */
34 const BackUpSite = ( { isStepActive, isStepComplete, goToNext, goToPrevious, stepIndex } ) => {
35 const isFirstStep = FIRST_STEP_NUMBER === stepIndex;
36
37 return (
38 <Step isActive={ isStepActive } isComplete={ isStepComplete }>
39 <StepIcon
40 index={ stepIndex }
41 isComplete={ isStepComplete }
42 />
43 <StepContent
44 heading={ __( 'Back Up Your Site', 'block-lab' ) }
45 isStepActive={ isStepActive }
46 >
47 <p>{ __( 'Migrating from Block Lab to Genesis Custom Blocks is a one-way action. It can’t be undone. Please back up your site before you begin, just in case you need to roll it back.', 'block-lab' ) }</p>
48 <StepFooter>
49 { ! isFirstStep && <ButtonPrevious onClick={ goToPrevious } /> }
50 <ButtonNext
51 checkboxLabel={ __( 'I have backed up my site.', 'block-lab' ) }
52 onClick={ goToNext }
53 stepIndex={ stepIndex }
54 />
55 </StepFooter>
56 </StepContent>
57 </Step>
58 );
59 };
60
61 export default BackUpSite;
1 // @ts-check
2 /* global blockLabMigration */
3
4 /**
5 * External dependencies
6 */
7 import * as React from 'react';
8
9 /**
10 * WordPress dependencies
11 */
12 import apiFetch from '@wordpress/api-fetch';
13 import { Spinner } from '@wordpress/components';
14 import { useState } from '@wordpress/element';
15 import { __ } from '@wordpress/i18n';
16
17 /**
18 * Internal dependencies
19 */
20 import { ButtonNext, Step, StepContent, StepFooter, StepIcon } from '../';
21
22 /**
23 * @typedef {Object} GetGenesisProProps The component props.
24 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
25 * @property {boolean} isStepActive Whether this step is active.
26 * @property {boolean} isStepComplete Whether this step is complete.
27 * @property {number} stepIndex The step index of this step.
28 */
29
30 /**
31 * The step to get Genesis Pro.
32 *
33 * @param {GetGenesisProProps} Props The component props.
34 * @return {React.ReactElement} The component to get Genesis Pro.
35 */
36 const GetGenesisPro = ( { goToNext, isStepActive, isStepComplete, stepIndex } ) => {
37 // @ts-ignore
38 const genesisProKey = blockLabMigration.genesisProKey;
39 const [ isSubmittingKey, setIsSubmittingKey ] = useState( false );
40 const [ keySubmittedSuccessfully, setKeySubmittedSuccessfully ] = useState( false );
41 const [ subscriptionKey, updateSubscriptionKey ] = useState( !! genesisProKey ? genesisProKey : '' );
42 const [ submissionMessage, setSubmissionMessage ] = useState( '' );
43
44 const urlMigrateWithoutGenPro = 'https://getblocklab.com/migrating-to-genesis-custom-blocks/';
45 const urlOptInGenesisPro = 'https://forms.gle/26u7NDRUp2A9i2aF8';
46 const shouldAllowNextStep = !! genesisProKey || keySubmittedSuccessfully;
47
48 /**
49 * The handler for changing the subscription key.
50 *
51 * @param {React.ChangeEvent<HTMLInputElement>} event The change event.
52 */
53 const onChangeSubscriptionKey = ( event ) => {
54 updateSubscriptionKey( event.target.value );
55 };
56
57 /**
58 * Submits the subscription key to the endpoint.
59 */
60 const submitSubscriptionKey = async () => {
61 setIsSubmittingKey( true );
62
63 await apiFetch( {
64 path: '/block-lab/update-subscription-key',
65 method: 'POST',
66 data: { subscriptionKey },
67 } ).then( () => {
68 setSubmissionMessage( __( 'Thanks! Your key is valid, and has been saved.', 'block-lab' ) );
69 setKeySubmittedSuccessfully( true );
70 } ).catch( ( error ) => {
71 const errorMessage = error.message ? error.message : __( 'There was an error validating the key.', 'block-lab' );
72 setSubmissionMessage( errorMessage );
73 setKeySubmittedSuccessfully( false );
74 updateSubscriptionKey( '' );
75 } );
76
77 setIsSubmittingKey( false );
78 };
79
80 return (
81 <Step isActive={ isStepActive } isComplete={ isStepComplete }>
82 <StepIcon
83 index={ stepIndex }
84 isComplete={ isStepComplete }
85 />
86 <StepContent
87 heading={ __( 'Get Genesis Pro', 'block-lab' ) }
88 isStepActive={ isStepActive }
89 >
90 <p></p>
91 <div className="pro-box">
92 <h3>{ __( 'Migrating from Block Lab Pro', 'block-lab' ) }</h3>
93 <p>{ __( "It looks like you’re a Block Lab Pro customer! Thank you so much for your support. We wouldn't be here without you! Rest assured, your Block Lab Pro license will continue to receive security updates and support for the duration of its term.", 'block-lab' ) }*</p>
94 <div className="pro-box-tiles">
95 <div className="pro-box-tile">
96 <div className="pro-box-tile__icon">
97 <svg
98 width="100%"
99 height="100%"
100 viewBox="0 0 91 75"
101 version="1.1"
102 xmlns="http://www.w3.org/2000/svg"
103 xmlnsXlink="http://www.w3.org/1999/xlink"
104 xmlSpace="preserve"
105 style={ {
106 fillRule: 'evenodd',
107 clipRule: 'evenodd',
108 strokeLinejoin: 'round',
109 strokeMiterlimit: 2,
110 } }
111 >
112 <g id="bl_genesis_icon">
113 <path d="M43.31,39.843c0.288,0.81 0.687,1.495 1.196,2.053c0.508,0.558 1.111,0.984 1.809,1.276c0.698,0.293 1.46,0.439 2.288,0.439c0.631,0 1.189,-0.055 1.675,-0.163c0.487,-0.108 0.945,-0.252 1.377,-0.432l0,-2.984l-1.944,0c-0.288,0 -0.513,-0.076 -0.675,-0.229c-0.162,-0.153 -0.243,-0.346 -0.243,-0.581l0,-2.512l6.994,0l0,8.306c-0.504,0.369 -1.028,0.686 -1.572,0.95c-0.545,0.267 -1.126,0.485 -1.742,0.656c-0.617,0.171 -1.275,0.297 -1.972,0.379c-0.698,0.081 -1.447,0.12 -2.249,0.12c-1.44,0 -2.772,-0.253 -3.997,-0.762c-1.224,-0.509 -2.284,-1.211 -3.179,-2.107c-0.896,-0.895 -1.599,-1.958 -2.107,-3.186c-0.509,-1.229 -0.763,-2.564 -0.763,-4.005c0,-1.466 0.243,-2.816 0.729,-4.045c0.486,-1.228 1.182,-2.288 2.087,-3.179c0.904,-0.892 1.998,-1.585 3.281,-2.079c1.283,-0.496 2.716,-0.744 4.3,-0.744c0.82,0 1.589,0.069 2.31,0.203c0.72,0.135 1.384,0.32 1.992,0.554c0.608,0.234 1.163,0.513 1.668,0.837c0.503,0.324 0.953,0.675 1.35,1.052l-1.324,2.013c-0.126,0.189 -0.276,0.338 -0.452,0.446c-0.175,0.108 -0.367,0.162 -0.574,0.162c-0.27,0 -0.549,-0.09 -0.837,-0.27c-0.36,-0.216 -0.7,-0.403 -1.019,-0.561c-0.32,-0.158 -0.647,-0.285 -0.98,-0.384c-0.333,-0.099 -0.684,-0.171 -1.053,-0.216c-0.369,-0.046 -0.783,-0.068 -1.243,-0.068c-0.855,0 -1.625,0.151 -2.308,0.453c-0.685,0.3 -1.267,0.727 -1.749,1.275c-0.483,0.55 -0.853,1.209 -1.115,1.978c-0.261,0.77 -0.391,1.628 -0.391,2.573c0,1.045 0.144,1.972 0.432,2.782Zm42.005,0.944c-0.658,1.855 -1.461,3.536 -2.437,4.951c0.979,-3.651 1.411,-7.51 1.208,-11.481c-1.438,-19.116 -17.393,-34.183 -36.878,-34.183c-12.247,0 -23.093,5.958 -29.826,15.127c-0.386,0.552 -0.769,1.11 -1.129,1.69c-2.23,3.589 -3.761,7.411 -4.65,11.311c-0.128,-3.334 0.369,-6.946 1.446,-10.724c-3.39,0.467 -6.246,1.301 -8.413,2.492c0.001,0 0.002,0.001 0.003,0.001c-1.918,1.056 -3.3,2.391 -4.03,4.003c-0.002,0.005 -0.005,0.01 -0.008,0.015c-0.103,0.231 -0.194,0.468 -0.271,0.71c-0.092,0.292 -0.163,0.59 -0.214,0.89c-0.015,0.089 -0.017,0.182 -0.029,0.272c-0.027,0.214 -0.055,0.427 -0.063,0.646c-0.002,0.102 0.007,0.208 0.008,0.311c0.001,0.21 0.001,0.419 0.021,0.632c0.01,0.104 0.032,0.209 0.045,0.313c0.029,0.217 0.057,0.434 0.103,0.655c0.02,0.099 0.053,0.201 0.077,0.301c0.056,0.228 0.113,0.457 0.187,0.688c0.03,0.093 0.069,0.188 0.102,0.281c0.085,0.242 0.173,0.485 0.277,0.73c0.036,0.084 0.08,0.17 0.118,0.254c0.117,0.258 0.237,0.515 0.375,0.775c0.039,0.074 0.084,0.15 0.125,0.224c0.149,0.273 0.306,0.547 0.478,0.821c0.04,0.064 0.084,0.128 0.124,0.191c0.186,0.289 0.379,0.578 0.588,0.87c0.038,0.052 0.079,0.105 0.117,0.157c0.223,0.305 0.455,0.609 0.702,0.914c0.034,0.042 0.069,0.082 0.103,0.124c0.26,0.318 0.532,0.637 0.82,0.957c0.027,0.03 0.056,0.061 0.083,0.091c0.301,0.332 0.613,0.664 0.941,0.996c0.02,0.021 0.04,0.041 0.06,0.06c0.339,0.344 0.692,0.687 1.061,1.031c0.012,0.011 0.025,0.023 0.037,0.034c0.378,0.352 0.77,0.704 1.178,1.057c0.004,0.005 0.01,0.008 0.016,0.014c0.414,0.358 0.843,0.715 1.286,1.073c0.001,0.001 0.001,0.002 0.002,0.003c0.447,0.359 0.909,0.717 1.384,1.076c7.15,5.394 17.488,10.59 29.489,14.386c2.195,0.695 4.369,1.315 6.515,1.87c-13.418,-1.826 -24.644,-4.124 -34.98,-10.802c4.203,15.814 18.606,27.468 35.742,27.468c12.033,0 22.719,-5.747 29.475,-14.642c6.999,-0.911 11.792,-3.35 13.03,-7.263c1.059,-3.348 -0.634,-7.309 -4.398,-11.37Z"></path>
114 </g>
115 </svg>
116 </div>
117 <h4>{ __( '12 months free', 'block-lab' ) }</h4>
118 <p>{ __( 'As part of the migration to Genesis Custom Blocks, we’d like to set you up with a free year of Genesis Pro. This new Genesis subscription will give you access to all the features you’ve loved in Block Lab Pro.', 'block-lab' ) }</p>
119 </div>
120 <div className="pro-box-tile">
121 <div className="pro-box-tile__icon">
122 <svg
123 width="100%"
124 height="100%"
125 viewBox="0 0 101 50"
126 version="1.1"
127 xmlns="http://www.w3.org/2000/svg"
128 xmlnsXlink="http://www.w3.org/1999/xlink"
129 xmlSpace="preserve"
130 style={ {
131 fillRule: 'evenodd',
132 clipRule: 'evenodd',
133 strokeLinejoin: 'round',
134 strokeMiterlimit: 2,
135 } }
136 >
137 <g id="bl_infinity_icon">
138 <path d="M50.017,16.489l8.579,-8.58c9.47,-9.47 24.848,-9.47 34.318,0c9.47,9.47 9.47,24.848 0,34.318c-9.47,9.47 -24.848,9.47 -34.318,0l-8.579,-8.58l-8.58,8.58c-9.47,9.47 -24.847,9.47 -34.318,0c-9.47,-9.47 -9.47,-24.848 0,-34.318c9.471,-9.47 24.848,-9.47 34.318,0l8.58,8.58Zm-17.159,0l8.579,8.579l-8.579,8.579c-4.735,4.736 -12.424,4.736 -17.159,0c-4.735,-4.735 -4.735,-12.423 0,-17.158c4.735,-4.736 12.424,-4.736 17.159,0Zm34.318,17.158l-8.58,-8.579l8.58,-8.579c4.735,-4.736 12.423,-4.736 17.158,0c4.736,4.735 4.736,12.423 0,17.158c-4.735,4.736 -12.423,4.736 -17.158,0Z"></path>
139 </g>
140 </svg>
141 </div>
142 <h4>{ __( 'Unlimited Sites', 'block-lab' ) }</h4>
143 <p>{ __( 'All Genesis Pro subscriptions are valid on an unlimited number of installs, and come with additional access to the Genesis Framework, Genesis Themes, and Genesis Page Builder.', 'block-lab' ) }</p>
144 </div>
145 <div className="pro-box-tile">
146 <div className="pro-box-tile__icon">
147 <svg
148 width="100%"
149 height="100%"
150 viewBox="0 0 101 52"
151 version="1.1"
152 xmlns="http://www.w3.org/2000/svg"
153 xmlnsXlink="http://www.w3.org/1999/xlink"
154 xmlSpace="preserve"
155 style={ {
156 fillRule: 'evenodd',
157 clipRule: 'evenodd',
158 strokeLinejoin: 'round',
159 strokeMiterlimit: 2,
160 } }
161 >
162 <g id="bl_key_icon">
163 <path d="M51.602,31.449c-2.477,11.61 -12.8,20.327 -25.143,20.327c-14.188,0 -25.708,-11.519 -25.708,-25.708c0,-14.189 11.52,-25.708 25.708,-25.708c11.678,0 21.547,7.802 24.675,18.474l49.617,0l0,12.615l-8.995,0l0,15.615l-12.616,0l0,-15.615l-27.538,0Zm-25.143,-15.898c5.805,0 10.517,4.713 10.517,10.517c0,5.804 -4.712,10.517 -10.517,10.517c-5.804,0 -10.517,-4.713 -10.517,-10.517c0,-5.804 4.713,-10.517 10.517,-10.517Z"></path>
164 </g>
165 </svg>
166 </div>
167 <h4>{ __( 'New Subscription Key', 'block-lab' ) }</h4>
168 <p>{ __( 'To migrate and maintain your Block Lab Pro feature set, you will need a Genesis Pro subscription key. Step number 1 below will walk you through setting up your account.', 'block-lab' ) }</p>
169 </div>
170 </div>
171 <p>{ __( '* Block Lab Pro licenses will not be renewing and Pro updates / support will end when your current license expires.', 'block-lab' ) }</p>
172 </div>
173 <p>{ __( "Since you're a Block Lab Pro customer, we've already emailed you regarding setting up a WP Engine account with a free Pro subscription.", 'block-lab' ) }</p>
174 <p>{ __( 'To migrate and maintain your Block Lab Pro feature set with Genesis Custom Blocks, you will need your Genesis Pro subscription key.', 'block-lab' ) }</p>
175 <ul>
176 <li>{ __( 'Already have got it? Enter the subscription key below to continue migrating.', 'block-lab' ) }</li>
177 <li>{ __( 'Don’t have one yet? Please opt-in using the link below.', 'block-lab' ) }</li>
178 </ul>
179 { ! keySubmittedSuccessfully && (
180 <>
181 <div className="get-genesis-pro">
182 <a
183 href={ urlOptInGenesisPro }
184 className="btn"
185 target="_blank"
186 rel="noopener noreferrer"
187 >
188 { __( 'Opt-in for Genesis Pro', 'block-lab' ) }
189 </a>
190 <span>{ __( '(This may take up to 3 working days)', 'block-lab' ) }</span>
191 </div>
192 <p>{ __( 'then', 'block-lab' ) }</p>
193 <div className="genesis-pro-form">
194 <input
195 type="text"
196 placeholder={ __( 'Paste your Genesis Pro subscription key', 'block-lab' ) }
197 value={ subscriptionKey }
198 onChange={ onChangeSubscriptionKey }
199 />
200 <button
201 onClick={ submitSubscriptionKey }
202 disabled={ isSubmittingKey }
203 >
204 { __( 'Save', 'block-lab' ) }
205 </button>
206 { isSubmittingKey && <Spinner /> }
207 </div>
208 </>
209 ) }
210 <p className="pro-submission-message">{ submissionMessage }</p>
211 { ! keySubmittedSuccessfully && (
212 <p className="help-text">
213 { __( 'Want to migrate but not set up Genesis Pro just now?', 'block-lab' ) }
214 &nbsp;
215 <a
216 href={ urlMigrateWithoutGenPro }
217 target="_blank"
218 rel="noopener noreferrer"
219 aria-label={ __( 'More information about migrating but not setting up Genesis Pro', 'genesis-custom-blocks' ) }
220 >
221 { __( 'Read here for what that means.', 'block-lab' ) }
222 </a>
223 </p>
224 ) }
225 <StepFooter>
226 <ButtonNext
227 checkboxLabel={ shouldAllowNextStep ? null : __( 'Migrate without Genesis Pro.', 'block-lab' ) }
228 onClick={ goToNext }
229 stepIndex={ stepIndex }
230 />
231 </StepFooter>
232 </StepContent>
233 </Step>
234 );
235 };
236
237 export default GetGenesisPro;
1 export { default as BackUpSite } from './back-up-site';
2 export { default as GetGenesisPro } from './get-genesis-pro';
3 export { default as InstallActivateGcb } from './install-activate-gcb';
4 export { default as MigrateBlocks } from './migrate-blocks';
5 export { default as UpdateHooks } from './update-hooks';
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { speak } from '@wordpress/a11y';
12 import apiFetch from '@wordpress/api-fetch';
13 import { Spinner } from '@wordpress/components';
14 import { useState } from '@wordpress/element';
15 import { __ } from '@wordpress/i18n';
16
17 /**
18 * Internal dependencies
19 */
20 import { ButtonNext, Step, StepContent, StepFooter, StepIcon } from '../';
21
22 /**
23 * @typedef {Object} InstallActivateGcbProps The component props.
24 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
25 * @property {boolean} isStepActive Whether this step is active.
26 * @property {boolean} isStepComplete Whether this step is complete.
27 * @property {number} stepIndex The step index of this step.
28 */
29
30 /**
31 * Installs and activates GCB.
32 *
33 * @param {InstallActivateGcbProps} Props The component props.
34 * @return {React.ReactElement} The component to activate Genesis Custom Blocks.
35 */
36 const InstallActivateGcb = ( { goToNext, isStepActive, isStepComplete, stepIndex } ) => {
37 const [ isInProgress, setIsInProgress ] = useState( false );
38 const [ isError, setIsError ] = useState( false );
39 const [ errorMessage, setErrorMessage ] = useState( '' );
40 const [ isSuccess, setIsSuccess ] = useState( false );
41
42 /**
43 * Installs and activates Genesis Custom Blocks.
44 */
45 const installAndActivateGcb = async () => {
46 speak( __( 'The installation is now in progress', 'block-lab' ) );
47 setIsInProgress( true );
48 setIsError( false );
49 setErrorMessage( '' );
50
51 await apiFetch( {
52 path: '/block-lab/install-activate-gcb',
53 method: 'POST',
54 } ).then( () => {
55 speak( __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) );
56 setIsSuccess( true );
57 } ).catch( ( result ) => {
58 speak( __( 'The installation and activation failed with the following error:', 'block-lab' ) );
59 if ( result.hasOwnProperty( 'message' ) ) {
60 speak( result.message );
61 setErrorMessage( result.message );
62 }
63 setIsSuccess( false );
64 setIsError( true );
65 } );
66
67 setIsInProgress( false );
68 };
69
70 return (
71 <Step isActive={ isStepActive } isComplete={ isStepComplete }>
72 <StepIcon
73 index={ stepIndex }
74 isComplete={ isStepComplete }
75 />
76 <StepContent
77 heading={ __( 'Install And Activate Genesis Custom Blocks', 'block-lab' ) }
78 isStepActive={ isStepActive }
79 >
80 { isInProgress && (
81 <>
82 <Spinner />
83 <p>{ __( 'Installing and activating Genesis Custom Blocks…', 'block-lab' ) }</p>
84 </>
85 ) }
86 { !! errorMessage && (
87 <div className="bl-migration__error">
88 <p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
89 <p>{ errorMessage }</p>
90 </div>
91 ) }
92 { ! isInProgress && ! isSuccess && (
93 <button
94 className="btn"
95 onClick={ installAndActivateGcb }
96 >
97 { isError ? __( 'Try Again', 'block-lab' ) : __( 'Install and activate', 'block-lab' ) }
98 </button>
99 ) }
100 { isSuccess && (
101 <>
102 <p>{ __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) }</p>
103 <StepFooter>
104 <ButtonNext
105 onClick={ goToNext }
106 stepIndex={ stepIndex }
107 />
108 </StepFooter>
109 </>
110 ) }
111 </StepContent>
112 </Step>
113 );
114 };
115
116 export default InstallActivateGcb;
1 // @ts-check
2 /* global blockLabMigration */
3
4 /**
5 * External dependencies
6 */
7 import * as React from 'react';
8
9 /**
10 * WordPress dependencies
11 */
12 import { speak } from '@wordpress/a11y';
13 import apiFetch from '@wordpress/api-fetch';
14 import { Spinner } from '@wordpress/components';
15 import { useState } from '@wordpress/element';
16 import { __ } from '@wordpress/i18n';
17
18 /**
19 * Internal dependencies
20 */
21 import { Step, StepContent, StepFooter, StepIcon } from '../';
22
23 /**
24 * @typedef {Object} MigrateBlocksProps The component props.
25 * @property {Function} goToNext Goes to the next step.
26 * @property {boolean} isStepActive Whether this step is active.
27 * @property {boolean} isStepComplete Whether this step is complete.
28 * @property {number} stepIndex The step index of this step.
29 */
30
31 /**
32 * The step that migrates the blocks.
33 *
34 * @param {MigrateBlocksProps} Props The component props.
35 * @return {React.ReactElement} The component to prompt to migrate the post content.
36 */
37 const MigrateBlocks = ( { isStepActive, isStepComplete, stepIndex } ) => {
38 const [ currentBlockMigrationStep, setCurrentBlockMigrationStep ] = useState( 0 );
39 const [ isInProgress, setIsInProgress ] = useState( false );
40 const [ isError, setIsError ] = useState( false );
41 const [ errorMessage, setErrorMessage ] = useState( '' );
42 const [ isSuccess, setIsSuccess ] = useState( false );
43
44 const migrationLabels = [
45 __( 'Migrating your blocks…', 'block-lab' ),
46 __( 'Migrating your post content…', 'block-lab' ),
47 ];
48
49 /**
50 * Migrates the custom post type, then chains post content migration to the callback.
51 */
52 const migrateCpt = async () => {
53 await apiFetch( {
54 path: '/block-lab/migrate-post-type',
55 method: 'POST',
56 } ).then( async () => {
57 setCurrentBlockMigrationStep( 1 );
58 await migratePostContent();
59 } ).catch( ( result ) => {
60 if ( result.hasOwnProperty( 'message' ) ) {
61 setErrorMessage( result.message );
62 }
63 speak( __( 'The migration failed in the CPT migration', 'block-lab' ) );
64 setIsError( true );
65 setIsInProgress( false );
66 } );
67 };
68
69 /**
70 * Migrates the post content.
71 */
72 const migratePostContent = async () => {
73 // Used for a 504 Gateway Timeout Error, but could also be for other errors.
74 const timeoutErrorCode = 'invalid_json';
75
76 await apiFetch( {
77 path: '/block-lab/migrate-post-content',
78 method: 'POST',
79 } ).then( () => {
80 speak( __( 'The migration was successful!', 'block-lab' ) );
81 setIsSuccess( true );
82 } ).catch( async ( result ) => {
83 if ( result.hasOwnProperty( 'code' ) && timeoutErrorCode === result.code ) {
84 await migratePostContent();
85 return;
86 } else if ( result.hasOwnProperty( 'message' ) ) {
87 setErrorMessage( result.message );
88 }
89
90 speak( __( 'The migration failed in the post content migration', 'block-lab' ) );
91 setIsError( true );
92 } );
93 };
94
95 /**
96 * Handles all of the migration for this step.
97 */
98 const migrate = async () => {
99 speak( __( 'The migration is now in progress', 'block-lab' ) );
100 setErrorMessage( '' );
101 setIsInProgress( true );
102
103 // The post content migration is chained to the callback in then().
104 await migrateCpt();
105
106 setIsInProgress( false );
107 };
108
109 return (
110 <Step isActive={ isStepActive } isComplete={ isStepComplete }>
111 <StepIcon
112 index={ stepIndex }
113 isComplete={ isStepComplete }
114 />
115 <StepContent
116 heading={ __( 'Migrate Your Blocks', 'block-lab' ) }
117 isStepActive={ isStepActive }
118 >
119 { ! isSuccess && <p>{ __( "Okay! Everything is ready. Let's do this. While the migration is underway, don't leave this page.", 'block-lab' ) }</p> }
120 { !! errorMessage && (
121 <div className="bl-migration__error">
122 <p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
123 <p>{ errorMessage }</p>
124 </div>
125 ) }
126 { isInProgress && (
127 <>
128 <Spinner />
129 <p>{ migrationLabels[ currentBlockMigrationStep ] }</p>
130 </>
131 ) }
132 { ! isInProgress && ! isSuccess && (
133 <button
134 className="btn"
135 onClick={ migrate }
136 >
137 { isError ? __( 'Try Again', 'block-lab' ) : __( 'Migrate Now', 'block-lab' ) }
138 </button>
139 ) }
140 { isSuccess && (
141 <>
142 <p>
143 <span role="img" aria-label={ __( 'party emoji', 'block-lab' ) }>🎉</span>
144 &nbsp;
145 { __( 'The migration completed successfully! Time to say goodbye to Block Lab (it’s been fun!) and step into the FUTURE', 'block-lab' ) }
146 &nbsp;
147 <span className="message-future">{ __( 'FUTURE', 'block-lab' ) }</span>
148 &nbsp;
149 <sub>{ __( 'FUTURE', 'block-lab' ) }</sub>.
150 </p>
151 <StepFooter>
152 { /* @ts-ignore */ }
153 <a href={ blockLabMigration.gcbUrl } className="btn">
154 { __( 'Go To Genesis Custom Blocks', 'block-lab' ) }
155 </a>
156 </StepFooter>
157 </>
158 ) }
159 </StepContent>
160 </Step>
161 );
162 };
163
164 export default MigrateBlocks;
1 /**
2 * External dependencies
3 */
4 import { render } from '@testing-library/react';
5 import user from '@testing-library/user-event';
6
7 /**
8 * Internal dependencies
9 */
10 import { BackUpSite } from '../';
11
12 test( 'back up site migration step', async () => {
13 const props = {
14 goToNext: jest.fn(),
15 isStepActive: true,
16 isStepComplete: false,
17 stepIndex: 1,
18 };
19 const { getByLabelText, getByText } = render( <BackUpSite { ...props } /> );
20
21 getByText( /back up your site/ );
22 getByText( props.stepIndex.toString() );
23
24 // Because the 'confirm' checkbox isn't checked, the 'next' button should be disabled.
25 user.click( getByText( 'Next Step' ) );
26 expect( props.goToNext ).not.toHaveBeenCalled();
27
28 // Now that the 'confirm' checkbox is checked, the 'next' button should work.
29 user.click( getByLabelText( 'I have backed up my site.' ) );
30 user.click( getByText( 'Next Step' ) );
31 expect( props.goToNext ).toHaveBeenCalled();
32 } );
1 /**
2 * External dependencies
3 */
4 import { fireEvent, render, waitFor } from '@testing-library/react';
5 import user from '@testing-library/user-event';
6
7 /**
8 * WordPress dependencies
9 */
10 import apiFetch from '@wordpress/api-fetch';
11
12 /**
13 * Internal dependencies
14 */
15 import { GetGenesisPro } from '../';
16
17 jest.mock( '@wordpress/api-fetch' );
18 window.blockLabMigration = {};
19
20 test( 'get Genesis Pro migration step', async () => {
21 apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
22
23 const props = {
24 goToNext: jest.fn(),
25 goToPrevious: jest.fn(),
26 isStepActive: true,
27 isStepComplete: false,
28 stepIndex: 1,
29 };
30 const { getByText, getByRole } = render( <GetGenesisPro { ...props } /> );
31
32 // Because the checkbox isn't checked, the 'next' button should be disabled.
33 user.click( getByText( 'Next Step' ) );
34 expect( props.goToNext ).not.toHaveBeenCalled();
35
36 fireEvent.change(
37 getByRole( 'textbox' ),
38 { target: { value: '1234567' } }
39 );
40
41 await waitFor( () =>
42 user.click( getByText( 'Save' ) )
43 );
44 getByText( 'Thanks! Your key is valid, and has been saved.' );
45
46 user.click( getByText( 'Next Step' ) );
47 expect( props.goToNext ).toHaveBeenCalled();
48 } );
1 /**
2 * External dependencies
3 */
4 import { render } from '@testing-library/react';
5
6 /**
7 * Internal dependencies
8 */
9 import { InstallActivateGcb } from '../';
10
11 global.blockLabMigration = {
12 activateUrl: 'https://example.com',
13 };
14
15 test( 'activate gcb migration step', async () => {
16 const props = {
17 isStepActive: true,
18 isStepComplete: false,
19 stepIndex: 5,
20 };
21 const { getByText } = render( <InstallActivateGcb { ...props } /> );
22
23 expect( getByText( props.stepIndex.toString() ) ).toBeInTheDocument();
24 } );
1 /**
2 * External dependencies
3 */
4 import { render, waitFor } from '@testing-library/react';
5 import user from '@testing-library/user-event';
6
7 /**
8 * WordPress dependencies
9 */
10 import apiFetch from '@wordpress/api-fetch';
11
12 /**
13 * Internal dependencies
14 */
15 import { MigrateBlocks } from '../';
16
17 jest.mock( '@wordpress/api-fetch' );
18 global.blockLabMigration = {
19 gcbUrl: 'https://example.com',
20 };
21
22 test( 'migrate blocks step', async () => {
23 apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
24 const props = {
25 currentStepIndex: 4,
26 goToNext: jest.fn(),
27 isStepActive: true,
28 isStepComplete: false,
29 stepIndex: 4,
30 };
31
32 const { getByText } = render( <MigrateBlocks { ...props } /> );
33
34 getByText( /migrate your blocks/i );
35 getByText( props.stepIndex.toString() );
36
37 await waitFor( () =>
38 user.click( getByText( 'Migrate Now' ) )
39 );
40
41 expect( getByText( 'The migration was successful!' ) ).toBeInTheDocument();
42 } );
1 /**
2 * External dependencies
3 */
4 import { render, screen } from '@testing-library/react';
5 import user from '@testing-library/user-event';
6
7 /**
8 * Internal dependencies
9 */
10 import { UpdateHooks } from '../';
11
12 describe( 'update hooks migration step', () => {
13 it( 'displays step content when this step is active', async () => {
14 const props = {
15 goToNext: jest.fn(),
16 goToPrevious: jest.fn(),
17 isStepActive: true,
18 isStepComplete: false,
19 stepIndex: 2,
20 };
21 const { getByText } = render( <UpdateHooks { ...props } /> );
22
23 getByText( 'Update Hooks & API' );
24 getByText( props.stepIndex.toString() );
25
26 // It should always be possible to click the 'previous' button.
27 user.click( getByText( 'Previous' ) );
28 expect( props.goToPrevious ).toHaveBeenCalled();
29 } );
30
31 it( 'does not display content when this step is not active', async () => {
32 const props = {
33 goToNext: jest.fn(),
34 goToPrevious: jest.fn(),
35 isStepActive: false,
36 isStepComplete: false,
37 stepIndex: 2,
38 };
39 const { getByText } = render( <UpdateHooks { ...props } /> );
40
41 // The heading should still display.
42 getByText( 'Update Hooks & API' );
43 getByText( props.stepIndex.toString() );
44
45 // The content of the step should now display, as it's not active.
46 expect( screen.queryByText( 'Previous' ) ).not.toBeInTheDocument();
47 } );
48 } );
1 // @ts-check
2
3 /**
4 * External dependencies
5 */
6 import * as React from 'react';
7
8 /**
9 * WordPress dependencies
10 */
11 import { __ } from '@wordpress/i18n';
12
13 /**
14 * Internal dependencies
15 */
16 import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
17
18 /**
19 * @typedef {Object} UpdateHooksProps The component props.
20 * @property {boolean} isStepActive Whether this step is active.
21 * @property {boolean} isStepComplete Whether this step is complete.
22 * @property {number} stepIndex The step index of this step.
23 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
24 * @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the next step.
25 */
26
27 /**
28 * The step that prompts to update hooks.
29 *
30 * @param {UpdateHooksProps} Props The component props.
31 * @return {React.ReactElement} The component to prompt to back up the site.
32 */
33 const UpdateHooks = ( { isStepActive, isStepComplete, stepIndex, goToNext, goToPrevious } ) => {
34 const hooksDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-hook-compatibility/';
35 const phpApiDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-php-api-compatibility/';
36
37 return (
38 <Step isActive={ isStepActive } isComplete={ isStepComplete }>
39 <StepIcon
40 index={ stepIndex }
41 isComplete={ isStepComplete }
42 />
43 <StepContent
44 heading={ __( 'Update Hooks & API', 'block-lab' ) }
45 isStepActive={ isStepActive }
46 >
47 <p>{ __( 'In most cases, you won’t have to worry about this step. However, there are some instances that will require manual edits to your custom block related files. These are:', 'block-lab' ) }</p>
48 <ul className="list-disc list-inside mt-2">
49 <li>
50 <b>{ __( 'Hooks', 'block-lab' ) }</b> - { __( 'The Block Lab hook names have changed. If you’ve extended Block Lab with custom functionality using these, you’ll need to make some small changes.', 'block-lab' ) }
51 &nbsp;
52 <a
53 href={ hooksDetailsUrl }
54 target="_blank"
55 rel="noopener noreferrer"
56 aria-label={ __( 'More details on the hooks', 'genesis-custom-blocks' ) }
57 >
58 { __( 'More details here.', 'block-lab' ) }
59 </a>
60 </li>
61 <li>
62 <b>{ __( 'API', 'block-lab' ) }</b> - { __( 'If you use Block Lab’s PHP API or JSON API to register and configure your custom blocks, you’ll need to make some small changes.', 'block-lab' ) }
63 &nbsp;
64 <a
65 href={ phpApiDetailsUrl }
66 target="_blank"
67 rel="noopener noreferrer"
68 aria-label={ __( 'More details on the PHP API', 'genesis-custom-blocks' ) }
69 >
70 { __( 'More details here.', 'block-lab' ) }
71 </a>
72 </li>
73 </ul>
74 <StepFooter>
75 <ButtonPrevious onClick={ goToPrevious } />
76 <ButtonNext
77 checkboxLabel={ __( "I'm all okay on the hooks and API front.", 'block-lab' ) }
78 onClick={ goToNext }
79 stepIndex={ stepIndex }
80 />
81 </StepFooter>
82 </StepContent>
83 </Step>
84 );
85 };
86
87 export default UpdateHooks;
1 /**
2 * External dependencies
3 */
4 import { render } from '@testing-library/react';
5
6 /**
7 * Internal dependencies
8 */
9 import App from '../app';
10
11 global.blockLabMigration = {
12 isPro: true,
13 };
14
15 test( 'migration app', async () => {
16 const { getByText } = render( <App /> );
17
18 expect( getByText( 'Migrating to Genesis Custom Blocks' ) ).toBeInTheDocument();
19 expect( getByText( 'Need to let the developer for this site know about this? Send them this link.' ) ).toBeInTheDocument();
20 } );
1 /**
2 * WordPress dependencies
3 */
4 import domReady from '@wordpress/dom-ready';
5 import { render } from '@wordpress/element';
6
7 /**
8 * Internal dependencies
9 */
10 import { App } from './components';
11
12 // Renders the app in the container.
13 domReady( () => {
14 render(
15 <App />,
16 document.querySelector( '.bl-migration__content' )
17 );
18 } );
1 <?php
2 /**
3 * WP Admin resources.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13 use Block_Lab\Admin\Migration\Api;
14 use Block_Lab\Admin\Migration\Subscription_Api;
15 use Block_Lab\Admin\Migration\Notice;
16 use Block_Lab\Admin\Migration\Submenu;
17
18 /**
19 * Class Admin
20 */
21 class Admin extends Component_Abstract {
22
23 /**
24 * JSON import.
25 *
26 * @var Import
27 */
28 public $import;
29
30 /**
31 * Plugin license.
32 *
33 * @var License
34 */
35 public $license;
36
37 /**
38 * User onboarding.
39 *
40 * @var Onboarding
41 */
42 public $onboarding;
43
44 /**
45 * Plugin settings.
46 *
47 * @var Settings
48 */
49 public $settings;
50
51 /**
52 * Plugin upgrade.
53 *
54 * @var Upgrade
55 */
56 public $upgrade;
57
58 /**
59 * The migration API.
60 *
61 * @var Api
62 */
63 private $api;
64
65 /**
66 * THe subscription API for the migration.
67 *
68 * @var Subscription_Api
69 */
70 private $subscription_api;
71
72 /**
73 * The migration notice.
74 *
75 * @var Notice
76 */
77 private $notice;
78
79 /**
80 * The migration submenu under the Block Lab menu item.
81 *
82 * @var Submenu
83 */
84 private $submenu;
85
86 /**
87 * Initialise the Admin component.
88 */
89 public function init() {
90 $this->settings = new Settings();
91 block_lab()->register_component( $this->settings );
92
93 $this->license = new License();
94 block_lab()->register_component( $this->license );
95
96 $this->onboarding = new Onboarding();
97 block_lab()->register_component( $this->onboarding );
98
99 $this->api = new Api();
100 block_lab()->register_component( $this->api );
101
102 $this->subscription_api = new Subscription_Api();
103 block_lab()->register_component( $this->subscription_api );
104
105 $this->notice = new Notice();
106 block_lab()->register_component( $this->notice );
107
108 $this->submenu = new Submenu();
109 block_lab()->register_component( $this->submenu );
110
111 $show_pro_nag = apply_filters( 'block_lab_show_pro_nag', false );
112 if ( $show_pro_nag && ! block_lab()->is_pro() ) {
113 $this->upgrade = new Upgrade();
114 block_lab()->register_component( $this->upgrade );
115 } else {
116 $this->maybe_settings_redirect();
117 }
118
119 if ( defined( 'WP_LOAD_IMPORTERS' ) && WP_LOAD_IMPORTERS ) {
120 $this->import = new Import();
121 block_lab()->register_component( $this->import );
122 }
123 }
124
125 /**
126 * Register any hooks that this component needs.
127 */
128 public function register_hooks() {
129 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
130 }
131
132 /**
133 * Enqueue scripts and styles used globally in the WP Admin.
134 *
135 * @return void
136 */
137 public function enqueue_scripts() {
138 wp_enqueue_style(
139 'block-lab',
140 $this->plugin->get_url( 'css/admin.css' ),
141 [],
142 $this->plugin->get_version()
143 );
144 }
145
146 /**
147 * Redirect to the Settings screen if the license is being saved.
148 */
149 public function maybe_settings_redirect() {
150 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
151
152 if ( 'block-lab-pro' === $page ) {
153 wp_safe_redirect(
154 add_query_arg(
155 [
156 'post_type' => 'block_lab',
157 'page' => 'block-lab-settings',
158 'tab' => 'license',
159 ],
160 admin_url( 'edit.php' )
161 )
162 );
163
164 die();
165 }
166 }
167 }
1 <?php
2 /**
3 * Block Lab Importer.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13
14 /**
15 * Class Import
16 */
17 class Import extends Component_Abstract {
18
19 /**
20 * Importer slug.
21 *
22 * @var string
23 */
24 public $slug = 'block-lab';
25
26 /**
27 * Register any hooks that this component needs.
28 */
29 public function register_hooks() {
30 add_action( 'admin_init', [ $this, 'register_importer' ] );
31 }
32
33 /**
34 * Register the importer for the Tools > Import admin screen
35 */
36 public function register_importer() {
37 register_importer(
38 $this->slug,
39 __( 'Block Lab', 'block-lab' ),
40 __( 'Import custom blocks created with Block Lab.', 'block-lab' ),
41 [ $this, 'render_page' ]
42 );
43 }
44
45 /**
46 * Render the import page. Manages the three separate stages of the JSON import process.
47 */
48 public function render_page() {
49 $step = filter_input( INPUT_GET, 'step', FILTER_SANITIZE_NUMBER_INT );
50
51 ob_start();
52
53 $this->render_page_header();
54
55 switch ( $step ) {
56 case 0:
57 default:
58 $this->render_welcome();
59 break;
60 case 1:
61 check_admin_referer( 'import-upload' );
62
63 $upload_dir = wp_get_upload_dir();
64
65 if ( ! isset( $upload_dir['basedir'] ) ) {
66 $this->render_import_error(
67 __( 'Sorry, there was an error uploading the file.', 'block-lab' ),
68 __( 'Upload base directory not set.', 'block-lab' )
69 );
70 }
71
72 $cache_dir = $upload_dir['basedir'] . '/block-lab';
73 $file = wp_import_handle_upload();
74
75 if ( $this->validate_upload( $file ) ) {
76 if ( ! file_exists( $cache_dir ) ) {
77 mkdir( $cache_dir, 0777, true );
78 }
79
80 // This is on the local filesystem, so file_get_contents() is ok to use here.
81 file_put_contents( $cache_dir . '/import.json', file_get_contents( $file['file'] ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions
82
83 $json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
84 $blocks = json_decode( $json, true );
85
86 $this->render_choose_blocks( $blocks );
87 }
88 break;
89 case 2:
90 $cache_dir = wp_get_upload_dir()['basedir'] . '/block-lab';
91 $file = [ 'file' => $cache_dir . '/import.json' ];
92
93 if ( $this->validate_upload( $file ) ) {
94 // This is on the local filesystem, so file_get_contents() is ok to use here.
95 $json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
96 $blocks = json_decode( $json, true );
97
98 $import_blocks = [];
99 foreach ( $blocks as $block_namespace => $block ) {
100 if ( 'on' === filter_input( INPUT_GET, $block_namespace, FILTER_SANITIZE_STRING ) ) {
101 $import_blocks[ $block_namespace ] = $block;
102 }
103 }
104
105 $this->import_blocks( $import_blocks );
106 }
107
108 break;
109 }
110
111 $html = ob_get_clean();
112 echo '<div class="wrap block-lab-import">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
113 }
114
115 /**
116 * Render the Import page header.
117 */
118 public function render_page_header() {
119 ?>
120 <h2><?php esc_html_e( 'Import Block Lab Content Blocks', 'block-lab' ); ?></h2>
121 <?php
122 }
123
124 /**
125 * Render the welcome message.
126 */
127 public function render_welcome() {
128 ?>
129 <p><?php esc_html_e( 'Welcome! This importer processes Block Lab JSON files, adding custom blocks to this site.', 'block-lab' ); ?></p>
130 <p><?php esc_html_e( 'Choose a JSON (.json) file to upload, then click Upload file and import.', 'block-lab' ); ?></p>
131 <p>
132 <?php
133 echo wp_kses(
134 sprintf(
135 /* translators: %1$s: an opening anchor tag, %2$s: a closing anchor tag */
136 __( 'This JSON file should come from the export link or bulk action in the %1$sContent Blocks screen%2$s, not from the main Export tool.', 'block-lab' ),
137 sprintf(
138 '<a href="%1$s">',
139 esc_url(
140 admin_url(
141 add_query_arg(
142 [ 'post_type' => block_lab()->get_post_type_slug() ],
143 'edit.php'
144 )
145 )
146 )
147 ),
148 '</a>'
149 ),
150 [ 'a' => [ 'href' => [] ] ]
151 );
152 ?>
153 </p>
154
155 <?php
156 wp_import_upload_form(
157 add_query_arg(
158 [
159 'import' => $this->slug,
160 'step' => 1,
161 ]
162 )
163 );
164 }
165
166 /**
167 * Render the currently importing block title.
168 *
169 * @param string $title The title of the block.
170 */
171 public function render_import_success( $title ) {
172 echo wp_kses_post(
173 sprintf(
174 '<p>%s</p>',
175 sprintf(
176 // translators: placeholder refers to title of custom block.
177 __( 'Successfully imported %1$s.', 'block-lab' ),
178 '<strong>' . esc_html( $title ) . '</strong>'
179 )
180 )
181 );
182 }
183
184 /**
185 * Render the currently importing block title.
186 *
187 * @param string $title The title of the block.
188 * @param string $error The error being reported.
189 */
190 public function render_import_error( $title, $error ) {
191 echo wp_kses_post(
192 sprintf( '<p><strong>%s</strong></p><p>%s</p>', $title, $error )
193 );
194 }
195
196 /**
197 * Render the successful import message.
198 */
199 public function render_done() {
200 ?>
201 <p><?php esc_html_e( 'All done!', 'block-lab' ); ?></p>
202 <?php
203 }
204
205 /**
206 * Render the interface for choosing blocks to update.
207 *
208 * @param array $blocks An array of block names to choose from.
209 */
210 public function render_choose_blocks( $blocks ) {
211 ?>
212 <p><?php esc_html_e( 'Please select the blocks to import:', 'block-lab' ); ?></p>
213 <form>
214 <?php
215 foreach ( $blocks as $block_namespace => $block ) {
216 $action = __( 'Import', 'block-lab' );
217 if ( $this->block_exists( $block_namespace ) ) {
218 $action = __( 'Replace', 'block-lab' );
219 }
220 ?>
221 <p>
222 <input type="checkbox" name="<?php echo esc_attr( $block_namespace ); ?>" id="<?php echo esc_attr( $block_namespace ); ?>" checked>
223 <label for="<?php echo esc_attr( $block_namespace ); ?>">
224 <?php echo esc_html( $action ); ?> <strong><?php echo esc_attr( $block['title'] ); ?></strong>
225 </label>
226 </p>
227 <?php
228 }
229 wp_nonce_field();
230 ?>
231 <input type="hidden" name="import" value="block-lab">
232 <input type="hidden" name="step" value="2">
233 <p class="submit"><input type="submit" value="<?php esc_attr_e( 'Import Selected', 'block-lab' ); ?>" class="button button-primary"></p>
234 </form>
235 <?php
236 }
237
238 /**
239 * Handles the JSON upload and initial parsing of the file.
240 *
241 * @param array $file The file.
242 * @return bool False if error uploading or invalid file, true otherwise.
243 */
244 public function validate_upload( $file ) {
245 if ( isset( $file['error'] ) ) {
246 $this->render_import_error(
247 __( 'Sorry, there was an error uploading the file.', 'block-lab' ),
248 $file['error']
249 );
250 return false;
251 } elseif ( ! file_exists( $file['file'] ) ) {
252 $this->render_import_error(
253 __( 'Sorry, there was an error uploading the file.', 'block-lab' ),
254 sprintf(
255 // translators: placeholder refers to a file directory.
256 __( 'The export file could not be found at %1$s. It is likely that this was caused by a permissions problem.', 'block-lab' ),
257 '<code>' . esc_html( $file['file'] ) . '</code>'
258 )
259 );
260 return false;
261 }
262
263 // This is on the local filesystem, so file_get_contents() is ok to use here.
264 $json = file_get_contents( $file['file'] ); // @codingStandardsIgnoreLine
265 $data = json_decode( $json, true );
266
267 if ( ! is_array( $data ) ) {
268 $this->render_import_error(
269 __( 'Sorry, there was an error processing the file.', 'block-lab' ),
270 __( 'Invalid JSON.', 'block-lab' )
271 );
272 return false;
273 }
274
275 return true;
276 }
277
278 /**
279 * Import data into new Block Lab posts.
280 *
281 * @param array $blocks An array of Block Lab content blocks.
282 */
283 public function import_blocks( $blocks ) {
284 foreach ( $blocks as $block_namespace => $block ) {
285 if ( ! isset( $block['title'] ) || ! isset( $block['name'] ) ) {
286 continue;
287 }
288
289 $post_id = false;
290
291 if ( $this->block_exists( $block_namespace ) ) {
292 $post = get_page_by_path( $block['name'], OBJECT, block_lab()->get_post_type_slug() );
293 if ( $post ) {
294 $post_id = $post->ID;
295 }
296 }
297
298 $json = wp_json_encode( [ $block_namespace => $block ], JSON_UNESCAPED_UNICODE );
299
300 $post_data = [
301 'post_title' => $block['title'],
302 'post_name' => $block['name'],
303 'post_content' => wp_slash( $json ),
304 'post_status' => 'publish',
305 'post_type' => block_lab()->get_post_type_slug(),
306 ];
307
308 if ( $post_id ) {
309 $post_data['ID'] = $post_id;
310 }
311 $post = wp_insert_post( $post_data );
312
313 if ( is_wp_error( $post ) ) {
314 $this->render_import_error(
315 sprintf(
316 // translators: placeholder refers to title of custom block.
317 __( 'Error importing %s.', 'block-lab' ),
318 $block['title']
319 ),
320 $post->get_error_message()
321 );
322 } else {
323 $this->render_import_success( $block['title'] );
324 }
325 }
326
327 $this->render_done();
328 }
329
330 /**
331 * Check if block already exists.
332 *
333 * @param string $block_namespace The JSON key for the block. e.g. block-lab/foo.
334 *
335 * @return bool
336 */
337 private function block_exists( $block_namespace ) {
338 $registered_blocks = get_dynamic_block_names();
339 if ( in_array( $block_namespace, $registered_blocks, true ) ) {
340 return true;
341 }
342
343 return false;
344 }
345 }
1 <?php
2 /**
3 * Enable and validate Pro version licensing.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13
14 /**
15 * Class License
16 */
17 class License extends Component_Abstract {
18
19 /**
20 * Option name of the license key.
21 *
22 * @var string
23 */
24 const LICENSE_KEY_OPTION_NAME = 'block_lab_license_key';
25
26 /**
27 * URL of the Block Lab store.
28 *
29 * @var string
30 */
31 public $store_url;
32
33 /**
34 * Product slug of the Pro version on the Block Lab store.
35 *
36 * @var string
37 */
38 public $product_slug;
39
40 /**
41 * The name of the license key transient.
42 *
43 * @var string
44 */
45 const TRANSIENT_NAME = 'block_lab_license';
46
47 /**
48 * The transient 'license' value for when the request to validate the Pro license failed.
49 *
50 * This is for when the actual POST request fails,
51 * not for when it returns that the license is invalid.
52 *
53 * @var string
54 */
55 const REQUEST_FAILED = 'request_failed';
56
57 /**
58 * Initialise the Pro component.
59 */
60 public function init() {
61 $this->store_url = 'https://getblocklab.com';
62 $this->product_slug = 'block-lab-pro';
63 }
64
65 /**
66 * Register any hooks that this component needs.
67 */
68 public function register_hooks() {
69 add_filter( 'pre_update_option_block_lab_license_key', [ $this, 'save_license_key' ] );
70 }
71
72 /**
73 * Check that the license key is valid before saving.
74 *
75 * @param string $key The license key that was submitted.
76 *
77 * @return string
78 */
79 public function save_license_key( $key ) {
80 $this->activate_license( $key );
81 $license = get_transient( self::TRANSIENT_NAME );
82
83 if ( ! $this->is_valid() ) {
84 $key = '';
85 if ( isset( $license['license'] ) && self::REQUEST_FAILED === $license['license'] ) {
86 block_lab()->admin->settings->prepare_notice( $this->license_request_failed_message() );
87 } else {
88 block_lab()->admin->settings->prepare_notice( $this->license_invalid_message() );
89 }
90 } else {
91 block_lab()->admin->settings->prepare_notice( $this->license_success_message() );
92 }
93
94 return $key;
95 }
96
97 /**
98 * Check if the license if valid.
99 *
100 * @return bool
101 */
102 public function is_valid() {
103 $license = $this->get_license();
104
105 if ( isset( $license['license'] ) && 'valid' === $license['license'] ) {
106 if ( isset( $license['expires'] ) && time() < strtotime( $license['expires'] ) ) {
107 return true;
108 }
109 }
110
111 return false;
112 }
113
114 /**
115 * Retrieve the license data.
116 *
117 * @return mixed
118 */
119 public function get_license() {
120 $license = get_transient( self::TRANSIENT_NAME );
121
122 if ( ! $license ) {
123 $key = get_option( self::LICENSE_KEY_OPTION_NAME );
124 if ( ! empty( $key ) ) {
125 $this->activate_license( $key );
126 $license = get_transient( self::TRANSIENT_NAME );
127 }
128 }
129
130 return $license;
131 }
132
133 /**
134 * Try to activate the license.
135 *
136 * @param string $key The license key to activate.
137 */
138 public function activate_license( $key ) {
139 // Data to send in our API request.
140 $api_params = [
141 'edd_action' => 'activate_license',
142 'license' => $key,
143 'item_name' => rawurlencode( $this->product_slug ),
144 'url' => home_url(),
145 ];
146
147 // Call the Block Lab store's API.
148 $response = wp_remote_post(
149 $this->store_url,
150 [
151 'timeout' => 10,
152 'sslverify' => true,
153 'body' => $api_params,
154 ]
155 );
156
157 if ( is_wp_error( $response ) ) {
158 $license = [ 'license' => self::REQUEST_FAILED ];
159 } else {
160 $license = json_decode( wp_remote_retrieve_body( $response ), true );
161 }
162
163 $expiration = DAY_IN_SECONDS;
164
165 set_transient( self::TRANSIENT_NAME, $license, $expiration );
166 }
167
168 /**
169 * Admin notice for correct license details.
170 *
171 * @return string
172 */
173 public function license_success_message() {
174 $message = __( 'Your Block Lab license was successfully activated!', 'block-lab' );
175 return sprintf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( $message ) );
176 }
177
178 /**
179 * Admin notice for the license request failing.
180 *
181 * This is for when the validation request fails entirely, like with a 404.
182 * Not for when it returns that the license is invalid.
183 *
184 * @return string
185 */
186 public function license_request_failed_message() {
187 $message = sprintf(
188 /* translators: %s is an HTML link to contact support */
189 __( 'There was a problem activating the license, but it may not be invalid. If the problem persists, please %s.', 'block-lab' ),
190 sprintf(
191 '<a href="%1$s">%2$s</a>',
192 'mailto:hi@getblocklab.com?subject=There was a problem activating my Block Lab Pro license',
193 esc_html__( 'contact support', 'block-lab' )
194 )
195 );
196
197 return sprintf( '<div class="notice notice-error"><p>%s</p></div>', wp_kses_post( $message ) );
198 }
199
200 /**
201 * Admin notice for incorrect license details.
202 *
203 * @return string
204 */
205 public function license_invalid_message() {
206 $message = __( 'There was a problem activating your Block Lab license.', 'block-lab' );
207 return sprintf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $message ) );
208 }
209 }
1 <?php
2 /**
3 * User onboarding.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2018, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13 use Block_Lab\Blocks\Block;
14
15 /**
16 * Class Onboarding
17 */
18 class Onboarding extends Component_Abstract {
19
20 /**
21 * Option name.
22 *
23 * @var string
24 */
25 public $option = 'block_lab_example_post_id';
26
27 /**
28 * Register any hooks that this component needs.
29 */
30 public function register_hooks() {
31 add_action( 'current_screen', [ $this, 'admin_notices' ] );
32 }
33
34 /**
35 * Runs during plugin activation.
36 */
37 public function plugin_activation() {
38 $this->add_dummy_data();
39 $this->prepare_welcome_notice();
40 }
41
42 /**
43 * Prepare onboarding notices.
44 */
45 public function admin_notices() {
46 $example_post_id = get_option( $this->option );
47
48 if ( ! $example_post_id ) {
49 return;
50 }
51
52 $screen = get_current_screen();
53 $slug = block_lab()->get_post_type_slug();
54
55 if ( ! is_object( $screen ) ) {
56 return;
57 }
58
59 $post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
60
61 /*
62 * On the edit post screen, editing the Example Block.
63 */
64 if ( $slug === $screen->id && 'post' === $screen->base && $post_id === $example_post_id ) {
65 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
66 add_action( 'block_lab_before_fields_list', [ $this, 'show_add_to_post_notice' ] );
67 }
68
69 if ( 'draft' !== get_post_status( $example_post_id ) ) {
70 return;
71 }
72
73 /*
74 * On the plugins screen, immediately after activating Block Lab.
75 */
76 if ( 'plugins' === $screen->id && 'true' === get_transient( 'block_lab_show_welcome' ) ) {
77 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
78 add_action( 'admin_notices', [ $this, 'show_welcome_notice' ] );
79 }
80
81 /*
82 * On the All Blocks screen, when a draft Example Block exists.
83 */
84 if ( "edit-$slug" === $screen->id ) {
85 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
86 add_action( 'admin_notices', [ $this, 'show_edit_block_notice' ] );
87 }
88
89 /*
90 * On the edit post screen, editing the draft Example Block.
91 */
92 if ( $slug === $screen->id && 'post' === $screen->base && $post_id === $example_post_id ) {
93 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
94 add_action( 'edit_form_advanced', [ $this, 'show_add_fields_notice' ] );
95 add_action( 'edit_form_before_permalink', [ $this, 'show_publish_notice' ] );
96
97 add_action(
98 'add_meta_boxes',
99 function() use ( $slug ) {
100 remove_meta_box( 'block_template', $slug, 'normal' );
101 },
102 20
103 );
104 }
105 }
106
107 /**
108 * Prepare the welcome notice on plugin activation.
109 *
110 * We can't hook into admin_notices at this point, so instead we set a short
111 * transient, and check that transient during the next page load.
112 */
113 public function prepare_welcome_notice() {
114 set_transient( 'block_lab_show_welcome', 'true', 1 );
115 }
116
117 /**
118 * Enqueue scripts and styles used by the onboarding screens.
119 *
120 * @return void
121 */
122 public function enqueue_scripts() {
123 wp_enqueue_style(
124 'block-lab-onboarding-css',
125 $this->plugin->get_url( 'css/admin.onboarding.css' ),
126 [],
127 $this->plugin->get_version()
128 );
129 }
130
131 /**
132 * Render the Welcome message.
133 */
134 public function show_welcome_notice() {
135 $example_post_id = get_option( $this->option );
136
137 if ( ! $example_post_id ) {
138 return;
139 }
140 ?>
141 <div class="block-lab-welcome block-lab-notice notice is-dismissible">
142 <h2>🖖 <?php esc_html_e( 'Welcome, traveller!', 'block-lab' ); ?></h2>
143 <p class="intro"><?php esc_html_e( 'Block Lab makes it super easy to build custom blocks for the WordPress editor.', 'block-lab' ); ?></p>
144 <p><strong><?php esc_html_e( 'Want to see how it\'s done?', 'block-lab' ); ?></strong> <?php esc_html_e( 'Here\'s one I prepared earlier.', 'block-lab' ); ?></p>
145 <?php
146 edit_post_link(
147 __( 'Let\'s get started!', 'block-lab' ),
148 '<p>',
149 '</p>',
150 $example_post_id,
151 'button button--white button_cta'
152 );
153 ?>
154 <p class="ps"><?php esc_html_e( 'P.S. We don\'t like to nag. This message won\'t be shown again.', 'block-lab' ); ?></p>
155 </div>
156 <?php
157 }
158
159 /**
160 * Render the Edit Your First Block message.
161 */
162 public function show_edit_block_notice() {
163 $example_post_id = get_option( 'block_lab_example_post_id' );
164
165 if ( ! $example_post_id ) {
166 return;
167 }
168 ?>
169 <div class="block-lab-edit-block block-lab-notice notice">
170 <h2>👩‍🔬 <?php echo esc_html_e( 'Ready to begin?', 'block-lab' ); ?></h2>
171 <p class="intro">
172 <?php
173 echo wp_kses_post(
174 sprintf(
175 // translators: Placeholders are <strong> html tags.
176 __( 'We created this %1$sExample Block%2$s to show you just how easy it is to get started.', 'block-lab' ),
177 '<strong>',
178 '</strong>'
179 )
180 );
181 ?>
182 </p>
183 <p>
184 <?php
185 echo wp_kses_post(
186 sprintf(
187 // translators: Placeholders are <strong> and <a> html tags.
188 __( 'You can %1$sEdit%2$s the block to learn more, or just %3$sTrash%4$s it to dismiss this message.', 'block-lab' ),
189 '<strong>',
190 '</strong>',
191 '<a href="' . get_delete_post_link( $example_post_id ) . '" class="trash">',
192 '</a>'
193 )
194 );
195 ?>
196 </p>
197 </div>
198 <?php
199 }
200
201 /**
202 * Render the Add Fields message.
203 */
204 public function show_add_fields_notice() {
205 $post = get_post();
206 $block = new Block( $post->ID );
207
208 /*
209 * We add 4 fields to our Example Block in add_dummy_data().
210 */
211 if ( count( $block->fields ) > 4 ) {
212 return;
213 }
214 ?>
215 <div class="block-lab-add-fields block-lab-notice">
216 <h2>🧐 <?php esc_html_e( 'Try adding a field.', 'block-lab' ); ?></h2>
217 <p><?php esc_html_e( 'Fields let you define the options you see when adding your block to a post.', 'block-lab' ); ?></p>
218 </div>
219 <?php
220 }
221
222 /**
223 * Render the Add Fields message.
224 */
225 public function show_publish_notice() {
226 $block = new Block( get_the_ID() );
227
228 /**
229 * We add 4 fields to our Example Block in add_dummy_data().
230 */
231 if ( count( $block->fields ) > 4 ) {
232 return;
233 }
234 ?>
235 <div class="block-lab-publish block-lab-notice">
236 <h2>🧪 <?php esc_html_e( 'Time to experiment!', 'block-lab' ); ?></h2>
237 <ol class="intro">
238 <li><?php esc_html_e( 'Choose an icon', 'block-lab' ); ?></li>
239 <li><?php esc_html_e( 'Change the category', 'block-lab' ); ?></li>
240 <li><?php esc_html_e( 'Investigate a few different field types', 'block-lab' ); ?></li>
241 </ol>
242 <p>
243 <?php
244 echo wp_kses_post(
245 sprintf(
246 // translators: Placeholders are <strong> html tags.
247 __( 'When you\'re ready, save your block by pressing %1$sPublish%2$s.', 'block-lab' ),
248 '<strong>',
249 '</strong>'
250 )
251 );
252 ?>
253 </p>
254 </div>
255 <?php
256 }
257
258 /**
259 * Render the Add to Post message.
260 */
261 public function show_add_to_post_notice() {
262 $post = get_post();
263
264 if ( ! $post || ! isset( $post->post_name ) || empty( $post->post_name ) ) {
265 return;
266 }
267
268 if ( 'publish' !== $post->post_status ) {
269 return;
270 }
271
272 $template = block_lab()->locate_template( "blocks/block-{$post->post_name}.php", '', true );
273
274 if ( ! $template ) {
275 return;
276 }
277 ?>
278 <div class="block-lab-add-to-block block-lab-notice notice notice-large is-dismissible">
279 <h2>🚀 <?php esc_html_e( 'Only one thing left to do!', 'block-lab' ); ?></h2>
280 <p class="intro"><?php esc_html_e( 'You\'ve created a new block, and added a block template. Well done!', 'block-lab' ); ?></p>
281 <p><?php esc_html_e( 'All that\'s left is to add your block to a post.', 'block-lab' ); ?></p>
282 <a href="<?php echo esc_attr( admin_url( 'post-new.php' ) ); ?>" class="button">
283 <?php esc_html_e( 'Add New Post', 'block-lab' ); ?>
284 </a>
285 </div>
286 <?php
287 /*
288 * After we've shown the Add to Post message once, we can delete the option. This will
289 * ensure that no further onboarding messages are shown.
290 */
291 delete_option( $this->option );
292 }
293
294 /**
295 * Create a dummy starter block when the plugin is activated for the first time.
296 */
297 public function add_dummy_data() {
298 /*
299 * Check if there are any block posts already added, and if so, bail.
300 * Note: wp_count_posts() does not work here.
301 */
302 $blocks = get_posts(
303 [
304 'post_type' => block_lab()->get_post_type_slug(),
305 'numberposts' => '1',
306 'post_status' => 'any',
307 'fields' => 'ids',
308 ]
309 );
310
311 if ( count( $blocks ) > 0 ) {
312 return;
313 }
314
315 $categories = get_block_categories( get_post() );
316
317 $example_post_id = wp_insert_post(
318 [
319 'post_title' => __( 'Example Block', 'block-lab' ),
320 'post_name' => 'example-block',
321 'post_status' => 'draft',
322 'post_type' => block_lab()->get_post_type_slug(),
323 'post_content' => wp_json_encode(
324 [
325 'block-lab\/example-block' => [
326 'name' => 'example-block',
327 'title' => __( 'Example Block', 'block-lab' ),
328 'icon' => 'block_lab',
329 'category' => isset( $categories[0] ) ? $categories[0] : [],
330 'keywords' => [
331 __( 'sample', 'block-lab' ), // translators: A keyword, used for search.
332 __( 'tutorial', 'block-lab' ), // translators: A keyword, used for search.
333 __( 'template', 'block-lab' ), // translators: A keyword, used for search.
334 ],
335 'fields' => [
336 'title' => [
337 'name' => 'title',
338 'label' => __( 'Title', 'block-lab' ),
339 'control' => 'text',
340 'type' => 'string',
341 'location' => 'editor',
342 'order' => 0,
343 'help' => __( 'The primary display text', 'block-lab' ),
344 'default' => '',
345 'placeholder' => '',
346 'maxlength' => null,
347 ],
348 'description' => [
349 'name' => 'description',
350 'label' => __( 'Description', 'block-lab' ),
351 'control' => 'textarea',
352 'type' => 'string',
353 'location' => 'editor',
354 'order' => 1,
355 'help' => '',
356 'default' => '',
357 'placeholder' => '',
358 'maxlength' => null,
359 'number_rows' => 4,
360 ],
361 'button-text' => [
362 'name' => 'button-text',
363 'label' => __( 'Button Text', 'block-lab' ),
364 'control' => 'text',
365 'type' => 'string',
366 'location' => 'editor',
367 'order' => 2,
368 'help' => __( 'A Call-to-Action', 'block-lab' ),
369 'default' => '',
370 'placeholder' => '',
371 'maxlength' => null,
372 ],
373 'button-link' => [
374 'name' => 'button-link',
375 'label' => __( 'Button Link', 'block-lab' ),
376 'control' => 'url',
377 'type' => 'string',
378 'location' => 'editor',
379 'order' => 3,
380 'help' => __( 'The destination URL', 'block-lab' ),
381 'default' => '',
382 'placeholder' => '',
383 ],
384 ],
385 ],
386 ]
387 ),
388 ]
389 );
390
391 update_option( $this->option, $example_post_id );
392 }
393 }
1 <?php
2 /**
3 * Block Lab Settings.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13
14 /**
15 * Class Settings
16 */
17 class Settings extends Component_Abstract {
18
19 /**
20 * Page slug.
21 *
22 * @var string
23 */
24 public $slug = 'block-lab-settings';
25
26 /**
27 * Register any hooks that this component needs.
28 */
29 public function register_hooks() {
30 add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
31 add_action( 'admin_init', [ $this, 'register_settings' ] );
32 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
33 add_action( 'admin_notices', [ $this, 'show_notices' ] );
34 }
35
36 /**
37 * Enqueue scripts and styles used by the Settings screen.
38 *
39 * @return void
40 */
41 public function enqueue_scripts() {
42 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
43
44 // Enqueue scripts and styles on the edit screen of the Block post type.
45 if ( $this->slug === $page ) {
46 wp_enqueue_style(
47 $this->slug,
48 $this->plugin->get_url( 'css/admin.settings.css' ),
49 [],
50 $this->plugin->get_version()
51 );
52 }
53 }
54
55 /**
56 * Add submenu pages to the Block Lab menu.
57 */
58 public function add_submenu_pages() {
59 add_submenu_page(
60 'edit.php?post_type=' . block_lab()->get_post_type_slug(),
61 __( 'Block Lab Settings', 'block-lab' ),
62 __( 'Settings', 'block-lab' ),
63 'manage_options',
64 $this->slug,
65 [ $this, 'render_page' ]
66 );
67 }
68
69 /**
70 * Register Block Lab settings.
71 */
72 public function register_settings() {
73 register_setting( 'block-lab-license-key', 'block_lab_license_key' );
74 }
75
76 /**
77 * Render the Settings page.
78 */
79 public function render_page() {
80 ?>
81 <div class="wrap block-lab-settings">
82 <?php
83 $this->render_page_header();
84 include block_lab()->get_path() . 'php/views/license.php';
85 ?>
86 </div>
87 <?php
88 }
89
90 /**
91 * Render the Settings page header.
92 */
93 public function render_page_header() {
94 ?>
95 <h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
96 <h2 class="nav-tab-wrapper">
97 <a href="<?php echo esc_url( add_query_arg( 'tab', 'license' ) ); ?>" title="<?php esc_attr_e( 'License', 'block-lab' ); ?>" class="nav-tab nav-tab-active dashicons-before dashicons-nametag">
98 <?php esc_html_e( 'License', 'block-lab' ); ?>
99 </a>
100 <a href="https://getblocklab.com/docs/" target="_blank" class="nav-tab dashicons-before dashicons-info">
101 <?php esc_html_e( 'Documentation', 'block-lab' ); ?>
102 </a>
103 <a href="https://wordpress.org/support/plugin/block-lab/" target="_blank" class="nav-tab dashicons-before dashicons-sos">
104 <?php esc_html_e( 'Help', 'block-lab' ); ?>
105 </a>
106 </h2>
107 <?php
108 }
109
110 /**
111 * Prepare notices to be displayed after saving the settings.
112 *
113 * @param string $notice The notice text to display.
114 */
115 public function prepare_notice( $notice ) {
116 $notices = get_option( 'block_lab_notices', [] );
117 $notices[] = $notice;
118 update_option( 'block_lab_notices', $notices );
119 }
120
121 /**
122 * Show any admin notices after saving the settings.
123 */
124 public function show_notices() {
125 $notices = get_option( 'block_lab_notices', [] );
126
127 if ( empty( $notices ) || ! is_array( $notices ) ) {
128 return;
129 }
130
131 foreach ( $notices as $notice ) {
132 echo wp_kses_post( $notice );
133 }
134
135 delete_option( 'block_lab_notices' );
136 }
137 }
1 <?php
2 /**
3 * Block Lab Upgrade Page.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin;
11
12 use Block_Lab\Component_Abstract;
13
14 /**
15 * Class Upgrade
16 */
17 class Upgrade extends Component_Abstract {
18
19 /**
20 * Page slug.
21 *
22 * @var string
23 */
24 public $slug = 'block-lab-pro';
25
26 /**
27 * Register any hooks that this component needs.
28 */
29 public function register_hooks() {
30 add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
31 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
32 }
33
34 /**
35 * Enqueue scripts and styles used by the Upgrade screen.
36 *
37 * @return void
38 */
39 public function enqueue_scripts() {
40 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
41
42 // Enqueue scripts and styles on the edit screen of the Block post type.
43 if ( $this->slug === $page ) {
44 wp_enqueue_style(
45 $this->slug,
46 $this->plugin->get_url( 'css/admin.upgrade.css' ),
47 [],
48 $this->plugin->get_version()
49 );
50 }
51 }
52
53 /**
54 * Add submenu pages to the Block Lab menu.
55 */
56 public function add_submenu_pages() {
57 add_submenu_page(
58 'edit.php?post_type=block_lab',
59 __( 'Block Lab Pro', 'block-lab' ),
60 __( 'Go Pro', 'block-lab' ),
61 'manage_options',
62 $this->slug,
63 [ $this, 'render_page' ]
64 );
65 }
66
67 /**
68 * Render the Upgrade page.
69 */
70 public function render_page() {
71 ?>
72 <div class="wrap block-lab-pro">
73 <h2 class="screen-reader-text"><?php echo esc_html( get_admin_page_title() ); ?></h2>
74 <?php include block_lab()->get_path() . 'php/views/upgrade.php'; ?>
75 </div>
76 <?php
77 }
78 }
1 <?php
2 /**
3 * Migration REST API endpoints.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use Plugin_Upgrader;
13 use WP_Ajax_Upgrader_Skin;
14 use WP_Error;
15 use WP_REST_Response;
16 use Block_Lab\Component_Abstract;
17
18 /**
19 * Class Post_Type
20 */
21 class Api extends Component_Abstract {
22
23 /**
24 * Adds the actions.
25 */
26 public function register_hooks() {
27 add_action( 'rest_api_init', [ $this, 'register_route_install_gcb' ] );
28 add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_content' ] );
29 add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_type' ] );
30 }
31
32 /**
33 * Registers a route to install and activate the plugin Genesis Custom Blocks.
34 */
35 public function register_route_install_gcb() {
36 register_rest_route(
37 block_lab()->get_slug(),
38 'install-activate-gcb',
39 [
40 'methods' => 'POST',
41 'callback' => [ $this, 'get_install_gcb_response' ],
42 'permission_callback' => function() {
43 return current_user_can( 'install_plugins' ) && current_user_can( 'activate_plugins' );
44 },
45 ]
46 );
47 }
48
49 /**
50 * Installs and activates Genesis Custom Blocks, and returns the result.
51 *
52 * @param array $data Data sent in the POST request.
53 * @return WP_REST_Response|WP_Error Response to the request.
54 */
55 public function get_install_gcb_response( $data ) {
56 unset( $data );
57
58 $installation_result = $this->install_plugin();
59 if ( is_wp_error( $installation_result ) ) {
60 return $installation_result;
61 }
62
63 $activation_result = $this->activate_plugin();
64 if ( is_wp_error( $activation_result ) ) {
65 return $activation_result;
66 }
67
68 return rest_ensure_response( [ 'message' => __( 'Plugin installed and activated', 'block-lab' ) ] );
69 }
70
71 /**
72 * Installs the new plugin.
73 *
74 * Mainly copied from Gutenberg, with slight changes.
75 * The main change being that it returns true
76 * if the plugin is already downloaded, not a WP_Error.
77 *
78 * @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L271-L369
79 * @return true|WP_Error True on success, WP_Error on failure.
80 */
81 private function install_plugin() {
82 global $wp_filesystem;
83
84 require_once ABSPATH . 'wp-admin/includes/file.php';
85 require_once ABSPATH . 'wp-admin/includes/plugin.php';
86 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
87 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
88 require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
89
90 // Check if the plugin is already installed.
91 if ( array_key_exists( $this->get_new_plugin_file(), get_plugins() ) ) {
92 return true;
93 }
94
95 // Verify filesystem is accessible first.
96 $filesystem_available = $this->is_filesystem_available();
97 if ( is_wp_error( $filesystem_available ) ) {
98 return $filesystem_available;
99 }
100
101 $download_link = $this->get_download_link();
102 if ( is_wp_error( $download_link ) ) {
103 return $download_link;
104 }
105
106 $skin = new WP_Ajax_Upgrader_Skin();
107 $upgrader = new Plugin_Upgrader( $skin );
108
109 $result = $upgrader->install( $download_link );
110
111 if ( is_wp_error( $result ) ) {
112 $result->add_data( [ 'status' => 500 ] );
113
114 return $result;
115 }
116
117 // This should be the same as $result above.
118 if ( is_wp_error( $skin->result ) ) {
119 $skin->result->add_data( [ 'status' => 500 ] );
120
121 return $skin->result;
122 }
123
124 if ( $skin->get_errors()->has_errors() ) {
125 $error = $skin->get_errors();
126 $error->add_data( [ 'status' => 500 ] );
127
128 return $error;
129 }
130
131 if ( is_null( $result ) ) {
132 // Pass through the error from WP_Filesystem if one was raised.
133 if ( $wp_filesystem instanceof WP_Filesystem_Base && isset( $wp_filesystem->errors ) && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
134 return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), [ 'status' => 500 ] );
135 }
136
137 return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'block-lab' ), [ 'status' => 500 ] );
138 }
139
140 $file = $upgrader->plugin_info();
141 if ( ! $file ) {
142 return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.', 'block-lab' ), [ 'status' => 500 ] );
143 }
144
145 return true;
146 }
147
148 /**
149 * Determines if the filesystem is available.
150 *
151 * Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
152 * Copied from Gutenberg.
153 *
154 * @see https://github.com/WordPress/gutenberg/blob/8d64aa3092d5d9e841895bf2d495565c9a770238/lib/class-wp-rest-plugins-controller.php#L799-L815
155 *
156 * @return true|WP_Error True if filesystem is available, WP_Error otherwise.
157 */
158 private function is_filesystem_available() {
159 if ( 'direct' === get_filesystem_method() ) {
160 return true;
161 }
162
163 ob_start();
164 $filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
165 ob_end_clean();
166
167 if ( $filesystem_credentials_are_stored ) {
168 return true;
169 }
170
171 return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.', 'block-lab' ), [ 'status' => 500 ] );
172 }
173
174 /**
175 * Gets the GCB Pro download link.
176 *
177 * @return string|WP_Error The download link, or a WP_Error.
178 */
179 public function get_download_link() {
180 if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
181 return get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
182 } else {
183 $api = plugins_api(
184 'plugin_information',
185 [
186 'slug' => 'genesis-custom-blocks',
187 'fields' => [
188 'sections' => false,
189 ],
190 ]
191 );
192
193 if ( is_wp_error( $api ) ) {
194 if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
195 $api->add_data( [ 'status' => 404 ] );
196 } else {
197 $api->add_data( [ 'status' => 500 ] );
198 }
199
200 return $api;
201 }
202
203 if ( empty( $api->download_link ) ) {
204 return new WP_Error(
205 'no_download_link',
206 __( 'There was no download_link in the API', 'block-lab' )
207 );
208 }
209
210 return $api->download_link;
211 }
212 }
213
214 /**
215 * Activates the new plugin.
216 *
217 * Mainly copied from Gutenberg's WP_REST_Plugins_Controller::handle_plugin_status().
218 *
219 * @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L679-L709
220 *
221 * @return true|WP_Error True on success, WP_Error on failure.
222 */
223 private function activate_plugin() {
224 $activation_result = activate_plugin( $this->get_new_plugin_file(), '', false, true );
225 if ( is_wp_error( $activation_result ) ) {
226 $activation_result->add_data( [ 'status' => 500 ] );
227 return $activation_result;
228 }
229
230 return true;
231 }
232
233 /**
234 * Registers a route to migrate the post content to the new namespace.
235 */
236 public function register_route_migrate_post_content() {
237 register_rest_route(
238 block_lab()->get_slug(),
239 'migrate-post-content',
240 [
241 'methods' => 'POST',
242 'callback' => [ $this, 'get_migrate_post_content_response' ],
243 'permission_callback' => function() {
244 return current_user_can( Submenu::MIGRATION_CAPABILITY );
245 },
246 ]
247 );
248 }
249
250 /**
251 * Gets the REST API response for the post content migration.
252 *
253 * @return WP_REST_Response The response to the request.
254 */
255 public function get_migrate_post_content_response() {
256 return rest_ensure_response( ( new Post_Content( 'block-lab', 'genesis-custom-blocks' ) )->migrate_all() );
257 }
258
259 /**
260 * Registers a route to migrate the post type.
261 */
262 public function register_route_migrate_post_type() {
263 register_rest_route(
264 block_lab()->get_slug(),
265 'migrate-post-type',
266 [
267 'methods' => 'POST',
268 'callback' => [ $this, 'get_migrate_post_type_response' ],
269 'permission_callback' => function() {
270 return current_user_can( Submenu::MIGRATION_CAPABILITY );
271 },
272 ]
273 );
274 }
275
276 /**
277 * Gets the REST API response for the post type migration.
278 *
279 * @return WP_REST_Response The response to the request.
280 */
281 public function get_migrate_post_type_response() {
282 return rest_ensure_response( ( new Post_Type( 'block_lab', 'block-lab', 'block_lab', 'genesis_custom_block', 'genesis-custom-blocks', 'genesis_custom_blocks' ) )->migrate_all() );
283 }
284
285 /**
286 * Gets the directory and file of the new plugin to install.
287 *
288 * @return string The plugin file.
289 */
290 public function get_new_plugin_file() {
291 if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
292 return 'genesis-custom-blocks-pro/genesis-custom-blocks-pro.php';
293 }
294
295 return 'genesis-custom-blocks/genesis-custom-blocks.php';
296 }
297 }
1 <?php
2 /**
3 * Displays a migration notice.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use Block_Lab\Component_Abstract;
13
14 /**
15 * Class Notice
16 */
17 class Notice extends Component_Abstract {
18
19 /**
20 * The AJAX action to dismiss the migration notice.
21 *
22 * @var string
23 */
24 const NOTICE_AJAX_ACTION = 'bl_dismiss_migration_notice';
25
26 /**
27 * The action of the migration notice nonce.
28 *
29 * @var string
30 */
31 const NOTICE_NONCE_ACTION = 'bl-migration-nonce';
32
33 /**
34 * The name of the migration notice nonce.
35 *
36 * @var string
37 */
38 const NOTICE_NONCE_NAME = 'bl-migration-nonce-name';
39
40 /**
41 * The slug of the stylesheet for the migration notice.
42 *
43 * @var string
44 */
45 const NOTICE_STYLE_SLUG = 'block-lab-migration-notice-style';
46
47 /**
48 * The slug of the script for the migration notice.
49 *
50 * @var string
51 */
52 const NOTICE_SCRIPT_SLUG = 'block-lab-migration-notice-script';
53
54 /**
55 * The user meta key to store whether a user has dismissed the migration notice.
56 *
57 * @var string
58 */
59 const NOTICE_USER_META_KEY = 'block_lab_show_migration_notice';
60
61 /**
62 * The user meta value stored if a user has dismissed the migration notice.
63 *
64 * @var string
65 */
66 const NOTICE_DISMISSED_META_VALUE = 'dismissed';
67
68 /**
69 * The capability required to see the notice.
70 *
71 * @var string
72 */
73 const NOTICE_CAPABILITY = 'install_plugins';
74
75 /**
76 * Adds an action for the notice.
77 */
78 public function register_hooks() {
79 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
80 add_action( 'wp_ajax_' . self::NOTICE_AJAX_ACTION, [ $this, 'ajax_handler_migration_notice' ] );
81 }
82
83 /**
84 * Outputs the migration notice if this is on the right page and the user has the right permission.
85 */
86 public function render_migration_notice() {
87 if ( ! $this->should_display_migration_notice() ) {
88 return;
89 }
90
91 // @todo: verify that this doc page exists, or change it to one that does exist.
92 $learn_more_link = 'https://getblocklab.com/docs/genesis-custom-blocks';
93 $migration_url = add_query_arg(
94 [
95 'post_type' => block_lab()->get_post_type_slug(),
96 'page' => 'block-lab-migration',
97 ],
98 admin_url( 'edit.php' )
99 );
100
101 ?>
102 <div id="bl-migration-notice" class="notice notice-info bl-notice-migration">
103 <?php wp_nonce_field( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME, false ); ?>
104 <div class="bl-migration-copy">
105 <p>
106 <?php
107 printf(
108 /* translators: %1$s: the plugin name */
109 esc_html__( 'The Block Lab team have moved. For future updates and improvements, migrate now to the new home of custom blocks: %1$s.', 'block-lab' ),
110 sprintf(
111 '<strong>%1$s</strong>',
112 esc_html__( 'Genesis Custom Blocks', 'block-lab' )
113 )
114 );
115 ?>
116 <a target="_blank" rel="noopener noreferrer" class="bl-notice-migration__learn-more" href="<?php echo esc_url( $learn_more_link ); ?>">
117 <?php esc_html_e( 'Learn more', 'block-lab' ); ?>
118 </a>
119 </p>
120 </div>
121 <button id="bl-notice-not-now" href="#" class="bl-notice-option button button-secondary">
122 <?php esc_html_e( 'Not Now', 'block-lab' ); ?>
123 </button>
124 <a href="<?php echo esc_url( $migration_url ); ?>" class="bl-notice-option button button-primary">
125 <?php esc_html_e( 'Migrate', 'block-lab' ); ?>
126 </a>
127 </div>
128 <div id="bl-not-now-notice" class="notice notice-info bl-notice-migration bl-hidden">
129 <div class="bl-migration-copy">
130 <p><?php esc_html_e( "When you're ready, our migration tool is available in the main menu, under Block Lab > Migrate.", 'block-lab' ); ?></p>
131 </div>
132 <a href="#" id="bl-notice-ok" class="bl-notice-option">
133 <?php esc_html_e( 'Okay', 'block-lab' ); ?>
134 </a>
135 </div>
136 <?php
137 }
138
139 /**
140 * Enqueues the migration notice assets.
141 */
142 public function enqueue_assets() {
143 if ( ! $this->should_display_migration_notice() ) {
144 return;
145 }
146
147 wp_enqueue_style(
148 self::NOTICE_STYLE_SLUG,
149 $this->plugin->get_url( 'css/admin.migration-notice.css' ),
150 [],
151 $this->plugin->get_version()
152 );
153
154 wp_enqueue_script(
155 self::NOTICE_SCRIPT_SLUG,
156 $this->plugin->get_url( 'js/admin.migration-notice.js' ),
157 [],
158 $this->plugin->get_version(),
159 true
160 );
161 }
162
163 /**
164 * Gets whether the migration notice should display.
165 *
166 * This should display on Block Lab > Content Blocks,
167 * /wp-admin/plugins.php, the Dashboard, and Block Lab > Settings.
168 *
169 * @return bool Whether the migration notice should display.
170 */
171 public function should_display_migration_notice() {
172 if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
173 return false;
174 }
175
176 // If the user has dismissed the notice, it shouldn't appear again.
177 if ( self::NOTICE_DISMISSED_META_VALUE === get_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, true ) ) {
178 return false;
179 }
180
181 $screen = get_current_screen();
182 return (
183 ( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && 'block_lab' === $screen->post_type )
184 ||
185 ( isset( $screen->base ) && in_array( $screen->base, [ 'plugins', 'dashboard', 'block_lab_page_block-lab-settings' ], true ) )
186 );
187 }
188
189 /**
190 * Handles an AJAX request to not display the notice.
191 *
192 * This stores in the user meta the fact that the notice was dismissed,
193 * so it's not displayed again.
194 */
195 public function ajax_handler_migration_notice() {
196 check_ajax_referer( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME );
197
198 if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
199 wp_send_json_error();
200 }
201
202 update_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, self::NOTICE_DISMISSED_META_VALUE );
203
204 wp_send_json_success();
205 }
206 }
1 <?php
2 /**
3 * Post_Content.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use WP_Error;
13
14 /**
15 * Class Post_Content
16 */
17 class Post_Content {
18
19 /**
20 * The previous namespace of the block.
21 *
22 * @var string
23 */
24 private $previous_block_namespace;
25
26 /**
27 * The new namespace of the block.
28 *
29 * @var string
30 */
31 private $new_block_namespace;
32
33 /**
34 * Post_Content constructor.
35 *
36 * @param string $previous_block_namespace Previous namespace of the blocks.
37 * @param string $new_block_namespace New namespace of the blocks.
38 */
39 public function __construct( $previous_block_namespace, $new_block_namespace ) {
40 $this->previous_block_namespace = $previous_block_namespace;
41 $this->new_block_namespace = $new_block_namespace;
42 }
43
44 /**
45 * Migrates all of the block namespaces in all of the posts that have Block Lab blocks.
46 *
47 * @return array|WP_Error The result of the migration, or a WP_Error if it failed.
48 */
49 public function migrate_all() {
50 $success_count = 0;
51 $error_count = 0;
52 $errors = new WP_Error();
53 $max_allowed_errors = 20;
54 $posts = $this->query_for_posts();
55
56 while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
57 foreach ( $posts as $post ) {
58 if ( isset( $post->ID ) ) {
59 $migrated_post = $this->migrate_single( $post->ID );
60 if ( is_wp_error( $migrated_post ) ) {
61 $error_count++;
62 $errors->add( $migrated_post->get_error_code(), $migrated_post->get_error_message() );
63 } else {
64 $success_count++;
65 }
66 }
67 }
68
69 $posts = $this->query_for_posts();
70 }
71
72 $is_success = $error_count < $max_allowed_errors;
73 if ( ! $is_success ) {
74 return $errors;
75 }
76
77 $results = [
78 'successCount' => $success_count,
79 'errorCount' => $error_count,
80 ];
81
82 if ( $errors->has_errors() ) {
83 $results['errorMessage'] = $errors->get_error_message();
84 }
85
86 return $results;
87 }
88
89 /**
90 * Migrates the block namespaces in post_content.
91 *
92 * Blocks are stored in the post_content of a post with a namespace,
93 * like '<!-- wp:block-lab/test-image {"example-image":8} /-->'.
94 * In that case, 'block-lab' needs to be changed to the new namespace.
95 * But nothing else in the block should be changed.
96 * The block pattern is mainly taken from Core.
97 *
98 * @see https://github.com/WordPress/wordpress-develop/blob/78d1ab2ed40093a5bd2a75b01ceea37811739f55/src/wp-includes/class-wp-block-parser.php#L413
99 *
100 * @param int $post_id The ID of the post to convert.
101 * @return int|WP_Error The post ID that was changed, or a WP_Error on failure.
102 */
103 public function migrate_single( $post_id ) {
104 $post = get_post( $post_id );
105 if ( ! isset( $post->ID ) ) {
106 return new WP_Error(
107 'invalid_post_id',
108 __( 'Invalid post ID', 'block-lab' )
109 );
110 }
111
112 $new_post_content = preg_replace(
113 '#(<!--\s+wp:)(' . sanitize_key( $this->previous_block_namespace ) . ')(/[a-z][a-z0-9_-]*)#s',
114 '$1' . sanitize_key( $this->new_block_namespace ) . '$3',
115 $post->post_content
116 );
117
118 return wp_update_post(
119 [
120 'ID' => $post->ID,
121 'post_content' => wp_slash( $new_post_content ),
122 ],
123 true
124 );
125 }
126
127 /**
128 * Gets posts that have Block Lab blocks in their post_content.
129 *
130 * Queries for posts that have wp:block-lab/ in the post content,
131 * meaning they probably have a Block Lab block.
132 * Excludes revision posts, as this could overwrite the entire history.
133 * This will allow users to go back to the content before it was migrated.
134 *
135 * @return array The posts that were found.
136 */
137 private function query_for_posts() {
138 global $wpdb;
139
140 $query_limit = 10;
141 return $wpdb->get_results(
142 $wpdb->prepare(
143 "SELECT * FROM {$wpdb->posts} WHERE post_type != %s AND post_content LIKE %s LIMIT %d",
144 'revision',
145 '%' . $wpdb->esc_like( 'wp:' . $this->previous_block_namespace . '/' ) . '%',
146 absint( $query_limit )
147 )
148 );
149 }
150 }
1 <?php
2 /**
3 * Post_Type.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use WP_Error;
13 use WP_Post;
14 use WP_Query;
15
16 /**
17 * Class Post_Type
18 */
19 class Post_Type {
20
21 /**
22 * The previous slug of the custom post type (in Block Lab).
23 *
24 * @var string
25 */
26 private $previous_post_type_slug;
27
28 /**
29 * The previous namespace of the block.
30 *
31 * @var string
32 */
33 private $previous_block_namespace;
34
35 /**
36 * The previous default block icon.
37 *
38 * @var string
39 */
40 private $previous_default_icon;
41
42 /**
43 * The new namespace of the block.
44 *
45 * @var string
46 */
47 private $new_block_namespace;
48
49 /**
50 * The new slug of the custom post type (not in Block Lab).
51 *
52 * @var string
53 */
54 private $new_post_type_slug;
55
56 /**
57 * The new default block icon.
58 *
59 * @var string
60 */
61 private $new_default_icon;
62
63 /**
64 * Post_Type constructor.
65 *
66 * @param string $previous_post_type_slug Previous slug of the custom post type.
67 * @param string $previous_block_namespace Previous block namespace.
68 * @param string $previous_default_icon Previous default block icon.
69 * @param string $new_post_type_slug New slug of the custom post type.
70 * @param string $new_block_namespace New namespace of the block.
71 * @param string $new_default_icon New default block icon.
72 */
73 public function __construct( $previous_post_type_slug, $previous_block_namespace, $previous_default_icon, $new_post_type_slug, $new_block_namespace, $new_default_icon ) {
74 $this->previous_post_type_slug = $previous_post_type_slug;
75 $this->previous_block_namespace = $previous_block_namespace;
76 $this->previous_default_icon = $previous_default_icon;
77 $this->new_post_type_slug = $new_post_type_slug;
78 $this->new_block_namespace = $new_block_namespace;
79 $this->new_default_icon = $new_default_icon;
80 }
81
82 /**
83 * Migrates all of the custom post type posts to the new post_type slug and block namespace.
84 *
85 * These each store a config for a custom block,
86 * they aren't blocks that users entered into the block editor.
87 *
88 * @return array|WP_Error An array on success, a WP_Error on failure.
89 */
90 public function migrate_all() {
91 $posts = $this->query_for_posts();
92 $success_count = 0;
93 $error_count = 0;
94 $max_allowed_errors = 20;
95 $errors = new WP_Error();
96
97 while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
98 foreach ( $posts as $post ) {
99 $migration_result = $this->migrate_single( $post );
100 if ( is_wp_error( $migration_result ) ) {
101 $error_count++;
102 $errors->add( $migration_result->get_error_code(), $migration_result->get_error_message() );
103 } else {
104 $success_count++;
105 }
106 }
107
108 $posts = $this->query_for_posts();
109 }
110
111 $is_success = ! empty( $success_count ) || ( empty( $success_count ) && empty( $error_count ) );
112
113 if ( ! $is_success ) {
114 return $errors;
115 }
116
117 return [
118 'successCount' => $success_count,
119 'errorCount' => $error_count,
120 ];
121 }
122
123 /**
124 * Migrates the custom post type post to the new post_type slug and block namespace.
125 *
126 * Inspired by the work of Weston Ruter: https://github.com/ampproject/amp-wp/blob/4880f0f58daaf07685854be8574ff25d76ff583e/includes/validation/class-amp-validated-url-post-type.php#L165-L170
127 * The post_content of the CPT has a configuration for a block like:
128 * '{"block-lab\/test-image":{"name":"test-image","title":"Test Image","excluded":[],"icon":"block_lab","category":{"slug":"common","title":"Common Blocks","icon":null},"keywords":[""],"fields":{"image":{"name":"image","label":"Image","control":"image","type":"integer","order":0,"location":"editor","width":"50","help":"Here is some help text"}}}}'
129 * The beginning of this has the 'block-lab' namespace, which this changes to the new namespace.
130 *
131 * @param WP_Post $post The post to convert.
132 * @return true|WP_Error True on success, WP_Error on failure.
133 */
134 public function migrate_single( WP_Post $post ) {
135 global $wpdb;
136
137 $block = json_decode( $post->post_content, true );
138 if ( JSON_ERROR_NONE !== json_last_error() || empty( $block ) ) {
139 return new WP_Error(
140 'block_invalid_json',
141 __( 'The block looks to be invalid JSON', 'block-lab' )
142 );
143 }
144
145 $block_keys = array_keys( $block );
146 $old_block_name = reset( $block_keys );
147 if ( empty( $block[ $old_block_name ] ) ) {
148 return new WP_Error(
149 'invalid_block_name',
150 __( 'The block name looks to be invalid', 'block-lab' )
151 );
152 }
153
154 $block_contents = $block[ $old_block_name ];
155 if ( isset( $block_contents['icon'] ) && $this->previous_default_icon === $block_contents['icon'] ) {
156 $block_contents['icon'] = $this->new_default_icon;
157 }
158
159 if ( empty( $block_contents['icon'] ) ) {
160 $block_contents['icon'] = $this->new_default_icon;
161 }
162
163 $new_block_name = preg_replace( '#^' . $this->previous_block_namespace . '(?=/)#', $this->new_block_namespace, $old_block_name );
164 $new_block = [ $new_block_name => $block_contents ];
165
166 $rows_updated = $wpdb->update(
167 $wpdb->posts,
168 [
169 'post_type' => sanitize_key( $this->new_post_type_slug ),
170 'post_content' => wp_json_encode( $new_block ),
171 ],
172 [
173 'ID' => $post->ID,
174 ]
175 );
176 clean_post_cache( $post->ID );
177
178 if ( empty( $rows_updated ) ) {
179 return new WP_Error(
180 'post_not_updated',
181 __( 'The post was not updated', 'block-lab' )
182 );
183 }
184
185 return true;
186 }
187
188 /**
189 * Gets the posts of the previous post_type.
190 *
191 * This query won't find posts that were already migrated, as the migration changes the 'post_type'.
192 * So this doesn't need an 'offset' parameter.
193 *
194 * @return WP_Post[] The posts that were found.
195 */
196 private function query_for_posts() {
197 $query = new WP_Query(
198 [
199 'post_type' => $this->previous_post_type_slug,
200 'posts_per_page' => 10,
201 'post_status' => 'any',
202 ]
203 );
204
205 return $query->posts;
206 }
207 }
1 <?php
2 /**
3 * Migration submenu.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use Block_Lab\Component_Abstract;
13 use Block_Lab\Admin\License;
14
15 /**
16 * Class Post_Type
17 */
18 class Submenu extends Component_Abstract {
19
20 /**
21 * The menu slug for the migration menu.
22 *
23 * @var string
24 */
25 const MENU_SLUG = 'block-lab-migration';
26
27 /**
28 * The user capability to migrate posts and post content.
29 *
30 * @var string
31 */
32 const MIGRATION_CAPABILITY = 'edit_others_posts';
33
34 /**
35 * The query var to deactivate this plugin and activate the new one.
36 *
37 * @var string
38 */
39 const QUERY_VAR_DEACTIVATE_AND_GCB_PAGE = 'bl_deactivate_and_gcb';
40
41 /**
42 * The query var to deactivate this plugin and activate the new one.
43 *
44 * @var string
45 */
46 const NONCE_ACTION_DEACTIVATE = 'deactivate_bl_and_activate_new';
47
48 /**
49 * Adds the actions.
50 */
51 public function register_hooks() {
52 add_action( 'admin_menu', [ $this, 'add_submenu_page' ], 9 );
53 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
54 add_action( 'admin_bar_init', [ $this, 'maybe_activate_plugin' ] );
55 }
56
57 /**
58 * Adds the submenu page for migration.
59 */
60 public function add_submenu_page() {
61 if ( $this->user_can_view_migration_page() ) {
62 add_submenu_page(
63 'edit.php?post_type=block_lab',
64 __( 'Migrate to Genesis Custom Blocks', 'block-lab' ),
65 __( 'Migrate', 'block-lab' ),
66 'manage_options',
67 self::MENU_SLUG,
68 [ $this, 'render_page' ]
69 );
70 }
71 }
72
73 /**
74 * Adds the scripts for the submenu.
75 */
76 public function enqueue_scripts() {
77 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
78
79 // Only enqueue if on the migration page.
80 if ( self::MENU_SLUG === $page && $this->user_can_view_migration_page() ) {
81 wp_enqueue_style(
82 self::MENU_SLUG,
83 block_lab()->get_url( 'css/admin.migration.css' ),
84 [],
85 block_lab()->get_version()
86 );
87
88 $script_config = require block_lab()->get_path( 'js/admin.migration.asset.php' );
89 wp_enqueue_script(
90 self::MENU_SLUG,
91 block_lab()->get_url( 'js/admin.migration.js' ),
92 $script_config['dependencies'],
93 $script_config['version'],
94 true
95 );
96
97 $gcb_url = add_query_arg(
98 [
99 self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE => true,
100 '_wpnonce' => wp_create_nonce( self::NONCE_ACTION_DEACTIVATE ),
101 ],
102 admin_url()
103 );
104
105 $is_pro = block_lab()->is_pro();
106 $genesis_pro_subscription_key = get_option( Subscription_Api::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
107 $script_data = [
108 'isPro' => $is_pro,
109 'gcbUrl' => $gcb_url,
110 ];
111
112 if ( $genesis_pro_subscription_key ) {
113 $script_data['genesisProKey'] = $genesis_pro_subscription_key;
114 }
115
116 wp_add_inline_script(
117 self::MENU_SLUG,
118 'const blockLabMigration = ' . wp_json_encode( $script_data ) . ';',
119 'before'
120 );
121 }
122 }
123
124 /**
125 * Gets whether the current user can view the migration page.
126 *
127 * @return bool Whether the user can view the migration page.
128 */
129 public function user_can_view_migration_page() {
130 return current_user_can( 'install_plugins' ) && current_user_can( self::MIGRATION_CAPABILITY );
131 }
132
133 /**
134 * Renders the submenu page.
135 */
136 public function render_page() {
137 echo '<div class="bl-migration__content"></div>';
138 }
139
140 /**
141 * Conditionally deactivates this plugin and goes to the Genesis Custom Blocks page.
142 *
143 * The logic to deactivate the plugin was mainly copied from Core.
144 * https://github.com/WordPress/wordpress-develop/blob/61803a37a41eca95efe964c7e02c768de6df75fa/src/wp-admin/plugins.php#L196-L221
145 */
146 public function maybe_activate_plugin() {
147 $previous_plugin_file = 'block-lab/block-lab.php';
148
149 if ( empty( $_GET[ self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE ] ) ) {
150 return;
151 }
152
153 if ( ! current_user_can( 'deactivate_plugin', $previous_plugin_file ) ) {
154 wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this plugin.', 'block-lab' ) );
155 }
156
157 check_admin_referer( self::NONCE_ACTION_DEACTIVATE );
158
159 if ( ! is_network_admin() && is_plugin_active_for_network( $previous_plugin_file ) ) {
160 wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this network-active plugin.', 'block-lab' ) );
161 }
162
163 deactivate_plugins( $previous_plugin_file, false, is_network_admin() );
164
165 if ( ! is_network_admin() ) {
166 update_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_option( 'recently_activated' ) );
167 } else {
168 update_site_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_site_option( 'recently_activated' ) );
169 }
170
171 // Go to the Genesis Custom Blocks page.
172 wp_safe_redirect(
173 add_query_arg(
174 'post_type',
175 'genesis_custom_block',
176 admin_url( 'edit.php' )
177 )
178 );
179 }
180 }
1 <?php
2 /**
3 * Verifies the Genesis Pro subscription, and saves the link to download GCB Pro.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use WP_Error;
13 use WP_REST_Request;
14 use WP_REST_Response;
15 use Block_Lab\Component_Abstract;
16
17 /**
18 * Class Subscription_Api
19 */
20 class Subscription_Api extends Component_Abstract {
21
22 /**
23 * Option name where the subscription key is stored for Genesis Pro plugins.
24 *
25 * @var string
26 */
27 const OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY = 'genesis_pro_subscription_key';
28
29 /**
30 * Transient name where the subscription endpoint response is stored.
31 *
32 * @var string
33 */
34 const TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK = 'genesis_custom_blocks_pro_download_link';
35
36 /**
37 * Adds the component action.
38 */
39 public function register_hooks() {
40 add_action( 'rest_api_init', [ $this, 'register_route_update_subscription_key' ] );
41 }
42
43 /**
44 * Registers a route to update the subscription key.
45 */
46 public function register_route_update_subscription_key() {
47 register_rest_route(
48 block_lab()->get_slug(),
49 'update-subscription-key',
50 [
51 'methods' => 'POST',
52 'callback' => [ $this, 'get_update_subscription_key_response' ],
53 'permission_callback' => function() {
54 return current_user_can( 'manage_options' );
55 },
56 'accept_json' => true,
57 ]
58 );
59 }
60
61 /**
62 * Gets the REST API response to the request to update the subscription key.
63 *
64 * @param WP_REST_Request $data Data sent in the POST request.
65 * @return WP_REST_Response|WP_Error A WP_REST_Response on success, WP_Error on failure.
66 */
67 public function get_update_subscription_key_response( $data ) {
68 $key = $data->get_param( 'subscriptionKey' );
69
70 if ( empty( $key ) ) {
71 $this->delete_subscription_data();
72 return new WP_Error( 'empty_subscription_key', __( 'Empty subscription key', 'block-lab' ) );
73 }
74
75 $sanitized_key = $this->sanitize_subscription_key( $key );
76 $subscription_response = $this->get_subscription_response( $sanitized_key );
77 if ( $subscription_response->is_valid() && ! empty( $subscription_response->get_product_info()->download_link ) ) {
78 $was_option_update_successful = update_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY, $sanitized_key );
79
80 if ( ! $was_option_update_successful ) {
81 $existing_option = get_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
82
83 // update_option() will return false when trying to save the same option that's already saved.
84 // In that case, there's no need for an error, but any other failure should be an error.
85 if ( $sanitized_key !== $existing_option ) {
86 $this->delete_subscription_data();
87 return new WP_Error( 'option_not_updated', __( 'The option was not updated', 'block-lab' ) );
88 }
89 }
90
91 set_transient(
92 self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK,
93 esc_url_raw( $subscription_response->get_product_info()->download_link )
94 );
95
96 return rest_ensure_response( [ 'success' => true ] );
97 } else {
98 $this->delete_subscription_data();
99 return new WP_Error(
100 $subscription_response->get_error_code(),
101 $this->get_subscription_invalid_message( $subscription_response->get_error_code() )
102 );
103 }
104 }
105
106 /**
107 * Deletes the stored Genesis Pro key and the GCB Pro download link.
108 */
109 public function delete_subscription_data() {
110 delete_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
111 delete_transient( self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
112 }
113
114 /**
115 * Gets a new subscription response.
116 *
117 * @param string $key The subscription key to check.
118 * @return Subscription_Response The subscription response.
119 */
120 public function get_subscription_response( $key ) {
121 return new Subscription_Response( $key );
122 }
123
124 /**
125 * Admin message for incorrect subscription details.
126 *
127 * @param string $error_code The error code from the endpoint.
128 * @return string The error message to display.
129 */
130 public function get_subscription_invalid_message( $error_code ) {
131 switch ( $error_code ) {
132 case 'key-unknown':
133 return esc_html__( 'The subscription key you entered appears to be invalid or is not associated with this product. Please verify the key you have saved here matches the key in your WP Engine Account Portal.', 'block-lab' );
134
135 case 'key-invalid':
136 return esc_html__( 'The subscription key you entered is invalid. Get your subscription key in the WP Engine Account Portal.', 'block-lab' );
137
138 case 'key-deleted':
139 return esc_html__( 'Your subscription key was regenerated in the WP Engine Account Portal but was not updated in this settings page. Update your subscription key here to receive updates.', 'block-lab' );
140
141 case 'subscription-expired':
142 return esc_html__( 'Your Genesis Pro subscription has expired. Please renew it.', 'block-lab' );
143
144 case 'subscription-notfound':
145 return esc_html__( 'A valid subscription for your subscription key was not found. Please contact support.', 'block-lab' );
146
147 case 'product-unknown':
148 return esc_html__( 'The product you requested information for is unknown. Please contact support.', 'block-lab' );
149
150 default:
151 return esc_html__( 'There was an unknown error connecting to the update service. Please ensure the key you have saved here matches the key in your WP Engine Account Portal. This issue could be temporary. Please contact support if this error persists.', 'block-lab' );
152 }
153 }
154
155 /**
156 * Gets the sanitized subscription key.
157 *
158 * @param string $subscription_key The subscription key.
159 * @return string The sanitized key.
160 */
161 public function sanitize_subscription_key( $subscription_key ) {
162 return preg_replace( '/[^A-Za-z0-9_-]/', '', $subscription_key );
163 }
164 }
1 <?php
2 /**
3 * The Genesis Pro subscription response.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Admin\Migration;
11
12 use stdClass;
13
14 /**
15 * Class Subscription_Response
16 */
17 class Subscription_Response {
18
19 /**
20 * Endpoint to validate the Genesis Pro subscription key.
21 *
22 * @var string
23 */
24 const ENDPOINT = 'https://wp-product-info.wpesvc.net/v1/plugins/genesis-custom-blocks-pro/subscriptions/';
25
26 /**
27 * The code expected in a success response.
28 *
29 * @var string
30 */
31 const SUCCESS_CODE = 200;
32
33 /**
34 * Whether the subscription key is valid.
35 *
36 * @var bool
37 */
38 private $is_valid = false;
39
40 /**
41 * The error code, if any.
42 *
43 * @var string|null
44 */
45 private $error_code;
46
47 /**
48 * The product info.
49 *
50 * @var stdClass|null
51 */
52 private $product_info;
53
54 /**
55 * Constructs the class.
56 *
57 * @param string $subscription_key The subscription key to check.
58 */
59 public function __construct( $subscription_key ) {
60 $this->evaluate( $subscription_key );
61 }
62
63 /**
64 * Evaluates the response, storing the response body and a possible error message.
65 *
66 * @param string $subscription_key The subscription key to check.
67 */
68 public function evaluate( $subscription_key ) {
69 $response = wp_remote_get(
70 self::ENDPOINT . $subscription_key,
71 [
72 'timeout' => defined( 'DOING_CRON' ) && DOING_CRON ? 30 : 3,
73 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ),
74 'body' => [
75 'version' => block_lab()->get_version(),
76 ],
77 ]
78 );
79
80 if ( is_wp_error( $response ) || self::SUCCESS_CODE !== wp_remote_retrieve_response_code( $response ) ) {
81 if ( is_wp_error( $response ) ) {
82 $this->error_code = $response->get_error_code();
83 } else {
84 $response_body = json_decode( wp_remote_retrieve_body( $response ), false );
85 $this->error_code = ! empty( $response_body->error_code ) ? $response_body->error_code : 'unknown';
86 }
87
88 return;
89 }
90
91 $this->is_valid = true;
92 $this->product_info = new stdClass();
93 $response_body = json_decode( wp_remote_retrieve_body( $response ) );
94
95 if ( ! is_object( $response_body ) ) {
96 $response_body = new stdClass();
97 }
98
99 $this->product_info = $response_body;
100 }
101
102 /**
103 * Gets whether the subscription key is valid.
104 *
105 * @return bool
106 */
107 public function is_valid() {
108 return $this->is_valid;
109 }
110
111 /**
112 * Gets the error code, if any.
113 *
114 * @return string|null
115 */
116 public function get_error_code() {
117 return $this->error_code;
118 }
119
120 /**
121 * Gets the product info, or null if there isn't any.
122 *
123 * @return stdClass|null
124 */
125 public function get_product_info() {
126 return $this->product_info;
127 }
128 }
1 <?php
2 /**
3 * Plugin Autoloader
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 /**
11 * Register Autoloader
12 */
13 spl_autoload_register(
14 function ( $class ) {
15 // Assume we're using namespaces (because that's how the plugin is structured).
16 $namespace = explode( '\\', $class );
17 $root = array_shift( $namespace );
18
19 // If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'.
20 $class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-';
21
22 // If we're not in the plugin's namespace then just return.
23 if ( 'Block_Lab' !== $root ) {
24 return;
25 }
26
27 // Class name is the last part of the FQN.
28 $class_name = array_pop( $namespace );
29
30 // Remove "Trait" from the class name.
31 if ( 'trait-' === $class_trait ) {
32 $class_name = str_replace( 'Trait', '', $class_name );
33 }
34
35 $filename = $class_trait . $class_name . '.php';
36
37 // For file naming, the namespace is everything but the class name and the root namespace.
38 $namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) );
39
40 // Because WordPress file naming conventions are odd.
41 $filename = strtolower( str_replace( '_', '-', $filename ) );
42 $namespace = strtolower( str_replace( '_', '-', $namespace ) );
43
44 // Get the path to our files.
45 $directory = dirname( __FILE__ );
46 if ( ! empty( $namespace ) ) {
47 $directory .= DIRECTORY_SEPARATOR . $namespace;
48 }
49
50 $file = $directory . DIRECTORY_SEPARATOR . $filename;
51
52 if ( file_exists( $file ) ) {
53 require_once $file;
54 }
55 }
56 );
1 <?php
2 /**
3 * Block.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks;
11
12 /**
13 * Class Block
14 */
15 class Block {
16
17 /**
18 * Block name (slug).
19 *
20 * @var string
21 */
22 public $name = '';
23
24 /**
25 * Block title.
26 *
27 * @var string
28 */
29 public $title = '';
30
31 /**
32 * Exclude the block in these post types.
33 *
34 * @var array
35 */
36 public $excluded = [];
37
38 /**
39 * Icon.
40 *
41 * @var string
42 */
43 public $icon = '';
44
45 /**
46 * Category. An array containing the keys slug, title, and icon.
47 *
48 * @var array
49 */
50 public $category = [
51 'slug' => '',
52 'title' => '',
53 'icon' => '',
54 ];
55
56 /**
57 * Block keywords.
58 *
59 * @var string[]
60 */
61 public $keywords = [];
62
63 /**
64 * Block fields.
65 *
66 * @var Field[]
67 */
68 public $fields = [];
69
70 /**
71 * Block constructor.
72 *
73 * @param int|bool $post_id Post ID.
74 *
75 * @return void
76 */
77 public function __construct( $post_id = false ) {
78 if ( ! $post_id ) {
79 return;
80 }
81
82 $post = get_post( $post_id );
83
84 if ( ! $post instanceof \WP_Post ) {
85 return;
86 }
87
88 $this->name = $post->post_name;
89 $this->from_json( $post->post_content );
90 }
91
92 /**
93 * Construct the Block from a JSON blob.
94 *
95 * @param string $json JSON blob.
96 *
97 * @return void
98 */
99 public function from_json( $json ) {
100 $json = json_decode( $json, true );
101
102 if ( ! isset( $json[ 'block-lab/' . $this->name ] ) ) {
103 return;
104 }
105
106 $config = $json[ 'block-lab/' . $this->name ];
107
108 $this->from_array( $config );
109 }
110
111 /**
112 * Construct the Block from a config array.
113 *
114 * @param array $config An array containing field parameters.
115 *
116 * @return void
117 */
118 public function from_array( $config ) {
119 if ( isset( $config['name'] ) ) {
120 $this->name = $config['name'];
121 }
122
123 if ( isset( $config['title'] ) ) {
124 $this->title = $config['title'];
125 }
126
127 if ( isset( $config['excluded'] ) ) {
128 $this->excluded = $config['excluded'];
129 }
130
131 if ( isset( $config['icon'] ) ) {
132 $this->icon = $config['icon'];
133 }
134
135 if ( isset( $config['category'] ) ) {
136 $this->category = $config['category'];
137 if ( ! is_array( $this->category ) ) {
138 $this->category = $this->get_category_array_from_slug( $this->category );
139 }
140 }
141
142 if ( isset( $config['keywords'] ) ) {
143 $this->keywords = $config['keywords'];
144 }
145
146 if ( isset( $config['fields'] ) ) {
147 foreach ( $config['fields'] as $key => $field ) {
148 $this->fields[ $key ] = new Field( $field );
149 }
150 }
151 }
152
153 /**
154 * Get the Block as a JSON blob.
155 *
156 * @return string
157 */
158 public function to_json() {
159 $config['name'] = $this->name;
160 $config['title'] = $this->title;
161 $config['excluded'] = $this->excluded;
162 $config['icon'] = $this->icon;
163 $config['category'] = $this->category;
164 $config['keywords'] = $this->keywords;
165
166 $config['fields'] = [];
167 foreach ( $this->fields as $key => $field ) {
168 $config['fields'][ $key ] = $field->to_array();
169 }
170
171 return wp_json_encode( [ 'block-lab/' . $this->name => $config ], JSON_UNESCAPED_UNICODE );
172 }
173
174 /**
175 * This is a backwards compatibility fix.
176 *
177 * Block categories used to be saved as strings, but were always included in
178 * the default list of categories, so we can find them.
179 *
180 * It's not possible to use get_block_categories() here, as Block's are
181 * sometimes instantiated before that function is available.
182 *
183 * @param string $slug The category slug to find.
184 *
185 * @return array
186 */
187 public function get_category_array_from_slug( $slug ) {
188 return [
189 'slug' => $slug,
190 'title' => ucwords( $slug, '-' ),
191 'icon' => null,
192 ];
193 }
194 }
1 <?php
2 /**
3 * Block Field.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks;
11
12 /**
13 * Class Field
14 */
15 class Field {
16
17 /**
18 * Field name (slug).
19 *
20 * @var string
21 */
22 public $name = '';
23
24 /**
25 * Field label.
26 *
27 * @var string
28 */
29 public $label = '';
30
31 /**
32 * Field control type.
33 *
34 * @var string
35 */
36 public $control = 'text';
37
38 /**
39 * Field variable type.
40 *
41 * @var string
42 */
43 public $type = 'string';
44
45 /**
46 * Field order.
47 *
48 * @var int
49 */
50 public $order = 0;
51
52 /**
53 * Field settings.
54 *
55 * @var array
56 */
57 public $settings = [];
58
59 /**
60 * Field constructor.
61 *
62 * @param array $config An associative array with keys corresponding to the Field's properties.
63 */
64 public function __construct( $config = [] ) {
65 $this->from_array( $config );
66 }
67
68 /**
69 * Get field properties as an array, ready to be stored as JSON.
70 *
71 * @return array
72 */
73 public function to_array() {
74 $config = [
75 'name' => $this->name,
76 'label' => $this->label,
77 'control' => $this->control,
78 'type' => $this->type,
79 'order' => $this->order,
80 ];
81
82 $config = array_merge(
83 $config,
84 $this->settings
85 );
86
87 // Handle the sub-fields setting used by the Repeater.
88 if ( isset( $this->settings['sub_fields'] ) ) {
89 /**
90 * Recursively loop through sub-fields.
91 *
92 * @var string $key The name of the sub-field's parent.
93 * @var Field $field The sub-field.
94 */
95 foreach ( $this->settings['sub_fields'] as $key => $field ) {
96 $config['sub_fields'][ $key ] = $field->to_array();
97 }
98 }
99
100 return $config;
101 }
102
103 /**
104 * Set field properties from an array, after being stored as JSON.
105 *
106 * @param array $config An array containing field parameters.
107 */
108 public function from_array( $config ) {
109 if ( isset( $config['name'] ) ) {
110 $this->name = $config['name'];
111 }
112 if ( isset( $config['label'] ) ) {
113 $this->label = $config['label'];
114 }
115 if ( isset( $config['control'] ) ) {
116 $this->control = $config['control'];
117 }
118 if ( isset( $config['type'] ) ) {
119 $this->type = $config['type'];
120 }
121 if ( isset( $config['order'] ) ) {
122 $this->order = $config['order'];
123 }
124 if ( isset( $config['settings'] ) ) {
125 $this->settings = $config['settings'];
126 }
127
128 if ( ! isset( $config['type'] ) ) {
129 $control_class_name = 'Block_Lab\\Blocks\\Controls\\';
130 $control_class_name .= ucwords( $this->control, '_' );
131 if ( class_exists( $control_class_name ) ) {
132 /**
133 * An instance of the control, to retrieve the correct type.
134 *
135 * @var Control_Abstract $control_class
136 */
137 $control_class = new $control_class_name();
138 $this->type = $control_class->type;
139 }
140 }
141
142 // Add any other non-default keys to the settings array.
143 $field_defaults = [ 'name', 'label', 'control', 'type', 'order', 'settings' ];
144 $field_settings = array_diff( array_keys( $config ), $field_defaults );
145
146 foreach ( $field_settings as $settings_key ) {
147 $this->settings[ $settings_key ] = $config[ $settings_key ];
148 }
149
150 // Handle the sub-fields setting used by the Repeater.
151 if ( isset( $this->settings['sub_fields'] ) ) {
152 /**
153 * Recursively loop through sub-fields.
154 */
155 foreach ( $this->settings['sub_fields'] as $key => $field ) {
156 $this->settings['sub_fields'][ $key ] = new Field( $field );
157 }
158 }
159 }
160
161 /**
162 * Return the value with the correct variable type.
163 *
164 * @param mixed $value The value to typecast.
165 * @return mixed
166 */
167 public function cast_value( $value ) {
168 switch ( $this->type ) {
169 case 'string':
170 $value = strval( $value );
171 break;
172 case 'textarea':
173 $value = strval( $value );
174 if ( isset( $this->settings['new_lines'] ) ) {
175 if ( 'autop' === $this->settings['new_lines'] ) {
176 $value = wpautop( $value );
177 }
178 if ( 'autobr' === $this->settings['new_lines'] ) {
179 $value = nl2br( $value );
180 }
181 }
182 break;
183 case 'boolean':
184 if ( 1 === $value ) {
185 $value = true;
186 }
187 break;
188 case 'integer':
189 $value = intval( $value );
190 break;
191 case 'array':
192 if ( ! $value ) {
193 $value = [];
194 } else {
195 $value = (array) $value;
196 }
197 break;
198 }
199
200 return $value;
201 }
202
203 /**
204 * Gets the field value as a string.
205 *
206 * @param mixed $value The field value.
207 *
208 * @return string $value The value to echo.
209 */
210 public function cast_value_to_string( $value ) {
211 if ( is_array( $value ) ) {
212 return implode( ', ', $value );
213 }
214
215 if ( true === $value ) {
216 return __( 'Yes', 'block-lab' );
217 }
218
219 if ( false === $value ) {
220 return __( 'No', 'block-lab' );
221 }
222
223 return strval( $value );
224 }
225 }
1 <?php
2 /**
3 * Loader initiates the loading of new blocks.
4 *
5 * @package Block_Lab
6 */
7
8 namespace Block_Lab\Blocks;
9
10 use Block_Lab\Component_Abstract;
11
12 /**
13 * Class Loader
14 */
15 class Loader extends Component_Abstract {
16
17 /**
18 * Asset paths and urls for blocks.
19 *
20 * @var array
21 */
22 protected $assets = [];
23
24 /**
25 * An associative array of block config data for the blocks that will be registered.
26 *
27 * The key of each item in the array is the block name.
28 *
29 * @var array
30 */
31 protected $blocks = [];
32
33 /**
34 * A data store for sharing data to helper functions.
35 *
36 * @var array
37 */
38 protected $data = [];
39
40 /**
41 * Load the Loader.
42 *
43 * @return $this
44 */
45 public function init() {
46 $this->assets = [
47 'path' => [
48 'entry' => $this->plugin->get_path( 'js/editor.blocks.js' ),
49 'editor_style' => $this->plugin->get_path( 'css/blocks.editor.css' ),
50 ],
51 'url' => [
52 'entry' => $this->plugin->get_url( 'js/editor.blocks.js' ),
53 'editor_style' => $this->plugin->get_url( 'css/blocks.editor.css' ),
54 ],
55 ];
56
57 return $this;
58 }
59
60 /**
61 * Register all the hooks.
62 */
63 public function register_hooks() {
64 /**
65 * Gutenberg JS block loading.
66 */
67 add_action( 'enqueue_block_editor_assets', $this->get_callback( 'editor_assets' ) );
68
69 /**
70 * Gutenberg custom categories.
71 */
72 add_filter( 'block_categories', $this->get_callback( 'register_categories' ) );
73
74 /**
75 * Block retrieval, must run before dynamic_block_loader().
76 */
77 add_action( 'init', $this->get_callback( 'retrieve_blocks' ) );
78
79 /**
80 * PHP block loading.
81 */
82 add_action( 'init', $this->get_callback( 'dynamic_block_loader' ) );
83 }
84
85 /**
86 * Retrieve data from the Loader's data store.
87 *
88 * @param string $key The data key to retrieve.
89 * @return mixed
90 */
91 public function get_data( $key ) {
92 $data = false;
93
94 if ( isset( $this->data[ $key ] ) ) {
95 $data = $this->data[ $key ];
96 }
97
98 /**
99 * Filters the data that gets returned.
100 *
101 * @param mixed $data The data from the Loader's data store.
102 * @param string $key The key for the data being retreived.
103 */
104 $data = apply_filters( 'block_lab_data', $data, $key );
105
106 /**
107 * Filters the data that gets returned, specifically for a single key.
108 *
109 * @param mixed $data The data from the Loader's data store.
110 */
111 $data = apply_filters( "block_lab_data_{$key}", $data );
112
113 return $data;
114 }
115
116 /**
117 * Gets the callback for an action or filter.
118 *
119 * Enables keeping these methods protected,
120 * while allowing actions and filters to call them.
121 *
122 * @param string $method_name The name of the method to get the callback for.
123 * @return callable An enclosure that calls the function.
124 */
125 protected function get_callback( $method_name ) {
126 return function( $arg ) use ( $method_name ) {
127 return call_user_func( [ $this, $method_name ], $arg );
128 };
129 }
130
131 /**
132 * Launch the blocks inside Gutenberg.
133 */
134 protected function editor_assets() {
135 $asset_config = require $this->plugin->get_path( 'js/editor.blocks.asset.php' );
136
137 wp_enqueue_script(
138 'block-lab-blocks',
139 $this->assets['url']['entry'],
140 $asset_config['dependencies'],
141 $asset_config['version'],
142 true
143 );
144
145 // Add dynamic Gutenberg blocks.
146 wp_add_inline_script(
147 'block-lab-blocks',
148 'const blockLabBlocks = ' . wp_json_encode( $this->blocks ),
149 'before'
150 );
151
152 // Used to conditionally show notices for blocks belonging to an author.
153 $author_blocks = get_posts(
154 [
155 'author' => get_current_user_id(),
156 'post_type' => 'block_lab',
157 // We could use -1 here, but that could be dangerous. 99 is more than enough.
158 'posts_per_page' => 99,
159 ]
160 );
161
162 $author_block_slugs = wp_list_pluck( $author_blocks, 'post_name' );
163 wp_localize_script(
164 'block-lab-blocks',
165 'blockLab',
166 [
167 'authorBlocks' => $author_block_slugs,
168 'postType' => get_post_type(), // To conditionally exclude blocks from certain post types.
169 ]
170 );
171
172 // Enqueue optional editor only styles.
173 wp_enqueue_style(
174 'block-lab-editor-css',
175 $this->assets['url']['editor_style'],
176 [],
177 $this->plugin->get_version()
178 );
179
180 $block_names = wp_list_pluck( $this->blocks, 'name' );
181
182 foreach ( $block_names as $block_name ) {
183 $this->enqueue_block_styles( $block_name, [ 'preview', 'block' ] );
184 }
185
186 $this->enqueue_global_styles();
187 }
188
189 /**
190 * Loads dynamic blocks via render_callback for each block.
191 */
192 protected function dynamic_block_loader() {
193 if ( ! function_exists( 'register_block_type' ) ) {
194 return;
195 }
196
197 foreach ( $this->blocks as $block_name => $block_config ) {
198 $block = new Block();
199 $block->from_array( $block_config );
200 $this->register_block( $block_name, $block );
201 }
202 }
203
204 /**
205 * Registers a block.
206 *
207 * @param string $block_name The name of the block, including namespace.
208 * @param Block $block The block to register.
209 */
210 protected function register_block( $block_name, $block ) {
211 $attributes = $this->get_block_attributes( $block );
212
213 // sanitize_title() allows underscores, but register_block_type doesn't.
214 $block_name = str_replace( '_', '-', $block_name );
215
216 // register_block_type doesn't allow slugs starting with a number.
217 if ( is_numeric( $block_name[0] ) ) {
218 $block_name = 'block-' . $block_name;
219 }
220
221 register_block_type(
222 $block_name,
223 [
224 'attributes' => $attributes,
225 // @see https://github.com/WordPress/gutenberg/issues/4671
226 'render_callback' => function ( $attributes ) use ( $block ) {
227 return $this->render_block_template( $block, $attributes );
228 },
229 ]
230 );
231 }
232
233 /**
234 * Register custom block categories.
235 *
236 * @param array $categories Array of block categories.
237 *
238 * @return array
239 */
240 protected function register_categories( $categories ) {
241 foreach ( $this->blocks as $block_config ) {
242 if ( ! isset( $block_config['category'] ) ) {
243 continue;
244 }
245
246 /*
247 * This is a backwards compatibility fix.
248 *
249 * Block categories used to be saved as strings, but were always included in
250 * the default list of categories, so it's safe to skip them.
251 */
252 if ( ! is_array( $block_config['category'] ) || empty( $block_config['category'] ) ) {
253 continue;
254 }
255
256 if ( ! in_array( $block_config['category'], $categories, true ) ) {
257 $categories[] = $block_config['category'];
258 }
259 }
260
261 return $categories;
262 }
263
264 /**
265 * Gets block attributes.
266 *
267 * @param Block $block The block to get attributes from.
268 *
269 * @return array
270 */
271 protected function get_block_attributes( $block ) {
272 $attributes = [];
273
274 // Default Editor attributes (applied to all blocks).
275 $attributes['className'] = [ 'type' => 'string' ];
276
277 foreach ( $block->fields as $field_name => $field ) {
278 $attributes = $this->get_attributes_from_field( $attributes, $field_name, $field );
279 }
280
281 /**
282 * Filters a given block's attributes.
283 *
284 * These are later passed to register_block_type() in $args['attributes'].
285 * Removing attributes here can cause 'Error loading block...' in the editor.
286 *
287 * @param array[] $attributes The attributes for a block.
288 * @param array $block Block data, including its name at $block['name'].
289 */
290 return apply_filters( 'block_lab_get_block_attributes', $attributes, $block );
291 }
292
293 /**
294 * Sets the field values in the attributes, enabling them to appear in the block.
295 *
296 * @param array $attributes The attributes in which to store the field value.
297 * @param string $field_name The name of the field, like 'home-hero'.
298 * @param Field $field The Field to set the attributes from.
299 * @return array $attributes The attributes, with the new field value set.
300 */
301 protected function get_attributes_from_field( $attributes, $field_name, $field ) {
302 $attributes[ $field_name ] = [
303 'type' => $field->type,
304 ];
305
306 if ( ! empty( $field->settings['default'] ) ) {
307 $attributes[ $field_name ]['default'] = $field->settings['default'];
308 }
309
310 if ( 'array' === $field->type ) {
311 /**
312 * This is a workaround to allow empty array values. We unset the default value before registering the
313 * block so that the default isn't used to auto-correct empty arrays. This allows the default to be
314 * used only when creating the form.
315 */
316 unset( $attributes[ $field_name ]['default'] );
317 $items_type = 'repeater' === $field->control ? 'object' : 'string';
318 $attributes[ $field_name ]['items'] = [ 'type' => $items_type ];
319 }
320
321 return $attributes;
322 }
323
324 /**
325 * Renders the block provided a template is provided.
326 *
327 * @param Block $block The block to render.
328 * @param array $attributes Attributes to render.
329 *
330 * @return mixed
331 */
332 protected function render_block_template( $block, $attributes ) {
333 $type = 'block';
334
335 // This is hacky, but the editor doesn't send the original request along.
336 $context = filter_input( INPUT_GET, 'context', FILTER_SANITIZE_STRING );
337
338 if ( 'edit' === $context ) {
339 $type = [ 'preview', 'block' ];
340 }
341
342 if ( ! is_admin() ) {
343 /**
344 * The block has been added, but its values weren't saved (not even the defaults). This is a phenomenon
345 * unique to frontend output, as the editor fetches its attributes from the form fields themselves.
346 */
347 $missing_schema_attributes = array_diff_key( $block->fields, $attributes );
348 foreach ( $missing_schema_attributes as $attribute_name => $schema ) {
349 if ( isset( $schema->settings['default'] ) ) {
350 $attributes[ $attribute_name ] = $schema->settings['default'];
351 }
352 }
353
354 // Similar to the logic above, populate the Repeater control's sub-fields with default values.
355 foreach ( $block->fields as $field ) {
356 if ( isset( $field->settings['sub_fields'] ) && isset( $attributes[ $field->name ]['rows'] ) ) {
357 $sub_field_settings = $field->settings['sub_fields'];
358 $rows = $attributes[ $field->name ]['rows'];
359
360 // In each row, apply a field's default value if a value doesn't exist in the attributes.
361 foreach ( $rows as $row_index => $row ) {
362 foreach ( $sub_field_settings as $sub_field_name => $sub_field ) {
363 if ( ! isset( $row[ $sub_field_name ] ) && isset( $sub_field_settings[ $sub_field_name ]->settings['default'] ) ) {
364 $rows[ $row_index ][ $sub_field_name ] = $sub_field_settings[ $sub_field_name ]->settings['default'];
365 }
366 }
367 }
368
369 $attributes[ $field->name ]['rows'] = $rows;
370 }
371 }
372
373 $this->enqueue_block_styles( $block->name, 'block' );
374
375 /**
376 * The wp_enqueue_style function handles duplicates, so we don't need to worry about multiple blocks
377 * loading the global styles more than once.
378 */
379 $this->enqueue_global_styles();
380 }
381
382 $this->data['attributes'] = $attributes;
383 $this->data['config'] = $block;
384
385 if ( ! is_admin() && ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && ! wp_doing_ajax() ) {
386
387 /**
388 * Runs in the 'render_callback' of the block, and only on the front-end, not in the editor.
389 *
390 * The block's name (slug) is in $block->name.
391 * If a block depends on a JavaScript file,
392 * this action is a good place to call wp_enqueue_script().
393 * In that case, pass true as the 5th argument ($in_footer) to wp_enqueue_script().
394 *
395 * @param Block $block The block that is rendered.
396 * @param array $attributes The block attributes.
397 */
398 do_action( 'block_lab_render_template', $block, $attributes );
399
400 /**
401 * Runs in a block's 'render_callback', and only on the front-end.
402 *
403 * Same as the action above, but with a dynamic action name that has the block name.
404 *
405 * @param Block $block The block that is rendered.
406 * @param array $attributes The block attributes.
407 */
408 do_action( "block_lab_render_template_{$block->name}", $block, $attributes );
409 }
410
411 ob_start();
412 $this->block_template( $block->name, $type );
413 $output = ob_get_clean();
414
415 return $output;
416 }
417
418 /**
419 * Enqueues styles for the block.
420 *
421 * @param string $name The name of the block (slug as defined in UI).
422 * @param string|array $type The type of template to load.
423 */
424 protected function enqueue_block_styles( $name, $type = 'block' ) {
425 $locations = [];
426 $types = (array) $type;
427
428 foreach ( $types as $type ) {
429 $locations = array_merge(
430 $locations,
431 block_lab()->get_stylesheet_locations( $name, $type )
432 );
433 }
434
435 $stylesheet_path = block_lab()->locate_template( $locations );
436 $stylesheet_url = block_lab()->get_url_from_path( $stylesheet_path );
437
438 /**
439 * Enqueue the stylesheet, if it exists. The wp_enqueue_style function handles duplicates, so we don't need
440 * to worry about the same block loading its stylesheets more than once.
441 */
442 if ( ! empty( $stylesheet_url ) ) {
443 wp_enqueue_style(
444 "block-lab__block-{$name}",
445 $stylesheet_url,
446 [],
447 wp_get_theme()->get( 'Version' )
448 );
449 }
450 }
451
452 /**
453 * Enqueues global block styles.
454 */
455 protected function enqueue_global_styles() {
456 $locations = [
457 'blocks/css/blocks.css',
458 'blocks/blocks.css',
459 ];
460
461 $stylesheet_path = block_lab()->locate_template( $locations );
462 $stylesheet_url = block_lab()->get_url_from_path( $stylesheet_path );
463
464 /**
465 * Enqueue the stylesheet, if it exists.
466 */
467 if ( ! empty( $stylesheet_url ) ) {
468 wp_enqueue_style(
469 'block-lab__global-styles',
470 $stylesheet_url,
471 [],
472 wp_get_theme()->get( 'Version' )
473 );
474 }
475 }
476
477 /**
478 * Loads a block template to render the block.
479 *
480 * @param string $name The name of the block (slug as defined in UI).
481 * @param string|array $type The type of template to load.
482 */
483 protected function block_template( $name, $type = 'block' ) {
484 // Loading async it might not come from a query, this breaks load_template().
485 global $wp_query;
486
487 // So lets fix it.
488 if ( empty( $wp_query ) ) {
489 $wp_query = new \WP_Query(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
490 }
491
492 $types = (array) $type;
493 $located = '';
494
495 foreach ( $types as $type ) {
496 $templates = block_lab()->get_template_locations( $name, $type );
497 $located = block_lab()->locate_template( $templates );
498
499 if ( ! empty( $located ) ) {
500 break;
501 }
502 }
503
504 if ( ! empty( $located ) ) {
505 $theme_template = apply_filters( 'block_lab_override_theme_template', $located );
506
507 // This is not a load once template, so require_once is false.
508 load_template( $theme_template, false );
509 } else {
510 if ( ! current_user_can( 'edit_posts' ) || ! isset( $templates[0] ) ) {
511 return;
512 }
513 // Hide the template not found notice on the frontend, unless WP_DEBUG is enabled.
514 if ( ! is_admin() && ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
515 return;
516 }
517 printf(
518 '<div class="notice notice-warning">%s</div>',
519 wp_kses_post(
520 // Translators: Placeholder is a file path.
521 sprintf( __( 'Template file %s not found.', 'block-lab' ), '<code>' . esc_html( $templates[0] ) . '</code>' )
522 )
523 );
524 }
525 }
526
527 /**
528 * Load all the published blocks and blocks/block.json files.
529 */
530 protected function retrieve_blocks() {
531 /**
532 * Retrieve blocks from blocks.json.
533 * Reverse to preserve order of preference when using array_merge.
534 */
535 $blocks_files = array_reverse( (array) block_lab()->locate_template( 'blocks/blocks.json', '', false ) );
536 foreach ( $blocks_files as $blocks_file ) {
537 // This is expected to be on the local filesystem, so file_get_contents() is ok to use here.
538 $json = file_get_contents( $blocks_file ); // @codingStandardsIgnoreLine
539 $block_data = json_decode( $json, true );
540
541 // Merge if no json_decode error occurred.
542 if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
543 $this->blocks = array_merge( $this->blocks, $block_data );
544 }
545 }
546
547 /**
548 * Retrieve blocks stored as posts in the WordPress database.
549 */
550 $block_posts = new \WP_Query(
551 [
552 'post_type' => block_lab()->get_post_type_slug(),
553 'post_status' => 'publish',
554 'posts_per_page' => 100, // This has to have a limit for this plugin to be scalable.
555 ]
556 );
557
558 if ( 0 < $block_posts->post_count ) {
559 /** The WordPress Post object. @var \WP_Post $post */
560 foreach ( $block_posts->posts as $post ) {
561 $block_data = json_decode( $post->post_content, true );
562
563 // Merge if no json_decode error occurred.
564 if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
565 $this->blocks = array_merge( $this->blocks, $block_data );
566 }
567 }
568 }
569
570 /**
571 * Use this action to add new blocks and fields with the block_lab_add_block and block_lab_add_field helper functions.
572 */
573 do_action( 'block_lab_add_blocks' );
574
575 /**
576 * Filter the available blocks.
577 *
578 * This is used internally by the block_lab_add_block and block_lab_add_field helper functions,
579 * but it can also be used to hide certain blocks if desired.
580 *
581 * @param array $blocks An associative array of blocks.
582 */
583 $this->blocks = apply_filters( 'block_lab_blocks', $this->blocks );
584 }
585
586 /**
587 * Add a new block.
588 *
589 * This method should be called during the block_lab_add_blocks action, to ensure
590 * that the block isn't added too late.
591 *
592 * @param array $block_config The config of the block to add.
593 */
594 public function add_block( $block_config ) {
595 if ( ! isset( $block_config['name'] ) ) {
596 return;
597 }
598
599 $this->blocks[ "block-lab/{$block_config['name']}" ] = $block_config;
600 }
601
602 /**
603 * Add a new field to an existing block.
604 *
605 * This method should be called during the block_lab_add_blocks action, to ensure
606 * that the block isn't added too late.
607 *
608 * @param string $block_name The name of the block that the field is added to.
609 * @param array $field_config The config of the field to add.
610 */
611 public function add_field( $block_name, $field_config ) {
612 if ( ! isset( $this->blocks[ "block-lab/{$block_name}" ] ) ) {
613 return;
614 }
615 if ( ! isset( $field_config['name'] ) ) {
616 return;
617 }
618
619 $this->blocks[ "block-lab/{$block_name}" ]['fields'][ $field_config['name'] ] = $field_config;
620 }
621 }
1 <?php
2 /**
3 * Repeater row looping.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks;
11
12 /**
13 * Class Loop
14 */
15 class Loop {
16
17 /**
18 * Current pointer in active loops.
19 *
20 * An associative array of $loop_name => $pointer.
21 * The $pointer is an int of the current iteration, e.g: 0, 1, or 2.
22 *
23 * @var array
24 */
25 public $loops = [];
26
27 /**
28 * Currently active loop
29 *
30 * @var string
31 */
32 public $active;
33
34 /**
35 * Set a loop to active.
36 *
37 * @param string $name The field name.
38 */
39 public function set_active( $name ) {
40 $this->active = $name;
41 }
42
43 /**
44 * Get the current pointer for a loop.
45 *
46 * @param string $name The field name.
47 *
48 * @return bool
49 */
50 public function get_row( $name = '' ) {
51 if ( empty( $name ) ) {
52 $name = $this->active;
53 }
54
55 if ( isset( $this->loops[ $name ] ) ) {
56 return $this->loops[ $name ];
57 }
58
59 return false;
60 }
61
62 /**
63 * Increment the row pointer for a loop.
64 *
65 * @param string $name The field name.
66 * @return int
67 */
68 public function increment( $name = '' ) {
69 if ( empty( $name ) ) {
70 $name = $this->active;
71 }
72
73 if ( isset( $this->loops[ $name ] ) ) {
74 $this->loops[ $name ]++;
75 } else {
76 $this->loops[ $name ] = 0;
77 }
78
79 return $this->loops[ $name ];
80 }
81
82 /**
83 * Reset the loop so that it can be restarted.
84 *
85 * @param string $name The field name.
86 */
87 public function reset( $name = '' ) {
88 if ( empty( $name ) ) {
89 $name = $this->active;
90 }
91
92 unset( $this->loops[ $name ] );
93 }
94 }
1 <?php
2 /**
3 * Radio control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Checkbox
14 */
15 class Checkbox extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'checkbox';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'boolean';
30
31 /**
32 * Checkbox constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Checkbox', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
48 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
49 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
50 $this->settings[] = new Control_Setting(
51 [
52 'name' => 'default',
53 'label' => __( 'Default Value', 'block-lab' ),
54 'type' => 'checkbox',
55 'default' => '0',
56 'sanitize' => [ $this, 'sanitize_checkbox' ],
57 ]
58 );
59 }
60 }
1 <?php
2 /**
3 * Classic Text control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Classic_Text
14 */
15 class Classic_Text extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'classic_text';
23
24 /**
25 * Class constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Classic Text', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 foreach ( [ 'help', 'default' ] as $setting ) {
41 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
42 }
43 }
44 }
1 <?php
2 /**
3 * Color control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Color
14 */
15 class Color extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'color';
23
24 /**
25 * Text constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Color', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 foreach ( [ 'location', 'width', 'help', 'default' ] as $setting ) {
41 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
42 }
43 }
44 }
1 <?php
2 /**
3 * Control abstract.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 use Block_Lab\Blocks\Field;
13
14 /**
15 * Class Control_Abstract
16 */
17 abstract class Control_Abstract {
18
19 /**
20 * Control name.
21 *
22 * @var string
23 */
24 public $name = '';
25
26 /**
27 * Control label.
28 *
29 * @var string
30 */
31 public $label = '';
32
33 /**
34 * Field variable type (passed as an attribute when registering the block in Javascript).
35 *
36 * @var string
37 */
38 public $type = 'string';
39
40 /**
41 * Control settings.
42 *
43 * @var Control_Setting[]
44 */
45 public $settings = [];
46
47 /**
48 * Configurations for common settings, like 'help' and 'placeholder'.
49 *
50 * @var array {
51 * An associative array of setting configurations.
52 *
53 * @type string $setting_name The name of the setting, like 'help'.
54 * @type array $setting_config The default configuration of the setting.
55 * }
56 */
57 public $settings_config = [];
58
59 /**
60 * The possible editor locations, either in the main block editor, or the inspector controls.
61 *
62 * @var array
63 */
64 public $locations = [];
65
66 /**
67 * Control constructor.
68 *
69 * @return void
70 */
71 public function __construct() {
72 $this->create_settings_config();
73 $this->register_settings();
74 }
75
76 /**
77 * Creates the setting configuration.
78 *
79 * This sets the values for common settings, to make adding settings more DRY.
80 * Then, controls can simply use the values here.
81 *
82 * @return void
83 */
84 public function create_settings_config() {
85 $this->settings_config = [
86 'location' => [
87 'name' => 'location',
88 'label' => __( 'Field Location', 'block-lab' ),
89 'type' => 'location',
90 'default' => 'editor',
91 'sanitize' => [ $this, 'sanitize_location' ],
92 ],
93 'width' => [
94 'name' => 'width',
95 'label' => __( 'Field Width', 'block-lab' ),
96 'type' => 'width',
97 'default' => '100',
98 'sanitize' => 'sanitize_text_field',
99 ],
100 'help' => [
101 'name' => 'help',
102 'label' => __( 'Help Text', 'block-lab' ),
103 'type' => 'text',
104 'default' => '',
105 'sanitize' => 'sanitize_text_field',
106 ],
107 'default' => [
108 'name' => 'default',
109 'label' => __( 'Default Value', 'block-lab' ),
110 'type' => 'text',
111 'default' => '',
112 'sanitize' => 'sanitize_text_field',
113 ],
114 'placeholder' => [
115 'name' => 'placeholder',
116 'label' => __( 'Placeholder Text', 'block-lab' ),
117 'type' => 'text',
118 'default' => '',
119 'sanitize' => 'sanitize_text_field',
120 ],
121 ];
122
123 $this->locations = [
124 'editor' => __( 'Editor', 'block-lab' ),
125 'inspector' => __( 'Inspector', 'block-lab' ),
126 ];
127 }
128
129 /**
130 * Register settings.
131 *
132 * @return void
133 */
134 abstract public function register_settings();
135
136 /**
137 * Render additional settings in table rows.
138 *
139 * @param Field $field The Field containing the options to render.
140 * @param string $uid A unique ID to used to unify the HTML name, for, and id attributes.
141 *
142 * @return void
143 */
144 public function render_settings( $field, $uid ) {
145 foreach ( $this->settings as $setting ) {
146 // Don't render the location setting for sub-fields.
147 if ( 'location' === $setting->type && isset( $field->settings['parent'] ) ) {
148 continue;
149 }
150
151 // Don't render the field width setting for sub-fields.
152 if ( 'width' === $setting->type && isset( $field->settings['parent'] ) ) {
153 continue;
154 }
155
156 if ( isset( $field->settings[ $setting->name ] ) ) {
157 $setting->value = $field->settings[ $setting->name ];
158 } else {
159 $setting->value = $setting->default;
160 }
161
162 $classes = [
163 "block-fields-edit-settings-{$this->name}-{$setting->name}",
164 "block-fields-edit-{$setting->name}-settings",
165 "block-fields-edit-settings-{$this->name}",
166 "block-fields-edit-{$setting->name}-settings",
167 'block-fields-edit-settings',
168 ];
169 $name = 'block-fields-settings[' . $uid . '][' . $setting->name . ']';
170 $id = 'block-fields-edit-settings-' . $this->name . '-' . $setting->name . '_' . $uid;
171 ?>
172 <tr class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
173 <td class="spacer"></td>
174 <th scope="row">
175 <label for="<?php echo esc_attr( $id ); ?>">
176 <?php echo esc_html( $setting->label ); ?>
177 </label>
178 <p class="description">
179 <?php echo wp_kses_post( $setting->help ); ?>
180 </p>
181 </th>
182 <td>
183 <?php
184 $method = 'render_settings_' . $setting->type;
185 if ( method_exists( $this, $method ) ) {
186 $this->$method( $setting, $name, $id );
187 } else {
188 $this->render_settings_text( $setting, $name, $id );
189 }
190 ?>
191 </td>
192 </tr>
193 <?php
194 }
195 }
196
197 /**
198 * Render text settings
199 *
200 * @param Control_Setting $setting The Control_Setting being rendered.
201 * @param string $name The name attribute of the option.
202 * @param string $id The id attribute of the option.
203 *
204 * @return void
205 */
206 public function render_settings_text( $setting, $name, $id ) {
207 ?>
208 <input
209 name="<?php echo esc_attr( $name ); ?>"
210 type="<?php echo esc_attr( $setting->type ); ?>"
211 id="<?php echo esc_attr( $id ); ?>"
212 class="regular-text"
213 value="<?php echo esc_attr( $setting->get_value() ); ?>" />
214 <?php
215 }
216
217 /**
218 * Render textarea settings
219 *
220 * @param Control_Setting $setting The Control_Setting being rendered.
221 * @param string $name The name attribute of the option.
222 * @param string $id The id attribute of the option.
223 *
224 * @return void
225 */
226 public function render_settings_textarea( $setting, $name, $id ) {
227 ?>
228 <textarea
229 name="<?php echo esc_attr( $name ); ?>"
230 id="<?php echo esc_attr( $id ); ?>"
231 rows="6"
232 class="large-text"><?php echo esc_html( $setting->get_value() ); ?></textarea>
233 <?php
234 }
235
236 /**
237 * Render checkbox settings
238 *
239 * @param Control_Setting $setting The Control_Setting being rendered.
240 * @param string $name The name attribute of the option.
241 * @param string $id The id attribute of the option.
242 *
243 * @return void
244 */
245 public function render_settings_checkbox( $setting, $name, $id ) {
246 ?>
247 <input
248 name="<?php echo esc_attr( $name ); ?>"
249 type="checkbox"
250 id="<?php echo esc_attr( $id ); ?>"
251 class=""
252 value="1"
253 <?php checked( '1', $setting->get_value() ); ?> />
254 <?php
255 }
256
257 /**
258 * Render number settings.
259 *
260 * @param Control_Setting $setting The Control_Setting being rendered.
261 * @param string $name The name attribute of the option.
262 * @param string $id The id attribute of the option.
263 *
264 * @return void
265 */
266 public function render_settings_number( $setting, $name, $id ) {
267 $this->render_number( $setting, $name, $id );
268 }
269
270 /**
271 * Render the number settings, forcing the number in the <input> to be non-negative.
272 * This could be 0, 1, 2, etc, but not -1.
273 *
274 * @param Control_Setting $setting The Control_Setting being rendered.
275 * @param string $name The name attribute of the option.
276 * @param string $id The id attribute of the option.
277 *
278 * @return void
279 */
280 public function render_settings_number_non_negative( $setting, $name, $id ) {
281 $this->render_number( $setting, $name, $id, true );
282 }
283
284 /**
285 * Render the number settings, optionally outputting a min="0" attribute to enforce a non-negative value.
286 *
287 * @param Control_Setting $setting The Control_Setting being rendered.
288 * @param string $name The name attribute of the option.
289 * @param string $id The id attribute of the option.
290 * @param bool $non_negative Whether to force the number to be non-negative via a min="0" attribute.
291 *
292 * @return void
293 */
294 public function render_number( $setting, $name, $id, $non_negative = false ) {
295 ?>
296 <input
297 name="<?php echo esc_attr( $name ); ?>"
298 type="number"
299 id="<?php echo esc_attr( $id ); ?>"
300 class="regular-text"
301 <?php echo $non_negative ? 'min="0"' : ''; ?>
302 value="<?php echo esc_attr( $setting->get_value() ); ?>" />
303 <?php
304 }
305
306 /**
307 * Render an array of settings inside a textarea.
308 *
309 * @param Control_Setting $setting The Control_Setting being rendered.
310 * @param string $name The name attribute of the option.
311 * @param string $id The id attribute of the option.
312 *
313 * @return void
314 */
315 public function render_settings_textarea_array( $setting, $name, $id ) {
316 $options = $setting->get_value();
317 if ( is_array( $options ) ) {
318 // Convert the array to text separated by new lines.
319 $value = '';
320 foreach ( $options as $option ) {
321 if ( ! is_array( $option ) ) {
322 $value .= $option . "\n";
323 continue;
324 }
325 if ( ! isset( $option['value'] ) || ! isset( $option['label'] ) ) {
326 continue;
327 }
328 if ( $option['value'] === $option['label'] ) {
329 $value .= $option['label'] . "\n";
330 } else {
331 $value .= $option['value'] . ' : ' . $option['label'] . "\n";
332 }
333 }
334 $setting->value = trim( $value );
335 }
336 $this->render_settings_textarea( $setting, $name, $id );
337 }
338
339 /**
340 * Renders a <select> of locations.
341 *
342 * @param Control_Setting $setting The Control_Setting being rendered.
343 * @param string $name The name attribute of the option.
344 * @param string $id The id attribute of the option.
345 *
346 * @return void
347 */
348 public function render_settings_location( $setting, $name, $id ) {
349 $this->render_select( $setting, $name, $id, $this->locations );
350 }
351
352 /**
353 * Renders a button group of field widths.
354 *
355 * @param Control_Setting $setting The Control_Setting being rendered.
356 * @param string $name The name attribute of the option.
357 * @param string $id The id attribute of the option.
358 *
359 * @return void
360 */
361 public function render_settings_width( $setting, $name, $id ) {
362 $widths = [
363 '25' => '25%',
364 '50' => '50%',
365 '75' => '75%',
366 '100' => '100%',
367 ];
368 ?>
369 <div class="button-group">
370 <?php
371 foreach ( $widths as $value => $label ) {
372 ?>
373 <input
374 class="button"
375 name="<?php echo esc_attr( $name ); ?>"
376 type="radio"
377 value="<?php echo esc_attr( $value ); ?>"
378 <?php checked( $value, $setting->get_value() ); ?>
379 />
380 <label><?php echo esc_html( $label ); ?></label>
381 <?php
382 }
383 ?>
384 </div>
385 <?php
386 }
387
388 /**
389 * Renders a <select> of the passed values.
390 *
391 * @param Control_Setting $setting The Control_Setting being rendered.
392 * @param string $name The name attribute of the option.
393 * @param string $id The id attribute of the option.
394 * @param array $values {
395 * An associative array of the post type REST slugs.
396 *
397 * @type string $rest_slug The rest slug, like 'tags' for the 'post_tag' taxonomy.
398 * @type string $label The label to display inside the <option>.
399 * }
400 *
401 * @return void
402 */
403 public function render_select( $setting, $name, $id, $values ) {
404 ?>
405 <select name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $id ); ?>">
406 <?php
407 foreach ( $values as $value => $label ) :
408 ?>
409 <option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $setting->get_value() ); ?>>
410 <?php echo esc_html( $label ); ?>
411 </option>
412 <?php endforeach; ?>
413 </select>
414 <?php
415 }
416
417 /**
418 * Sanitize checkbox.
419 *
420 * @param string $value The value to sanitize.
421 *
422 * @return string
423 */
424 public function sanitize_checkbox( $value ) {
425 if ( '1' === $value ) {
426 return 1;
427 }
428 return 0;
429 }
430
431 /**
432 * Sanitize non-zero number.
433 *
434 * @param string $value The value to sanitize.
435 *
436 * @return int
437 */
438 public function sanitize_number( $value ) {
439 if ( empty( $value ) || '0' === $value ) {
440 return null;
441 }
442 return (int) filter_var( $value, FILTER_SANITIZE_NUMBER_INT );
443 }
444
445 /**
446 * Sanitize an array of settings inside a textarea.
447 *
448 * @param string $value The value to sanitize.
449 *
450 * @return array
451 */
452 public function sanitize_textarea_assoc_array( $value ) {
453 $rows = preg_split( '/\r\n|[\r\n]/', $value );
454 $options = [];
455
456 foreach ( $rows as $key => $option ) {
457 if ( '' === $option ) {
458 continue;
459 }
460
461 $key_value = explode( ' : ', $option );
462
463 if ( count( $key_value ) > 1 ) {
464 $options[ $key ]['label'] = $key_value[1];
465 $options[ $key ]['value'] = $key_value[0];
466 } else {
467 $options[ $key ]['label'] = $option;
468 $options[ $key ]['value'] = $option;
469 }
470 }
471
472 // Reindex array in case of blank lines.
473 $options = array_values( $options );
474
475 return $options;
476 }
477
478 /**
479 * Sanitize an array of settings inside a textarea.
480 *
481 * @param string $value The value to sanitize.
482 *
483 * @return array
484 */
485 public function sanitize_textarea_array( $value ) {
486 $rows = preg_split( '/\r\n|[\r\n]/', $value );
487 $options = [];
488
489 foreach ( $rows as $key => $option ) {
490 if ( '' === $option ) {
491 continue;
492 }
493
494 $key_value = explode( ' : ', $option );
495
496 if ( count( $key_value ) > 1 ) {
497 $options[] = $key_value[0];
498 } else {
499 $options[] = $option;
500 }
501 }
502
503 // Reindex array in case of blank lines.
504 $options = array_values( $options );
505
506 return $options;
507 }
508
509 /**
510 * Sanitize a location value.
511 *
512 * @param string $value The value to sanitize.
513 *
514 * @return array
515 */
516 public function sanitize_location( $value ) {
517 if ( is_string( $value ) && array_key_exists( $value, $this->locations ) ) {
518 return $value;
519 }
520 }
521
522 /**
523 * Validate that the value is contained within a list of options,
524 * and if not, return the first option.
525 *
526 * @param mixed $value The value to be validated.
527 * @param array $settings The field settings.
528 *
529 * @return mixed
530 */
531 public function validate_options( $value, $settings ) {
532 if ( ! array_key_exists( 'options', $settings ) ) {
533 return $value;
534 }
535
536 // Allow an empty value.
537 if ( '' === $value ) {
538 return $value;
539 }
540
541 $options = [];
542
543 // Reindex the options into a more workable format.
544 array_walk(
545 $settings['options'],
546 function( $option ) use ( &$options ) {
547 $options[] = $option['value'];
548 }
549 );
550
551 if ( is_array( $value ) ) {
552 // Filter out invalid options where multiple options can be chosen.
553 foreach ( $value as $key => $option ) {
554 if ( ! in_array( $option, $options, true ) ) {
555 unset( $value[ $key ] );
556 }
557 }
558 } else {
559 // If the value is not in the set of options, return an empty string.
560 if ( ! in_array( $value, $options, true ) ) {
561 $value = '';
562 }
563 }
564
565 return $value;
566 }
567 }
1 <?php
2 /**
3 * Control_Setting.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Control_Setting
14 */
15 class Control_Setting {
16
17 /**
18 * Setting name (slug).
19 *
20 * @var string
21 */
22 public $name = '';
23
24 /**
25 * Setting label.
26 *
27 * @var string
28 */
29 public $label = '';
30
31 /**
32 * Setting type.
33 *
34 * @var string
35 */
36 public $type = '';
37
38 /**
39 * Default value.
40 *
41 * @var mixed
42 */
43 public $default = '';
44
45 /**
46 * Help text.
47 *
48 * @var string
49 */
50 public $help = '';
51
52 /**
53 * Sanitizing function.
54 *
55 * @var mixed
56 */
57 public $sanitize = '';
58
59 /**
60 * Validating function.
61 *
62 * @var mixed
63 */
64 public $validate = '';
65
66 /**
67 * Current value. Null for unset.
68 *
69 * @var mixed
70 */
71 public $value = null;
72
73 /**
74 * Control_Setting constructor.
75 *
76 * @param array $args An associative array with keys corresponding to the Option's properties.
77 *
78 * @return void
79 */
80 public function __construct( $args = [] ) {
81 if ( isset( $args['name'] ) ) {
82 $this->name = $args['name'];
83 }
84 if ( isset( $args['label'] ) ) {
85 $this->label = $args['label'];
86 }
87 if ( isset( $args['type'] ) ) {
88 $this->type = $args['type'];
89 }
90 if ( isset( $args['default'] ) ) {
91 $this->default = $args['default'];
92 }
93 if ( isset( $args['help'] ) ) {
94 $this->help = $args['help'];
95 }
96 if ( isset( $args['sanitize'] ) ) {
97 $this->sanitize = $args['sanitize'];
98 }
99 if ( isset( $args['validate'] ) ) {
100 $this->validate = $args['validate'];
101 }
102 if ( isset( $args['value'] ) ) {
103 $this->value = $args['value'];
104 }
105 }
106
107 /**
108 * Get the current value, using the default if there is none set.
109 *
110 * @return mixed
111 */
112 public function get_value() {
113 if ( null === $this->value ) {
114 return $this->default;
115 }
116
117 return $this->value;
118 }
119 }
1 <?php
2 /**
3 * Email control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Text
14 */
15 class Email extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'email';
23
24 /**
25 * Text constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Email', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
41 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
42 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
43 $this->settings[] = new Control_Setting(
44 [
45 'name' => 'default',
46 'label' => __( 'Default Value', 'block-lab' ),
47 'type' => 'email',
48 'default' => '',
49 'sanitize' => 'sanitize_email',
50 ]
51 );
52 $this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
53 }
54 }
1 <?php
2 /**
3 * Image control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Image
14 */
15 class Image extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'image';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'integer';
30
31 /**
32 * Text constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Image', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 foreach ( [ 'location', 'width', 'help' ] as $setting ) {
48 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
49 }
50 }
51
52 /**
53 * Validates the value to be made available to the front-end template.
54 *
55 * @param string $value The value to either make available as a variable or echoed on the front-end template.
56 * @param bool $echo Whether this value will be echoed.
57 * @return string|int $value The value to be made available or echoed on the front-end template, possibly 0 if none found.
58 */
59 public function validate( $value, $echo ) {
60 $image_id = intval( $value );
61
62 // Backwards compatibility, as the value used to be the image's URL instead of its post ID.
63 if ( empty( $image_id ) && is_string( $value ) ) {
64 $legacy_src = $value;
65 $legacy_id = attachment_url_to_postid( $value );
66 }
67
68 if ( $echo ) {
69 if ( isset( $legacy_src ) ) {
70 return $legacy_src;
71 }
72 $image = wp_get_attachment_image_src( $image_id, 'full' );
73 return ! empty( $image[0] ) ? $image[0] : '';
74 } else {
75 return isset( $legacy_id ) ? $legacy_id : $image_id;
76 }
77 }
78 }
1 <?php
2 /**
3 * Select control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Select
14 */
15 class Multiselect extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'multiselect';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'array';
30
31 /**
32 * Select constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Multi-Select', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
48 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
49 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
50 $this->settings[] = new Control_Setting(
51 [
52 'name' => 'options',
53 'label' => __( 'Choices', 'block-lab' ),
54 'type' => 'textarea_array',
55 'default' => '',
56 'help' => sprintf(
57 '%s %s<br />%s<br />%s',
58 __( 'Enter each choice on a new line.', 'block-lab' ),
59 __( 'To specify the value and label separately, use this format:', 'block-lab' ),
60 _x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
61 _x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
62 ),
63 'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
64 ]
65 );
66 $this->settings[] = new Control_Setting(
67 [
68 'name' => 'default',
69 'label' => __( 'Default Value', 'block-lab' ),
70 'type' => 'textarea_array',
71 'default' => '',
72 'help' => __( 'Enter each default value on a new line.', 'block-lab' ),
73 'sanitize' => [ $this, 'sanitize_textarea_array' ],
74 'validate' => [ $this, 'validate_options' ],
75 ]
76 );
77 }
78 }
1 <?php
2 /**
3 * Number control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Text
14 */
15 class Number extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'number';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'integer';
30
31 /**
32 * Text constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Number', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
48 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
49 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
50 $this->settings[] = new Control_Setting(
51 [
52 'name' => 'default',
53 'label' => __( 'Default Value', 'block-lab' ),
54 'type' => 'number',
55 'default' => '',
56 'sanitize' => function ( $value ) {
57 return filter_var( $value, FILTER_SANITIZE_NUMBER_INT );
58 },
59 ]
60 );
61 $this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
62 }
63 }
1 <?php
2 /**
3 * Post control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Post
14 */
15 class Post extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'post';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'object';
30
31 /**
32 * Post constructor.
33 */
34 public function __construct() {
35 parent::__construct();
36 $this->label = __( 'Post', 'block-lab' );
37 }
38
39 /**
40 * Register settings.
41 *
42 * @return void
43 */
44 public function register_settings() {
45 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
46 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
47 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
48 $this->settings[] = new Control_Setting(
49 [
50 'name' => 'post_type_rest_slug',
51 'label' => __( 'Post Type', 'block-lab' ),
52 'type' => 'post_type_rest_slug',
53 'default' => 'posts',
54 'sanitize' => [ $this, 'sanitize_post_type_rest_slug' ],
55 ]
56 );
57 }
58
59 /**
60 * Render a <select> of public post types.
61 *
62 * @param Control_Setting $setting The Control_Setting being rendered.
63 * @param string $name The name attribute of the option.
64 * @param string $id The id attribute of the option.
65 *
66 * @return void
67 */
68 public function render_settings_post_type_rest_slug( $setting, $name, $id ) {
69 $post_type_slugs = $this->get_post_type_rest_slugs();
70 $this->render_select( $setting, $name, $id, $post_type_slugs );
71 }
72
73 /**
74 * Gets the REST slugs of public post types, other than 'attachment'.
75 *
76 * @return array {
77 * An associative array of the post type REST slugs.
78 *
79 * @type string $rest_slug The REST slug of the post type.
80 * @type string $name The name of the post type.n
81 * }
82 */
83 public function get_post_type_rest_slugs() {
84 $post_type_rest_slugs = [];
85 foreach ( get_post_types( [ 'public' => true ] ) as $post_type ) {
86 $post_type_object = get_post_type_object( $post_type );
87 if ( ! $post_type_object || empty( $post_type_object->show_in_rest ) ) {
88 continue;
89 }
90 if ( 'attachment' === $post_type ) {
91 continue;
92 }
93 $rest_slug = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type;
94 $labels = get_post_type_labels( $post_type_object );
95 $post_type_name = isset( $labels->name ) ? $labels->name : $post_type;
96 $post_type_rest_slugs[ $rest_slug ] = $post_type_name;
97 }
98 return $post_type_rest_slugs;
99 }
100
101 /**
102 * Sanitize the post type REST slug, to ensure that it's a public post type.
103 *
104 * This expects the rest_base of the post type, as it's easier to pass that to apiFetch in the Post control.
105 * So this iterates through the public post types, to find if one has the rest_base equal to $value.
106 *
107 * @param string $value The rest_base of the post type to sanitize.
108 * @return string|null The sanitized rest_base of the post type, or null.
109 */
110 public function sanitize_post_type_rest_slug( $value ) {
111 if ( array_key_exists( $value, $this->get_post_type_rest_slugs() ) ) {
112 return $value;
113 }
114 return null;
115 }
116
117 /**
118 * Validates the value to be made available to the front-end template.
119 *
120 * @param mixed $value The value to either make available as a variable or echoed on the front-end template.
121 * @param bool $echo Whether this will be echoed.
122 * @return string|WP_Post|null $value The value to be made available or echoed on the front-end template.
123 */
124 public function validate( $value, $echo ) {
125 $post = isset( $value['id'] ) ? get_post( $value['id'] ) : null;
126 if ( $echo ) {
127 return $post ? get_the_title( $post ) : '';
128 } else {
129 return $post;
130 }
131 }
132 }
1 <?php
2 /**
3 * Radio control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Radio
14 */
15 class Radio extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'radio';
23
24 /**
25 * Radio constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Radio', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
41 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
42 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
43 $this->settings[] = new Control_Setting(
44 [
45 'name' => 'options',
46 'label' => __( 'Choices', 'block-lab' ),
47 'type' => 'textarea_array',
48 'default' => '',
49 'help' => sprintf(
50 '%s %s<br />%s<br />%s',
51 __( 'Enter each choice on a new line.', 'block-lab' ),
52 __( 'To specify the value and label separately, use this format:', 'block-lab' ),
53 _x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
54 _x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
55 ),
56 'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
57 ]
58 );
59 $this->settings[] = new Control_Setting(
60 [
61 'name' => 'default',
62 'label' => __( 'Default Value', 'block-lab' ),
63 'type' => 'text',
64 'default' => '',
65 'sanitize' => 'sanitize_text_field',
66 'validate' => [ $this, 'validate_options' ],
67 ]
68 );
69 }
70 }
1 <?php
2 /**
3 * Range control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Range
14 */
15 class Range extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'range';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'integer';
30
31 /**
32 * Range constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Range', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
48 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
49 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
50 $this->settings[] = new Control_Setting(
51 [
52 'name' => 'min',
53 'label' => __( 'Minimum Value', 'block-lab' ),
54 'type' => 'number',
55 'default' => '',
56 'sanitize' => [ $this, 'sanitize_number' ],
57 ]
58 );
59 $this->settings[] = new Control_Setting(
60 [
61 'name' => 'max',
62 'label' => __( 'Maximum Value', 'block-lab' ),
63 'type' => 'number',
64 'default' => '',
65 'sanitize' => [ $this, 'sanitize_number' ],
66 ]
67 );
68 $this->settings[] = new Control_Setting(
69 [
70 'name' => 'step',
71 'label' => __( 'Step Size', 'block-lab' ),
72 'type' => 'number_non_negative',
73 'default' => 1,
74 'sanitize' => [ $this, 'sanitize_number' ],
75 ]
76 );
77 $this->settings[] = new Control_Setting(
78 [
79 'name' => 'default',
80 'label' => __( 'Default Value', 'block-lab' ),
81 'type' => 'number',
82 'default' => '',
83 'sanitize' => [ $this, 'sanitize_number' ],
84 ]
85 );
86 }
87 }
1 <?php
2 /**
3 * Repeater control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Repeater
14 */
15 class Repeater extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'repeater';
23
24 /**
25 * Field variable type.
26 *
27 * The Repeater control is an array of objects, with each row being an object.
28 * For example, a repeater with one row might be [ { 'example-text': 'Foo', 'example-image': 4232 } ].
29 *
30 * @var string
31 */
32 public $type = 'object';
33
34 /**
35 * Repeater constructor.
36 */
37 public function __construct() {
38 parent::__construct();
39 $this->label = __( 'Repeater', 'block-lab' );
40 }
41
42 /**
43 * Register settings.
44 *
45 * @return void
46 */
47 public function register_settings() {
48 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
49 $this->settings[] = new Control_Setting(
50 [
51 'name' => 'min',
52 'label' => __( 'Minimum Rows', 'block-lab' ),
53 'type' => 'number_non_negative',
54 'sanitize' => [ $this, 'sanitize_number' ],
55 ]
56 );
57 $this->settings[] = new Control_Setting(
58 [
59 'name' => 'max',
60 'label' => __( 'Maximum Rows', 'block-lab' ),
61 'type' => 'number_non_negative',
62 'sanitize' => [ $this, 'sanitize_number' ],
63 ]
64 );
65 }
66
67 /**
68 * Remove empty placeholder rows.
69 *
70 * @param mixed $value The value to either make available as a variable or echoed on the front-end template.
71 * @param bool $echo Whether this will be echoed.
72 * @return mixed $value The value to be made available or echoed on the front-end template.
73 */
74 public function validate( $value, $echo ) {
75 if ( isset( $value['rows'] ) ) {
76 foreach ( $value['rows'] as $key => $row ) {
77 unset( $value['rows'][ $key ][''] );
78 unset( $value['rows'][ $key ][0] );
79 }
80 }
81
82 if ( $echo && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
83 $value = sprintf(
84 // translators: Placeholders are the opening and closing anchor tags of a link.
85 __( '⚠️ Please use Block Lab\'s %1$srepeater functions%2$s to display repeater fields in your template.', 'block-lab' ),
86 '<a href="https://getblocklab.com/docs/fields/repeater/">',
87 '</a>'
88 );
89 }
90
91 return $value;
92 }
93 }
1 <?php
2 /**
3 * Rich Text control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Rich_Text
14 */
15 class Rich_Text extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'rich_text';
23
24 /**
25 * Class constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Rich Text', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 foreach ( [ 'help', 'default', 'placeholder' ] as $setting ) {
41 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
42 }
43 }
44
45 /**
46 * Validates the value to be made available to the front-end template.
47 *
48 * @param mixed $value The value to either make available as a variable or echoed on the front-end template.
49 * @param bool $echo Whether this will be echoed.
50 * @return mixed $value The value to be made available or echoed on the front-end template.
51 */
52 public function validate( $value, $echo ) {
53 unset( $echo );
54
55 // If there's no text entered, Rich Text saves '<p></p>', so instead return ''.
56 if ( '<p></p>' === $value ) {
57 return '';
58 }
59
60 return wpautop( $value );
61 }
62 }
1 <?php
2 /**
3 * Select control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Select
14 */
15 class Select extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'select';
23
24 /**
25 * Select constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Select', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
41 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
42 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
43 $this->settings[] = new Control_Setting(
44 [
45 'name' => 'options',
46 'label' => __( 'Choices', 'block-lab' ),
47 'type' => 'textarea_array',
48 'default' => '',
49 'help' => sprintf(
50 '%s %s<br />%s<br />%s',
51 __( 'Enter each choice on a new line.', 'block-lab' ),
52 __( 'To specify the value and label separately, use this format:', 'block-lab' ),
53 _x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
54 _x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
55 ),
56 'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
57 ]
58 );
59 $this->settings[] = new Control_Setting(
60 [
61 'name' => 'default',
62 'label' => __( 'Default Value', 'block-lab' ),
63 'type' => 'text',
64 'default' => '',
65 'sanitize' => 'sanitize_text_field',
66 'validate' => [ $this, 'validate_options' ],
67 ]
68 );
69 }
70 }
1 <?php
2 /**
3 * Taxonomy control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Taxonomy
14 */
15 class Taxonomy extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'taxonomy';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'object';
30
31 /**
32 * Taxonomy constructor.
33 */
34 public function __construct() {
35 parent::__construct();
36 $this->label = __( 'Taxonomy', 'block-lab' );
37 }
38
39 /**
40 * Register settings.
41 *
42 * @return void
43 */
44 public function register_settings() {
45 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
46 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
47 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
48 $this->settings[] = new Control_Setting(
49 [
50 'name' => 'post_type_rest_slug',
51 'label' => __( 'Taxonomy Type', 'block-lab' ),
52 'type' => 'taxonomy_type_rest_slug',
53 'default' => 'posts',
54 'sanitize' => [ $this, 'sanitize_taxonomy_type_rest_slug' ],
55 ]
56 );
57 }
58
59 /**
60 * Renders a <select> of public taxonomy types.
61 *
62 * @param Control_Setting $setting The Control_Setting being rendered.
63 * @param string $name The name attribute of the option.
64 * @param string $id The id attribute of the option.
65 *
66 * @return void
67 */
68 public function render_settings_taxonomy_type_rest_slug( $setting, $name, $id ) {
69 $taxonomy_slugs = $this->get_taxonomy_type_rest_slugs();
70 $this->render_select( $setting, $name, $id, $taxonomy_slugs );
71 }
72
73 /**
74 * Gets the REST slugs of public taxonomy types.
75 *
76 * @return array {
77 * An associative array of the post type REST slugs.
78 *
79 * @type string $rest_slug The REST slug of the post type.
80 * @type string $name The name of the post type.
81 * }
82 */
83 public function get_taxonomy_type_rest_slugs() {
84 $taxonomy_rest_slugs = [];
85 foreach ( get_taxonomies( [ 'show_in_rest' => true ] ) as $taxonomy_slug ) {
86 $taxonomy_object = get_taxonomy( $taxonomy_slug );
87 $rest_slug = ! empty( $taxonomy_object->rest_base ) ? $taxonomy_object->rest_base : $taxonomy_slug;
88 $taxonomy_rest_slugs[ $rest_slug ] = $taxonomy_object->label;
89 }
90 return $taxonomy_rest_slugs;
91 }
92
93 /**
94 * Sanitize the taxonomy type REST slug, to ensure that it's registered and public.
95 *
96 * @param string $value The rest_base of the post type to sanitize.
97 * @return string|null The sanitized rest_base of the post type, or null.
98 */
99 public function sanitize_taxonomy_type_rest_slug( $value ) {
100 if ( array_key_exists( $value, $this->get_taxonomy_type_rest_slugs() ) ) {
101 return $value;
102 }
103 return null;
104 }
105
106 /**
107 * Validates the value to be made available to the front-end template.
108 *
109 * @param mixed $value The value to either make available as a variable or echoed on the front-end template.
110 * @param bool $echo Whether this will be echoed.
111 * @return string|WP_Term|null $value The value to be made available or echoed on the front-end template.
112 */
113 public function validate( $value, $echo ) {
114 $term = isset( $value['id'] ) ? get_term( $value['id'] ) : null;
115
116 if ( $echo ) {
117 return $term ? $term->name : '';
118 } else {
119 return $term;
120 }
121 }
122 }
1 <?php
2 /**
3 * Text control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Text
14 */
15 class Text extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'text';
23
24 /**
25 * Text constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'Text', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 foreach ( [ 'location', 'width', 'help', 'default', 'placeholder' ] as $setting ) {
41 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
42 }
43
44 $this->settings[] = new Control_Setting(
45 [
46 'name' => 'maxlength',
47 'label' => __( 'Character Limit', 'block-lab' ),
48 'type' => 'number_non_negative',
49 'default' => '',
50 'sanitize' => [ $this, 'sanitize_number' ],
51 ]
52 );
53 }
54 }
1 <?php
2 /**
3 * Textarea control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Textarea
14 */
15 class Textarea extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'textarea';
23
24 /**
25 * Control type.
26 *
27 * @var string
28 */
29 public $type = 'textarea';
30
31 /**
32 * Textarea constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Textarea', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 foreach ( [ 'location', 'width', 'help' ] as $setting ) {
48 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
49 }
50
51 $this->settings[] = new Control_Setting(
52 [
53 'name' => 'default',
54 'label' => __( 'Default Value', 'block-lab' ),
55 'type' => 'textarea',
56 'default' => '',
57 'sanitize' => 'sanitize_textarea_field',
58 ]
59 );
60 $this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
61 $this->settings[] = new Control_Setting(
62 [
63 'name' => 'maxlength',
64 'label' => __( 'Character Limit', 'block-lab' ),
65 'type' => 'number_non_negative',
66 'default' => '',
67 'sanitize' => [ $this, 'sanitize_number' ],
68 ]
69 );
70 $this->settings[] = new Control_Setting(
71 [
72 'name' => 'number_rows',
73 'label' => __( 'Number of Rows', 'block-lab' ),
74 'type' => 'number_non_negative',
75 'default' => 4,
76 'sanitize' => [ $this, 'sanitize_number' ],
77 ]
78 );
79 $this->settings[] = new Control_Setting(
80 [
81 'name' => 'new_lines',
82 'label' => __( 'New Lines', 'block-lab' ),
83 'type' => 'new_line_format',
84 'default' => 'autop',
85 'sanitize' => [ $this, 'sanitize_new_line_format' ],
86 ]
87 );
88 }
89
90 /**
91 * Renders a <select> of new line rendering formats.
92 *
93 * @param Control_Setting $setting The Control_Setting being rendered.
94 * @param string $name The name attribute of the option.
95 * @param string $id The id attribute of the option.
96 *
97 * @return void
98 */
99 public function render_settings_new_line_format( $setting, $name, $id ) {
100 $formats = $this->get_new_line_formats();
101 $this->render_select( $setting, $name, $id, $formats );
102 }
103
104 /**
105 * Gets the new line formats.
106 *
107 * @return array {
108 * An associative array of new line formats.
109 *
110 * @type string $key The option value to save.
111 * @type string $label The label.
112 * }
113 */
114 public function get_new_line_formats() {
115 $formats = [
116 'autop' => __( 'Automatically add paragraphs', 'block-lab' ),
117 'autobr' => __( 'Automatically add line breaks', 'block-lab' ),
118 'none' => __( 'No formatting', 'block-lab' ),
119 ];
120 return $formats;
121 }
122
123 /**
124 * Sanitize the new line format, to ensure that it's valid.
125 *
126 * @param string $value The format to sanitize.
127 * @return string|null The sanitized rest_base of the post type, or null.
128 */
129 public function sanitize_new_line_format( $value ) {
130 if ( is_string( $value ) && array_key_exists( $value, $this->get_new_line_formats() ) ) {
131 return $value;
132 }
133 return null;
134 }
135 }
1 <?php
2 /**
3 * Toggle control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Toggle
14 */
15 class Toggle extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'toggle';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'boolean';
30
31 /**
32 * Toggle constructor.
33 *
34 * @return void
35 */
36 public function __construct() {
37 parent::__construct();
38 $this->label = __( 'Toggle', 'block-lab' );
39 }
40
41 /**
42 * Register settings.
43 *
44 * @return void
45 */
46 public function register_settings() {
47 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
48 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
49 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
50 $this->settings[] = new Control_Setting(
51 [
52 'name' => 'default',
53 'label' => __( 'Default Value', 'block-lab' ),
54 'type' => 'checkbox',
55 'default' => '0',
56 'sanitize' => [ $this, 'sanitize_checkbox' ],
57 ]
58 );
59 }
60 }
1 <?php
2 /**
3 * Url control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class Text
14 */
15 class Url extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'url';
23
24 /**
25 * Text constructor.
26 *
27 * @return void
28 */
29 public function __construct() {
30 parent::__construct();
31 $this->label = __( 'URL', 'block-lab' );
32 }
33
34 /**
35 * Register settings.
36 *
37 * @return void
38 */
39 public function register_settings() {
40 $this->settings[] = new Control_Setting( $this->settings_config['location'] );
41 $this->settings[] = new Control_Setting( $this->settings_config['width'] );
42 $this->settings[] = new Control_Setting( $this->settings_config['help'] );
43 $this->settings[] = new Control_Setting(
44 [
45 'name' => 'default',
46 'label' => __( 'Default Value', 'block-lab' ),
47 'type' => 'url',
48 'default' => '',
49 'sanitize' => 'esc_url_raw',
50 ]
51 );
52 $this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
53 }
54 }
1 <?php
2 /**
3 * User control.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Blocks\Controls;
11
12 /**
13 * Class User
14 */
15 class User extends Control_Abstract {
16
17 /**
18 * Control name.
19 *
20 * @var string
21 */
22 public $name = 'user';
23
24 /**
25 * Field variable type.
26 *
27 * @var string
28 */
29 public $type = 'object';
30
31 /**
32 * User constructor.
33 */
34 public function __construct() {
35 parent::__construct();
36 $this->label = __( 'User', 'block-lab' );
37 }
38
39 /**
40 * Register settings.
41 *
42 * @return void
43 */
44 public function register_settings() {
45 foreach ( [ 'location', 'width', 'help' ] as $setting ) {
46 $this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
47 }
48 }
49
50 /**
51 * Validates the value to be made available to the front-end template.
52 *
53 * @param mixed $value The value to either make available as a variable or echoed on the front-end template.
54 * @param bool $echo Whether this will be echoed.
55 * @return mixed $value The value to be made available or echoed on the front-end template.
56 */
57 public function validate( $value, $echo ) {
58 $wp_user = isset( $value['id'] ) ? get_user_by( 'id', $value['id'] ) : null;
59
60 if ( $echo ) {
61 return $wp_user ? $wp_user->get( 'display_name' ) : '';
62 } else {
63 return $wp_user ? $wp_user : false;
64 }
65 }
66 }
1 <?php
2 /**
3 * Component abstract.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab;
11
12 /**
13 * Class ComponentAbstract
14 */
15 abstract class Component_Abstract implements Component_Interface {
16
17 /**
18 * Point to the $plugin instance.
19 *
20 * @var Plugin_Interface
21 */
22 protected $plugin;
23
24 /**
25 * Set the plugin so that it can be referenced later.
26 *
27 * @param Plugin_Interface $plugin The plugin.
28 *
29 * @return Component_Interface $this
30 */
31 public function set_plugin( Plugin_Interface $plugin ) {
32 $this->plugin = $plugin;
33 return $this;
34 }
35
36 /**
37 * Handle deprecated component methods.
38 *
39 * @param string $name The name of the method called in this class.
40 * @param array $arguments The arguments passed to the method.
41 *
42 * @return mixed The result of calling the deprecated method, if it exists.
43 *
44 * @throws \Error Fallback to a standard PHP error.
45 */
46 public function __call( $name, $arguments ) {
47 $class = get_class( $this );
48 $class_name = strtolower( str_replace( '\\', '__', $class ) );
49 $function_name = "${class_name}__${name}";
50
51 if ( function_exists( $function_name ) ) {
52 return call_user_func_array( $function_name, $arguments );
53 }
54
55 // Intentionally untranslated, to match PHP's error message.
56 throw new \Error( "Call to undefined method $class::$name()" );
57 }
58
59 /**
60 * Register any hooks that this component needs.
61 *
62 * @return void
63 */
64 abstract public function register_hooks();
65 }
1 <?php
2 /**
3 * Component interface.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab;
11
12 /**
13 * Interface ComponentInterface
14 */
15 interface Component_Interface {
16
17 /**
18 * Set the plugin so that it can be referenced later.
19 *
20 * @param Plugin_Interface $plugin The plugin.
21 *
22 * @return Component_Interface $this
23 */
24 public function set_plugin( Plugin_Interface $plugin );
25
26 /**
27 * Register any hooks that this component needs.
28 *
29 * @return void
30 */
31 public function register_hooks();
32 }
1 <?php
2 /**
3 * Plugin abstract.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab;
11
12 /**
13 * Class Plugin_Abstract
14 */
15 abstract class Plugin_Abstract implements Plugin_Interface {
16
17 /**
18 * Plugin components.
19 *
20 * @var array
21 */
22 protected $components = [];
23
24 /**
25 * Plugin basename.
26 *
27 * @since 1.0.0
28 * @var string
29 */
30 protected $basename;
31
32 /**
33 * Absolute path to the main plugin directory.
34 *
35 * @since 1.0.0
36 * @var string
37 */
38 protected $directory;
39
40 /**
41 * Absolute path to the main plugin file.
42 *
43 * @since 1.0.0
44 * @var string
45 */
46 protected $file;
47
48 /**
49 * Plugin identifier.
50 *
51 * @since 1.0.0
52 * @var string
53 */
54 protected $slug;
55
56 /**
57 * URL to the main plugin directory.
58 *
59 * @since 1.0.0
60 * @var string
61 */
62 protected $url;
63
64 /**
65 * The plugin version.
66 *
67 * @since 1.0.2
68 * @var string
69 */
70 protected $version;
71
72 /**
73 * Allows calling methods in the Util class, directly in this class.
74 *
75 * When calling a method in this class that isn't defined, this calls it in $this->util if it exists.
76 * For example, on calling ->example_method() in this class,
77 * this looks for $this->util->example_method().
78 *
79 * @param string $name The name of the method called in this class.
80 * @param array $arguments The arguments passed to the method.
81 * @return mixed The result of calling the util method, if it exists.
82 * @throws \Exception On calling a method that isn't defined in this class or Util.
83 */
84 public function __call( $name, $arguments ) {
85 if ( method_exists( $this->util, $name ) ) {
86 return call_user_func_array( [ $this->util, $name ], $arguments );
87 }
88
89 if ( ! method_exists( $this, $name ) ) {
90 $class = get_class( $this );
91 throw new \Exception( "Call to undefined method {$class}::{$name}()" );
92 }
93 }
94
95 /**
96 * Get the plugin basename.
97 *
98 * @return string The basename.
99 */
100 public function get_basename() {
101 return $this->basename;
102 }
103
104 /**
105 * Set the plugin basename.
106 *
107 * @param string $basename The basename.
108 *
109 * @return Plugin_Abstract The plugin instance.
110 */
111 public function set_basename( $basename ) {
112 $this->basename = $basename;
113 return $this;
114 }
115
116 /**
117 * Get the plugin's directory.
118 *
119 * @return string The directory.
120 */
121 public function get_directory() {
122 return $this->directory;
123 }
124
125 /**
126 * Set the plugin's directory.
127 *
128 * @param string $directory The directory.
129 *
130 * @return Plugin_Abstract The plugin instance.
131 */
132 public function set_directory( $directory ) {
133 $this->directory = rtrim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
134 return $this;
135 }
136
137 /**
138 * Get the relative path to the plugin's directory.
139 *
140 * @param string $path Relative path to return.
141 *
142 * @return string The path.
143 */
144 public function get_path( $path = '' ) {
145 return $this->directory . ltrim( $path, DIRECTORY_SEPARATOR );
146 }
147
148 /**
149 * Get the plugin file.
150 *
151 * @return string The file.
152 */
153 public function get_file() {
154 return $this->file;
155 }
156
157 /**
158 * Set the plugin file.
159 *
160 * @param string $file The plugin file.
161 *
162 * @return Plugin_Abstract The plugin instance.
163 */
164 public function set_file( $file ) {
165 $this->file = $file;
166 return $this;
167 }
168
169 /**
170 * Get the plugin's slug.
171 *
172 * @return string The slug.
173 */
174 public function get_slug() {
175 return $this->slug;
176 }
177
178 /**
179 * Set the plugin's slug.
180 *
181 * @param string $slug The slug.
182 *
183 * @return Plugin_Abstract The plugin instance.
184 */
185 public function set_slug( $slug ) {
186 $this->slug = $slug;
187 return $this;
188 }
189
190 /**
191 * Get the relative url.
192 *
193 * @param string $path The relative url to get.
194 *
195 * @return string The url.
196 */
197 public function get_url( $path = '' ) {
198 return $this->url . ltrim( $path, '/' );
199 }
200
201 /**
202 * Set the plugin's url.
203 *
204 * @param string $url The url.
205 *
206 * @return Plugin_Abstract The plugin instance.
207 */
208 public function set_url( $url ) {
209 $this->url = rtrim( $url, '/' ) . '/';
210 return $this;
211 }
212
213 /**
214 * Get the plugin's version.
215 *
216 * @return string The url.
217 */
218 public function get_version() {
219 if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
220 return time();
221 }
222 return $this->version;
223 }
224
225 /**
226 * Set the plugin's version.
227 *
228 * @param string $file The absolute path to the plugin file.
229 *
230 * @return Plugin_Abstract The plugin instance.
231 */
232 public function set_version( $file ) {
233 $headers = [ 'Version' => 'Version' ];
234 $file_data = get_file_data( $file, $headers, 'plugin' );
235
236 if ( isset( $file_data['Version'] ) ) {
237 $this->version = $file_data['Version'];
238 };
239
240 return $this;
241 }
242
243 /**
244 * Get url relative to assets url.
245 *
246 * @param string $path The relative url to get.
247 *
248 * @return string The url.
249 */
250 public function get_assets_url( $path = '' ) {
251 return $this->url . 'assets/' . ltrim( $path, '/' );
252 }
253
254 /**
255 * Get the relative path to the assets directory.
256 *
257 * @param string $path Relative path to return.
258 *
259 * @return string The path.
260 */
261 public function get_assets_path( $path = '' ) {
262 return $this->directory . 'assets' . DIRECTORY_SEPARATOR . ltrim( $path, DIRECTORY_SEPARATOR );
263 }
264
265 /**
266 * Register a new Component.
267 *
268 * @param Component_Interface $component The new component.
269 *
270 * @return Plugin_Abstract The plugin instance.
271 */
272 public function register_component( Component_Interface $component ) {
273
274 $component_class = get_class( $component );
275
276 // If component already registered, then there is nothing left to do.
277 if ( array_key_exists( $component_class, $this->components ) ) {
278 return $this;
279 }
280
281 // Make sure the plugin is available.
282 if ( method_exists( $component, 'set_plugin' ) ) {
283 $component->set_plugin( $this );
284 }
285
286 // Run component init method.
287 if ( method_exists( $component, 'init' ) ) {
288 $component->init( $this );
289 }
290
291 $component->register_hooks();
292
293 $this->components[ $component_class ] = $component;
294
295 return $this;
296 }
297
298 /**
299 * Runs as early as possible.
300 *
301 * @return void Nothing to return.
302 */
303 abstract public function init();
304
305 /**
306 * Runs once 'plugins_loaded' hook fires.
307 *
308 * @return void Nothing to return.
309 */
310 abstract public function plugin_loaded();
311 }
1 <?php
2 /**
3 * Plugin interface.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab;
11
12 /**
13 * Interface Plugin_Interface
14 */
15 interface Plugin_Interface {
16
17 /**
18 * Get the plugin basename.
19 *
20 * @return string The basename.
21 */
22 public function get_basename();
23
24 /**
25 * Set the plugin basename.
26 *
27 * @param string $basename The basename.
28 *
29 * @return Plugin_Interface The plugin instance.
30 */
31 public function set_basename( $basename );
32
33 /**
34 * Get the plugin's directory.
35 *
36 * @return string The directory.
37 */
38 public function get_directory();
39
40 /**
41 * Set the plugin's directory.
42 *
43 * @param string $directory The directory.
44 *
45 * @return Plugin_Interface The plugin instance.
46 */
47 public function set_directory( $directory );
48
49 /**
50 * Get the relative path to the plugin's directory.
51 *
52 * @param string $path Relative path to return.
53 *
54 * @return string The path.
55 */
56 public function get_path( $path = '' );
57
58 /**
59 * Get the plugin file.
60 *
61 * @return string The file.
62 */
63 public function get_file();
64
65 /**
66 * Set the plugin file.
67 *
68 * @param string $file The plugin file.
69 *
70 * @return Plugin_Interface The plugin instance.
71 */
72 public function set_file( $file );
73
74 /**
75 * Get the plugin's slug.
76 *
77 * @return string The slug.
78 */
79 public function get_slug();
80
81 /**
82 * Set the plugin's slug.
83 *
84 * @param string $slug The slug.
85 *
86 * @return Plugin_Interface The plugin instance.
87 */
88 public function set_slug( $slug );
89
90 /**
91 * Get the relative url.
92 *
93 * @param string $path The relative url to get.
94 *
95 * @return string The url.
96 */
97 public function get_url( $path = '' );
98
99 /**
100 * Set the plugin's url.
101 *
102 * @param string $url The url.
103 *
104 * @return Plugin_Interface The plugin instance.
105 */
106 public function set_url( $url );
107
108 /**
109 * Get the plugin's version.
110 *
111 * @return string The version.
112 */
113 public function get_version();
114
115 /**
116 * Set the plugin's version, based on the file.
117 *
118 * @param string $file The absolute path to the plugin file.
119 *
120 * @return Plugin_Interface The plugin instance.
121 */
122 public function set_version( $file );
123
124 /**
125 * Get url relative to assets url.
126 *
127 * @param string $path The relative url to get.
128 *
129 * @return string The url.
130 */
131 public function get_assets_url( $path = '' );
132
133 /**
134 * Get the relative path to the assets directory.
135 *
136 * @param string $path Relative path to return.
137 *
138 * @return string The path.
139 */
140 public function get_assets_path( $path = '' );
141
142 /**
143 * Register a new Component.
144 *
145 * @param Component_Interface $component The new component.
146 *
147 * @return Plugin_Interface The plugin instance.
148 */
149 public function register_component( Component_Interface $component );
150
151 /**
152 * Runs once 'plugins_loaded' hook fires.
153 *
154 * @return void Nothing to return.
155 */
156 public function plugin_loaded();
157 }
1 <?php
2 /**
3 * Primary plugin file.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab;
11
12 /**
13 * Class Plugin
14 */
15 class Plugin extends Plugin_Abstract {
16
17 /**
18 * Utility methods.
19 *
20 * @var Util
21 */
22 protected $util;
23
24 /**
25 * WP Admin resources.
26 *
27 * @var Admin\Admin
28 */
29 public $admin;
30
31 /**
32 * Block loader.
33 *
34 * @var Blocks\Loader
35 */
36 public $loader;
37
38 /**
39 * The slug of the post type that stores the blocks.
40 *
41 * @since 1.3.5
42 * @var string
43 */
44 public $post_type_slug = 'block_lab';
45
46 /**
47 * Execute this as early as possible.
48 */
49 public function init() {
50 $this->util = new Util();
51 $this->register_component( $this->util );
52 $this->register_component( new Post_Types\Block_Post() );
53
54 $this->loader = new Blocks\Loader();
55 $this->register_component( $this->loader );
56
57 register_activation_hook(
58 $this->get_file(),
59 function() {
60 $onboarding = new Admin\Onboarding();
61 $onboarding->plugin_activation();
62 }
63 );
64 }
65
66 /**
67 * Execute this once plugins are loaded. (not the best place for all hooks)
68 */
69 public function plugin_loaded() {
70 $this->admin = new Admin\Admin();
71 $this->register_component( $this->admin );
72 }
73
74 /**
75 * Requires helpers, or displays a notice if there's a conflict with another plugin.
76 */
77 public function require_helpers() {
78 if ( $this->is_plugin_conflict() ) {
79 add_action( 'admin_notices', [ $this, 'plugin_conflict_notice' ] );
80 } else {
81 require_once __DIR__ . '/helpers.php';
82 require_once __DIR__ . '/deprecated.php';
83 }
84 }
85
86 /**
87 * Gets whether there is a conflict from another plugin having the same functions.
88 *
89 * @return bool Whether there is a conflict.
90 */
91 public function is_plugin_conflict() {
92 return function_exists( 'block_field' ) && function_exists( 'block_value' );
93 }
94
95 /**
96 * An admin notice for another plugin being active.
97 *
98 * Only display this if the user can deactivate plugins,
99 * and if this is on a Block Lab or plugins page.
100 */
101 public function plugin_conflict_notice() {
102 if ( ! current_user_can( 'deactivate_plugins' ) ) {
103 return;
104 }
105
106 $screen = get_current_screen();
107 $should_display_notice = (
108 ( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && $this->post_type_slug === $screen->post_type )
109 ||
110 ( isset( $screen->base ) && in_array( $screen->base, [ 'plugins', 'block_lab_page_block-lab-settings' ], true ) )
111 );
112
113 if ( ! $should_display_notice ) {
114 return;
115 }
116
117 wp_enqueue_style(
118 'block-lab-plugin-conflict-notice-style',
119 $this->get_url( 'css/admin.conflict-notice.css' ),
120 [],
121 $this->get_version()
122 );
123
124 $plugin_file = 'block-lab/block-lab.php';
125 $deactivation_url = add_query_arg(
126 [
127 'action' => 'deactivate',
128 'plugin' => rawurlencode( $plugin_file ),
129 'plugin_status' => 'all',
130 'paged' => 1,
131 '_wpnonce' => wp_create_nonce( 'deactivate-plugin_' . $plugin_file ),
132 ],
133 admin_url( 'plugins.php' )
134 );
135
136 ?>
137 <div id="bl-conflict-notice" class="notice notice-error bl-notice-conflict">
138 <div class="bl-conflict-copy">
139 <p><?php esc_html_e( 'It looks like Block Lab is active. Please deactivate it or migrate, as it will not work while Genesis Custom Blocks is active.', 'block-lab' ); ?></p>
140 </div>
141 <a href="<?php echo esc_url( $deactivation_url ); ?>" class="bl-link-deactivate button button-primary">
142 <?php echo esc_html_x( 'Deactivate', 'plugin', 'block-lab' ); ?>
143 </a>
144 </div>
145 <?php
146 }
147 }
1 <?php
2 /**
3 * Helper functions for the Block_Lab plugin.
4 *
5 * These are publicly accessible via a magic method, like block_lab()->get_template_locations().
6 * So these methods should generally be 'getter' functions, and should not affect the global state.
7 *
8 * @package Block_Lab
9 */
10
11 namespace Block_Lab;
12
13 use Block_Lab\Blocks;
14
15 /**
16 * Class Util
17 */
18 class Util extends Component_Abstract {
19
20 /**
21 * Not implemented, as this class only has utility methods.
22 */
23 public function register_hooks() {}
24
25 /**
26 * Gets whether a valid Pro license has been activated on this site.
27 *
28 * @return bool
29 */
30 public function is_pro() {
31 return true;
32 }
33
34 /**
35 * Get the loop handler.
36 *
37 * @return Blocks\Loop
38 */
39 public function loop() {
40 static $instance;
41
42 if ( null === $instance ) {
43 $instance = new Blocks\Loop();
44 return $instance;
45 }
46
47 return $instance;
48 }
49
50 /**
51 * Gets an array of possible template locations.
52 *
53 * @param string $name The name of the block (slug as defined in UI).
54 * @param string $type The type of template to load. Typically block or preview.
55 *
56 * @return array
57 */
58 public function get_template_locations( $name, $type = 'block' ) {
59 return [
60 "blocks/{$name}/{$type}.php",
61 "blocks/{$type}-{$name}.php",
62 "blocks/{$type}.php",
63 ];
64 }
65
66 /**
67 * Gets an array of possible stylesheet locations.
68 *
69 * @param string $name The name of the block (slug as defined in UI).
70 * @param string $type The type of template to load. Typically block or preview.
71 *
72 * @return array
73 */
74 public function get_stylesheet_locations( $name, $type = 'block' ) {
75 return [
76 "blocks/{$name}/{$type}.css",
77 "blocks/css/{$type}-{$name}.css",
78 "blocks/{$type}-{$name}.css",
79 ];
80 }
81
82 /**
83 * Locates templates.
84 *
85 * Works similar to `locate_template`, but allows specifying a path outside of themes
86 * and allows to be called when STYLESHEET_PATH has not been set yet. Handy for async.
87 *
88 * @param string|array $template_names Templates to locate.
89 * @param string $path (Optional) Path to locate the templates first.
90 * @param bool $single `true` - Returns only the first found item. Like standard `locate_template`
91 * `false` - Returns all found templates.
92 *
93 * @return string|array
94 */
95 public function locate_template( $template_names, $path = '', $single = true ) {
96 /**
97 * Filters the path where block templates are saved.
98 *
99 * Note that template names are prefixed with the blocks directory.
100 * e.g. `blocks/block-template.php`
101 * The logic below will look for the prefixed template name inside the $path.
102 *
103 * @param string $path The absolute path to the stylesheet directory.
104 * @param string|array $template_names Templates to locate.
105 */
106 $path = apply_filters( 'block_lab_template_path', $path, $template_names );
107
108 $stylesheet_path = get_template_directory();
109 $template_path = get_stylesheet_directory();
110
111 $located = [];
112
113 foreach ( (array) $template_names as $template_name ) {
114
115 if ( ! $template_name ) {
116 continue;
117 }
118
119 if ( ! empty( $path ) && file_exists( trailingslashit( $path ) . $template_name ) ) {
120 $located[] = trailingslashit( $path ) . $template_name;
121 if ( $single ) {
122 break;
123 }
124 }
125
126 if ( file_exists( trailingslashit( $template_path ) . $template_name ) ) {
127 $located[] = trailingslashit( $template_path ) . $template_name;
128 if ( $single ) {
129 break;
130 }
131 }
132
133 if ( file_exists( trailingslashit( $stylesheet_path ) . $template_name ) ) {
134 $located[] = trailingslashit( $stylesheet_path ) . $template_name;
135 if ( $single ) {
136 break;
137 }
138 }
139
140 if ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
141 $located[] = ABSPATH . WPINC . '/theme-compat/' . $template_name;
142 if ( $single ) {
143 break;
144 }
145 }
146 }
147
148 // Remove duplicates and re-index array.
149 $located = array_values( array_unique( $located ) );
150
151 if ( $single ) {
152 return array_shift( $located );
153 }
154
155 return $located;
156 }
157
158 /**
159 * Provides a list of all available block icons.
160 *
161 * To include additional icons in this list, use the block_lab_icons filter, and add a new svg string to the array,
162 * using a unique key. For example:
163 *
164 * $icons['foo'] = '<svg>…</svg>';
165 *
166 * @return array
167 */
168 public function get_icons() {
169 // This is on the local filesystem, so file_get_contents() is ok to use here.
170 $json_file = block_lab()->get_assets_path( 'icons.json' );
171 $json = file_get_contents( $json_file ); // @codingStandardsIgnoreLine
172 $icons = json_decode( $json, true );
173
174 /**
175 * The available block icons.
176 *
177 * @param array $icons The available icons.
178 */
179 return apply_filters( 'block_lab_icons', $icons );
180 }
181
182 /**
183 * Provides a list of allowed tags to be used by an <svg>.
184 *
185 * @return array
186 */
187 public function allowed_svg_tags() {
188 $allowed_tags = [
189 'svg' => [
190 'xmlns' => true,
191 'width' => true,
192 'height' => true,
193 'viewbox' => true,
194 ],
195 'g' => [ 'fill' => true ],
196 'title' => [ 'title' => true ],
197 'path' => [
198 'd' => true,
199 'fill' => true,
200 'opacity' => true,
201 ],
202 'circle' => [
203 'cx' => true,
204 'cy' => true,
205 'r' => true,
206 'fill' => true,
207 ],
208 ];
209
210 /**
211 * The tags that an <svg> allows.
212 *
213 * @param array $allowed_tags The allowed tags.
214 */
215 return apply_filters( 'block_lab_allowed_svg_tags', $allowed_tags );
216 }
217
218 /**
219 * Gets the slug of the post type that stores the blocks.
220 *
221 * @return string The slug.
222 */
223 public function get_post_type_slug() {
224 return $this->plugin->post_type_slug;
225 }
226
227 /**
228 * Get a relative URL from a path.
229 *
230 * @param string $path The absolute path to a file.
231 *
232 * @return string
233 */
234 public function get_url_from_path( $path ) {
235 $abspath = ABSPATH;
236
237 // Workaround for weird hosting situations.
238 if ( trailingslashit( ABSPATH ) . 'wp-content' !== WP_CONTENT_DIR && isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
239 $abspath = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) );
240 }
241
242 $stylesheet_url = str_replace( untrailingslashit( $abspath ), '', $path );
243
244 return $stylesheet_url;
245 }
246 }
1 <?php
2 /**
3 * Deprecated functions.
4 *
5 * Deprecated methods can also appear as functions here, with the format namespace__class__method().
6 *
7 * @see Block_Lab\Component_Abstract->_call()
8 *
9 * @package Block_Lab
10 * @copyright Copyright(c) 2018, Block Lab
11 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
12 */
13
14 /**
15 * Show a PHP error to warn developers using deprecated functions.
16 *
17 * @param string $function The function that was called.
18 * @param string $version The version of Block Lab that deprecated the function.
19 * @param string $replacement The function that should have been called.
20 */
21 function block_lab_deprecated_function( $function, $version, $replacement ) {
22 _deprecated_function(
23 // filter_var is used for sanitization here as it allows arrow functions ("->").
24 filter_var(
25 sprintf(
26 // translators: A function name.
27 __( 'Block Lab\'s %1$s', 'block-lab' ),
28 $function
29 ),
30 FILTER_SANITIZE_STRING
31 ),
32 esc_html( $version ),
33 filter_var( $replacement, FILTER_SANITIZE_STRING )
34 );
35 }
36
37 /**
38 * Handle the deprecated block_lab_get_icons() function.
39 *
40 * @see \Block_Lab\Util->get_icons()
41 *
42 * @return array
43 */
44 function block_lab_get_icons() {
45 block_lab_deprecated_function( 'block_lab_get_icons', '1.3.5', 'block_lab()->get_icons()' );
46 return block_lab()->get_icons();
47 }
48
49 /**
50 * Handle the deprecated block_lab_allowed_svg_tags() function.
51 *
52 * @see \Block_Lab\Util->allowed_svg_tags()
53 *
54 * @return array
55 */
56 function block_lab_allowed_svg_tags() {
57 block_lab_deprecated_function( 'block_lab_allowed_svg_tags', '1.3.5', 'block_lab()->allowed_svg_tags()' );
58 return block_lab()->allowed_svg_tags();
59 }
1 <?php
2 /**
3 * Helper functions.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 use Block_Lab\Blocks;
11
12 /**
13 * Return the value of a block field.
14 *
15 * @param string $name The name of the field.
16 * @param bool $echo Whether to echo and return the field, or just return the field.
17 *
18 * @return mixed
19 */
20 function block_field( $name, $echo = true ) {
21 $attributes = block_lab()->loader->get_data( 'attributes' );
22
23 if ( ! $attributes ) {
24 return null;
25 }
26
27 $config = block_lab()->loader->get_data( 'config' );
28
29 if ( ! $config ) {
30 return null;
31 }
32
33 $default_fields = [ 'className' => 'string' ];
34
35 /**
36 * Filters the default fields that are allowed in addition to Block Lab fields.
37 *
38 * Adding an attribute to this can enable outputting it via block_field().
39 * Normally, this function only returns or echoes Block Lab attributes (fields), and one default field.
40 * But this allows getting block attributes that might have been added by other plugins or JS.
41 * To allow getting another attribute, add it to the $default_fields associative array.
42 * For example, 'your-example-field' => 'array'.
43 *
44 * @param array $default_fields An associative array of $field_name => $field_type.
45 * @param string $name The name of value to get.
46 */
47 $default_fields = apply_filters( 'block_lab_default_fields', $default_fields, $name );
48
49 if ( ! isset( $config->fields[ $name ] ) && ! isset( $default_fields[ $name ] ) ) {
50 return null;
51 }
52
53 $field = null;
54 $value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
55 $control = null;
56
57 if ( array_key_exists( $name, $attributes ) ) {
58 $value = $attributes[ $name ];
59 }
60
61 if ( isset( $config->fields[ $name ] ) ) {
62 // Cast the value with the correct type.
63 $field = $config->fields[ $name ];
64 $value = $field->cast_value( $value );
65 $control = $field->control;
66 } elseif ( isset( $default_fields[ $name ] ) ) {
67 // Cast default Editor attributes and those added via a filter.
68 $field = new Blocks\Field( [ 'type' => $default_fields[ $name ] ] );
69 $value = $field->cast_value( $value );
70 }
71
72 /**
73 * Filters the value to be made available or echoed on the front-end template.
74 *
75 * @param mixed $value The value.
76 * @param string|null $control The type of the control, like 'user', or null if this is the 'className', which has no control.
77 * @param bool $echo Whether or not this value will be echoed.
78 */
79 $value = apply_filters( 'block_lab_field_value', $value, $control, $echo );
80
81 if ( $echo ) {
82 if ( $field ) {
83 $value = $field->cast_value_to_string( $value );
84 }
85
86 /*
87 * Escaping this value may cause it to break in some use cases.
88 * If this happens, retrieve the field's value using block_value(),
89 * and then output the field with a more suitable escaping function.
90 */
91 echo wp_kses_post( $value );
92
93 return null;
94 }
95
96 return $value;
97 }
98
99 /**
100 * Return the value of a block field, without echoing it.
101 *
102 * @param string $name The name of the field as created in the UI.
103 *
104 * @uses block_field()
105 *
106 * @return mixed
107 */
108 function block_value( $name ) {
109 return block_field( $name, false );
110 }
111
112 /**
113 * Prepare a loop with the first or next row in a repeater.
114 *
115 * @param string $name The name of the repeater field.
116 *
117 * @return int
118 */
119 function block_row( $name ) {
120 block_lab()->loop()->set_active( $name );
121 return block_lab()->loop()->increment( $name );
122 }
123
124 /**
125 * Determine whether another repeater row exists to loop through.
126 *
127 * @param string $name The name of the repeater field.
128 *
129 * @return bool
130 */
131 function block_rows( $name ) {
132 $attributes = block_lab()->loader->get_data( 'attributes' );
133
134 if ( ! isset( $attributes[ $name ] ) ) {
135 return false;
136 }
137
138 $current_row = block_lab()->loop()->get_row( $name );
139
140 if ( false === $current_row ) {
141 $next_row = 0;
142 } else {
143 $next_row = $current_row + 1;
144 }
145
146 if ( isset( $attributes[ $name ]['rows'][ $next_row ] ) ) {
147 return true;
148 }
149
150 return false;
151 }
152
153 /**
154 * Resets the repeater block rows after the while loop.
155 *
156 * Similar to wp_reset_postdata(). Call this after the repeater loop.
157 * For example:
158 *
159 * while ( block_rows( 'example-repeater-name' ) ) :
160 * block_row( 'example-repeater-name' );
161 * block_sub_field( 'example-field' );
162 * endwhile;
163 * reset_block_rows( 'example-repeater-name' );
164 *
165 * @param string $name The name of the repeater field.
166 */
167 function reset_block_rows( $name ) {
168 block_lab()->loop()->reset( $name );
169 }
170
171 /**
172 * Return the total amount of rows in a repeater.
173 *
174 * @param string $name The name of the repeater field.
175 * @return int|bool The total amount of rows. False if the repeater isn't found.
176 */
177 function block_row_count( $name ) {
178 $attributes = block_lab()->loader->get_data( 'attributes' );
179
180 if ( ! isset( $attributes[ $name ]['rows'] ) ) {
181 return false;
182 }
183
184 return count( $attributes[ $name ]['rows'] );
185 }
186
187 /**
188 * Return the index of the current repeater row.
189 *
190 * Note: The index is zero-based, which means that the first row in a repeater has
191 * an index of 0, the second row has an index of 1, and so on.
192 *
193 * @param string $name (Optional) The name of the repeater field.
194 * @return int|bool The index of the row. False if the repeater isn't found.
195 */
196 function block_row_index( $name = '' ) {
197 if ( '' === $name ) {
198 $name = block_lab()->loop()->active;
199 }
200
201 if ( ! isset( block_lab()->loop()->loops[ $name ] ) ) {
202 return false;
203 }
204
205 return block_lab()->loop()->loops[ $name ];
206 }
207
208 /**
209 * Return the value of a sub-field.
210 *
211 * @param string $name The name of the sub-field.
212 * @param bool $echo Whether to echo and return the field, or just return the field.
213 *
214 * @return mixed
215 */
216 function block_sub_field( $name, $echo = true ) {
217 $attributes = block_lab()->loader->get_data( 'attributes' );
218
219 if ( ! is_array( $attributes ) ) {
220 return null;
221 }
222
223 $config = block_lab()->loader->get_data( 'config' );
224
225 if ( ! $config ) {
226 return null;
227 }
228
229 $parent = block_lab()->loop()->active;
230 $pointer = block_lab()->loop()->get_row( $parent );
231
232 if ( ! isset( $config->fields[ $parent ] ) ) {
233 return null;
234 }
235
236 $value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
237 $control = null;
238
239 // Get the value from the block attributes, with the correct type.
240 if ( ! array_key_exists( $parent, $attributes ) || ! isset( $attributes[ $parent ]['rows'] ) ) {
241 return;
242 }
243
244 $parent_attributes = $attributes[ $parent ]['rows'];
245 $row_attributes = $parent_attributes[ $pointer ];
246
247 if ( ! array_key_exists( $name, $row_attributes ) ) {
248 return;
249 }
250
251 $field = $config->fields[ $parent ]->settings['sub_fields'][ $name ];
252 $control = $field->control;
253 $value = $row_attributes[ $name ];
254 $value = $field->cast_value( $value );
255
256 /**
257 * Filters the value to be made available or echoed on the front-end template.
258 *
259 * @param mixed $value The value.
260 * @param string|null $control The type of the control, like 'user', or null if this is the 'className', which has no control.
261 * @param bool $echo Whether or not this value will be echoed.
262 */
263 $value = apply_filters( 'block_lab_sub_field_value', $value, $control, $echo );
264
265 if ( $echo ) {
266 $value = $field->cast_value_to_string( $value );
267
268 /*
269 * Escaping this value may cause it to break in some use cases.
270 * If this happens, retrieve the field's value using block_value(),
271 * and then output the field with a more suitable escaping function.
272 */
273 echo wp_kses_post( $value );
274 }
275
276 return $value;
277 }
278
279 /**
280 * Return the value of a sub-field, without echoing it.
281 *
282 * @param string $name The name of the sub-field.
283 *
284 * @uses block_field()
285 *
286 * @return mixed
287 */
288 function block_sub_value( $name ) {
289 return block_sub_field( $name, false );
290 }
291
292 /**
293 * Convenience method to return the block configuration.
294 *
295 * @return array
296 */
297 function block_config() {
298 $config = block_lab()->loader->get_data( 'config' );
299
300 if ( ! $config ) {
301 return null;
302 }
303
304 return (array) $config;
305 }
306
307 /**
308 * Convenience method to return a field's configuration.
309 *
310 * @param string $name The name of the field as created in the UI.
311 *
312 * @return array|null
313 */
314 function block_field_config( $name ) {
315 $config = block_lab()->loader->get_data( 'config' );
316
317 if ( ! $config || ! isset( $config->fields[ $name ] ) ) {
318 return null;
319 }
320
321 return (array) $config->fields[ $name ];
322 }
323
324 /**
325 * Add a new block.
326 *
327 * @param string $block_name The block name (slug), like 'example-block'.
328 * @param array $block_config {
329 * An associative array containing the block configuration.
330 *
331 * @type string $title The block title.
332 * @type string $icon The block icon. See assets/icons.json for a JSON array of all possible values. Default: 'block_lab'.
333 * @type string $category The slug of a registered category. Categories include: common, formatting, layout, widgets, embed. Default: 'common'.
334 * @type array $excluded Exclude the block in these post types. Default: [].
335 * @type string[] $keywords An array of up to three keywords. Default: [].
336 * @type array $fields {
337 * An associative array containing block fields. Each key in the array should be the field slug.
338 *
339 * @type array {$slug} {
340 * An associative array describing a field. Refer to the $field_config parameter of block_lab_add_field().
341 * }
342 * }
343 * }
344 */
345 function block_lab_add_block( $block_name, $block_config = [] ) {
346 $block_config['name'] = str_replace( '_', '-', sanitize_title( $block_name ) );
347
348 $default_config = [
349 'title' => str_replace( '-', ' ', ucwords( $block_config['name'], '-' ) ),
350 'icon' => 'block_lab',
351 'category' => 'common',
352 'excluded' => [],
353 'keywords' => [],
354 'fields' => [],
355 ];
356
357 $block_config = wp_parse_args( $block_config, $default_config );
358 block_lab()->loader->add_block( $block_config );
359 }
360
361 /**
362 * Add a field to a block.
363 *
364 * @param string $block_name The block name (slug), like 'example-block'.
365 * @param string $field_name The field name (slug), like 'first-name'.
366 * @param array $field_config {
367 * An associative array containing the field configuration.
368 *
369 * @type string $name The field name.
370 * @type string $label The field label.
371 * @type string $control The field control type. Default: 'text'.
372 * @type int $order The order that the field appears in. Default: 0.
373 * @type array $settings {
374 * An associative array of settings for the field. Each field has a different set of possible settings.
375 * Check the register_settings method for the field, found in php/blocks/controls/class-{field name}.php.
376 * }
377 * }
378 */
379 function block_lab_add_field( $block_name, $field_name, $field_config = [] ) {
380 $field_config['name'] = str_replace( '_', '-', sanitize_title( $field_name ) );
381
382 $default_config = [
383 'label' => str_replace( '-', ' ', ucwords( $field_config['name'], '-' ) ),
384 'control' => 'text',
385 'order' => 0,
386 'settings' => [],
387 ];
388
389 $field_config = wp_parse_args( $field_config, $default_config );
390 block_lab()->loader->add_field( $block_name, $field_config );
391 }
1 <?php
2 /**
3 * Block Post Type.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 namespace Block_Lab\Post_Types;
11
12 use Block_Lab\Component_Abstract;
13 use Block_Lab\Blocks\Block;
14 use Block_Lab\Blocks\Field;
15 use Block_Lab\Blocks\Controls;
16
17 /**
18 * Class Block
19 */
20 class Block_Post extends Component_Abstract {
21
22 /**
23 * Slug used for the custom post type.
24 *
25 * @var string
26 */
27 public $slug;
28
29 /**
30 * Registered controls.
31 *
32 * @var Controls\Control_Abstract[]
33 */
34 public $controls = [];
35
36 /**
37 * The pro controls.
38 *
39 * @var array
40 */
41 public $pro_controls = [
42 'repeater',
43 'post',
44 'rich_text',
45 'classic_text',
46 'taxonomy',
47 'user',
48 ];
49
50 /**
51 * Block Post constructor.
52 */
53 public function __construct() {
54 $this->slug = block_lab()->get_post_type_slug();
55 }
56
57 /**
58 * Register any hooks that this component needs.
59 *
60 * @return void
61 */
62 public function register_hooks() {
63 add_action( 'init', [ $this, 'register_post_type' ] );
64 add_action( 'admin_init', [ $this, 'add_caps' ] );
65 add_action( 'admin_init', [ $this, 'row_export' ] );
66 add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
67 add_action( 'add_meta_boxes', [ $this, 'remove_meta_boxes' ] );
68 add_action( 'edit_form_before_permalink', [ $this, 'template_location' ] );
69 add_action( 'post_submitbox_start', [ $this, 'save_draft_button' ] );
70 add_filter( 'enter_title_here', [ $this, 'post_title_placeholder' ] );
71 add_action( 'post_submitbox_misc_actions', [ $this, 'post_type_condition' ] );
72 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
73 add_action( 'wp_insert_post_data', [ $this, 'save_block' ], 10, 2 );
74 add_action( 'init', [ $this, 'register_controls' ] );
75 add_filter( 'block_lab_field_value', [ $this, 'get_field_value' ], 10, 3 );
76 add_filter( 'block_lab_sub_field_value', [ $this, 'get_field_value' ], 10, 3 );
77
78 // Clean up the list table.
79 add_filter( 'disable_months_dropdown', '__return_true', 10, $this->slug );
80 add_filter( 'page_row_actions', [ $this, 'page_row_actions' ], 10, 1 );
81 add_filter( 'bulk_actions-edit-' . $this->slug, [ $this, 'bulk_actions' ] );
82 add_filter( 'handle_bulk_actions-edit-' . $this->slug, [ $this, 'bulk_export' ], 10, 3 );
83 add_filter( 'manage_edit-' . $this->slug . '_columns', [ $this, 'list_table_columns' ] );
84 add_action( 'manage_' . $this->slug . '_posts_custom_column', [ $this, 'list_table_content' ], 10, 2 );
85
86 // AJAX Handlers.
87 add_action( 'wp_ajax_fetch_field_settings', [ $this, 'ajax_field_settings' ] );
88 }
89
90 /**
91 * Register the controls.
92 *
93 * @return void
94 */
95 public function register_controls() {
96 $control_names = [
97 'text',
98 'textarea',
99 'url',
100 'email',
101 'number',
102 'color',
103 'image',
104 'select',
105 'multiselect',
106 'toggle',
107 'range',
108 'checkbox',
109 'radio',
110 ];
111
112 if ( block_lab()->is_pro() ) {
113 $control_names = array_merge( $control_names, $this->pro_controls );
114 }
115
116 foreach ( $control_names as $control_name ) {
117 $control = $this->get_control( $control_name );
118 if ( $control ) {
119 $controls[ $control->name ] = $control;
120 }
121 }
122
123 /**
124 * Filters the available controls.
125 *
126 * @param array $controls {
127 * An associative array of the available controls.
128 *
129 * @type string $control_name The name of the control, like 'user'.
130 * @type object $control The control opbject, extending Controls\Control_Abstract.
131 * }
132 */
133 $this->controls = apply_filters( 'block_lab_controls', $controls );
134 }
135
136 /**
137 * Gets an instantiated control.
138 *
139 * @param string $control_name The name of the control.
140 * @return object|null The instantiated control, or null.
141 */
142 public function get_control( $control_name ) {
143 if ( isset( $this->controls[ $control_name ] ) ) {
144 return $this->controls[ $control_name ];
145 }
146
147 $class_name = ucwords( $control_name, '_' );
148 $control_class = 'Block_Lab\\Blocks\\Controls\\' . $class_name;
149 if ( class_exists( $control_class ) ) {
150 return new $control_class();
151 }
152 }
153
154 /**
155 * Gets the field value to be made available or echoed on the front-end template.
156 *
157 * Gets the value based on the control type.
158 * For example, a 'user' control can return a WP_User, a string, or false.
159 * The $echo parameter is whether the value will be echoed on the front-end template,
160 * or simply made available.
161 *
162 * @param mixed $value The field value.
163 * @param string $control The type of the control, like 'user'.
164 * @param bool $echo Whether or not this value will be echoed.
165 * @return mixed $value The filtered field value.
166 */
167 public function get_field_value( $value, $control, $echo ) {
168 if ( isset( $this->controls[ $control ] ) && method_exists( $this->controls[ $control ], 'validate' ) ) {
169 return call_user_func( [ $this->controls[ $control ], 'validate' ], $value, $echo );
170 } elseif ( in_array( $control, $this->pro_controls, true ) && ! block_lab()->is_pro() ) {
171 $pro_control = $this->get_control( $control );
172 if ( method_exists( $pro_control, 'validate' ) ) {
173 return call_user_func( [ $pro_control, 'validate' ], $value, $echo );
174 }
175 }
176
177 return $value;
178 }
179
180 /**
181 * Register the custom post type.
182 *
183 * @return void
184 */
185 public function register_post_type() {
186 $labels = [
187 'name' => _x( 'Content Blocks', 'post type general name', 'block-lab' ),
188 'singular_name' => _x( 'Content Block', 'post type singular name', 'block-lab' ),
189 'menu_name' => _x( 'Block Lab', 'admin menu', 'block-lab' ),
190 'name_admin_bar' => _x( 'Block', 'add new on admin bar', 'block-lab' ),
191 'add_new' => _x( 'Add New', 'block', 'block-lab' ),
192 'add_new_item' => __( 'Add New Block', 'block-lab' ),
193 'new_item' => __( 'New Block', 'block-lab' ),
194 'edit_item' => __( 'Edit Block', 'block-lab' ),
195 'view_item' => __( 'View Block', 'block-lab' ),
196 'all_items' => __( 'All Blocks', 'block-lab' ),
197 'search_items' => __( 'Search Blocks', 'block-lab' ),
198 'parent_item_colon' => __( 'Parent Blocks:', 'block-lab' ),
199 'not_found' => __( 'No blocks found.', 'block-lab' ),
200 'not_found_in_trash' => __( 'No blocks found in Trash.', 'block-lab' ),
201 ];
202
203 $args = [
204 'labels' => $labels,
205 'public' => false,
206 'show_ui' => true,
207 'show_in_menu' => true,
208 'menu_position' => 100,
209 'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
210 file_get_contents( $this->plugin->get_assets_path( 'images/admin-menu-icon.svg' ) ) // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- This SVG icon is being included from the plugin directory, so using file_get_contents is okay.
211 ),
212 'query_var' => true,
213 'rewrite' => [ 'slug' => $this->slug ],
214 'hierarchical' => true,
215 'capabilities' => $this->get_capabilities(),
216 'map_meta_cap' => true,
217 'supports' => [ 'title' ],
218 ];
219
220 register_post_type( $this->slug, $args );
221 }
222
223 /**
224 * Add custom capabilities
225 *
226 * @return void
227 */
228 public function add_caps() {
229 $admin = get_role( 'administrator' );
230 if ( ! $admin ) {
231 return;
232 }
233
234 foreach ( $this->get_capabilities() as $capability => $custom_capability ) {
235 $admin->add_cap( $custom_capability );
236 }
237 }
238
239 /**
240 * Gets the mapping of capabilities for the custom post type.
241 *
242 * @return array An associative array of capability key => custom capability value.
243 */
244 public function get_capabilities() {
245 return [
246 'edit_post' => 'block_lab_edit_block',
247 'edit_posts' => 'block_lab_edit_blocks',
248 'edit_others_posts' => 'block_lab_edit_others_blocks',
249 'publish_posts' => 'block_lab_publish_blocks',
250 'read_post' => 'block_lab_read_block',
251 'read_private_posts' => 'block_lab_read_private_blocks',
252 'delete_post' => 'block_lab_delete_block',
253 ];
254 }
255
256 /**
257 * Enqueue scripts and styles used by the Block post type.
258 *
259 * @return void
260 */
261 public function enqueue_scripts() {
262 $post = get_post();
263 $screen = get_current_screen();
264
265 if ( ! is_object( $screen ) ) {
266 return;
267 }
268
269 // Enqueue scripts and styles on the edit screen of the Block post type.
270 if ( $this->slug === $screen->post_type && 'post' === $screen->base ) {
271 wp_enqueue_style(
272 'block-post',
273 $this->plugin->get_url( 'css/admin.block-post.css' ),
274 [],
275 $this->plugin->get_version()
276 );
277
278 if ( ! in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) ) {
279 wp_add_inline_style( 'block-post', '#delete-action { display: none; }' );
280 }
281
282 wp_enqueue_script(
283 'block-post',
284 $this->plugin->get_url( 'js/admin.block-post.js' ),
285 [ 'jquery', 'jquery-ui-sortable', 'wp-util', 'wp-blocks' ],
286 $this->plugin->get_version(),
287 false
288 );
289
290 wp_localize_script(
291 'block-post',
292 'blockLab',
293 [
294 'fieldSettingsNonce' => wp_create_nonce( 'block_lab_field_settings_nonce' ),
295 'postTypes' => [
296 'all' => __( 'All', 'block-lab' ),
297 'none' => __( 'None', 'block-lab' ),
298 ],
299 'copySuccessMessage' => __( 'Copied to clipboard.', 'block-lab' ),
300 'copyFailMessage' => sprintf(
301 // translators: Placeholder is a shortcut key combination.
302 __( '%1$s to copy.', 'block-lab' ),
303 strpos( getenv( 'HTTP_USER_AGENT' ), 'Mac' ) ? 'Cmd+C' : 'Ctrl+C'
304 ),
305 ]
306 );
307 }
308
309 if ( $this->slug === $screen->post_type && 'edit' === $screen->base ) {
310 wp_enqueue_style(
311 'block-edit',
312 $this->plugin->get_url( 'css/admin.block-edit.css' ),
313 [],
314 $this->plugin->get_version()
315 );
316 }
317 }
318
319 /**
320 * Add meta boxes.
321 *
322 * @return void
323 */
324 public function add_meta_boxes() {
325 $post = get_post();
326
327 add_meta_box(
328 'block_properties',
329 __( 'Block Properties', 'block-lab' ),
330 [ $this, 'render_properties_meta_box' ],
331 $this->slug,
332 'side',
333 'default'
334 );
335
336 add_meta_box(
337 'block_fields',
338 __( 'Block Fields', 'block-lab' ),
339 [ $this, 'render_fields_meta_box' ],
340 $this->slug,
341 'normal',
342 'default'
343 );
344
345 if ( ! empty( $post->post_name ) ) {
346 $locations = block_lab()->get_template_locations( $post->post_name );
347 $template = block_lab()->locate_template( $locations, '', true );
348
349 if ( ! $template ) {
350 add_meta_box(
351 'block_template',
352 __( 'Template', 'block-lab' ),
353 [ $this, 'render_template_meta_box' ],
354 $this->slug,
355 'normal',
356 'high'
357 );
358 }
359 }
360 }
361
362 /**
363 * Removes unneeded meta boxes.
364 *
365 * @return void
366 */
367 public function remove_meta_boxes() {
368 $screen = get_current_screen();
369
370 if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
371 return;
372 }
373
374 remove_meta_box( 'slugdiv', $this->slug, 'normal' );
375 }
376
377 /**
378 * Adds a "Save Draft" button next to the "Publish" button.
379 *
380 * @return void
381 */
382 public function save_draft_button() {
383 $post = get_post();
384 $screen = get_current_screen();
385
386 if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
387 return;
388 }
389
390 if ( ! in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) ) {
391 ?>
392 <input type="submit" name="save" value="<?php esc_attr_e( 'Save Draft', 'block-lab' ); ?>" class="button" />
393 <?php
394 }
395 }
396
397 /**
398 * Render the Block Fields meta box.
399 *
400 * @return void
401 */
402 public function render_properties_meta_box() {
403 $post = get_post();
404 $block = new Block( $post->ID );
405 $icons = block_lab()->get_icons();
406
407 if ( ! $block->icon ) {
408 $block->icon = 'block_lab';
409 }
410 ?>
411 <p>
412 <label for="block-properties-slug">
413 <?php esc_html_e( 'Slug:', 'block-lab' ); ?>
414 </label>
415 <input
416 name="post_name"
417 type="text"
418 id="block-properties-slug"
419 value="<?php echo esc_attr( $post->post_name ); ?>" />
420 </p>
421 <p class="description">
422 <?php
423 esc_html_e(
424 'Used to determine the name of the template file.',
425 'block-lab'
426 );
427 ?>
428 </p>
429 <p>
430 <label for="block-properties-icon">
431 <?php esc_html_e( 'Icon:', 'block-lab' ); ?>
432 </label>
433 <input
434 name="block-properties-icon"
435 type="hidden"
436 id="block-properties-icon"
437 value="<?php echo esc_attr( $block->icon ); ?>" />
438 <span id="block-properties-icon-current">
439 <?php
440 if ( array_key_exists( $block->icon, $icons ) ) {
441 echo wp_kses( $icons[ $block->icon ], block_lab()->allowed_svg_tags() );
442 }
443 ?>
444 </span>
445 <a class="button block-properties-icon-button" id="block-properties-icon-choose" href="#block-properties-icon-choose">
446 <?php esc_attr_e( 'Choose', 'block-lab' ); ?>
447 </a>
448 <a class="button block-properties-icon-button" id="block-properties-icon-close" href="#">
449 <?php esc_attr_e( 'Close', 'block-lab' ); ?>
450 </a>
451 <span class="block-properties-icon-select" id="block-properties-icon-select">
452 <?php
453 foreach ( $icons as $icon => $svg ) {
454 $selected = $icon === $block->icon ? 'selected' : '';
455 printf(
456 '<span class="icon %1$s" data-value="%2$s">%3$s</span>',
457 esc_attr( $selected ),
458 esc_attr( $icon ),
459 wp_kses( $svg, block_lab()->allowed_svg_tags() )
460 );
461 }
462 ?>
463 </span>
464 </p>
465 <p>
466 <label for="block-properties-category">
467 <?php esc_html_e( 'Category:', 'block-lab' ); ?>
468 </label>
469 <select name="block-properties-category" id="block-properties-category" class="block-properties-category">
470 <?php
471 $categories = get_block_categories( $post );
472 foreach ( $categories as $category ) {
473 ?>
474 <option value="<?php echo esc_attr( $category['slug'] ); ?>" <?php selected( $category['slug'], $block->category['slug'] ); ?>>
475 <?php echo esc_html( $category['title'] ); ?>
476 </option>
477 <?php
478 }
479 ?>
480 <option disabled>────────</option>
481 <option value="__custom"><?php esc_html_e( 'Custom Category', 'block-lab' ); ?></option>
482 </select>
483 <span class="block-properties-category-custom">
484 <label for="block-properties-category-name">
485 <?php esc_html_e( 'New Category Name:', 'block-lab' ); ?>
486 </label>
487 <input
488 name="block-properties-category-name"
489 type="text"
490 id="block-properties-category-name"
491 value="" />
492 </span>
493 </p>
494 <p>
495 <label for="block-properties-keywords">
496 <?php esc_html_e( 'Keywords:', 'block-lab' ); ?>
497 </label>
498 <input
499 name="block-properties-keywords"
500 type="text"
501 id="block-properties-keywords"
502 value="<?php echo esc_attr( implode( ', ', $block->keywords ) ); ?>" />
503 </p>
504 <p class="description">
505 <?php
506 esc_html_e(
507 'A comma separated list of keywords, used when searching. Maximum of 3.',
508 'block-lab'
509 );
510 ?>
511 </p>
512 <?php
513 wp_nonce_field( 'block_lab_save_properties', 'block_lab_properties_nonce' );
514 }
515
516 /**
517 * Render the Block Fields meta box.
518 *
519 * @return void
520 */
521 public function render_fields_meta_box() {
522 $post = get_post();
523 $block = new Block( $post->ID );
524 do_action( 'block_lab_before_fields_list' );
525 ?>
526 <div class="block-fields-list">
527 <table class="widefat">
528 <thead>
529 <tr>
530 <th class="block-fields-sort"></th>
531 <th class="block-fields-label">
532 <?php esc_html_e( 'Field Label', 'block-lab' ); ?>
533 </th>
534 <th class="block-fields-name">
535 <?php esc_html_e( 'Field Name', 'block-lab' ); ?>
536 </th>
537 <th class="block-fields-control">
538 <?php esc_html_e( 'Field Type', 'block-lab' ); ?>
539 </th>
540 </tr>
541 </thead>
542 <tbody>
543 <tr>
544 <td colspan="4">
545 <div class="block-fields-rows">
546 <p class="block-no-fields">
547 <?php echo wp_kses_post( __( 'Click <strong>Add Field</strong> below to add your first field.', 'block-lab' ) ); ?>
548 </p>
549 <?php
550 if ( count( $block->fields ) > 0 ) {
551 foreach ( $block->fields as $field ) {
552 $this->render_fields_meta_box_row( $field, uniqid() );
553 }
554 }
555 ?>
556 </div>
557 </td>
558 </tr>
559 </tbody>
560 </table>
561 </div>
562 <div class="block-fields-actions-add-field">
563 <button type="button" aria-label="Add Field" class="block-fields-action" id="block-add-field">
564 <span class="dashicons dashicons-plus"></span>
565 <?php esc_attr_e( 'Add Field', 'block-lab' ); ?>
566 </button>
567 <script type="text/html" id="tmpl-field-repeater">
568 <?php
569 $args = [
570 'name' => 'new-field',
571 'label' => __( 'New Field', 'block-lab' ),
572 ];
573 $this->render_fields_meta_box_row( new Field( $args ) );
574 ?>
575 </script>
576 <script type="text/html" id="tmpl-sub-field-rows">
577 <?php $this->render_fields_sub_rows(); ?>
578 </script>
579 </div>
580 <?php
581 do_action( 'block_lab_after_fields_list' );
582 wp_nonce_field( 'block_lab_save_fields', 'block_lab_fields_nonce' );
583 }
584
585 /**
586 * Render a single Field as a row.
587 *
588 * @param Field $field The Field containing the options to render.
589 * @param mixed $uid A unique ID to used to unify the HTML name, for, and id attributes.
590 * @param mixed $parent_uid The parent's unique ID, if the field has a parent.
591 *
592 * @return void
593 */
594 public function render_fields_meta_box_row( $field, $uid = false, $parent_uid = false ) {
595 // Use a template placeholder if no UID provided.
596 if ( ! $uid ) {
597 $uid = '{{ data.uid }}';
598 }
599
600 $is_field_disabled = ( ! isset( $this->controls[ $field->control ] ) && in_array( $field->control, $this->pro_controls, true ) );
601 ?>
602 <div class="block-fields-row" data-uid="<?php echo esc_attr( $uid ); ?>">
603 <div class="block-fields-row-columns">
604 <div class="block-fields-sort">
605 <span class="block-fields-sort-handle"></span>
606 </div>
607 <div class="block-fields-label">
608 <a class="row-title" href="javascript:" id="block-fields-label_<?php echo esc_attr( $uid ); ?>">
609 <?php echo esc_html( $field->label ); ?>
610 </a>
611 <div class="block-fields-actions">
612 <a class="block-fields-actions-edit" href="javascript:">
613 <?php esc_html_e( 'Edit', 'block-lab' ); ?>
614 </a>
615 &nbsp;|&nbsp;
616 <a class="block-fields-actions-duplicate" href="javascript:">
617 <?php esc_html_e( 'Duplicate', 'block-lab' ); ?>
618 </a>
619 &nbsp;|&nbsp;
620 <a class="block-fields-actions-delete" href="javascript:">
621 <?php esc_html_e( 'Delete', 'block-lab' ); ?>
622 </a>
623 </div>
624 </div>
625 <div class="block-fields-name" id="block-fields-name_<?php echo esc_attr( $uid ); ?>">
626 <code id="block-fields-name-code_<?php echo esc_attr( $uid ); ?>"><?php echo esc_html( $field->name ); ?></code>
627 </div>
628 <div class="block-fields-control" id="block-fields-control_<?php echo esc_attr( $uid ); ?>">
629 <?php
630 if ( ! $is_field_disabled && isset( $this->controls[ $field->control ] ) ) :
631 echo esc_html( $this->controls[ $field->control ]->label );
632 else :
633 ?>
634 <span class="dashicons dashicons-warning"></span>
635 <span class="pro-required">
636 <?php
637 /* translators: %1$s is the field type, %2$s is the URL for the Pro license */
638 printf(
639 wp_kses_post( 'This <code>%1$s</code> field requires an active <a href="%2$s">pro license</a>.', 'block-lab' ),
640 esc_html( $field->control ),
641 esc_url(
642 add_query_arg(
643 [
644 'post_type' => 'block_lab',
645 'page' => 'block-lab-pro',
646 ],
647 admin_url( 'edit.php' )
648 )
649 )
650 );
651 ?>
652 </span>
653 <?php endif; ?>
654 </div>
655 </div>
656 <div class="block-fields-edit">
657 <table class="widefat">
658 <tr class="block-fields-edit-label">
659 <td class="spacer"></td>
660 <th scope="row">
661 <label for="block-fields-edit-label-input_<?php echo esc_attr( $uid ); ?>">
662 <?php esc_html_e( 'Field Label', 'block-lab' ); ?>
663 </label>
664 <p class="description">
665 <?php
666 esc_html_e(
667 'A label describing your block\'s custom field.',
668 'block-lab'
669 );
670 ?>
671 </p>
672 </th>
673 <td>
674 <input
675 name="block-fields-label[<?php echo esc_attr( $uid ); ?>]"
676 type="text"
677 id="block-fields-edit-label-input_<?php echo esc_attr( $uid ); ?>"
678 class="regular-text"
679 value="<?php echo esc_attr( $field->label ); ?>"
680 data-sync="block-fields-label_<?php echo esc_attr( $uid ); ?>"
681 <?php echo $is_field_disabled ? 'readonly="readonly"' : ''; ?>
682 />
683 <?php if ( $is_field_disabled ) : ?>
684 <input
685 name="block-is-disabled-pro-field[<?php echo esc_attr( $uid ); ?>]"
686 type="hidden"
687 value="true"
688 />
689 <?php endif; ?>
690 </td>
691 </tr>
692 <tr class="block-fields-edit-name">
693 <td class="spacer"></td>
694 <th scope="row">
695 <label for="block-fields-edit-name-input_<?php echo esc_attr( $uid ); ?>">
696 <?php esc_html_e( 'Field Name', 'block-lab' ); ?>
697 </label>
698 <p class="description">
699 <?php esc_html_e( 'Single word, no spaces.', 'block-lab' ); ?>
700 </p>
701 </th>
702 <td>
703 <input
704 name="block-fields-name[<?php echo esc_attr( $uid ); ?>]"
705 type="text"
706 id="block-fields-edit-name-input_<?php echo esc_attr( $uid ); ?>"
707 class="regular-text"
708 value="<?php echo esc_attr( $field->name ); ?>"
709 data-sync="block-fields-name-code_<?php echo esc_attr( $uid ); ?>"
710 <?php echo $is_field_disabled ? 'readonly="readonly"' : ''; ?>
711 />
712 </td>
713 </tr>
714 <tr class="block-fields-edit-control">
715 <td class="spacer"></td>
716 <th scope="row">
717 <label for="block-fields-edit-control-input_<?php echo esc_attr( $uid ); ?>">
718 <?php esc_html_e( 'Field Type', 'block-lab' ); ?>
719 </label>
720 </th>
721 <td>
722 <select
723 name="block-fields-control[<?php echo esc_attr( $uid ); ?>]"
724 id="block-fields-edit-control-input_<?php echo esc_attr( $uid ); ?>"
725 data-sync="block-fields-control_<?php echo esc_attr( $uid ); ?>"
726 <?php disabled( $is_field_disabled ); ?> >
727 <?php
728 $controls_for_select = $this->controls;
729
730 // If this field is disabled, it was probably added when there was a valid pro license, so still display it.
731 if ( $is_field_disabled && in_array( $field->control, $this->pro_controls, true ) ) {
732 $controls_for_select[ $field->control ] = $this->get_control( $field->control );
733 }
734
735 // Don't allow nesting repeaters inside repeaters.
736 if ( ! empty( $field->settings['parent'] ) ) {
737 unset( $controls_for_select['repeater'] );
738 }
739
740 foreach ( $controls_for_select as $control_for_select ) :
741 ?>
742 <option
743 value="<?php echo esc_attr( $control_for_select->name ); ?>"
744 <?php selected( $field->control, $control_for_select->name ); ?>>
745 <?php echo esc_html( $control_for_select->label ); ?>
746 </option>
747 <?php endforeach; ?>
748 </select>
749 </td>
750 </tr>
751 <?php $this->render_field_settings( $field, $uid ); ?>
752 <tr class="block-fields-edit-actions-close">
753 <td class="spacer"></td>
754 <th scope="row">
755 </th>
756 <td>
757 <a class="button" title="<?php esc_attr_e( 'Close Field', 'block-lab' ); ?>" href="javascript:">
758 <?php esc_html_e( 'Close Field', 'block-lab' ); ?>
759 </a>
760 </td>
761 </tr>
762 </table>
763 </div>
764 <?php
765 if ( 'repeater' === $field->control ) {
766 if ( ! isset( $field->settings['sub_fields'] ) ) {
767 $field->settings['sub_fields'] = [];
768 }
769 $this->render_fields_sub_rows( $field->settings['sub_fields'], $uid );
770 }
771 if ( $parent_uid ) {
772 ?>
773 <input
774 type="hidden"
775 name="block-fields-parent[<?php echo esc_attr( $uid ); ?>]"
776 value="<?php echo esc_attr( $parent_uid ); ?>"
777 />
778 <?php
779 }
780 ?>
781 </div>
782 <?php
783 }
784
785 /**
786 * Render the actions row when adding a Repeater field.
787 *
788 * @param Field[] $fields The sub fields to render.
789 * @param mixed $parent_uid The unique ID of the field's parent.
790 *
791 * @return void
792 */
793 public function render_fields_sub_rows( $fields = [], $parent_uid = false ) {
794 ?>
795 <div class="block-fields-sub-rows">
796 <?php
797 if ( ! empty( $fields ) ) {
798 foreach ( $fields as $field ) {
799 $this->render_fields_meta_box_row( $field, uniqid(), $parent_uid );
800 }
801 }
802 ?>
803 </div>
804 <div class="block-fields-sub-rows-actions">
805 <p class="repeater-no-fields <?php echo esc_attr( empty( $fields ) ? '' : 'hidden' ); ?>">
806 <button type="button" aria-label="Add Sub-Field" id="block-add-sub-field">
807 <span class="dashicons dashicons-plus"></span>
808 <?php esc_attr_e( 'Add your first Sub-Field', 'block-lab' ); ?>
809 </button>
810 </p>
811 <p class="repeater-has-fields <?php echo esc_attr( empty( $fields ) ? 'hidden' : '' ); ?>">
812 <button type="button" aria-label="Add Sub-Field" id="block-add-sub-field">
813 <span class="dashicons dashicons-plus"></span>
814 <?php esc_attr_e( 'Add Sub-Field', 'block-lab' ); ?>
815 </button>
816 </p>
817 </div>
818 <?php
819 }
820
821 /**
822 * Render the Block Template meta box.
823 *
824 * @return void
825 */
826 public function render_template_meta_box() {
827 $post = get_post();
828 ?>
829 <div class="template-notice">
830 <h3>✔️ <?php esc_html_e( 'Next step: Create a block template.', 'block-lab' ); ?></h3>
831 <p>
832 <?php esc_html_e( 'To display this block, Block Lab will look for this template file in your theme:', 'block-lab' ); ?>
833 </p>
834 <?php
835 // Formatting to make the template paths easier to understand.
836 $template = get_stylesheet_directory() . '/blocks/block-' . $post->post_name . '.php';
837 $template_short = str_replace( WP_CONTENT_DIR, basename( WP_CONTENT_DIR ), $template );
838 $template_parts = explode( '/', $template_short );
839 $filename = array_pop( $template_parts );
840 $template_breaks = '/' . trailingslashit( implode( '/<wbr>', $template_parts ) );
841 ?>
842 <p class="template-location">
843 <span class="path"><?php echo wp_kses( $template_breaks, [ 'wbr' => [] ] ); ?></span>
844 <a class="filename" data-tooltip="<?php esc_attr_e( 'Click to copy.', 'block-lab' ); ?>" href="#"><?php echo esc_html( $filename ); ?></a>
845 <span class="click-to-copy">
846 <input type="text" readonly="readonly" value="<?php echo esc_html( $filename ); ?>" />
847 </span>
848 </p>
849 <p>
850 <strong><?php esc_html_e( 'Learn more:', 'block-lab' ); ?></strong>
851 <?php
852 echo wp_kses_post(
853 sprintf(
854 '<a href="%1$s" target="_blank">%2$s</a> | ',
855 'https://getblocklab.com/docs/get-started/add-a-block-lab-block-to-your-website-content/',
856 esc_html__( 'Block Templates', 'block-lab' )
857 )
858 );
859 echo wp_kses_post(
860 sprintf(
861 '<a href="%1$s" target="_blank">%2$s</a>',
862 'https://getblocklab.com/docs/functions/',
863 esc_html__( 'Template Functions', 'block-lab' )
864 )
865 );
866 ?>
867 </p>
868 </div>
869 <?php
870 }
871
872 /**
873 * Display the template location below the title.
874 */
875 public function template_location() {
876 $post = get_post();
877 $screen = get_current_screen();
878
879 if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
880 return;
881 }
882
883 if ( ! isset( $post->post_name ) || empty( $post->post_name ) ) {
884 return;
885 }
886
887 $locations = block_lab()->get_template_locations( $post->post_name, 'block' );
888 $template = block_lab()->locate_template( $locations, '', true );
889
890 if ( ! $template ) {
891 return;
892 }
893
894 // Formatting to make the template paths easier to understand.
895 $template_short = str_replace( WP_CONTENT_DIR, basename( WP_CONTENT_DIR ), $template );
896 $template_parts = explode( '/', $template_short );
897 $filename = array_pop( $template_parts );
898 $template_breaks = '/' . trailingslashit( implode( '/', $template_parts ) );
899
900 if ( $template ) {
901 ?>
902 <div id="edit-slug-box">
903 <strong><?php esc_html_e( 'Template:', 'block-lab' ); ?></strong>
904 <?php echo esc_html( $template_breaks ); ?><strong><?php echo esc_html( $filename ); ?></strong>
905 </div>
906 <?php
907 }
908 }
909
910 /**
911 * Render the settings for a given field.
912 *
913 * @param Field $field The Field containing the options to render.
914 * @param string $uid A unique ID to used to unify the HTML name, for, and id attributes.
915 *
916 * @return void
917 */
918 public function render_field_settings( $field, $uid ) {
919 if ( isset( $this->controls[ $field->control ] ) ) {
920 $this->controls[ $field->control ]->render_settings( $field, $uid );
921 }
922 }
923
924 /**
925 * Ajax response for fetching field settings.
926 *
927 * @return void
928 */
929 public function ajax_field_settings() {
930 wp_verify_nonce( 'block_lab_field_options_nonce' );
931
932 if ( ! isset( $_POST['control'] ) || ! isset( $_POST['uid'] ) ) {
933 wp_send_json_error();
934 return;
935 }
936
937 $control = sanitize_key( $_POST['control'] );
938 $uid = sanitize_key( $_POST['uid'] );
939
940 ob_start();
941 $field = new Field( [ 'control' => $control ] );
942
943 if ( isset( $_POST['parent'] ) ) {
944 $field->settings['parent'] = sanitize_key( $_POST['parent'] );
945 }
946
947 $this->render_field_settings( $field, $uid );
948 $data['html'] = ob_get_clean();
949
950 if ( '' === $data['html'] ) {
951 wp_send_json_error();
952 }
953
954 wp_send_json_success( $data );
955 }
956
957 /**
958 * Save block meta boxes as a json blob in post content.
959 *
960 * @param array $data An array of slashed post data.
961 *
962 * @return array
963 */
964 public function save_block( $data ) {
965 if ( ! isset( $_POST['post_ID'] ) ) {
966 return $data;
967 }
968
969 $post_id = sanitize_key( $_POST['post_ID'] );
970
971 // Exits script depending on save status.
972 if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
973 return $data;
974 }
975
976 // Exits script if not the right post type.
977 if ( $this->slug !== $data['post_type'] ) {
978 return $data;
979 }
980
981 check_admin_referer( 'block_lab_save_fields', 'block_lab_fields_nonce' );
982 check_admin_referer( 'block_lab_save_properties', 'block_lab_properties_nonce' );
983
984 // Strip encoded special characters, like 🖖 (%f0%9f%96%96).
985 $data['post_name'] = preg_replace( '/%[a-f|0-9][a-f|0-9]/', '', $data['post_name'] );
986
987 // sanitize_title() allows underscores, but register_block_type doesn't.
988 $data['post_name'] = str_replace( '_', '-', $data['post_name'] );
989
990 // If only special characters were used, it's possible the post_name is now empty.
991 if ( '' === $data['post_name'] ) {
992 $data['post_name'] = $post_id;
993 }
994
995 // register_block_type doesn't allow slugs starting with a number.
996 if ( is_numeric( $data['post_name'][0] ) ) {
997 $data['post_name'] = 'block-' . $data['post_name'];
998 }
999
1000 // Make sure the block slug is still unique.
1001 $data['post_name'] = wp_unique_post_slug(
1002 $data['post_name'],
1003 $post_id,
1004 $data['post_status'],
1005 $data['post_type'],
1006 $data['post_parent']
1007 );
1008
1009 $block = new Block();
1010
1011 // Block name.
1012 $block->name = sanitize_key( $data['post_name'] );
1013 if ( '' === $block->name ) {
1014 $block->name = $post_id;
1015 }
1016
1017 // Block title.
1018 $block->title = sanitize_text_field(
1019 wp_unslash( $data['post_title'] )
1020 );
1021 if ( '' === $block->title ) {
1022 $block->title = $post_id;
1023 }
1024
1025 // Block excluded post type.
1026 if ( isset( $_POST['block-excluded-post-types'] ) ) {
1027 $excluded = sanitize_text_field(
1028 wp_unslash( $_POST['block-excluded-post-types'] )
1029 );
1030 if ( ! empty( $excluded ) ) {
1031 $block->excluded = explode( ',', $excluded );
1032 }
1033 }
1034
1035 // Block icon.
1036 if ( isset( $_POST['block-properties-icon'] ) ) {
1037 $block->icon = sanitize_key( $_POST['block-properties-icon'] );
1038 }
1039
1040 // Block category.
1041 if ( isset( $_POST['block-properties-category'] ) ) {
1042 $category_slug = sanitize_key( $_POST['block-properties-category'] );
1043 $categories = get_block_categories( get_post() );
1044
1045 if ( '__custom' === $category_slug && isset( $_POST['block-properties-category-name'] ) ) {
1046 $category = [
1047 'slug' => sanitize_key( $_POST['block-properties-category-name'] ),
1048 'title' => sanitize_text_field(
1049 wp_unslash( $_POST['block-properties-category-name'] )
1050 ),
1051 'icon' => null,
1052 ];
1053 } else {
1054 $category_slugs = wp_list_pluck( $categories, 'slug' );
1055 $category_key = array_search( $category_slug, $category_slugs, true );
1056 $category = $categories[ $category_key ];
1057 }
1058
1059 if ( ! $category ) {
1060 $category = isset( $categories[0] ) ? $categories[0] : '';
1061 }
1062
1063 $block->category = $category;
1064 }
1065
1066 // Block keywords.
1067 if ( isset( $_POST['block-properties-keywords'] ) ) {
1068 $keywords = sanitize_text_field(
1069 wp_unslash( $_POST['block-properties-keywords'] )
1070 );
1071 $keywords = explode( ',', $keywords );
1072 $keywords = array_map( 'trim', $keywords );
1073 $keywords = array_slice( $keywords, 0, 3 );
1074
1075 $block->keywords = $keywords;
1076 }
1077
1078 // Block fields.
1079 if ( isset( $_POST['block-fields-name'] ) && is_array( $_POST['block-fields-name'] ) ) {
1080 // We loop through this array and sanitize its content according to the content type.
1081 $fields = wp_unslash( $_POST['block-fields-name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1082 foreach ( $fields as $key => $name ) {
1083 // Field name and order.
1084 $field_config = [ 'name' => sanitize_key( $name ) ];
1085
1086 // Field label.
1087 if ( isset( $_POST['block-fields-label'][ $key ] ) ) {
1088 $field_config['label'] = sanitize_text_field(
1089 wp_unslash( $_POST['block-fields-label'][ $key ] )
1090 );
1091 }
1092
1093 // Field control.
1094 if ( isset( $_POST['block-fields-control'][ $key ] ) ) {
1095 $field_config['control'] = sanitize_text_field(
1096 wp_unslash( $_POST['block-fields-control'][ $key ] )
1097 );
1098 }
1099
1100 // Field type.
1101 if ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) {
1102 $field_config['type'] = $this->controls[ $field_config['control'] ]->type;
1103 }
1104
1105 /*
1106 * Field settings.
1107 * If the field is a pro field that's no longer available, re-save the previous value of that field.
1108 * This allows saving other new fields, while retaining the previous pro field value in case the user reactivates the license.
1109 */
1110 if ( ! empty( $_POST['block-is-disabled-pro-field'][ $key ] ) ) {
1111 $previous_block = new Block( $post_id );
1112 foreach ( $previous_block->fields as $previous_field ) {
1113 if ( $name === $previous_field->name ) {
1114 $field = $previous_field;
1115 break;
1116 }
1117 }
1118 } elseif ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) {
1119 $control = $this->controls[ $field_config['control'] ];
1120 foreach ( $control->settings as $setting ) {
1121 $value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
1122
1123 if ( isset( $_POST['block-fields-settings'][ $key ][ $setting->name ] ) ) {
1124 $value = $_POST['block-fields-settings'][ $key ][ $setting->name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1125 $value = wp_unslash( $value );
1126 }
1127
1128 // Sanitize the field options according to their type.
1129 if ( is_callable( $setting->sanitize ) ) {
1130 $value = call_user_func( $setting->sanitize, $value );
1131 }
1132
1133 // Validate the field options according to their type.
1134 if ( is_callable( $setting->validate ) ) {
1135 $value = call_user_func(
1136 $setting->validate,
1137 $value,
1138 $field_config['settings']
1139 );
1140 }
1141
1142 $field_config['settings'][ $setting->name ] = $value;
1143
1144 $field = new Field( $field_config );
1145 }
1146 } else {
1147 $field = new Field( $field_config );
1148 }
1149
1150 /*
1151 * Sub-Fields
1152 * If there's a "block-fields-parent" input, include the current field in a "sub-fields" field setting
1153 * for the specified parent.
1154 */
1155 if ( ! empty( $_POST['block-fields-parent'][ $key ] ) ) {
1156 $parent_uid = sanitize_key( $_POST['block-fields-parent'][ $key ] );
1157
1158 // The parent's name should have been submitted.
1159 if ( ! isset( $fields[ $parent_uid ] ) ) {
1160 continue;
1161 }
1162
1163 $parent = $fields[ $parent_uid ];
1164
1165 // The parent field should be set by now. We expect it to always precede the child field.
1166 if ( ! isset( $block->fields[ $parent ] ) ) {
1167 continue;
1168 }
1169
1170 if ( ! isset( $block->fields[ $parent ]->settings['sub_fields'] ) ) {
1171 $block->fields[ $parent ]->settings['sub_fields'] = [];
1172 }
1173
1174 $field->settings['parent'] = $parent;
1175 $field->order = count(
1176 $block->fields[ $parent ]->settings['sub_fields']
1177 );
1178
1179 $block->fields[ $parent ]->settings['sub_fields'][ $name ] = $field;
1180 } else {
1181 $field->order = count( $block->fields );
1182
1183 $block->fields[ $name ] = $field;
1184 }
1185 }
1186 }
1187
1188 $data['post_content'] = wp_slash( $block->to_json() );
1189 return $data;
1190 }
1191
1192 /**
1193 * Change the default "Enter Title Here" placeholder on the edit post screen.
1194 *
1195 * @param string $title Placeholder text. Default 'Enter title here'.
1196 *
1197 * @return string
1198 */
1199 public function post_title_placeholder( $title ) {
1200 $screen = get_current_screen();
1201
1202 // Enqueue scripts and styles on the edit screen of the Block post type.
1203 if ( is_object( $screen ) && $this->slug === $screen->post_type ) {
1204 $title = __( 'Enter block name here', 'block-lab' );
1205 }
1206
1207 return $title;
1208 }
1209
1210 /**
1211 * Displays an option for editing the post type that this block appears on.
1212 */
1213 public function post_type_condition() {
1214 if ( ! block_lab()->is_pro() ) {
1215 return;
1216 }
1217
1218 $screen = get_current_screen();
1219
1220 // Enqueue scripts and styles on the edit screen of the Block post type.
1221 if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
1222 return;
1223 }
1224
1225 $post_types = get_post_types(
1226 [
1227 'show_in_rest' => true,
1228 'show_in_menu' => true,
1229 ],
1230 'objects'
1231 );
1232
1233 $post_types = array_filter(
1234 $post_types,
1235 function( $post_type ) {
1236 return post_type_supports( $post_type->name, 'editor' );
1237 }
1238 );
1239
1240 $block = new Block( get_the_ID() );
1241 ?>
1242 <div class="block-lab-pub-section hide-if-no-js">
1243 <?php esc_html_e( 'Post Types:', 'block-lab' ); ?> <span class="post-types-display"></span>
1244 <a href="#post-types-select" class="edit-post-types" role="button">
1245 <span aria-hidden="true"><?php esc_html_e( 'Edit', 'block-lab' ); ?></span>
1246 </a>
1247 <input type="hidden" value="<?php echo esc_attr( implode( ',', $block->excluded ) ); ?>" name="block-excluded-post-types" id="block-excluded-post-types" />
1248 <div class="post-types-select">
1249 <div class="post-types-select-items">
1250 <?php
1251 foreach ( $post_types as $post_type ) {
1252 ?>
1253 <input type="checkbox" id="block-post-type-<?php echo esc_attr( $post_type->name ); ?>" value="<?php echo esc_attr( $post_type->name ); ?>">
1254 <label for="block-post-type-<?php echo esc_attr( $post_type->name ); ?>"><?php echo esc_html( $post_type->label ); ?></label>
1255 <br />
1256 <?php
1257 }
1258 ?>
1259 </div>
1260 <a href="#post-types" class="save-post-types button"><?php esc_html_e( 'OK', 'block-lab' ); ?></a>
1261 <a href="#post-types" class="button-cancel"><?php esc_html_e( 'Cancel', 'block-lab' ); ?></a>
1262 </div>
1263 </div>
1264 <?php
1265 }
1266
1267 /**
1268 * Change the columns in the Custom Blocks list table
1269 *
1270 * @param array $columns An array of column name ⇒ label. The name is passed to functions to identify the column.
1271 *
1272 * @return array
1273 */
1274 public function list_table_columns( $columns ) {
1275 $new_columns = [
1276 'cb' => $columns['cb'],
1277 'title' => $columns['title'],
1278 'icon' => __( 'Icon', 'block-lab' ),
1279 'template' => __( 'Template', 'block-lab' ),
1280 'category' => __( 'Category', 'block-lab' ),
1281 'keywords' => __( 'Keywords', 'block-lab' ),
1282 ];
1283 return $new_columns;
1284 }
1285
1286 /**
1287 * Output custom column data into the table
1288 *
1289 * @param string $column The name of the column to display.
1290 * @param int $post_id The ID of the current post.
1291 *
1292 * @return void
1293 */
1294 public function list_table_content( $column, $post_id ) {
1295 if ( 'icon' === $column ) {
1296 $block = new Block( $post_id );
1297 $icons = block_lab()->get_icons();
1298
1299 if ( isset( $icons[ $block->icon ] ) ) {
1300 printf(
1301 '<span class="icon %1$s">%2$s</span>',
1302 esc_attr( $block->icon ),
1303 wp_kses( $icons[ $block->icon ], block_lab()->allowed_svg_tags() )
1304 );
1305 }
1306 }
1307 if ( 'template' === $column ) {
1308 $block = new Block( $post_id );
1309 $locations = block_lab()->get_template_locations( $block->name, 'block' );
1310 $template = block_lab()->locate_template( $locations, '', true );
1311
1312 if ( ! $template ) {
1313 esc_html_e( 'No template found.', 'block-lab' );
1314 } else {
1315 // Formatting to make the template path easier to understand.
1316 $template_short = str_replace( WP_CONTENT_DIR . '/themes/', '', $template );
1317 $template_parts = explode( '/', $template_short );
1318 $template_breaks = implode( '/', $template_parts );
1319 echo wp_kses(
1320 '<code>' . $template_breaks . '</code>',
1321 [
1322 'code' => [],
1323 'wbr' => [],
1324 ]
1325 );
1326 }
1327 }
1328 if ( 'keywords' === $column ) {
1329 $block = new Block( $post_id );
1330 echo esc_html( implode( ', ', $block->keywords ) );
1331 }
1332 if ( 'category' === $column ) {
1333 $block = new Block( $post_id );
1334 echo esc_html( $block->category['title'] );
1335 }
1336 }
1337
1338 /**
1339 * Hide the Quick Edit row action.
1340 *
1341 * @param array $actions An array of row action links.
1342 *
1343 * @return array
1344 */
1345 public function page_row_actions( $actions = [] ) {
1346 $post = get_post();
1347
1348 // Abort if the post type is incorrect.
1349 if ( $this->slug !== $post->post_type ) {
1350 return $actions;
1351 }
1352
1353 // Remove the Quick Edit link.
1354 if ( isset( $actions['inline hide-if-no-js'] ) ) {
1355 unset( $actions['inline hide-if-no-js'] );
1356 }
1357
1358 // Add the Export link.
1359 if ( block_lab()->is_pro() ) {
1360 $export = [
1361 'export' => sprintf(
1362 '<a href="%1$s" aria-label="%2$s">%3$s</a>',
1363 add_query_arg( [ 'export' => $post->ID ] ),
1364 sprintf(
1365 // translators: Placeholder is a post title.
1366 __( 'Export %1$s', 'block-lab' ),
1367 get_the_title( $post->ID )
1368 ),
1369 __( 'Export', 'block-lab' )
1370 ),
1371 ];
1372
1373 $actions = array_merge(
1374 array_slice( $actions, 0, 1 ),
1375 $export,
1376 array_slice( $actions, 1 )
1377 );
1378 }
1379
1380 // Return the set of links without Quick Edit.
1381 return $actions;
1382 }
1383
1384 /**
1385 * Remove Edit from the Bulk Actions menu
1386 *
1387 * @param array $actions An array of bulk actions.
1388 *
1389 * @return array
1390 */
1391 public function bulk_actions( $actions ) {
1392 unset( $actions['edit'] );
1393
1394 if ( block_lab()->is_pro() ) {
1395 $actions['export'] = __( 'Export', 'block-lab' );
1396 }
1397
1398 return $actions;
1399 }
1400
1401 /**
1402 * Handle the Export of a single block.
1403 */
1404 public function row_export() {
1405 if ( ! block_lab()->is_pro() ) {
1406 return;
1407 }
1408
1409 $post_id = filter_input( INPUT_GET, 'export', FILTER_SANITIZE_NUMBER_INT );
1410
1411 // Check if the export has been requested, and the user has permission.
1412 if ( $post_id <= 0 || ! current_user_can( 'block_lab_read_block', $post_id ) ) {
1413 return;
1414 }
1415
1416 $this->export( [ $post_id ] );
1417 }
1418
1419 /**
1420 * Handle Exporting blocks via Bulk Actions
1421 *
1422 * @param string $redirect Location to redirect to after the bulk action is completed.
1423 * @param string $action The action to handle.
1424 * @param array $post_ids The IDs to handle.
1425 *
1426 * @return string
1427 */
1428 public function bulk_export( $redirect, $action, $post_ids ) {
1429 if ( ! block_lab()->is_pro() ) {
1430 return $redirect;
1431 }
1432
1433 if ( 'export' !== $action ) {
1434 return $redirect;
1435 }
1436
1437 $this->export( $post_ids );
1438
1439 $redirect = add_query_arg( 'bulk_export', count( $post_ids ), $redirect );
1440 return $redirect;
1441 }
1442
1443 /**
1444 * Export Blocks
1445 *
1446 * @param int[] $post_ids The post IDs to export.
1447 */
1448 private function export( $post_ids ) {
1449 $blocks = [];
1450
1451 foreach ( $post_ids as $post_id ) {
1452 $post = get_post( $post_id );
1453
1454 if ( ! $post ) {
1455 break;
1456 }
1457
1458 // Check that the post content is valid JSON.
1459 $block = json_decode( $post->post_content, true );
1460
1461 if ( JSON_ERROR_NONE !== json_last_error() ) {
1462 break;
1463 }
1464
1465 $blocks = array_merge( $blocks, $block );
1466 }
1467
1468 // If only one block is being exported, use the block's slug as the filename.
1469 $filename = 'blocks.json';
1470 if ( 1 === count( $post_ids ) ) {
1471 $post = get_post( $post_ids[0] );
1472 $filename = $post->post_name . '.json';
1473 }
1474
1475 // Output the JSON file.
1476 header( 'Content-disposition: attachment; filename=' . $filename );
1477 header( 'Content-type:application/json;charset=utf-8' );
1478 echo wp_json_encode( $blocks ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1479 die();
1480 }
1481 }
1 <?php
2 /**
3 * Block Lab settings form for the License tab.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 ?>
11 <form method="post" action="options.php">
12 <?php
13 settings_fields( 'block-lab-license-key' );
14 do_settings_sections( 'block-lab-license-key' );
15 ?>
16 <table class="form-table">
17 <tr valign="top">
18 <th scope="row">
19 <label><?php esc_html_e( 'License', 'block-lab' ); ?></label>
20 </th>
21 <td>
22 <?php
23 if ( block_lab()->is_pro() ) {
24 $license = block_lab()->admin->license->get_license();
25
26 $limit = __( 'unlimited', 'block-lab' );
27 if ( isset( $license['license_limit'] ) && intval( $license['license_limit'] ) > 0 ) {
28 $limit = $license['license_limit'];
29 }
30
31 $count = '0';
32 if ( isset( $license['site_count'] ) ) {
33 $count = $license['site_count'];
34 }
35
36 $expiry = gmdate( get_option( 'date_format' ) );
37 if ( isset( $license['expires'] ) ) {
38 $expiry = gmdate( get_option( 'date_format' ), strtotime( $license['expires'] ) );
39 }
40
41 echo wp_kses_post(
42 sprintf(
43 '<p>%1$s %2$s</p>',
44 sprintf(
45 // translators: A number, wrapped in <strong> tags.
46 __( 'Your license includes %1$s site installs.', 'block-lab' ),
47 '<strong>' . $limit . '</strong>'
48 ),
49 sprintf(
50 // translators: A number, wrapped in <strong> tags.
51 __( '%1$s of them are in use.', 'block-lab' ),
52 '<strong>' . $count . '</strong>'
53 )
54 )
55 );
56
57 echo wp_kses_post(
58 sprintf(
59 '<p>%1$s %2$s</p>',
60 sprintf(
61 // translators: A date.
62 __( 'Your license expires on %1$s.', 'block-lab' ),
63 '<strong>' . $expiry . '</strong>'
64 ),
65 sprintf(
66 // translators: An opening and closing anchor tag.
67 __( '%1$sManage Licenses%2$s', 'block-lab' ),
68 '<a href="https://getblocklab.com/checkout/purchase-history/" target="_blank">',
69 '</a>'
70 )
71 )
72 );
73 } else {
74 echo wp_kses_post(
75 sprintf(
76 '<p>%1$s</p>',
77 __( 'No license was found for this installation.', 'block-lab' )
78 )
79 );
80 }
81 ?>
82 </td>
83 </tr>
84 <tr valign="top">
85 <th scope="row">
86 <label for="block_lab_license_key"><?php esc_html_e( 'License key', 'block-lab' ); ?></label>
87 </th>
88 <td>
89 <input type="password" name="block_lab_license_key" id="block_lab_license_key" class="regular-text" value="<?php echo esc_attr( get_option( 'block_lab_license_key' ) ); ?>" />
90 </td>
91 </tr>
92 </table>
93 <?php submit_button(); ?>
94 </form>
1 <?php
2 /**
3 * Block Lab Pro upgrade page.
4 *
5 * @package Block_Lab
6 * @copyright Copyright(c) 2020, Block Lab
7 * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
8 */
9
10 ?>
11 <section class="container">
12 <div class="dashboard_welcome tile">
13 <div class="tile_body">
14 <div>
15 <h1>Block Lab <span class="pro-pill">Pro</span></h1>
16 <p class="description"><?php esc_html_e( '…is no longer available. 😢', 'block-lab' ); ?></p>
17 </div>
18 </div>
19 </div>
20 <!-- Dashboard Tile -->
21 <div class="tile tile_3">
22 <div class="tile_body">
23 <h4><?php esc_html_e( '★★ Loving Block Lab? ★★', 'block-lab' ); ?></h4>
24 <p><?php esc_html_e( 'If Block Lab has helped you build amazing custom blocks for your site, leave us a review on WordPress.org.', 'block-lab' ); ?></p>
25 <a class="button" target="_blank" href="https://wordpress.org/plugins/block-lab/#reviews"><?php esc_html_e( '★ Leave Review ★', 'block-lab' ); ?></a>
26 </div>
27 </div>
28 <!-- Dashboard Tile -->
29 <div class="tile tile_3">
30 <div class="tile_body">
31 <h4><?php esc_html_e( 'Get more out of Block Lab', 'block-lab' ); ?></h4>
32 <p><?php esc_html_e( 'Subscribe to our newsletter for news, updates, and tutorials on working with Gutenberg.', 'block-lab' ); ?></p>
33 </div>
34 <div class="tile_footer tile_footer_email">
35 <div id="mc_embed_signup">
36 <form action="https://getblocklab.us19.list-manage.com/subscribe/post?u=f8e0c6b0ab32fc57ded52ab4a&amp;id=f05b221414" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
37 <div id="mc_embed_signup_scroll">
38 <div class="mc-field-group">
39 <label class="input_label" for="mce-EMAIL">Email Address </label>
40 <input class="input" type="email" value="" placeholder="Email Address" name="EMAIL" id="mce-EMAIL" />
41 </div>
42 <div id="mce-responses" class="clear">
43 <div class="response" id="mce-error-response" style="display:none"></div>
44 <div class="response" id="mce-success-response" style="display:none"></div>
45 </div>
46 <div class="clear">
47 <input class="button" type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" />
48 </div>
49 </div>
50 </form>
51 </div>
52
53 <!--End mc_embed_signup-->
54 </div>
55 </div>
56 </section>
1 === Block Lab ===
2
3 Contributors: lukecarbis, ryankienstra, Stino11, rheinardkorf
4 Tags: gutenberg, blocks, block editor, fields, template
5 Requires at least: 5.0
6 Tested up to: 5.5
7 Requires PHP: 5.6
8 Stable tag: 1.6.0
9 License: GPLv2 or later
10 License URI: http://www.gnu.org/licenses/gpl
11
12 The easy way to build custom blocks for Gutenberg.
13
14 == Description ==
15
16 = IMPORTANT! =
17 The Block Lab team has moved its custom block efforts over to [Genesis Custom Blocks](https://wordpress.org/plugins/genesis-custom-blocks/). To take advantage of all the great things about Block Lab as well as gain access to all new features as they are released, we recommend that you install Genesis Custom Blocks.
18
19 If you’re an existing Block Lab user and would like to learn more about what this means for you, including how to easily and automatically migrate to the new plugin, you can find more details [here](https://getblocklab.com/welcome-to-genesis-custom-blocks).
20
21 ----
22
23 Gutenberg, the new WordPress editor, opens up a whole new world for the way we build pages, posts, and websites with WordPress. Block Lab makes it easy to harness Gutenberg and build custom blocks the way you want them to be built. Whether you want to implement a custom design, deliver unique functionality, or even remove your dependence on other plugins, Block Lab equips you with the tools you need to hit “Publish” sooner.
24
25 == Features ==
26
27 = A Familiar Experience =
28 Work within the WordPress admin with an interface you already know.
29
30 = Block Fields =
31 Add from a growing list of available fields to your custom blocks.
32
33 = Simple Templating =
34 Let the plugin do the heavy lifting so you can use familiar WordPress development practices to build block templates.
35
36 = Developer Friendly Functions =
37 Simple to use functions, ready to render and work with the values stored through your custom block fields.
38
39 == Links ==
40 * [WordPress.org](https://wordpress.org/plugins/block-lab)
41 * [Github](https://github.com/getblocklab/block-lab)
42 * [Documentation](https://getblocklab.com/docs)
43 * [Support](https://wordpress.org/support/plugin/block-lab)
44
45 == Installation ==
46 = From Within WordPress =
47 * Visit Plugins > Add New
48 * Search for "Block Lab"
49 * Install the Block Lab plugin
50 * Activate Block Lab from your Plugins page.
51
52 = Manually =
53 * Clone Block Lab into a working directory with `https://github.com/getblocklab/block-lab.git`
54 * `cd` into the `block-lab` directory, and run `npm install && composer install`
55 * Next, build the scripts and styles with `npm build`
56 * Move the `block-lab` folder to your `/wp-content/plugins/` directory
57 * Activate the Block Lab plugin through the Plugins menu in WordPress
58
59 == Frequently Asked Questions =
60 **Q: Do I need to write code to use this plugin?**
61 A: Although the plugin handles the majority of the work in building a custom block, you will need to build HTML templates to display the content of the block. You can learn how in the developer documentation.
62
63 **Q: I have an idea for the plugin**
64 A: This plugin is open source and can only be better through community contribution. The GitHub repo is [here](https://github.com/getblocklab/block-lab).
65
66 **Q: Where can I find documentation for this plugin?**
67 A: [Here](https://getblocklab.com/docs/)
68
69 == Contributing ==
70
71 See [Contributing to Genesis Custom Blocks](https://github.com/studiopress/genesis-custom-blocks/blob/develop/CONTRIBUTING.md).
72
73 == Changelog ==
74
75 = 1.6.0 – 2020-09-02 =
76
77 Migration to Genesis Custom Blocks
78
79 * New: Full migration UI to Genesis Custom Blocks, the new home of our custom block efforts
80 * The new plugin has the same features, and your existing blocks and content should work the same after migration
81
82 = 1.5.6 – 2020-08-10 =
83
84 Small bugfixes, improved testing
85
86 * Fix: Prevent a console warning for a prop in WP 5.5
87 * New: More JS component tests, and an e2e test
88
89 = 1.5.5 – 2020-04-23 =
90
91 Removed upgrade screen, dependency updates
92
93 * Tweak: By default, the upgrade screen is hidden
94 * Tweak: Minor package.json dependency updates for security and reliability
95
96 = 1.5.4 – 2020-03-26 =
97
98 Improved stability, small bugfixes.
99
100 * Fix: Now block_field() returns null if the second argument is true, preventing confusion.
101 * New: JavaScript component tests, improving reliability.
102 * New: Linting for accessibility issues.
103
104 = 1.5.3 – 2020-01-20 =
105
106 Some UI improvements, bugfixes, and improved stability.
107
108 * Fix: Improved import error feedback, and cleaner methods
109 * Fix: Editor bug from `@wordpress/nux` package being deprecated
110 * New: Improved stability, including JS tests and query limit
111 * New: Pre-commit hook to lint only staged files
112
113 = 1.5.2 – 2020-02-04 =
114
115 Some small tweaks to the Block Importer and onboarding dialogs.
116
117 * New: Selective import now allows you to choose which of the blocks contained in your export file you'd like to import
118 * Fix: Onboarding notices are fixed so that they show in the right places, and at the right times
119
120 = 1.5.1 – 2019-11-11 =
121
122 This is a bugfix release, focused mostly on compatibility with WordPress 5.3.
123
124 * Fix: Themes can now hook into the `block_lab_add_blocks` action from the `functions.php` file
125 * Fix: Classic Text fields now function as expected when inside a repeater
126 * Fix: Rare instances of a `NaN` error when duplicating fields
127 * Fix: Style fixes for the Block Editor in WordPress 5.3
128
129 = 1.5.0 – 2019-10-30 =
130
131 Ready for a big release? We're really happy to be introducing quite a number of highly requested features, including a PHP API for registering blocks with code, a new text field with lists and headings, and some neat workflow efficiencies when building your block.
132
133 * New: There's now a PHP API for registering blocks using code (instead of the WP Admin UI). Documentation is [here](https://github.com/getblocklab/block-lab/pull/434) for now, but more on its way soon
134 * New: Classic Text control (for Block Lab Pro users)! This field is similar to Rich Text, but has a few extra options for things like lists and headings
135 * New: Duplicate fields – building your block is now so much easier, with the ability to duplicate rows
136 * New: Repeater Row Count function – a helper function that returns the total amount of rows in a given repeater. Documentation [here](https://github.com/getblocklab/block-lab/pull/429)
137 * New: Repeater Row Index function – a helper function that returns the current row, while looping through a repeater. Documentation [here](https://github.com/getblocklab/block-lab/pull/429)
138 * Tweak: We've removed our dependency on global variables. This is mostly a best practice thing, not user facing. More details [here](https://github.com/getblocklab/block-lab/pull/435).
139 * Tweak: We've refactored quite a lot about our block Loader class, to make it more robust, secure, and maintainable
140 * Tweak: Loads of new unit and integration tests - these help prevent us from introducing bugs or regressions in the future
141 * Fix: Bug which affected sites which had removed or renamed the admin user role
142 * Fix: Empty Rich Text fields now no longer output a single `</p>` tag
143
144 = 1.4.1 – 2019-09-11 =
145
146 You can now add a Minimum and Maximum Rows setting to repeaters, allowing you to specify a lower and upper limit on how many repeater rows can be added.
147
148 * New: The repeater field now includes a minimum and maximum row setting
149 * Fix: Location and Width settings are now visible again when adding a new field
150 * Fix: Using block_sub_field() with an image now correctly outputs the image URL instead of the ID
151
152 = 1.4.0 – 2019-09-04 =
153
154 This release applies some finishing touches to the repeater field. It also introduces a new "Field Width" feature, which lets you choose the width of the fields as seen in the Editor.
155
156 * New: Function to reset repeater rows: reset_block_rows()
157 * New: Add a "Field Width" control to blocks
158 * Fix: Empty repeater rows now save and can be moved properly
159 * Fix: An issue which occasionally prevented repeater rows from being deleted
160 * Fix: Prevent repeated requests to validate a Pro license
161 * Tweaks: Add a different admin notice for when a license validation request fails
162 * Tweaks: Many new and shiny unit and integration tests, making Block Lab more solid than ever
163
164 = 1.3.6 – 2019-08-22 =
165
166 * New: 🔁 REPEATER FIELD 🔁
167 * New: Conditional Blocks, based on Post Type
168 * Tweaks: Just about everything! We did a lot of refactoring in this release to make things silky smooth and über-maintainable.
169
170 = 1.3.5 – 2019-08-18 =
171
172 * New: Block Lab will now enqueue a global stylesheet, so you can keep your common block styles in one place. [Read more](https://github.com/getblocklab/block-lab/pull/371)
173 * New: Block templates can now be placed inside a sub-folder, for an even cleaner directory structure. [Read more](https://github.com/getblocklab/block-lab/pull/372)
174 * Tweak: Use a textarea for specifying the default value of a textarea control.
175 * Tweak: Better handling of deprecated functions.
176 * Tweak: Rewrite of various functions, making developer access to common commands much simpler.
177 * Fix: Child theme templates are now correctly loaded before their parent templates.
178 * Fix: Autoslugs now continue to work properly after the title field loses focus.
179
180 = 1.3.4 - 2019-07-22 =
181
182 * New: Block Lab grew to level 1.3.4. Block Lab learned **Custom Categories**.
183 * Tweak: **@phpbits** used Pull Request. All right! The **`block_lab_get_block_attributes`** filter was caught!
184 * Tweak: **Template Loader** used Harden. **Template Loader**'s defense rose!
185 * Tweak: Booted up a TM! It contained **Unit Tests**!
186 * Fix: Wild **Missing Filter in Inspector Controls** bug appeared! Go! Bugfix!
187 * Fix: Enemy **Mixed Up Inspector Controls** fainted! @kienstra gained 0902a06 EXP. Points!
188
189 = 1.3.3 - 2019-06-21 =
190
191 * Fix: The previous release broke the `className` field, used for the Additional CSS Class setting. This fixes it.
192
193 = 1.3.2 - 2019-06-21 =
194
195 * New: Rich Text Control (for Block Lab Pro users)!
196 * New: Show Block Category in the list table
197 * New: We've got a new `block_lab_render_template` hook which fires before rendering a block on the front end. Great for enqueuing JS
198 * Tweak: Updated logo
199 * Tweak: Prevent block field slugs from changing when you edit the field title
200 * Fix: Saving your license key no longer results in an error page
201 * Fix: License details screen showing the wrong information
202 * Fix: Remove duplicate IDs on the edit block screen
203 * Fix: Range sliders can now set a minimum value of zero
204 * Fix: A console warning about unique props
205
206 = 1.3.1 - 2019-05-22 =
207
208 * New: Support for Gutenberg's built-in Additional CSS Class in your block template, by using the field `className`. [Read more](https://getblocklab.com/docs/faqs/)
209 * New: The Textarea field now has an option to automatically add paragraph tags and line-breaks
210 * Fix: Bug affecting blocks containing Pro fields when there's no active Pro license
211
212 = 1.3.0 - 2019-04-30 =
213
214 **Important**: This update includes a backwards compatibility break related to the User field.
215
216 [Read more here](https://github.com/getblocklab/block-lab/pull/294===issue-272649668)
217
218 * New: A Taxonomy control type, for selecting a Category / Tag / or custom term from a dropdown menu (for Block Lab Pro users)
219 * Fix: Bug with the Post control when outputting data with block_field()
220 * Tweak: Update the User control to store data as an object, matching the Post control
221
222 = 1.2.3 - 2019-04-23 =
223
224 **Important**: This update includes a backwards compatibility break related to the Image field.
225 If you are using the `block_value()` function with an image field and externally hosted images, this update may effect you.
226
227 [Read more here](https://getblocklab.com/backwards-compatability-break-for-the-image-field/)
228
229 * New: A Post control type, for selecting a Post from a dropdown menu (for Block Lab Pro users)
230 * New: Added the block_lab_controls filter to allow custom controls to be loaded (props @rohan2388)
231 * New: The Image control now returns the image's Post ID
232 * Tweak: Travis CI support
233
234 = 1.2.2 - 2019-04-05 =
235
236 * New: Block Editor redesign
237
238 = 1.2.1 - 2019-03-21 =
239
240 * New: Automatic stylesheet enqueuing. Now you can create custom stylesheets for individual blocks! [Read more here](https://getblocklab.com/docs/get-started/style-block-lab-custom-blocks/).
241 * New: A User control type (for Block Lab Pro users)
242 * Fix: Various multiselect bug fixes, allowing for empty values in the multiselect control
243
244 = 1.2.0 - 2019-02-27 =
245
246 * New: Introducing Block Lab Pro!
247 * New: A setting for the number of rows to display in a Textarea control
248 * Fix: Allow negative numbers in Number and Range controls
249
250 = 1.1.3 - 2019-01-25 =
251
252 * New: Image field
253
254 = 1.1.2 - 2019-01-11 =
255
256 * New: Color field
257 * Fix: Incorrect output for empty fields
258
259 = 1.1.1 - 2018-12-14 =
260
261 * Fix: Undefined index error for multiselect and select fields
262 * Fix: Correct values now returned for boolean fields like checkbox and toggle
263 * Fix: Editor preview templates are back! Use the filename `preview-{blog slug}.php`
264 * Fix: "Field instructions" is now a single line text, and renamed to "Help Text"
265 * Fix: Slashes being added to field options
266 * Fix: Allow empty value for select and number fields
267 * Fix: Allow empty default values
268
269 = 1.1.0 - 2018-12-07 =
270
271 * New: Complete revamp of the in-editor preview
272 * New: Email field
273 * New: URL field
274 * New: Number field
275 * New: `block_config()` and `block_field_config` helper functions, to retrieve your block's configuration
276 * Fix: filemtime errors
277 * Fix: HTML tags were being merged together when previewed in the editor
278 * Fix: Problems with quotes and dashes in a block's title or field parameters
279 * Fix: `field_value()` sometimes returned the wrong value
280 * Fix: Incorrect values shown in the editor preview
281
282 = 1.0.1 - 2018-11-16 =
283
284 * New: Added "Save Draft" button, so you can save Blocks-in-Progress
285 * New: Better handling of the auto-slug feature, so you don't accidentally change your block's slug
286 * New: Better expanding / contracting of the Field settings
287 * New: Emoji (and special character) support! 😎
288 * Fix: Resolved Fatal Error that could occur in some environments
289 * Fix: Remove unused "Description" field
290 * Fix: Remove duplicate star icon
291
292 = 1.0.0 - 2018-11-14 =
293
294 *Rename!*
295 * Advanced Custom Blocks is now Block Lab
296
297 *Added*
298 * New control types (Radio, Checkbox, Toggle, Select, Range)
299 * Block icons
300 * Field location – add your block fields to the inspector
301 * List table refinements
302 * Field repeater table improvements
303
304 *Fixed*
305 * All the things. Probably not _all_ the things, but close.
306
307 = 0.1.2 - 2018-08-10 =
308
309 *Added*
310 * New properties `help`, `default` and `required` added to fields.
311 * Ability to import blocks from a `{theme}/blocks/blocks.json` file. Documentation still to be added.
312 * Gutenberg controls library updated preparing for `0.0.3`.
313
314 *Technical Changes*
315 * Updated control architecture to improve development and adding adding of additional controls.
316 * Clean up enqueuing of scripts.
317
318 = 0.1 - 2018-08-03 =
319 * Initial release.
1 <div class="numbers-block" style="color:#fff;background-color:<?php block_field( 'color' ); ?>;">
2 <h3><span class="number"><?php block_field( 'number' ); ?></span><span><?php block_field( 'header' ); ?></span></h3>
3 <p><?php block_field( 'paragraph' ); ?></p>
4 </div>
...\ No newline at end of file ...\ No newline at end of file
...@@ -16417,6 +16417,73 @@ h4, .h4 { ...@@ -16417,6 +16417,73 @@ h4, .h4 {
16417 display: none !important; 16417 display: none !important;
16418 } 16418 }
16419 16419
16420 @media (min-width: 1400px) {
16421 .container,
16422 .container-lg,
16423 .container-md,
16424 .container-sm,
16425 .container-xl,
16426 .container-xxl {
16427 max-width: 1366px;
16428 }
16429 }
16430 @media (min-width: 1200px) {
16431 .container,
16432 .container-lg,
16433 .container-md,
16434 .container-sm,
16435 .container-xl {
16436 max-width: 1234px;
16437 }
16438 }
16439 a.find {
16440 margin-bottom: 50px;
16441 background-color: #012169;
16442 text-transform: uppercase;
16443 font-family: "Calibri-bold";
16444 font-size: 1.25rem;
16445 line-height: 1.25rem;
16446 padding: 2px 50px 17px 60px;
16447 border-radius: 0px;
16448 width: 368px;
16449 color: #fff;
16450 margin-right: 0px !important;
16451 }
16452
16453 a.find:focus,
16454 a.find:hover {
16455 background-color: #005eb8;
16456 }
16457
16458 a.find:before {
16459 content: "";
16460 width: 22px;
16461 height: 31px;
16462 display: inline-block;
16463 top: 10px;
16464 position: relative;
16465 margin-left: -25px;
16466 margin-right: 10px;
16467 background-repeat: no-repeat;
16468 background-position: center;
16469 background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
16470 }
16471
16472 ss3-force-full-width {
16473 top: 0px !important;
16474 position: relative !important;
16475 }
16476 @media (max-width: 768px) {
16477 ss3-force-full-width {
16478 top: 66px !important;
16479 position: relative !important;
16480 }
16481 }
16482
16483 .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-bottom, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-bottom {
16484 z-index: 9999999 !important;
16485 }
16486
16420 .n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image { 16487 .n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image {
16421 z-index: 9999999 !important; 16488 z-index: 9999999 !important;
16422 position: absolute !important; 16489 position: absolute !important;
...@@ -16472,52 +16539,196 @@ h4, .h4 { ...@@ -16472,52 +16539,196 @@ h4, .h4 {
16472 text-shadow: 0px 3px 3px #00000059 !important; 16539 text-shadow: 0px 3px 3px #00000059 !important;
16473 } 16540 }
16474 16541
16475 ss3-force-full-width { 16542 .intro {
16476 top: 0px !important; 16543 background-color: #333F48;
16477 position: relative !important; 16544 margin-left: -100%;
16545 padding-left: 95%;
16546 margin-right: -50%;
16547 padding-right: 45%;
16548 padding-top: 50px;
16549 padding-bottom: 50px;
16478 } 16550 }
16479 @media (max-width: 768px) { 16551 .intro p {
16480 ss3-force-full-width { 16552 font-family: "Calibri";
16481 top: 66px !important; 16553 font-size: 1.875rem;
16482 position: relative !important; 16554 color: #fff;
16555 line-height: 2.125rem;
16556 }
16557 @media (max-width: 800px) {
16558 .intro {
16559 margin-left: -30%;
16560 padding-left: 35%;
16561 margin-right: -30%;
16562 padding-right: 35%;
16563 }
16564 }
16565 @media (max-width: 600px) {
16566 .intro {
16567 min-height: 750px;
16568 margin-left: -10%;
16569 padding-left: 15%;
16570 margin-right: -10%;
16571 padding-right: 15%;
16483 } 16572 }
16484 } 16573 }
16485 16574
16486 .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-bottom, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-bottom { 16575 .yellow {
16487 z-index: 9999999 !important; 16576 background-color: #FFB81C;
16577 margin-left: -100%;
16578 padding-left: 95%;
16579 margin-right: -50%;
16580 padding-right: 45%;
16581 padding-top: 50px;
16582 padding-bottom: 50px;
16583 }
16584 .yellow p {
16585 font-family: "Calibri";
16586 font-size: 1.125rem;
16587 color: #333F48;
16588 line-height: 1.3125rem;
16589 }
16590 @media (max-width: 800px) {
16591 .yellow {
16592 margin-left: -30%;
16593 padding-left: 35%;
16594 margin-right: -30%;
16595 padding-right: 35%;
16596 }
16597 }
16598 @media (max-width: 600px) {
16599 .yellow {
16600 min-height: 750px;
16601 margin-left: -10%;
16602 padding-left: 15%;
16603 margin-right: -10%;
16604 padding-right: 15%;
16605 }
16606 }
16607 .yellow .number-text {
16608 display: inline-block;
16609 margin-left: 10px;
16610 }
16611 .yellow span.number-big {
16612 font-family: "Calibri-bold";
16613 font-size: 12.8125rem;
16614 color: #012169;
16615 }
16616 .yellow span.number-med {
16617 font-family: "Calibri-bold";
16618 font-size: 2.4375rem;
16619 color: #333F48;
16620 margin-left: 0px;
16621 }
16622 .yellow span.number-med.last {
16623 margin-left: -25px;
16488 } 16624 }
16489 16625
16490 a.find { 16626 .body {
16491 margin-bottom: 50px; 16627 margin-left: -100%;
16492 background-color: #012169; 16628 padding-left: 95%;
16493 text-transform: uppercase; 16629 margin-right: -50%;
16630 padding-right: 45%;
16631 padding-top: 50px;
16632 padding-bottom: 50px;
16633 }
16634 @media (max-width: 800px) {
16635 .body {
16636 margin-left: -30%;
16637 padding-left: 35%;
16638 margin-right: -30%;
16639 padding-right: 35%;
16640 }
16641 }
16642 @media (max-width: 600px) {
16643 .body {
16644 min-height: 750px;
16645 margin-left: -10%;
16646 padding-left: 15%;
16647 margin-right: -10%;
16648 padding-right: 15%;
16649 }
16650 }
16651
16652 .numbers-block {
16653 padding: 40px 50px 30px 50px;
16654 }
16655 .numbers-block h3, .numbers-block .h3 {
16656 margin-top: 50px;
16657 margin-bottom: -60px;
16658 color: #fff;
16494 font-family: "Calibri-bold"; 16659 font-family: "Calibri-bold";
16495 font-size: 1.25rem; 16660 font-size: 2.5rem;
16496 line-height: 1.25rem; 16661 text-transform: uppercase;
16497 padding: 2px 50px 17px 60px; 16662 line-height: 9.375rem;
16498 border-radius: 0px; 16663 margin-left: -10px;
16499 width: 368px; 16664 }
16665 .numbers-block h3 span, .numbers-block .h3 span {
16666 display: inline-block;
16667 width: 67%;
16668 line-height: 2.5rem;
16669 }
16670 .numbers-block h3 span.number, .numbers-block .h3 span.number {
16671 font-size: 12.8125rem;
16672 width: 33%;
16673 }
16674 .numbers-block p {
16500 color: #fff; 16675 color: #fff;
16501 margin-right: 0px !important; 16676 font-family: "Calibri";
16677 font-size: 1.125rem;
16678 line-height: 1.3125rem;
16502 } 16679 }
16503 16680
16504 a.find:focus, 16681 .find-text {
16505 a.find:hover { 16682 color: #5B6770;
16506 background-color: #005eb8; 16683 font-family: "Calibri";
16684 font-size: 2.5rem;
16685 line-height: 2.75rem;
16686 width: 72%;
16687 display: inline-block;
16507 } 16688 }
16508 16689
16509 a.find:before { 16690 .find-number {
16510 content: ""; 16691 color: #012169;
16511 width: 22px; 16692 font-family: "Calibri";
16512 height: 31px; 16693 font-size: 12.8125rem;
16694 line-height: 9.375rem;
16695 width: 25%;
16513 display: inline-block; 16696 display: inline-block;
16514 top: 10px; 16697 padding-right: 30px;
16515 position: relative; 16698 text-align: right;
16516 margin-left: -25px; 16699 }
16517 margin-right: 10px; 16700
16518 background-repeat: no-repeat; 16701 .wp-block-group.gray {
16519 background-position: center; 16702 background-color: #f2f2f2;
16520 background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg"); 16703 margin-left: -100%;
16704 padding-left: 95%;
16705 margin-right: -50%;
16706 padding-right: 45%;
16707 padding-top: 50px;
16708 padding-bottom: 50px;
16709 }
16710 @media (max-width: 800px) {
16711 .wp-block-group.gray {
16712 margin-left: -30%;
16713 padding-left: 35%;
16714 margin-right: -30%;
16715 padding-right: 35%;
16716 }
16717 }
16718 @media (max-width: 600px) {
16719 .wp-block-group.gray {
16720 min-height: 750px;
16721 margin-left: -10%;
16722 padding-left: 15%;
16723 margin-right: -10%;
16724 padding-right: 15%;
16725 }
16726 }
16727 .wp-block-group.gray figcaption {
16728 background-color: #333f48;
16729 color: #fff;
16730 padding: 20px;
16731 margin-top: -5px;
16521 } 16732 }
16522 16733
16523 #wrapper-footer { 16734 #wrapper-footer {
......
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
...@@ -71,4 +71,16 @@ jQuery( document ).ready(function($) { ...@@ -71,4 +71,16 @@ jQuery( document ).ready(function($) {
71 return Math.random() * (max - min) + min; 71 return Math.random() * (max - min) + min;
72 } 72 }
73 73
74
75
76
77 var maxHeight = 0;
78
79 $(".numbers-block").each(function(){
80 if ($(this).height() > maxHeight) { maxHeight = $(this).height(); }
81 });
82
83 $(".numbers-block").height(maxHeight);
84
85
74 }); 86 });
...\ No newline at end of file ...\ No newline at end of file
......
1 @media (min-width: 1400px) {
2 .container,
3 .container-lg,
4 .container-md,
5 .container-sm,
6 .container-xl,
7 .container-xxl {
8 max-width: 1366px;
9 }
10 }
11
12 @media (min-width: 1200px) {
13 .container,
14 .container-lg,
15 .container-md,
16 .container-sm,
17 .container-xl {
18 max-width: 1234px;
19 }
20
21 }
22
23
24
25
26 a.find {
27 margin-bottom: 50px;
28 background-color: #012169;
29 text-transform: uppercase;
30 font-family: "Calibri-bold";
31 font-size: 20px;
32 line-height: 20px;
33 padding: 2px 50px 17px 60px;
34 border-radius: 0px;
35 width: 368px;
36 color: #fff;
37 margin-right: 0px !important;
38 }
39 a.find:focus,
40 a.find:hover{
41 background-color: #005eb8;
42 }
43 a.find:before {
44 content: "";
45 width: 22px;
46 height: 31px;
47 display: inline-block;
48 top: 10px;
49 position: relative;
50 margin-left: -25px;
51 margin-right: 10px;
52 background-repeat: no-repeat;
53 background-position: center;
54 background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
55 }
56
57
58 ss3-force-full-width{
59 top: 0px !important;
60 position: relative !important;
61 @media (max-width: 768px) {
62 top: 66px !important;
63 position: relative !important;
64 }
65 }
66
67 .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-bottom, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-bottom{
68 z-index: 9999999 !important;
69
70 }
71
72
73
1 .n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image{ 74 .n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image{
2 z-index: 9999999 !important; 75 z-index: 9999999 !important;
3 position: absolute !important; 76 position: absolute !important;
...@@ -44,47 +117,187 @@ div#n2-ss-2item3{ ...@@ -44,47 +117,187 @@ div#n2-ss-2item3{
44 117
45 } 118 }
46 119
47 ss3-force-full-width{ 120 .intro{
48 top: 0px !important; 121 background-color: #333F48;
49 position: relative !important; 122 margin-left: -100%;
50 @media (max-width: 768px) { 123 padding-left: 95%;
51 top: 66px !important; 124 margin-right: -50%;
52 position: relative !important; 125 padding-right: 45%;
126 padding-top: 50px;
127 padding-bottom: 50px;
128 p{
129 font-family: "Calibri";
130 font-size: 30px;
131 color: #fff;
132 line-height: 34px;
133 }
134 @media (max-width: 800px) {
135 margin-left: -30%;
136 padding-left: 35%;
137 margin-right: -30%;
138 padding-right: 35%;
53 } 139 }
140 @media (max-width: 600px) {
141 min-height: 750px;
142 margin-left: -10%;
143 padding-left: 15%;
144 margin-right: -10%;
145 padding-right: 15%;
146 }
147
54 } 148 }
55 149
56 .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-top, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-left-bottom, .n2-ss-slider .n2-ss-slider-wrapper-inside .n2-ss-slider-controls-absolute-right-bottom{ 150 .yellow{
57 z-index: 9999999 !important; 151 background-color: #FFB81C;
152 margin-left: -100%;
153 padding-left: 95%;
154 margin-right: -50%;
155 padding-right: 45%;
156 padding-top: 50px;
157 padding-bottom: 50px;
158 p{
159 font-family: "Calibri";
160 font-size: 18px;
161 color: #333F48;
162 line-height: 21px;
163 }
164 @media (max-width: 800px) {
165 margin-left: -30%;
166 padding-left: 35%;
167 margin-right: -30%;
168 padding-right: 35%;
169 }
170 @media (max-width: 600px) {
171 min-height: 750px;
172 margin-left: -10%;
173 padding-left: 15%;
174 margin-right: -10%;
175 padding-right: 15%;
176 }
177 .number-text {
178 display: inline-block;
179 margin-left: 10px;
180 //width: 49%;
181 }
182 span.number-big{
183 font-family: "Calibri-bold";
184 font-size: 205px;
185 color: #012169;
58 186
59 } 187 }
188 span.number-med{
189 font-family: "Calibri-bold";
190 font-size: 39px;
191 color: #333F48;
192 margin-left: 0px;
193 }
194 span.number-med.last{
195 margin-left: -25px;
60 196
61 a.find { 197 }
62 margin-bottom: 50px; 198
63 background-color: #012169; 199 }
64 text-transform: uppercase; 200
201 .body{
202 margin-left: -100%;
203 padding-left: 95%;
204 margin-right: -50%;
205 padding-right: 45%;
206 padding-top: 50px;
207 padding-bottom: 50px;
208 @media (max-width: 800px) {
209 margin-left: -30%;
210 padding-left: 35%;
211 margin-right: -30%;
212 padding-right: 35%;
213 }
214 @media (max-width: 600px) {
215 min-height: 750px;
216 margin-left: -10%;
217 padding-left: 15%;
218 margin-right: -10%;
219 padding-right: 15%;
220 }
221 }
222 .numbers-block{
223 padding:40px 50px 30px 50px;
224 h3{
225 margin-top: 50px;
226 margin-bottom: -60px;
227 color: #fff;
65 font-family: "Calibri-bold"; 228 font-family: "Calibri-bold";
66 font-size: 20px; 229 font-size: 40px;
67 line-height: 20px; 230 text-transform: uppercase;
68 padding: 2px 50px 17px 60px; 231 line-height: 150px;
69 border-radius: 0px; 232 margin-left: -10px;
70 width: 368px; 233
234 span{
235 display: inline-block;
236 width: 67%;
237 line-height: 40px;
238 }
239 span.number{
240 font-size: 205px;
241 width: 33%;
242 }
243 }
244 p{
71 color: #fff; 245 color: #fff;
72 margin-right: 0px !important; 246 font-family: "Calibri";
247 font-size: 18px;
248 line-height: 21px;
249 }
250
73 } 251 }
74 a.find:focus, 252 .find-text{
75 a.find:hover{ 253 color: #5B6770;
76 background-color: #005eb8; 254 font-family: "Calibri";
255 font-size: 40px;
256 line-height: 44px;
257 width: 72%;
258 display: inline-block;
77 } 259 }
78 a.find:before { 260 .find-number{
79 content: ""; 261 color: #012169;
80 width: 22px; 262 font-family: "Calibri";
81 height: 31px; 263 font-size: 205px;
264 line-height: 150px;
265 width: 25%;
82 display: inline-block; 266 display: inline-block;
83 top: 10px; 267 padding-right: 30px;
84 position: relative; 268 text-align: right;
85 margin-left: -25px; 269 }
86 margin-right: 10px; 270
87 background-repeat: no-repeat; 271
88 background-position: center; 272 .wp-block-group.gray {
89 background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg"); 273 background-color: #f2f2f2;
274 margin-left: -100%;
275 padding-left: 95%;
276 margin-right: -50%;
277 padding-right: 45%;
278 padding-top: 50px;
279 padding-bottom: 50px;
280 @media (max-width: 800px) {
281 margin-left: -30%;
282 padding-left: 35%;
283 margin-right: -30%;
284 padding-right: 35%;
285 }
286 @media (max-width: 600px) {
287 min-height: 750px;
288 margin-left: -10%;
289 padding-left: 15%;
290 margin-right: -10%;
291 padding-right: 15%;
292 }
293 figcaption{
294 background-color: #333f48;
295 color: #fff;
296 padding: 20px;
297 margin-top: -5px
298 }
90 } 299 }
300
301
302
303
......