632636fb by Jeff Balicki

home content

1 parent e4dc7cb8
Showing 104 changed files with 14352 additions and 69 deletions
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
{
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>",
"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>"
}
<svg fillRule="evenodd" clipRule="evenodd" strokeLinecap="round" strokeLinejoin="round" strokeMiterlimit="1.5"
width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
id="Block_Lab_Icon"
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"
fill="#82878c"
/>
</svg>
<?php
/**
* Block Lab
*
* @package Block_Lab_Custom_Pro
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*
* Plugin Name: Block Lab Custom Pro
* Plugin URI:
* Description: The easy way to build custom blocks for Gutenberg.
* Version: 200
* Author:
* Author URI:
* License: GPL2
* License URI:
* Text Domain: block-lab
* Domain Path: languages
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Setup the plugin auto loader.
require_once 'php/autoloader.php';
/**
* Admin notice for incompatible versions of PHP.
*/
function block_lab_php_version_error() {
printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_php_version_text() ) );
}
/**
* String describing the minimum PHP version.
*
* "Namespace" is a PHP 5.3 introduced feature. This is a hard requirement
* for the plugin structure.
*
* "Traits" is a PHP 5.4 introduced feature. Remove "Traits" support from
* php/autoloader if you want to support a lower PHP version.
* Remember to update the checked version below if you do.
*
* @return string
*/
function block_lab_php_version_text() {
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' );
}
// If the PHP version is too low, show warning and return.
if ( version_compare( phpversion(), '5.4', '<' ) ) {
if ( defined( 'WP_CLI' ) ) {
WP_CLI::warning( block_lab_php_version_text() );
} else {
add_action( 'admin_notices', 'block_lab_php_version_error' );
}
return;
}
/**
* Admin notice for incompatible versions of WordPress or missing Gutenberg Plugin.
*/
function block_lab_wp_version_error() {
printf( '<div class="error"><p>%s</p></div>', esc_html( block_lab_wp_version_text() ) );
}
/**
* String describing the minimum WP version or Gutenberg Plugin requirement.
*
* "Blocks" are a feature of WordPress 5.0+ or require the Gutenberg plugin.
*
* @return string
*/
function block_lab_wp_version_text() {
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' );
}
// If the WordPress version is too low, show warning and return.
if ( ! function_exists( 'register_block_type' ) ) {
if ( defined( 'WP_CLI' ) ) {
WP_CLI::warning( block_lab_wp_version_text() );
} else {
add_action( 'admin_notices', 'block_lab_wp_version_error' );
}
}
/**
* Get the plugin object.
*
* @return \Block_Lab\Plugin
*/
function block_lab() {
static $instance;
if ( null === $instance ) {
$instance = new \Block_Lab\Plugin();
}
return $instance;
}
/**
* Setup the plugin instance.
*/
block_lab()
->set_basename( plugin_basename( __FILE__ ) )
->set_directory( plugin_dir_path( __FILE__ ) )
->set_file( __FILE__ )
->set_slug( 'block-lab' )
->set_url( plugin_dir_url( __FILE__ ) )
->set_version( __FILE__ )
->init();
// Sometimes we need to do some things after the plugin is loaded, so call the Plugin_Interface::plugin_loaded().
add_action( 'plugins_loaded', [ block_lab(), 'plugin_loaded' ] );
// Require helpers at 11, so if GCB is active, its helpers will be required first and prevent a PHP error.
add_action( 'plugins_loaded', [ block_lab(), 'require_helpers' ], 11 );
.post-type-block_lab .tablenav.top,
.post-type-block_lab .search-box,
.post-type-block_lab .inline-edit-date,
.post-type-block_lab .inline-edit-group {
display: none;
}
.post-type-block_lab .column-template code {
background: none;
font-size: 12px;
padding-left: 0;
}
.post-type-block_lab .fixed .column-icon {
width: 10%;
}
.post-type-block_lab .fixed td.column-icon {
color: #72777c;
}
.post-type-block_lab .fixed td.column-icon .icon {
background: #ffffff;
border: 1px solid #aaa;
border-radius: 4px;
padding: 4px;
width: 24px;
height: 24px;
display: inline-block;
}
.post-type-block_lab .fixed .column-fields {
width: 10%;
}
#minor-publishing-actions,
#misc-publishing-actions #visibility,
#misc-publishing-actions .curtime {
display: none;
}
#misc-publishing-actions .block-lab-pub-section {
padding: 6px 10px 14px;
}
#misc-publishing-actions .block-lab-pub-section:before {
content: "\f537";
color: #82878c;
font: normal 20px/1 dashicons;
speak: none;
display: inline-block;
margin-left: -1px;
padding-right: 3px;
vertical-align: top;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#misc-publishing-actions .block-lab-pub-section .post-types-display {
font-weight: bold;
}
#misc-publishing-actions .block-lab-pub-section .post-types-select {
display: none;
}
#misc-publishing-actions .block-lab-pub-section .post-types-select .post-types-select-items {
margin: 3px 0;
border: 1px solid #eeeeee;
padding: 6px 10px;
margin-bottom: 6px;
}
.handle-order-higher,
.handle-order-lower {
display: none;
}
#block_fields a,
#block_fields a:focus {
outline: none;
box-shadow: none;
}
#block_fields .handlediv,
#block_fields .hndle {
display: none;
}
#block_fields .inside {
padding: 0;
margin: 0;
}
#block_fields .block-fields-list .widefat {
border: none;
}
#block_fields .block-fields-list td {
padding: 0;
line-height: 0;
}
#block_fields .block-fields-list thead th {
font-weight: 600;
width: 32%;
border-bottom: 1px solid #eee;
}
#block_fields .block-fields-list thead th.block-fields-sort {
width: 4%;
min-width: 32px;
}
#block_fields .block-fields-list tbody {
background: #f5f5f5;
}
#block_fields button {
border: 0;
background: transparent;
padding: 0;
margin: 0;
cursor: pointer;
color: #0073aa;
font-weight: bold;
transition: none;
line-height: 20px;
}
#block_fields button:hover {
color: #00a0d2;
}
#block_fields button:active,
#block_fields button:focus {
outline: 0;
}
#block_fields button > .dashicons {
color: #0073aa;
font-size: 12px;
border: 2px solid #0073aa;
border-radius: 10px;
padding: 0;
margin: 1px 4px 0 0;
width: 14px;
height: 14px;
text-align: center;
line-height: 16px;
transition: none;
}
#block_fields button:hover > .dashicons {
color: #00a0d2;
border-color: #00a0d2;
}
#block_fields .block-fields-rows {
width: 100%;
line-height: 1.5em;
}
#block_fields .block-fields-rows .block-no-fields {
display: none;
padding: 20px 10px 10px;
margin: 0;
text-align: center;
}
#block_fields .block-fields-rows .block-fields-row {
background: #fff;
border: 1px solid #eee;
box-shadow: 0 1px 1px rgba(0,0,0,.04);
margin: 10px 10px 0;
}
#block_fields .block-fields-rows .block-fields-row-columns {
display: grid;
grid-template-columns: [handle] minmax( 20px, calc(4% - 10px) ) calc(32% + 14px) calc(32% + 14px) calc(32% - 18px);
}
#block_fields .block-fields-rows .block-fields-row > div {
padding: 8px 10px;
}
#block_fields .block-fields-sort-handle:before {
content: "\f545";
}
#block_fields .block-fields-sort-handle {
display: none;
width: 15px;
height: 15px;
font-family: 'Dashicons', monospace;
font-size: 15px;
line-height: 15px;
cursor: grab;
padding-top: 2px;
}
#block_fields .block-fields-rows .row-title {
display: block;
padding-bottom: 6px;
}
#block_fields .block-fields-actions-add-field {
padding: 20px 10px;
clear: both;
background: #f5f5f5;
text-align: center;
}
#block_fields .block-fields-actions {
font-size: 12px;
line-height: 12px;
visibility: hidden;
}
#block_fields .block-fields-row-columns:hover > .block-fields-label .block-fields-actions {
visibility: visible;
}
#block_fields .block-fields-row-columns:hover > .block-fields-sort .block-fields-sort-handle {
display: block;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-edit {
display: none;
grid-column: 1 / span 4;
padding: 0;
border-bottom: 1px solid #e1e1e1;
border-top: 1px solid #e1e1e1;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-edit tbody {
background: #f7f7f7;
}
#block_fields .block-fields-rows .block-fields-edit td,
#block_fields .block-fields-rows .block-fields-edit th {
padding: 8px 10px;
}
#block_fields .block-fields-rows .block-fields-edit td.spacer {
width: 4%;
}
#block_fields .block-fields-rows .block-fields-edit th {
font-size: 12px;
width: 32%;
border-right: 1px solid #e1e1e1;
}
#block_fields .block-fields-rows .block-fields-edit th label {
font-weight: 600;
}
#block_fields .block-fields-rows .block-fields-control {
display: flex;
}
#block_fields .block-fields-rows .block-fields-control span.pro-required {
margin-left: 0.5rem;
}
#block_fields .block-fields-rows input[readonly="readonly"],
#block_fields .block-fields-rows textarea[readonly="readonly"] {
color: #999;
}
#block_fields .block-fields-rows .button-group > .button {
width: 5em;
position: relative;
z-index: 10;
}
#block_fields .block-fields-rows .button-group > .button:last-of-type {
border-radius: 0 3px 3px 0;
}
#block_fields .block-fields-rows .button-group > .button:hover {
z-index: 30;
}
#block_fields .block-fields-rows .button-group > .button:checked {
background: #eee;
border-color: #999;
box-shadow: inset 0 2px 5px -3px rgba(0, 0, 0, 0.5), 0 1px 0 #cccccc;
z-index: 20;
}
#block_fields .block-fields-rows .button-group > .button:checked:before {
width: 0;
height: 0;
background-color: transparent;
}
#block_fields .block-fields-rows .button-group > label {
font-size: 13px;
position: absolute;
margin-left: -5em;
width: 5em;
top: 13px;
text-align: center;
pointer-events: none;
z-index: 40;
}
#block_fields .block-fields-rows .block-fields-edit-loading .loading:before {
content: '';
box-sizing: border-box;
display: block;
width: 20px;
height: 20px;
border-radius: 50%;
border: 2px solid #e1e1e1;
border-top-color: #444;
animation: spinner .6s linear infinite;
}
@media only screen and (max-width: 850px) {
#block_fields .block-fields-rows {
max-height: none;
overflow-x: auto;
overflow-y: auto;
line-height: 1.5em;
}
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows {
grid-column: 1 / span 4;
margin: 0 10px;
padding: 0 0 0 32px;
overflow: hidden;
}
#block_fields .block-fields-sub-rows .block-fields-row-columns {
grid-template-columns: [handle] minmax( 20px, 3% ) calc(33% - 22px) calc(32% + 38px) calc(32% - 16px);
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows .block-fields-row {
border: none;
background: #f7f7f7;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions {
grid-column: 1 / span 4;
padding: 0;
margin: 0 10px 0 42px;
text-align: center;
background: #fff;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions p {
padding: 10px 0;
margin: 10px 0;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions p.repeater-no-fields {
background: #f7f7f7;
}
#block_fields .block-fields-rows .block-fields-row .block-fields-sub-rows-actions button {
margin-left: -32px;
}
#block_properties input,
#block_properties select,
#block_properties label {
width: 100%;
display: block;
}
#block_properties #block-properties-icon-current {
background: #ffffff;
border: 1px solid #cccccc;
border-right: 0;
border-radius: 3px 0 0 3px;
padding: 3px 6px;
margin-right: 0;
width: 24px;
height: 24px;
display: inline-block;
box-shadow: 0 1px 0 #cccccc;
}
#block_properties #block-properties-icon-current svg {
color: #23282d;
fill: #23282d;
}
#block_properties .block-properties-icon-button {
width: auto;
display: inline-block;
vertical-align: top;
margin-left: -4px;
border-radius: 0 3px 3px 0;
height: 32px;
line-height: 28px;
padding: 0 10px 1px;
}
#block_properties .block-properties-icon-button:active {
transform: translateY(0);
box-shadow: inset 0 2px 5px -3px rgba( 0, 0, 0, 0.5 ), 0 1px 0 #cccccc;
}
#block_properties #block-properties-icon-select,
#block_properties #block-properties-icon-close,
#block_properties #block-properties-icon-choose:target {
display: none;
}
#block-properties-icon-choose:target + #block-properties-icon-close {
display: inline-block;
}
#block-properties-icon-choose:target ~ #block-properties-icon-select {
display: block;
}
#block_properties .block-properties-icon-select {
background: #f9f9f9;
overflow-x: hidden;
overflow-y: scroll;
padding: 4px;
margin: 5px 0 0;
height: 110px;
border: 1px solid #cccccc;
border-radius: 3px;
}
#block_properties .block-properties-icon-select .icon {
display: inline-block;
padding: 4px;
margin: 1px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
color: #72777c;
font-size: 25px;
width: 1em;
height: 1em;
}
#block_properties .block-properties-icon-select .icon:hover {
background: #fff;
border: 1px solid #aaa;
color: #444;
}
#block_properties .block-properties-icon-select .icon.selected,
#block_properties .block-properties-icon-select .icon.selected:hover {
border: 1px solid rgba(0, 160, 210, 1);
color: rgba(0, 160, 210, 1);
fill: rgba(0, 160, 210, 1);
}
#block_properties .block-properties-icon-select .icon svg {
vertical-align: top;
}
#block_properties .block-properties-category-custom {
display: none;
margin: 12px 0;
}
#block_template > h2 {
display: none;
}
#block_template .inside {
padding: 0;
margin: 0;
}
#block_template .template-notice {
background: #fff;
border-left: 4px solid #ffffff;
padding: 1px 12px;
border-left-color: #7D5DEC;
}
#block_template .template-success {
border-left-color: #46b450;
}
#block_template .template-location {
margin: 0 0 6px;
padding: 5px 8px;
background: rgba(0, 0, 0, 0.07);
display: inline-block;
font-size: 0;
}
#block_template .template-location * {
font-size: 12px;
line-height: 23px;
}
#block_template .template-location a.filename {
color: #444;
text-decoration: none;
border-bottom: 1px dotted #444;
}
#block_template .template-location a.filename:focus {
outline: none;
box-shadow: none;
}
#block_template .template-location .click-to-copy {
display: none;
}
#block_template .template-location .click-to-copy input {
color: #444;
margin: 0;
font-size: 12px;
line-height: 15px;
}
/**
* Tooltip Styles
*/
/* Add this attribute to the element that needs a tooltip */
[data-tooltip] {
position: relative;
z-index: 2;
cursor: pointer;
}
/* Hide the tooltip content by default */
[data-tooltip]:before,
[data-tooltip]:after {
visibility: hidden;
opacity: 0;
pointer-events: none;
}
/* Position tooltip above the element */
[data-tooltip]:before {
position: absolute;
bottom: 150%;
left: 50%;
margin-bottom: 5px;
margin-left: -60px;
padding: 7px;
width: 120px;
border-radius: 3px;
background-color: #000;
background-color: rgba(40, 40, 40, 0.95);
color: #fff;
content: attr(data-tooltip);
text-align: center;
font-size: 12px;
line-height: 1.2;
}
/* Triangle hack to make tooltip look like a speech bubble */
[data-tooltip]:after {
position: absolute;
bottom: 150%;
left: 50%;
margin-left: 3px;
width: 0;
border-top: 5px solid #000;
border-top: 5px solid rgba(40, 40, 40, 0.95);
border-right: 5px solid transparent;
border-left: 5px solid transparent;
content: " ";
font-size: 0;
line-height: 0;
}
/* Show tooltip content on hover */
[data-tooltip]:hover:before,
[data-tooltip]:hover:after {
visibility: visible;
opacity: 1;
}
@keyframes spinner {
to {
transform: rotate( 360deg );
}
}
.bl-notice-conflict {
display: flex;
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.bl-notice-conflict .bl-conflict-copy {
margin-right: auto;
}
.bl-notice-conflict .bl-link-deactivate {
margin: auto 0.2rem;
}
#adminmenu ul > li > a[href="edit.php?post_type=block_lab&page=block-lab-pro"] {
color: #00b9eb;
}
\ No newline at end of file
.bl-notice-migration {
display: flex;
}
.bl-notice-migration .bl-migration-copy {
margin-right: auto;
}
.bl-notice-migration__learn-more {
display: block;
}
.bl-notice-migration .bl-notice-option {
margin: auto 0.2rem;
}
.bl-notice-migration.bl-hidden {
display: none;
}
#bl-notice-not-now {
margin-left: 16px;
}
:root {
--color-brand: #5C34E8;
--color-pink: #FF227E;
--color-orange: #FF7C53;
--color-heading: #000;
--color-body: rgb(51, 51, 51);
--color-white: #fff;
--color-black: #000;
--color-gray: #edf2f7;
--color-green: #48bb78;
--color-purple: #422E62;
--color-purple-100: #FAF5FF;
--color-purple-200: #E9D8FD;
--color-purple-300: #D6BCFA;
--color-purple-400: #B794F4;
--color-purple-500: #9F7AEA;
--color-purple-600: #805AD5;
--color-purple-700: #6B46C1;
--color-purple-800: #553C9A;
--color-purple-900: #44337A;
--color-gray-100: #F7FAFC;
--color-gray-200: #EDF2F7;
--color-gray-300: #E2E8F0;
--color-gray-400: #CBD5E0;
--color-gray-500: #A0AEC0;
--color-gray-600: #718096;
--color-gray-700: #4A5568;
--color-gray-800: #2D3748;
--color-gray-900: #1A202C;
--box-shadow: 10px 10px 20px 0px rgba(121,110,255,0.1);
--box-shadow-small: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06);
}
#wpcontent {
padding-left: 0;
}
.bl-migration__content {
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";
line-height: 1.5;
}
.bl-migration__content {
color: var(--color-body);
font-size: 16px;
}
.bl-migration__content .get-genesis-pro {
border-top: solid 1px var(--color-gray-300);
padding-top: 2rem;
font-size: 18px;
}
.bl-migration__content p {
margin-bottom: 1em;
font-size: 16px;
}
.bl-migration__content h1,
.bl-migration__content h2,
.bl-migration__content h3,
.bl-migration__content h4,
.bl-migration__content h5,
.bl-migration__content h6 {
font-weight: 500;
margin-bottom: 1em;
}
.bl-migration__content h1 {
font-size: 1.65rem;
}
.bl-migration__content a {
color: var(--color-brand);
text-decoration: underline;
}
.bl-migration__content {
display: flex;
flex-grow: 1;
flex-direction: column;
background-color: var(--color-gray);
}
.bl-migration__content ul {
list-style-type: disc;
padding-left: 2rem;
margin-bottom: 2rem;
}
.bl-migration__content-wrapper {
padding: 1rem;
overflow: auto;
flex-grow: 1;
}
.bl-migration__content-container {
box-shadow: var(--box-shadow);
padding: 2.5rem;
background-color: var(--color-white);
}
.bl-migration__content .container {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.bl-migration__content .btn {
appearance: none;
border: none;
display: flex;
color: var(--color-purple-100);
cursor: pointer;
box-shadow: var(--box-shadow-small);
padding-left: 1.25rem;
padding-right: 1.25rem;
font-size: .875rem;
height: 2.25rem;
line-height: 1;
font-weight: 500;
align-items: center;
border-radius: .25rem;
background-color: var(--color-purple-700);
text-decoration: none;
}
.bl-migration__content .btn:hover {
cursor: pointer;
background-color: var(--color-purple-800);
}
.bl-migration__content .btn[disabled],
.genesis-pro-form button[disabled] {
color: var(--color-gray-500);
background-color: var(--color-gray-200);
box-shadow: none;
cursor: not-allowed;
}
.bl-migration__content .get-genesis-pro .btn {
display: inline-flex;
margin-right: 1rem;
}
.bl-migration__content .btn-secondary {
color: var(--color-purple-600);
border-color: var(--color-purple-200);
background-color: var(--color-purple-100);
box-shadow: none;
border-width: 1px;
border-style: solid;
}
.bl-migration__content .btn-secondary:hover {
color: var(--color-purple-700);
background-color: var(--color-purple-200);
}
.help-text {
opacity: 0.7;
font-style: italic;
margin-top: .5rem;
font-size: .875rem;
}
.dev-notice {
display: flex;
padding-left: .5rem;
padding-right: .5rem;
height: 3rem;
justify-content: flex-start;
align-items: center;
border-width: 1px;
border-radius: .25rem;
border-color: var(--color-brand);
background-color: var(--color-purple-100);
}
.dev-notice svg {
color: var(--color-purple-600);
fill: currentColor;
height: 1.25rem;
width: 1.25rem;
margin-left: 0.25rem;
}
.dev-notice>span {
color: var(--color-purple-700);
font-size: .875rem;
margin-left: .5rem;
line-height: 1;
font-weight: 500;
}
.dev-notice .btn {
color: var(--color-purple-700);
background-color: var(--color-purple-200);
height: 2rem;
padding-right: 0.75rem;
padding-left: 0.75rem;
margin-left: auto;
}
.dev-notice .btn:hover,
.genesis-pro-form button:hover {
color: var(--color-purple-800);
background-color: var(--color-purple-300);
}
.step {
background-color: var(--color-gray-100);
display: flex;
justify-content: flex-start;
padding-top: 1.1rem;
padding-bottom: 1rem;
padding-left: 1rem;
padding-right: 1rem;
border-top-width: 1px;
border-top-style: solid;
border-top-color: var(--color-gray-400);
max-height: 2.4rem;
overflow: hidden;
transition-duration: 200ms;
}
.step:last-child {
border-bottom: 1px solid var(--color-gray-400);
}
.step-icon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
height: 2.25rem;
width: 2.25rem;
border-radius: 99999px;
border-color: var(--color-gray-300);
border-width: 2px;
border-style: solid;
color: var(--color-gray-500)
}
.step-icon svg {
width: 1.5rem;
height: 1.5rem;
fill: currentColor;
}
.step-icon span {
font-size: 1.25rem;
font-weight: 600;
}
.step h3 {
color: var(--color-gray-500);
margin-top: 6px;
}
.step--active {
overflow: hidden;
transition: max-height 1s ease-in-out;
max-height: 200rem;
background-color: var(--color-white);
padding-top: 2rem;
padding-bottom: 2rem;
}
.step--active h3 {
color: var(--color-body);
}
.step--active .step-icon {
border-color: var(--color-green);
color: var(--color-green);
}
.step--complete .step-icon {
background-color: var(--color-green);
border-color: var(--color-green);
color: var(--color-white);
}
.step--complete h3 {
color: var(--color-body);
}
.step-content {
flex-grow: 1;
margin-left: 2rem;
}
.step-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 2rem;
border-top: solid 1px var(--color-gray-300);
padding-top: 1rem;
}
.step-footer form {
display: flex;
align-items: center;
margin-left: auto;
margin-bottom: 0;
margin-right: 1rem;
}
.step-footer button:only-child {
margin-left: auto;
}
.step-footer input[type="checkbox"] {
margin: 0;
}
label {
font-size: .875rem;
margin-left: .25rem;
font-weight: 500;
color: var(--color-gray-700);
}
.genesis-pro-form {
display: flex;
align-items: center;
}
.genesis-pro-form form {
display: flex;
margin-bottom: 0;
}
.genesis-pro-form input {
appearance: none;
width: 20rem;
box-shadow: var(--box-shadow-small);
padding-left: 1rem;
padding-right: 1rem;
font-size: .875rem;
height: 2.5rem;
border-style: solid;
border-width: 1px;
border-radius: 0.25rem 0 0 0.25rem;
border-color: var(--color-gray-400)
}
.genesis-pro-form button {
color: var(--color-purple-600);
box-shadow: var(--box-shadow-small);
padding-left: 1rem;
padding-right: 1rem;
font-size: .875rem;
height: 2.5rem;
margin-left: -1px;
font-weight: 500;
border-style: solid;
border-width: 1px;
border-bottom-right-radius: .25rem;
border-top-right-radius: .25rem;
border-color: var(--color-purple-300);
background-color: var(--color-purple-200);
cursor: pointer;
}
.genesis-pro-form p {
margin-left: 1rem;
margin-right: 1rem;
margin-bottom: 0;
margin-top: 0;
font-weight: 500;
}
.pro-submission-message {
font-style: italic;
color: var(--color-purple-600);
margin-top: 0.5rem;
}
.pro-box {
background-color: var(--color-gray-800);
color: var(--color-gray-200);
padding: 2.5rem 2.5rem 1rem 2.5rem;
margin-top: 2.5rem;
border-radius: .25rem;
box-shadow: var(--box-shadow);
margin-bottom: 2rem;;
}
.pro-box h3 {
color: var(--color-gray-200);
}
.pro-box-tiles {
display: flex;
margin-top: 1rem;
margin-left: -.5rem;
margin-right: -.5rem;
}
.pro-box-tile {
background-color: var(--color-gray-900);
padding: 2rem;
border-radius: .25rem;
margin-left: .5rem;
margin-right: .5rem;
width: 0;
flex-grow: 1;
}
.pro-box-tile__icon {
display: flex;
align-items: center;
justify-content: center;
border-radius: .25rem;
background-color: var(--color-purple-700);
height: 3rem;
width: 3rem;
margin-bottom: .75rem;
}
.pro-box-tile__icon svg {
width: 2rem;
height: 2rem;
color: var(--color-purple-300);
fill: currentColor;
}
.bl-migration__error {
background-color: var(--color-purple-100);
padding: 0.1rem 1rem;
margin-bottom: 1rem;
}
.bl-migration__error p:first-child {
color: var(--color-purple-700);
font-weight: 500;
}
.message-future {
font-size: 12px;
}
/*
* Copied from Gutenberg.
* https://github.com/WordPress/gutenberg/blob/eb856ef0892d6b9d15f39483ac2f45673d68f2d4/packages/components/src/spinner/style.scss
*/
.components-spinner {
display: inline-block;
background-color: #7e8993;
width: 18px;
height: 18px;
opacity: 0.7;
margin: 5px 11px 0;
border-radius: 100%;
position: relative;
}
.components-spinner::before {
content: "";
position: absolute;
background-color: #fff;
top: 3px;
left: 3px;
width: 4px;
height: 4px;
border-radius: 100%;
transform-origin: 6px 6px;
animation: components-spinner__animation 1s infinite linear;
}
@keyframes components-spinner__animation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (max-width: 1000px) {
.step .pro-box-tiles {
flex-direction: column;
}
.step .pro-box-tile {
width: auto;
}
}
.block-lab-notice {
border-left: 4px solid #7D5DEC;
padding: 10px 20px;
background: #fff;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
margin: 5px 0 15px;
}
.block-lab-notice h2 {
margin: 0.5em 0 1em;
}
.block-lab-notice p {
font-size: 1em;
}
.block-lab-notice .intro {
font-size: 1.2em;
line-height: 1.5em;
}
.block-lab-notice p.ps {
font-size: smaller;
}
.block-lab-notice .button {
display: inline-block;
position: relative;
background-color: #7D5DEC;
padding: 10px 12px;
margin: 1em 0;
height: auto;
border-radius: 4px;
border: 2px solid #7D5DEC;
color: #fff;
font-size: 13px;
text-decoration: none;
line-height: 13px;
cursor: pointer;
box-shadow: none;
}
.block-lab-notice .button:active,
.block-lab-notice .button:focus,
.block-lab-notice .button:visited {
color: #fff;
}
.block-lab-notice .button:hover {
color: #fff;
background-color: #6444d3;
border-color: #6444d3;
}
.block-lab-notice .button:nth-of-type( 2 ) {
margin-left: 10px;
}
.block-lab-notice .button--white {
background-color: #fff;
border-color: #fff;
color: #7D5DEC;
}
.block-lab-notice .button--white:active,
.block-lab-notice .button--white:focus,
.block-lab-notice .button--white:visited {
color: #7D5DEC;
}
.block-lab-notice .button--white:hover {
color: #7D5DEC;
background-color: #f4f4f4;
border-color: #f4f4f4;
}
.block-lab-notice .button_cta {
font-weight: 600;
padding: 16px 15px 15px;
box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
}
.block-lab-welcome {
background-color: #7D5DEC;
background-image: url('https://getblocklab.com/wp-content/uploads/2019/02/Block-Lab-Pro-Hero-Background-1.svg');
background-size: cover;
background-position: center;
color: #fff;
padding: 32px 32px 6px;
border: none;
box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
border-radius: 3px;
}
.block-lab-welcome h2 {
color: #fff;
line-height: 1em;
font-size: 2em;
font-weight: 700;
margin-bottom: 0.5em;
margin-top: 0;
font-style: italic;
}
.block-lab-welcome p {
font-size: 1.1em;
}
.block-lab-welcome .intro {
font-size: 1.3em;
}
.block-lab-notice p.ps {
opacity: 0.5;
}
.block-lab-welcome .notice-dismiss:before {
color: #372182;
}
.block-lab-welcome .notice-dismiss:hover:before {
color: #f4f4f4;
}
.block-lab-edit-block a.trash {
color: #444;
}
.block-lab-edit-block a.trash:hover {
color: #dc3232;
}
.block-lab-add-fields {
position: relative;
}
.block-lab-add-fields h2 {
font-size: 1.3em !important;
padding: 0 !important;
margin: 0.5em 0 1em !important;
font-weight: 600 !important;
}
.block-lab-publish {
margin: 1em 0 0;
}
.block-lab-publish h2 {
font-size: 1.3em !important;
padding: 0 !important;
margin: 0.5em 0 1em !important;
}
@keyframes block-lab-edit-block-point {
from {
transform: rotate(230deg) translateX(0px) translateY(0px);
}
to {
transform: rotate(220deg) translateX(10px) translateY(-40px);
}
}
@keyframes block-lab-add-fields-point {
from {
transform: rotate(30deg) translateX(0px) translateY(0px);
}
to {
transform: rotate(40deg) translateX(40px) translateY(20px);
}
}
.block-lab-settings .nav-tab-wrapper .dashicons-before:before {
padding: 2px 3px 0 0;
}
\ No newline at end of file
.block-lab-pro {
padding: 20px 20px 0 0;
margin: 0 auto;
max-width: 1280px;
}
.block-lab-pro .container {
margin-top: 20px;
display:grid;
grid-template-columns: repeat( 6, 1fr );
grid-gap: 20px;
}
.block-lab-pro .tile {
box-shadow: 10px 10px 20px 0px rgba( 121, 110, 255, 0.05 );
border-radius: 3px;
background-color: #fff;
}
.block-lab-pro .tile p {
font-size: 16px;
}
.block-lab-pro .tile_header {
padding: 40px 40px 0;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.block-lab-pro .tile_header span {
font-size: 16px;
line-height: 16px;
}
.block-lab-pro .tile_body {
padding: 0 40px 20px;
}
.block-lab-pro .tile_body h4 {
font-size: 24px;
line-height: 24px;
}
.block-lab-pro .tile_footer {
border-top: 2px solid #F7F9FC;
padding: 30px 40px 30px;
display: flex;
justify-content: space-between;
align-items: center;
}
.block-lab-pro .tile_footer.tile_footer_email {
background-color: #F7F9FC;
}
.block-lab-pro .tile_icon {
width: 180px;
display: block;
margin: auto;
max-width: 100%;
}
.block-lab-pro .tile_icon_wrapper {
width: 180px;
height: 180px;
display: block;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
margin: auto;
}
.block-lab-pro .button {
display: inline-block;
position: relative;
background-color: #5c34e8;
padding: 10px 12px;
height: auto;
border-radius: 4px;
border: 2px solid #5c34e8;
color: #fff;
font-size: 13px;
text-decoration: none;
line-height: 13px;
cursor: pointer;
box-shadow: none;
}
.block-lab-pro .button:active,
.block-lab-pro .button:focus,
.block-lab-pro .button:visited {
color: #fff;
background-color: #5c34e8;
}
.block-lab-pro .button:hover {
color: #fff;
background-color: #5c34e8;
border-color: #5c34e8;
opacity: 0.9;
}
.block-lab-pro .button:nth-of-type( 2 ) {
margin-left: 10px;
}
.block-lab-pro .button--secondary {
background-color: #ff237e;
border-color: #ff237e;
color: #fff;
}
.block-lab-pro .button--secondary:active,
.block-lab-pro .button--secondary:focus,
.block-lab-pro .button--secondary:visited {
color: #fff;
background-color: #ff237e;
}
.block-lab-pro .button--secondary:hover {
background-color: #ff237e;
border-color: #ff237e;
opacity: 0.9;
}
.block-lab-pro .button--secondary-stroke {
border-color: #fff;
background-color: transparent;
}
.block-lab-pro .button--secondary-stroke:active,
.block-lab-pro .button--secondary-stroke:focus,
.block-lab-pro .button--secondary-stroke:visited {
color: #fff;
}
.block-lab-pro .button--secondary-stroke:hover {
color: #5c34e8;
background-color: #fff;
border-color: #fff;
}
.block-lab-pro .button_close {
background-color: rgba( 0, 0, 0, 0.1 );
border-radius: 50%;
line-height: 1.6em;
display: flex;
justify-content: center;
border: none;
color: red;
font-weight: 700;
}
.block-lab-pro .section_heading {
grid-column-start: 1;
grid-column-end: 7;
}
.block-lab-pro .dashboard_welcome {
grid-column-start: 1;
grid-column-end: 7;
background-color: #fff;
background-image: url('http://getblocklab.com/wp-content/uploads/2019/08/block_lab_hero_bg.svg');
background-size: cover;
background-position: top;
color: #000;
padding: 32px;
}
.block-lab-pro .dashboard_welcome .notice p {
color: #444;
font-size: 13px;
margin: 0.5em 0;
padding: 2px;
}
.block-lab-pro .dashboard_welcome h1 {
color: #000;
font-weight: 700;
font-size: 52px;
line-height: 52px;
margin-top: 10px;
text-shadow: 0 0 10px rgba(255,255,255,0.6);
}
.block-lab-pro .dashboard_welcome h1 .pro-pill {
font-size: .6em;
line-height: 1.4;
color: #fff;
background-color: #5c34e8;
display: inline-block;
border-radius: 8px;
padding: 0 11px;
top: -5px;
position: relative;
box-shadow: 0 0 10px rgba(255,255,255,0.6);
text-shadow: none;
}
.block-lab-pro .dashboard_welcome .tile_body {
display: flex;
justify-content: left;
align-items: center;
}
.block-lab-pro .dashboard_welcome p.description {
font-size: 18px;
margin-bottom: 30px;
max-width: 480px;
color: #000;
padding-top: 12px;
text-shadow: 0 0 4px #fff;
font-style: normal;
}
/* Tile sizing */
.block-lab-pro .tile_2 {
grid-column: span 2;
}
.block-lab-pro .tile_3 {
grid-column: span 3;
}
.block-lab-pro .tile_4 {
grid-column: span 4;
}
.block-lab-pro .tile_5 {
grid-column: span 5;
}
.block-lab-pro .tile_6 {
grid-column: span 6;
}
.block-lab-pro .ul_pills {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}
.block-lab-pro .ul_pills li {
display: block;
padding: 3px 6px;
margin-right: 16px;
margin-bottom: 16px;
border-radius: 3px;
box-shadow: 0 0 0 4px rgba( 121, 110, 255, 0.1 );
font-weight: 600;
}
.block-lab-pro .align_center {
text-align: center;
}
/* Mailchimp Styling */
#mc_embed_signup {
width: 100%;
}
#mc_embed_signup #mc_embed_signup_scroll {
display: flex;
}
.block-lab-pro input[type=email].input {
background-color: #fff;
border: 1px solid rgba( 0, 0, 0, 0.1 );
border-radius: 3px;
padding: 9px 10px;
width: 100%;
}
.block-lab-pro label.input_label {
display: none;
}
.mc-field-group {
flex-grow: 1;
padding-right: 10px
}
.block-lab-pro .cta_license_form_wrapper {
display: flex;
align-items: center;
}
.block-lab-pro .button_cta {
padding: 16px 15px 15px;
box-shadow: 10px 10px 20px 0 rgba( 0, 0, 0, 0.2 );
}
.block-lab-pro .license_key_form {
display: flex;
background-color: rgba( 0, 0, 0, 0.06 );
border-radius: 3px;
padding: 4px;
}
.block-lab-pro .license_key_text {
font-size: 14px !important;
font-style: italic !important;
margin-bottom: 14px !important;
margin-right: 10px;
margin-left: 10px;
}
.block-lab-pro input[type="text"].input_text {
border-radius: 3px;
padding: 11px 10px 10px;
width: 100%;
box-shadow: none;
border: none;
background-color: rgba( 255,255,255,1 );
margin-right: 4px;
}
@media ( max-width: 1200px ) {
.block-lab-pro .cta_license_form_wrapper {
display: block;
}
}
@media ( max-width: 720px ) {
.block-lab-pro .container {
display: block;
}
.block-lab-pro .tile {
margin-bottom: 20px;
}
.block-lab-pro .dashboard_welcome {
display: block;
}
.block-lab-pro .tile_body {
padding-top: 40px;
}
}
<?php return array('dependencies' => array(), 'version' => '734085aee55b820c6613aebadcd335c2');
\ No newline at end of file
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}
/**
* Colors
*
* The variables below are taken from Gutenberg's styling in _colors.scss.
*
* @see https://github.com/WordPress/gutenberg/blob/master/assets/stylesheets/_colors.scss
*/
// Hugo's new WordPress shades of gray, from http://codepen.io/hugobaeta/pen/grJjVp.
$black: #000;
$dark-gray-900: #191e23;
$dark-gray-800: #23282d;
$dark-gray-700: #32373c;
$dark-gray-600: #40464d;
$dark-gray-500: #555d66; // Use this most of the time for dark items.
$dark-gray-400: #606a73;
$dark-gray-300: #6c7781; // Lightest gray that can be used for AA text contrast.
$dark-gray-200: #7e8993;
$dark-gray-150: #8d96a0; // Lightest gray that can be used for AA non-text contrast.
$dark-gray-100: #8f98a1;
$light-gray-900: #a2aab2;
$light-gray-800: #b5bcc2;
$light-gray-700: #ccd0d4;
$light-gray-600: #d7dade;
$light-gray-500: #e2e4e7; // Good for "grayed" items and borders.
$light-gray-400: #e8eaeb; // Good for "readonly" input fields and special text selection.
$light-gray-300: #edeff0;
$light-gray-200: #f3f4f5;
$light-gray-100: #f8f9f9;
$white: #fff;
// Dark opacities, for use with light themes.
$dark-opacity-900: rgba(#000510, 0.9);
$dark-opacity-800: rgba(#00000a, 0.85);
$dark-opacity-700: rgba(#06060b, 0.8);
$dark-opacity-600: rgba(#000913, 0.75);
$dark-opacity-500: rgba(#0a1829, 0.7);
$dark-opacity-400: rgba(#0a1829, 0.65);
$dark-opacity-300: rgba(#0e1c2e, 0.62);
$dark-opacity-200: rgba(#162435, 0.55);
$dark-opacity-100: rgba(#223443, 0.5);
$dark-opacity-light-900: rgba(#304455, 0.45);
$dark-opacity-light-800: rgba(#425863, 0.4);
$dark-opacity-light-700: rgba(#667886, 0.35);
$dark-opacity-light-600: rgba(#7b86a2, 0.3);
$dark-opacity-light-500: rgba(#9197a2, 0.25);
$dark-opacity-light-400: rgba(#95959c, 0.2);
$dark-opacity-light-300: rgba(#829493, 0.15);
$dark-opacity-light-200: rgba(#8b8b96, 0.1);
$dark-opacity-light-100: rgba(#747474, 0.05);
$dark-opacity-background-fill: rgba($dark-gray-700, 0.7); // Similar to $dark-opacity-light-200, but more opaque.
// Light opacities, for use with dark themes.
$light-opacity-900: rgba($white, 1);
$light-opacity-800: rgba($white, 0.9);
$light-opacity-700: rgba($white, 0.85);
$light-opacity-600: rgba($white, 0.8);
$light-opacity-500: rgba($white, 0.75);
$light-opacity-400: rgba($white, 0.7);
$light-opacity-300: rgba($white, 0.65);
$light-opacity-200: rgba($white, 0.6);
$light-opacity-100: rgba($white, 0.55);
$light-opacity-light-900: rgba($white, 0.5);
$light-opacity-light-800: rgba($white, 0.45);
$light-opacity-light-700: rgba($white, 0.4);
$light-opacity-light-600: rgba($white, 0.35);
$light-opacity-light-500: rgba($white, 0.3);
$light-opacity-light-400: rgba($white, 0.25);
$light-opacity-light-300: rgba($white, 0.2);
$light-opacity-light-200: rgba($white, 0.15);
$light-opacity-light-100: rgba($white, 0.1);
$light-opacity-background-fill: rgba($light-gray-300, 0.8); // Similar to $light-opacity-light-200, but more opaque.
// Additional colors.
// Some are from https://make.wordpress.org/design/handbook/foundations/colors/.
$blue-wordpress-700: #00669b;
$blue-dark-900: #0071a1;
$blue-medium-900: #006589;
$blue-medium-800: #00739c;
$blue-medium-700: #007fac;
$blue-medium-600: #008dbe;
$blue-medium-500: #00a0d2;
$blue-medium-400: #33b3db;
$blue-medium-300: #66c6e4;
$blue-medium-200: #bfe7f3;
$blue-medium-100: #e5f5fa;
$blue-medium-highlight: #b3e7fe;
$blue-medium-focus: #007cba;
// Alert colors.
$alert-yellow: #f0b849;
$alert-red: #d94f4f;
$alert-green: #4ab866;
\ No newline at end of file
@import 'color';
$input-padding: 8px;
$input-width: 300px;
$default-font-size: 13px;
$break-small: 600px;
$font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
@mixin break-small() {
@media (min-width: #{ ($break-small) }) {
@content;
}
}
/* Menu items. */
@mixin menu-style__neutral() {
border: none;
box-shadow: none;
}
/* Block form in editor & sidebar */
div[class^="wp-block-block-lab-"],
.edit-post-settings-sidebar__panel-block .components-panel__body {
:required:invalid {
border-color: #C00000;
}
.text-control__error {
border-color: #d94f4f;
box-shadow: 0 0 0 1px #d94f4f;
}
/* Color Control Component */
.block-lab-color-control {
.components-base-control {
display: inline-block;
margin-bottom: 0 !important;
.components-base-control__field {
margin: 0 !important;
width: 100%;
height: 100%;
}
&.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;
&:hover {
transform: scale(1.2)
}
.component-color-indicator {
width: 100%;
height: 100%;
margin: 0;
border: none;
border-radius: 50%;
}
}
}
}
/* Media Upload Component */
.block-lab-media-controls {
.bl-image__img {
max-height: 200px;
display: block;
margin-bottom: 8px;
}
.components-placeholder {
position: relative;
}
.bl-image__placeholder .components-button.is-button {
white-space: normal;
}
.components-form-file-upload,
.components-media-library-button,
.bl-image__remove {
margin: 0 4px 4px 0;
display: inline-block;
vertical-align: top;
}
.components-base-control__field {
.components-base-control__help {
margin-bottom: 4px;
margin-top: 0;
}
}
}
}
/* Block form in editor */
div[class^="wp-block-block-lab-"] {
margin: 0;
.block-form {
border-left: none;
background: $dark-opacity-light-200;
padding: 22px 0 22px 22px;
font-size: 0.8125rem;
display: flex;
flex-wrap: wrap;
h3 {
color: #111;
font-size: 1rem;
margin: 0;
flex: 1 1 100%;
svg {
position: relative;
top: 6px;
margin-right: 4px;
}
}
p {
font-size: 1rem;
font-family: $font-family;
}
.block-lab-control {
flex-basis: 100%;
padding-right: 22px;
&.width-25 {
flex-basis: 25%;
}
&.width-50 {
flex-basis: 50%;
}
&.width-75 {
flex-basis: 75%;
}
}
@media screen and (max-width: 782px) {
.block-lab-control.width-25,
.block-lab-control.width-50,
.block-lab-control.width-75 {
flex-basis: 100%;
}
}
.components-base-control {
font-family: $font-family;
}
.components-base-control__field {
margin: 1em 0 0;
.components-base-control__label {
display: block;
font-weight: 600;
}
}
.components-base-control__help {
margin: 0 0 1em 0.5em;
font-size: 1em;
color: rgba(0, 0, 0, 0.8);
}
/* Override a max-width: 25rem style rule in Core that can prevent displaying at the selected with, like 75% */
.components-select-control__input {
max-width: unset;
}
}
/* Rich Text Control Component */
.block-lab-rich-text-control {
.components-base-control__field {
margin-top: 20px;
}
.input-control {
background: #fff;
min-height: 7em;
line-height: 1.4rem;
font-size: 13px;
p {
font-size: 13px;
margin-top: 0.67rem;
margin-bottom: 0.67rem;
}
p:first-child,
p:first-child > p {
margin-top: 0;
}
}
/* Override native styling that created a double border */
.editor-rich-text__inline-toolbar .components-toolbar > .components-toolbar {
border: none;
border-right: 1px solid $light-gray-500;
}
}
/* Classic Text Control Component */
.block-lab-classic-text-control {
.classic-text__toolbar {
position: relative;
z-index: 10;
top: 1px;
> .mce-container {
width: 100% !important;
border: 1px solid #c5c5c5;
box-shadow: none;
box-sizing: border-box;
}
.mce-container-body {
width: 100% !important;
> .mce-container {
width: 100% !important;
}
}
}
/* The editing area, not the toolbar. */
.classic-text__edit {
background: $white;
padding: 0.2rem 1rem;
border: 1px solid $dark-gray-150;
outline: none;
min-height: 6rem;
/* Clear the float in case an image is floated. */
&:after {
content: "";
display: table;
clear: both;
}
p {
font-size: 13px;
margin-top: 0.67rem;
margin-bottom: 0.67rem;
}
ul, ol {
margin-left: 1rem;
}
}
}
.block-lab-repeater {
.block-lab-repeater__rows {
width: 100%;
.block-lab-repeater--row {
min-width: 100%;
border: 1px dashed $dark-gray-150;
border-top: 0;
margin-bottom: 0;
padding: 10px 14px 0;
position: relative;
transition: background 1s ease-in-out;
&:first-child {
border-top: 1px dashed $dark-gray-150;
.block-lab-repeater--row-actions .button-move-up {
display: none;
}
}
&:last-child {
.block-lab-repeater--row-actions .button-move-down {
display: none;
}
}
.components-base-control__field {
margin: 0 0 14px;
}
.block-lab-repeater--row-actions {
position: absolute;
display: none;
top: -1px;
left: -30px;
.components-icon-button {
width: 28px;
padding: 0;
background: $white;
border-color: $dark-gray-150;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
svg {
width: 100%;
}
}
}
.block-lab-repeater--row-delete {
display: none;
position: absolute;
right: 2px;
top: 4px;
.components-icon-button {
width: 28px;
padding: 0;
border-color: transparent;
box-shadow: none;
background: transparent;
color: $dark-gray-150;
svg {
width: 100%;
}
&:hover:not(:disabled) {
color: $alert-red;
}
&:active:not(:disabled) {
color: $dark-gray-600;
}
&:disabled {
opacity: 0;
}
}
}
&.row-to {
transition: background 0.2s ease-in-out;
background: #fef8ee;
}
&.row-from {
transition: none;
background: transparent;
}
&:hover:not(.row-from),
&.row-to {
.block-lab-repeater--row-actions {
display: block;
background: #fff;
box-sizing: border-box;
border: 1px solid $dark-gray-150;
border-radius: 3px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
.components-icon-button {
border-color: transparent;
box-shadow: none;
background: transparent;
&:hover {
background-color: $dark-opacity-light-200;
}
}
}
.block-lab-repeater--row-delete {
display: block;
}
}
&:before {
content: '';
width: 29px;
height: 100%;
position: absolute;
left: -30px;
top: 0;
}
}
}
.block-lab-repeater--row-add {
margin-top: 4px;
display: flex;
justify-content: center;
.components-icon-button:hover:not(:disabled) {
border-color: transparent;
box-shadow: none;
background: transparent;
color: $blue-medium-focus;
}
.components-icon-button:active:disabled {
color: #555d66;
}
}
}
}
/* Block form in sidebar */
.edit-post-settings-sidebar__panel-block .components-panel__body {
/* Media Upload Component */
.block-lab-media-controls {
.components-spinner {
float: none;
}
}
.block-lab-color-control .components-base-control__label {
display: block;
}
/* The FetchInput component */
.bl-fetch--input input[type="text"] {
width: 100%;
}
}
/* Miscellaneous global styles */
.edit-post-layout {
.components-popover:not(.is-mobile):not(.bl-fetch__popover) .components-popover__content .components-color-picker {
min-width: 340px;
}
}
/*
* FetchInput styling forked from URLInput styling in Gutenberg, with different class names.
* This uses a Popover, which often appears outside of its container.
* So this doesn't work inside the "Block form in editor" section above.
* @see https://github.com/WordPress/gutenberg/blob/fd2468d6c486220f7f1fa5bfa2366c510b46af27/packages/editor/src/components/url-input/style.scss#L42
*/
.bl-fetch__input {
width: 100%;
}
.bl-fetch__popover.editor:not(.is-mobile) .components-popover__content {
width: $input-width;
}
.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;
}
// Hide suggestions on mobile until we @todo find a better way to show them.
.bl-fetch-input__suggestions,
.bl-fetch-input .components-spinner {
display: none;
@include break-small() {
display: inherit;
}
}
.bl-fetch-input {
> .components-base-control__field {
display: flex;
flex-wrap: wrap;
> .components-base-control__label {
flex: 3 3 100%;
}
> .bl-fetch__input {
flex: 1 1 0;
}
> .components-spinner {
flex: 0 0 auto;
}
}
}
.bl-fetch-input__suggestion {
padding: 4px $input-padding;
color: $dark-gray-300; // Lightest we can use for contrast.
display: block;
font-size: $default-font-size;
cursor: pointer;
background: $white;
width: 100%;
border: none;
text-align: left;
@include menu-style__neutral();
&:hover {
background: $light-gray-500;
}
&:focus,
&.is-selected {
background: rgb(0, 113, 158);
color: $white;
outline: none;
}
}
.bl-dot-tip.read-more {
float: left;
margin: 0;
padding-right: 1em;
}
// In the <ServerSideRender> display, correct an issue where <ul> and <ol> can appear outside (to the left) of their container.
.editor-styles-wrapper .block-lab-editor__ssr {
ul, ol {
margin-left: 1rem;
}
}
/**
* Used for editing Blocks.
*
* @copyright Copyright(c) 2020, Block Lab
* @license GPL-2.0-only
*/
/* global blockLab, jQuery */
( function( $ ) {
$( function() {
blockTitleInit();
blockIconInit();
blockFieldInit();
blockPostTypesInit();
$( '#block-add-field' ).on( 'click', function() {
const template = wp.template( 'field-repeater' ),
data = { uid: new Date().getTime() },
row = $( template( data ) ),
edit = row.find( '.block-fields-actions-edit' ),
label = row.find( '.block-fields-edit-label input' );
incrementRow( row );
$( '.block-fields-rows' ).append( row );
$( '.block-no-fields' ).hide();
$( '.block-lab-add-fields' ).hide();
edit.trigger( 'click' );
label.data( 'defaultValue', label.val() );
label.trigger( 'change' );
label.select();
} );
$( '#block_fields' ).on( 'click', '#block-add-sub-field', function() {
const template = wp.template( 'field-repeater' ),
data = { uid: new Date().getTime() },
row = $( template( data ) ),
parent = $( this ).closest( '.block-fields-row' ),
edit = row.find( '.block-fields-actions-edit' ),
label = row.find( '.block-fields-edit-label input' );
incrementRow( row );
// Prevents adding a repeater, in a repeater, in a repeater…
row.find( '.block-fields-edit-control option[value="repeater"]' ).remove();
// Don't render the location or width settings for sub-fields.
row.find( '.block-fields-edit-location-settings' ).remove();
row.find( '.block-fields-edit-width-settings' ).remove();
// Add parent UID as a hidden input
const parentInput = $( '<input>' ).attr( {
type: 'hidden',
name: 'block-fields-parent[' + data.uid + ']',
value: parent.data( 'uid' ),
} );
row.append( parentInput );
$( '.block-fields-sub-rows', parent ).append( row );
$( '.repeater-no-fields', parent ).hide();
$( '.repeater-has-fields', parent ).show();
edit.trigger( 'click' );
label.data( 'defaultValue', label.val() );
label.trigger( 'change' );
label.select();
} );
$( '.block-lab-pub-section .edit-post-types' ).on( 'click', function() {
const excludedPostTypes = $( '#block-excluded-post-types' ).val().split( ',' ).filter( Boolean );
$( '.post-types-select-items input' ).prop( 'checked', true );
for ( const postType of excludedPostTypes ) {
$( '.post-types-select-items input[value="' + postType + '"]' ).prop( 'checked', false );
}
$( '.block-lab-pub-section .post-types-select' ).slideDown( 200 );
$( this ).hide();
} );
$( '.block-lab-pub-section .save-post-types' ).on( 'click', function() {
const checked = $( '.post-types-select-items input:not(:checked)' ),
postTypes = [];
for ( const input of checked ) {
postTypes.push( $( input ).val() );
}
$( '#block-excluded-post-types' ).val( postTypes.join( ',' ) );
blockPostTypesInit();
$( '.block-lab-pub-section .post-types-select' ).slideUp( 200 );
$( '.block-lab-pub-section .edit-post-types' ).show();
} );
$( '.block-lab-pub-section .button-cancel' ).on( 'click', function() {
$( '.block-lab-pub-section .post-types-select' ).slideUp( 200 );
$( '.block-lab-pub-section .edit-post-types' ).show();
} );
$( '#block_properties .block-properties-icon-select span' ).on( 'click', function() {
const svg = $( 'svg', this ).clone();
$( '#block_properties .block-properties-icon-select span.selected' ).removeClass( 'selected' );
$( this ).addClass( 'selected' );
$( '#block-properties-icon' ).val( $( this ).data( 'value' ) );
$( '#block-properties-icon-current' ).html( svg );
} );
$( '#block_properties .block-properties-category' ).on( 'change', function() {
if ( '__custom' === $( this ).val() ) {
$( this ).next( '.block-properties-category-custom' ).css( 'display', 'block' );
} else {
$( this ).next( '.block-properties-category-custom' ).hide();
}
} );
$( '#block_template .template-location a.filename' ).on( 'click', function( event ) {
event.preventDefault();
const copy = $( '#block_template .template-location .click-to-copy' ),
input = $( 'input', copy ),
width = $( this ).width() + input.outerWidth( false ) - input.width();
copy.show();
input.outerWidth( width ).focus().select();
const copied = document.execCommand( 'copy' );
if ( copied ) {
copy.attr( 'data-tooltip', blockLab.copySuccessMessage );
} else {
copy.attr( 'data-tooltip', blockLab.copyFailMessage );
}
$( this ).hide();
} );
$( '#block_template .template-location .click-to-copy input' ).on( 'blur', function() {
$( '#block_template .template-location a.filename' ).show();
$( this ).parent().hide();
} );
$( '.block-fields-rows' )
.on( 'click', '.block-fields-actions-delete', function() {
const subRows = $( this ).closest( '.block-fields-sub-rows' );
$( this ).closest( '.block-fields-row' ).remove();
if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) {
$( '.block-no-fields' ).show();
}
if ( 0 !== subRows.length && 0 === $( '.block-fields-row', subRows ).length ) {
subRows.parent().find( '.repeater-no-fields' ).show();
subRows.parent().find( '.repeater-has-fields' ).hide();
}
} )
.on( 'click', '.block-fields-actions-duplicate', function() {
const row = $( this ).closest( '.block-fields-row' ),
newRow = cloneRow( row );
// Expand the duplicated row.
if ( newRow.hasClass( 'block-fields-row-active' ) ) {
row.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' );
} else {
newRow.find( '.block-fields-actions-edit' ).eq( 0 ).trigger( 'click' );
}
// Select the label field of the new row.
const label = newRow.find( '.block-fields-edit-label input' );
label.trigger( 'change' );
label.select();
} )
.on( 'click', '.block-fields-actions-edit, a.row-title', function() {
const currentRow = $( this ).closest( '.block-fields-row' );
// If we're expanding this row, first collapse all other rows and scroll this row into view.
if ( ! currentRow.hasClass( 'block-fields-row-active' ) ) {
const editRow = $( '.block-fields-rows .block-fields-edit' );
scrollRowIntoView( currentRow );
editRow.slideUp();
$( '.block-fields-rows .block-fields-row-active' ).removeClass( 'block-fields-row-active' );
}
currentRow.toggleClass( 'block-fields-row-active' );
currentRow.find( '.block-fields-edit' ).first().slideToggle();
// Fetch field settings if field is active and there are no settings.
if ( $( this ).closest( '.block-fields-row' ).hasClass( 'block-fields-row-active' ) ) {
const fieldRow = $( this ).closest( '.block-fields-row' );
if ( 0 === fieldRow.find( '.block-fields-edit-settings' ).length ) {
const fieldControl = fieldRow.find( '.block-fields-edit-control select' ).val();
fetchFieldSettings( fieldRow, fieldControl );
}
}
} )
.on( 'click', '.block-fields-edit-actions-close a.button', function() {
const fieldRow = $( this ).closest( '.block-fields-row' );
fieldRow.removeClass( 'block-fields-row-active' );
$( '.block-fields-edit', fieldRow ).slideUp();
} )
.on( 'change keyup', '.block-fields-edit input', function() {
const sync = $( this ).data( 'sync' );
$( '#' + sync ).text( $( this ).val() );
} )
.on( 'change keyup', '.block-fields-edit select', function() {
const sync = $( this ).data( 'sync' ),
option = $( 'option:selected', $( this ) ).text();
$( '#' + sync ).text( option );
} )
.on( 'change', '.block-fields-edit-control select', function() {
const fieldRow = $( this ).closest( '.block-fields-row' );
fetchFieldSettings( fieldRow, $( this ).val() );
if ( 'repeater' === $( this ).val() ) {
const subRows = wp.template( 'sub-field-rows' );
fieldRow.append( subRows );
blockFieldSubRowsInit( $( '.block-fields-sub-rows', fieldRow ) );
} else {
$( '.block-fields-sub-rows,.block-fields-sub-rows-actions', fieldRow ).remove();
}
} )
.on( 'change', '.block-fields-edit-location-settings select', function() {
blockFieldWidthInit( $( this ).closest( '.block-fields-row' ) );
} )
.on( 'change keyup', '.block-fields-edit-label input', function() {
const slug = $( this )
.closest( '.block-fields-edit' )
.find( '.block-fields-edit-name input' );
if ( 'false' !== slug.data( 'autoslug' ) ) {
slug
.val( slugify( $( this ).val() ) )
.trigger( 'change' );
}
} )
.on( 'blur', '.block-fields-edit-label input', function() {
// If the value hasn't changed from default, don't turn off autoslug.
if ( $( this ).data( 'defaultValue' ) === $( this ).val() ) {
return;
}
$( this )
.closest( '.block-fields-edit' )
.find( '.block-fields-edit-name input' )
.data( 'autoslug', 'false' );
} )
.on( 'mouseenter', '.block-fields-row div:not(.block-fields-edit,.block-fields-sub-rows,.block-fields-sub-rows-actions)', function() {
$( this ).parent().addClass( 'hover' );
} )
.on( 'mouseleave', '.block-fields-row div', function() {
$( this ).parent().removeClass( 'hover' );
} )
.sortable( {
axis: 'y',
cursor: 'grabbing',
handle: '> .block-fields-row-columns .block-fields-sort-handle',
containment: 'parent',
tolerance: 'pointer',
} );
} );
const blockTitleInit = function() {
const title = $( '#title' ),
slug = $( '#block-properties-slug' );
// If this is a new block, then enable auto-generated slugs.
if ( '' === title.val() && '' === slug.val() ) {
// If auto-generated slugs are enabled, set the slug based on the title.
title.on( 'change keyup', function() {
if ( 'false' !== slug.data( 'autoslug' ) ) {
slug.val( slugify( title.val() ) );
}
} );
// Turn auto-generated slugs off once a title has been set.
title.on( 'blur', function() {
if ( '' !== title.val() ) {
slug.data( 'autoslug', 'false' );
}
} );
}
};
const blockIconInit = function() {
const iconsContainer = $( '.block-properties-icon-select' ),
selectedIcon = $( '.selected', iconsContainer );
if ( 0 !== iconsContainer.length && 0 !== selectedIcon.length ) {
iconsContainer.scrollTop( selectedIcon.position().top );
}
};
const blockFieldInit = function() {
if ( 0 === $( '.block-fields-rows' ).children( '.block-fields-row' ).length ) {
$( '.block-no-fields' ).show();
}
$( '.block-fields-edit-name input' ).data( 'autoslug', 'false' );
$( '.block-fields-sub-rows' ).each( function() {
blockFieldSubRowsInit( $( this ) );
} );
};
const blockFieldSubRowsInit = function( subRows ) {
subRows.sortable( {
axis: 'y',
cursor: 'grabbing',
handle: '> .block-fields-row-columns .block-fields-sort-handle',
containment: 'parent',
tolerance: 'pointer',
} );
};
const blockFieldWidthInit = function( fieldRow ) {
const widthSettings = fieldRow.find( '.block-fields-edit-width-settings' ),
locationSettings = fieldRow.find( '.block-fields-edit-location-settings' );
if ( 'editor' !== $( 'select', locationSettings ).val() ) {
widthSettings.hide();
} else {
widthSettings.show();
}
};
const blockPostTypesInit = function() {
if ( 0 === $( '.block-lab-pub-section' ).length ) {
return;
}
const display = $( '.post-types-display' );
const excludedPostTypes = $( '#block-excluded-post-types' )
.val()
.split( ',' )
.filter( Boolean );
if ( 0 === excludedPostTypes.length ) {
display.text( blockLab.postTypes.all );
return;
}
const inputs = $( '.post-types-select-items input' );
if ( excludedPostTypes.length === inputs.length ) {
display.text( blockLab.postTypes.none );
return;
}
const displayList = [];
for ( const input of inputs ) {
const postType = $( input ).val();
if ( -1 === excludedPostTypes.indexOf( postType ) ) {
displayList.push(
$( input ).next( 'label' ).text()
);
}
}
display.text( displayList.join( ', ' ) );
};
const fetchFieldSettings = function( fieldRow, fieldControl ) {
if ( ! blockLab.hasOwnProperty( 'fieldSettingsNonce' ) ) {
return;
}
const loadingRow = '' +
'<tr class="block-fields-edit-loading">' +
' <td class="spacer"></td>' +
' <th></th>' +
' <td><span class="loading"></span></td>' +
'</tr>';
$( '.block-fields-edit-settings', fieldRow ).remove();
$( '.block-fields-edit-control', fieldRow ).after( $( loadingRow ) );
const data = {
control: fieldControl,
uid: fieldRow.data( 'uid' ),
nonce: blockLab.fieldSettingsNonce,
};
// If this is a sub-field, pass along the parent UID as well.
if ( fieldRow.parent( '.block-fields-sub-rows' ).length > 0 ) {
data.parent = fieldRow.closest( '.block-fields-row' ).data( 'uid' );
}
wp.ajax.send( 'fetch_field_settings', {
success( result ) {
$( '.block-fields-edit-loading', fieldRow ).remove();
if ( ! result.hasOwnProperty( 'html' ) ) {
return;
}
const settingsRows = $( result.html );
$( '.block-fields-edit-control', fieldRow ).after( settingsRows );
blockFieldWidthInit( fieldRow );
scrollRowIntoView( fieldRow );
},
error() {
$( '.block-fields-edit-loading', fieldRow ).remove();
},
data,
} );
};
const scrollRowIntoView = function( row ) {
let scrollTop = 0;
$( '.block-fields-rows .block-fields-row' ).each( function() {
// Add the height of all previous rows to the target scrollTop position.
if ( $( this ).is( row ) ) {
return false;
}
const height = $( this ).children().first().outerHeight();
scrollTop += height;
} );
$( 'body' ).animate( { scrollTop } );
};
/**
* Clone a row, used for row duplication.
*
* @param {jQuery} row The row to clone.
* @param {boolean} append Whether to append the cloned row, or just return it.
* @return {jQuery} The cloned row.
*/
const cloneRow = function( row, append = true ) {
const uid = row.data( 'uid' ),
newUid = Math.floor( Math.random() * 1000000000000 ),
newRow = row.clone(),
subRows = newRow.find( '.block-fields-sub-rows' ).children();
// Remove the sub rows (we'll add them back again later).
if ( subRows.length > 0 ) {
subRows.remove();
}
// Replace all the UIDs.
newRow.attr( 'data-uid', newUid );
newRow.html( function( index, html ) {
return html.replace( new RegExp( uid, 'g' ), newUid );
} );
// Set the values manually. jQuery's clone method doesn't work for dynamic data.
row.find( '[name*="[' + uid + ']"]' ).each( function() {
const newRowName = $( this ).attr( 'name' ).replace( uid, newUid ),
newRowInput = newRow.find( '[name="' + newRowName + '"]' );
// Radio and Checkbox inputs are unique in that multiple can exist with the same name.
if ( $( this ).is( '[type="radio"],[type="checkbox"]' ) ) {
newRowInput.parent().find( '[value="' + $( this ).val() + '"]' ).prop( 'checked', $( this ).prop( 'checked' ) );
} else {
newRowInput.val( $( this ).val() );
}
} );
incrementRow( newRow );
// Insert the new row.
if ( append ) {
newRow.insertAfter( row );
}
subRows.each( function() {
$( this ).find( 'input[name^="block-fields-parent"]' ).val( newUid );
newRow.find( '.block-fields-sub-rows' ).append( cloneRow( $( this ), false ) );
} );
return newRow;
};
const incrementRow = function( row ) {
const label = row.find( '.block-fields-edit-label input' ).eq( 0 ),
name = row.find( '.block-fields-edit-name input' ).eq( 0 ),
baseName = name.val().replace( /-\d+$/, '' ),
baseLabel = label.val().replace( / \d+$/, '' ),
nameMatchRegex = new RegExp( '^' + baseName + '(-\\d+)?$' ),
matchedNames = $( 'input[name^="block-fields-name"]' ).filter( function() {
// Get all other rows that have the same base name.
return $( this ).val().match( nameMatchRegex );
} );
let numbers = [];
// Get the number of each row, then sort them.
matchedNames.each( function() {
numbers.push( $( this ).val().match( /\d*$/ )[ 0 ] );
} );
// Filter out duplicate numbers.
numbers = numbers.filter( function( value, index, self ) {
return self.indexOf( value ) === index;
} );
// Put the numbers in ascending order.
numbers = numbers.sort( function( a, b ) {
return b - a;
} );
// Assign the new names.
if ( numbers.length > 1 ) {
const newNumber = parseInt( numbers[ 0 ] ) + 1;
name.val( baseName + '-' + newNumber );
label.val( baseLabel + ' ' + newNumber );
} else if ( 1 === numbers.length ) {
name.val( name.val() + '-1' );
label.val( label.val() + ' 1' );
} else {
name.val( name.val() );
label.val( label.val() );
}
label.data( 'defaultValue', label.val() );
};
const slugify = function( text ) {
return text
.toLowerCase()
.replace( /[^\w ]+/g, '' )
.replace( / +/g, '-' )
.replace( /_+/g, '-' );
};
}( jQuery ) );
/* global XMLHttpRequest, FormData, ajaxurl */
document.addEventListener( 'DOMContentLoaded', function() {
const hiddenClass = 'bl-hidden';
// In the main migration notice, on clicking 'Not Now',
// make an AJAX request to store the user meta to not display the notice again.
// Also, remove this notice and display another.
document.querySelector( '#bl-notice-not-now' ).addEventListener( 'click', function() {
const request = new XMLHttpRequest();
const data = new FormData();
data.append( 'action', 'bl_dismiss_migration_notice' );
data.append( 'bl-migration-nonce-name', document.querySelector( '#bl-migration-nonce-name' ).value );
request.open( 'POST', ajaxurl, true );
request.send( data );
// Remove this notice.
const notice = document.querySelector( '#bl-migration-notice' );
notice.parentNode.removeChild( notice );
// Display the 'Not Now' notice.
document.querySelector( '#bl-not-now-notice' ).classList.remove( hiddenClass );
} );
// In the 'Not Now' notice, on clicking 'OK', hide the notice.
document.querySelector( '#bl-notice-ok' ).addEventListener( 'click', function() {
document.querySelector( '#bl-not-now-notice' ).classList.add( hiddenClass );
} );
} );
<?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
!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
<?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
This diff could not be displayed because it is too large.
/* global blockLabMigration */
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
/**
* Internal dependencies
*/
import { Intro } from './';
import { BackUpSite, GetGenesisPro, InstallActivateGcb, MigrateBlocks, UpdateHooks } from './steps';
import { FIRST_STEP_NUMBER } from '../constants';
/**
* The migration admin page.
*
* @return {React.ReactElement} The component for the admin page.
*/
const App = () => {
const [ currentStepIndex, setStepIndex ] = useState( FIRST_STEP_NUMBER );
/**
* Sets the step index to the previous step.
*/
const goToPrevious = () => {
setStepIndex( currentStepIndex - 1 );
};
/**
* Sets the step index to the next step.
*/
const goToNext = () => {
setStepIndex( currentStepIndex + 1 );
};
const steps = [
BackUpSite,
UpdateHooks,
InstallActivateGcb,
MigrateBlocks,
];
// Conditionally add the step to get Genesis Pro.
// @ts-ignore
if ( blockLabMigration.isPro ) {
steps.unshift( GetGenesisPro );
}
return (
<div className="bl-migration__content-wrapper">
<div className="container bl-migration__content-container">
<Intro />
{
steps.map( ( MigrationStep, index ) => {
const stepIndex = FIRST_STEP_NUMBER + index;
const isStepActive = currentStepIndex === stepIndex;
const isStepComplete = currentStepIndex > stepIndex;
return (
<MigrationStep
key={ `bl-migration-step-${ stepIndex }` }
{ ...{ currentStepIndex, goToNext, goToPrevious, isStepActive, isStepComplete, stepIndex } }
/>
);
} )
}
</div>
</div>
);
};
export default App;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
/**
* @typedef ButtonNextProps
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
* @property {string} [checkboxLabel] The label of the checkbox, if there should be one.
* @property {number} stepIndex The index of this button's step.
*/
/**
* The next button.
*
* @param {ButtonNextProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const ButtonNext = ( { onClick, checkboxLabel, stepIndex } ) => {
const [ isCheckboxChecked, setCheckboxChecked ] = useState( false );
// If there's no label for the 'confirmation' checkbox, return a simple button.
if ( ! checkboxLabel ) {
return <button className="btn" onClick={ onClick }>{ __( 'Next Step', 'block-lab' ) }</button>;
}
const inputId = `bl-migration-check-${ stepIndex }`;
return (
<>
<form>
<input
id={ inputId }
type="checkbox"
onClick={ () => {
setCheckboxChecked( ! isCheckboxChecked );
} }
/>
<label htmlFor={ inputId } className="ml-2 font-medium">{ checkboxLabel }</label>
</form>
<button
className="btn"
onClick={ onClick }
disabled={ ! isCheckboxChecked }
>
{ __( 'Next Step', 'block-lab' ) }
</button>
</>
);
};
export default ButtonNext;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* @typedef ButtonPreviousProps
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} onClick The click handler.
*/
/**
* The previous button.
*
* @param {ButtonPreviousProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const ButtonPrevious = ( { onClick } ) => {
return (
<button className="btn btn-secondary" onClick={ onClick }>
{ __( 'Previous', 'block-lab' ) }
</button>
);
};
export default ButtonPrevious;
export { default as App } from './app';
export { default as ButtonNext } from './button-next';
export { default as ButtonPrevious } from './button-previous';
export { default as Intro } from './intro';
export { default as Step } from './step';
export { default as StepContent } from './step-content';
export { default as StepFooter } from './step-footer';
export { default as StepIcon } from './step-icon';
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* The introduction to the migration.
*
* @return {React.ReactElement} The introduction to the migration.
*/
const Intro = () => {
const developerNoticeUrl = 'https://getblocklab.com/migrating-to-genesis-custom-blocks/';
const announcementUrl = 'https://getblocklab.com/the-block-lab-team-are-joining-wp-engine/';
return (
<>
<div>
<h1>{ __( 'Migrating to Genesis Custom Blocks', 'block-lab' ) }</h1>
<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>
<p>
{ __( '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' ) }
&nbsp;
{ __( 'Genesis Custom Blocks is now the home of all our custom block efforts and what we have planned is very very cool!', 'block-lab' ) }
&nbsp;
{ __( 'Version 1.0 of this plugin is now released and has full feature parity with Block Lab.', 'block-lab' ) }
</p>
<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>
<div className="dev-notice">
<svg fill="currentColor" viewBox="0 0 20 20">
<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>
</svg>
<span>{ __( 'Need to let the developer for this site know about this? Send them this link.', 'block-lab' ) }</span>
<a href={ developerNoticeUrl } target="_blank" rel="noopener noreferrer" className="btn">
<span>{ __( 'Developer Notice', 'block-lab' ) }</span>
<svg fill="currentColor" viewBox="0 0 20 20">
<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>
<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>
</svg>
</a>
</div>
</div>
<h2>{ __( "Let's Migrate", 'block-lab' ) }</h2>
</>
);
};
export default Intro;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* @typedef StepContentProps
* @property {React.ReactNode} children The component's children.
* @property {string} heading The step heading.
* @property {boolean} isStepActive Whether this step is active.
*/
/**
* The content of the step.
*
* @param {StepContentProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const StepContent = ( { children, heading, isStepActive } ) => {
return (
<div className="step-content">
<h3>{ heading }</h3>
{ isStepActive && children }
</div>
);
};
export default StepContent;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* @typedef StepFooterProps
* @property {React.ReactNode} children The component's children.
*/
/**
* The footer of the step.
*
* @param {StepFooterProps} props The component props.
* @return {React.ReactElement} The component for the step content.
*/
const StepFooter = ( { children } ) => {
return (
<div className="step-footer">
{ children }
</div>
);
};
export default StepFooter;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* @typedef StepIconProps
* @property {number} index The index of this icon's step.
* @property {boolean} isComplete Whether this icon's step is active.
*/
/**
* The icon of the step number.
*
* @param {StepIconProps} props The component props.
* @return {React.ReactElement} props The icon component.
*/
const StepIcon = ( { index, isComplete } ) => {
const titleId = `bl-migration-icon-${ index }`;
const svg = (
<svg fill="currentColor" viewBox="0 0 20 20" aria-labelledby={ titleId }>
<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>
<title id={ titleId }>{ __( 'Step completed', 'block-lab' ) }</title>
</svg>
);
return (
<div className="step-icon">
{ isComplete ? svg : index }
</div>
);
};
export default StepIcon;
// @ts-check
/**
* External dependencies
*/
import classNames from 'classnames';
import * as React from 'react';
/**
* @typedef StepProps
* @property {boolean} isActive Whether this step is active.
* @property {boolean} isComplete Whether this step is complete.
* @property {React.ReactNode} children The children of the component.
*/
/**
* Migration step.
*
* @param {StepProps} props The component props.
*/
const Step = ( { isActive, isComplete, children } ) => {
return (
<div
className={ classNames( 'step', {
'step--active': isActive,
'step--complete': isComplete,
} ) }
>
{ children }
</div>
);
};
export default Step;
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
import { FIRST_STEP_NUMBER } from '../../constants';
/**
* @typedef {Object} BackUpSiteProps The component props.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the previous step.
*/
/**
* The step that prompts to back up the site.
*
* @param {BackUpSiteProps} Props The component props.
* @return {React.ReactElement} The component to prompt to back up the site.
*/
const BackUpSite = ( { isStepActive, isStepComplete, goToNext, goToPrevious, stepIndex } ) => {
const isFirstStep = FIRST_STEP_NUMBER === stepIndex;
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Back Up Your Site', 'block-lab' ) }
isStepActive={ isStepActive }
>
<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>
<StepFooter>
{ ! isFirstStep && <ButtonPrevious onClick={ goToPrevious } /> }
<ButtonNext
checkboxLabel={ __( 'I have backed up my site.', 'block-lab' ) }
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</StepContent>
</Step>
);
};
export default BackUpSite;
// @ts-check
/* global blockLabMigration */
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} GetGenesisProProps The component props.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
*/
/**
* The step to get Genesis Pro.
*
* @param {GetGenesisProProps} Props The component props.
* @return {React.ReactElement} The component to get Genesis Pro.
*/
const GetGenesisPro = ( { goToNext, isStepActive, isStepComplete, stepIndex } ) => {
// @ts-ignore
const genesisProKey = blockLabMigration.genesisProKey;
const [ isSubmittingKey, setIsSubmittingKey ] = useState( false );
const [ keySubmittedSuccessfully, setKeySubmittedSuccessfully ] = useState( false );
const [ subscriptionKey, updateSubscriptionKey ] = useState( !! genesisProKey ? genesisProKey : '' );
const [ submissionMessage, setSubmissionMessage ] = useState( '' );
const urlMigrateWithoutGenPro = 'https://getblocklab.com/migrating-to-genesis-custom-blocks/';
const urlOptInGenesisPro = 'https://forms.gle/26u7NDRUp2A9i2aF8';
const shouldAllowNextStep = !! genesisProKey || keySubmittedSuccessfully;
/**
* The handler for changing the subscription key.
*
* @param {React.ChangeEvent<HTMLInputElement>} event The change event.
*/
const onChangeSubscriptionKey = ( event ) => {
updateSubscriptionKey( event.target.value );
};
/**
* Submits the subscription key to the endpoint.
*/
const submitSubscriptionKey = async () => {
setIsSubmittingKey( true );
await apiFetch( {
path: '/block-lab/update-subscription-key',
method: 'POST',
data: { subscriptionKey },
} ).then( () => {
setSubmissionMessage( __( 'Thanks! Your key is valid, and has been saved.', 'block-lab' ) );
setKeySubmittedSuccessfully( true );
} ).catch( ( error ) => {
const errorMessage = error.message ? error.message : __( 'There was an error validating the key.', 'block-lab' );
setSubmissionMessage( errorMessage );
setKeySubmittedSuccessfully( false );
updateSubscriptionKey( '' );
} );
setIsSubmittingKey( false );
};
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Get Genesis Pro', 'block-lab' ) }
isStepActive={ isStepActive }
>
<p></p>
<div className="pro-box">
<h3>{ __( 'Migrating from Block Lab Pro', 'block-lab' ) }</h3>
<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>
<div className="pro-box-tiles">
<div className="pro-box-tile">
<div className="pro-box-tile__icon">
<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,
} }
>
<g id="bl_genesis_icon">
<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>
</g>
</svg>
</div>
<h4>{ __( '12 months free', 'block-lab' ) }</h4>
<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>
</div>
<div className="pro-box-tile">
<div className="pro-box-tile__icon">
<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,
} }
>
<g id="bl_infinity_icon">
<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>
</g>
</svg>
</div>
<h4>{ __( 'Unlimited Sites', 'block-lab' ) }</h4>
<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>
</div>
<div className="pro-box-tile">
<div className="pro-box-tile__icon">
<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,
} }
>
<g id="bl_key_icon">
<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>
</g>
</svg>
</div>
<h4>{ __( 'New Subscription Key', 'block-lab' ) }</h4>
<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>
</div>
</div>
<p>{ __( '* Block Lab Pro licenses will not be renewing and Pro updates / support will end when your current license expires.', 'block-lab' ) }</p>
</div>
<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>
<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>
<ul>
<li>{ __( 'Already have got it? Enter the subscription key below to continue migrating.', 'block-lab' ) }</li>
<li>{ __( 'Don’t have one yet? Please opt-in using the link below.', 'block-lab' ) }</li>
</ul>
{ ! keySubmittedSuccessfully && (
<>
<div className="get-genesis-pro">
<a
href={ urlOptInGenesisPro }
className="btn"
target="_blank"
rel="noopener noreferrer"
>
{ __( 'Opt-in for Genesis Pro', 'block-lab' ) }
</a>
<span>{ __( '(This may take up to 3 working days)', 'block-lab' ) }</span>
</div>
<p>{ __( 'then', 'block-lab' ) }</p>
<div className="genesis-pro-form">
<input
type="text"
placeholder={ __( 'Paste your Genesis Pro subscription key', 'block-lab' ) }
value={ subscriptionKey }
onChange={ onChangeSubscriptionKey }
/>
<button
onClick={ submitSubscriptionKey }
disabled={ isSubmittingKey }
>
{ __( 'Save', 'block-lab' ) }
</button>
{ isSubmittingKey && <Spinner /> }
</div>
</>
) }
<p className="pro-submission-message">{ submissionMessage }</p>
{ ! keySubmittedSuccessfully && (
<p className="help-text">
{ __( 'Want to migrate but not set up Genesis Pro just now?', 'block-lab' ) }
&nbsp;
<a
href={ urlMigrateWithoutGenPro }
target="_blank"
rel="noopener noreferrer"
aria-label={ __( 'More information about migrating but not setting up Genesis Pro', 'genesis-custom-blocks' ) }
>
{ __( 'Read here for what that means.', 'block-lab' ) }
</a>
</p>
) }
<StepFooter>
<ButtonNext
checkboxLabel={ shouldAllowNextStep ? null : __( 'Migrate without Genesis Pro.', 'block-lab' ) }
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</StepContent>
</Step>
);
};
export default GetGenesisPro;
export { default as BackUpSite } from './back-up-site';
export { default as GetGenesisPro } from './get-genesis-pro';
export { default as InstallActivateGcb } from './install-activate-gcb';
export { default as MigrateBlocks } from './migrate-blocks';
export { default as UpdateHooks } from './update-hooks';
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import apiFetch from '@wordpress/api-fetch';
import { Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} InstallActivateGcbProps The component props.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
*/
/**
* Installs and activates GCB.
*
* @param {InstallActivateGcbProps} Props The component props.
* @return {React.ReactElement} The component to activate Genesis Custom Blocks.
*/
const InstallActivateGcb = ( { goToNext, isStepActive, isStepComplete, stepIndex } ) => {
const [ isInProgress, setIsInProgress ] = useState( false );
const [ isError, setIsError ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( '' );
const [ isSuccess, setIsSuccess ] = useState( false );
/**
* Installs and activates Genesis Custom Blocks.
*/
const installAndActivateGcb = async () => {
speak( __( 'The installation is now in progress', 'block-lab' ) );
setIsInProgress( true );
setIsError( false );
setErrorMessage( '' );
await apiFetch( {
path: '/block-lab/install-activate-gcb',
method: 'POST',
} ).then( () => {
speak( __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) );
setIsSuccess( true );
} ).catch( ( result ) => {
speak( __( 'The installation and activation failed with the following error:', 'block-lab' ) );
if ( result.hasOwnProperty( 'message' ) ) {
speak( result.message );
setErrorMessage( result.message );
}
setIsSuccess( false );
setIsError( true );
} );
setIsInProgress( false );
};
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Install And Activate Genesis Custom Blocks', 'block-lab' ) }
isStepActive={ isStepActive }
>
{ isInProgress && (
<>
<Spinner />
<p>{ __( 'Installing and activating Genesis Custom Blocks…', 'block-lab' ) }</p>
</>
) }
{ !! errorMessage && (
<div className="bl-migration__error">
<p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
<p>{ errorMessage }</p>
</div>
) }
{ ! isInProgress && ! isSuccess && (
<button
className="btn"
onClick={ installAndActivateGcb }
>
{ isError ? __( 'Try Again', 'block-lab' ) : __( 'Install and activate', 'block-lab' ) }
</button>
) }
{ isSuccess && (
<>
<p>{ __( 'Success! Genesis Custom Blocks is installed and activated.', 'block-lab' ) }</p>
<StepFooter>
<ButtonNext
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</>
) }
</StepContent>
</Step>
);
};
export default InstallActivateGcb;
// @ts-check
/* global blockLabMigration */
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import apiFetch from '@wordpress/api-fetch';
import { Spinner } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} MigrateBlocksProps The component props.
* @property {Function} goToNext Goes to the next step.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
*/
/**
* The step that migrates the blocks.
*
* @param {MigrateBlocksProps} Props The component props.
* @return {React.ReactElement} The component to prompt to migrate the post content.
*/
const MigrateBlocks = ( { isStepActive, isStepComplete, stepIndex } ) => {
const [ currentBlockMigrationStep, setCurrentBlockMigrationStep ] = useState( 0 );
const [ isInProgress, setIsInProgress ] = useState( false );
const [ isError, setIsError ] = useState( false );
const [ errorMessage, setErrorMessage ] = useState( '' );
const [ isSuccess, setIsSuccess ] = useState( false );
const migrationLabels = [
__( 'Migrating your blocks…', 'block-lab' ),
__( 'Migrating your post content…', 'block-lab' ),
];
/**
* Migrates the custom post type, then chains post content migration to the callback.
*/
const migrateCpt = async () => {
await apiFetch( {
path: '/block-lab/migrate-post-type',
method: 'POST',
} ).then( async () => {
setCurrentBlockMigrationStep( 1 );
await migratePostContent();
} ).catch( ( result ) => {
if ( result.hasOwnProperty( 'message' ) ) {
setErrorMessage( result.message );
}
speak( __( 'The migration failed in the CPT migration', 'block-lab' ) );
setIsError( true );
setIsInProgress( false );
} );
};
/**
* Migrates the post content.
*/
const migratePostContent = async () => {
// Used for a 504 Gateway Timeout Error, but could also be for other errors.
const timeoutErrorCode = 'invalid_json';
await apiFetch( {
path: '/block-lab/migrate-post-content',
method: 'POST',
} ).then( () => {
speak( __( 'The migration was successful!', 'block-lab' ) );
setIsSuccess( true );
} ).catch( async ( result ) => {
if ( result.hasOwnProperty( 'code' ) && timeoutErrorCode === result.code ) {
await migratePostContent();
return;
} else if ( result.hasOwnProperty( 'message' ) ) {
setErrorMessage( result.message );
}
speak( __( 'The migration failed in the post content migration', 'block-lab' ) );
setIsError( true );
} );
};
/**
* Handles all of the migration for this step.
*/
const migrate = async () => {
speak( __( 'The migration is now in progress', 'block-lab' ) );
setErrorMessage( '' );
setIsInProgress( true );
// The post content migration is chained to the callback in then().
await migrateCpt();
setIsInProgress( false );
};
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Migrate Your Blocks', 'block-lab' ) }
isStepActive={ isStepActive }
>
{ ! isSuccess && <p>{ __( "Okay! Everything is ready. Let's do this. While the migration is underway, don't leave this page.", 'block-lab' ) }</p> }
{ !! errorMessage && (
<div className="bl-migration__error">
<p>{ __( 'The following error ocurred:', 'block-lab' ) }</p>
<p>{ errorMessage }</p>
</div>
) }
{ isInProgress && (
<>
<Spinner />
<p>{ migrationLabels[ currentBlockMigrationStep ] }</p>
</>
) }
{ ! isInProgress && ! isSuccess && (
<button
className="btn"
onClick={ migrate }
>
{ isError ? __( 'Try Again', 'block-lab' ) : __( 'Migrate Now', 'block-lab' ) }
</button>
) }
{ isSuccess && (
<>
<p>
<span role="img" aria-label={ __( 'party emoji', 'block-lab' ) }>🎉</span>
&nbsp;
{ __( 'The migration completed successfully! Time to say goodbye to Block Lab (it’s been fun!) and step into the FUTURE', 'block-lab' ) }
&nbsp;
<span className="message-future">{ __( 'FUTURE', 'block-lab' ) }</span>
&nbsp;
<sub>{ __( 'FUTURE', 'block-lab' ) }</sub>.
</p>
<StepFooter>
{ /* @ts-ignore */ }
<a href={ blockLabMigration.gcbUrl } className="btn">
{ __( 'Go To Genesis Custom Blocks', 'block-lab' ) }
</a>
</StepFooter>
</>
) }
</StepContent>
</Step>
);
};
export default MigrateBlocks;
/**
* External dependencies
*/
import { render } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { BackUpSite } from '../';
test( 'back up site migration step', async () => {
const props = {
goToNext: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 1,
};
const { getByLabelText, getByText } = render( <BackUpSite { ...props } /> );
getByText( /back up your site/ );
getByText( props.stepIndex.toString() );
// Because the 'confirm' checkbox isn't checked, the 'next' button should be disabled.
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).not.toHaveBeenCalled();
// Now that the 'confirm' checkbox is checked, the 'next' button should work.
user.click( getByLabelText( 'I have backed up my site.' ) );
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).toHaveBeenCalled();
} );
/**
* External dependencies
*/
import { fireEvent, render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { GetGenesisPro } from '../';
jest.mock( '@wordpress/api-fetch' );
window.blockLabMigration = {};
test( 'get Genesis Pro migration step', async () => {
apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 1,
};
const { getByText, getByRole } = render( <GetGenesisPro { ...props } /> );
// Because the checkbox isn't checked, the 'next' button should be disabled.
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).not.toHaveBeenCalled();
fireEvent.change(
getByRole( 'textbox' ),
{ target: { value: '1234567' } }
);
await waitFor( () =>
user.click( getByText( 'Save' ) )
);
getByText( 'Thanks! Your key is valid, and has been saved.' );
user.click( getByText( 'Next Step' ) );
expect( props.goToNext ).toHaveBeenCalled();
} );
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import { InstallActivateGcb } from '../';
global.blockLabMigration = {
activateUrl: 'https://example.com',
};
test( 'activate gcb migration step', async () => {
const props = {
isStepActive: true,
isStepComplete: false,
stepIndex: 5,
};
const { getByText } = render( <InstallActivateGcb { ...props } /> );
expect( getByText( props.stepIndex.toString() ) ).toBeInTheDocument();
} );
/**
* External dependencies
*/
import { render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
import { MigrateBlocks } from '../';
jest.mock( '@wordpress/api-fetch' );
global.blockLabMigration = {
gcbUrl: 'https://example.com',
};
test( 'migrate blocks step', async () => {
apiFetch.mockImplementation( () => new Promise( ( resolve ) => resolve( { success: true } ) ) );
const props = {
currentStepIndex: 4,
goToNext: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 4,
};
const { getByText } = render( <MigrateBlocks { ...props } /> );
getByText( /migrate your blocks/i );
getByText( props.stepIndex.toString() );
await waitFor( () =>
user.click( getByText( 'Migrate Now' ) )
);
expect( getByText( 'The migration was successful!' ) ).toBeInTheDocument();
} );
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';
import user from '@testing-library/user-event';
/**
* Internal dependencies
*/
import { UpdateHooks } from '../';
describe( 'update hooks migration step', () => {
it( 'displays step content when this step is active', async () => {
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: true,
isStepComplete: false,
stepIndex: 2,
};
const { getByText } = render( <UpdateHooks { ...props } /> );
getByText( 'Update Hooks & API' );
getByText( props.stepIndex.toString() );
// It should always be possible to click the 'previous' button.
user.click( getByText( 'Previous' ) );
expect( props.goToPrevious ).toHaveBeenCalled();
} );
it( 'does not display content when this step is not active', async () => {
const props = {
goToNext: jest.fn(),
goToPrevious: jest.fn(),
isStepActive: false,
isStepComplete: false,
stepIndex: 2,
};
const { getByText } = render( <UpdateHooks { ...props } /> );
// The heading should still display.
getByText( 'Update Hooks & API' );
getByText( props.stepIndex.toString() );
// The content of the step should now display, as it's not active.
expect( screen.queryByText( 'Previous' ) ).not.toBeInTheDocument();
} );
} );
// @ts-check
/**
* External dependencies
*/
import * as React from 'react';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { ButtonNext, ButtonPrevious, Step, StepContent, StepFooter, StepIcon } from '../';
/**
* @typedef {Object} UpdateHooksProps The component props.
* @property {boolean} isStepActive Whether this step is active.
* @property {boolean} isStepComplete Whether this step is complete.
* @property {number} stepIndex The step index of this step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToNext Goes to the next step.
* @property {React.EventHandler<React.MouseEvent<HTMLButtonElement, MouseEvent>>} goToPrevious Goes to the next step.
*/
/**
* The step that prompts to update hooks.
*
* @param {UpdateHooksProps} Props The component props.
* @return {React.ReactElement} The component to prompt to back up the site.
*/
const UpdateHooks = ( { isStepActive, isStepComplete, stepIndex, goToNext, goToPrevious } ) => {
const hooksDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-hook-compatibility/';
const phpApiDetailsUrl = 'https://developer.wpengine.com/genesis-custom-blocks/block-lab-php-api-compatibility/';
return (
<Step isActive={ isStepActive } isComplete={ isStepComplete }>
<StepIcon
index={ stepIndex }
isComplete={ isStepComplete }
/>
<StepContent
heading={ __( 'Update Hooks & API', 'block-lab' ) }
isStepActive={ isStepActive }
>
<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>
<ul className="list-disc list-inside mt-2">
<li>
<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' ) }
&nbsp;
<a
href={ hooksDetailsUrl }
target="_blank"
rel="noopener noreferrer"
aria-label={ __( 'More details on the hooks', 'genesis-custom-blocks' ) }
>
{ __( 'More details here.', 'block-lab' ) }
</a>
</li>
<li>
<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' ) }
&nbsp;
<a
href={ phpApiDetailsUrl }
target="_blank"
rel="noopener noreferrer"
aria-label={ __( 'More details on the PHP API', 'genesis-custom-blocks' ) }
>
{ __( 'More details here.', 'block-lab' ) }
</a>
</li>
</ul>
<StepFooter>
<ButtonPrevious onClick={ goToPrevious } />
<ButtonNext
checkboxLabel={ __( "I'm all okay on the hooks and API front.", 'block-lab' ) }
onClick={ goToNext }
stepIndex={ stepIndex }
/>
</StepFooter>
</StepContent>
</Step>
);
};
export default UpdateHooks;
/**
* External dependencies
*/
import { render } from '@testing-library/react';
/**
* Internal dependencies
*/
import App from '../app';
global.blockLabMigration = {
isPro: true,
};
test( 'migration app', async () => {
const { getByText } = render( <App /> );
expect( getByText( 'Migrating to Genesis Custom Blocks' ) ).toBeInTheDocument();
expect( getByText( 'Need to let the developer for this site know about this? Send them this link.' ) ).toBeInTheDocument();
} );
/**
* WordPress dependencies
*/
import domReady from '@wordpress/dom-ready';
import { render } from '@wordpress/element';
/**
* Internal dependencies
*/
import { App } from './components';
// Renders the app in the container.
domReady( () => {
render(
<App />,
document.querySelector( '.bl-migration__content' )
);
} );
<?php
/**
* WP Admin resources.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
use Block_Lab\Admin\Migration\Api;
use Block_Lab\Admin\Migration\Subscription_Api;
use Block_Lab\Admin\Migration\Notice;
use Block_Lab\Admin\Migration\Submenu;
/**
* Class Admin
*/
class Admin extends Component_Abstract {
/**
* JSON import.
*
* @var Import
*/
public $import;
/**
* Plugin license.
*
* @var License
*/
public $license;
/**
* User onboarding.
*
* @var Onboarding
*/
public $onboarding;
/**
* Plugin settings.
*
* @var Settings
*/
public $settings;
/**
* Plugin upgrade.
*
* @var Upgrade
*/
public $upgrade;
/**
* The migration API.
*
* @var Api
*/
private $api;
/**
* THe subscription API for the migration.
*
* @var Subscription_Api
*/
private $subscription_api;
/**
* The migration notice.
*
* @var Notice
*/
private $notice;
/**
* The migration submenu under the Block Lab menu item.
*
* @var Submenu
*/
private $submenu;
/**
* Initialise the Admin component.
*/
public function init() {
$this->settings = new Settings();
block_lab()->register_component( $this->settings );
$this->license = new License();
block_lab()->register_component( $this->license );
$this->onboarding = new Onboarding();
block_lab()->register_component( $this->onboarding );
$this->api = new Api();
block_lab()->register_component( $this->api );
$this->subscription_api = new Subscription_Api();
block_lab()->register_component( $this->subscription_api );
$this->notice = new Notice();
block_lab()->register_component( $this->notice );
$this->submenu = new Submenu();
block_lab()->register_component( $this->submenu );
$show_pro_nag = apply_filters( 'block_lab_show_pro_nag', false );
if ( $show_pro_nag && ! block_lab()->is_pro() ) {
$this->upgrade = new Upgrade();
block_lab()->register_component( $this->upgrade );
} else {
$this->maybe_settings_redirect();
}
if ( defined( 'WP_LOAD_IMPORTERS' ) && WP_LOAD_IMPORTERS ) {
$this->import = new Import();
block_lab()->register_component( $this->import );
}
}
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Enqueue scripts and styles used globally in the WP Admin.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_style(
'block-lab',
$this->plugin->get_url( 'css/admin.css' ),
[],
$this->plugin->get_version()
);
}
/**
* Redirect to the Settings screen if the license is being saved.
*/
public function maybe_settings_redirect() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
if ( 'block-lab-pro' === $page ) {
wp_safe_redirect(
add_query_arg(
[
'post_type' => 'block_lab',
'page' => 'block-lab-settings',
'tab' => 'license',
],
admin_url( 'edit.php' )
)
);
die();
}
}
}
<?php
/**
* Block Lab Importer.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Import
*/
class Import extends Component_Abstract {
/**
* Importer slug.
*
* @var string
*/
public $slug = 'block-lab';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_init', [ $this, 'register_importer' ] );
}
/**
* Register the importer for the Tools > Import admin screen
*/
public function register_importer() {
register_importer(
$this->slug,
__( 'Block Lab', 'block-lab' ),
__( 'Import custom blocks created with Block Lab.', 'block-lab' ),
[ $this, 'render_page' ]
);
}
/**
* Render the import page. Manages the three separate stages of the JSON import process.
*/
public function render_page() {
$step = filter_input( INPUT_GET, 'step', FILTER_SANITIZE_NUMBER_INT );
ob_start();
$this->render_page_header();
switch ( $step ) {
case 0:
default:
$this->render_welcome();
break;
case 1:
check_admin_referer( 'import-upload' );
$upload_dir = wp_get_upload_dir();
if ( ! isset( $upload_dir['basedir'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
__( 'Upload base directory not set.', 'block-lab' )
);
}
$cache_dir = $upload_dir['basedir'] . '/block-lab';
$file = wp_import_handle_upload();
if ( $this->validate_upload( $file ) ) {
if ( ! file_exists( $cache_dir ) ) {
mkdir( $cache_dir, 0777, true );
}
// This is on the local filesystem, so file_get_contents() is ok to use here.
file_put_contents( $cache_dir . '/import.json', file_get_contents( $file['file'] ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$blocks = json_decode( $json, true );
$this->render_choose_blocks( $blocks );
}
break;
case 2:
$cache_dir = wp_get_upload_dir()['basedir'] . '/block-lab';
$file = [ 'file' => $cache_dir . '/import.json' ];
if ( $this->validate_upload( $file ) ) {
// This is on the local filesystem, so file_get_contents() is ok to use here.
$json = file_get_contents( $file['file'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$blocks = json_decode( $json, true );
$import_blocks = [];
foreach ( $blocks as $block_namespace => $block ) {
if ( 'on' === filter_input( INPUT_GET, $block_namespace, FILTER_SANITIZE_STRING ) ) {
$import_blocks[ $block_namespace ] = $block;
}
}
$this->import_blocks( $import_blocks );
}
break;
}
$html = ob_get_clean();
echo '<div class="wrap block-lab-import">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Render the Import page header.
*/
public function render_page_header() {
?>
<h2><?php esc_html_e( 'Import Block Lab Content Blocks', 'block-lab' ); ?></h2>
<?php
}
/**
* Render the welcome message.
*/
public function render_welcome() {
?>
<p><?php esc_html_e( 'Welcome! This importer processes Block Lab JSON files, adding custom blocks to this site.', 'block-lab' ); ?></p>
<p><?php esc_html_e( 'Choose a JSON (.json) file to upload, then click Upload file and import.', 'block-lab' ); ?></p>
<p>
<?php
echo wp_kses(
sprintf(
/* translators: %1$s: an opening anchor tag, %2$s: a closing anchor tag */
__( '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' ),
sprintf(
'<a href="%1$s">',
esc_url(
admin_url(
add_query_arg(
[ 'post_type' => block_lab()->get_post_type_slug() ],
'edit.php'
)
)
)
),
'</a>'
),
[ 'a' => [ 'href' => [] ] ]
);
?>
</p>
<?php
wp_import_upload_form(
add_query_arg(
[
'import' => $this->slug,
'step' => 1,
]
)
);
}
/**
* Render the currently importing block title.
*
* @param string $title The title of the block.
*/
public function render_import_success( $title ) {
echo wp_kses_post(
sprintf(
'<p>%s</p>',
sprintf(
// translators: placeholder refers to title of custom block.
__( 'Successfully imported %1$s.', 'block-lab' ),
'<strong>' . esc_html( $title ) . '</strong>'
)
)
);
}
/**
* Render the currently importing block title.
*
* @param string $title The title of the block.
* @param string $error The error being reported.
*/
public function render_import_error( $title, $error ) {
echo wp_kses_post(
sprintf( '<p><strong>%s</strong></p><p>%s</p>', $title, $error )
);
}
/**
* Render the successful import message.
*/
public function render_done() {
?>
<p><?php esc_html_e( 'All done!', 'block-lab' ); ?></p>
<?php
}
/**
* Render the interface for choosing blocks to update.
*
* @param array $blocks An array of block names to choose from.
*/
public function render_choose_blocks( $blocks ) {
?>
<p><?php esc_html_e( 'Please select the blocks to import:', 'block-lab' ); ?></p>
<form>
<?php
foreach ( $blocks as $block_namespace => $block ) {
$action = __( 'Import', 'block-lab' );
if ( $this->block_exists( $block_namespace ) ) {
$action = __( 'Replace', 'block-lab' );
}
?>
<p>
<input type="checkbox" name="<?php echo esc_attr( $block_namespace ); ?>" id="<?php echo esc_attr( $block_namespace ); ?>" checked>
<label for="<?php echo esc_attr( $block_namespace ); ?>">
<?php echo esc_html( $action ); ?> <strong><?php echo esc_attr( $block['title'] ); ?></strong>
</label>
</p>
<?php
}
wp_nonce_field();
?>
<input type="hidden" name="import" value="block-lab">
<input type="hidden" name="step" value="2">
<p class="submit"><input type="submit" value="<?php esc_attr_e( 'Import Selected', 'block-lab' ); ?>" class="button button-primary"></p>
</form>
<?php
}
/**
* Handles the JSON upload and initial parsing of the file.
*
* @param array $file The file.
* @return bool False if error uploading or invalid file, true otherwise.
*/
public function validate_upload( $file ) {
if ( isset( $file['error'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
$file['error']
);
return false;
} elseif ( ! file_exists( $file['file'] ) ) {
$this->render_import_error(
__( 'Sorry, there was an error uploading the file.', 'block-lab' ),
sprintf(
// translators: placeholder refers to a file directory.
__( 'The export file could not be found at %1$s. It is likely that this was caused by a permissions problem.', 'block-lab' ),
'<code>' . esc_html( $file['file'] ) . '</code>'
)
);
return false;
}
// This is on the local filesystem, so file_get_contents() is ok to use here.
$json = file_get_contents( $file['file'] ); // @codingStandardsIgnoreLine
$data = json_decode( $json, true );
if ( ! is_array( $data ) ) {
$this->render_import_error(
__( 'Sorry, there was an error processing the file.', 'block-lab' ),
__( 'Invalid JSON.', 'block-lab' )
);
return false;
}
return true;
}
/**
* Import data into new Block Lab posts.
*
* @param array $blocks An array of Block Lab content blocks.
*/
public function import_blocks( $blocks ) {
foreach ( $blocks as $block_namespace => $block ) {
if ( ! isset( $block['title'] ) || ! isset( $block['name'] ) ) {
continue;
}
$post_id = false;
if ( $this->block_exists( $block_namespace ) ) {
$post = get_page_by_path( $block['name'], OBJECT, block_lab()->get_post_type_slug() );
if ( $post ) {
$post_id = $post->ID;
}
}
$json = wp_json_encode( [ $block_namespace => $block ], JSON_UNESCAPED_UNICODE );
$post_data = [
'post_title' => $block['title'],
'post_name' => $block['name'],
'post_content' => wp_slash( $json ),
'post_status' => 'publish',
'post_type' => block_lab()->get_post_type_slug(),
];
if ( $post_id ) {
$post_data['ID'] = $post_id;
}
$post = wp_insert_post( $post_data );
if ( is_wp_error( $post ) ) {
$this->render_import_error(
sprintf(
// translators: placeholder refers to title of custom block.
__( 'Error importing %s.', 'block-lab' ),
$block['title']
),
$post->get_error_message()
);
} else {
$this->render_import_success( $block['title'] );
}
}
$this->render_done();
}
/**
* Check if block already exists.
*
* @param string $block_namespace The JSON key for the block. e.g. block-lab/foo.
*
* @return bool
*/
private function block_exists( $block_namespace ) {
$registered_blocks = get_dynamic_block_names();
if ( in_array( $block_namespace, $registered_blocks, true ) ) {
return true;
}
return false;
}
}
<?php
/**
* Enable and validate Pro version licensing.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class License
*/
class License extends Component_Abstract {
/**
* Option name of the license key.
*
* @var string
*/
const LICENSE_KEY_OPTION_NAME = 'block_lab_license_key';
/**
* URL of the Block Lab store.
*
* @var string
*/
public $store_url;
/**
* Product slug of the Pro version on the Block Lab store.
*
* @var string
*/
public $product_slug;
/**
* The name of the license key transient.
*
* @var string
*/
const TRANSIENT_NAME = 'block_lab_license';
/**
* The transient 'license' value for when the request to validate the Pro license failed.
*
* This is for when the actual POST request fails,
* not for when it returns that the license is invalid.
*
* @var string
*/
const REQUEST_FAILED = 'request_failed';
/**
* Initialise the Pro component.
*/
public function init() {
$this->store_url = 'https://getblocklab.com';
$this->product_slug = 'block-lab-pro';
}
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_filter( 'pre_update_option_block_lab_license_key', [ $this, 'save_license_key' ] );
}
/**
* Check that the license key is valid before saving.
*
* @param string $key The license key that was submitted.
*
* @return string
*/
public function save_license_key( $key ) {
$this->activate_license( $key );
$license = get_transient( self::TRANSIENT_NAME );
if ( ! $this->is_valid() ) {
$key = '';
if ( isset( $license['license'] ) && self::REQUEST_FAILED === $license['license'] ) {
block_lab()->admin->settings->prepare_notice( $this->license_request_failed_message() );
} else {
block_lab()->admin->settings->prepare_notice( $this->license_invalid_message() );
}
} else {
block_lab()->admin->settings->prepare_notice( $this->license_success_message() );
}
return $key;
}
/**
* Check if the license if valid.
*
* @return bool
*/
public function is_valid() {
$license = $this->get_license();
if ( isset( $license['license'] ) && 'valid' === $license['license'] ) {
if ( isset( $license['expires'] ) && time() < strtotime( $license['expires'] ) ) {
return true;
}
}
return false;
}
/**
* Retrieve the license data.
*
* @return mixed
*/
public function get_license() {
$license = get_transient( self::TRANSIENT_NAME );
if ( ! $license ) {
$key = get_option( self::LICENSE_KEY_OPTION_NAME );
if ( ! empty( $key ) ) {
$this->activate_license( $key );
$license = get_transient( self::TRANSIENT_NAME );
}
}
return $license;
}
/**
* Try to activate the license.
*
* @param string $key The license key to activate.
*/
public function activate_license( $key ) {
// Data to send in our API request.
$api_params = [
'edd_action' => 'activate_license',
'license' => $key,
'item_name' => rawurlencode( $this->product_slug ),
'url' => home_url(),
];
// Call the Block Lab store's API.
$response = wp_remote_post(
$this->store_url,
[
'timeout' => 10,
'sslverify' => true,
'body' => $api_params,
]
);
if ( is_wp_error( $response ) ) {
$license = [ 'license' => self::REQUEST_FAILED ];
} else {
$license = json_decode( wp_remote_retrieve_body( $response ), true );
}
$expiration = DAY_IN_SECONDS;
set_transient( self::TRANSIENT_NAME, $license, $expiration );
}
/**
* Admin notice for correct license details.
*
* @return string
*/
public function license_success_message() {
$message = __( 'Your Block Lab license was successfully activated!', 'block-lab' );
return sprintf( '<div class="notice notice-success"><p>%s</p></div>', esc_html( $message ) );
}
/**
* Admin notice for the license request failing.
*
* This is for when the validation request fails entirely, like with a 404.
* Not for when it returns that the license is invalid.
*
* @return string
*/
public function license_request_failed_message() {
$message = sprintf(
/* translators: %s is an HTML link to contact support */
__( 'There was a problem activating the license, but it may not be invalid. If the problem persists, please %s.', 'block-lab' ),
sprintf(
'<a href="%1$s">%2$s</a>',
'mailto:hi@getblocklab.com?subject=There was a problem activating my Block Lab Pro license',
esc_html__( 'contact support', 'block-lab' )
)
);
return sprintf( '<div class="notice notice-error"><p>%s</p></div>', wp_kses_post( $message ) );
}
/**
* Admin notice for incorrect license details.
*
* @return string
*/
public function license_invalid_message() {
$message = __( 'There was a problem activating your Block Lab license.', 'block-lab' );
return sprintf( '<div class="notice notice-error"><p>%s</p></div>', esc_html( $message ) );
}
}
<?php
/**
* User onboarding.
*
* @package Block_Lab
* @copyright Copyright(c) 2018, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
use Block_Lab\Blocks\Block;
/**
* Class Onboarding
*/
class Onboarding extends Component_Abstract {
/**
* Option name.
*
* @var string
*/
public $option = 'block_lab_example_post_id';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'current_screen', [ $this, 'admin_notices' ] );
}
/**
* Runs during plugin activation.
*/
public function plugin_activation() {
$this->add_dummy_data();
$this->prepare_welcome_notice();
}
/**
* Prepare onboarding notices.
*/
public function admin_notices() {
$example_post_id = get_option( $this->option );
if ( ! $example_post_id ) {
return;
}
$screen = get_current_screen();
$slug = block_lab()->get_post_type_slug();
if ( ! is_object( $screen ) ) {
return;
}
$post_id = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
/*
* On the edit post screen, editing the Example Block.
*/
if ( $slug === $screen->id && 'post' === $screen->base && $post_id === $example_post_id ) {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'block_lab_before_fields_list', [ $this, 'show_add_to_post_notice' ] );
}
if ( 'draft' !== get_post_status( $example_post_id ) ) {
return;
}
/*
* On the plugins screen, immediately after activating Block Lab.
*/
if ( 'plugins' === $screen->id && 'true' === get_transient( 'block_lab_show_welcome' ) ) {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_notices', [ $this, 'show_welcome_notice' ] );
}
/*
* On the All Blocks screen, when a draft Example Block exists.
*/
if ( "edit-$slug" === $screen->id ) {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_notices', [ $this, 'show_edit_block_notice' ] );
}
/*
* On the edit post screen, editing the draft Example Block.
*/
if ( $slug === $screen->id && 'post' === $screen->base && $post_id === $example_post_id ) {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'edit_form_advanced', [ $this, 'show_add_fields_notice' ] );
add_action( 'edit_form_before_permalink', [ $this, 'show_publish_notice' ] );
add_action(
'add_meta_boxes',
function() use ( $slug ) {
remove_meta_box( 'block_template', $slug, 'normal' );
},
20
);
}
}
/**
* Prepare the welcome notice on plugin activation.
*
* We can't hook into admin_notices at this point, so instead we set a short
* transient, and check that transient during the next page load.
*/
public function prepare_welcome_notice() {
set_transient( 'block_lab_show_welcome', 'true', 1 );
}
/**
* Enqueue scripts and styles used by the onboarding screens.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_style(
'block-lab-onboarding-css',
$this->plugin->get_url( 'css/admin.onboarding.css' ),
[],
$this->plugin->get_version()
);
}
/**
* Render the Welcome message.
*/
public function show_welcome_notice() {
$example_post_id = get_option( $this->option );
if ( ! $example_post_id ) {
return;
}
?>
<div class="block-lab-welcome block-lab-notice notice is-dismissible">
<h2>🖖 <?php esc_html_e( 'Welcome, traveller!', 'block-lab' ); ?></h2>
<p class="intro"><?php esc_html_e( 'Block Lab makes it super easy to build custom blocks for the WordPress editor.', 'block-lab' ); ?></p>
<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>
<?php
edit_post_link(
__( 'Let\'s get started!', 'block-lab' ),
'<p>',
'</p>',
$example_post_id,
'button button--white button_cta'
);
?>
<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>
</div>
<?php
}
/**
* Render the Edit Your First Block message.
*/
public function show_edit_block_notice() {
$example_post_id = get_option( 'block_lab_example_post_id' );
if ( ! $example_post_id ) {
return;
}
?>
<div class="block-lab-edit-block block-lab-notice notice">
<h2>👩‍🔬 <?php echo esc_html_e( 'Ready to begin?', 'block-lab' ); ?></h2>
<p class="intro">
<?php
echo wp_kses_post(
sprintf(
// translators: Placeholders are <strong> html tags.
__( 'We created this %1$sExample Block%2$s to show you just how easy it is to get started.', 'block-lab' ),
'<strong>',
'</strong>'
)
);
?>
</p>
<p>
<?php
echo wp_kses_post(
sprintf(
// translators: Placeholders are <strong> and <a> html tags.
__( 'You can %1$sEdit%2$s the block to learn more, or just %3$sTrash%4$s it to dismiss this message.', 'block-lab' ),
'<strong>',
'</strong>',
'<a href="' . get_delete_post_link( $example_post_id ) . '" class="trash">',
'</a>'
)
);
?>
</p>
</div>
<?php
}
/**
* Render the Add Fields message.
*/
public function show_add_fields_notice() {
$post = get_post();
$block = new Block( $post->ID );
/*
* We add 4 fields to our Example Block in add_dummy_data().
*/
if ( count( $block->fields ) > 4 ) {
return;
}
?>
<div class="block-lab-add-fields block-lab-notice">
<h2>🧐 <?php esc_html_e( 'Try adding a field.', 'block-lab' ); ?></h2>
<p><?php esc_html_e( 'Fields let you define the options you see when adding your block to a post.', 'block-lab' ); ?></p>
</div>
<?php
}
/**
* Render the Add Fields message.
*/
public function show_publish_notice() {
$block = new Block( get_the_ID() );
/**
* We add 4 fields to our Example Block in add_dummy_data().
*/
if ( count( $block->fields ) > 4 ) {
return;
}
?>
<div class="block-lab-publish block-lab-notice">
<h2>🧪 <?php esc_html_e( 'Time to experiment!', 'block-lab' ); ?></h2>
<ol class="intro">
<li><?php esc_html_e( 'Choose an icon', 'block-lab' ); ?></li>
<li><?php esc_html_e( 'Change the category', 'block-lab' ); ?></li>
<li><?php esc_html_e( 'Investigate a few different field types', 'block-lab' ); ?></li>
</ol>
<p>
<?php
echo wp_kses_post(
sprintf(
// translators: Placeholders are <strong> html tags.
__( 'When you\'re ready, save your block by pressing %1$sPublish%2$s.', 'block-lab' ),
'<strong>',
'</strong>'
)
);
?>
</p>
</div>
<?php
}
/**
* Render the Add to Post message.
*/
public function show_add_to_post_notice() {
$post = get_post();
if ( ! $post || ! isset( $post->post_name ) || empty( $post->post_name ) ) {
return;
}
if ( 'publish' !== $post->post_status ) {
return;
}
$template = block_lab()->locate_template( "blocks/block-{$post->post_name}.php", '', true );
if ( ! $template ) {
return;
}
?>
<div class="block-lab-add-to-block block-lab-notice notice notice-large is-dismissible">
<h2>🚀 <?php esc_html_e( 'Only one thing left to do!', 'block-lab' ); ?></h2>
<p class="intro"><?php esc_html_e( 'You\'ve created a new block, and added a block template. Well done!', 'block-lab' ); ?></p>
<p><?php esc_html_e( 'All that\'s left is to add your block to a post.', 'block-lab' ); ?></p>
<a href="<?php echo esc_attr( admin_url( 'post-new.php' ) ); ?>" class="button">
<?php esc_html_e( 'Add New Post', 'block-lab' ); ?>
</a>
</div>
<?php
/*
* After we've shown the Add to Post message once, we can delete the option. This will
* ensure that no further onboarding messages are shown.
*/
delete_option( $this->option );
}
/**
* Create a dummy starter block when the plugin is activated for the first time.
*/
public function add_dummy_data() {
/*
* Check if there are any block posts already added, and if so, bail.
* Note: wp_count_posts() does not work here.
*/
$blocks = get_posts(
[
'post_type' => block_lab()->get_post_type_slug(),
'numberposts' => '1',
'post_status' => 'any',
'fields' => 'ids',
]
);
if ( count( $blocks ) > 0 ) {
return;
}
$categories = get_block_categories( get_post() );
$example_post_id = wp_insert_post(
[
'post_title' => __( 'Example Block', 'block-lab' ),
'post_name' => 'example-block',
'post_status' => 'draft',
'post_type' => block_lab()->get_post_type_slug(),
'post_content' => wp_json_encode(
[
'block-lab\/example-block' => [
'name' => 'example-block',
'title' => __( 'Example Block', 'block-lab' ),
'icon' => 'block_lab',
'category' => isset( $categories[0] ) ? $categories[0] : [],
'keywords' => [
__( 'sample', 'block-lab' ), // translators: A keyword, used for search.
__( 'tutorial', 'block-lab' ), // translators: A keyword, used for search.
__( 'template', 'block-lab' ), // translators: A keyword, used for search.
],
'fields' => [
'title' => [
'name' => 'title',
'label' => __( 'Title', 'block-lab' ),
'control' => 'text',
'type' => 'string',
'location' => 'editor',
'order' => 0,
'help' => __( 'The primary display text', 'block-lab' ),
'default' => '',
'placeholder' => '',
'maxlength' => null,
],
'description' => [
'name' => 'description',
'label' => __( 'Description', 'block-lab' ),
'control' => 'textarea',
'type' => 'string',
'location' => 'editor',
'order' => 1,
'help' => '',
'default' => '',
'placeholder' => '',
'maxlength' => null,
'number_rows' => 4,
],
'button-text' => [
'name' => 'button-text',
'label' => __( 'Button Text', 'block-lab' ),
'control' => 'text',
'type' => 'string',
'location' => 'editor',
'order' => 2,
'help' => __( 'A Call-to-Action', 'block-lab' ),
'default' => '',
'placeholder' => '',
'maxlength' => null,
],
'button-link' => [
'name' => 'button-link',
'label' => __( 'Button Link', 'block-lab' ),
'control' => 'url',
'type' => 'string',
'location' => 'editor',
'order' => 3,
'help' => __( 'The destination URL', 'block-lab' ),
'default' => '',
'placeholder' => '',
],
],
],
]
),
]
);
update_option( $this->option, $example_post_id );
}
}
<?php
/**
* Block Lab Settings.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Settings
*/
class Settings extends Component_Abstract {
/**
* Page slug.
*
* @var string
*/
public $slug = 'block-lab-settings';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
add_action( 'admin_init', [ $this, 'register_settings' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_notices', [ $this, 'show_notices' ] );
}
/**
* Enqueue scripts and styles used by the Settings screen.
*
* @return void
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( $this->slug === $page ) {
wp_enqueue_style(
$this->slug,
$this->plugin->get_url( 'css/admin.settings.css' ),
[],
$this->plugin->get_version()
);
}
}
/**
* Add submenu pages to the Block Lab menu.
*/
public function add_submenu_pages() {
add_submenu_page(
'edit.php?post_type=' . block_lab()->get_post_type_slug(),
__( 'Block Lab Settings', 'block-lab' ),
__( 'Settings', 'block-lab' ),
'manage_options',
$this->slug,
[ $this, 'render_page' ]
);
}
/**
* Register Block Lab settings.
*/
public function register_settings() {
register_setting( 'block-lab-license-key', 'block_lab_license_key' );
}
/**
* Render the Settings page.
*/
public function render_page() {
?>
<div class="wrap block-lab-settings">
<?php
$this->render_page_header();
include block_lab()->get_path() . 'php/views/license.php';
?>
</div>
<?php
}
/**
* Render the Settings page header.
*/
public function render_page_header() {
?>
<h2><?php echo esc_html( get_admin_page_title() ); ?></h2>
<h2 class="nav-tab-wrapper">
<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">
<?php esc_html_e( 'License', 'block-lab' ); ?>
</a>
<a href="https://getblocklab.com/docs/" target="_blank" class="nav-tab dashicons-before dashicons-info">
<?php esc_html_e( 'Documentation', 'block-lab' ); ?>
</a>
<a href="https://wordpress.org/support/plugin/block-lab/" target="_blank" class="nav-tab dashicons-before dashicons-sos">
<?php esc_html_e( 'Help', 'block-lab' ); ?>
</a>
</h2>
<?php
}
/**
* Prepare notices to be displayed after saving the settings.
*
* @param string $notice The notice text to display.
*/
public function prepare_notice( $notice ) {
$notices = get_option( 'block_lab_notices', [] );
$notices[] = $notice;
update_option( 'block_lab_notices', $notices );
}
/**
* Show any admin notices after saving the settings.
*/
public function show_notices() {
$notices = get_option( 'block_lab_notices', [] );
if ( empty( $notices ) || ! is_array( $notices ) ) {
return;
}
foreach ( $notices as $notice ) {
echo wp_kses_post( $notice );
}
delete_option( 'block_lab_notices' );
}
}
<?php
/**
* Block Lab Upgrade Page.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin;
use Block_Lab\Component_Abstract;
/**
* Class Upgrade
*/
class Upgrade extends Component_Abstract {
/**
* Page slug.
*
* @var string
*/
public $slug = 'block-lab-pro';
/**
* Register any hooks that this component needs.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_pages' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
/**
* Enqueue scripts and styles used by the Upgrade screen.
*
* @return void
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( $this->slug === $page ) {
wp_enqueue_style(
$this->slug,
$this->plugin->get_url( 'css/admin.upgrade.css' ),
[],
$this->plugin->get_version()
);
}
}
/**
* Add submenu pages to the Block Lab menu.
*/
public function add_submenu_pages() {
add_submenu_page(
'edit.php?post_type=block_lab',
__( 'Block Lab Pro', 'block-lab' ),
__( 'Go Pro', 'block-lab' ),
'manage_options',
$this->slug,
[ $this, 'render_page' ]
);
}
/**
* Render the Upgrade page.
*/
public function render_page() {
?>
<div class="wrap block-lab-pro">
<h2 class="screen-reader-text"><?php echo esc_html( get_admin_page_title() ); ?></h2>
<?php include block_lab()->get_path() . 'php/views/upgrade.php'; ?>
</div>
<?php
}
}
<?php
/**
* Migration REST API endpoints.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Plugin_Upgrader;
use WP_Ajax_Upgrader_Skin;
use WP_Error;
use WP_REST_Response;
use Block_Lab\Component_Abstract;
/**
* Class Post_Type
*/
class Api extends Component_Abstract {
/**
* Adds the actions.
*/
public function register_hooks() {
add_action( 'rest_api_init', [ $this, 'register_route_install_gcb' ] );
add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_content' ] );
add_action( 'rest_api_init', [ $this, 'register_route_migrate_post_type' ] );
}
/**
* Registers a route to install and activate the plugin Genesis Custom Blocks.
*/
public function register_route_install_gcb() {
register_rest_route(
block_lab()->get_slug(),
'install-activate-gcb',
[
'methods' => 'POST',
'callback' => [ $this, 'get_install_gcb_response' ],
'permission_callback' => function() {
return current_user_can( 'install_plugins' ) && current_user_can( 'activate_plugins' );
},
]
);
}
/**
* Installs and activates Genesis Custom Blocks, and returns the result.
*
* @param array $data Data sent in the POST request.
* @return WP_REST_Response|WP_Error Response to the request.
*/
public function get_install_gcb_response( $data ) {
unset( $data );
$installation_result = $this->install_plugin();
if ( is_wp_error( $installation_result ) ) {
return $installation_result;
}
$activation_result = $this->activate_plugin();
if ( is_wp_error( $activation_result ) ) {
return $activation_result;
}
return rest_ensure_response( [ 'message' => __( 'Plugin installed and activated', 'block-lab' ) ] );
}
/**
* Installs the new plugin.
*
* Mainly copied from Gutenberg, with slight changes.
* The main change being that it returns true
* if the plugin is already downloaded, not a WP_Error.
*
* @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L271-L369
* @return true|WP_Error True on success, WP_Error on failure.
*/
private function install_plugin() {
global $wp_filesystem;
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/plugin.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
// Check if the plugin is already installed.
if ( array_key_exists( $this->get_new_plugin_file(), get_plugins() ) ) {
return true;
}
// Verify filesystem is accessible first.
$filesystem_available = $this->is_filesystem_available();
if ( is_wp_error( $filesystem_available ) ) {
return $filesystem_available;
}
$download_link = $this->get_download_link();
if ( is_wp_error( $download_link ) ) {
return $download_link;
}
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->install( $download_link );
if ( is_wp_error( $result ) ) {
$result->add_data( [ 'status' => 500 ] );
return $result;
}
// This should be the same as $result above.
if ( is_wp_error( $skin->result ) ) {
$skin->result->add_data( [ 'status' => 500 ] );
return $skin->result;
}
if ( $skin->get_errors()->has_errors() ) {
$error = $skin->get_errors();
$error->add_data( [ 'status' => 500 ] );
return $error;
}
if ( is_null( $result ) ) {
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && isset( $wp_filesystem->errors ) && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), [ 'status' => 500 ] );
}
return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'block-lab' ), [ 'status' => 500 ] );
}
$file = $upgrader->plugin_info();
if ( ! $file ) {
return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.', 'block-lab' ), [ 'status' => 500 ] );
}
return true;
}
/**
* Determines if the filesystem is available.
*
* Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
* Copied from Gutenberg.
*
* @see https://github.com/WordPress/gutenberg/blob/8d64aa3092d5d9e841895bf2d495565c9a770238/lib/class-wp-rest-plugins-controller.php#L799-L815
*
* @return true|WP_Error True if filesystem is available, WP_Error otherwise.
*/
private function is_filesystem_available() {
if ( 'direct' === get_filesystem_method() ) {
return true;
}
ob_start();
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
ob_end_clean();
if ( $filesystem_credentials_are_stored ) {
return true;
}
return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.', 'block-lab' ), [ 'status' => 500 ] );
}
/**
* Gets the GCB Pro download link.
*
* @return string|WP_Error The download link, or a WP_Error.
*/
public function get_download_link() {
if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
return get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
} else {
$api = plugins_api(
'plugin_information',
[
'slug' => 'genesis-custom-blocks',
'fields' => [
'sections' => false,
],
]
);
if ( is_wp_error( $api ) ) {
if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
$api->add_data( [ 'status' => 404 ] );
} else {
$api->add_data( [ 'status' => 500 ] );
}
return $api;
}
if ( empty( $api->download_link ) ) {
return new WP_Error(
'no_download_link',
__( 'There was no download_link in the API', 'block-lab' )
);
}
return $api->download_link;
}
}
/**
* Activates the new plugin.
*
* Mainly copied from Gutenberg's WP_REST_Plugins_Controller::handle_plugin_status().
*
* @see https://github.com/WordPress/gutenberg/blob/fef0445bf47adc6c8d8b69e19616feb8b6de8c2e/lib/class-wp-rest-plugins-controller.php#L679-L709
*
* @return true|WP_Error True on success, WP_Error on failure.
*/
private function activate_plugin() {
$activation_result = activate_plugin( $this->get_new_plugin_file(), '', false, true );
if ( is_wp_error( $activation_result ) ) {
$activation_result->add_data( [ 'status' => 500 ] );
return $activation_result;
}
return true;
}
/**
* Registers a route to migrate the post content to the new namespace.
*/
public function register_route_migrate_post_content() {
register_rest_route(
block_lab()->get_slug(),
'migrate-post-content',
[
'methods' => 'POST',
'callback' => [ $this, 'get_migrate_post_content_response' ],
'permission_callback' => function() {
return current_user_can( Submenu::MIGRATION_CAPABILITY );
},
]
);
}
/**
* Gets the REST API response for the post content migration.
*
* @return WP_REST_Response The response to the request.
*/
public function get_migrate_post_content_response() {
return rest_ensure_response( ( new Post_Content( 'block-lab', 'genesis-custom-blocks' ) )->migrate_all() );
}
/**
* Registers a route to migrate the post type.
*/
public function register_route_migrate_post_type() {
register_rest_route(
block_lab()->get_slug(),
'migrate-post-type',
[
'methods' => 'POST',
'callback' => [ $this, 'get_migrate_post_type_response' ],
'permission_callback' => function() {
return current_user_can( Submenu::MIGRATION_CAPABILITY );
},
]
);
}
/**
* Gets the REST API response for the post type migration.
*
* @return WP_REST_Response The response to the request.
*/
public function get_migrate_post_type_response() {
return rest_ensure_response( ( new Post_Type( 'block_lab', 'block-lab', 'block_lab', 'genesis_custom_block', 'genesis-custom-blocks', 'genesis_custom_blocks' ) )->migrate_all() );
}
/**
* Gets the directory and file of the new plugin to install.
*
* @return string The plugin file.
*/
public function get_new_plugin_file() {
if ( ! empty( get_transient( Subscription_Api::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK ) ) ) {
return 'genesis-custom-blocks-pro/genesis-custom-blocks-pro.php';
}
return 'genesis-custom-blocks/genesis-custom-blocks.php';
}
}
<?php
/**
* Displays a migration notice.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Block_Lab\Component_Abstract;
/**
* Class Notice
*/
class Notice extends Component_Abstract {
/**
* The AJAX action to dismiss the migration notice.
*
* @var string
*/
const NOTICE_AJAX_ACTION = 'bl_dismiss_migration_notice';
/**
* The action of the migration notice nonce.
*
* @var string
*/
const NOTICE_NONCE_ACTION = 'bl-migration-nonce';
/**
* The name of the migration notice nonce.
*
* @var string
*/
const NOTICE_NONCE_NAME = 'bl-migration-nonce-name';
/**
* The slug of the stylesheet for the migration notice.
*
* @var string
*/
const NOTICE_STYLE_SLUG = 'block-lab-migration-notice-style';
/**
* The slug of the script for the migration notice.
*
* @var string
*/
const NOTICE_SCRIPT_SLUG = 'block-lab-migration-notice-script';
/**
* The user meta key to store whether a user has dismissed the migration notice.
*
* @var string
*/
const NOTICE_USER_META_KEY = 'block_lab_show_migration_notice';
/**
* The user meta value stored if a user has dismissed the migration notice.
*
* @var string
*/
const NOTICE_DISMISSED_META_VALUE = 'dismissed';
/**
* The capability required to see the notice.
*
* @var string
*/
const NOTICE_CAPABILITY = 'install_plugins';
/**
* Adds an action for the notice.
*/
public function register_hooks() {
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
add_action( 'wp_ajax_' . self::NOTICE_AJAX_ACTION, [ $this, 'ajax_handler_migration_notice' ] );
}
/**
* Outputs the migration notice if this is on the right page and the user has the right permission.
*/
public function render_migration_notice() {
if ( ! $this->should_display_migration_notice() ) {
return;
}
// @todo: verify that this doc page exists, or change it to one that does exist.
$learn_more_link = 'https://getblocklab.com/docs/genesis-custom-blocks';
$migration_url = add_query_arg(
[
'post_type' => block_lab()->get_post_type_slug(),
'page' => 'block-lab-migration',
],
admin_url( 'edit.php' )
);
?>
<div id="bl-migration-notice" class="notice notice-info bl-notice-migration">
<?php wp_nonce_field( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME, false ); ?>
<div class="bl-migration-copy">
<p>
<?php
printf(
/* translators: %1$s: the plugin name */
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' ),
sprintf(
'<strong>%1$s</strong>',
esc_html__( 'Genesis Custom Blocks', 'block-lab' )
)
);
?>
<a target="_blank" rel="noopener noreferrer" class="bl-notice-migration__learn-more" href="<?php echo esc_url( $learn_more_link ); ?>">
<?php esc_html_e( 'Learn more', 'block-lab' ); ?>
</a>
</p>
</div>
<button id="bl-notice-not-now" href="#" class="bl-notice-option button button-secondary">
<?php esc_html_e( 'Not Now', 'block-lab' ); ?>
</button>
<a href="<?php echo esc_url( $migration_url ); ?>" class="bl-notice-option button button-primary">
<?php esc_html_e( 'Migrate', 'block-lab' ); ?>
</a>
</div>
<div id="bl-not-now-notice" class="notice notice-info bl-notice-migration bl-hidden">
<div class="bl-migration-copy">
<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>
</div>
<a href="#" id="bl-notice-ok" class="bl-notice-option">
<?php esc_html_e( 'Okay', 'block-lab' ); ?>
</a>
</div>
<?php
}
/**
* Enqueues the migration notice assets.
*/
public function enqueue_assets() {
if ( ! $this->should_display_migration_notice() ) {
return;
}
wp_enqueue_style(
self::NOTICE_STYLE_SLUG,
$this->plugin->get_url( 'css/admin.migration-notice.css' ),
[],
$this->plugin->get_version()
);
wp_enqueue_script(
self::NOTICE_SCRIPT_SLUG,
$this->plugin->get_url( 'js/admin.migration-notice.js' ),
[],
$this->plugin->get_version(),
true
);
}
/**
* Gets whether the migration notice should display.
*
* This should display on Block Lab > Content Blocks,
* /wp-admin/plugins.php, the Dashboard, and Block Lab > Settings.
*
* @return bool Whether the migration notice should display.
*/
public function should_display_migration_notice() {
if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
return false;
}
// If the user has dismissed the notice, it shouldn't appear again.
if ( self::NOTICE_DISMISSED_META_VALUE === get_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, true ) ) {
return false;
}
$screen = get_current_screen();
return (
( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && 'block_lab' === $screen->post_type )
||
( isset( $screen->base ) && in_array( $screen->base, [ 'plugins', 'dashboard', 'block_lab_page_block-lab-settings' ], true ) )
);
}
/**
* Handles an AJAX request to not display the notice.
*
* This stores in the user meta the fact that the notice was dismissed,
* so it's not displayed again.
*/
public function ajax_handler_migration_notice() {
check_ajax_referer( self::NOTICE_NONCE_ACTION, self::NOTICE_NONCE_NAME );
if ( ! current_user_can( self::NOTICE_CAPABILITY ) ) {
wp_send_json_error();
}
update_user_meta( get_current_user_id(), self::NOTICE_USER_META_KEY, self::NOTICE_DISMISSED_META_VALUE );
wp_send_json_success();
}
}
<?php
/**
* Post_Content.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
/**
* Class Post_Content
*/
class Post_Content {
/**
* The previous namespace of the block.
*
* @var string
*/
private $previous_block_namespace;
/**
* The new namespace of the block.
*
* @var string
*/
private $new_block_namespace;
/**
* Post_Content constructor.
*
* @param string $previous_block_namespace Previous namespace of the blocks.
* @param string $new_block_namespace New namespace of the blocks.
*/
public function __construct( $previous_block_namespace, $new_block_namespace ) {
$this->previous_block_namespace = $previous_block_namespace;
$this->new_block_namespace = $new_block_namespace;
}
/**
* Migrates all of the block namespaces in all of the posts that have Block Lab blocks.
*
* @return array|WP_Error The result of the migration, or a WP_Error if it failed.
*/
public function migrate_all() {
$success_count = 0;
$error_count = 0;
$errors = new WP_Error();
$max_allowed_errors = 20;
$posts = $this->query_for_posts();
while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
foreach ( $posts as $post ) {
if ( isset( $post->ID ) ) {
$migrated_post = $this->migrate_single( $post->ID );
if ( is_wp_error( $migrated_post ) ) {
$error_count++;
$errors->add( $migrated_post->get_error_code(), $migrated_post->get_error_message() );
} else {
$success_count++;
}
}
}
$posts = $this->query_for_posts();
}
$is_success = $error_count < $max_allowed_errors;
if ( ! $is_success ) {
return $errors;
}
$results = [
'successCount' => $success_count,
'errorCount' => $error_count,
];
if ( $errors->has_errors() ) {
$results['errorMessage'] = $errors->get_error_message();
}
return $results;
}
/**
* Migrates the block namespaces in post_content.
*
* Blocks are stored in the post_content of a post with a namespace,
* like '<!-- wp:block-lab/test-image {"example-image":8} /-->'.
* In that case, 'block-lab' needs to be changed to the new namespace.
* But nothing else in the block should be changed.
* The block pattern is mainly taken from Core.
*
* @see https://github.com/WordPress/wordpress-develop/blob/78d1ab2ed40093a5bd2a75b01ceea37811739f55/src/wp-includes/class-wp-block-parser.php#L413
*
* @param int $post_id The ID of the post to convert.
* @return int|WP_Error The post ID that was changed, or a WP_Error on failure.
*/
public function migrate_single( $post_id ) {
$post = get_post( $post_id );
if ( ! isset( $post->ID ) ) {
return new WP_Error(
'invalid_post_id',
__( 'Invalid post ID', 'block-lab' )
);
}
$new_post_content = preg_replace(
'#(<!--\s+wp:)(' . sanitize_key( $this->previous_block_namespace ) . ')(/[a-z][a-z0-9_-]*)#s',
'$1' . sanitize_key( $this->new_block_namespace ) . '$3',
$post->post_content
);
return wp_update_post(
[
'ID' => $post->ID,
'post_content' => wp_slash( $new_post_content ),
],
true
);
}
/**
* Gets posts that have Block Lab blocks in their post_content.
*
* Queries for posts that have wp:block-lab/ in the post content,
* meaning they probably have a Block Lab block.
* Excludes revision posts, as this could overwrite the entire history.
* This will allow users to go back to the content before it was migrated.
*
* @return array The posts that were found.
*/
private function query_for_posts() {
global $wpdb;
$query_limit = 10;
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_type != %s AND post_content LIKE %s LIMIT %d",
'revision',
'%' . $wpdb->esc_like( 'wp:' . $this->previous_block_namespace . '/' ) . '%',
absint( $query_limit )
)
);
}
}
<?php
/**
* Post_Type.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
use WP_Post;
use WP_Query;
/**
* Class Post_Type
*/
class Post_Type {
/**
* The previous slug of the custom post type (in Block Lab).
*
* @var string
*/
private $previous_post_type_slug;
/**
* The previous namespace of the block.
*
* @var string
*/
private $previous_block_namespace;
/**
* The previous default block icon.
*
* @var string
*/
private $previous_default_icon;
/**
* The new namespace of the block.
*
* @var string
*/
private $new_block_namespace;
/**
* The new slug of the custom post type (not in Block Lab).
*
* @var string
*/
private $new_post_type_slug;
/**
* The new default block icon.
*
* @var string
*/
private $new_default_icon;
/**
* Post_Type constructor.
*
* @param string $previous_post_type_slug Previous slug of the custom post type.
* @param string $previous_block_namespace Previous block namespace.
* @param string $previous_default_icon Previous default block icon.
* @param string $new_post_type_slug New slug of the custom post type.
* @param string $new_block_namespace New namespace of the block.
* @param string $new_default_icon New default block icon.
*/
public function __construct( $previous_post_type_slug, $previous_block_namespace, $previous_default_icon, $new_post_type_slug, $new_block_namespace, $new_default_icon ) {
$this->previous_post_type_slug = $previous_post_type_slug;
$this->previous_block_namespace = $previous_block_namespace;
$this->previous_default_icon = $previous_default_icon;
$this->new_post_type_slug = $new_post_type_slug;
$this->new_block_namespace = $new_block_namespace;
$this->new_default_icon = $new_default_icon;
}
/**
* Migrates all of the custom post type posts to the new post_type slug and block namespace.
*
* These each store a config for a custom block,
* they aren't blocks that users entered into the block editor.
*
* @return array|WP_Error An array on success, a WP_Error on failure.
*/
public function migrate_all() {
$posts = $this->query_for_posts();
$success_count = 0;
$error_count = 0;
$max_allowed_errors = 20;
$errors = new WP_Error();
while ( ! empty( $posts ) && $error_count < $max_allowed_errors ) {
foreach ( $posts as $post ) {
$migration_result = $this->migrate_single( $post );
if ( is_wp_error( $migration_result ) ) {
$error_count++;
$errors->add( $migration_result->get_error_code(), $migration_result->get_error_message() );
} else {
$success_count++;
}
}
$posts = $this->query_for_posts();
}
$is_success = ! empty( $success_count ) || ( empty( $success_count ) && empty( $error_count ) );
if ( ! $is_success ) {
return $errors;
}
return [
'successCount' => $success_count,
'errorCount' => $error_count,
];
}
/**
* Migrates the custom post type post to the new post_type slug and block namespace.
*
* 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
* The post_content of the CPT has a configuration for a block like:
* '{"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"}}}}'
* The beginning of this has the 'block-lab' namespace, which this changes to the new namespace.
*
* @param WP_Post $post The post to convert.
* @return true|WP_Error True on success, WP_Error on failure.
*/
public function migrate_single( WP_Post $post ) {
global $wpdb;
$block = json_decode( $post->post_content, true );
if ( JSON_ERROR_NONE !== json_last_error() || empty( $block ) ) {
return new WP_Error(
'block_invalid_json',
__( 'The block looks to be invalid JSON', 'block-lab' )
);
}
$block_keys = array_keys( $block );
$old_block_name = reset( $block_keys );
if ( empty( $block[ $old_block_name ] ) ) {
return new WP_Error(
'invalid_block_name',
__( 'The block name looks to be invalid', 'block-lab' )
);
}
$block_contents = $block[ $old_block_name ];
if ( isset( $block_contents['icon'] ) && $this->previous_default_icon === $block_contents['icon'] ) {
$block_contents['icon'] = $this->new_default_icon;
}
if ( empty( $block_contents['icon'] ) ) {
$block_contents['icon'] = $this->new_default_icon;
}
$new_block_name = preg_replace( '#^' . $this->previous_block_namespace . '(?=/)#', $this->new_block_namespace, $old_block_name );
$new_block = [ $new_block_name => $block_contents ];
$rows_updated = $wpdb->update(
$wpdb->posts,
[
'post_type' => sanitize_key( $this->new_post_type_slug ),
'post_content' => wp_json_encode( $new_block ),
],
[
'ID' => $post->ID,
]
);
clean_post_cache( $post->ID );
if ( empty( $rows_updated ) ) {
return new WP_Error(
'post_not_updated',
__( 'The post was not updated', 'block-lab' )
);
}
return true;
}
/**
* Gets the posts of the previous post_type.
*
* This query won't find posts that were already migrated, as the migration changes the 'post_type'.
* So this doesn't need an 'offset' parameter.
*
* @return WP_Post[] The posts that were found.
*/
private function query_for_posts() {
$query = new WP_Query(
[
'post_type' => $this->previous_post_type_slug,
'posts_per_page' => 10,
'post_status' => 'any',
]
);
return $query->posts;
}
}
<?php
/**
* Migration submenu.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use Block_Lab\Component_Abstract;
use Block_Lab\Admin\License;
/**
* Class Post_Type
*/
class Submenu extends Component_Abstract {
/**
* The menu slug for the migration menu.
*
* @var string
*/
const MENU_SLUG = 'block-lab-migration';
/**
* The user capability to migrate posts and post content.
*
* @var string
*/
const MIGRATION_CAPABILITY = 'edit_others_posts';
/**
* The query var to deactivate this plugin and activate the new one.
*
* @var string
*/
const QUERY_VAR_DEACTIVATE_AND_GCB_PAGE = 'bl_deactivate_and_gcb';
/**
* The query var to deactivate this plugin and activate the new one.
*
* @var string
*/
const NONCE_ACTION_DEACTIVATE = 'deactivate_bl_and_activate_new';
/**
* Adds the actions.
*/
public function register_hooks() {
add_action( 'admin_menu', [ $this, 'add_submenu_page' ], 9 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'admin_bar_init', [ $this, 'maybe_activate_plugin' ] );
}
/**
* Adds the submenu page for migration.
*/
public function add_submenu_page() {
if ( $this->user_can_view_migration_page() ) {
add_submenu_page(
'edit.php?post_type=block_lab',
__( 'Migrate to Genesis Custom Blocks', 'block-lab' ),
__( 'Migrate', 'block-lab' ),
'manage_options',
self::MENU_SLUG,
[ $this, 'render_page' ]
);
}
}
/**
* Adds the scripts for the submenu.
*/
public function enqueue_scripts() {
$page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_STRING );
// Only enqueue if on the migration page.
if ( self::MENU_SLUG === $page && $this->user_can_view_migration_page() ) {
wp_enqueue_style(
self::MENU_SLUG,
block_lab()->get_url( 'css/admin.migration.css' ),
[],
block_lab()->get_version()
);
$script_config = require block_lab()->get_path( 'js/admin.migration.asset.php' );
wp_enqueue_script(
self::MENU_SLUG,
block_lab()->get_url( 'js/admin.migration.js' ),
$script_config['dependencies'],
$script_config['version'],
true
);
$gcb_url = add_query_arg(
[
self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE => true,
'_wpnonce' => wp_create_nonce( self::NONCE_ACTION_DEACTIVATE ),
],
admin_url()
);
$is_pro = block_lab()->is_pro();
$genesis_pro_subscription_key = get_option( Subscription_Api::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
$script_data = [
'isPro' => $is_pro,
'gcbUrl' => $gcb_url,
];
if ( $genesis_pro_subscription_key ) {
$script_data['genesisProKey'] = $genesis_pro_subscription_key;
}
wp_add_inline_script(
self::MENU_SLUG,
'const blockLabMigration = ' . wp_json_encode( $script_data ) . ';',
'before'
);
}
}
/**
* Gets whether the current user can view the migration page.
*
* @return bool Whether the user can view the migration page.
*/
public function user_can_view_migration_page() {
return current_user_can( 'install_plugins' ) && current_user_can( self::MIGRATION_CAPABILITY );
}
/**
* Renders the submenu page.
*/
public function render_page() {
echo '<div class="bl-migration__content"></div>';
}
/**
* Conditionally deactivates this plugin and goes to the Genesis Custom Blocks page.
*
* The logic to deactivate the plugin was mainly copied from Core.
* https://github.com/WordPress/wordpress-develop/blob/61803a37a41eca95efe964c7e02c768de6df75fa/src/wp-admin/plugins.php#L196-L221
*/
public function maybe_activate_plugin() {
$previous_plugin_file = 'block-lab/block-lab.php';
if ( empty( $_GET[ self::QUERY_VAR_DEACTIVATE_AND_GCB_PAGE ] ) ) {
return;
}
if ( ! current_user_can( 'deactivate_plugin', $previous_plugin_file ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this plugin.', 'block-lab' ) );
}
check_admin_referer( self::NONCE_ACTION_DEACTIVATE );
if ( ! is_network_admin() && is_plugin_active_for_network( $previous_plugin_file ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to deactivate this network-active plugin.', 'block-lab' ) );
}
deactivate_plugins( $previous_plugin_file, false, is_network_admin() );
if ( ! is_network_admin() ) {
update_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_option( 'recently_activated' ) );
} else {
update_site_option( 'recently_activated', [ $previous_plugin_file => time() ] + (array) get_site_option( 'recently_activated' ) );
}
// Go to the Genesis Custom Blocks page.
wp_safe_redirect(
add_query_arg(
'post_type',
'genesis_custom_block',
admin_url( 'edit.php' )
)
);
}
}
<?php
/**
* Verifies the Genesis Pro subscription, and saves the link to download GCB Pro.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use Block_Lab\Component_Abstract;
/**
* Class Subscription_Api
*/
class Subscription_Api extends Component_Abstract {
/**
* Option name where the subscription key is stored for Genesis Pro plugins.
*
* @var string
*/
const OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY = 'genesis_pro_subscription_key';
/**
* Transient name where the subscription endpoint response is stored.
*
* @var string
*/
const TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK = 'genesis_custom_blocks_pro_download_link';
/**
* Adds the component action.
*/
public function register_hooks() {
add_action( 'rest_api_init', [ $this, 'register_route_update_subscription_key' ] );
}
/**
* Registers a route to update the subscription key.
*/
public function register_route_update_subscription_key() {
register_rest_route(
block_lab()->get_slug(),
'update-subscription-key',
[
'methods' => 'POST',
'callback' => [ $this, 'get_update_subscription_key_response' ],
'permission_callback' => function() {
return current_user_can( 'manage_options' );
},
'accept_json' => true,
]
);
}
/**
* Gets the REST API response to the request to update the subscription key.
*
* @param WP_REST_Request $data Data sent in the POST request.
* @return WP_REST_Response|WP_Error A WP_REST_Response on success, WP_Error on failure.
*/
public function get_update_subscription_key_response( $data ) {
$key = $data->get_param( 'subscriptionKey' );
if ( empty( $key ) ) {
$this->delete_subscription_data();
return new WP_Error( 'empty_subscription_key', __( 'Empty subscription key', 'block-lab' ) );
}
$sanitized_key = $this->sanitize_subscription_key( $key );
$subscription_response = $this->get_subscription_response( $sanitized_key );
if ( $subscription_response->is_valid() && ! empty( $subscription_response->get_product_info()->download_link ) ) {
$was_option_update_successful = update_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY, $sanitized_key );
if ( ! $was_option_update_successful ) {
$existing_option = get_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
// update_option() will return false when trying to save the same option that's already saved.
// In that case, there's no need for an error, but any other failure should be an error.
if ( $sanitized_key !== $existing_option ) {
$this->delete_subscription_data();
return new WP_Error( 'option_not_updated', __( 'The option was not updated', 'block-lab' ) );
}
}
set_transient(
self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK,
esc_url_raw( $subscription_response->get_product_info()->download_link )
);
return rest_ensure_response( [ 'success' => true ] );
} else {
$this->delete_subscription_data();
return new WP_Error(
$subscription_response->get_error_code(),
$this->get_subscription_invalid_message( $subscription_response->get_error_code() )
);
}
}
/**
* Deletes the stored Genesis Pro key and the GCB Pro download link.
*/
public function delete_subscription_data() {
delete_option( self::OPTION_NAME_GENESIS_PRO_SUBSCRIPTION_KEY );
delete_transient( self::TRANSIENT_NAME_GCB_PRO_DOWNLOAD_LINK );
}
/**
* Gets a new subscription response.
*
* @param string $key The subscription key to check.
* @return Subscription_Response The subscription response.
*/
public function get_subscription_response( $key ) {
return new Subscription_Response( $key );
}
/**
* Admin message for incorrect subscription details.
*
* @param string $error_code The error code from the endpoint.
* @return string The error message to display.
*/
public function get_subscription_invalid_message( $error_code ) {
switch ( $error_code ) {
case 'key-unknown':
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' );
case 'key-invalid':
return esc_html__( 'The subscription key you entered is invalid. Get your subscription key in the WP Engine Account Portal.', 'block-lab' );
case 'key-deleted':
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' );
case 'subscription-expired':
return esc_html__( 'Your Genesis Pro subscription has expired. Please renew it.', 'block-lab' );
case 'subscription-notfound':
return esc_html__( 'A valid subscription for your subscription key was not found. Please contact support.', 'block-lab' );
case 'product-unknown':
return esc_html__( 'The product you requested information for is unknown. Please contact support.', 'block-lab' );
default:
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' );
}
}
/**
* Gets the sanitized subscription key.
*
* @param string $subscription_key The subscription key.
* @return string The sanitized key.
*/
public function sanitize_subscription_key( $subscription_key ) {
return preg_replace( '/[^A-Za-z0-9_-]/', '', $subscription_key );
}
}
<?php
/**
* The Genesis Pro subscription response.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Admin\Migration;
use stdClass;
/**
* Class Subscription_Response
*/
class Subscription_Response {
/**
* Endpoint to validate the Genesis Pro subscription key.
*
* @var string
*/
const ENDPOINT = 'https://wp-product-info.wpesvc.net/v1/plugins/genesis-custom-blocks-pro/subscriptions/';
/**
* The code expected in a success response.
*
* @var string
*/
const SUCCESS_CODE = 200;
/**
* Whether the subscription key is valid.
*
* @var bool
*/
private $is_valid = false;
/**
* The error code, if any.
*
* @var string|null
*/
private $error_code;
/**
* The product info.
*
* @var stdClass|null
*/
private $product_info;
/**
* Constructs the class.
*
* @param string $subscription_key The subscription key to check.
*/
public function __construct( $subscription_key ) {
$this->evaluate( $subscription_key );
}
/**
* Evaluates the response, storing the response body and a possible error message.
*
* @param string $subscription_key The subscription key to check.
*/
public function evaluate( $subscription_key ) {
$response = wp_remote_get(
self::ENDPOINT . $subscription_key,
[
'timeout' => defined( 'DOING_CRON' ) && DOING_CRON ? 30 : 3,
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ),
'body' => [
'version' => block_lab()->get_version(),
],
]
);
if ( is_wp_error( $response ) || self::SUCCESS_CODE !== wp_remote_retrieve_response_code( $response ) ) {
if ( is_wp_error( $response ) ) {
$this->error_code = $response->get_error_code();
} else {
$response_body = json_decode( wp_remote_retrieve_body( $response ), false );
$this->error_code = ! empty( $response_body->error_code ) ? $response_body->error_code : 'unknown';
}
return;
}
$this->is_valid = true;
$this->product_info = new stdClass();
$response_body = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! is_object( $response_body ) ) {
$response_body = new stdClass();
}
$this->product_info = $response_body;
}
/**
* Gets whether the subscription key is valid.
*
* @return bool
*/
public function is_valid() {
return $this->is_valid;
}
/**
* Gets the error code, if any.
*
* @return string|null
*/
public function get_error_code() {
return $this->error_code;
}
/**
* Gets the product info, or null if there isn't any.
*
* @return stdClass|null
*/
public function get_product_info() {
return $this->product_info;
}
}
<?php
/**
* Plugin Autoloader
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
/**
* Register Autoloader
*/
spl_autoload_register(
function ( $class ) {
// Assume we're using namespaces (because that's how the plugin is structured).
$namespace = explode( '\\', $class );
$root = array_shift( $namespace );
// If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'.
$class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-';
// If we're not in the plugin's namespace then just return.
if ( 'Block_Lab' !== $root ) {
return;
}
// Class name is the last part of the FQN.
$class_name = array_pop( $namespace );
// Remove "Trait" from the class name.
if ( 'trait-' === $class_trait ) {
$class_name = str_replace( 'Trait', '', $class_name );
}
$filename = $class_trait . $class_name . '.php';
// For file naming, the namespace is everything but the class name and the root namespace.
$namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) );
// Because WordPress file naming conventions are odd.
$filename = strtolower( str_replace( '_', '-', $filename ) );
$namespace = strtolower( str_replace( '_', '-', $namespace ) );
// Get the path to our files.
$directory = dirname( __FILE__ );
if ( ! empty( $namespace ) ) {
$directory .= DIRECTORY_SEPARATOR . $namespace;
}
$file = $directory . DIRECTORY_SEPARATOR . $filename;
if ( file_exists( $file ) ) {
require_once $file;
}
}
);
<?php
/**
* Block.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks;
/**
* Class Block
*/
class Block {
/**
* Block name (slug).
*
* @var string
*/
public $name = '';
/**
* Block title.
*
* @var string
*/
public $title = '';
/**
* Exclude the block in these post types.
*
* @var array
*/
public $excluded = [];
/**
* Icon.
*
* @var string
*/
public $icon = '';
/**
* Category. An array containing the keys slug, title, and icon.
*
* @var array
*/
public $category = [
'slug' => '',
'title' => '',
'icon' => '',
];
/**
* Block keywords.
*
* @var string[]
*/
public $keywords = [];
/**
* Block fields.
*
* @var Field[]
*/
public $fields = [];
/**
* Block constructor.
*
* @param int|bool $post_id Post ID.
*
* @return void
*/
public function __construct( $post_id = false ) {
if ( ! $post_id ) {
return;
}
$post = get_post( $post_id );
if ( ! $post instanceof \WP_Post ) {
return;
}
$this->name = $post->post_name;
$this->from_json( $post->post_content );
}
/**
* Construct the Block from a JSON blob.
*
* @param string $json JSON blob.
*
* @return void
*/
public function from_json( $json ) {
$json = json_decode( $json, true );
if ( ! isset( $json[ 'block-lab/' . $this->name ] ) ) {
return;
}
$config = $json[ 'block-lab/' . $this->name ];
$this->from_array( $config );
}
/**
* Construct the Block from a config array.
*
* @param array $config An array containing field parameters.
*
* @return void
*/
public function from_array( $config ) {
if ( isset( $config['name'] ) ) {
$this->name = $config['name'];
}
if ( isset( $config['title'] ) ) {
$this->title = $config['title'];
}
if ( isset( $config['excluded'] ) ) {
$this->excluded = $config['excluded'];
}
if ( isset( $config['icon'] ) ) {
$this->icon = $config['icon'];
}
if ( isset( $config['category'] ) ) {
$this->category = $config['category'];
if ( ! is_array( $this->category ) ) {
$this->category = $this->get_category_array_from_slug( $this->category );
}
}
if ( isset( $config['keywords'] ) ) {
$this->keywords = $config['keywords'];
}
if ( isset( $config['fields'] ) ) {
foreach ( $config['fields'] as $key => $field ) {
$this->fields[ $key ] = new Field( $field );
}
}
}
/**
* Get the Block as a JSON blob.
*
* @return string
*/
public function to_json() {
$config['name'] = $this->name;
$config['title'] = $this->title;
$config['excluded'] = $this->excluded;
$config['icon'] = $this->icon;
$config['category'] = $this->category;
$config['keywords'] = $this->keywords;
$config['fields'] = [];
foreach ( $this->fields as $key => $field ) {
$config['fields'][ $key ] = $field->to_array();
}
return wp_json_encode( [ 'block-lab/' . $this->name => $config ], JSON_UNESCAPED_UNICODE );
}
/**
* This is a backwards compatibility fix.
*
* Block categories used to be saved as strings, but were always included in
* the default list of categories, so we can find them.
*
* It's not possible to use get_block_categories() here, as Block's are
* sometimes instantiated before that function is available.
*
* @param string $slug The category slug to find.
*
* @return array
*/
public function get_category_array_from_slug( $slug ) {
return [
'slug' => $slug,
'title' => ucwords( $slug, '-' ),
'icon' => null,
];
}
}
<?php
/**
* Block Field.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks;
/**
* Class Field
*/
class Field {
/**
* Field name (slug).
*
* @var string
*/
public $name = '';
/**
* Field label.
*
* @var string
*/
public $label = '';
/**
* Field control type.
*
* @var string
*/
public $control = 'text';
/**
* Field variable type.
*
* @var string
*/
public $type = 'string';
/**
* Field order.
*
* @var int
*/
public $order = 0;
/**
* Field settings.
*
* @var array
*/
public $settings = [];
/**
* Field constructor.
*
* @param array $config An associative array with keys corresponding to the Field's properties.
*/
public function __construct( $config = [] ) {
$this->from_array( $config );
}
/**
* Get field properties as an array, ready to be stored as JSON.
*
* @return array
*/
public function to_array() {
$config = [
'name' => $this->name,
'label' => $this->label,
'control' => $this->control,
'type' => $this->type,
'order' => $this->order,
];
$config = array_merge(
$config,
$this->settings
);
// Handle the sub-fields setting used by the Repeater.
if ( isset( $this->settings['sub_fields'] ) ) {
/**
* Recursively loop through sub-fields.
*
* @var string $key The name of the sub-field's parent.
* @var Field $field The sub-field.
*/
foreach ( $this->settings['sub_fields'] as $key => $field ) {
$config['sub_fields'][ $key ] = $field->to_array();
}
}
return $config;
}
/**
* Set field properties from an array, after being stored as JSON.
*
* @param array $config An array containing field parameters.
*/
public function from_array( $config ) {
if ( isset( $config['name'] ) ) {
$this->name = $config['name'];
}
if ( isset( $config['label'] ) ) {
$this->label = $config['label'];
}
if ( isset( $config['control'] ) ) {
$this->control = $config['control'];
}
if ( isset( $config['type'] ) ) {
$this->type = $config['type'];
}
if ( isset( $config['order'] ) ) {
$this->order = $config['order'];
}
if ( isset( $config['settings'] ) ) {
$this->settings = $config['settings'];
}
if ( ! isset( $config['type'] ) ) {
$control_class_name = 'Block_Lab\\Blocks\\Controls\\';
$control_class_name .= ucwords( $this->control, '_' );
if ( class_exists( $control_class_name ) ) {
/**
* An instance of the control, to retrieve the correct type.
*
* @var Control_Abstract $control_class
*/
$control_class = new $control_class_name();
$this->type = $control_class->type;
}
}
// Add any other non-default keys to the settings array.
$field_defaults = [ 'name', 'label', 'control', 'type', 'order', 'settings' ];
$field_settings = array_diff( array_keys( $config ), $field_defaults );
foreach ( $field_settings as $settings_key ) {
$this->settings[ $settings_key ] = $config[ $settings_key ];
}
// Handle the sub-fields setting used by the Repeater.
if ( isset( $this->settings['sub_fields'] ) ) {
/**
* Recursively loop through sub-fields.
*/
foreach ( $this->settings['sub_fields'] as $key => $field ) {
$this->settings['sub_fields'][ $key ] = new Field( $field );
}
}
}
/**
* Return the value with the correct variable type.
*
* @param mixed $value The value to typecast.
* @return mixed
*/
public function cast_value( $value ) {
switch ( $this->type ) {
case 'string':
$value = strval( $value );
break;
case 'textarea':
$value = strval( $value );
if ( isset( $this->settings['new_lines'] ) ) {
if ( 'autop' === $this->settings['new_lines'] ) {
$value = wpautop( $value );
}
if ( 'autobr' === $this->settings['new_lines'] ) {
$value = nl2br( $value );
}
}
break;
case 'boolean':
if ( 1 === $value ) {
$value = true;
}
break;
case 'integer':
$value = intval( $value );
break;
case 'array':
if ( ! $value ) {
$value = [];
} else {
$value = (array) $value;
}
break;
}
return $value;
}
/**
* Gets the field value as a string.
*
* @param mixed $value The field value.
*
* @return string $value The value to echo.
*/
public function cast_value_to_string( $value ) {
if ( is_array( $value ) ) {
return implode( ', ', $value );
}
if ( true === $value ) {
return __( 'Yes', 'block-lab' );
}
if ( false === $value ) {
return __( 'No', 'block-lab' );
}
return strval( $value );
}
}
<?php
/**
* Loader initiates the loading of new blocks.
*
* @package Block_Lab
*/
namespace Block_Lab\Blocks;
use Block_Lab\Component_Abstract;
/**
* Class Loader
*/
class Loader extends Component_Abstract {
/**
* Asset paths and urls for blocks.
*
* @var array
*/
protected $assets = [];
/**
* An associative array of block config data for the blocks that will be registered.
*
* The key of each item in the array is the block name.
*
* @var array
*/
protected $blocks = [];
/**
* A data store for sharing data to helper functions.
*
* @var array
*/
protected $data = [];
/**
* Load the Loader.
*
* @return $this
*/
public function init() {
$this->assets = [
'path' => [
'entry' => $this->plugin->get_path( 'js/editor.blocks.js' ),
'editor_style' => $this->plugin->get_path( 'css/blocks.editor.css' ),
],
'url' => [
'entry' => $this->plugin->get_url( 'js/editor.blocks.js' ),
'editor_style' => $this->plugin->get_url( 'css/blocks.editor.css' ),
],
];
return $this;
}
/**
* Register all the hooks.
*/
public function register_hooks() {
/**
* Gutenberg JS block loading.
*/
add_action( 'enqueue_block_editor_assets', $this->get_callback( 'editor_assets' ) );
/**
* Gutenberg custom categories.
*/
add_filter( 'block_categories', $this->get_callback( 'register_categories' ) );
/**
* Block retrieval, must run before dynamic_block_loader().
*/
add_action( 'init', $this->get_callback( 'retrieve_blocks' ) );
/**
* PHP block loading.
*/
add_action( 'init', $this->get_callback( 'dynamic_block_loader' ) );
}
/**
* Retrieve data from the Loader's data store.
*
* @param string $key The data key to retrieve.
* @return mixed
*/
public function get_data( $key ) {
$data = false;
if ( isset( $this->data[ $key ] ) ) {
$data = $this->data[ $key ];
}
/**
* Filters the data that gets returned.
*
* @param mixed $data The data from the Loader's data store.
* @param string $key The key for the data being retreived.
*/
$data = apply_filters( 'block_lab_data', $data, $key );
/**
* Filters the data that gets returned, specifically for a single key.
*
* @param mixed $data The data from the Loader's data store.
*/
$data = apply_filters( "block_lab_data_{$key}", $data );
return $data;
}
/**
* Gets the callback for an action or filter.
*
* Enables keeping these methods protected,
* while allowing actions and filters to call them.
*
* @param string $method_name The name of the method to get the callback for.
* @return callable An enclosure that calls the function.
*/
protected function get_callback( $method_name ) {
return function( $arg ) use ( $method_name ) {
return call_user_func( [ $this, $method_name ], $arg );
};
}
/**
* Launch the blocks inside Gutenberg.
*/
protected function editor_assets() {
$asset_config = require $this->plugin->get_path( 'js/editor.blocks.asset.php' );
wp_enqueue_script(
'block-lab-blocks',
$this->assets['url']['entry'],
$asset_config['dependencies'],
$asset_config['version'],
true
);
// Add dynamic Gutenberg blocks.
wp_add_inline_script(
'block-lab-blocks',
'const blockLabBlocks = ' . wp_json_encode( $this->blocks ),
'before'
);
// Used to conditionally show notices for blocks belonging to an author.
$author_blocks = get_posts(
[
'author' => get_current_user_id(),
'post_type' => 'block_lab',
// We could use -1 here, but that could be dangerous. 99 is more than enough.
'posts_per_page' => 99,
]
);
$author_block_slugs = wp_list_pluck( $author_blocks, 'post_name' );
wp_localize_script(
'block-lab-blocks',
'blockLab',
[
'authorBlocks' => $author_block_slugs,
'postType' => get_post_type(), // To conditionally exclude blocks from certain post types.
]
);
// Enqueue optional editor only styles.
wp_enqueue_style(
'block-lab-editor-css',
$this->assets['url']['editor_style'],
[],
$this->plugin->get_version()
);
$block_names = wp_list_pluck( $this->blocks, 'name' );
foreach ( $block_names as $block_name ) {
$this->enqueue_block_styles( $block_name, [ 'preview', 'block' ] );
}
$this->enqueue_global_styles();
}
/**
* Loads dynamic blocks via render_callback for each block.
*/
protected function dynamic_block_loader() {
if ( ! function_exists( 'register_block_type' ) ) {
return;
}
foreach ( $this->blocks as $block_name => $block_config ) {
$block = new Block();
$block->from_array( $block_config );
$this->register_block( $block_name, $block );
}
}
/**
* Registers a block.
*
* @param string $block_name The name of the block, including namespace.
* @param Block $block The block to register.
*/
protected function register_block( $block_name, $block ) {
$attributes = $this->get_block_attributes( $block );
// sanitize_title() allows underscores, but register_block_type doesn't.
$block_name = str_replace( '_', '-', $block_name );
// register_block_type doesn't allow slugs starting with a number.
if ( is_numeric( $block_name[0] ) ) {
$block_name = 'block-' . $block_name;
}
register_block_type(
$block_name,
[
'attributes' => $attributes,
// @see https://github.com/WordPress/gutenberg/issues/4671
'render_callback' => function ( $attributes ) use ( $block ) {
return $this->render_block_template( $block, $attributes );
},
]
);
}
/**
* Register custom block categories.
*
* @param array $categories Array of block categories.
*
* @return array
*/
protected function register_categories( $categories ) {
foreach ( $this->blocks as $block_config ) {
if ( ! isset( $block_config['category'] ) ) {
continue;
}
/*
* This is a backwards compatibility fix.
*
* Block categories used to be saved as strings, but were always included in
* the default list of categories, so it's safe to skip them.
*/
if ( ! is_array( $block_config['category'] ) || empty( $block_config['category'] ) ) {
continue;
}
if ( ! in_array( $block_config['category'], $categories, true ) ) {
$categories[] = $block_config['category'];
}
}
return $categories;
}
/**
* Gets block attributes.
*
* @param Block $block The block to get attributes from.
*
* @return array
*/
protected function get_block_attributes( $block ) {
$attributes = [];
// Default Editor attributes (applied to all blocks).
$attributes['className'] = [ 'type' => 'string' ];
foreach ( $block->fields as $field_name => $field ) {
$attributes = $this->get_attributes_from_field( $attributes, $field_name, $field );
}
/**
* Filters a given block's attributes.
*
* These are later passed to register_block_type() in $args['attributes'].
* Removing attributes here can cause 'Error loading block...' in the editor.
*
* @param array[] $attributes The attributes for a block.
* @param array $block Block data, including its name at $block['name'].
*/
return apply_filters( 'block_lab_get_block_attributes', $attributes, $block );
}
/**
* Sets the field values in the attributes, enabling them to appear in the block.
*
* @param array $attributes The attributes in which to store the field value.
* @param string $field_name The name of the field, like 'home-hero'.
* @param Field $field The Field to set the attributes from.
* @return array $attributes The attributes, with the new field value set.
*/
protected function get_attributes_from_field( $attributes, $field_name, $field ) {
$attributes[ $field_name ] = [
'type' => $field->type,
];
if ( ! empty( $field->settings['default'] ) ) {
$attributes[ $field_name ]['default'] = $field->settings['default'];
}
if ( 'array' === $field->type ) {
/**
* This is a workaround to allow empty array values. We unset the default value before registering the
* block so that the default isn't used to auto-correct empty arrays. This allows the default to be
* used only when creating the form.
*/
unset( $attributes[ $field_name ]['default'] );
$items_type = 'repeater' === $field->control ? 'object' : 'string';
$attributes[ $field_name ]['items'] = [ 'type' => $items_type ];
}
return $attributes;
}
/**
* Renders the block provided a template is provided.
*
* @param Block $block The block to render.
* @param array $attributes Attributes to render.
*
* @return mixed
*/
protected function render_block_template( $block, $attributes ) {
$type = 'block';
// This is hacky, but the editor doesn't send the original request along.
$context = filter_input( INPUT_GET, 'context', FILTER_SANITIZE_STRING );
if ( 'edit' === $context ) {
$type = [ 'preview', 'block' ];
}
if ( ! is_admin() ) {
/**
* The block has been added, but its values weren't saved (not even the defaults). This is a phenomenon
* unique to frontend output, as the editor fetches its attributes from the form fields themselves.
*/
$missing_schema_attributes = array_diff_key( $block->fields, $attributes );
foreach ( $missing_schema_attributes as $attribute_name => $schema ) {
if ( isset( $schema->settings['default'] ) ) {
$attributes[ $attribute_name ] = $schema->settings['default'];
}
}
// Similar to the logic above, populate the Repeater control's sub-fields with default values.
foreach ( $block->fields as $field ) {
if ( isset( $field->settings['sub_fields'] ) && isset( $attributes[ $field->name ]['rows'] ) ) {
$sub_field_settings = $field->settings['sub_fields'];
$rows = $attributes[ $field->name ]['rows'];
// In each row, apply a field's default value if a value doesn't exist in the attributes.
foreach ( $rows as $row_index => $row ) {
foreach ( $sub_field_settings as $sub_field_name => $sub_field ) {
if ( ! isset( $row[ $sub_field_name ] ) && isset( $sub_field_settings[ $sub_field_name ]->settings['default'] ) ) {
$rows[ $row_index ][ $sub_field_name ] = $sub_field_settings[ $sub_field_name ]->settings['default'];
}
}
}
$attributes[ $field->name ]['rows'] = $rows;
}
}
$this->enqueue_block_styles( $block->name, 'block' );
/**
* The wp_enqueue_style function handles duplicates, so we don't need to worry about multiple blocks
* loading the global styles more than once.
*/
$this->enqueue_global_styles();
}
$this->data['attributes'] = $attributes;
$this->data['config'] = $block;
if ( ! is_admin() && ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) && ! wp_doing_ajax() ) {
/**
* Runs in the 'render_callback' of the block, and only on the front-end, not in the editor.
*
* The block's name (slug) is in $block->name.
* If a block depends on a JavaScript file,
* this action is a good place to call wp_enqueue_script().
* In that case, pass true as the 5th argument ($in_footer) to wp_enqueue_script().
*
* @param Block $block The block that is rendered.
* @param array $attributes The block attributes.
*/
do_action( 'block_lab_render_template', $block, $attributes );
/**
* Runs in a block's 'render_callback', and only on the front-end.
*
* Same as the action above, but with a dynamic action name that has the block name.
*
* @param Block $block The block that is rendered.
* @param array $attributes The block attributes.
*/
do_action( "block_lab_render_template_{$block->name}", $block, $attributes );
}
ob_start();
$this->block_template( $block->name, $type );
$output = ob_get_clean();
return $output;
}
/**
* Enqueues styles for the block.
*
* @param string $name The name of the block (slug as defined in UI).
* @param string|array $type The type of template to load.
*/
protected function enqueue_block_styles( $name, $type = 'block' ) {
$locations = [];
$types = (array) $type;
foreach ( $types as $type ) {
$locations = array_merge(
$locations,
block_lab()->get_stylesheet_locations( $name, $type )
);
}
$stylesheet_path = block_lab()->locate_template( $locations );
$stylesheet_url = block_lab()->get_url_from_path( $stylesheet_path );
/**
* Enqueue the stylesheet, if it exists. The wp_enqueue_style function handles duplicates, so we don't need
* to worry about the same block loading its stylesheets more than once.
*/
if ( ! empty( $stylesheet_url ) ) {
wp_enqueue_style(
"block-lab__block-{$name}",
$stylesheet_url,
[],
wp_get_theme()->get( 'Version' )
);
}
}
/**
* Enqueues global block styles.
*/
protected function enqueue_global_styles() {
$locations = [
'blocks/css/blocks.css',
'blocks/blocks.css',
];
$stylesheet_path = block_lab()->locate_template( $locations );
$stylesheet_url = block_lab()->get_url_from_path( $stylesheet_path );
/**
* Enqueue the stylesheet, if it exists.
*/
if ( ! empty( $stylesheet_url ) ) {
wp_enqueue_style(
'block-lab__global-styles',
$stylesheet_url,
[],
wp_get_theme()->get( 'Version' )
);
}
}
/**
* Loads a block template to render the block.
*
* @param string $name The name of the block (slug as defined in UI).
* @param string|array $type The type of template to load.
*/
protected function block_template( $name, $type = 'block' ) {
// Loading async it might not come from a query, this breaks load_template().
global $wp_query;
// So lets fix it.
if ( empty( $wp_query ) ) {
$wp_query = new \WP_Query(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
$types = (array) $type;
$located = '';
foreach ( $types as $type ) {
$templates = block_lab()->get_template_locations( $name, $type );
$located = block_lab()->locate_template( $templates );
if ( ! empty( $located ) ) {
break;
}
}
if ( ! empty( $located ) ) {
$theme_template = apply_filters( 'block_lab_override_theme_template', $located );
// This is not a load once template, so require_once is false.
load_template( $theme_template, false );
} else {
if ( ! current_user_can( 'edit_posts' ) || ! isset( $templates[0] ) ) {
return;
}
// Hide the template not found notice on the frontend, unless WP_DEBUG is enabled.
if ( ! is_admin() && ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
return;
}
printf(
'<div class="notice notice-warning">%s</div>',
wp_kses_post(
// Translators: Placeholder is a file path.
sprintf( __( 'Template file %s not found.', 'block-lab' ), '<code>' . esc_html( $templates[0] ) . '</code>' )
)
);
}
}
/**
* Load all the published blocks and blocks/block.json files.
*/
protected function retrieve_blocks() {
/**
* Retrieve blocks from blocks.json.
* Reverse to preserve order of preference when using array_merge.
*/
$blocks_files = array_reverse( (array) block_lab()->locate_template( 'blocks/blocks.json', '', false ) );
foreach ( $blocks_files as $blocks_file ) {
// This is expected to be on the local filesystem, so file_get_contents() is ok to use here.
$json = file_get_contents( $blocks_file ); // @codingStandardsIgnoreLine
$block_data = json_decode( $json, true );
// Merge if no json_decode error occurred.
if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$this->blocks = array_merge( $this->blocks, $block_data );
}
}
/**
* Retrieve blocks stored as posts in the WordPress database.
*/
$block_posts = new \WP_Query(
[
'post_type' => block_lab()->get_post_type_slug(),
'post_status' => 'publish',
'posts_per_page' => 100, // This has to have a limit for this plugin to be scalable.
]
);
if ( 0 < $block_posts->post_count ) {
/** The WordPress Post object. @var \WP_Post $post */
foreach ( $block_posts->posts as $post ) {
$block_data = json_decode( $post->post_content, true );
// Merge if no json_decode error occurred.
if ( json_last_error() == JSON_ERROR_NONE ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
$this->blocks = array_merge( $this->blocks, $block_data );
}
}
}
/**
* Use this action to add new blocks and fields with the block_lab_add_block and block_lab_add_field helper functions.
*/
do_action( 'block_lab_add_blocks' );
/**
* Filter the available blocks.
*
* This is used internally by the block_lab_add_block and block_lab_add_field helper functions,
* but it can also be used to hide certain blocks if desired.
*
* @param array $blocks An associative array of blocks.
*/
$this->blocks = apply_filters( 'block_lab_blocks', $this->blocks );
}
/**
* Add a new block.
*
* This method should be called during the block_lab_add_blocks action, to ensure
* that the block isn't added too late.
*
* @param array $block_config The config of the block to add.
*/
public function add_block( $block_config ) {
if ( ! isset( $block_config['name'] ) ) {
return;
}
$this->blocks[ "block-lab/{$block_config['name']}" ] = $block_config;
}
/**
* Add a new field to an existing block.
*
* This method should be called during the block_lab_add_blocks action, to ensure
* that the block isn't added too late.
*
* @param string $block_name The name of the block that the field is added to.
* @param array $field_config The config of the field to add.
*/
public function add_field( $block_name, $field_config ) {
if ( ! isset( $this->blocks[ "block-lab/{$block_name}" ] ) ) {
return;
}
if ( ! isset( $field_config['name'] ) ) {
return;
}
$this->blocks[ "block-lab/{$block_name}" ]['fields'][ $field_config['name'] ] = $field_config;
}
}
<?php
/**
* Repeater row looping.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks;
/**
* Class Loop
*/
class Loop {
/**
* Current pointer in active loops.
*
* An associative array of $loop_name => $pointer.
* The $pointer is an int of the current iteration, e.g: 0, 1, or 2.
*
* @var array
*/
public $loops = [];
/**
* Currently active loop
*
* @var string
*/
public $active;
/**
* Set a loop to active.
*
* @param string $name The field name.
*/
public function set_active( $name ) {
$this->active = $name;
}
/**
* Get the current pointer for a loop.
*
* @param string $name The field name.
*
* @return bool
*/
public function get_row( $name = '' ) {
if ( empty( $name ) ) {
$name = $this->active;
}
if ( isset( $this->loops[ $name ] ) ) {
return $this->loops[ $name ];
}
return false;
}
/**
* Increment the row pointer for a loop.
*
* @param string $name The field name.
* @return int
*/
public function increment( $name = '' ) {
if ( empty( $name ) ) {
$name = $this->active;
}
if ( isset( $this->loops[ $name ] ) ) {
$this->loops[ $name ]++;
} else {
$this->loops[ $name ] = 0;
}
return $this->loops[ $name ];
}
/**
* Reset the loop so that it can be restarted.
*
* @param string $name The field name.
*/
public function reset( $name = '' ) {
if ( empty( $name ) ) {
$name = $this->active;
}
unset( $this->loops[ $name ] );
}
}
<?php
/**
* Radio control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Checkbox
*/
class Checkbox extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'checkbox';
/**
* Field variable type.
*
* @var string
*/
public $type = 'boolean';
/**
* Checkbox constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Checkbox', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'checkbox',
'default' => '0',
'sanitize' => [ $this, 'sanitize_checkbox' ],
]
);
}
}
<?php
/**
* Classic Text control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Classic_Text
*/
class Classic_Text extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'classic_text';
/**
* Class constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Classic Text', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'help', 'default' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
}
}
<?php
/**
* Color control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Color
*/
class Color extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'color';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Color', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'location', 'width', 'help', 'default' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
}
}
<?php
/**
* Control abstract.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
use Block_Lab\Blocks\Field;
/**
* Class Control_Abstract
*/
abstract class Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = '';
/**
* Control label.
*
* @var string
*/
public $label = '';
/**
* Field variable type (passed as an attribute when registering the block in Javascript).
*
* @var string
*/
public $type = 'string';
/**
* Control settings.
*
* @var Control_Setting[]
*/
public $settings = [];
/**
* Configurations for common settings, like 'help' and 'placeholder'.
*
* @var array {
* An associative array of setting configurations.
*
* @type string $setting_name The name of the setting, like 'help'.
* @type array $setting_config The default configuration of the setting.
* }
*/
public $settings_config = [];
/**
* The possible editor locations, either in the main block editor, or the inspector controls.
*
* @var array
*/
public $locations = [];
/**
* Control constructor.
*
* @return void
*/
public function __construct() {
$this->create_settings_config();
$this->register_settings();
}
/**
* Creates the setting configuration.
*
* This sets the values for common settings, to make adding settings more DRY.
* Then, controls can simply use the values here.
*
* @return void
*/
public function create_settings_config() {
$this->settings_config = [
'location' => [
'name' => 'location',
'label' => __( 'Field Location', 'block-lab' ),
'type' => 'location',
'default' => 'editor',
'sanitize' => [ $this, 'sanitize_location' ],
],
'width' => [
'name' => 'width',
'label' => __( 'Field Width', 'block-lab' ),
'type' => 'width',
'default' => '100',
'sanitize' => 'sanitize_text_field',
],
'help' => [
'name' => 'help',
'label' => __( 'Help Text', 'block-lab' ),
'type' => 'text',
'default' => '',
'sanitize' => 'sanitize_text_field',
],
'default' => [
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'text',
'default' => '',
'sanitize' => 'sanitize_text_field',
],
'placeholder' => [
'name' => 'placeholder',
'label' => __( 'Placeholder Text', 'block-lab' ),
'type' => 'text',
'default' => '',
'sanitize' => 'sanitize_text_field',
],
];
$this->locations = [
'editor' => __( 'Editor', 'block-lab' ),
'inspector' => __( 'Inspector', 'block-lab' ),
];
}
/**
* Register settings.
*
* @return void
*/
abstract public function register_settings();
/**
* Render additional settings in table rows.
*
* @param Field $field The Field containing the options to render.
* @param string $uid A unique ID to used to unify the HTML name, for, and id attributes.
*
* @return void
*/
public function render_settings( $field, $uid ) {
foreach ( $this->settings as $setting ) {
// Don't render the location setting for sub-fields.
if ( 'location' === $setting->type && isset( $field->settings['parent'] ) ) {
continue;
}
// Don't render the field width setting for sub-fields.
if ( 'width' === $setting->type && isset( $field->settings['parent'] ) ) {
continue;
}
if ( isset( $field->settings[ $setting->name ] ) ) {
$setting->value = $field->settings[ $setting->name ];
} else {
$setting->value = $setting->default;
}
$classes = [
"block-fields-edit-settings-{$this->name}-{$setting->name}",
"block-fields-edit-{$setting->name}-settings",
"block-fields-edit-settings-{$this->name}",
"block-fields-edit-{$setting->name}-settings",
'block-fields-edit-settings',
];
$name = 'block-fields-settings[' . $uid . '][' . $setting->name . ']';
$id = 'block-fields-edit-settings-' . $this->name . '-' . $setting->name . '_' . $uid;
?>
<tr class="<?php echo esc_attr( implode( ' ', $classes ) ); ?>">
<td class="spacer"></td>
<th scope="row">
<label for="<?php echo esc_attr( $id ); ?>">
<?php echo esc_html( $setting->label ); ?>
</label>
<p class="description">
<?php echo wp_kses_post( $setting->help ); ?>
</p>
</th>
<td>
<?php
$method = 'render_settings_' . $setting->type;
if ( method_exists( $this, $method ) ) {
$this->$method( $setting, $name, $id );
} else {
$this->render_settings_text( $setting, $name, $id );
}
?>
</td>
</tr>
<?php
}
}
/**
* Render text settings
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_text( $setting, $name, $id ) {
?>
<input
name="<?php echo esc_attr( $name ); ?>"
type="<?php echo esc_attr( $setting->type ); ?>"
id="<?php echo esc_attr( $id ); ?>"
class="regular-text"
value="<?php echo esc_attr( $setting->get_value() ); ?>" />
<?php
}
/**
* Render textarea settings
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_textarea( $setting, $name, $id ) {
?>
<textarea
name="<?php echo esc_attr( $name ); ?>"
id="<?php echo esc_attr( $id ); ?>"
rows="6"
class="large-text"><?php echo esc_html( $setting->get_value() ); ?></textarea>
<?php
}
/**
* Render checkbox settings
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_checkbox( $setting, $name, $id ) {
?>
<input
name="<?php echo esc_attr( $name ); ?>"
type="checkbox"
id="<?php echo esc_attr( $id ); ?>"
class=""
value="1"
<?php checked( '1', $setting->get_value() ); ?> />
<?php
}
/**
* Render number settings.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_number( $setting, $name, $id ) {
$this->render_number( $setting, $name, $id );
}
/**
* Render the number settings, forcing the number in the <input> to be non-negative.
* This could be 0, 1, 2, etc, but not -1.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_number_non_negative( $setting, $name, $id ) {
$this->render_number( $setting, $name, $id, true );
}
/**
* Render the number settings, optionally outputting a min="0" attribute to enforce a non-negative value.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
* @param bool $non_negative Whether to force the number to be non-negative via a min="0" attribute.
*
* @return void
*/
public function render_number( $setting, $name, $id, $non_negative = false ) {
?>
<input
name="<?php echo esc_attr( $name ); ?>"
type="number"
id="<?php echo esc_attr( $id ); ?>"
class="regular-text"
<?php echo $non_negative ? 'min="0"' : ''; ?>
value="<?php echo esc_attr( $setting->get_value() ); ?>" />
<?php
}
/**
* Render an array of settings inside a textarea.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_textarea_array( $setting, $name, $id ) {
$options = $setting->get_value();
if ( is_array( $options ) ) {
// Convert the array to text separated by new lines.
$value = '';
foreach ( $options as $option ) {
if ( ! is_array( $option ) ) {
$value .= $option . "\n";
continue;
}
if ( ! isset( $option['value'] ) || ! isset( $option['label'] ) ) {
continue;
}
if ( $option['value'] === $option['label'] ) {
$value .= $option['label'] . "\n";
} else {
$value .= $option['value'] . ' : ' . $option['label'] . "\n";
}
}
$setting->value = trim( $value );
}
$this->render_settings_textarea( $setting, $name, $id );
}
/**
* Renders a <select> of locations.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_location( $setting, $name, $id ) {
$this->render_select( $setting, $name, $id, $this->locations );
}
/**
* Renders a button group of field widths.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_width( $setting, $name, $id ) {
$widths = [
'25' => '25%',
'50' => '50%',
'75' => '75%',
'100' => '100%',
];
?>
<div class="button-group">
<?php
foreach ( $widths as $value => $label ) {
?>
<input
class="button"
name="<?php echo esc_attr( $name ); ?>"
type="radio"
value="<?php echo esc_attr( $value ); ?>"
<?php checked( $value, $setting->get_value() ); ?>
/>
<label><?php echo esc_html( $label ); ?></label>
<?php
}
?>
</div>
<?php
}
/**
* Renders a <select> of the passed values.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
* @param array $values {
* An associative array of the post type REST slugs.
*
* @type string $rest_slug The rest slug, like 'tags' for the 'post_tag' taxonomy.
* @type string $label The label to display inside the <option>.
* }
*
* @return void
*/
public function render_select( $setting, $name, $id, $values ) {
?>
<select name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $id ); ?>">
<?php
foreach ( $values as $value => $label ) :
?>
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, $setting->get_value() ); ?>>
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
</select>
<?php
}
/**
* Sanitize checkbox.
*
* @param string $value The value to sanitize.
*
* @return string
*/
public function sanitize_checkbox( $value ) {
if ( '1' === $value ) {
return 1;
}
return 0;
}
/**
* Sanitize non-zero number.
*
* @param string $value The value to sanitize.
*
* @return int
*/
public function sanitize_number( $value ) {
if ( empty( $value ) || '0' === $value ) {
return null;
}
return (int) filter_var( $value, FILTER_SANITIZE_NUMBER_INT );
}
/**
* Sanitize an array of settings inside a textarea.
*
* @param string $value The value to sanitize.
*
* @return array
*/
public function sanitize_textarea_assoc_array( $value ) {
$rows = preg_split( '/\r\n|[\r\n]/', $value );
$options = [];
foreach ( $rows as $key => $option ) {
if ( '' === $option ) {
continue;
}
$key_value = explode( ' : ', $option );
if ( count( $key_value ) > 1 ) {
$options[ $key ]['label'] = $key_value[1];
$options[ $key ]['value'] = $key_value[0];
} else {
$options[ $key ]['label'] = $option;
$options[ $key ]['value'] = $option;
}
}
// Reindex array in case of blank lines.
$options = array_values( $options );
return $options;
}
/**
* Sanitize an array of settings inside a textarea.
*
* @param string $value The value to sanitize.
*
* @return array
*/
public function sanitize_textarea_array( $value ) {
$rows = preg_split( '/\r\n|[\r\n]/', $value );
$options = [];
foreach ( $rows as $key => $option ) {
if ( '' === $option ) {
continue;
}
$key_value = explode( ' : ', $option );
if ( count( $key_value ) > 1 ) {
$options[] = $key_value[0];
} else {
$options[] = $option;
}
}
// Reindex array in case of blank lines.
$options = array_values( $options );
return $options;
}
/**
* Sanitize a location value.
*
* @param string $value The value to sanitize.
*
* @return array
*/
public function sanitize_location( $value ) {
if ( is_string( $value ) && array_key_exists( $value, $this->locations ) ) {
return $value;
}
}
/**
* Validate that the value is contained within a list of options,
* and if not, return the first option.
*
* @param mixed $value The value to be validated.
* @param array $settings The field settings.
*
* @return mixed
*/
public function validate_options( $value, $settings ) {
if ( ! array_key_exists( 'options', $settings ) ) {
return $value;
}
// Allow an empty value.
if ( '' === $value ) {
return $value;
}
$options = [];
// Reindex the options into a more workable format.
array_walk(
$settings['options'],
function( $option ) use ( &$options ) {
$options[] = $option['value'];
}
);
if ( is_array( $value ) ) {
// Filter out invalid options where multiple options can be chosen.
foreach ( $value as $key => $option ) {
if ( ! in_array( $option, $options, true ) ) {
unset( $value[ $key ] );
}
}
} else {
// If the value is not in the set of options, return an empty string.
if ( ! in_array( $value, $options, true ) ) {
$value = '';
}
}
return $value;
}
}
<?php
/**
* Control_Setting.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Control_Setting
*/
class Control_Setting {
/**
* Setting name (slug).
*
* @var string
*/
public $name = '';
/**
* Setting label.
*
* @var string
*/
public $label = '';
/**
* Setting type.
*
* @var string
*/
public $type = '';
/**
* Default value.
*
* @var mixed
*/
public $default = '';
/**
* Help text.
*
* @var string
*/
public $help = '';
/**
* Sanitizing function.
*
* @var mixed
*/
public $sanitize = '';
/**
* Validating function.
*
* @var mixed
*/
public $validate = '';
/**
* Current value. Null for unset.
*
* @var mixed
*/
public $value = null;
/**
* Control_Setting constructor.
*
* @param array $args An associative array with keys corresponding to the Option's properties.
*
* @return void
*/
public function __construct( $args = [] ) {
if ( isset( $args['name'] ) ) {
$this->name = $args['name'];
}
if ( isset( $args['label'] ) ) {
$this->label = $args['label'];
}
if ( isset( $args['type'] ) ) {
$this->type = $args['type'];
}
if ( isset( $args['default'] ) ) {
$this->default = $args['default'];
}
if ( isset( $args['help'] ) ) {
$this->help = $args['help'];
}
if ( isset( $args['sanitize'] ) ) {
$this->sanitize = $args['sanitize'];
}
if ( isset( $args['validate'] ) ) {
$this->validate = $args['validate'];
}
if ( isset( $args['value'] ) ) {
$this->value = $args['value'];
}
}
/**
* Get the current value, using the default if there is none set.
*
* @return mixed
*/
public function get_value() {
if ( null === $this->value ) {
return $this->default;
}
return $this->value;
}
}
<?php
/**
* Email control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Text
*/
class Email extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'email';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Email', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'email',
'default' => '',
'sanitize' => 'sanitize_email',
]
);
$this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
}
}
<?php
/**
* Image control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Image
*/
class Image extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'image';
/**
* Field variable type.
*
* @var string
*/
public $type = 'integer';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Image', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'location', 'width', 'help' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
}
/**
* Validates the value to be made available to the front-end template.
*
* @param string $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this value will be echoed.
* @return string|int $value The value to be made available or echoed on the front-end template, possibly 0 if none found.
*/
public function validate( $value, $echo ) {
$image_id = intval( $value );
// Backwards compatibility, as the value used to be the image's URL instead of its post ID.
if ( empty( $image_id ) && is_string( $value ) ) {
$legacy_src = $value;
$legacy_id = attachment_url_to_postid( $value );
}
if ( $echo ) {
if ( isset( $legacy_src ) ) {
return $legacy_src;
}
$image = wp_get_attachment_image_src( $image_id, 'full' );
return ! empty( $image[0] ) ? $image[0] : '';
} else {
return isset( $legacy_id ) ? $legacy_id : $image_id;
}
}
}
<?php
/**
* Select control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Select
*/
class Multiselect extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'multiselect';
/**
* Field variable type.
*
* @var string
*/
public $type = 'array';
/**
* Select constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Multi-Select', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'options',
'label' => __( 'Choices', 'block-lab' ),
'type' => 'textarea_array',
'default' => '',
'help' => sprintf(
'%s %s<br />%s<br />%s',
__( 'Enter each choice on a new line.', 'block-lab' ),
__( 'To specify the value and label separately, use this format:', 'block-lab' ),
_x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
_x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
),
'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'textarea_array',
'default' => '',
'help' => __( 'Enter each default value on a new line.', 'block-lab' ),
'sanitize' => [ $this, 'sanitize_textarea_array' ],
'validate' => [ $this, 'validate_options' ],
]
);
}
}
<?php
/**
* Number control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Text
*/
class Number extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'number';
/**
* Field variable type.
*
* @var string
*/
public $type = 'integer';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Number', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'number',
'default' => '',
'sanitize' => function ( $value ) {
return filter_var( $value, FILTER_SANITIZE_NUMBER_INT );
},
]
);
$this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
}
}
<?php
/**
* Post control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Post
*/
class Post extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'post';
/**
* Field variable type.
*
* @var string
*/
public $type = 'object';
/**
* Post constructor.
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Post', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'post_type_rest_slug',
'label' => __( 'Post Type', 'block-lab' ),
'type' => 'post_type_rest_slug',
'default' => 'posts',
'sanitize' => [ $this, 'sanitize_post_type_rest_slug' ],
]
);
}
/**
* Render a <select> of public post types.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_post_type_rest_slug( $setting, $name, $id ) {
$post_type_slugs = $this->get_post_type_rest_slugs();
$this->render_select( $setting, $name, $id, $post_type_slugs );
}
/**
* Gets the REST slugs of public post types, other than 'attachment'.
*
* @return array {
* An associative array of the post type REST slugs.
*
* @type string $rest_slug The REST slug of the post type.
* @type string $name The name of the post type.n
* }
*/
public function get_post_type_rest_slugs() {
$post_type_rest_slugs = [];
foreach ( get_post_types( [ 'public' => true ] ) as $post_type ) {
$post_type_object = get_post_type_object( $post_type );
if ( ! $post_type_object || empty( $post_type_object->show_in_rest ) ) {
continue;
}
if ( 'attachment' === $post_type ) {
continue;
}
$rest_slug = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type;
$labels = get_post_type_labels( $post_type_object );
$post_type_name = isset( $labels->name ) ? $labels->name : $post_type;
$post_type_rest_slugs[ $rest_slug ] = $post_type_name;
}
return $post_type_rest_slugs;
}
/**
* Sanitize the post type REST slug, to ensure that it's a public post type.
*
* This expects the rest_base of the post type, as it's easier to pass that to apiFetch in the Post control.
* So this iterates through the public post types, to find if one has the rest_base equal to $value.
*
* @param string $value The rest_base of the post type to sanitize.
* @return string|null The sanitized rest_base of the post type, or null.
*/
public function sanitize_post_type_rest_slug( $value ) {
if ( array_key_exists( $value, $this->get_post_type_rest_slugs() ) ) {
return $value;
}
return null;
}
/**
* Validates the value to be made available to the front-end template.
*
* @param mixed $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this will be echoed.
* @return string|WP_Post|null $value The value to be made available or echoed on the front-end template.
*/
public function validate( $value, $echo ) {
$post = isset( $value['id'] ) ? get_post( $value['id'] ) : null;
if ( $echo ) {
return $post ? get_the_title( $post ) : '';
} else {
return $post;
}
}
}
<?php
/**
* Radio control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Radio
*/
class Radio extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'radio';
/**
* Radio constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Radio', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'options',
'label' => __( 'Choices', 'block-lab' ),
'type' => 'textarea_array',
'default' => '',
'help' => sprintf(
'%s %s<br />%s<br />%s',
__( 'Enter each choice on a new line.', 'block-lab' ),
__( 'To specify the value and label separately, use this format:', 'block-lab' ),
_x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
_x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
),
'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'text',
'default' => '',
'sanitize' => 'sanitize_text_field',
'validate' => [ $this, 'validate_options' ],
]
);
}
}
<?php
/**
* Range control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Range
*/
class Range extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'range';
/**
* Field variable type.
*
* @var string
*/
public $type = 'integer';
/**
* Range constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Range', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'min',
'label' => __( 'Minimum Value', 'block-lab' ),
'type' => 'number',
'default' => '',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'max',
'label' => __( 'Maximum Value', 'block-lab' ),
'type' => 'number',
'default' => '',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'step',
'label' => __( 'Step Size', 'block-lab' ),
'type' => 'number_non_negative',
'default' => 1,
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'number',
'default' => '',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
}
}
<?php
/**
* Repeater control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Repeater
*/
class Repeater extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'repeater';
/**
* Field variable type.
*
* The Repeater control is an array of objects, with each row being an object.
* For example, a repeater with one row might be [ { 'example-text': 'Foo', 'example-image': 4232 } ].
*
* @var string
*/
public $type = 'object';
/**
* Repeater constructor.
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Repeater', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'min',
'label' => __( 'Minimum Rows', 'block-lab' ),
'type' => 'number_non_negative',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'max',
'label' => __( 'Maximum Rows', 'block-lab' ),
'type' => 'number_non_negative',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
}
/**
* Remove empty placeholder rows.
*
* @param mixed $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this will be echoed.
* @return mixed $value The value to be made available or echoed on the front-end template.
*/
public function validate( $value, $echo ) {
if ( isset( $value['rows'] ) ) {
foreach ( $value['rows'] as $key => $row ) {
unset( $value['rows'][ $key ][''] );
unset( $value['rows'][ $key ][0] );
}
}
if ( $echo && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$value = sprintf(
// translators: Placeholders are the opening and closing anchor tags of a link.
__( '⚠️ Please use Block Lab\'s %1$srepeater functions%2$s to display repeater fields in your template.', 'block-lab' ),
'<a href="https://getblocklab.com/docs/fields/repeater/">',
'</a>'
);
}
return $value;
}
}
<?php
/**
* Rich Text control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Rich_Text
*/
class Rich_Text extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'rich_text';
/**
* Class constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Rich Text', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'help', 'default', 'placeholder' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
}
/**
* Validates the value to be made available to the front-end template.
*
* @param mixed $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this will be echoed.
* @return mixed $value The value to be made available or echoed on the front-end template.
*/
public function validate( $value, $echo ) {
unset( $echo );
// If there's no text entered, Rich Text saves '<p></p>', so instead return ''.
if ( '<p></p>' === $value ) {
return '';
}
return wpautop( $value );
}
}
<?php
/**
* Select control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Select
*/
class Select extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'select';
/**
* Select constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Select', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'options',
'label' => __( 'Choices', 'block-lab' ),
'type' => 'textarea_array',
'default' => '',
'help' => sprintf(
'%s %s<br />%s<br />%s',
__( 'Enter each choice on a new line.', 'block-lab' ),
__( 'To specify the value and label separately, use this format:', 'block-lab' ),
_x( 'foo : Foo', 'Format for the menu values. option_value : Option Name', 'block-lab' ),
_x( 'bar : Bar', 'Format for the menu values. option_value : Option Name', 'block-lab' )
),
'sanitize' => [ $this, 'sanitize_textarea_assoc_array' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'text',
'default' => '',
'sanitize' => 'sanitize_text_field',
'validate' => [ $this, 'validate_options' ],
]
);
}
}
<?php
/**
* Taxonomy control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Taxonomy
*/
class Taxonomy extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'taxonomy';
/**
* Field variable type.
*
* @var string
*/
public $type = 'object';
/**
* Taxonomy constructor.
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Taxonomy', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'post_type_rest_slug',
'label' => __( 'Taxonomy Type', 'block-lab' ),
'type' => 'taxonomy_type_rest_slug',
'default' => 'posts',
'sanitize' => [ $this, 'sanitize_taxonomy_type_rest_slug' ],
]
);
}
/**
* Renders a <select> of public taxonomy types.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_taxonomy_type_rest_slug( $setting, $name, $id ) {
$taxonomy_slugs = $this->get_taxonomy_type_rest_slugs();
$this->render_select( $setting, $name, $id, $taxonomy_slugs );
}
/**
* Gets the REST slugs of public taxonomy types.
*
* @return array {
* An associative array of the post type REST slugs.
*
* @type string $rest_slug The REST slug of the post type.
* @type string $name The name of the post type.
* }
*/
public function get_taxonomy_type_rest_slugs() {
$taxonomy_rest_slugs = [];
foreach ( get_taxonomies( [ 'show_in_rest' => true ] ) as $taxonomy_slug ) {
$taxonomy_object = get_taxonomy( $taxonomy_slug );
$rest_slug = ! empty( $taxonomy_object->rest_base ) ? $taxonomy_object->rest_base : $taxonomy_slug;
$taxonomy_rest_slugs[ $rest_slug ] = $taxonomy_object->label;
}
return $taxonomy_rest_slugs;
}
/**
* Sanitize the taxonomy type REST slug, to ensure that it's registered and public.
*
* @param string $value The rest_base of the post type to sanitize.
* @return string|null The sanitized rest_base of the post type, or null.
*/
public function sanitize_taxonomy_type_rest_slug( $value ) {
if ( array_key_exists( $value, $this->get_taxonomy_type_rest_slugs() ) ) {
return $value;
}
return null;
}
/**
* Validates the value to be made available to the front-end template.
*
* @param mixed $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this will be echoed.
* @return string|WP_Term|null $value The value to be made available or echoed on the front-end template.
*/
public function validate( $value, $echo ) {
$term = isset( $value['id'] ) ? get_term( $value['id'] ) : null;
if ( $echo ) {
return $term ? $term->name : '';
} else {
return $term;
}
}
}
<?php
/**
* Text control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Text
*/
class Text extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'text';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Text', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'location', 'width', 'help', 'default', 'placeholder' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
$this->settings[] = new Control_Setting(
[
'name' => 'maxlength',
'label' => __( 'Character Limit', 'block-lab' ),
'type' => 'number_non_negative',
'default' => '',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
}
}
<?php
/**
* Textarea control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Textarea
*/
class Textarea extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'textarea';
/**
* Control type.
*
* @var string
*/
public $type = 'textarea';
/**
* Textarea constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Textarea', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'location', 'width', 'help' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'textarea',
'default' => '',
'sanitize' => 'sanitize_textarea_field',
]
);
$this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
$this->settings[] = new Control_Setting(
[
'name' => 'maxlength',
'label' => __( 'Character Limit', 'block-lab' ),
'type' => 'number_non_negative',
'default' => '',
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'number_rows',
'label' => __( 'Number of Rows', 'block-lab' ),
'type' => 'number_non_negative',
'default' => 4,
'sanitize' => [ $this, 'sanitize_number' ],
]
);
$this->settings[] = new Control_Setting(
[
'name' => 'new_lines',
'label' => __( 'New Lines', 'block-lab' ),
'type' => 'new_line_format',
'default' => 'autop',
'sanitize' => [ $this, 'sanitize_new_line_format' ],
]
);
}
/**
* Renders a <select> of new line rendering formats.
*
* @param Control_Setting $setting The Control_Setting being rendered.
* @param string $name The name attribute of the option.
* @param string $id The id attribute of the option.
*
* @return void
*/
public function render_settings_new_line_format( $setting, $name, $id ) {
$formats = $this->get_new_line_formats();
$this->render_select( $setting, $name, $id, $formats );
}
/**
* Gets the new line formats.
*
* @return array {
* An associative array of new line formats.
*
* @type string $key The option value to save.
* @type string $label The label.
* }
*/
public function get_new_line_formats() {
$formats = [
'autop' => __( 'Automatically add paragraphs', 'block-lab' ),
'autobr' => __( 'Automatically add line breaks', 'block-lab' ),
'none' => __( 'No formatting', 'block-lab' ),
];
return $formats;
}
/**
* Sanitize the new line format, to ensure that it's valid.
*
* @param string $value The format to sanitize.
* @return string|null The sanitized rest_base of the post type, or null.
*/
public function sanitize_new_line_format( $value ) {
if ( is_string( $value ) && array_key_exists( $value, $this->get_new_line_formats() ) ) {
return $value;
}
return null;
}
}
<?php
/**
* Toggle control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Toggle
*/
class Toggle extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'toggle';
/**
* Field variable type.
*
* @var string
*/
public $type = 'boolean';
/**
* Toggle constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'Toggle', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'checkbox',
'default' => '0',
'sanitize' => [ $this, 'sanitize_checkbox' ],
]
);
}
}
<?php
/**
* Url control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class Text
*/
class Url extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'url';
/**
* Text constructor.
*
* @return void
*/
public function __construct() {
parent::__construct();
$this->label = __( 'URL', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
$this->settings[] = new Control_Setting( $this->settings_config['location'] );
$this->settings[] = new Control_Setting( $this->settings_config['width'] );
$this->settings[] = new Control_Setting( $this->settings_config['help'] );
$this->settings[] = new Control_Setting(
[
'name' => 'default',
'label' => __( 'Default Value', 'block-lab' ),
'type' => 'url',
'default' => '',
'sanitize' => 'esc_url_raw',
]
);
$this->settings[] = new Control_Setting( $this->settings_config['placeholder'] );
}
}
<?php
/**
* User control.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Blocks\Controls;
/**
* Class User
*/
class User extends Control_Abstract {
/**
* Control name.
*
* @var string
*/
public $name = 'user';
/**
* Field variable type.
*
* @var string
*/
public $type = 'object';
/**
* User constructor.
*/
public function __construct() {
parent::__construct();
$this->label = __( 'User', 'block-lab' );
}
/**
* Register settings.
*
* @return void
*/
public function register_settings() {
foreach ( [ 'location', 'width', 'help' ] as $setting ) {
$this->settings[] = new Control_Setting( $this->settings_config[ $setting ] );
}
}
/**
* Validates the value to be made available to the front-end template.
*
* @param mixed $value The value to either make available as a variable or echoed on the front-end template.
* @param bool $echo Whether this will be echoed.
* @return mixed $value The value to be made available or echoed on the front-end template.
*/
public function validate( $value, $echo ) {
$wp_user = isset( $value['id'] ) ? get_user_by( 'id', $value['id'] ) : null;
if ( $echo ) {
return $wp_user ? $wp_user->get( 'display_name' ) : '';
} else {
return $wp_user ? $wp_user : false;
}
}
}
<?php
/**
* Component abstract.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab;
/**
* Class ComponentAbstract
*/
abstract class Component_Abstract implements Component_Interface {
/**
* Point to the $plugin instance.
*
* @var Plugin_Interface
*/
protected $plugin;
/**
* Set the plugin so that it can be referenced later.
*
* @param Plugin_Interface $plugin The plugin.
*
* @return Component_Interface $this
*/
public function set_plugin( Plugin_Interface $plugin ) {
$this->plugin = $plugin;
return $this;
}
/**
* Handle deprecated component methods.
*
* @param string $name The name of the method called in this class.
* @param array $arguments The arguments passed to the method.
*
* @return mixed The result of calling the deprecated method, if it exists.
*
* @throws \Error Fallback to a standard PHP error.
*/
public function __call( $name, $arguments ) {
$class = get_class( $this );
$class_name = strtolower( str_replace( '\\', '__', $class ) );
$function_name = "${class_name}__${name}";
if ( function_exists( $function_name ) ) {
return call_user_func_array( $function_name, $arguments );
}
// Intentionally untranslated, to match PHP's error message.
throw new \Error( "Call to undefined method $class::$name()" );
}
/**
* Register any hooks that this component needs.
*
* @return void
*/
abstract public function register_hooks();
}
<?php
/**
* Component interface.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab;
/**
* Interface ComponentInterface
*/
interface Component_Interface {
/**
* Set the plugin so that it can be referenced later.
*
* @param Plugin_Interface $plugin The plugin.
*
* @return Component_Interface $this
*/
public function set_plugin( Plugin_Interface $plugin );
/**
* Register any hooks that this component needs.
*
* @return void
*/
public function register_hooks();
}
<?php
/**
* Plugin abstract.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab;
/**
* Class Plugin_Abstract
*/
abstract class Plugin_Abstract implements Plugin_Interface {
/**
* Plugin components.
*
* @var array
*/
protected $components = [];
/**
* Plugin basename.
*
* @since 1.0.0
* @var string
*/
protected $basename;
/**
* Absolute path to the main plugin directory.
*
* @since 1.0.0
* @var string
*/
protected $directory;
/**
* Absolute path to the main plugin file.
*
* @since 1.0.0
* @var string
*/
protected $file;
/**
* Plugin identifier.
*
* @since 1.0.0
* @var string
*/
protected $slug;
/**
* URL to the main plugin directory.
*
* @since 1.0.0
* @var string
*/
protected $url;
/**
* The plugin version.
*
* @since 1.0.2
* @var string
*/
protected $version;
/**
* Allows calling methods in the Util class, directly in this class.
*
* When calling a method in this class that isn't defined, this calls it in $this->util if it exists.
* For example, on calling ->example_method() in this class,
* this looks for $this->util->example_method().
*
* @param string $name The name of the method called in this class.
* @param array $arguments The arguments passed to the method.
* @return mixed The result of calling the util method, if it exists.
* @throws \Exception On calling a method that isn't defined in this class or Util.
*/
public function __call( $name, $arguments ) {
if ( method_exists( $this->util, $name ) ) {
return call_user_func_array( [ $this->util, $name ], $arguments );
}
if ( ! method_exists( $this, $name ) ) {
$class = get_class( $this );
throw new \Exception( "Call to undefined method {$class}::{$name}()" );
}
}
/**
* Get the plugin basename.
*
* @return string The basename.
*/
public function get_basename() {
return $this->basename;
}
/**
* Set the plugin basename.
*
* @param string $basename The basename.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_basename( $basename ) {
$this->basename = $basename;
return $this;
}
/**
* Get the plugin's directory.
*
* @return string The directory.
*/
public function get_directory() {
return $this->directory;
}
/**
* Set the plugin's directory.
*
* @param string $directory The directory.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_directory( $directory ) {
$this->directory = rtrim( $directory, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
return $this;
}
/**
* Get the relative path to the plugin's directory.
*
* @param string $path Relative path to return.
*
* @return string The path.
*/
public function get_path( $path = '' ) {
return $this->directory . ltrim( $path, DIRECTORY_SEPARATOR );
}
/**
* Get the plugin file.
*
* @return string The file.
*/
public function get_file() {
return $this->file;
}
/**
* Set the plugin file.
*
* @param string $file The plugin file.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_file( $file ) {
$this->file = $file;
return $this;
}
/**
* Get the plugin's slug.
*
* @return string The slug.
*/
public function get_slug() {
return $this->slug;
}
/**
* Set the plugin's slug.
*
* @param string $slug The slug.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_slug( $slug ) {
$this->slug = $slug;
return $this;
}
/**
* Get the relative url.
*
* @param string $path The relative url to get.
*
* @return string The url.
*/
public function get_url( $path = '' ) {
return $this->url . ltrim( $path, '/' );
}
/**
* Set the plugin's url.
*
* @param string $url The url.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_url( $url ) {
$this->url = rtrim( $url, '/' ) . '/';
return $this;
}
/**
* Get the plugin's version.
*
* @return string The url.
*/
public function get_version() {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
return time();
}
return $this->version;
}
/**
* Set the plugin's version.
*
* @param string $file The absolute path to the plugin file.
*
* @return Plugin_Abstract The plugin instance.
*/
public function set_version( $file ) {
$headers = [ 'Version' => 'Version' ];
$file_data = get_file_data( $file, $headers, 'plugin' );
if ( isset( $file_data['Version'] ) ) {
$this->version = $file_data['Version'];
};
return $this;
}
/**
* Get url relative to assets url.
*
* @param string $path The relative url to get.
*
* @return string The url.
*/
public function get_assets_url( $path = '' ) {
return $this->url . 'assets/' . ltrim( $path, '/' );
}
/**
* Get the relative path to the assets directory.
*
* @param string $path Relative path to return.
*
* @return string The path.
*/
public function get_assets_path( $path = '' ) {
return $this->directory . 'assets' . DIRECTORY_SEPARATOR . ltrim( $path, DIRECTORY_SEPARATOR );
}
/**
* Register a new Component.
*
* @param Component_Interface $component The new component.
*
* @return Plugin_Abstract The plugin instance.
*/
public function register_component( Component_Interface $component ) {
$component_class = get_class( $component );
// If component already registered, then there is nothing left to do.
if ( array_key_exists( $component_class, $this->components ) ) {
return $this;
}
// Make sure the plugin is available.
if ( method_exists( $component, 'set_plugin' ) ) {
$component->set_plugin( $this );
}
// Run component init method.
if ( method_exists( $component, 'init' ) ) {
$component->init( $this );
}
$component->register_hooks();
$this->components[ $component_class ] = $component;
return $this;
}
/**
* Runs as early as possible.
*
* @return void Nothing to return.
*/
abstract public function init();
/**
* Runs once 'plugins_loaded' hook fires.
*
* @return void Nothing to return.
*/
abstract public function plugin_loaded();
}
<?php
/**
* Plugin interface.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab;
/**
* Interface Plugin_Interface
*/
interface Plugin_Interface {
/**
* Get the plugin basename.
*
* @return string The basename.
*/
public function get_basename();
/**
* Set the plugin basename.
*
* @param string $basename The basename.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_basename( $basename );
/**
* Get the plugin's directory.
*
* @return string The directory.
*/
public function get_directory();
/**
* Set the plugin's directory.
*
* @param string $directory The directory.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_directory( $directory );
/**
* Get the relative path to the plugin's directory.
*
* @param string $path Relative path to return.
*
* @return string The path.
*/
public function get_path( $path = '' );
/**
* Get the plugin file.
*
* @return string The file.
*/
public function get_file();
/**
* Set the plugin file.
*
* @param string $file The plugin file.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_file( $file );
/**
* Get the plugin's slug.
*
* @return string The slug.
*/
public function get_slug();
/**
* Set the plugin's slug.
*
* @param string $slug The slug.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_slug( $slug );
/**
* Get the relative url.
*
* @param string $path The relative url to get.
*
* @return string The url.
*/
public function get_url( $path = '' );
/**
* Set the plugin's url.
*
* @param string $url The url.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_url( $url );
/**
* Get the plugin's version.
*
* @return string The version.
*/
public function get_version();
/**
* Set the plugin's version, based on the file.
*
* @param string $file The absolute path to the plugin file.
*
* @return Plugin_Interface The plugin instance.
*/
public function set_version( $file );
/**
* Get url relative to assets url.
*
* @param string $path The relative url to get.
*
* @return string The url.
*/
public function get_assets_url( $path = '' );
/**
* Get the relative path to the assets directory.
*
* @param string $path Relative path to return.
*
* @return string The path.
*/
public function get_assets_path( $path = '' );
/**
* Register a new Component.
*
* @param Component_Interface $component The new component.
*
* @return Plugin_Interface The plugin instance.
*/
public function register_component( Component_Interface $component );
/**
* Runs once 'plugins_loaded' hook fires.
*
* @return void Nothing to return.
*/
public function plugin_loaded();
}
<?php
/**
* Primary plugin file.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab;
/**
* Class Plugin
*/
class Plugin extends Plugin_Abstract {
/**
* Utility methods.
*
* @var Util
*/
protected $util;
/**
* WP Admin resources.
*
* @var Admin\Admin
*/
public $admin;
/**
* Block loader.
*
* @var Blocks\Loader
*/
public $loader;
/**
* The slug of the post type that stores the blocks.
*
* @since 1.3.5
* @var string
*/
public $post_type_slug = 'block_lab';
/**
* Execute this as early as possible.
*/
public function init() {
$this->util = new Util();
$this->register_component( $this->util );
$this->register_component( new Post_Types\Block_Post() );
$this->loader = new Blocks\Loader();
$this->register_component( $this->loader );
register_activation_hook(
$this->get_file(),
function() {
$onboarding = new Admin\Onboarding();
$onboarding->plugin_activation();
}
);
}
/**
* Execute this once plugins are loaded. (not the best place for all hooks)
*/
public function plugin_loaded() {
$this->admin = new Admin\Admin();
$this->register_component( $this->admin );
}
/**
* Requires helpers, or displays a notice if there's a conflict with another plugin.
*/
public function require_helpers() {
if ( $this->is_plugin_conflict() ) {
add_action( 'admin_notices', [ $this, 'plugin_conflict_notice' ] );
} else {
require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/deprecated.php';
}
}
/**
* Gets whether there is a conflict from another plugin having the same functions.
*
* @return bool Whether there is a conflict.
*/
public function is_plugin_conflict() {
return function_exists( 'block_field' ) && function_exists( 'block_value' );
}
/**
* An admin notice for another plugin being active.
*
* Only display this if the user can deactivate plugins,
* and if this is on a Block Lab or plugins page.
*/
public function plugin_conflict_notice() {
if ( ! current_user_can( 'deactivate_plugins' ) ) {
return;
}
$screen = get_current_screen();
$should_display_notice = (
( isset( $screen->base, $screen->post_type ) && 'edit' === $screen->base && $this->post_type_slug === $screen->post_type )
||
( isset( $screen->base ) && in_array( $screen->base, [ 'plugins', 'block_lab_page_block-lab-settings' ], true ) )
);
if ( ! $should_display_notice ) {
return;
}
wp_enqueue_style(
'block-lab-plugin-conflict-notice-style',
$this->get_url( 'css/admin.conflict-notice.css' ),
[],
$this->get_version()
);
$plugin_file = 'block-lab/block-lab.php';
$deactivation_url = add_query_arg(
[
'action' => 'deactivate',
'plugin' => rawurlencode( $plugin_file ),
'plugin_status' => 'all',
'paged' => 1,
'_wpnonce' => wp_create_nonce( 'deactivate-plugin_' . $plugin_file ),
],
admin_url( 'plugins.php' )
);
?>
<div id="bl-conflict-notice" class="notice notice-error bl-notice-conflict">
<div class="bl-conflict-copy">
<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>
</div>
<a href="<?php echo esc_url( $deactivation_url ); ?>" class="bl-link-deactivate button button-primary">
<?php echo esc_html_x( 'Deactivate', 'plugin', 'block-lab' ); ?>
</a>
</div>
<?php
}
}
<?php
/**
* Helper functions for the Block_Lab plugin.
*
* These are publicly accessible via a magic method, like block_lab()->get_template_locations().
* So these methods should generally be 'getter' functions, and should not affect the global state.
*
* @package Block_Lab
*/
namespace Block_Lab;
use Block_Lab\Blocks;
/**
* Class Util
*/
class Util extends Component_Abstract {
/**
* Not implemented, as this class only has utility methods.
*/
public function register_hooks() {}
/**
* Gets whether a valid Pro license has been activated on this site.
*
* @return bool
*/
public function is_pro() {
return true;
}
/**
* Get the loop handler.
*
* @return Blocks\Loop
*/
public function loop() {
static $instance;
if ( null === $instance ) {
$instance = new Blocks\Loop();
return $instance;
}
return $instance;
}
/**
* Gets an array of possible template locations.
*
* @param string $name The name of the block (slug as defined in UI).
* @param string $type The type of template to load. Typically block or preview.
*
* @return array
*/
public function get_template_locations( $name, $type = 'block' ) {
return [
"blocks/{$name}/{$type}.php",
"blocks/{$type}-{$name}.php",
"blocks/{$type}.php",
];
}
/**
* Gets an array of possible stylesheet locations.
*
* @param string $name The name of the block (slug as defined in UI).
* @param string $type The type of template to load. Typically block or preview.
*
* @return array
*/
public function get_stylesheet_locations( $name, $type = 'block' ) {
return [
"blocks/{$name}/{$type}.css",
"blocks/css/{$type}-{$name}.css",
"blocks/{$type}-{$name}.css",
];
}
/**
* Locates templates.
*
* Works similar to `locate_template`, but allows specifying a path outside of themes
* and allows to be called when STYLESHEET_PATH has not been set yet. Handy for async.
*
* @param string|array $template_names Templates to locate.
* @param string $path (Optional) Path to locate the templates first.
* @param bool $single `true` - Returns only the first found item. Like standard `locate_template`
* `false` - Returns all found templates.
*
* @return string|array
*/
public function locate_template( $template_names, $path = '', $single = true ) {
/**
* Filters the path where block templates are saved.
*
* Note that template names are prefixed with the blocks directory.
* e.g. `blocks/block-template.php`
* The logic below will look for the prefixed template name inside the $path.
*
* @param string $path The absolute path to the stylesheet directory.
* @param string|array $template_names Templates to locate.
*/
$path = apply_filters( 'block_lab_template_path', $path, $template_names );
$stylesheet_path = get_template_directory();
$template_path = get_stylesheet_directory();
$located = [];
foreach ( (array) $template_names as $template_name ) {
if ( ! $template_name ) {
continue;
}
if ( ! empty( $path ) && file_exists( trailingslashit( $path ) . $template_name ) ) {
$located[] = trailingslashit( $path ) . $template_name;
if ( $single ) {
break;
}
}
if ( file_exists( trailingslashit( $template_path ) . $template_name ) ) {
$located[] = trailingslashit( $template_path ) . $template_name;
if ( $single ) {
break;
}
}
if ( file_exists( trailingslashit( $stylesheet_path ) . $template_name ) ) {
$located[] = trailingslashit( $stylesheet_path ) . $template_name;
if ( $single ) {
break;
}
}
if ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
$located[] = ABSPATH . WPINC . '/theme-compat/' . $template_name;
if ( $single ) {
break;
}
}
}
// Remove duplicates and re-index array.
$located = array_values( array_unique( $located ) );
if ( $single ) {
return array_shift( $located );
}
return $located;
}
/**
* Provides a list of all available block icons.
*
* To include additional icons in this list, use the block_lab_icons filter, and add a new svg string to the array,
* using a unique key. For example:
*
* $icons['foo'] = '<svg>…</svg>';
*
* @return array
*/
public function get_icons() {
// This is on the local filesystem, so file_get_contents() is ok to use here.
$json_file = block_lab()->get_assets_path( 'icons.json' );
$json = file_get_contents( $json_file ); // @codingStandardsIgnoreLine
$icons = json_decode( $json, true );
/**
* The available block icons.
*
* @param array $icons The available icons.
*/
return apply_filters( 'block_lab_icons', $icons );
}
/**
* Provides a list of allowed tags to be used by an <svg>.
*
* @return array
*/
public function allowed_svg_tags() {
$allowed_tags = [
'svg' => [
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
],
'g' => [ 'fill' => true ],
'title' => [ 'title' => true ],
'path' => [
'd' => true,
'fill' => true,
'opacity' => true,
],
'circle' => [
'cx' => true,
'cy' => true,
'r' => true,
'fill' => true,
],
];
/**
* The tags that an <svg> allows.
*
* @param array $allowed_tags The allowed tags.
*/
return apply_filters( 'block_lab_allowed_svg_tags', $allowed_tags );
}
/**
* Gets the slug of the post type that stores the blocks.
*
* @return string The slug.
*/
public function get_post_type_slug() {
return $this->plugin->post_type_slug;
}
/**
* Get a relative URL from a path.
*
* @param string $path The absolute path to a file.
*
* @return string
*/
public function get_url_from_path( $path ) {
$abspath = ABSPATH;
// Workaround for weird hosting situations.
if ( trailingslashit( ABSPATH ) . 'wp-content' !== WP_CONTENT_DIR && isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
$abspath = sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) );
}
$stylesheet_url = str_replace( untrailingslashit( $abspath ), '', $path );
return $stylesheet_url;
}
}
<?php
/**
* Deprecated functions.
*
* Deprecated methods can also appear as functions here, with the format namespace__class__method().
*
* @see Block_Lab\Component_Abstract->_call()
*
* @package Block_Lab
* @copyright Copyright(c) 2018, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
/**
* Show a PHP error to warn developers using deprecated functions.
*
* @param string $function The function that was called.
* @param string $version The version of Block Lab that deprecated the function.
* @param string $replacement The function that should have been called.
*/
function block_lab_deprecated_function( $function, $version, $replacement ) {
_deprecated_function(
// filter_var is used for sanitization here as it allows arrow functions ("->").
filter_var(
sprintf(
// translators: A function name.
__( 'Block Lab\'s %1$s', 'block-lab' ),
$function
),
FILTER_SANITIZE_STRING
),
esc_html( $version ),
filter_var( $replacement, FILTER_SANITIZE_STRING )
);
}
/**
* Handle the deprecated block_lab_get_icons() function.
*
* @see \Block_Lab\Util->get_icons()
*
* @return array
*/
function block_lab_get_icons() {
block_lab_deprecated_function( 'block_lab_get_icons', '1.3.5', 'block_lab()->get_icons()' );
return block_lab()->get_icons();
}
/**
* Handle the deprecated block_lab_allowed_svg_tags() function.
*
* @see \Block_Lab\Util->allowed_svg_tags()
*
* @return array
*/
function block_lab_allowed_svg_tags() {
block_lab_deprecated_function( 'block_lab_allowed_svg_tags', '1.3.5', 'block_lab()->allowed_svg_tags()' );
return block_lab()->allowed_svg_tags();
}
<?php
/**
* Helper functions.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
use Block_Lab\Blocks;
/**
* Return the value of a block field.
*
* @param string $name The name of the field.
* @param bool $echo Whether to echo and return the field, or just return the field.
*
* @return mixed
*/
function block_field( $name, $echo = true ) {
$attributes = block_lab()->loader->get_data( 'attributes' );
if ( ! $attributes ) {
return null;
}
$config = block_lab()->loader->get_data( 'config' );
if ( ! $config ) {
return null;
}
$default_fields = [ 'className' => 'string' ];
/**
* Filters the default fields that are allowed in addition to Block Lab fields.
*
* Adding an attribute to this can enable outputting it via block_field().
* Normally, this function only returns or echoes Block Lab attributes (fields), and one default field.
* But this allows getting block attributes that might have been added by other plugins or JS.
* To allow getting another attribute, add it to the $default_fields associative array.
* For example, 'your-example-field' => 'array'.
*
* @param array $default_fields An associative array of $field_name => $field_type.
* @param string $name The name of value to get.
*/
$default_fields = apply_filters( 'block_lab_default_fields', $default_fields, $name );
if ( ! isset( $config->fields[ $name ] ) && ! isset( $default_fields[ $name ] ) ) {
return null;
}
$field = null;
$value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
$control = null;
if ( array_key_exists( $name, $attributes ) ) {
$value = $attributes[ $name ];
}
if ( isset( $config->fields[ $name ] ) ) {
// Cast the value with the correct type.
$field = $config->fields[ $name ];
$value = $field->cast_value( $value );
$control = $field->control;
} elseif ( isset( $default_fields[ $name ] ) ) {
// Cast default Editor attributes and those added via a filter.
$field = new Blocks\Field( [ 'type' => $default_fields[ $name ] ] );
$value = $field->cast_value( $value );
}
/**
* Filters the value to be made available or echoed on the front-end template.
*
* @param mixed $value The value.
* @param string|null $control The type of the control, like 'user', or null if this is the 'className', which has no control.
* @param bool $echo Whether or not this value will be echoed.
*/
$value = apply_filters( 'block_lab_field_value', $value, $control, $echo );
if ( $echo ) {
if ( $field ) {
$value = $field->cast_value_to_string( $value );
}
/*
* Escaping this value may cause it to break in some use cases.
* If this happens, retrieve the field's value using block_value(),
* and then output the field with a more suitable escaping function.
*/
echo wp_kses_post( $value );
return null;
}
return $value;
}
/**
* Return the value of a block field, without echoing it.
*
* @param string $name The name of the field as created in the UI.
*
* @uses block_field()
*
* @return mixed
*/
function block_value( $name ) {
return block_field( $name, false );
}
/**
* Prepare a loop with the first or next row in a repeater.
*
* @param string $name The name of the repeater field.
*
* @return int
*/
function block_row( $name ) {
block_lab()->loop()->set_active( $name );
return block_lab()->loop()->increment( $name );
}
/**
* Determine whether another repeater row exists to loop through.
*
* @param string $name The name of the repeater field.
*
* @return bool
*/
function block_rows( $name ) {
$attributes = block_lab()->loader->get_data( 'attributes' );
if ( ! isset( $attributes[ $name ] ) ) {
return false;
}
$current_row = block_lab()->loop()->get_row( $name );
if ( false === $current_row ) {
$next_row = 0;
} else {
$next_row = $current_row + 1;
}
if ( isset( $attributes[ $name ]['rows'][ $next_row ] ) ) {
return true;
}
return false;
}
/**
* Resets the repeater block rows after the while loop.
*
* Similar to wp_reset_postdata(). Call this after the repeater loop.
* For example:
*
* while ( block_rows( 'example-repeater-name' ) ) :
* block_row( 'example-repeater-name' );
* block_sub_field( 'example-field' );
* endwhile;
* reset_block_rows( 'example-repeater-name' );
*
* @param string $name The name of the repeater field.
*/
function reset_block_rows( $name ) {
block_lab()->loop()->reset( $name );
}
/**
* Return the total amount of rows in a repeater.
*
* @param string $name The name of the repeater field.
* @return int|bool The total amount of rows. False if the repeater isn't found.
*/
function block_row_count( $name ) {
$attributes = block_lab()->loader->get_data( 'attributes' );
if ( ! isset( $attributes[ $name ]['rows'] ) ) {
return false;
}
return count( $attributes[ $name ]['rows'] );
}
/**
* Return the index of the current repeater row.
*
* Note: The index is zero-based, which means that the first row in a repeater has
* an index of 0, the second row has an index of 1, and so on.
*
* @param string $name (Optional) The name of the repeater field.
* @return int|bool The index of the row. False if the repeater isn't found.
*/
function block_row_index( $name = '' ) {
if ( '' === $name ) {
$name = block_lab()->loop()->active;
}
if ( ! isset( block_lab()->loop()->loops[ $name ] ) ) {
return false;
}
return block_lab()->loop()->loops[ $name ];
}
/**
* Return the value of a sub-field.
*
* @param string $name The name of the sub-field.
* @param bool $echo Whether to echo and return the field, or just return the field.
*
* @return mixed
*/
function block_sub_field( $name, $echo = true ) {
$attributes = block_lab()->loader->get_data( 'attributes' );
if ( ! is_array( $attributes ) ) {
return null;
}
$config = block_lab()->loader->get_data( 'config' );
if ( ! $config ) {
return null;
}
$parent = block_lab()->loop()->active;
$pointer = block_lab()->loop()->get_row( $parent );
if ( ! isset( $config->fields[ $parent ] ) ) {
return null;
}
$value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
$control = null;
// Get the value from the block attributes, with the correct type.
if ( ! array_key_exists( $parent, $attributes ) || ! isset( $attributes[ $parent ]['rows'] ) ) {
return;
}
$parent_attributes = $attributes[ $parent ]['rows'];
$row_attributes = $parent_attributes[ $pointer ];
if ( ! array_key_exists( $name, $row_attributes ) ) {
return;
}
$field = $config->fields[ $parent ]->settings['sub_fields'][ $name ];
$control = $field->control;
$value = $row_attributes[ $name ];
$value = $field->cast_value( $value );
/**
* Filters the value to be made available or echoed on the front-end template.
*
* @param mixed $value The value.
* @param string|null $control The type of the control, like 'user', or null if this is the 'className', which has no control.
* @param bool $echo Whether or not this value will be echoed.
*/
$value = apply_filters( 'block_lab_sub_field_value', $value, $control, $echo );
if ( $echo ) {
$value = $field->cast_value_to_string( $value );
/*
* Escaping this value may cause it to break in some use cases.
* If this happens, retrieve the field's value using block_value(),
* and then output the field with a more suitable escaping function.
*/
echo wp_kses_post( $value );
}
return $value;
}
/**
* Return the value of a sub-field, without echoing it.
*
* @param string $name The name of the sub-field.
*
* @uses block_field()
*
* @return mixed
*/
function block_sub_value( $name ) {
return block_sub_field( $name, false );
}
/**
* Convenience method to return the block configuration.
*
* @return array
*/
function block_config() {
$config = block_lab()->loader->get_data( 'config' );
if ( ! $config ) {
return null;
}
return (array) $config;
}
/**
* Convenience method to return a field's configuration.
*
* @param string $name The name of the field as created in the UI.
*
* @return array|null
*/
function block_field_config( $name ) {
$config = block_lab()->loader->get_data( 'config' );
if ( ! $config || ! isset( $config->fields[ $name ] ) ) {
return null;
}
return (array) $config->fields[ $name ];
}
/**
* Add a new block.
*
* @param string $block_name The block name (slug), like 'example-block'.
* @param array $block_config {
* An associative array containing the block configuration.
*
* @type string $title The block title.
* @type string $icon The block icon. See assets/icons.json for a JSON array of all possible values. Default: 'block_lab'.
* @type string $category The slug of a registered category. Categories include: common, formatting, layout, widgets, embed. Default: 'common'.
* @type array $excluded Exclude the block in these post types. Default: [].
* @type string[] $keywords An array of up to three keywords. Default: [].
* @type array $fields {
* An associative array containing block fields. Each key in the array should be the field slug.
*
* @type array {$slug} {
* An associative array describing a field. Refer to the $field_config parameter of block_lab_add_field().
* }
* }
* }
*/
function block_lab_add_block( $block_name, $block_config = [] ) {
$block_config['name'] = str_replace( '_', '-', sanitize_title( $block_name ) );
$default_config = [
'title' => str_replace( '-', ' ', ucwords( $block_config['name'], '-' ) ),
'icon' => 'block_lab',
'category' => 'common',
'excluded' => [],
'keywords' => [],
'fields' => [],
];
$block_config = wp_parse_args( $block_config, $default_config );
block_lab()->loader->add_block( $block_config );
}
/**
* Add a field to a block.
*
* @param string $block_name The block name (slug), like 'example-block'.
* @param string $field_name The field name (slug), like 'first-name'.
* @param array $field_config {
* An associative array containing the field configuration.
*
* @type string $name The field name.
* @type string $label The field label.
* @type string $control The field control type. Default: 'text'.
* @type int $order The order that the field appears in. Default: 0.
* @type array $settings {
* An associative array of settings for the field. Each field has a different set of possible settings.
* Check the register_settings method for the field, found in php/blocks/controls/class-{field name}.php.
* }
* }
*/
function block_lab_add_field( $block_name, $field_name, $field_config = [] ) {
$field_config['name'] = str_replace( '_', '-', sanitize_title( $field_name ) );
$default_config = [
'label' => str_replace( '-', ' ', ucwords( $field_config['name'], '-' ) ),
'control' => 'text',
'order' => 0,
'settings' => [],
];
$field_config = wp_parse_args( $field_config, $default_config );
block_lab()->loader->add_field( $block_name, $field_config );
}
<?php
/**
* Block Post Type.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
namespace Block_Lab\Post_Types;
use Block_Lab\Component_Abstract;
use Block_Lab\Blocks\Block;
use Block_Lab\Blocks\Field;
use Block_Lab\Blocks\Controls;
/**
* Class Block
*/
class Block_Post extends Component_Abstract {
/**
* Slug used for the custom post type.
*
* @var string
*/
public $slug;
/**
* Registered controls.
*
* @var Controls\Control_Abstract[]
*/
public $controls = [];
/**
* The pro controls.
*
* @var array
*/
public $pro_controls = [
'repeater',
'post',
'rich_text',
'classic_text',
'taxonomy',
'user',
];
/**
* Block Post constructor.
*/
public function __construct() {
$this->slug = block_lab()->get_post_type_slug();
}
/**
* Register any hooks that this component needs.
*
* @return void
*/
public function register_hooks() {
add_action( 'init', [ $this, 'register_post_type' ] );
add_action( 'admin_init', [ $this, 'add_caps' ] );
add_action( 'admin_init', [ $this, 'row_export' ] );
add_action( 'add_meta_boxes', [ $this, 'add_meta_boxes' ] );
add_action( 'add_meta_boxes', [ $this, 'remove_meta_boxes' ] );
add_action( 'edit_form_before_permalink', [ $this, 'template_location' ] );
add_action( 'post_submitbox_start', [ $this, 'save_draft_button' ] );
add_filter( 'enter_title_here', [ $this, 'post_title_placeholder' ] );
add_action( 'post_submitbox_misc_actions', [ $this, 'post_type_condition' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'wp_insert_post_data', [ $this, 'save_block' ], 10, 2 );
add_action( 'init', [ $this, 'register_controls' ] );
add_filter( 'block_lab_field_value', [ $this, 'get_field_value' ], 10, 3 );
add_filter( 'block_lab_sub_field_value', [ $this, 'get_field_value' ], 10, 3 );
// Clean up the list table.
add_filter( 'disable_months_dropdown', '__return_true', 10, $this->slug );
add_filter( 'page_row_actions', [ $this, 'page_row_actions' ], 10, 1 );
add_filter( 'bulk_actions-edit-' . $this->slug, [ $this, 'bulk_actions' ] );
add_filter( 'handle_bulk_actions-edit-' . $this->slug, [ $this, 'bulk_export' ], 10, 3 );
add_filter( 'manage_edit-' . $this->slug . '_columns', [ $this, 'list_table_columns' ] );
add_action( 'manage_' . $this->slug . '_posts_custom_column', [ $this, 'list_table_content' ], 10, 2 );
// AJAX Handlers.
add_action( 'wp_ajax_fetch_field_settings', [ $this, 'ajax_field_settings' ] );
}
/**
* Register the controls.
*
* @return void
*/
public function register_controls() {
$control_names = [
'text',
'textarea',
'url',
'email',
'number',
'color',
'image',
'select',
'multiselect',
'toggle',
'range',
'checkbox',
'radio',
];
if ( block_lab()->is_pro() ) {
$control_names = array_merge( $control_names, $this->pro_controls );
}
foreach ( $control_names as $control_name ) {
$control = $this->get_control( $control_name );
if ( $control ) {
$controls[ $control->name ] = $control;
}
}
/**
* Filters the available controls.
*
* @param array $controls {
* An associative array of the available controls.
*
* @type string $control_name The name of the control, like 'user'.
* @type object $control The control opbject, extending Controls\Control_Abstract.
* }
*/
$this->controls = apply_filters( 'block_lab_controls', $controls );
}
/**
* Gets an instantiated control.
*
* @param string $control_name The name of the control.
* @return object|null The instantiated control, or null.
*/
public function get_control( $control_name ) {
if ( isset( $this->controls[ $control_name ] ) ) {
return $this->controls[ $control_name ];
}
$class_name = ucwords( $control_name, '_' );
$control_class = 'Block_Lab\\Blocks\\Controls\\' . $class_name;
if ( class_exists( $control_class ) ) {
return new $control_class();
}
}
/**
* Gets the field value to be made available or echoed on the front-end template.
*
* Gets the value based on the control type.
* For example, a 'user' control can return a WP_User, a string, or false.
* The $echo parameter is whether the value will be echoed on the front-end template,
* or simply made available.
*
* @param mixed $value The field value.
* @param string $control The type of the control, like 'user'.
* @param bool $echo Whether or not this value will be echoed.
* @return mixed $value The filtered field value.
*/
public function get_field_value( $value, $control, $echo ) {
if ( isset( $this->controls[ $control ] ) && method_exists( $this->controls[ $control ], 'validate' ) ) {
return call_user_func( [ $this->controls[ $control ], 'validate' ], $value, $echo );
} elseif ( in_array( $control, $this->pro_controls, true ) && ! block_lab()->is_pro() ) {
$pro_control = $this->get_control( $control );
if ( method_exists( $pro_control, 'validate' ) ) {
return call_user_func( [ $pro_control, 'validate' ], $value, $echo );
}
}
return $value;
}
/**
* Register the custom post type.
*
* @return void
*/
public function register_post_type() {
$labels = [
'name' => _x( 'Content Blocks', 'post type general name', 'block-lab' ),
'singular_name' => _x( 'Content Block', 'post type singular name', 'block-lab' ),
'menu_name' => _x( 'Block Lab', 'admin menu', 'block-lab' ),
'name_admin_bar' => _x( 'Block', 'add new on admin bar', 'block-lab' ),
'add_new' => _x( 'Add New', 'block', 'block-lab' ),
'add_new_item' => __( 'Add New Block', 'block-lab' ),
'new_item' => __( 'New Block', 'block-lab' ),
'edit_item' => __( 'Edit Block', 'block-lab' ),
'view_item' => __( 'View Block', 'block-lab' ),
'all_items' => __( 'All Blocks', 'block-lab' ),
'search_items' => __( 'Search Blocks', 'block-lab' ),
'parent_item_colon' => __( 'Parent Blocks:', 'block-lab' ),
'not_found' => __( 'No blocks found.', 'block-lab' ),
'not_found_in_trash' => __( 'No blocks found in Trash.', 'block-lab' ),
];
$args = [
'labels' => $labels,
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'menu_position' => 100,
'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
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.
),
'query_var' => true,
'rewrite' => [ 'slug' => $this->slug ],
'hierarchical' => true,
'capabilities' => $this->get_capabilities(),
'map_meta_cap' => true,
'supports' => [ 'title' ],
];
register_post_type( $this->slug, $args );
}
/**
* Add custom capabilities
*
* @return void
*/
public function add_caps() {
$admin = get_role( 'administrator' );
if ( ! $admin ) {
return;
}
foreach ( $this->get_capabilities() as $capability => $custom_capability ) {
$admin->add_cap( $custom_capability );
}
}
/**
* Gets the mapping of capabilities for the custom post type.
*
* @return array An associative array of capability key => custom capability value.
*/
public function get_capabilities() {
return [
'edit_post' => 'block_lab_edit_block',
'edit_posts' => 'block_lab_edit_blocks',
'edit_others_posts' => 'block_lab_edit_others_blocks',
'publish_posts' => 'block_lab_publish_blocks',
'read_post' => 'block_lab_read_block',
'read_private_posts' => 'block_lab_read_private_blocks',
'delete_post' => 'block_lab_delete_block',
];
}
/**
* Enqueue scripts and styles used by the Block post type.
*
* @return void
*/
public function enqueue_scripts() {
$post = get_post();
$screen = get_current_screen();
if ( ! is_object( $screen ) ) {
return;
}
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( $this->slug === $screen->post_type && 'post' === $screen->base ) {
wp_enqueue_style(
'block-post',
$this->plugin->get_url( 'css/admin.block-post.css' ),
[],
$this->plugin->get_version()
);
if ( ! in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) ) {
wp_add_inline_style( 'block-post', '#delete-action { display: none; }' );
}
wp_enqueue_script(
'block-post',
$this->plugin->get_url( 'js/admin.block-post.js' ),
[ 'jquery', 'jquery-ui-sortable', 'wp-util', 'wp-blocks' ],
$this->plugin->get_version(),
false
);
wp_localize_script(
'block-post',
'blockLab',
[
'fieldSettingsNonce' => wp_create_nonce( 'block_lab_field_settings_nonce' ),
'postTypes' => [
'all' => __( 'All', 'block-lab' ),
'none' => __( 'None', 'block-lab' ),
],
'copySuccessMessage' => __( 'Copied to clipboard.', 'block-lab' ),
'copyFailMessage' => sprintf(
// translators: Placeholder is a shortcut key combination.
__( '%1$s to copy.', 'block-lab' ),
strpos( getenv( 'HTTP_USER_AGENT' ), 'Mac' ) ? 'Cmd+C' : 'Ctrl+C'
),
]
);
}
if ( $this->slug === $screen->post_type && 'edit' === $screen->base ) {
wp_enqueue_style(
'block-edit',
$this->plugin->get_url( 'css/admin.block-edit.css' ),
[],
$this->plugin->get_version()
);
}
}
/**
* Add meta boxes.
*
* @return void
*/
public function add_meta_boxes() {
$post = get_post();
add_meta_box(
'block_properties',
__( 'Block Properties', 'block-lab' ),
[ $this, 'render_properties_meta_box' ],
$this->slug,
'side',
'default'
);
add_meta_box(
'block_fields',
__( 'Block Fields', 'block-lab' ),
[ $this, 'render_fields_meta_box' ],
$this->slug,
'normal',
'default'
);
if ( ! empty( $post->post_name ) ) {
$locations = block_lab()->get_template_locations( $post->post_name );
$template = block_lab()->locate_template( $locations, '', true );
if ( ! $template ) {
add_meta_box(
'block_template',
__( 'Template', 'block-lab' ),
[ $this, 'render_template_meta_box' ],
$this->slug,
'normal',
'high'
);
}
}
}
/**
* Removes unneeded meta boxes.
*
* @return void
*/
public function remove_meta_boxes() {
$screen = get_current_screen();
if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
return;
}
remove_meta_box( 'slugdiv', $this->slug, 'normal' );
}
/**
* Adds a "Save Draft" button next to the "Publish" button.
*
* @return void
*/
public function save_draft_button() {
$post = get_post();
$screen = get_current_screen();
if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
return;
}
if ( ! in_array( $post->post_status, [ 'publish', 'future', 'pending' ], true ) ) {
?>
<input type="submit" name="save" value="<?php esc_attr_e( 'Save Draft', 'block-lab' ); ?>" class="button" />
<?php
}
}
/**
* Render the Block Fields meta box.
*
* @return void
*/
public function render_properties_meta_box() {
$post = get_post();
$block = new Block( $post->ID );
$icons = block_lab()->get_icons();
if ( ! $block->icon ) {
$block->icon = 'block_lab';
}
?>
<p>
<label for="block-properties-slug">
<?php esc_html_e( 'Slug:', 'block-lab' ); ?>
</label>
<input
name="post_name"
type="text"
id="block-properties-slug"
value="<?php echo esc_attr( $post->post_name ); ?>" />
</p>
<p class="description">
<?php
esc_html_e(
'Used to determine the name of the template file.',
'block-lab'
);
?>
</p>
<p>
<label for="block-properties-icon">
<?php esc_html_e( 'Icon:', 'block-lab' ); ?>
</label>
<input
name="block-properties-icon"
type="hidden"
id="block-properties-icon"
value="<?php echo esc_attr( $block->icon ); ?>" />
<span id="block-properties-icon-current">
<?php
if ( array_key_exists( $block->icon, $icons ) ) {
echo wp_kses( $icons[ $block->icon ], block_lab()->allowed_svg_tags() );
}
?>
</span>
<a class="button block-properties-icon-button" id="block-properties-icon-choose" href="#block-properties-icon-choose">
<?php esc_attr_e( 'Choose', 'block-lab' ); ?>
</a>
<a class="button block-properties-icon-button" id="block-properties-icon-close" href="#">
<?php esc_attr_e( 'Close', 'block-lab' ); ?>
</a>
<span class="block-properties-icon-select" id="block-properties-icon-select">
<?php
foreach ( $icons as $icon => $svg ) {
$selected = $icon === $block->icon ? 'selected' : '';
printf(
'<span class="icon %1$s" data-value="%2$s">%3$s</span>',
esc_attr( $selected ),
esc_attr( $icon ),
wp_kses( $svg, block_lab()->allowed_svg_tags() )
);
}
?>
</span>
</p>
<p>
<label for="block-properties-category">
<?php esc_html_e( 'Category:', 'block-lab' ); ?>
</label>
<select name="block-properties-category" id="block-properties-category" class="block-properties-category">
<?php
$categories = get_block_categories( $post );
foreach ( $categories as $category ) {
?>
<option value="<?php echo esc_attr( $category['slug'] ); ?>" <?php selected( $category['slug'], $block->category['slug'] ); ?>>
<?php echo esc_html( $category['title'] ); ?>
</option>
<?php
}
?>
<option disabled>────────</option>
<option value="__custom"><?php esc_html_e( 'Custom Category', 'block-lab' ); ?></option>
</select>
<span class="block-properties-category-custom">
<label for="block-properties-category-name">
<?php esc_html_e( 'New Category Name:', 'block-lab' ); ?>
</label>
<input
name="block-properties-category-name"
type="text"
id="block-properties-category-name"
value="" />
</span>
</p>
<p>
<label for="block-properties-keywords">
<?php esc_html_e( 'Keywords:', 'block-lab' ); ?>
</label>
<input
name="block-properties-keywords"
type="text"
id="block-properties-keywords"
value="<?php echo esc_attr( implode( ', ', $block->keywords ) ); ?>" />
</p>
<p class="description">
<?php
esc_html_e(
'A comma separated list of keywords, used when searching. Maximum of 3.',
'block-lab'
);
?>
</p>
<?php
wp_nonce_field( 'block_lab_save_properties', 'block_lab_properties_nonce' );
}
/**
* Render the Block Fields meta box.
*
* @return void
*/
public function render_fields_meta_box() {
$post = get_post();
$block = new Block( $post->ID );
do_action( 'block_lab_before_fields_list' );
?>
<div class="block-fields-list">
<table class="widefat">
<thead>
<tr>
<th class="block-fields-sort"></th>
<th class="block-fields-label">
<?php esc_html_e( 'Field Label', 'block-lab' ); ?>
</th>
<th class="block-fields-name">
<?php esc_html_e( 'Field Name', 'block-lab' ); ?>
</th>
<th class="block-fields-control">
<?php esc_html_e( 'Field Type', 'block-lab' ); ?>
</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4">
<div class="block-fields-rows">
<p class="block-no-fields">
<?php echo wp_kses_post( __( 'Click <strong>Add Field</strong> below to add your first field.', 'block-lab' ) ); ?>
</p>
<?php
if ( count( $block->fields ) > 0 ) {
foreach ( $block->fields as $field ) {
$this->render_fields_meta_box_row( $field, uniqid() );
}
}
?>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="block-fields-actions-add-field">
<button type="button" aria-label="Add Field" class="block-fields-action" id="block-add-field">
<span class="dashicons dashicons-plus"></span>
<?php esc_attr_e( 'Add Field', 'block-lab' ); ?>
</button>
<script type="text/html" id="tmpl-field-repeater">
<?php
$args = [
'name' => 'new-field',
'label' => __( 'New Field', 'block-lab' ),
];
$this->render_fields_meta_box_row( new Field( $args ) );
?>
</script>
<script type="text/html" id="tmpl-sub-field-rows">
<?php $this->render_fields_sub_rows(); ?>
</script>
</div>
<?php
do_action( 'block_lab_after_fields_list' );
wp_nonce_field( 'block_lab_save_fields', 'block_lab_fields_nonce' );
}
/**
* Render a single Field as a row.
*
* @param Field $field The Field containing the options to render.
* @param mixed $uid A unique ID to used to unify the HTML name, for, and id attributes.
* @param mixed $parent_uid The parent's unique ID, if the field has a parent.
*
* @return void
*/
public function render_fields_meta_box_row( $field, $uid = false, $parent_uid = false ) {
// Use a template placeholder if no UID provided.
if ( ! $uid ) {
$uid = '{{ data.uid }}';
}
$is_field_disabled = ( ! isset( $this->controls[ $field->control ] ) && in_array( $field->control, $this->pro_controls, true ) );
?>
<div class="block-fields-row" data-uid="<?php echo esc_attr( $uid ); ?>">
<div class="block-fields-row-columns">
<div class="block-fields-sort">
<span class="block-fields-sort-handle"></span>
</div>
<div class="block-fields-label">
<a class="row-title" href="javascript:" id="block-fields-label_<?php echo esc_attr( $uid ); ?>">
<?php echo esc_html( $field->label ); ?>
</a>
<div class="block-fields-actions">
<a class="block-fields-actions-edit" href="javascript:">
<?php esc_html_e( 'Edit', 'block-lab' ); ?>
</a>
&nbsp;|&nbsp;
<a class="block-fields-actions-duplicate" href="javascript:">
<?php esc_html_e( 'Duplicate', 'block-lab' ); ?>
</a>
&nbsp;|&nbsp;
<a class="block-fields-actions-delete" href="javascript:">
<?php esc_html_e( 'Delete', 'block-lab' ); ?>
</a>
</div>
</div>
<div class="block-fields-name" id="block-fields-name_<?php echo esc_attr( $uid ); ?>">
<code id="block-fields-name-code_<?php echo esc_attr( $uid ); ?>"><?php echo esc_html( $field->name ); ?></code>
</div>
<div class="block-fields-control" id="block-fields-control_<?php echo esc_attr( $uid ); ?>">
<?php
if ( ! $is_field_disabled && isset( $this->controls[ $field->control ] ) ) :
echo esc_html( $this->controls[ $field->control ]->label );
else :
?>
<span class="dashicons dashicons-warning"></span>
<span class="pro-required">
<?php
/* translators: %1$s is the field type, %2$s is the URL for the Pro license */
printf(
wp_kses_post( 'This <code>%1$s</code> field requires an active <a href="%2$s">pro license</a>.', 'block-lab' ),
esc_html( $field->control ),
esc_url(
add_query_arg(
[
'post_type' => 'block_lab',
'page' => 'block-lab-pro',
],
admin_url( 'edit.php' )
)
)
);
?>
</span>
<?php endif; ?>
</div>
</div>
<div class="block-fields-edit">
<table class="widefat">
<tr class="block-fields-edit-label">
<td class="spacer"></td>
<th scope="row">
<label for="block-fields-edit-label-input_<?php echo esc_attr( $uid ); ?>">
<?php esc_html_e( 'Field Label', 'block-lab' ); ?>
</label>
<p class="description">
<?php
esc_html_e(
'A label describing your block\'s custom field.',
'block-lab'
);
?>
</p>
</th>
<td>
<input
name="block-fields-label[<?php echo esc_attr( $uid ); ?>]"
type="text"
id="block-fields-edit-label-input_<?php echo esc_attr( $uid ); ?>"
class="regular-text"
value="<?php echo esc_attr( $field->label ); ?>"
data-sync="block-fields-label_<?php echo esc_attr( $uid ); ?>"
<?php echo $is_field_disabled ? 'readonly="readonly"' : ''; ?>
/>
<?php if ( $is_field_disabled ) : ?>
<input
name="block-is-disabled-pro-field[<?php echo esc_attr( $uid ); ?>]"
type="hidden"
value="true"
/>
<?php endif; ?>
</td>
</tr>
<tr class="block-fields-edit-name">
<td class="spacer"></td>
<th scope="row">
<label for="block-fields-edit-name-input_<?php echo esc_attr( $uid ); ?>">
<?php esc_html_e( 'Field Name', 'block-lab' ); ?>
</label>
<p class="description">
<?php esc_html_e( 'Single word, no spaces.', 'block-lab' ); ?>
</p>
</th>
<td>
<input
name="block-fields-name[<?php echo esc_attr( $uid ); ?>]"
type="text"
id="block-fields-edit-name-input_<?php echo esc_attr( $uid ); ?>"
class="regular-text"
value="<?php echo esc_attr( $field->name ); ?>"
data-sync="block-fields-name-code_<?php echo esc_attr( $uid ); ?>"
<?php echo $is_field_disabled ? 'readonly="readonly"' : ''; ?>
/>
</td>
</tr>
<tr class="block-fields-edit-control">
<td class="spacer"></td>
<th scope="row">
<label for="block-fields-edit-control-input_<?php echo esc_attr( $uid ); ?>">
<?php esc_html_e( 'Field Type', 'block-lab' ); ?>
</label>
</th>
<td>
<select
name="block-fields-control[<?php echo esc_attr( $uid ); ?>]"
id="block-fields-edit-control-input_<?php echo esc_attr( $uid ); ?>"
data-sync="block-fields-control_<?php echo esc_attr( $uid ); ?>"
<?php disabled( $is_field_disabled ); ?> >
<?php
$controls_for_select = $this->controls;
// If this field is disabled, it was probably added when there was a valid pro license, so still display it.
if ( $is_field_disabled && in_array( $field->control, $this->pro_controls, true ) ) {
$controls_for_select[ $field->control ] = $this->get_control( $field->control );
}
// Don't allow nesting repeaters inside repeaters.
if ( ! empty( $field->settings['parent'] ) ) {
unset( $controls_for_select['repeater'] );
}
foreach ( $controls_for_select as $control_for_select ) :
?>
<option
value="<?php echo esc_attr( $control_for_select->name ); ?>"
<?php selected( $field->control, $control_for_select->name ); ?>>
<?php echo esc_html( $control_for_select->label ); ?>
</option>
<?php endforeach; ?>
</select>
</td>
</tr>
<?php $this->render_field_settings( $field, $uid ); ?>
<tr class="block-fields-edit-actions-close">
<td class="spacer"></td>
<th scope="row">
</th>
<td>
<a class="button" title="<?php esc_attr_e( 'Close Field', 'block-lab' ); ?>" href="javascript:">
<?php esc_html_e( 'Close Field', 'block-lab' ); ?>
</a>
</td>
</tr>
</table>
</div>
<?php
if ( 'repeater' === $field->control ) {
if ( ! isset( $field->settings['sub_fields'] ) ) {
$field->settings['sub_fields'] = [];
}
$this->render_fields_sub_rows( $field->settings['sub_fields'], $uid );
}
if ( $parent_uid ) {
?>
<input
type="hidden"
name="block-fields-parent[<?php echo esc_attr( $uid ); ?>]"
value="<?php echo esc_attr( $parent_uid ); ?>"
/>
<?php
}
?>
</div>
<?php
}
/**
* Render the actions row when adding a Repeater field.
*
* @param Field[] $fields The sub fields to render.
* @param mixed $parent_uid The unique ID of the field's parent.
*
* @return void
*/
public function render_fields_sub_rows( $fields = [], $parent_uid = false ) {
?>
<div class="block-fields-sub-rows">
<?php
if ( ! empty( $fields ) ) {
foreach ( $fields as $field ) {
$this->render_fields_meta_box_row( $field, uniqid(), $parent_uid );
}
}
?>
</div>
<div class="block-fields-sub-rows-actions">
<p class="repeater-no-fields <?php echo esc_attr( empty( $fields ) ? '' : 'hidden' ); ?>">
<button type="button" aria-label="Add Sub-Field" id="block-add-sub-field">
<span class="dashicons dashicons-plus"></span>
<?php esc_attr_e( 'Add your first Sub-Field', 'block-lab' ); ?>
</button>
</p>
<p class="repeater-has-fields <?php echo esc_attr( empty( $fields ) ? 'hidden' : '' ); ?>">
<button type="button" aria-label="Add Sub-Field" id="block-add-sub-field">
<span class="dashicons dashicons-plus"></span>
<?php esc_attr_e( 'Add Sub-Field', 'block-lab' ); ?>
</button>
</p>
</div>
<?php
}
/**
* Render the Block Template meta box.
*
* @return void
*/
public function render_template_meta_box() {
$post = get_post();
?>
<div class="template-notice">
<h3>✔️ <?php esc_html_e( 'Next step: Create a block template.', 'block-lab' ); ?></h3>
<p>
<?php esc_html_e( 'To display this block, Block Lab will look for this template file in your theme:', 'block-lab' ); ?>
</p>
<?php
// Formatting to make the template paths easier to understand.
$template = get_stylesheet_directory() . '/blocks/block-' . $post->post_name . '.php';
$template_short = str_replace( WP_CONTENT_DIR, basename( WP_CONTENT_DIR ), $template );
$template_parts = explode( '/', $template_short );
$filename = array_pop( $template_parts );
$template_breaks = '/' . trailingslashit( implode( '/<wbr>', $template_parts ) );
?>
<p class="template-location">
<span class="path"><?php echo wp_kses( $template_breaks, [ 'wbr' => [] ] ); ?></span>
<a class="filename" data-tooltip="<?php esc_attr_e( 'Click to copy.', 'block-lab' ); ?>" href="#"><?php echo esc_html( $filename ); ?></a>
<span class="click-to-copy">
<input type="text" readonly="readonly" value="<?php echo esc_html( $filename ); ?>" />
</span>
</p>
<p>
<strong><?php esc_html_e( 'Learn more:', 'block-lab' ); ?></strong>
<?php
echo wp_kses_post(
sprintf(
'<a href="%1$s" target="_blank">%2$s</a> | ',
'https://getblocklab.com/docs/get-started/add-a-block-lab-block-to-your-website-content/',
esc_html__( 'Block Templates', 'block-lab' )
)
);
echo wp_kses_post(
sprintf(
'<a href="%1$s" target="_blank">%2$s</a>',
'https://getblocklab.com/docs/functions/',
esc_html__( 'Template Functions', 'block-lab' )
)
);
?>
</p>
</div>
<?php
}
/**
* Display the template location below the title.
*/
public function template_location() {
$post = get_post();
$screen = get_current_screen();
if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
return;
}
if ( ! isset( $post->post_name ) || empty( $post->post_name ) ) {
return;
}
$locations = block_lab()->get_template_locations( $post->post_name, 'block' );
$template = block_lab()->locate_template( $locations, '', true );
if ( ! $template ) {
return;
}
// Formatting to make the template paths easier to understand.
$template_short = str_replace( WP_CONTENT_DIR, basename( WP_CONTENT_DIR ), $template );
$template_parts = explode( '/', $template_short );
$filename = array_pop( $template_parts );
$template_breaks = '/' . trailingslashit( implode( '/', $template_parts ) );
if ( $template ) {
?>
<div id="edit-slug-box">
<strong><?php esc_html_e( 'Template:', 'block-lab' ); ?></strong>
<?php echo esc_html( $template_breaks ); ?><strong><?php echo esc_html( $filename ); ?></strong>
</div>
<?php
}
}
/**
* Render the settings for a given field.
*
* @param Field $field The Field containing the options to render.
* @param string $uid A unique ID to used to unify the HTML name, for, and id attributes.
*
* @return void
*/
public function render_field_settings( $field, $uid ) {
if ( isset( $this->controls[ $field->control ] ) ) {
$this->controls[ $field->control ]->render_settings( $field, $uid );
}
}
/**
* Ajax response for fetching field settings.
*
* @return void
*/
public function ajax_field_settings() {
wp_verify_nonce( 'block_lab_field_options_nonce' );
if ( ! isset( $_POST['control'] ) || ! isset( $_POST['uid'] ) ) {
wp_send_json_error();
return;
}
$control = sanitize_key( $_POST['control'] );
$uid = sanitize_key( $_POST['uid'] );
ob_start();
$field = new Field( [ 'control' => $control ] );
if ( isset( $_POST['parent'] ) ) {
$field->settings['parent'] = sanitize_key( $_POST['parent'] );
}
$this->render_field_settings( $field, $uid );
$data['html'] = ob_get_clean();
if ( '' === $data['html'] ) {
wp_send_json_error();
}
wp_send_json_success( $data );
}
/**
* Save block meta boxes as a json blob in post content.
*
* @param array $data An array of slashed post data.
*
* @return array
*/
public function save_block( $data ) {
if ( ! isset( $_POST['post_ID'] ) ) {
return $data;
}
$post_id = sanitize_key( $_POST['post_ID'] );
// Exits script depending on save status.
if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
return $data;
}
// Exits script if not the right post type.
if ( $this->slug !== $data['post_type'] ) {
return $data;
}
check_admin_referer( 'block_lab_save_fields', 'block_lab_fields_nonce' );
check_admin_referer( 'block_lab_save_properties', 'block_lab_properties_nonce' );
// Strip encoded special characters, like 🖖 (%f0%9f%96%96).
$data['post_name'] = preg_replace( '/%[a-f|0-9][a-f|0-9]/', '', $data['post_name'] );
// sanitize_title() allows underscores, but register_block_type doesn't.
$data['post_name'] = str_replace( '_', '-', $data['post_name'] );
// If only special characters were used, it's possible the post_name is now empty.
if ( '' === $data['post_name'] ) {
$data['post_name'] = $post_id;
}
// register_block_type doesn't allow slugs starting with a number.
if ( is_numeric( $data['post_name'][0] ) ) {
$data['post_name'] = 'block-' . $data['post_name'];
}
// Make sure the block slug is still unique.
$data['post_name'] = wp_unique_post_slug(
$data['post_name'],
$post_id,
$data['post_status'],
$data['post_type'],
$data['post_parent']
);
$block = new Block();
// Block name.
$block->name = sanitize_key( $data['post_name'] );
if ( '' === $block->name ) {
$block->name = $post_id;
}
// Block title.
$block->title = sanitize_text_field(
wp_unslash( $data['post_title'] )
);
if ( '' === $block->title ) {
$block->title = $post_id;
}
// Block excluded post type.
if ( isset( $_POST['block-excluded-post-types'] ) ) {
$excluded = sanitize_text_field(
wp_unslash( $_POST['block-excluded-post-types'] )
);
if ( ! empty( $excluded ) ) {
$block->excluded = explode( ',', $excluded );
}
}
// Block icon.
if ( isset( $_POST['block-properties-icon'] ) ) {
$block->icon = sanitize_key( $_POST['block-properties-icon'] );
}
// Block category.
if ( isset( $_POST['block-properties-category'] ) ) {
$category_slug = sanitize_key( $_POST['block-properties-category'] );
$categories = get_block_categories( get_post() );
if ( '__custom' === $category_slug && isset( $_POST['block-properties-category-name'] ) ) {
$category = [
'slug' => sanitize_key( $_POST['block-properties-category-name'] ),
'title' => sanitize_text_field(
wp_unslash( $_POST['block-properties-category-name'] )
),
'icon' => null,
];
} else {
$category_slugs = wp_list_pluck( $categories, 'slug' );
$category_key = array_search( $category_slug, $category_slugs, true );
$category = $categories[ $category_key ];
}
if ( ! $category ) {
$category = isset( $categories[0] ) ? $categories[0] : '';
}
$block->category = $category;
}
// Block keywords.
if ( isset( $_POST['block-properties-keywords'] ) ) {
$keywords = sanitize_text_field(
wp_unslash( $_POST['block-properties-keywords'] )
);
$keywords = explode( ',', $keywords );
$keywords = array_map( 'trim', $keywords );
$keywords = array_slice( $keywords, 0, 3 );
$block->keywords = $keywords;
}
// Block fields.
if ( isset( $_POST['block-fields-name'] ) && is_array( $_POST['block-fields-name'] ) ) {
// We loop through this array and sanitize its content according to the content type.
$fields = wp_unslash( $_POST['block-fields-name'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
foreach ( $fields as $key => $name ) {
// Field name and order.
$field_config = [ 'name' => sanitize_key( $name ) ];
// Field label.
if ( isset( $_POST['block-fields-label'][ $key ] ) ) {
$field_config['label'] = sanitize_text_field(
wp_unslash( $_POST['block-fields-label'][ $key ] )
);
}
// Field control.
if ( isset( $_POST['block-fields-control'][ $key ] ) ) {
$field_config['control'] = sanitize_text_field(
wp_unslash( $_POST['block-fields-control'][ $key ] )
);
}
// Field type.
if ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) {
$field_config['type'] = $this->controls[ $field_config['control'] ]->type;
}
/*
* Field settings.
* If the field is a pro field that's no longer available, re-save the previous value of that field.
* This allows saving other new fields, while retaining the previous pro field value in case the user reactivates the license.
*/
if ( ! empty( $_POST['block-is-disabled-pro-field'][ $key ] ) ) {
$previous_block = new Block( $post_id );
foreach ( $previous_block->fields as $previous_field ) {
if ( $name === $previous_field->name ) {
$field = $previous_field;
break;
}
}
} elseif ( isset( $field_config['control'] ) && isset( $this->controls[ $field_config['control'] ] ) ) {
$control = $this->controls[ $field_config['control'] ];
foreach ( $control->settings as $setting ) {
$value = false; // This is a good default, it allows us to pick up on unchecked checkboxes.
if ( isset( $_POST['block-fields-settings'][ $key ][ $setting->name ] ) ) {
$value = $_POST['block-fields-settings'][ $key ][ $setting->name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$value = wp_unslash( $value );
}
// Sanitize the field options according to their type.
if ( is_callable( $setting->sanitize ) ) {
$value = call_user_func( $setting->sanitize, $value );
}
// Validate the field options according to their type.
if ( is_callable( $setting->validate ) ) {
$value = call_user_func(
$setting->validate,
$value,
$field_config['settings']
);
}
$field_config['settings'][ $setting->name ] = $value;
$field = new Field( $field_config );
}
} else {
$field = new Field( $field_config );
}
/*
* Sub-Fields
* If there's a "block-fields-parent" input, include the current field in a "sub-fields" field setting
* for the specified parent.
*/
if ( ! empty( $_POST['block-fields-parent'][ $key ] ) ) {
$parent_uid = sanitize_key( $_POST['block-fields-parent'][ $key ] );
// The parent's name should have been submitted.
if ( ! isset( $fields[ $parent_uid ] ) ) {
continue;
}
$parent = $fields[ $parent_uid ];
// The parent field should be set by now. We expect it to always precede the child field.
if ( ! isset( $block->fields[ $parent ] ) ) {
continue;
}
if ( ! isset( $block->fields[ $parent ]->settings['sub_fields'] ) ) {
$block->fields[ $parent ]->settings['sub_fields'] = [];
}
$field->settings['parent'] = $parent;
$field->order = count(
$block->fields[ $parent ]->settings['sub_fields']
);
$block->fields[ $parent ]->settings['sub_fields'][ $name ] = $field;
} else {
$field->order = count( $block->fields );
$block->fields[ $name ] = $field;
}
}
}
$data['post_content'] = wp_slash( $block->to_json() );
return $data;
}
/**
* Change the default "Enter Title Here" placeholder on the edit post screen.
*
* @param string $title Placeholder text. Default 'Enter title here'.
*
* @return string
*/
public function post_title_placeholder( $title ) {
$screen = get_current_screen();
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( is_object( $screen ) && $this->slug === $screen->post_type ) {
$title = __( 'Enter block name here', 'block-lab' );
}
return $title;
}
/**
* Displays an option for editing the post type that this block appears on.
*/
public function post_type_condition() {
if ( ! block_lab()->is_pro() ) {
return;
}
$screen = get_current_screen();
// Enqueue scripts and styles on the edit screen of the Block post type.
if ( ! is_object( $screen ) || $this->slug !== $screen->post_type ) {
return;
}
$post_types = get_post_types(
[
'show_in_rest' => true,
'show_in_menu' => true,
],
'objects'
);
$post_types = array_filter(
$post_types,
function( $post_type ) {
return post_type_supports( $post_type->name, 'editor' );
}
);
$block = new Block( get_the_ID() );
?>
<div class="block-lab-pub-section hide-if-no-js">
<?php esc_html_e( 'Post Types:', 'block-lab' ); ?> <span class="post-types-display"></span>
<a href="#post-types-select" class="edit-post-types" role="button">
<span aria-hidden="true"><?php esc_html_e( 'Edit', 'block-lab' ); ?></span>
</a>
<input type="hidden" value="<?php echo esc_attr( implode( ',', $block->excluded ) ); ?>" name="block-excluded-post-types" id="block-excluded-post-types" />
<div class="post-types-select">
<div class="post-types-select-items">
<?php
foreach ( $post_types as $post_type ) {
?>
<input type="checkbox" id="block-post-type-<?php echo esc_attr( $post_type->name ); ?>" value="<?php echo esc_attr( $post_type->name ); ?>">
<label for="block-post-type-<?php echo esc_attr( $post_type->name ); ?>"><?php echo esc_html( $post_type->label ); ?></label>
<br />
<?php
}
?>
</div>
<a href="#post-types" class="save-post-types button"><?php esc_html_e( 'OK', 'block-lab' ); ?></a>
<a href="#post-types" class="button-cancel"><?php esc_html_e( 'Cancel', 'block-lab' ); ?></a>
</div>
</div>
<?php
}
/**
* Change the columns in the Custom Blocks list table
*
* @param array $columns An array of column name ⇒ label. The name is passed to functions to identify the column.
*
* @return array
*/
public function list_table_columns( $columns ) {
$new_columns = [
'cb' => $columns['cb'],
'title' => $columns['title'],
'icon' => __( 'Icon', 'block-lab' ),
'template' => __( 'Template', 'block-lab' ),
'category' => __( 'Category', 'block-lab' ),
'keywords' => __( 'Keywords', 'block-lab' ),
];
return $new_columns;
}
/**
* Output custom column data into the table
*
* @param string $column The name of the column to display.
* @param int $post_id The ID of the current post.
*
* @return void
*/
public function list_table_content( $column, $post_id ) {
if ( 'icon' === $column ) {
$block = new Block( $post_id );
$icons = block_lab()->get_icons();
if ( isset( $icons[ $block->icon ] ) ) {
printf(
'<span class="icon %1$s">%2$s</span>',
esc_attr( $block->icon ),
wp_kses( $icons[ $block->icon ], block_lab()->allowed_svg_tags() )
);
}
}
if ( 'template' === $column ) {
$block = new Block( $post_id );
$locations = block_lab()->get_template_locations( $block->name, 'block' );
$template = block_lab()->locate_template( $locations, '', true );
if ( ! $template ) {
esc_html_e( 'No template found.', 'block-lab' );
} else {
// Formatting to make the template path easier to understand.
$template_short = str_replace( WP_CONTENT_DIR . '/themes/', '', $template );
$template_parts = explode( '/', $template_short );
$template_breaks = implode( '/', $template_parts );
echo wp_kses(
'<code>' . $template_breaks . '</code>',
[
'code' => [],
'wbr' => [],
]
);
}
}
if ( 'keywords' === $column ) {
$block = new Block( $post_id );
echo esc_html( implode( ', ', $block->keywords ) );
}
if ( 'category' === $column ) {
$block = new Block( $post_id );
echo esc_html( $block->category['title'] );
}
}
/**
* Hide the Quick Edit row action.
*
* @param array $actions An array of row action links.
*
* @return array
*/
public function page_row_actions( $actions = [] ) {
$post = get_post();
// Abort if the post type is incorrect.
if ( $this->slug !== $post->post_type ) {
return $actions;
}
// Remove the Quick Edit link.
if ( isset( $actions['inline hide-if-no-js'] ) ) {
unset( $actions['inline hide-if-no-js'] );
}
// Add the Export link.
if ( block_lab()->is_pro() ) {
$export = [
'export' => sprintf(
'<a href="%1$s" aria-label="%2$s">%3$s</a>',
add_query_arg( [ 'export' => $post->ID ] ),
sprintf(
// translators: Placeholder is a post title.
__( 'Export %1$s', 'block-lab' ),
get_the_title( $post->ID )
),
__( 'Export', 'block-lab' )
),
];
$actions = array_merge(
array_slice( $actions, 0, 1 ),
$export,
array_slice( $actions, 1 )
);
}
// Return the set of links without Quick Edit.
return $actions;
}
/**
* Remove Edit from the Bulk Actions menu
*
* @param array $actions An array of bulk actions.
*
* @return array
*/
public function bulk_actions( $actions ) {
unset( $actions['edit'] );
if ( block_lab()->is_pro() ) {
$actions['export'] = __( 'Export', 'block-lab' );
}
return $actions;
}
/**
* Handle the Export of a single block.
*/
public function row_export() {
if ( ! block_lab()->is_pro() ) {
return;
}
$post_id = filter_input( INPUT_GET, 'export', FILTER_SANITIZE_NUMBER_INT );
// Check if the export has been requested, and the user has permission.
if ( $post_id <= 0 || ! current_user_can( 'block_lab_read_block', $post_id ) ) {
return;
}
$this->export( [ $post_id ] );
}
/**
* Handle Exporting blocks via Bulk Actions
*
* @param string $redirect Location to redirect to after the bulk action is completed.
* @param string $action The action to handle.
* @param array $post_ids The IDs to handle.
*
* @return string
*/
public function bulk_export( $redirect, $action, $post_ids ) {
if ( ! block_lab()->is_pro() ) {
return $redirect;
}
if ( 'export' !== $action ) {
return $redirect;
}
$this->export( $post_ids );
$redirect = add_query_arg( 'bulk_export', count( $post_ids ), $redirect );
return $redirect;
}
/**
* Export Blocks
*
* @param int[] $post_ids The post IDs to export.
*/
private function export( $post_ids ) {
$blocks = [];
foreach ( $post_ids as $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
break;
}
// Check that the post content is valid JSON.
$block = json_decode( $post->post_content, true );
if ( JSON_ERROR_NONE !== json_last_error() ) {
break;
}
$blocks = array_merge( $blocks, $block );
}
// If only one block is being exported, use the block's slug as the filename.
$filename = 'blocks.json';
if ( 1 === count( $post_ids ) ) {
$post = get_post( $post_ids[0] );
$filename = $post->post_name . '.json';
}
// Output the JSON file.
header( 'Content-disposition: attachment; filename=' . $filename );
header( 'Content-type:application/json;charset=utf-8' );
echo wp_json_encode( $blocks ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
die();
}
}
<?php
/**
* Block Lab settings form for the License tab.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
?>
<form method="post" action="options.php">
<?php
settings_fields( 'block-lab-license-key' );
do_settings_sections( 'block-lab-license-key' );
?>
<table class="form-table">
<tr valign="top">
<th scope="row">
<label><?php esc_html_e( 'License', 'block-lab' ); ?></label>
</th>
<td>
<?php
if ( block_lab()->is_pro() ) {
$license = block_lab()->admin->license->get_license();
$limit = __( 'unlimited', 'block-lab' );
if ( isset( $license['license_limit'] ) && intval( $license['license_limit'] ) > 0 ) {
$limit = $license['license_limit'];
}
$count = '0';
if ( isset( $license['site_count'] ) ) {
$count = $license['site_count'];
}
$expiry = gmdate( get_option( 'date_format' ) );
if ( isset( $license['expires'] ) ) {
$expiry = gmdate( get_option( 'date_format' ), strtotime( $license['expires'] ) );
}
echo wp_kses_post(
sprintf(
'<p>%1$s %2$s</p>',
sprintf(
// translators: A number, wrapped in <strong> tags.
__( 'Your license includes %1$s site installs.', 'block-lab' ),
'<strong>' . $limit . '</strong>'
),
sprintf(
// translators: A number, wrapped in <strong> tags.
__( '%1$s of them are in use.', 'block-lab' ),
'<strong>' . $count . '</strong>'
)
)
);
echo wp_kses_post(
sprintf(
'<p>%1$s %2$s</p>',
sprintf(
// translators: A date.
__( 'Your license expires on %1$s.', 'block-lab' ),
'<strong>' . $expiry . '</strong>'
),
sprintf(
// translators: An opening and closing anchor tag.
__( '%1$sManage Licenses%2$s', 'block-lab' ),
'<a href="https://getblocklab.com/checkout/purchase-history/" target="_blank">',
'</a>'
)
)
);
} else {
echo wp_kses_post(
sprintf(
'<p>%1$s</p>',
__( 'No license was found for this installation.', 'block-lab' )
)
);
}
?>
</td>
</tr>
<tr valign="top">
<th scope="row">
<label for="block_lab_license_key"><?php esc_html_e( 'License key', 'block-lab' ); ?></label>
</th>
<td>
<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' ) ); ?>" />
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<?php
/**
* Block Lab Pro upgrade page.
*
* @package Block_Lab
* @copyright Copyright(c) 2020, Block Lab
* @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0)
*/
?>
<section class="container">
<div class="dashboard_welcome tile">
<div class="tile_body">
<div>
<h1>Block Lab <span class="pro-pill">Pro</span></h1>
<p class="description"><?php esc_html_e( '…is no longer available. 😢', 'block-lab' ); ?></p>
</div>
</div>
</div>
<!-- Dashboard Tile -->
<div class="tile tile_3">
<div class="tile_body">
<h4><?php esc_html_e( '★★ Loving Block Lab? ★★', 'block-lab' ); ?></h4>
<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>
<a class="button" target="_blank" href="https://wordpress.org/plugins/block-lab/#reviews"><?php esc_html_e( '★ Leave Review ★', 'block-lab' ); ?></a>
</div>
</div>
<!-- Dashboard Tile -->
<div class="tile tile_3">
<div class="tile_body">
<h4><?php esc_html_e( 'Get more out of Block Lab', 'block-lab' ); ?></h4>
<p><?php esc_html_e( 'Subscribe to our newsletter for news, updates, and tutorials on working with Gutenberg.', 'block-lab' ); ?></p>
</div>
<div class="tile_footer tile_footer_email">
<div id="mc_embed_signup">
<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>
<div id="mc_embed_signup_scroll">
<div class="mc-field-group">
<label class="input_label" for="mce-EMAIL">Email Address </label>
<input class="input" type="email" value="" placeholder="Email Address" name="EMAIL" id="mce-EMAIL" />
</div>
<div id="mce-responses" class="clear">
<div class="response" id="mce-error-response" style="display:none"></div>
<div class="response" id="mce-success-response" style="display:none"></div>
</div>
<div class="clear">
<input class="button" type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" />
</div>
</div>
</form>
</div>
<!--End mc_embed_signup-->
</div>
</div>
</section>
=== Block Lab ===
Contributors: lukecarbis, ryankienstra, Stino11, rheinardkorf
Tags: gutenberg, blocks, block editor, fields, template
Requires at least: 5.0
Tested up to: 5.5
Requires PHP: 5.6
Stable tag: 1.6.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl
The easy way to build custom blocks for Gutenberg.
== Description ==
= IMPORTANT! =
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.
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).
----
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.
== Features ==
= A Familiar Experience =
Work within the WordPress admin with an interface you already know.
= Block Fields =
Add from a growing list of available fields to your custom blocks.
= Simple Templating =
Let the plugin do the heavy lifting so you can use familiar WordPress development practices to build block templates.
= Developer Friendly Functions =
Simple to use functions, ready to render and work with the values stored through your custom block fields.
== Links ==
* [WordPress.org](https://wordpress.org/plugins/block-lab)
* [Github](https://github.com/getblocklab/block-lab)
* [Documentation](https://getblocklab.com/docs)
* [Support](https://wordpress.org/support/plugin/block-lab)
== Installation ==
= From Within WordPress =
* Visit Plugins > Add New
* Search for "Block Lab"
* Install the Block Lab plugin
* Activate Block Lab from your Plugins page.
= Manually =
* Clone Block Lab into a working directory with `https://github.com/getblocklab/block-lab.git`
* `cd` into the `block-lab` directory, and run `npm install && composer install`
* Next, build the scripts and styles with `npm build`
* Move the `block-lab` folder to your `/wp-content/plugins/` directory
* Activate the Block Lab plugin through the Plugins menu in WordPress
== Frequently Asked Questions =
**Q: Do I need to write code to use this plugin?**
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.
**Q: I have an idea for the plugin**
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).
**Q: Where can I find documentation for this plugin?**
A: [Here](https://getblocklab.com/docs/)
== Contributing ==
See [Contributing to Genesis Custom Blocks](https://github.com/studiopress/genesis-custom-blocks/blob/develop/CONTRIBUTING.md).
== Changelog ==
= 1.6.0 – 2020-09-02 =
Migration to Genesis Custom Blocks
* New: Full migration UI to Genesis Custom Blocks, the new home of our custom block efforts
* The new plugin has the same features, and your existing blocks and content should work the same after migration
= 1.5.6 – 2020-08-10 =
Small bugfixes, improved testing
* Fix: Prevent a console warning for a prop in WP 5.5
* New: More JS component tests, and an e2e test
= 1.5.5 – 2020-04-23 =
Removed upgrade screen, dependency updates
* Tweak: By default, the upgrade screen is hidden
* Tweak: Minor package.json dependency updates for security and reliability
= 1.5.4 – 2020-03-26 =
Improved stability, small bugfixes.
* Fix: Now block_field() returns null if the second argument is true, preventing confusion.
* New: JavaScript component tests, improving reliability.
* New: Linting for accessibility issues.
= 1.5.3 – 2020-01-20 =
Some UI improvements, bugfixes, and improved stability.
* Fix: Improved import error feedback, and cleaner methods
* Fix: Editor bug from `@wordpress/nux` package being deprecated
* New: Improved stability, including JS tests and query limit
* New: Pre-commit hook to lint only staged files
= 1.5.2 – 2020-02-04 =
Some small tweaks to the Block Importer and onboarding dialogs.
* New: Selective import now allows you to choose which of the blocks contained in your export file you'd like to import
* Fix: Onboarding notices are fixed so that they show in the right places, and at the right times
= 1.5.1 – 2019-11-11 =
This is a bugfix release, focused mostly on compatibility with WordPress 5.3.
* Fix: Themes can now hook into the `block_lab_add_blocks` action from the `functions.php` file
* Fix: Classic Text fields now function as expected when inside a repeater
* Fix: Rare instances of a `NaN` error when duplicating fields
* Fix: Style fixes for the Block Editor in WordPress 5.3
= 1.5.0 – 2019-10-30 =
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.
* 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
* 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
* New: Duplicate fields – building your block is now so much easier, with the ability to duplicate rows
* 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)
* 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)
* 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).
* Tweak: We've refactored quite a lot about our block Loader class, to make it more robust, secure, and maintainable
* Tweak: Loads of new unit and integration tests - these help prevent us from introducing bugs or regressions in the future
* Fix: Bug which affected sites which had removed or renamed the admin user role
* Fix: Empty Rich Text fields now no longer output a single `</p>` tag
= 1.4.1 – 2019-09-11 =
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.
* New: The repeater field now includes a minimum and maximum row setting
* Fix: Location and Width settings are now visible again when adding a new field
* Fix: Using block_sub_field() with an image now correctly outputs the image URL instead of the ID
= 1.4.0 – 2019-09-04 =
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.
* New: Function to reset repeater rows: reset_block_rows()
* New: Add a "Field Width" control to blocks
* Fix: Empty repeater rows now save and can be moved properly
* Fix: An issue which occasionally prevented repeater rows from being deleted
* Fix: Prevent repeated requests to validate a Pro license
* Tweaks: Add a different admin notice for when a license validation request fails
* Tweaks: Many new and shiny unit and integration tests, making Block Lab more solid than ever
= 1.3.6 – 2019-08-22 =
* New: 🔁 REPEATER FIELD 🔁
* New: Conditional Blocks, based on Post Type
* Tweaks: Just about everything! We did a lot of refactoring in this release to make things silky smooth and über-maintainable.
= 1.3.5 – 2019-08-18 =
* 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)
* 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)
* Tweak: Use a textarea for specifying the default value of a textarea control.
* Tweak: Better handling of deprecated functions.
* Tweak: Rewrite of various functions, making developer access to common commands much simpler.
* Fix: Child theme templates are now correctly loaded before their parent templates.
* Fix: Autoslugs now continue to work properly after the title field loses focus.
= 1.3.4 - 2019-07-22 =
* New: Block Lab grew to level 1.3.4. Block Lab learned **Custom Categories**.
* Tweak: **@phpbits** used Pull Request. All right! The **`block_lab_get_block_attributes`** filter was caught!
* Tweak: **Template Loader** used Harden. **Template Loader**'s defense rose!
* Tweak: Booted up a TM! It contained **Unit Tests**!
* Fix: Wild **Missing Filter in Inspector Controls** bug appeared! Go! Bugfix!
* Fix: Enemy **Mixed Up Inspector Controls** fainted! @kienstra gained 0902a06 EXP. Points!
= 1.3.3 - 2019-06-21 =
* Fix: The previous release broke the `className` field, used for the Additional CSS Class setting. This fixes it.
= 1.3.2 - 2019-06-21 =
* New: Rich Text Control (for Block Lab Pro users)!
* New: Show Block Category in the list table
* 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
* Tweak: Updated logo
* Tweak: Prevent block field slugs from changing when you edit the field title
* Fix: Saving your license key no longer results in an error page
* Fix: License details screen showing the wrong information
* Fix: Remove duplicate IDs on the edit block screen
* Fix: Range sliders can now set a minimum value of zero
* Fix: A console warning about unique props
= 1.3.1 - 2019-05-22 =
* 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/)
* New: The Textarea field now has an option to automatically add paragraph tags and line-breaks
* Fix: Bug affecting blocks containing Pro fields when there's no active Pro license
= 1.3.0 - 2019-04-30 =
**Important**: This update includes a backwards compatibility break related to the User field.
[Read more here](https://github.com/getblocklab/block-lab/pull/294===issue-272649668)
* New: A Taxonomy control type, for selecting a Category / Tag / or custom term from a dropdown menu (for Block Lab Pro users)
* Fix: Bug with the Post control when outputting data with block_field()
* Tweak: Update the User control to store data as an object, matching the Post control
= 1.2.3 - 2019-04-23 =
**Important**: This update includes a backwards compatibility break related to the Image field.
If you are using the `block_value()` function with an image field and externally hosted images, this update may effect you.
[Read more here](https://getblocklab.com/backwards-compatability-break-for-the-image-field/)
* New: A Post control type, for selecting a Post from a dropdown menu (for Block Lab Pro users)
* New: Added the block_lab_controls filter to allow custom controls to be loaded (props @rohan2388)
* New: The Image control now returns the image's Post ID
* Tweak: Travis CI support
= 1.2.2 - 2019-04-05 =
* New: Block Editor redesign
= 1.2.1 - 2019-03-21 =
* 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/).
* New: A User control type (for Block Lab Pro users)
* Fix: Various multiselect bug fixes, allowing for empty values in the multiselect control
= 1.2.0 - 2019-02-27 =
* New: Introducing Block Lab Pro!
* New: A setting for the number of rows to display in a Textarea control
* Fix: Allow negative numbers in Number and Range controls
= 1.1.3 - 2019-01-25 =
* New: Image field
= 1.1.2 - 2019-01-11 =
* New: Color field
* Fix: Incorrect output for empty fields
= 1.1.1 - 2018-12-14 =
* Fix: Undefined index error for multiselect and select fields
* Fix: Correct values now returned for boolean fields like checkbox and toggle
* Fix: Editor preview templates are back! Use the filename `preview-{blog slug}.php`
* Fix: "Field instructions" is now a single line text, and renamed to "Help Text"
* Fix: Slashes being added to field options
* Fix: Allow empty value for select and number fields
* Fix: Allow empty default values
= 1.1.0 - 2018-12-07 =
* New: Complete revamp of the in-editor preview
* New: Email field
* New: URL field
* New: Number field
* New: `block_config()` and `block_field_config` helper functions, to retrieve your block's configuration
* Fix: filemtime errors
* Fix: HTML tags were being merged together when previewed in the editor
* Fix: Problems with quotes and dashes in a block's title or field parameters
* Fix: `field_value()` sometimes returned the wrong value
* Fix: Incorrect values shown in the editor preview
= 1.0.1 - 2018-11-16 =
* New: Added "Save Draft" button, so you can save Blocks-in-Progress
* New: Better handling of the auto-slug feature, so you don't accidentally change your block's slug
* New: Better expanding / contracting of the Field settings
* New: Emoji (and special character) support! 😎
* Fix: Resolved Fatal Error that could occur in some environments
* Fix: Remove unused "Description" field
* Fix: Remove duplicate star icon
= 1.0.0 - 2018-11-14 =
*Rename!*
* Advanced Custom Blocks is now Block Lab
*Added*
* New control types (Radio, Checkbox, Toggle, Select, Range)
* Block icons
* Field location – add your block fields to the inspector
* List table refinements
* Field repeater table improvements
*Fixed*
* All the things. Probably not _all_ the things, but close.
= 0.1.2 - 2018-08-10 =
*Added*
* New properties `help`, `default` and `required` added to fields.
* Ability to import blocks from a `{theme}/blocks/blocks.json` file. Documentation still to be added.
* Gutenberg controls library updated preparing for `0.0.3`.
*Technical Changes*
* Updated control architecture to improve development and adding adding of additional controls.
* Clean up enqueuing of scripts.
= 0.1 - 2018-08-03 =
* Initial release.
<div class="numbers-block" style="color:#fff;background-color:<?php block_field( 'color' ); ?>;">
<h3><span class="number"><?php block_field( 'number' ); ?></span><span><?php block_field( 'header' ); ?></span></h3>
<p><?php block_field( 'paragraph' ); ?></p>
</div>
\ No newline at end of file
......@@ -16417,6 +16417,73 @@ h4, .h4 {
display: none !important;
}
@media (min-width: 1400px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
max-width: 1366px;
}
}
@media (min-width: 1200px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl {
max-width: 1234px;
}
}
a.find {
margin-bottom: 50px;
background-color: #012169;
text-transform: uppercase;
font-family: "Calibri-bold";
font-size: 1.25rem;
line-height: 1.25rem;
padding: 2px 50px 17px 60px;
border-radius: 0px;
width: 368px;
color: #fff;
margin-right: 0px !important;
}
a.find:focus,
a.find:hover {
background-color: #005eb8;
}
a.find:before {
content: "";
width: 22px;
height: 31px;
display: inline-block;
top: 10px;
position: relative;
margin-left: -25px;
margin-right: 10px;
background-repeat: no-repeat;
background-position: center;
background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
}
ss3-force-full-width {
top: 0px !important;
position: relative !important;
}
@media (max-width: 768px) {
ss3-force-full-width {
top: 66px !important;
position: relative !important;
}
}
.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 {
z-index: 9999999 !important;
}
.n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image {
z-index: 9999999 !important;
position: absolute !important;
......@@ -16472,52 +16539,196 @@ h4, .h4 {
text-shadow: 0px 3px 3px #00000059 !important;
}
ss3-force-full-width {
top: 0px !important;
position: relative !important;
.intro {
background-color: #333F48;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
}
@media (max-width: 768px) {
ss3-force-full-width {
top: 66px !important;
position: relative !important;
.intro p {
font-family: "Calibri";
font-size: 1.875rem;
color: #fff;
line-height: 2.125rem;
}
@media (max-width: 800px) {
.intro {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
}
@media (max-width: 600px) {
.intro {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.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 {
z-index: 9999999 !important;
.yellow {
background-color: #FFB81C;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
}
.yellow p {
font-family: "Calibri";
font-size: 1.125rem;
color: #333F48;
line-height: 1.3125rem;
}
@media (max-width: 800px) {
.yellow {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
}
@media (max-width: 600px) {
.yellow {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.yellow .number-text {
display: inline-block;
margin-left: 10px;
}
.yellow span.number-big {
font-family: "Calibri-bold";
font-size: 12.8125rem;
color: #012169;
}
.yellow span.number-med {
font-family: "Calibri-bold";
font-size: 2.4375rem;
color: #333F48;
margin-left: 0px;
}
.yellow span.number-med.last {
margin-left: -25px;
}
a.find {
margin-bottom: 50px;
background-color: #012169;
text-transform: uppercase;
.body {
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
}
@media (max-width: 800px) {
.body {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
}
@media (max-width: 600px) {
.body {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.numbers-block {
padding: 40px 50px 30px 50px;
}
.numbers-block h3, .numbers-block .h3 {
margin-top: 50px;
margin-bottom: -60px;
color: #fff;
font-family: "Calibri-bold";
font-size: 1.25rem;
line-height: 1.25rem;
padding: 2px 50px 17px 60px;
border-radius: 0px;
width: 368px;
font-size: 2.5rem;
text-transform: uppercase;
line-height: 9.375rem;
margin-left: -10px;
}
.numbers-block h3 span, .numbers-block .h3 span {
display: inline-block;
width: 67%;
line-height: 2.5rem;
}
.numbers-block h3 span.number, .numbers-block .h3 span.number {
font-size: 12.8125rem;
width: 33%;
}
.numbers-block p {
color: #fff;
margin-right: 0px !important;
font-family: "Calibri";
font-size: 1.125rem;
line-height: 1.3125rem;
}
a.find:focus,
a.find:hover {
background-color: #005eb8;
.find-text {
color: #5B6770;
font-family: "Calibri";
font-size: 2.5rem;
line-height: 2.75rem;
width: 72%;
display: inline-block;
}
a.find:before {
content: "";
width: 22px;
height: 31px;
.find-number {
color: #012169;
font-family: "Calibri";
font-size: 12.8125rem;
line-height: 9.375rem;
width: 25%;
display: inline-block;
top: 10px;
position: relative;
margin-left: -25px;
margin-right: 10px;
background-repeat: no-repeat;
background-position: center;
background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
padding-right: 30px;
text-align: right;
}
.wp-block-group.gray {
background-color: #f2f2f2;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
}
@media (max-width: 800px) {
.wp-block-group.gray {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
}
@media (max-width: 600px) {
.wp-block-group.gray {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.wp-block-group.gray figcaption {
background-color: #333f48;
color: #fff;
padding: 20px;
margin-top: -5px;
}
#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($) {
return Math.random() * (max - min) + min;
}
var maxHeight = 0;
$(".numbers-block").each(function(){
if ($(this).height() > maxHeight) { maxHeight = $(this).height(); }
});
$(".numbers-block").height(maxHeight);
});
\ No newline at end of file
......
@media (min-width: 1400px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl,
.container-xxl {
max-width: 1366px;
}
}
@media (min-width: 1200px) {
.container,
.container-lg,
.container-md,
.container-sm,
.container-xl {
max-width: 1234px;
}
}
a.find {
margin-bottom: 50px;
background-color: #012169;
text-transform: uppercase;
font-family: "Calibri-bold";
font-size: 20px;
line-height: 20px;
padding: 2px 50px 17px 60px;
border-radius: 0px;
width: 368px;
color: #fff;
margin-right: 0px !important;
}
a.find:focus,
a.find:hover{
background-color: #005eb8;
}
a.find:before {
content: "";
width: 22px;
height: 31px;
display: inline-block;
top: 10px;
position: relative;
margin-left: -25px;
margin-right: 10px;
background-repeat: no-repeat;
background-position: center;
background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
}
ss3-force-full-width{
top: 0px !important;
position: relative !important;
@media (max-width: 768px) {
top: 66px !important;
position: relative !important;
}
}
.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{
z-index: 9999999 !important;
}
.n2-ss-widget.n2-style-f17ddbf2d8ed14421f9093b94b93b8a9-heading.nextend-autoplay.n2-ow-all.nextend-autoplay-image{
z-index: 9999999 !important;
position: absolute !important;
......@@ -44,47 +117,187 @@ div#n2-ss-2item3{
}
ss3-force-full-width{
top: 0px !important;
position: relative !important;
@media (max-width: 768px) {
top: 66px !important;
position: relative !important;
.intro{
background-color: #333F48;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
p{
font-family: "Calibri";
font-size: 30px;
color: #fff;
line-height: 34px;
}
@media (max-width: 800px) {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
@media (max-width: 600px) {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.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{
z-index: 9999999 !important;
.yellow{
background-color: #FFB81C;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
p{
font-family: "Calibri";
font-size: 18px;
color: #333F48;
line-height: 21px;
}
@media (max-width: 800px) {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
@media (max-width: 600px) {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
.number-text {
display: inline-block;
margin-left: 10px;
//width: 49%;
}
span.number-big{
font-family: "Calibri-bold";
font-size: 205px;
color: #012169;
}
a.find {
margin-bottom: 50px;
background-color: #012169;
text-transform: uppercase;
span.number-med{
font-family: "Calibri-bold";
font-size: 20px;
line-height: 20px;
padding: 2px 50px 17px 60px;
border-radius: 0px;
width: 368px;
color: #fff;
margin-right: 0px !important;
font-size: 39px;
color: #333F48;
margin-left: 0px;
}
a.find:focus,
a.find:hover{
background-color: #005eb8;
}
a.find:before {
content: "";
width: 22px;
height: 31px;
display: inline-block;
top: 10px;
position: relative;
span.number-med.last{
margin-left: -25px;
margin-right: 10px;
background-repeat: no-repeat;
background-position: center;
background-image: url("/wp-content/themes/understrap-child/src/images/pin.svg");
}
\ No newline at end of file
}
}
.body{
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
@media (max-width: 800px) {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
@media (max-width: 600px) {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
}
.numbers-block{
padding:40px 50px 30px 50px;
h3{
margin-top: 50px;
margin-bottom: -60px;
color: #fff;
font-family: "Calibri-bold";
font-size: 40px;
text-transform: uppercase;
line-height: 150px;
margin-left: -10px;
span{
display: inline-block;
width: 67%;
line-height: 40px;
}
span.number{
font-size: 205px;
width: 33%;
}
}
p{
color: #fff;
font-family: "Calibri";
font-size: 18px;
line-height: 21px;
}
}
.find-text{
color: #5B6770;
font-family: "Calibri";
font-size: 40px;
line-height: 44px;
width: 72%;
display: inline-block;
}
.find-number{
color: #012169;
font-family: "Calibri";
font-size: 205px;
line-height: 150px;
width: 25%;
display: inline-block;
padding-right: 30px;
text-align: right;
}
.wp-block-group.gray {
background-color: #f2f2f2;
margin-left: -100%;
padding-left: 95%;
margin-right: -50%;
padding-right: 45%;
padding-top: 50px;
padding-bottom: 50px;
@media (max-width: 800px) {
margin-left: -30%;
padding-left: 35%;
margin-right: -30%;
padding-right: 35%;
}
@media (max-width: 600px) {
min-height: 750px;
margin-left: -10%;
padding-left: 15%;
margin-right: -10%;
padding-right: 15%;
}
figcaption{
background-color: #333f48;
color: #fff;
padding: 20px;
margin-top: -5px
}
}
......