first commit

This commit is contained in:
2018-04-11 17:57:57 +02:00
commit fdfcfbd2a6
4073 changed files with 501866 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
assets/logo.svg
*.xcf
.idea
+50
View File
@@ -0,0 +1,50 @@
# v1.3.0
## 02/05/2017
1. [](#new)
* Added support image or link for not embed media (Thanks to [@magikcypres](https://github.com/magikcypress))
* Added support for **Twitter.com** (Thanks to [@magikcypres](https://github.com/magikcypress))
2. [](#bugfix)
* Fixed support for **Github** (Thanks to [@magikcypres](https://github.com/magikcypress))
* Fixed partial bug [#9](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/9) (Mediaembed converts images into images)
* Fixed [#15](https://github.com/Sommerregen/grav-plugin-mediaembed/pull/15) (Fixed `media.responsive`)
* Fixed [#25](https://github.com/Sommerregen/grav-plugin-mediaembed/pull/25) (Fix Admin Panel issues [#13](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/13), [#17](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/17), and [#24](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/24))
# v1.2.0
## 08/08/2015
1. [](#new)
* Added admin configurations **(requires Grav 0.9.34+)**
2. [](#improved)
* Updated `README.md`
# v1.1.0
## 05/17/2015
1. [](#new)
* Added support for **Slides.com SlideDesk** as requested in issue [#4](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/4)
2. [](#improved)
* Assets checks (in rare cases it was possible that MediaEmbed throws an error)
# v1.0.2
## 05/10/2015
1. [](#new)
* Added plugin roadmap
* Added support for modular pages
2. [](#improved)
* Prevent potential division by zero error [#2](https://github.com/Sommerregen/grav-plugin-mediaembed/pull/2)
3. [](#bugfix)
* Fixed link generation in case no MediaEmbed service is found
# v1.0.1
## 04/28/2015
3. [](#bugfix)
* Fixed issue [#1](https://github.com/Sommerregen/grav-plugin-mediaembed/issues/1) with broken MediaEmbed functionality (i.e. removed test code)
# v1.0.0
## 04/26/2015
1. [](#new)
* ChangeLog started...
+727
View File
@@ -0,0 +1,727 @@
Grav MediaEmbed Plugin License
==================================
Grav MediaEmbed Plugin is meant to be a free and open source for all now
and in future.
For ease of distribution, Grav MediaEmbed Plugin is licensed under a dual
license and is based on the principle of Give-and-Take.
Unless otherwise noted Grav MediaEmbed Plugin is licensed under the terms
of GPLv3 <http://opensource.org/licenses/GPL-3.0> (or see license text below).
However it is licensed under the terms of MIT
<http://opensource.org/licenses/MIT>
(or see license text below) when the following conditions are met:
- Grav MediaEmbed Plugin is used, copied, merged, published for or
distributed with Grav (http://getgrav.org)
and can be understood as a Free and Open Source Software ("FOSS") License
Exception especially for all plugins, themes, extensions available for Grav
using Grav MediaEmbed Plugin in any way.
MIT LICENSE
-----------
Copyright (c) 2017 Benjamin Regler, https://github.com/sommerregen/grav-plugin-mediaembed
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
GPL VERSION 3
-------------
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>.
+144
View File
@@ -0,0 +1,144 @@
# [![Grav MediaEmbed Plugin](assets/logo.png)][project]
[![Release](https://img.shields.io/github/release/sommerregen/grav-plugin-mediaembed.svg)][project] [![Issues](https://img.shields.io/github/issues/sommerregen/grav-plugin-mediaembed.svg)][issues] [![Dual license](https://img.shields.io/badge/dual%20license-MIT%2FGPL-blue.svg)](LICENSE "License") <span style="float:right;">[![Flattr](https://api.flattr.com/button/flattr-badge-large.png)][flattr] [![PayPal](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)][paypal]</span>
> This plugin embeds several media sites (e.g. YouTube, Vimeo, Soundcloud) by only providing the URL to the medium.
##### Table of Contents:
* [About](#about)
* [Installation and Updates](#installation-and-updates)
* [Usage](#usage)
* [Contributing](#contributing)
* [Licencse](#license)
## About
Grav MediaEmbed plugin is the official successor of [Grav VideoEmbed plugin](https://github.com/sommerregen/grav-plugin-videoembed/), allows to embed several media sites by only providing the URL to the medium and supports lazy loading techniques for videos and other media. Currently it supports
- YouTube
- Vimeo
- DailyMotion
- SoundCloud
- Spotify
- Flickr
- Imgur
- Instagram
- GitHub
- Twitter
but more services are coming soon! In principle it supports any service, which provides the [oEmbed format](http://www.oembed.com/). Media are embedded using the Markdown syntax for images (`![Alt](URL "Title")`), e.g. the below screenshot was created with the following code:
```
![](https://www.flickr.com/photos/chris_gin/6585842063)
```
![Screenshot MediaEmbed Plugin](assets/screenshot.png "MediaEmbed Preview")
### The Roadmap
Grav MediaEmbed plugin is neat and a powerful tool to embed other media types. However at the moment only a few services are supported. Further the configuration for the services may differ under certain circumstances; some may be more configurable than others. In future releases I intend to implement:
- [ ] a unified interface for accessing [OEmbed](http://oembed.com "An overview about OEmbed services") services
- [ ] a cache mechanism for requests, data and images
- [ ] support more than 200+ OEmbed services
- [ ] and a more powerful API
If you have any ideas to extend the list, then please don't hesitate to open an issue and spread your idea with the community!
So far I'm happy that Grav MediaEmbed plugin becomes one of the most popular a d used plugins in a few days. Thank you! I try to give my best to provide you a fast, easy to use and extensible plugin. I have already spent a lot of time and will spend a lot of time even more. The plan is to extend Grav MediaEmbed plugin even more to embed arbitrary websites and embed media as cards (e.g. Wordpress, Twitter, Facebook, GitHub). This will however mean a many hour work and thus I plan to support such features in a paid version of Grav MediaEmbed plugin called "MediaEmbed Pro", which will coming soon. From then on, Grav MediaEmbed plugin will come in two flavors: a free version supporting all OEmbed services and a paid version with better embedding support of media and enabling the use of embed media cards.
## Installation and Updates
Installing or updating the `MediaEmbed` plugin can be done in one of two ways. Using the GPM (Grav Package Manager) installation update method (i.e. `bin/gpm install mediaembed`) or manual install by downloading [this plugin](https://github.com/sommerregen/grav-plugin-mediaembed) and extracting all plugin files to
user/plugins/mediaembed
For more informations, please check the [Installation and update guide](docs/INSTALL.md).
## Usage
The `MediaEmbed` plugin comes with some sensible default configuration, that are pretty self explanatory:
### Config Defaults
```yaml
# Global plugin configurations
enabled: true # Set to false to disable this plugin completely
built_in_css: true # Use built-in CSS of the plugin
built_in_js: true # Use built-in JS of the plugin
# Default options for MediaEmbed configuration.
# -- Media --
media:
width: 640 # Default media width
height: 390 # Default media height including controls
adjust: true # Adjust media or keep default dimensions?
preview: true # Show or hide media preview
responsive: false # Allow media to be responsive
protocol: "http://" # Default protocol for remote media resources
services:
<ServiceName>:
enabled: true # Set to false to disable this service completely
type: <Type> # Type of the media service
# URL of media service used for embedding
url: "www.domain.com/embed/{:id}"
# Canonical URL of media service (used in endpoint calls)
canonical: "http://www.domain.com/{:id}"
# Endpoint to grab media informations
endpoint: "http://www.domain.com/oembed?url={:canonical}&format=json"
# Regex filters ("~REGEX~i") to grab media id
schemes:
- "domain.com/*"
# Custom service-related media option overrides
params:
<Param>: <Value>
```
If you need to change any value, then the best process is to copy the [mediaembed.yaml](mediaembed.yaml) file into your `users/config/plugins/` folder (create it if it doesn't exist), and then modify there. This will override the default settings.
## Contributing
You can contribute at any time! Before opening any issue, please search for existing issues and review the [guidelines for contributing](docs/CONTRIBUTING.md).
After that please note:
* If you find a bug, would like to make a feature request or suggest an improvement, [please open a new issue][issues]. If you have any interesting ideas for additions to the syntax please do suggest them as well!
* Feature requests are more likely to get attention if you include a clearly described use case.
* If you wish to submit a pull request, please make again sure that your request match the [guidelines for contributing](docs/CONTRIBUTING.md) and that you keep track of adding unit tests for any new or changed functionality.
### Support and donations
If you like my project, feel free to support me via [![Flattr](https://api.flattr.com/button/flattr-badge-large.png)][flattr] or by sending me some bitcoins to [**1HQdy5aBzNKNvqspiLvcmzigCq7doGfLM4**][bitcoin].
Thanks!
## License
Copyright (c) 2017 [Benjamin Regler][github]. See also the list of [contributors] who participated in this project.
[Dual-licensed](LICENSE) for use under the terms of the [MIT][mit-license] or [GPLv3][gpl-license] licenses.
![GNU license - Some rights reserved][gnu]
[github]: https://github.com/sommerregen/ "GitHub account from Benjamin Regler"
[gpl-license]: http://opensource.org/licenses/GPL-3.0 "GPLv3 license"
[mit-license]: http://www.opensource.org/licenses/mit-license.php "MIT license"
[flattr]: https://flattr.com/submit/auto?user_id=Sommerregen&url=https://github.com/sommerregen/grav-plugin-mediaembed "Flatter my GitHub project"
[paypal]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SYFNP82USG3RN "Donate for my GitHub project using PayPal"
[bitcoin]: bitcoin:1HQdy5aBzNKNvqspiLvcmzigCq7doGfLM4?label=GitHub%20project "Donate for my GitHub project using BitCoin"
[gnu]: https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/License_icon-gpl-88x31.svg/88px-License_icon-gpl-88x31.svg.png "GNU license - Some rights reserved"
[project]: https://github.com/sommerregen/grav-plugin-mediaembed
[issues]: https://github.com/sommerregen/grav-plugin-mediaembed/issues "GitHub Issues for Grav MediaEmbed Plugin"
[contributors]: https://github.com/sommerregen/grav-plugin-mediaembed/graphs/contributors "List of contributors of the project"
@@ -0,0 +1,330 @@
/* MediaEmbed */
.mediaembed,
.mediaembed-responsive {
margin: 0 auto;
overflow: hidden;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.mediaembed-media .mediaembed-media,
.mediaembed-media embed,
.mediaembed-media iframe,
.mediaembed-media object,
.mediaembed-media video,
.mediaembed-embed img {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
z-index: 1;
}
/* Media screen of death */
.mediaembed-msod {
background-color: #000;
color: white;
display: block;
font-size: 80%;
overflow: hidden;
padding: 1em;
position: relative;
text-align: center;
vertical-align: middle;
z-index: 1;
}
.mediaembed-msod:before {
content: "";
display: block;
position: absolute;
z-index: -1;
/* We make the element extra-large so we can shuffle it around without exposing the edges */
height: 300%;
left: -100%;
top: -100%;
width: 300%;
-webkit-animation: grain 5s steps(10) infinite;
-moz-animation: grain 5s steps(10) infinite;
-ms-animation: grain 5s steps(10) infinite;
animation: grain 5s steps(10) infinite;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAUVBMVEWFhYWDg4N3d3dtbW17e3t1dXWBgYGHh4d5eXlzc3OLi4ubm5uVlZWPj4+NjY19fX2JiYl/f39ra2uRkZGZmZlpaWmXl5dvb29xcXGTk5NnZ2c8TV1mAAAAG3RSTlNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAvEOwtAAAFVklEQVR4XpWWB67c2BUFb3g557T/hRo9/WUMZHlgr4Bg8Z4qQgQJlHI4A8SzFVrapvmTF9O7dmYRFZ60YiBhJRCgh1FYhiLAmdvX0CzTOpNE77ME0Zty/nWWzchDtiqrmQDeuv3powQ5ta2eN0FY0InkqDD73lT9c9lEzwUNqgFHs9VQce3TVClFCQrSTfOiYkVJQBmpbq2L6iZavPnAPcoU0dSw0SUTqz/GtrGuXfbyyBniKykOWQWGqwwMA7QiYAxi+IlPdqo+hYHnUt5ZPfnsHJyNiDtnpJyayNBkF6cWoYGAMY92U2hXHF/C1M8uP/ZtYdiuj26UdAdQQSXQErwSOMzt/XWRWAz5GuSBIkwG1H3FabJ2OsUOUhGC6tK4EMtJO0ttC6IBD3kM0ve0tJwMdSfjZo+EEISaeTr9P3wYrGjXqyC1krcKdhMpxEnt5JetoulscpyzhXN5FRpuPHvbeQaKxFAEB6EN+cYN6xD7RYGpXpNndMmZgM5Dcs3YSNFDHUo2LGfZuukSWyUYirJAdYbF3MfqEKmjM+I2EfhA94iG3L7uKrR+GdWD73ydlIB+6hgref1QTlmgmbM3/LeX5GI1Ux1RWpgxpLuZ2+I+IjzZ8wqE4nilvQdkUdfhzI5QDWy+kw5Wgg2pGpeEVeCCA7b85BO3F9DzxB3cdqvBzWcmzbyMiqhzuYqtHRVG2y4x+KOlnyqla8AoWWpuBoYRxzXrfKuILl6SfiWCbjxoZJUaCBj1CjH7GIaDbc9kqBY3W/Rgjda1iqQcOJu2WW+76pZC9QG7M00dffe9hNnseupFL53r8F7YHSwJWUKP2q+k7RdsxyOB11n0xtOvnW4irMMFNV4H0uqwS5ExsmP9AxbDTc9JwgneAT5vTiUSm1E7BSflSt3bfa1tv8Di3R8n3Af7MNWzs49hmauE2wP+ttrq+AsWpFG2awvsuOqbipWHgtuvuaAE+A1Z/7gC9hesnr+7wqCwG8c5yAg3AL1fm8T9AZtp/bbJGwl1pNrE7RuOX7PeMRUERVaPpEs+yqeoSmuOlokqw49pgomjLeh7icHNlG19yjs6XXOMedYm5xH2YxpV2tc0Ro2jJfxC50ApuxGob7lMsxfTbeUv07TyYxpeLucEH1gNd4IKH2LAg5TdVhlCafZvpskfncCfx8pOhJzd76bJWeYFnFciwcYfubRc12Ip/ppIhA1/mSZ/RxjFDrJC5xifFjJpY2Xl5zXdguFqYyTR1zSp1Y9p+tktDYYSNflcxI0iyO4TPBdlRcpeqjK/piF5bklq77VSEaA+z8qmJTFzIWiitbnzR794USKBUaT0NTEsVjZqLaFVqJoPN9ODG70IPbfBHKK+/q/AWR0tJzYHRULOa4MP+W/HfGadZUbfw177G7j/OGbIs8TahLyynl4X4RinF793Oz+BU0saXtUHrVBFT/DnA3ctNPoGbs4hRIjTok8i+algT1lTHi4SxFvONKNrgQFAq2/gFnWMXgwffgYMJpiKYkmW3tTg3ZQ9Jq+f8XN+A5eeUKHWvJWJ2sgJ1Sop+wwhqFVijqWaJhwtD8MNlSBeWNNWTa5Z5kPZw5+LbVT99wqTdx29lMUH4OIG/D86ruKEauBjvH5xy6um/Sfj7ei6UUVk4AIl3MyD4MSSTOFgSwsH/QJWaQ5as7ZcmgBZkzjjU1UrQ74ci1gWBCSGHtuV1H2mhSnO3Wp/3fEV5a+4wz//6qy8JxjZsmxxy5+4w9CDNJY09T072iKG0EnOS0arEYgXqYnXcYHwjTtUNAcMelOd4xpkoqiTYICWFq0JSiPfPDQdnt+4/wuqcXY47QILbgAAAABJRU5ErkJggg==);
}
.mediaembed-msod .mediaembed-icon {
font-size: 7em;
line-height: 1em;
margin: 0.25em auto 0;
}
.mediaembed-msod p b {
display: block;
font-weight: bold;
text-align: center;
}
.mediaembed-msod .mediaembed-error-message {
margin: 0 3em 1em;
text-align: justify;
}
.mediaembed-msod .mediaembed-error-message b {
color: #f00;
text-transform: uppercase;
}
/* MediaEmbed media */
.mediaembed-container {
position: relative;
}
/* MediaEmbed Play button */
.mediaembed-play {
margin: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 10px;
width: 1em;
height: 1em;
color: #fff;
display: block;
font-size: 6em;
line-height: 1;
text-align: center;
text-shadow: 0 0 2px #ccc;
visibility: visible;
opacity: 1;
z-index: 2;
}
.mediaembed-play:before {
box-shadow: 0 0 2px 0 #ccc inset, 0 0 2px 0 #ccc;
border: medium solid;
border-radius: 100%;
content: "";
display: block;
height: 120%;
left: calc(-10% - 10px);
position: absolute;
top: calc(-5% - 5px);
width: 120%;
}
.mediaembed-play::after {
bottom: -1000%;
content: "";
display: block;
left: -1000%;
position: absolute;
right: -1000%;
top: -1000%;
}
.mediaembed-play:hover {
color: #ccc;
}
.mediaembed-media iframe ~ .mediaembed-play,
.mediaembed-media input:checked ~ .mediaembed-play {
visibility: hidden;
opacity: 0;
z-index: 1;
-webkit-transition: opacity 1.5s ease-in-out 0s, visibility 1.5s linear 0s, z-index 1.5s linear 0s;
-moz-transition: opacity 1.5s ease-in-out 0s, visibility 1.5s linear 0s, z-index 1.5s linear 0s;
-ms-transition: opacity 1.5s ease-in-out 0s, visibility 1.5s linear 0s, z-index 1.5s linear 0s;
-o-transition: opacity 1.5s ease-in-out 0s, visibility 1.5s linear 0s, z-index 1.5s linear 0s;
transition: opacity 1.5s ease-in-out 0s, visibility 1.5s linear 0s, z-index 1.5s linear 0s;
}
.mediaembed-media iframe ~ .mediaembed-play,
.mediaembed-media input:checked ~ .mediaembed-play:before {
-webkit-animation: ring 1.5s ease-in-out 1;
-moz-animation: ring 1.5s ease-in-out 1;
-ms-animation: ring 1.5s ease-in-out 1;
-o-animation: ring 1.5s ease-in-out 1;
animation: ring 1.5s ease-in-out 1;
}
.mediaembed-media input:checked ~ noscript iframe {
display: block !important;
}
.mediaembed-media .mediaembed-input {
display: none;
}
.mediaembed-thumbnail {
margin: auto;
position: absolute;
top: -50%;
right: 0;
bottom: -50%;
left: 0;
width: 100%;
z-index: 0;
}
/* GitHub adjustments */
.mediaembed-github {
overflow: auto;
}
.mediaembed-github .gist .line-numbers {
width: 4em;
}
.mediaembed-github .gist-file {
padding: 1em;
}
/*
* Animations
*/
/* -- Grain -- */
@-webkit-keyframes grain {
0%, 100% {
-webkit-transform: translate(0, 0);
}
10% {
-webkit-transform: translate(-5%, -10%);
}
20% {
-webkit-transform: translate(-15%, 5%);
}
30% {
-webkit-transform: translate(7%, -25%);
}
40% {
-webkit-transform: translate(-5%, 25%);
}
50% {
-webkit-transform: translate(-15%, 10%);
}
60% {
-webkit-transform: translate(15%, 0%);
}
70% {
-webkit-transform: translate(0%, 15%);
}
80% {
-webkit-transform: translate(3%, 20%);
}
90% {
-webkit-transform: translate(-10%, 10%);
}
}
@-moz-keyframes grain {
0%, 100% {
-moz-transform: translate(0, 0);
}
10% {
-moz-transform: translate(-5%, -10%);
}
20% {
-moz-transform: translate(-15%, 5%);
}
30% {
-moz-transform: translate(7%, -25%);
}
40% {
-moz-transform: translate(-5%, 25%);
}
50% {
-moz-transform: translate(-15%, 10%);
}
60% {
-moz-transform: translate(15%, 0%);
}
70% {
-moz-transform: translate(0%, 15%);
}
80% {
-moz-transform: translate(3%, 20%);
}
90% {
-moz-transform: translate(-10%, 10%);
}
}
@-ms-keyframes grain {
0%, 100% {
-ms-transform: translate(0, 0);
}
10% {
-ms-transform: translate(-5%, -10%);
}
20% {
-ms-transform: translate(-15%, 5%);
}
30% {
-ms-transform: translate(7%, -25%);
}
40% {
-ms-transform: translate(-5%, 25%);
}
50% {
-ms-transform: translate(-15%, 10%);
}
60% {
-ms-transform: translate(15%, 0%);
}
70% {
-ms-transform: translate(0%, 15%);
}
80% {
-ms-transform: translate(3%, 20%);
}
90% {
-ms-transform: translate(-10%, 10%);
}
}
@keyframes grain {
0%, 100% {
transform: translate(0, 0);
}
10% {
transform: translate(-5%, -10%);
}
20% {
transform: translate(-15%, 5%);
}
30% {
transform: translate(7%, -25%);
}
40% {
transform: translate(-5%, 25%);
}
50% {
transform: translate(-15%, 10%);
}
60% {
transform: translate(15%, 0%);
}
70% {
transform: translate(0%, 15%);
}
80% {
transform: translate(3%, 20%);
}
90% {
transform: translate(-10%, 10%);
}
}
/* -- Ring -- */
@keyframes ring {
0% {
transform: scale(1);
opacity: 1;
border-width: medium;
}
/* hidden */
100% {
transform: scale(3);
opacity: 0;
border-width: thin;
}
}
@@ -0,0 +1,18 @@
function lazyload(anchor) {
setTimeout(function () {
// Strip comment tags around innerHTML
anchor.innerHTML = anchor.innerHTML.replace('<!--', '').replace('-->', '');
}, 1000);
// Remove <noscript> tag in element DOM
var elem = anchor.getElementsByTagName('noscript');
if ( elem.length > 0 ) {
elem[0].remove();
}
// Suppress further onClick events
anchor.removeAttribute('href');
anchor.onclick = null;
return false;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

+242
View File
@@ -0,0 +1,242 @@
name: MediaEmbed
version: 1.3.0
description: "This plugin embeds several media sites (e.g. YouTube, Vimeo, Soundcloud) by only providing the URL to the medium."
icon: spinner
author:
name: Sommerregen
email: sommerregen@benjamin-regler.de
homepage: https://github.com/sommerregen/grav-plugin-mediaembed
keywords: [video, embed, oembed, media, youtube, vimeo, plugin]
docs: https://github.com/sommerregen/grav-plugin-mediaembed/blob/master/README.md
bugs: https://github.com/sommerregen/grav-plugin-mediaembed/issues
license: MIT/GPL
form:
validation: strict
fields:
global:
type: section
title: "Global plugin configurations"
underline: 1
fields:
enabled:
type: toggle
label: "Plugin Status"
highlight: 1
default: 0
options:
1: "Enabled"
0: "Disabled"
validate:
type: bool
built_in_css:
type: toggle
label: "Use built in CSS"
highlight: 1
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
built_in_js:
type: toggle
label: "Use built in JS"
highlight: 1
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
default:
type: section
title: "Default values for MediaEmbed configuration"
underline: 1
fields:
media:
type: section
title: "Media"
fields:
media.width:
type: text
size: x-small
label: "Default media width"
default: 640
placeholder: 640
validate:
type: int
min: 0
media.height:
type: text
size: x-small
label: "Default media height including controls"
default: 390
placeholder: 390
validate:
type: int
min: 0
media.adjust:
type: select
size: medium
label: "Adjust media size"
help: Adjust media or keep default dimensions?
default: 1
options:
1: "True - Adjust media size"
0: "False - Keep default dimensions"
validate:
type: bool
media.preview:
type: toggle
label: "Show preview"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
media.responsive:
type: toggle
label: "Responsive media"
help: "Allow media to be responsive"
default: 1
options:
1: "Enabled"
0: "Disabled"
validate:
type: bool
media.protocol:
type: text
size: medium
label: "Protocol"
help: "Default protocol for remote media resources"
default: "http://"
placeholder: "http://"
services:
type: section
title: "Services"
fields:
services.SoundCloud.enabled:
type: toggle
label: "Embed SoundCloud"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Spotify.enabled:
type: toggle
label: "Embed Spotify"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Flickr.enabled:
type: toggle
label: "Embed Flickr"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Imgur.enabled:
type: toggle
label: "Embed Imgur"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Instagram.enabled:
type: toggle
label: "Embed Instagram"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Dailymotion.enabled:
type: toggle
label: "Embed Dailymotion"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.YouTube.enabled:
type: toggle
label: "Embed YouTube"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Vimeo.enabled:
type: toggle
label: "Embed Vimeo"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.GitHub.enabled:
type: toggle
label: "Embed GitHub"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Slides.enabled:
type: toggle
label: "Embed Slides"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
services.Twitter.enabled:
type: toggle
label: "Embed Twitter"
default: 1
options:
1: "Yes"
0: "No"
validate:
type: bool
@@ -0,0 +1,103 @@
<?php
/**
* Autoloader
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed;
/**
* Autoloader
*/
class Autoloader
{
protected $routes = [];
public function __construct($routes = [])
{
// Set routes for autoloading
if (!is_array($routes) || count($routes) == 0) {
$routes = [__NAMESPACE__ => __DIR__];
}
$this->route($routes);
}
public function route($var = null, $reset = true)
{
if ($var !== null && is_array($var)) {
if ($reset) {
$this->routes = [];
}
// Setup routes
foreach ($var as $prefix => $path) {
if (false !== strrpos($prefix, '\\')) {
// Prefix is a namespaced path
$prefix = rtrim($prefix, '_\\') . '\\';
} else {
// Prefix contain underscores
$prefix = rtrim($prefix, '_') . '_';
}
$this->routes[$prefix] = rtrim($path, '/\\') . '/';
}
}
return $this->routes;
}
/**
* Autoload classes
*
* @param string $class Class name
*
* @return mixed false FALSE if unable to load $class; Class name if
* $class is successfully loaded
*/
public function autoload($class)
{
foreach ($this->routes as $prefix => $path) {
// Only load classes of MediaEmbed plugin
if (false !== strpos($class, $prefix)) {
// Remove prefix from class
$class = substr($class, strlen($prefix));
// Replace namespace tokens to directory separators
$file = $path . preg_replace('#\\\|_(?!.+\\\)#', '/', $class) . '.php';
// Load class
if (stream_resolve_include_path($file)) {
return include_once($file);
}
return false;
}
}
return false;
}
/**
* Registers this instance as an autoloader
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'autoload'), false, $prepend);
}
/**
* Unregisters this instance as an autoloader
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'autoload'));
}
}
@@ -0,0 +1,497 @@
<?php
/**
* MediaEmbed
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Plugin\MediaEmbed\Service;
use RocketTheme\Toolbox\Event\Event;
/**
* MediaEmbed
*
* Helper class to embed several media sites (e.g. YouTube, Vimeo,
* Soundcloud) by only providing the URL to the medium.
*/
class MediaEmbed
{
/**
* @var MediaEmbed
*/
use GravTrait;
/** ---------------------------
* Private/protected properties
* ----------------------------
*/
/**
* A unique identifier
*
* @var string
*/
protected $id;
/**
* A key-valued array used for hashing math formulas of a page
*
* @var array
*/
protected $hashes;
/**
* @var array
*/
protected $config;
/**
* @var array
*/
protected $assets = [];
/**
* @var Grav\Plugin\MediaEmbed\Service
*/
protected $service;
/** -------------
* Public methods
* --------------
*/
/**
* Constructor
*
* @param [type] $config [description]
*/
public function __construct($config)
{
// Initialize Service class
$this->service = new Service();
$this->config = $config;
$this->hashes = [];
$services = $this->config->get('plugins.mediaembed.services', []);
foreach ($services as $name => $config) {
if (!$config['enabled']) {
continue;
}
// Load providers in directory "services"
$class = __NAMESPACE__ . "\\Services\\$name";
if (!class_exists($class)) {
// Fallback to a more generic one
$type = isset($config['type']) ? $config['type'] : '';
$class = __NAMESPACE__."\\OEmbed\\OEmbed".ucfirst($type);
}
// Populate config
$config['media'] = $this->config->get('plugins.mediaembed.media', []);
$config['name'] = $name;
if (class_exists($class)) {
// Load ServiceProvider
$provider = new $class($config);
// Register ServiceProvider
$this->service->register($provider);
}
}
}
/**
* Gets and sets the identifier for hashing.
*
* @param string $var the identifier
*
* @return string the identifier
*/
public function id($var = null)
{
if ($var !== null) {
$this->id = $var;
}
return $this->id;
}
public function prepare($content, $id = '')
{
// Set unique identifier based on page content
$this->id(md5(time() . $id . md5($content)));
// Reset class hashes before processing
$this->reset();
$regex = "~
( # wrap whole match in $1
!\\[
(?P<alt>.*?) # alt text = $2
\\]
\\( # literal paren
[ \\t]*
<?(?P<src>\S+?)>? # src url = $3
[ \\t]*
( # $4
(['\"]) # quote char = $5
(?<title>.*?) # title = $6
\\5 # matching quote
[ \\t]*
)? # title is optional
\\)
)
~xs";
// Replace all mediaembed links by a (unique) hash
$content = preg_replace_callback($regex, function($matches) {
// Get the url and parse it
$url = parse_url(htmlspecialchars_decode($matches[3]));
// If there is no host set but there is a path, the file is local
if (!isset($url['host']) && isset($url['path'])) {
return $matches[0];
}
if (!isset($matches['title'])) {
$matches['title'] = '';
}
return $this->hash($matches[0], $matches);
}, $content);
return $content;
}
public function process($content, $config = [])
{
/** @var Twig $twig */
$twig = self::getGrav()['twig'];
// Initialize unique per-page counter
$uid = 1;
// '~(<p>)?\s*<a[^>]*href\s*=\s*([\'"])(?P<href>.*?)\2[^>]*>(?P<code>.*?)</a>\s*(?(1)(</p>))~i',
// Get all <a> tags and extract "href" attribute
$content = preg_replace_callback(
'~mediaembed::([0-9a-z]+)::([0-9]+)::M~i',
function($match) use ($twig, &$uid, $config) {
list($embed, $data) = $this->hashes[$match[0]];
// Check if a service for a specific domain is registered
if ($this->service->match($data['src'])) {
$mediaembed = [
'uid' => $uid++,
'service' => null,
'config' => $config,
'raw' => [
'alt' => $data['alt'],
'title' => $data['title'],
'src' => html_entity_decode($data['src']),
],
'success' => true,
'message' => '',
];
// Load and get data of OEmbed Media Service
try {
$provider = $this->service->embed($data['src']);
} catch (\Exception $e) {
$mediaembed['message'] = $e->getMessage();
$mediaembed['success'] = false;
}
// Setup variables for embedding OEmbed Media Service
if ($mediaembed['success']) {
// Get assets/options of current provider
$assets = $provider->onTwigTemplateVariables(
new Event(['service' => $this->service, 'mediaembed' => $this])
);
// Assets are passed by value as an array
if (is_array($assets)) {
$this->addAssets($assets);
}
// Add OEmbed Service to variables
$mediaembed['service'] = $provider;
// TODO: Cache contents from thumbnail and url
}
// Embed OEmbed Media
$vars = ['mediaembed' => $mediaembed];
$template = 'partials/mediaembed' . TEMPLATE_EXT;
$embed = $twig->processTemplate($template, $vars);
} else {
$text = (strlen($data['alt']) > 0) ? $data['alt'] : $data['src'];
// If display link or img
$link = $config->get('link');
if($link == true) {
$attributes = [
'href' => $data['src'],
'title' => $data['title'],
];
$format = '<a%s>%s</a>';
} else {
$attributes = [
'src' => $data['src'],
'title' => $data['title'],
'alt' => $data['alt'],
];
$format = '<img%s>';
}
foreach ($attributes as $key => $value) {
if (strlen($value) == 0) {
unset($attributes[$key]);
} else {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
$attributes[$key] = $key . '="' . $value . '"';
}
}
$attributes = $attributes ? ' ' . implode(' ', $attributes) : '';
// Transform embed media to link or img for compatibility
$embed = sprintf($format, $attributes, $text);
}
return $embed;
}, $content);
$this->reset();
// Write content back to page
return $content;
}
/**
* Fires an event with optional parameters.
*
* @param string $eventName The name of the event.
* @param Event $event Optional parameter to be passed to the
* called methods.
* @return Event
*/
public function fireEvent($eventName, Event $event = null)
{
// Dispatch event; just propagate it to service class
return $this->service->call($eventName, $event);
}
/**
* Get assets of loaded media services.
*
* @param boolean $reset Toggle whether to reset assets after retrieving
* or not.
*/
public function getAssets($reset = true)
{
$assets = $this->assets;
if ($reset) {
$this->assets = [];
}
return $assets;
}
/**
* Add assets to the queue of MediaEmbed plugin
*
* @param array $assets An array of assets to add.
* @param boolean $append Append assets to array or reset assets.
*/
public function addAssets($assets, $append = true)
{
// Append or reset assets
if (!$append) {
$this->assets = [];
}
// Wrap non-array assets in an array
if (!is_array($assets)) {
$assets = array($assets);
}
// Merge assets
$assets = array_merge($this->assets, $assets);
// Remove duplicates
$this->assets = array_keys(array_flip($assets));
}
/**
* Add assets to the queue of MediaEmbed plugin
*
* Alias for `addAssets`
*
* @param array $assets An array of assets to add.
* @param boolean $append Append assets to array or reset assets.
*/
public function add($assets, $append = true)
{
return $this->addAssets($assets, $append);
}
/**
* Add assets to the queue of MediaEmbed plugin
*
* Alias for `addAssets`
*
* @param array $assets An array of assets to add.
* @param boolean $append Append assets to array or reset assets.
*/
public function addCss($assets, $append = true)
{
return $this->addAssets($assets, $append);
}
/**
* Add assets to the queue of MediaEmbed plugin
*
* Alias for `addAssets`
*
* @param array $assets An array of assets to add.
* @param boolean $append Append assets to array or reset assets.
*/
public function addJs($assets, $append = true)
{
return $this->addAssets($assets, $append);
}
/** -------------------------------
* Private/protected helper methods
* --------------------------------
*/
/**
* Get cached media or media with key.
*
* @param string $key The key to load from the cache.
* @return mixed The media content.
*/
protected function getCachedMedia($key)
{
/** @var Cache $cache */
$cache = $grav['cache'];
// Check, if cache should be used or not
if ($this->config->get('cache.enabled')) {
// Get cache id and try to fetch data
$cache_id = md5('mediaembed' . $key . $cache->getKey());
$data = $cache->fetch($cache_id);
if ((false === $data) || (time() > $data['expire'])) {
// Pack and provide data with a time stamp.
$data = array(
'content' => $this->service->embed($key),
'expire' => time() + $this->config->get('cache.lifetime'),
);
$cache->save($cache_id, $data);
}
// Return data contents
$content = $data['content'];
} else {
// Just call callback and return result
$content = $this->service->embed($key);
}
return $content;
}
protected function parseUrl($url)
{
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return [];
}
// Parse URL
$url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$parts = parse_url($url);
$parts['url'] = $url;
// Get top-level domain from URL
$parts['domain'] = isset($parts['host']) ? $parts['host'] : '';
if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {
$parts['domain'] = $match['domain'];
}
if (isset($parts['query'])) {
parse_str(urldecode($parts['query']), $parts['query']);
}
$parts['query'] = [];
return $parts;
}
/**
* Reset MathJax class
*/
protected function reset()
{
$this->hashes = [];
}
/**
* Hash a given text.
*
* Called whenever a tag must be hashed when a function insert an
* atomic element in the text stream. Passing $text to through this
* function gives a unique text-token which will be reverted back when
* calling unhash.
*
* @param string $text The text to be hashed
* @param string $type The type (category) the text should be saved
*
* @return string Return a unique text-token which will be
* reverted back when calling unhash.
*/
protected function hash($text, $data = [])
{
static $counter = 0;
// Swap back any tag hash found in $text so we do not have to `unhash`
// multiple times at the end.
$text = $this->unhash($text);
// Then hash the block
$key = implode('::', array('mediaembed', $this->id, ++$counter, 'M'));
$this->hashes[$key] = [$text, $data];
// String that will replace the tag
return $key;
}
/**
* Swap back in all the tags hashed by hash.
*
* @param string $text The text to be un-hashed
*
* @return string A text containing no hash inside
*/
protected function unhash($text)
{
$text = preg_replace_callback(
'~mediaembed::([0-9a-z]+)::([0-9]+)::M~i', function($atches) {
return $this->hashes[$matches[0]][0];
}, $text);
return $text;
}
}
@@ -0,0 +1,494 @@
<?php
/**
* OEmbed
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
use Grav\Common\GravTrait;
use Grav\Common\Data\Data;
use RocketTheme\Toolbox\Event\Event;
/**
* OEmbed
*/
class OEmbed implements OEmbedInterface
{
use GravTrait;
/**
* @var \Grav\Common\Data\Data
*/
protected $base_config;
/**
* @var \Grav\Common\Data\Data
*/
protected $config;
/**
* @var string
*/
protected $embedCode = '';
/**
* @var array
*/
protected $attributes;
/**
* @var array
*/
protected $params;
/**
* @var array
*/
protected $oembed;
protected $protocol;
/** -------------
* Public methods
* --------------
*/
/**
* Constructor.
*/
public function __construct(array $config = [])
{
$this->base_config = $this->config = new Data($config);
$schemes = $this->base_config->get('schemes', []);
if (!is_array($schemes)) {
$schemes = [$schemes];
}
foreach ($schemes as $index => $scheme) {
$scheme = preg_quote($scheme);
$schemes[$index] = preg_replace_callback('~((?:\\\\\*){1,2})(.[^\\\\]?|$)~',
function($match) {
// Remove control characters
$separator = preg_replace('~[^\p{L}]~i', '', $match[2]);
$star = strlen(str_replace('\\', '', $match[1]));
if (ctype_alnum($separator)) {
$replace = '.*?';
} else {
$separator = (strlen($separator) == 0) ? substr($match[2], -1) : $separator;
$replace = (strlen($match[2]) > 0) ? "[^$separator ]+" : '[^\"\&\?\. ]+';
}
// Wrap one star result in parenthesis
$replace = ($star > 1) ? $replace : "($replace)";
return $replace . $match[2];
}, $scheme);
}
$this->base_config->set('schemes', $schemes);
}
public function init($embedCode, $config = [])
{
$this->reset();
// Normalize URL to embed
$url = $this->parseUrl($embedCode);
$this->embedCode = $this->canonicalize($embedCode);
$this->oembed = new Data((array) $this->getOEmbed());
// Get media attributes and object parameters
$attributes = [
'width'=> $this->oembed->get('width', 0),
'height' => $this->oembed->get('height', 0),
'protocol' => ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://",
];
// $this->config->merge($config);
// $attributes = $this->config->get('media', []);
$params = array_replace_recursive($this->config->get('params', []), $url['query']);
// Copy media attributes from object parameters
$attr_keys = ['width', 'height', 'adjust', 'preview', 'responsive'];
foreach ($attr_keys as $key) {
if (isset($params[$key])) {
$attributes[$key] = $params[$key];
unset($params[$key]);
}
}
// Set media attributes and object parameters
$this->attributes($attributes);
$this->params($params);
}
public function canonicalize($embedCode)
{
$schemes = $this->config->get('schemes', []);
foreach ($schemes as $scheme) {
preg_match("~$scheme~i", $embedCode, $matches);
if ($matches && $this->validId(end($matches))) {
return end($matches);
}
}
}
/**
* Check if a media id is valid.
*
* @param string $id Id to check against the oembed stream.
*
* @return boolean TRUE if id is valid, FALSE otherwise. Throws errors
* on invalid ids.
*/
protected function validId($id)
{
$endpoint = $this->config->get('endpoint', '');
$endpoint = $this->format($endpoint, ['{:id}' => $id]);
if (!$id || !$endpoint) {
return false;
}
$response = \Requests::head($endpoint);
// If a head request fails, try to send a get request
if ($response->status_code != 200) {
$response = \Requests::get($endpoint);
}
if ( $response->status_code == 401 ) {
throw new \Exception('Embedding has been disabled for this media.');
} elseif ( $response->status_code == 404 ) {
throw new \Exception('The media ID was not found.');
} elseif ( $response->status_code == 501 ) {
throw new \Exception('Media informations can not be retrieved.');
} elseif ( $response->status_code != 200 ) {
throw new \Exception('The media ID is invalid or the media was deleted.');
} elseif (!$response->success) {
$response->throw_for_status();
}
return true;
}
public function reset()
{
// Reset values
$this->embedCode = '';
$this->oembed = null;
$this->attributes([], true);
$this->params([], true);
$this->config = new Data($this->base_config->toArray());
}
public function id()
{
return $this->embedCode;
}
public function slug()
{
$slug = strtolower($this->name()) . '://' . $this->id();
return $slug;
}
public function name()
{
$name = get_class($this);
$name = substr($name, strrpos($name, '\\') + 1);
if ($this->embedCode) {
$name = $this->config->get('name', $name);
if (mb_strlen($name) == 0 && $this->oembed) {
$this->oembed->get('provider_name', '');
}
}
return $name;
}
public function title()
{
$title = '';
if ($this->oembed) {
$title = $this->oembed->get('title', '');
}
return $title;
}
public function description()
{
$description = '';
if ($this->oembed) {
$description = $this->oembed->get('description', '');
}
return $description;
}
public function url()
{
$url = '';
if ($this->embedCode && $this->oembed) {
$url = $this->format($this->config->get('url', ''), ['{:url}' => '']);
if (strlen($url) == 0) {
$url = $this->oembed->get('url', $url);
} else {
$protocol = isset($this->attributes['protocol']) ? $this->attributes['protocol'] : '//';
$url = $protocol . $url;
}
}
return $url;
}
public function website()
{
$website = '';
if ($this->oembed) {
$website = $this->oembed->get('provider_url', '');
}
return $website;
}
/**
* Returns a png img
*
* @param array $stub or string $alias
* @return Resource or null if not available
*/
public function icon() {
$icon = '';
$endpoint = '';
if ($this->oembed) {
$endpoint = $this->format($this->config->get('endpoint', ''));
}
if (!$endpoint) {
return $icon;
}
$pieces = parse_url($endpoint);
$url = $pieces['host'];
// Grab favicon from Google cache
$icon = 'http://www.google.com/s2/favicons?domain=';
$icon .= urlencode($url);
return $icon;
}
public function thumbnail()
{
$thumbnail = '';
if ($this->oembed) {
$thumbnail = $this->oembed->get('thumbnail_url', '');
}
return $thumbnail;
}
public function type()
{
$type = $this->config->get('type', 'generic');
if ($type === 'generic' && $this->embedCode && $this->oembed) {
$type = $this->oembed->get('type', $type);
}
return $type;
}
public function author($key = 'name')
{
$author = '';
if ($this->embedCode && $this->oembed) {
$author = $this->oembed->get('author_' . strtolower($key), '');
}
return $author;
}
public function attributes($var = null, $reset = false)
{
if ($var !== null) {
if ($reset) {
$this->attributes = $var;
} else {
$this->attributes = array_replace_recursive($this->attributes, $var);
}
}
if (!is_array($this->attributes)) {
$this->attributes = [];
}
return $this->attributes;
}
public function params($var = null, $reset = false)
{
if ($var !== null) {
if ($reset) {
$this->params = $var;
} else {
$this->params = array_replace_recursive($this->params, $var);
}
}
if (!is_array($this->params)) {
$this->params = [];
}
return $this->params;
}
public function getEmbedCode($params = [])
{
$params = array_replace_recursive($this->params(), $params);
$url = $this->url();
$query = http_build_query($params);
if (mb_strlen($query) > 0) {
$query = (false === strpos($url, '?') ? '?' : '&') . $query;
}
return $url . $query;
}
/**
* Returns information about the media. See http://www.oembed.com/.
*
* @return
* If oEmbed information is available, an array containing 'title', 'type',
* 'url', and other information as specified by the oEmbed standard.
* Otherwise, NULL.
*/
public function getOEmbed()
{
if ($this->oembed) {
return $this->oembed;
}
$endpoint = $this->format($this->config->get('endpoint', ''));
if (!$endpoint) {
return [];
}
$response = \Requests::get($endpoint);
if (!$response->success) {
$response->throw_for_status();
}
return json_decode($response->body, true);
}
/**
* Return the domain(s) of this media resource
*
* @return string
*/
public function getDomains()
{
// Get domains of media resources
$schemes = $this->base_config->get('schemes', []);
// Ensure domains are of type array
if (!is_array($schemes)) {
$schemes = [$schemes];
}
$domains = [];
foreach ($schemes as $scheme) {
// Trick: extract domains from scheme attributes
$domain = parse_url(str_replace('\.', '.', "http://$scheme"), PHP_URL_HOST);
// Take out the www. in front of domain
$domains[] = preg_replace("/^www\./", '', $domain);
}
// Faster alternative to PHPs array unique function
return array_keys(array_flip($domains));
}
public function onTwigTemplateVariables(Event $event)
{
$mediaembed = $event['mediaembed'];
foreach ($this->config->get('assets', []) as $asset) {
if (is_string($asset) && strlen($asset) > 0) {
$mediaembed->add($asset);
}
}
}
/**
* Convenience wrapper for `echo $ServiceProvider`
*
* @return string
*/
public function __toString()
{
return $this->getEmbedCode();
}
/** -------------------------------
* Private/protected helper methods
* --------------------------------
*/
protected function format($string, $params = [])
{
$keys = ['id', 'name', 'url'];
foreach ($keys as $key) {
if (!isset($params["{:$key}"])) {
$params["{:$key}"] = $this->{$key}();
}
}
$params += [
'{:canonical}' => $this->config->get('canonical', ''),
];
// Format URL placeholder with params
$keys = ['{:url}', '{:canonical}'];
foreach ($keys as $key) {
$params[$key] = urlencode(str_ireplace(
array_keys($params), $params, $params[$key])
);
}
// Replace OEmbed calls with response
$string = preg_replace_callback('~\{\:oembed(?:\.(?=\w))([\.\w_]+)?\}~i',
function($match) {
$ombed = $this->getOEmbed();
return $oembed ? $oembed->get($match[1], '') : $match[0];
}, $string);
return str_ireplace(array_keys($params), $params, $string);
}
protected function parseUrl($url)
{
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return [];
}
// Parse URL
$url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$parts = parse_url($url);
$parts['url'] = $url;
// Get top-level domain from URL
$parts['domain'] = isset($parts['host']) ? $parts['host'] : '';
if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {
$parts['domain'] = $match['domain'];
}
if (isset($parts['query'])) {
parse_str(urldecode($parts['query']), $parts['query']);
} else {
$parts['query'] = [];
}
return $parts;
}
}
@@ -0,0 +1,156 @@
<?php
/**
* OEmbedInterface
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
/**
* OEmbedInterface
*/
interface OEmbedInterface
{
/**
* Initialize service.
*/
public function init($embedCode, $config = []);
/**
* Reset service.
*/
public function reset();
/**
* Extract and normalize id from embed code.
*
* @param string $embedCode The embed code to be canonicalized
* @return string Returns the canonicalized embed code,
* usually an id.
*/
public function canonicalize($embedCode);
/**
* Returns the unique id of a media resource.
*
* @return string
*/
public function id();
/**
* Returns the host as slugged string.
*
* @return string
*/
public function slug();
/**
* Returns the name of this media.
*
* @return string
*/
public function name();
/**
* Returns the title of this media.
*
* @return string
*/
public function title();
/**
* Returns the description of this media.
*
* @return string
*/
public function description();
/**
* The URL of this media
*
* @return string
*/
public function url();
/**
* The website where this media come from.
*
* @return string
*/
public function website();
/**
* Gets the thumbnail of the media and its dimensions.
*
* @return array
*/
public function thumbnail();
/**
* Returns the type of this media.
*
* @return string
*/
public function type();
/**
* Gets the author and informations about him from the media.
*
* @return array
*/
public function author($key = 'name');
/**
* Gets or sets object attributes about the media
*
* @param bool $var Media attributes
*
* @return array Returns object attributes about the media i.e.
* width, height and so on.
*/
public function attributes($var = [], $reset = false);
/**
* Gets or sets object parameter about the media
*
* @param bool $var Media parameter.
*
* @return array Returns the object parameter about the media i.e.
* additional parameter for the request
*/
public function params($var = [], $reset = false);
/**
* Returns the final HTML code for display.
*
* @return string
*/
public function getEmbedCode($params = []);
/**
* Returns information about the media. See http://www.oembed.com/.
*
* @return
* If oEmbed information is available, an array containing 'title', 'type',
* 'url', and other information as specified by the oEmbed standard.
* Otherwise, NULL.
*/
public function getOEmbed();
/**
* Returns the accepted domains of this media resource
*
* @return array
*/
public function getDomains();
/**
* Special Template events fired by Grav\Plugin\MediaEmbed\Service.
*/
// public function onTwigTemplatePaths();
// public function onTwigTemplateVariables(Event $event);
}
@@ -0,0 +1,25 @@
<?php
/**
* OEmbedLink
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbed;
/**
* OEmbedLink
*
* Responses of this type allow a provider to return any generic embed
* data (such as title and author_name), without providing either the
* url or html parameters. The consumer may then link to the resource,
* using the URL specified in the original request.
*/
class OEmbedLink extends OEmbed
{
}
@@ -0,0 +1,48 @@
<?php
/**
* OEmbedPhoto
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbed;
/**
* OEmbedPhoto
*
* This type is used for representing static photos. The following
* parameters are defined:
*
* url (required)
* The source URL of the image. Consumers should be able to insert
* this URL into an <img> element. Only HTTP and HTTPS URLs are valid.
*
* width (required)
* The width in pixels of the image specified in the url parameter.
*
* height (required)
* The height in pixels of the image specified in the url parameter.
*
* Responses of this type must obey the maxwidth and maxheight request
* parameter.
*/
class OEmbedPhoto extends OEmbed
{
public function getOEmbed()
{
$oembed = parent::getOEmbed();
if ($this->embedCode && $this->oembed) {
$width = $this->oembed->get('width');
$height = $this->oembed->get('height');
$this->attributes(['width' => $width, 'height' => $height]);
}
return $oembed;
}
}
@@ -0,0 +1,62 @@
<?php
/**
* OEmbedRich
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbed;
/**
* OEmbedRich
*
* This type is used for rich HTML content that does not fall under one
* of the other categories. The following parameters are defined:
*
* html (required)
* The HTML required to display the resource. The HTML should have no
* padding or margins. Consumers may wish to load the HTML in an
* off-domain iframe to avoid XSS vulnerabilities. The markup should
* be valid XHTML 1.0 Basic.
*
* width (required)
* The width in pixels required to display the HTML.
*
* height (required)
* The height in pixels required to display the HTML.
*
* Responses of this type must obey the maxwidth and maxheight request
* parameters.
*/
class OEmbedRich extends OEmbed
{
public function getOEmbed()
{
$oembed = parent::getOEmbed();
$sizes = ['width', 'height'];
foreach ($sizes as $key) {
$size = isset($oembed[$key]) ? $oembed[$key] : 0;
if (!preg_match('~^\d+$~', $size)) {
$oembed[$key] = 0;
}
}
return $oembed;
}
public function getEmbedCode($params = [])
{
$embed = parent::getEmbedCode($params);
if ($this->embedCode && $this->oembed) {
$embed = $this->oembed->get('html', '');
}
return $embed;
}
}
@@ -0,0 +1,61 @@
<?php
/**
* OEmbedVideo
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\OEmbed;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbed;
/**
* OEmbedVideo
*
* This type is used for representing playable videos. The following
* parameters are defined:
*
* html (required)
* The HTML required to embed a video player. The HTML should have
* no padding or margins. Consumers may wish to load the HTML in an
* off-domain iframe to avoid XSS vulnerabilities.
*
* width (required)
* The width in pixels required to display the HTML.
*
* height (required)
* The height in pixels required to display the HTML.
*
* Responses of this type must obey the maxwidth and maxheight request
* parameters. If a provider wishes the consumer to just provide a
* thumbnail, rather than an embeddable player, they should instead
* return a photo response type.
*/
class OEmbedVideo extends OEmbed
{
public function getOEmbed()
{
$oembed = parent::getOEmbed();
if ($this->embedCode && $this->oembed) {
$width = $this->oembed->get('width');
$height = $this->oembed->get('height');
$this->attributes(['width' => $width, 'height' => $height]);
}
return $oembed;
}
public function getEmbedCode($params = [])
{
$embed = parent::getEmbedCode($params);
if (mb_strlen($embed) == 0 && $this->embedCode && $this->oembed) {
$embed = $this->oembed->get('html', '');
}
return $embed;
}
}
@@ -0,0 +1,115 @@
<?php
/**
* ProviderInterface
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed;
/**
* ProviderInterface
*/
interface ProviderInterface
{
/**
* Initialize service.
*/
public function init($embedCode);
/**
* Reset service.
*/
public function reset();
/**
* Extract and normalize id from embed code.
*
* @param string $embedCode The embed code to be canonicalized
* @return string Returns the canonicalized embed code,
* usually an id.
*/
public function canonicalize($embedCode);
/**
* Returns the unique id of a media resource.
*
* @return string
*/
public function id();
/**
* Returns the host as slugged string.
*
* @return string
*/
public function slug();
/**
* Returns the name of this media.
*
* @return string
*/
public function name();
/**
* Returns the type of this media.
*
* @return string
*/
public function type();
/**
* Gets or sets object attributes about the media
*
* @param bool $var Media attributes
*
* @return array Returns object attributes about the media i.e.
* width, height and so on.
*/
public function attributes($var = []);
/**
* Gets or sets object parameter about the media
*
* @param bool $var Media parameter.
*
* @return array Returns the object parameter about the media i.e.
* additional parameter for the request
*/
public function params($var = []);
/**
* Returns the final HTML code for display.
*
* @return string
*/
public function getEmbedCode();
/**
* Returns information about the media. See http://www.oembed.com/.
*
* @return
* If oEmbed information is available, an array containing 'title', 'type',
* 'url', and other information as specified by the oEmbed standard.
* Otherwise, NULL.
*/
public function getOEmbed();
/**
* Returns the accepted domains of this media resource
*
* @return array
*/
public function getDomains();
/**
* Special Template events fired by Grav\Plugin\MediaEmbed\Service.
*/
// public function onTwigTemplatePaths();
// public function onTwigTemplateVariables(Event $event);
}
+318
View File
@@ -0,0 +1,318 @@
<?php
/**
* Service
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\Event\Event;
/**
* Service
*/
class Service
{
/**
* @var Service
*/
use GravTrait;
/** ---------------------------
* Private/protected properties
* ----------------------------
*/
/**
* @var Grav\Plugin\MediaEmbed\ServiceProvider
*/
protected $services = [];
/**
* @var array
*/
protected $domains;
/** -------------
* Public methods
* --------------
*/
public function __construct()
{
// Fire event
self::getGrav()->fireEvent('onMediaEmbed', new Event(['service' => $this]));
}
public function call($method, $params = [])
{
$result = [];
foreach ($this->services as $key => $service) {
if (method_exists($service['provider'], $method)) {
$data = call_user_method_array([$service['provider'], $method], $params);
if ($data) {
$result[] = $data;
}
}
}
return $result;
}
public function match($url, $embedCode = null)
{
$embed_key = 'embed';
if ($embedCode && (strtolower($embedCode) !== $embed_key)) {
return false;
}
$parts = $this->parseUrl($url);
if ($parts['host'] == $embed_key) {
return false;
}
return isset($this->domains[$parts['host']]);
}
public function embed($embedCode, $options = [])
{
if (!$this->match($embedCode)) {
throw new \Exception('Unknown embed code "' . htmlspecialchars($embedCode) . '".');
}
// Extract domain from embed code
$domain = strtolower($this->parseUrl($embedCode)['host']);
$key = $this->domains[$domain];
// Call OEmbed service provider
$provider = $this->services[$key]['provider'];
$provider->init($embedCode, $options);
// Return initialized provider
return $provider;
// // Get type of media
// $type = ucfirst($provider->type());
// // Get the default properties of the response class
// $class = __NAMESPACE__ . "\\Response\\{$type}Response";
// // Repopulate properties
// $vars = [
// 'raw' => $embedCode,
// 'options' => $provider->attributes(),
// 'url' => $provider->getEmbedCode(,
// ] + get_class_vars($class);
// // Enrich properties with media resource informations
// foreach ($vars as $key => $value) {
// if (!$value) {
// $vars[$key] = $provider->{$key}();
// }
// }
// // Create response
// $response = new $class($vars);
// return [$provider, $response];
// try {
// // Call ServiceProvider
// $provider->init($embedCode);
// $embed += array(
// // Get unique id of video
// 'id' => $provider->canonicalize($embedCode),
// // Templates are stored in the templates/partials folder
// 'assets' => $provider->getAssets(),
// 'template' => $provider->getTemplatePaths(),
// 'variables' => $provider->getTemplateVariables(),
// // Store embed status of ServiceProvider call
// 'success' => true,
// 'message' => '',
// );
// } catch (\Exception $e) {
// $embed['success'] = false;
// $embed['message'] = $e->getMessage();
// }
// }
// return $embed;
}
public function getDomains()
{
$allDomains = [];
foreach ($this->services as $key => $service) {
$allDomains[] = $service['domains'];
}
// Flatten multidimensional array of domains
$domains = [];
array_walk($allDomains, function($domain) use (&$domains) {
$domains[] = $domain;
});
// Return unique array of domains
return array_unique($domains);
}
public function getProviders()
{
// Get providers sorted by priority
$classes = $this->collectProviders(function($key, $service) {
return true;
}, true, 'class');
// Replace names with provider classes
$providers = [];
foreach ($classes as $class) {
$name = substr($class, strrpos($class, '\\') + 1);
$provider = $this->services[$class]['provider'];
if ( isset($providers[$name]) ) {
$providers[$name] = array($providers[$name], $provider);
} else {
$providers[$name][] = $provider;
}
}
return $providers;
}
public function getProviderByName($name, $all = false)
{
$providers = $this->collectProviders(
function($key, $service) use ($name) {
return preg_match("~^$name$~i", $service['name']);
}, $all);
return $providers;
}
public function getProviderByDomain($domain, $all = false)
{
$providers = $this->collectProviders(
function($key, $service) use ($domain) {
return in_array($domain, $service['domains']);
}, $all);
return $providers;
}
public function register($provider, $priority = 0)
{
if ($provider instanceof \Grav\Plugin\MediaEmbed\OEmbed\OEmbed) {
$key = md5(spl_object_hash($provider));
$domains = $provider->getDomains();
$this->services[$key] = array(
'priority' => $priority,
'domains' => $domains,
'provider' => $provider,
'name' => $provider->name(),
'class' => $key,
);
foreach ($domains as $domain) {
if (!isset($this->domains[$domain]) || ($priority > $this->services[$domain]['priority'])) {
$this->domains[$domain] = $key;
}
}
}
}
public function unregister($provider)
{
if ($provider instanceof \Grav\Plugin\MediaEmbed\OEmbed\OEmbed) {
$key = md5(spl_object_hash($provider));
if (isset($this->services[$key])) {
$domains = $this->services[$key]['domains'];
unset($this->services[$key]);
// Unset and repopulate domain keys, if possible
foreach ($domains as $domain) {
if ($provider = $this->getProviderByDomain($domain)) {
$this->domains[$domain] = get_class($provider);
} else {
unset($this->domains[$domain]);
}
}
}
}
}
protected function collectProviders($callback, $all, $id = 'provider')
{
$services = [];
foreach ($this->services as $key => $service) {
if ($callback($key, $service)) {
$services[] = $service;
}
}
// Sort providers based on priority
uasort($services, function ($a, $b) {
// Priority is first sort criterion
$cmp = $a['priority'] - $b['priority'];
// Text string is second criterion (in case of two equal priorities)
if ( $cmp == 0 ) {
$cmp = strnatcmp($a['name'], $b['name']);
}
return $cmp;
});
// Strip additional service informations
$providers = array_map(function($service) use ($id) {
return $service[$id];
}, $services);
if (count($providers)) {
// Return providers
return ($all ? $providers : $providers[0]);
}
return [];
}
/** -------------------------------
* Private/protected helper methods
* --------------------------------
*/
protected function parseUrl($url)
{
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return [];
}
// Parse URL
$url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$parts = parse_url($url);
$parts['url'] = $url;
$parts['host'] = preg_replace("/^www\./", '', $parts['host']);
// Get top-level domain from URL
$parts['domain'] = isset($parts['host']) ? $parts['host'] : '';
if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {
$parts['domain'] = $match['domain'];
}
if (isset($parts['query'])) {
parse_str(urldecode($parts['query']), $parts['query']);
}
$parts['query'] = [];
return $parts;
}
}
@@ -0,0 +1,234 @@
<?php
/**
* ServiceProvider
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed;
use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Data;
use RocketTheme\Toolbox\Event\Event;
/**
* ServiceProvider
*/
abstract class ServiceProvider implements ProviderInterface
{
use GravTrait;
/**
* @var \Grav\Common\Data\Data
*/
protected $config;
/**
* @var string
*/
protected $embedCode = '';
/**
* @var array
*/
protected $attributes;
/**
* @var array
*/
protected $params;
/** -------------
* Public methods
* --------------
*/
/**
* Constructor.
*/
public function __construct(array $config = [])
{
$this->config = new Data($config);
}
public function init($embedCode)
{
$this->reset();
// Normalize URL to embed
$this->embedCode = $this->canonicalize($embedCode);
$url = $this->parseUrl($embedCode);
// Get media attributes and object parameters
$attributes = $this->config->get('media', []);
$params = array_replace_recursive($this->config->get('params', []), $url['query']);
// Copy media attributes from object parameters
$attr_keys = ['width', 'height', 'crop', 'preview', 'responsive'];
foreach ($attr_keys as $key) {
if (isset($params[$key])) {
$attributes[$key] = $params[$key];
unset($params[$key]);
}
}
// Set media attributes and object parameters
$this->attributes($attributes);
$this->params($params);
}
public function reset() {
// Reset values
$this->embedCode = '';
$this->attributes([]);
$this->params([]);
}
public function id()
{
return $this->embedCode;
}
public function slug()
{
$slug = strtolower($this->name()) . '://' . $this->id();
return $slug;
}
public function name()
{
$name = $this->config->get('name', get_class($this));
return substr($name, strrpos($name, '\\') + 1);
}
public function type()
{
return $this->config->get('type', 'unknown');
}
public function thumbnail() {
$thumbnails = $this->config->get('thumbnail', []);
if (is_string($thumbnails)) {
$thumbnails = [$thumbnails];
}
$url = '';
foreach ($thumbnails as $thumbnail) {
$thumbnail = $this->format($thumbnail);
if (substr(get_headers($thumbnail)[0], -6) == '200 OK') {
$url = $thumbnail;
break;
}
}
return $url;
}
public function attributes($var = null)
{
if ($var !== null) {
$this->attributes = $var;
}
if (!is_array($this->attributes)) {
$this->attributes = [];
}
return $this->attributes;
}
public function params($var = null)
{
if ($var !== null) {
$this->params = $var;
}
if (!is_array($this->params)) {
$this->params = [];
}
return $this->params;
}
/**
* Return the domain(s) of this media resource
*
* @return string
*/
// public function getDomains()
// {
// return [];
// }
public function onTwigTemplateVariables(Event $event)
{
$mediaembed = $event['mediaembed'];
foreach ($this->config->get('assets', []) as $asset) {
$mediaembed->add($asset);
}
}
/**
* Convenience wrapper for `echo $ServiceProvider`
*
* @return string
*/
public function __toString() {
return $this->getEmbedCode();
}
/** -------------------------------
* Private/protected helper methods
* --------------------------------
*/
protected function format($string, $params = [])
{
$params += [
'{:id}' => $this->id(),
'{:name}' => $this->name(),
'{:url}' => urlencode($this->config->get('website', '')),
];
// Format URL placeholder with params
$params['{:url}'] = urlencode(str_ireplace(array_keys($params), $params, $params['{:url}']));
$string = preg_replace_callback('~\{\:oembed(?:\.(?=\w))([\.\w_]+)?\}~i',
function($match) {
static $oembed;
if (is_null($oembed)) {
$oembed = new Data($this->getOEmbed());
}
return $oembed->get($match[1], '');
}, $string);
return str_ireplace(array_keys($params), $params, $string);
}
protected function parseUrl($url)
{
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return [];
}
// Parse URL
$url = html_entity_decode($url, ENT_COMPAT | ENT_HTML401, 'UTF-8');
$parts = parse_url($url);
$parts['url'] = $url;
// Get top-level domain from URL
$parts['domain'] = isset($parts['host']) ? $parts['host'] : '';
if ( preg_match('~(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$~i', $parts['domain'], $match) ) {
$parts['domain'] = $match['domain'];
}
if (isset($parts['query'])) {
parse_str(urldecode($parts['query']), $parts['query']);
}
$parts['query'] = [];
return $parts;
}
}
@@ -0,0 +1,56 @@
<?php
/**
* GitHub
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\Services;
use Grav\Common\Debugger;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbedRich;
/**
* GitHub
*/
class GitHub extends OEmbedRich
{
public function getOEmbed()
{
if ($this->oembed) {
return $this->oembed;
}
$endpoint = $this->format($this->config->get('endpoint', ''));
if (!$endpoint) {
return [];
}
$response = \Requests::get($endpoint);
if (!$response->success) {
$response->throw_for_status();
}
$json = json_decode($response->body, true);
$this->oembed = [
'type' => 'rich',
'title' => $json['files'],
'description' => $json['description'],
'author_name' => $json['owner'],
'author_url' => 'https://github.com/' . $json['owner'],
'provider' => 'GitHub',
'provider_url' => 'https://gist.github.com/',
'url' => 'https://gist.github.com/' . $this->embedCode,
'html' => $json['div'],
];
$this->config->join('assets', [$json['stylesheet']]);
return $this->oembed;
}
}
@@ -0,0 +1,72 @@
<?php
/**
* Slides.com
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\Services;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbedRich;
/**
* Slides
*/
class Slides extends OEmbedRich
{
public function getOEmbed()
{
if ($this->oembed) {
return $this->oembed;
}
$endpoint = $this->format($this->config->get('endpoint', ''));
if (!$endpoint) {
return [];
}
// Extract owner from embed code
list($owner, $id) = explode('/', $this->embedCode, 2);
// Fake response
$this->oembed = [
'type' => 'rich',
'title' => '',
'description' => '',
'author_name' => $owner,
'author_url' => 'http://slides.com/'.$owner,
'provider' => 'Slides',
'provider_url' => 'http://slides.com',
'url' => 'http://slides.com/'.$this->embedCode,
'html' => '<iframe src="//slides.com/'.rtrim($this->embedCode, '/').'/embed" width="576" height="420" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>',
'width' => 576,
'height' => 420,
];
return $this->oembed;
}
public function getEmbedCode($params = [])
{
$embed = parent::getEmbedCode($params);
if ($this->embedCode && $this->oembed) {
// Inject parameters directly into HTML OEmbed attribute
$query = http_build_query($this->params());
$url = $this->attributes['protocol'].'slides.com/'.rtrim($this->embedCode, '/').'/embed';
if (mb_strlen($query) > 0) {
$url .= (false === strpos($url, '?') ? '?' : '&') . $query;
}
// Get width and height
$width = $this->attributes['width'];
$height = $this->attributes['height'];
$embed = '<iframe src="'.$url.'" width="'.$width.'" height="'.$height.'" scrolling="no" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
}
return $embed;
}
}
@@ -0,0 +1,51 @@
<?php
/**
* Twitter
*
* This file is part of Grav MediaEmbed plugin.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*/
namespace Grav\Plugin\MediaEmbed\Services;
use Grav\Plugin\MediaEmbed\OEmbed\OEmbedRich;
/**
* Twitter
*/
class Twitter extends OEmbedRich
{
public function getOEmbed()
{
if ($this->oembed) {
return $this->oembed;
}
$endpoint = $this->format($this->config->get('endpoint', ''));
if (!$endpoint) {
return [];
}
$response = \Requests::get($endpoint);
if (!$response->success) {
$response->throw_for_status();
}
$json = json_decode($response->body, true);
$this->oembed = [
'type' => 'rich',
'author_name' => $json['author_name'],
'author_url' => 'https://twitter.com/' . $json['author_name'],
'provider_name' => 'Twitter',
'provider_url' => 'https://twitter.com/',
'url' => 'https://www.twitter.com/' . $this->embedCode,
'html' => $json['html'],
];
return $this->oembed;
}
}
@@ -0,0 +1,125 @@
# Contributing to Grav MediaEmbed plugin
Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved.
Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue or assessing patches and features.
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs), [features requests](#features) and [submitting pull requests](#pull-requests), but please respect the following restrictions:
* Please format your issue using [GitHub's markdown syntax and features][markdown].
* Please **do not** use the issue tracker for personal support requests (use [Stack Overflow](http://stackoverflow.com) or IRC).
* Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
As a courtesy to avoid sending notifications to any user that might have the `@username` being referenced, please remember that GitHub usernames also start with the at sign and therefore wrap words that begin with the at sign (`@`) in backticks.
<a id="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; create a [reduced test case](http://css-tricks.com/6263-reduced-test-cases/) and a live example that should be included in each bug report.
A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs.
_Example:_
> Short and descriptive example bug report title
>
> A summary of the issue and the browser/OS environment in which it occurs. If
> suitable, include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
Last but not least, if you have a solution or suggestion for how to fix the bug you're reporting, please include it, too, or make a pull request - this will make the life of the maintainers easier and a bug faster to be fixed.
<a id="features"></a>
## Feature requests
Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible and don't forget:
* Please search for existing feature requests first to see if something similar already exists.
* Include a clear and specific use-case. We love new ideas, but we do not add features without a reason.
* Consider whether or not your feature would be better as a function or implemented in a separate project.
## Pull requests
Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. You can start by adding a feature request to get feedback and see how your idea is received.
**Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code, porting to a different language), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project.
Please adhere to the coding conventions used throughout a project (indentation, accurate comments, etc.) and any other requirements (such as test coverage).
Follow this process if you'd like your work considered for inclusion in the project:
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/<repo-name>
# Navigate to the newly cloned directory
cd <repo-name>
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/<upstream-owner>/<repo-name>
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout <dev-branch>
git pull upstream <dev-branch>
```
3. Create a new topic branch (off the main project development branch) to contain your feature, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Please adhere to these [git commit message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is unlikely be merged into the main project. Use Git's [interactive rebase](https://help.github.com/articles/interactive-rebase) feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream development branch into your topic branch:
```bash
git pull [--rebase] upstream <dev-branch>
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description.
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
license your work under the same license as that used by the project.
### Coding Standards
* Always use spaces, never tabs
* End lines in semi-colons.
## Developing
If you want to take an issue, just add a small comment saying you are having a go at something, so we don't get duplication.
[markdown]: https://help.github.com/articles/github-flavored-markdown
+47
View File
@@ -0,0 +1,47 @@
# [Installation and update guide][project]
[project]: https://github.com/sommerregen/grav-plugin-mediaembed
## Installation
Installing the `MediaEmbed` plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file.
### GPM Installation (Preferred)
The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type:
bin/gpm install mediaembed
This will install the `MediaEmbed` plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/mediaembed`.
### Manual Installation
To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `mediaembed`. You can find these files either on [GitHub](https://github.com/sommerregen/grav-plugin-mediaembed) or via [GetGrav.org](http://getgrav.org/downloads/plugins).
You should now have all the plugin files under
/your/site/grav/user/plugins/mediaembed
>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav) and a theme to be installed in order to operate.
## Updating
As development for `MediaEmbed` continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating `MediaEmbed` is easy, and can be done through Grav's GPM system, as well as manually.
### GPM Update (Preferred)
The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following:
bin/gpm update mediaembed
This command will check your Grav install to see if your `MediaEmbed` plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache.
#### Manual Update
Manually updating `MediaEmbed` is pretty simple. Here is what you will need to do to get this done:
* Delete the `your/site/user/plugins/mediaembed` directory.
* Download the new version of the `MediaEmbed` plugin from either [GitHub](https://github.com/sommerregen/grav-plugin-mediaembed) or [GetGrav.org](http://getgrav.org/downloads/plugins).
* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `mediaembed`.
* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`.
>> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact.
+15
View File
@@ -0,0 +1,15 @@
{
"project":"grav-plugin-mediaembed",
"platforms":{
"grav":{
"nodes":{
"plugin":[
{
"source":"/",
"destination":"/user/plugins/mediaembed"
}
]
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
<?php
/**
* MediaEmbed v1.3.0
*
* This plugin embeds several media sites (e.g. YouTube, Vimeo,
* Soundcloud) by only providing the URL to the medium.
*
* Dual licensed under the MIT or GPL Version 3 licenses, see LICENSE.
* http://benjamin-regler.de/license/
*
* @package MediaEmbed
* @version 1.3.0
* @link <https://github.com/sommerregen/grav-plugin-archive-plus>
* @author Benjamin Regler <sommerregen@benjamin-regler.de>
* @copyright 2017, Benjamin Regler
* @license <http://opensource.org/licenses/MIT> MIT
* @license <http://opensource.org/licenses/GPL-3.0> GPLv3
*/
namespace Grav\Plugin;
use Grav\Common\Grav;
use Grav\Common\Plugin;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\Event\Event;
use Grav\Plugin\MediaEmbed\Autoloader;
use Grav\Plugin\MediaEmbed\MediaEmbed;
/**
* MediaEmbed
*
* This plugin ...
*/
class MediaEmbedPlugin extends Plugin
{
/**
* @var MediaEmbedPlugin
*/
/** ---------------------------
* Private/protected properties
* ----------------------------
*/
/**
* Instance of MediaEmbed class
*
* @var object
*/
protected $mediaembed;
/** -------------
* Public methods
* --------------
*/
/**
* Return a list of subscribed events.
*
* @return array The list of events of the plugin of the form
* 'name' => ['method_name', priority].
*/
public static function getSubscribedEvents()
{
return [
'onPluginsInitialized' => ['onPluginsInitialized', 0],
];
}
/**
* Initialize configuration.
*/
public function onPluginsInitialized()
{
if ($this->isAdmin()) {
$this->active = false;
return;
}
if ($this->config->get('plugins.mediaembed.enabled')) {
// Initialize Autoloader
require_once(__DIR__ . '/classes/Autoloader.php');
require_once(__DIR__ . '/vendor/Requests/library/Requests.php');
$autoloader = new Autoloader();
$autoloader->route([
'Requests_' => __DIR__ . '/vendor/Requests/library/Requests',
], false);
$autoloader->register();
// Initialize MediaEmbed class
$this->mediaembed = new MediaEmbed($this->config);
$this->enable([
'onPageContentRaw' => ['onPageContentRaw', 0],
'onPageContentProcessed' => ['onPageContentProcessed', 0],
'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
'onTwigSiteVariables' => ['onTwigSiteVariables', 0],
]);
}
}
/**
* Add content after page content was read into the system.
*
* @param Event $event An event object, when `onPageContentRaw` is
* fired.
*/
public function onPageContentRaw(Event $event)
{
/** @var Page $page */
$page = $event['page'];
$config = $this->mergeConfig($page);
if ($config->get('enabled')) {
// Get raw content and substitute all formulas by a unique token
$raw_content = $page->getRawContent();
// Save modified page content with tokens as placeholders
$page->setRawContent(
$this->mediaembed->prepare($raw_content, $page->id())
);
}
}
/**
* Apply mediaembed filter to content, when each page has not been
* cached yet.
*
* @param Event $event The event when 'onPageContentProcessed' was
* fired.
*/
public function onPageContentProcessed(Event $event)
{
/** @var Page $page */
$page = $event['page'];
$config = $this->mergeConfig($page);
if ($config->get('enabled') && $this->compileOnce($page)) {
// Get content
$content = $page->getRawContent();
// Apply MediaEmbed filter and save modified page content
$page->setRawContent(
$this->mediaembed->process($content, $config)
);
}
}
/**
* Add current directory to twig lookup paths.
*/
public function onTwigTemplatePaths()
{
// Register MediaEmbed Twig templates
$this->grav['twig']->twig_paths[] = __DIR__ . '/templates';
// Fire event for MediaEmbed plugins
$this->mediaembed->fireEvent('onTwigTemplatePaths');
}
/**
* Set needed variables to display videos.
*/
public function onTwigSiteVariables()
{
// Register built-in CSS assets
if ($this->config->get('plugins.mediaembed.built_in_css')) {
$this->grav['assets']
->add('plugin://mediaembed/assets/css/mediaembed.css');
}
if ($this->config->get('plugins.mediaembed.built_in_js')) {
$this->grav['assets']
->add('plugin://mediaembed/assets/js/mediaembed.js');
}
// Register assets from MediaEmbed Services
$assets = $this->mediaembed->getAssets();
foreach ($assets as $asset) {
$this->grav['assets']->add($asset);
}
}
/** -------------------------------
* Private/protected helper methods
* --------------------------------
*/
/**
* Checks if a page has already been compiled yet.
*
* @param Page $page The page to check
*
* @return boolean Returns TRUE if page has already been
* compiled yet, FALSE otherwise
*/
protected function compileOnce(Page $page)
{
static $processed = [];
$id = md5($page->path());
// Make sure that contents is only processed once
if (!isset($processed[$id]) || ($processed[$id] < $page->modified())) {
$processed[$id] = $page->modified();
return true;
}
return false;
}
}
+241
View File
@@ -0,0 +1,241 @@
# Global plugin configurations
enabled: true # Set to false to disable this plugin completely
link: false # Set if display link or img, if false display img tag for not mediaembed
built_in_css: true # Use built-in CSS of the plugin
built_in_js: true # Use built-in JS of the plugin
# Default options for MediaEmbed configuration
# -- Media --
media:
width: 640 # Default media width
height: 390 # Default media height including controls
adjust: true # Adjust media or keep default dimensions?
preview: true # Show or hide media preview
responsive: false # Allow media to be responsive
protocol: "http://" # Default protocol for remote media resources
# -- Services --
services:
## Audio ##
SoundCloud:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
example: "https://soundcloud.com/semiseria/verruckert-ausschnitt"
# URL of media service used for embedding
url: "w.soundcloud.com/player/?url=http://api.soundcloud.com/tracks/{:id}"
# Canonical URL of media service (used in endpoint calls)
canonical: "http://soundcloud.com/{:id}"
# Endpoint to grab media informations
endpoint: "http://soundcloud.com/oembed?url={:canonical}&format=json"
# Schemes to grab media id
schemes:
- "soundcloud.com/*"
- "soundcloud.com/*/*"
- "soundcloud.com/*/sets/*"
- "soundcloud.com/groups/*"
- "snd.sc/*"
params:
auto_play: false
buying: true
download: true
hide_related: false
liking: true
sharing: true
show_artwork: true
show_comments: true
show_playcount: true
show_user: true
visual: false
Spotify:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "http://open.spotify.com/track/{:id}"
# Endpoint to grab media informations
endpoint: "https://embed.spotify.com/oembed/?url={:canonical}&format=json"
# Schemes to grab media id
schemes:
- "open.spotify.com/track/*"
- "spoti.fi/*"
## Photo ##
Flickr:
enabled: true # Set to false to disable this service completely
type: "photo" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "http://www.flickr.com/photos/{:id}"
# Endpoint to grab media informations
endpoint: "http://flickr.com/services/oembed?url={:canonical}&format=json"
# Schemes to grab media id
schemes:
- "flickr.com/photos/*"
- "flic.kr/*"
Imgur:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "http://imgur.com/gallery/{:id}"
# Endpoint to grab media informations
endpoint: "http://api.imgur.com/oembed?url={:canonical}&format=json"
# Schemes to grab media id
schemes:
- "imgur.com/gallery/*"
- "imgur.com/a/*"
- "imgur.com/*"
Instagram:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "http://instagram.com/p/{:id}"
# Endpoint to grab media informations
endpoint: "http://api.instagram.com/oembed?url={:canonical}&format=json"
# Schemes to grab media id
schemes:
- "instagr.am/p/*"
- "instagram.com/p/*"
## Video ##
Dailymotion: # Legacy media service name
enabled: true # Set to false to disable this service completely
type: "video" # Type of the legacy media service
# URL of media service used for embedding
url: "www.dailymotion.com/embed/video/{:id}"
# Canonical URL of media service (used in endpoint calls)
canonical: "http://dailymotion.com/video/{:id}"
# Endpoint to grab media informations
endpoint: "http://www.dailymotion.com/services/oembed?url={:canonical}&format=json"
schemes: # Regex filter ("~REGEX~i") to grab media id
- "dailymotion.com/video/*"
- "dailymotion.com/*/video/*"
- "dai\.ly/*"
# Custom service-related media option overrides
params:
quality: 1080
YouTube:
enabled: true # Set to false to disable this service completely
type: "video" # Type of the media service
# URL of media service used for embedding
url: "www.youtube.com/embed/{:id}"
# Canonical URL of media service (used in endpoint calls)
canonical: "http://www.youtube.com/watch?v={:id}"
# Endpoint to grab media informations
endpoint: "http://www.youtube.com/oembed?url={:canonical}&format=json"
# Regex filters ("~REGEX~i") to grab media id
schemes:
- "youtube.com/watch?*v=*"
- "youtube.com/embed/*"
- "youtube.com/v/*"
- "youtube.com/?*v=*"
- "youtu.be/*"
# Custom service-related media option overrides
params:
autoplay: 0
modestbranding: 1
theme: "light"
Vimeo: # Legacy media service name
enabled: true # Set to false to disable this service completely
type: "video" # Type of the legacy media service
url: "player.vimeo.com/video/{:id}"
canonical: "https://vimeo.com/{:id}"
# Endpoint to grab media informations
endpoint: "http://vimeo.com/api/oembed.json?url={:canonical}"
schemes: # Regex filter ("~REGEX~i") to grab media id
- "vimeo.com/*"
- "vimeo.com/channels/*/*"
- "vimeo.com/groups/*/videos/*"
# Custom service-related media option overrides
params:
autoplay: 0
## Others ##
Github:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "https://gist.github.com/{:id}"
# Endpoint to grab media informations
endpoint: "https://gist.github.com/{:id}.json"
# Schemes to grab media id
schemes:
- "gist.github.com/*"
- "gist.github.com/*/*"
- "gist.github.com/*?*"
Slides:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "http://slides.com/{:id}"
# Endpoint to grab media informations
endpoint: "http://slides.com/{:id}"
# Schemes to grab media id
schemes:
- "slides.com/*"
- "slid.es/*"
# Custom service-related media option overrides
params:
style: "light" # Footer style: dark, light, hidden
width: 1920
height: 1400
Twitter:
enabled: true # Set to false to disable this service completely
type: "rich" # Type of the media service
# Canonical URL of media service (used in endpoint calls)
canonical: "https://twitter.com/{:id}"
# Endpoint to grab media informations
endpoint: "https://api.twitter.com/1/statuses/oembed.json?url={:canonical}"
# Schemes to grab media id
schemes:
- "twitter.com/*"
- "twitter.com/*/*"
@@ -0,0 +1,124 @@
{# Shortcut variables #}
{% set default = mediaembed.config.media %}
{% set oembed = mediaembed.service %}
{# Processing error #}
{% if not mediaembed.success %}
{% if default.responsive %}
{% set class = "-responsive mediaembed-msod" %}
{% else %}
{% set class = " mediaembed-msod\" style=\"max-width: " ~ default.width ~ "px;" %}
{% endif %}
<div class="mediaembed{{ class }}">
<p class="mediaembed-icon">&#9749;</p>
<p class="mediaembed-error-title"><b>Unable to process oEmbed media:</b><a href="{{ mediaembed.raw.src }}" alt="{{ mediaembed.raw.alt }}" title="{{ mediaembed.raw.title }}">{{ mediaembed.raw.src|e }}</a></p>
<p class="mediaembed-error-message"><b>Error:</b>{{ mediaembed.message }}</p>
</div>
{# Setup block #}
{% else %}
{% set response = oembed.attributes %}
{# Normalize response #}
{% if (response.width is empty) or not (response.width is defined) or (response.width == 0) %}
{% set response = response|merge({'width' : default.width|default(1)}) %}
{% endif %}
{% if (response.height is empty) or not (response.height is defined) or (response.height == 0) %}
{% set response = response|merge({'height' : default.height|default(1)}) %}
{% endif %}
{% set width = response.width %}
{% set height = response.height %}
{% set ratio = height / width %}
{# Adjust OEmbed media dimensions or restrict them? #}
{% if default.adjust %}
{# Check if computed height is larger than default setting #}
{% if (width * ratio) > default.height or (height / ratio) > default.width %}
{# Rescale width and height #}
{% set width = default.width|default(1) %}
{% set height = (default.width * ratio)|round %}
{% endif %}
{% else %}
{% set width = default.width|default(1) %}
{% set height = default.height|default(1) %}
{% endif %}
{# Recompute aspect ratio #}
{% set ratio = height / width %}
{% set container_styles = "padding-bottom: " ~ "%.2f"|format(ratio * 100) ~ "%;" %}
{# Embed responsive OEmbed media #}
{% if default.responsive %}
{% set responsive = "-responsive" %}
{% set width = 1920 %}
{% set height = (width * ratio)|round %}
{% else %}
{% set styles = " style=\"max-width: " ~ width ~ "px; max-height: " ~ height ~ "px;\"" %}
{% endif %}
{# Setup lazy loading media for those with preview enabled #}
{% if default.preview %}
{% set lazyload = " lazyload" %}
{% set lazyload_script = " onclick=\"lazyload(this)\"" %}
{% endif %}
<div class="mediaembed{{ responsive }} mediaembed-media mediaembed-{{ oembed.type }} mediaembed-{{ oembed.name|lower }}{{ lazyload }}"{{ styles }}>
<div class="mediaembed-container" style="{{ container_styles }}">
{# Embed content according to media type #}
{# -- Photo -- #}
{% if oembed.type == "photo" %}
{% set title = oembed.getOembed().provider_name ~ ': &#8220;' ~ oembed.title ~ '&#8221; by ' ~ oembed.author %}
<a class="mediaembed-embed" href="{{ oembed.author('url') }}" title="{{ title }}">
<img src="{{ oembed.url }}" alt="{{ oembed.title }}">
</a>
{# -- Video -- #}
{% elseif oembed.type == "video" %}
<a href="{{ oembed.getEmbedCode }}" alt="{{ mediaembed.raw.alt }}" title="{{ mediaembed.raw.title }}" class="mediaembed-media"{{ lazyload_script }}>
{# JavaScript lazy-loading #}
<!--
<iframe class="mediaembed-embed" src="{{ oembed.getEmbedCode }}" width="{{ width }}" height="{{ height }}" frameborder="0" scrolling="no" webkitallowfullscreen mozallowfullscreen allowfullscreen>
<p>Your browser does not support iframes.</p>
</iframe>
-->
{# Show Thumbnail #}
{% if default.preview and oembed.thumbnail %}
<img src="{{ oembed.thumbnail }}" alt="{{ mediaembed.raw.alt }}" title="{{ mediaembed.raw.title }}" class="mediaembed-thumbnail" />
{% else %}
{# Show placeholder #}
{% endif %}
{# Toggle for loading iframe content #}
<input type="checkbox" id="mediaembed-hidden-input-{{ mediaembed.uid }}" class="mediaembed-input" />
{# Implement JavaScript-less lazyload technique #}
<noscript>
<iframe class="mediaembed-embed" src="{{ oembed.getEmbedCode }}" width="{{ width }}" height="{{ height }}" frameborder="0" scrolling="no" webkitallowfullscreen mozallowfullscreen allowfullscreen style="display: none;">
<p>Your browser does not support iframes.</p>
</iframe>
</noscript>
<label class="mediaembed-play" for="mediaembed-hidden-input-{{ mediaembed.uid }}">&#9654;</label>
</a>
{# -- Link -- #}
{% elseif oembed.type == "link" %}
{% set title = oembed.getOembed().provider_name ~ ': &#8220;' ~ oembed.mediaembed.raw.title|default(title) ~ '&#8221; by ' ~ oembed.author %}
<a href="{{ mediaembed.raw.src }}" alt="{{ mediaembed.raw.alt }}" title="{{ title }}">{{ mediaembed.raw.src|e }}</a>
{# -- Rich media -- #}
{% elseif oembed.type == "rich" %}
<div class="mediaembed-media">{{ oembed.getEmbedCode() }}</div>
{# -- Nothing from above -- #}
{% else %}
{# Show at least a link for the user #}
<a href="{{ mediaembed.raw.src }}" alt="{{ mediaembed.raw.alt }}" title="{{ mediaembed.raw.title }}">{{ mediaembed.raw.src|e }}</a>
{% endif %}
</div>
</div>
{% endif %}
@@ -0,0 +1,4 @@
src_dir: library
coverage_clover: tests/clover.xml
json_path: tests/coveralls.json
service_name: travis-ci
+27
View File
@@ -0,0 +1,27 @@
language: php
before_script:
# Setup Coveralls and httpbin-php
- phpenv local 5.5
- composer install --dev --no-interaction
- TESTPHPBIN=$(phpenv which php)
- sudo PHPBIN=$TESTPHPBIN vendor/bin/start.sh
- export REQUESTS_TEST_HOST_HTTP=localhost
- phpenv local --unset
# Work out of the tests directory
- cd tests
script:
- phpunit --coverage-clover clover.xml
after_script:
- cd ..
- phpenv local 5.5
- sudo PATH=$PATH vendor/bin/stop.sh
- php vendor/bin/coveralls -v
- phpenv local --unset
php:
- 5.2
- 5.3
- 5.4
- 5.5
- hhvm
+48
View File
@@ -0,0 +1,48 @@
Changelog
=========
1.6.0
-----
- [Add multiple request support][#23] - Send multiple HTTP requests with both
fsockopen and cURL, transparently falling back to synchronous when
not supported.
- [Add proxy support][#70] - HTTP proxies are now natively supported via a
[high-level API][docs/proxy]. Major props to Ozh for his fantastic work
on this.
- [Verify host name for SSL requests][#63] - Requests is now the first and only
standalone HTTP library to fully verify SSL hostnames even with socket
connections. Thanks to Michael Adams, Dion Hulse, Jon Cave, and Pádraic Brady
for reviewing the crucial code behind this.
- [Add cookie support][#64] - Adds built-in support for cookies (built entirely
as a high-level API)
- [Add sessions][#62] - To compliment cookies, [sessions][docs/usage-advanced]
can be created with a base URL and default options, plus a shared cookie jar.
- Add [PUT][#1], [DELETE][#3], and [PATCH][#2] request support
- [Add Composer support][#6] - You can now install Requests via the
`rmccue/requests` package on Composer
[docs/proxy]: http://requests.ryanmccue.info/docs/proxy.html
[docs/usage-advanced]: http://requests.ryanmccue.info/docs/usage-advanced.html
[#1]: https://github.com/rmccue/Requests/issues/1
[#2]: https://github.com/rmccue/Requests/issues/2
[#3]: https://github.com/rmccue/Requests/issues/3
[#6]: https://github.com/rmccue/Requests/issues/6
[#9]: https://github.com/rmccue/Requests/issues/9
[#23]: https://github.com/rmccue/Requests/issues/23
[#62]: https://github.com/rmccue/Requests/issues/62
[#63]: https://github.com/rmccue/Requests/issues/63
[#64]: https://github.com/rmccue/Requests/issues/64
[#70]: https://github.com/rmccue/Requests/issues/70
[View all changes][https://github.com/rmccue/Requests/compare/v1.5.0...v1.6.0]
1.5.0
-----
Initial release!
+49
View File
@@ -0,0 +1,49 @@
Requests
========
Copyright (c) 2010-2012 Ryan McCue and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ComplexPie IRI Parser
=====================
Copyright (c) 2007-2010, Geoffrey Sneddon and Steve Minutillo.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the SimplePie Team nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
+145
View File
@@ -0,0 +1,145 @@
Requests for PHP
================
Requests is a HTTP library written in PHP, for human beings. It is roughly
based on the API from the excellent [Requests Python
library](http://python-requests.org/). Requests is [ISC
Licensed](https://github.com/rmccue/Requests/blob/master/LICENSE) (similar to
the new BSD license) and has no dependencies, except for PHP 5.2+.
Despite PHP's use as a language for the web, its tools for sending HTTP requests
are severely lacking. cURL has an
[interesting API](http://php.net/manual/en/function.curl-setopt.php), to say the
least, and you can't always rely on it being available. Sockets provide only low
level access, and require you to build most of the HTTP response parsing
yourself.
We all have better things to do. That's why Requests was born.
```php
$headers = array('Accept' => 'application/json');
$options = array('auth' => array('user', 'pass'));
$request = Requests::get('https://api.github.com/gists', $headers, $options);
var_dump($request->status_code);
// int(200)
var_dump($request->headers['content-type']);
// string(31) "application/json; charset=utf-8"
var_dump($request->body);
// string(26891) "[...]"
```
Requests allows you to send **HEAD**, **GET**, **POST**, **PUT**, **DELETE**,
and **PATCH** HTTP requests. You can add headers, form data, multipart files,
and parameters with simple arrays, and access the response data in the same way.
Requests uses cURL and fsockopen, depending on what your system has available,
but abstracts all the nasty stuff out of your way, providing a consistent API.
Features
--------
- International Domains and URLs
- Browser-style SSL Verification
- Basic/Digest Authentication
- Automatic Decompression
- Connection Timeouts
Installation
------------
### Install with Composer
If you're using [Composer](https://github.com/composer/composer) to manage
dependencies, you can add Requests with it.
{
"require": {
"rmccue/requests": ">=1.0"
}
}
### Install source from GitHub
To install the source code:
$ git clone git://github.com/rmccue/Requests.git
And include it in your scripts:
require_once '/path/to/Requests/library/Requests.php';
You'll probably also want to register an autoloader:
Requests::register_autoloader();
### Install source from zip/tarball
Alternatively, you can fetch a [tarball][] or [zipball][]:
$ curl -L https://github.com/rmccue/Requests/tarball/master | tar xzv
(or)
$ wget https://github.com/rmccue/Requests/tarball/master -O - | tar xzv
[tarball]: https://github.com/rmccue/Requests/tarball/master
[zipball]: https://github.com/rmccue/Requests/zipball/master
### Using a Class Loader
If you're using a class loader (e.g., [Symfony Class Loader][]) for
[PSR-0][]-style class loading:
$loader->registerPrefix('Requests', 'path/to/vendor/Requests/library');
[Symfony Class Loader]: https://github.com/symfony/ClassLoader
[PSR-0]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
Documentation
-------------
The best place to start is our [prose-based documentation][], which will guide
you through using Requests.
After that, take a look at [the documentation for
`Requests::request()`][request_method], where all the parameters are fully
documented.
Requests is [100% documented with PHPDoc](http://requests.ryanmccue.info/api/).
If you find any problems with it, [create a new
issue](https://github.com/rmccue/Requests/issues/new)!
[prose-based documentation]: https://github.com/rmccue/Requests/blob/master/docs/README.md
[request_method]: http://requests.ryanmccue.info/api/class-Requests.html#_request
Testing
-------
[![Build Status](https://secure.travis-ci.org/rmccue/Requests.png?branch=master)](http://travis-ci.org/rmccue/Requests)
[![Coverage Status](https://coveralls.io/repos/rmccue/Requests/badge.png?branch=master)][coveralls]
Requests strives to have 100% code-coverage of the library with an extensive
set of tests. We're not quite there yet, but [we're getting close][coveralls].
[coveralls]: https://coveralls.io/r/rmccue/Requests?branch=master
To run the test suite, first check that you have the [PHP
JSON extension ](http://php.net/manual/en/book.json.php) enabled. Then
simply:
$ cd tests
$ phpunit
If you'd like to run a single set of tests, specify just the name:
$ phpunit Transport/cURL
Contribute
----------
1. Check for open issues or open a new issue for a feature request or a bug
2. Fork [the repository][] on Github to start making your changes to the
`master` branch (or branch off of it)
3. Write a test which shows that the bug was fixed or that the feature works as expected
4. Send a pull request and bug me until I merge it
[the repository]: https://github.com/rmccue/Requests
@@ -0,0 +1,55 @@
<?php
/**
* PEAR package builder
*
* Inspired by Twig's create_pear_package.php.
* @link https://raw.github.com/fabpot/Twig/master/bin/create_pear_package.php
* @author Twig Team
* @license BSD license
*/
if (!isset($argv[1]) || $argv[1] === '-h' || $argv[1] === '--help') {
echo 'usage: php ' . $argv[0] . ' <version> <stability>' . PHP_EOL;
echo PHP_EOL;
echo ' version:' . PHP_EOL;
echo ' Version of the package, in the form of major.minor.bug' . PHP_EOL;
echo PHP_EOL;
echo ' stability:' . PHP_EOL;
echo ' One of alpha, beta, stable' . PHP_EOL;
die();
}
if (!isset($argv[2])) {
die('You must provide the stability (alpha, beta, or stable)');
}
$context = array(
'date' => gmdate('Y-m-d'),
'time' => gmdate('H:m:00'),
'version' => $argv[1],
'api_version' => $argv[1],
'stability' => $argv[2],
'api_stability' => $argv[2],
);
$context['files'] = '';
$path = realpath(dirname(__FILE__).'/../library/Requests');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if (preg_match('/\.php$/', $file)) {
$name = str_replace($path . DIRECTORY_SEPARATOR, '', $file);
$name = str_replace(DIRECTORY_SEPARATOR, '/', $name);
$context['files'][] = "\t\t\t\t\t" . '<file install-as="Requests/' . $name . '" name="' . $name . '" role="php" />';
}
}
$context['files'] = implode("\n", $context['files']);
$template = file_get_contents(dirname(__FILE__).'/../package.xml.tpl');
$content = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', 'replace_parameters', $template);
file_put_contents(dirname(__FILE__).'/../package.xml', $content);
function replace_parameters($matches) {
global $context;
return isset($context[$matches[1]]) ? $context[$matches[1]] : null;
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "rmccue/requests",
"description": "A HTTP library written in PHP, for human beings.",
"homepage": "http://github.com/rmccue/Requests",
"license": "ISC",
"keywords": ["http", "idna", "iri", "ipv6", "curl", "sockets", "fsockopen"],
"authors": [
{
"name": "Ryan McCue",
"homepage": "http://ryanmccue.info"
}
],
"require": {
"php": ">=5.2"
},
"require-dev": {
"satooshi/php-coveralls": "dev-master",
"requests/test-server": "dev-master"
},
"type": "library",
"autoload": {
"psr-0": {"Requests": "library/"}
}
}
+28
View File
@@ -0,0 +1,28 @@
Documentation
=============
If you're here, you're looking for documentation for Requests! The documents
here are prose; you might also want to check out the [API documentation][].
[API documentation]: http://requests.ryanmccue.info/api/
* Introduction
* [Goals][goals]
* [Why should I use Requests instead of X?][why-requests]
* Usage
* [Making a request][usage]
* [Advanced usage][usage-advanced]
* [Authenticating your request][authentication]
* Advanced Usage
* [Custom authentication][authentication-custom]
* [Requests through proxy][proxy]
* [Hooking system][hooks]
[goals]: goals.md
[why-requests]: why-requests.md
[usage]: usage.md
[usage-advanced]: usage-advanced.md
[authentication]: authentication.md
[authentication-custom]: authentication-custom.md
[hooks]: hooks.md
[proxy]: proxy.md
@@ -0,0 +1,44 @@
Custom Authentication
=====================
Custom authentication handlers are designed to be extremely simple to write.
In order to write a handler, you'll need to implement the `Requests_Auth`
interface.
An instance of this handler is then passed in by the user via the `auth`
option, just like for normal authentication.
Let's say we have a HTTP endpoint that checks for the `Hotdog` header and
authenticates you if said header is set to `Yummy`. (I don't know of any
services that do this; perhaps this is a market waiting to be tapped?)
```php
class MySoftware_Auth_Hotdog implements Requests_Auth {
protected $password;
public function __construct($password) {
$this->password = $password;
}
public function register(Requests_Hooks &$hooks) {
$hooks->register('requests.before_request', array(&$this, 'before_request'));
}
public function before_request(&$url, &$headers, &$data, &$type, &$options) {
$headers['Hotdog'] = $this->password;
}
}
```
We then use this in our request calls:
```
$options = array(
'auth' => new MySoftware_Auth_Hotdog('yummy')
);
$response = Requests::get('http://hotdogbin.org/admin', array(), $options);
```
(For more information on how to register and use hooks, see the [hooking
system documentation][hooks])
[hooks]: hooks.md
@@ -0,0 +1,31 @@
Authentication
==============
Many requests that you make will require authentication of some type. Requests
includes support out of the box for HTTP Basic authentication, with more
built-ins coming soon.
Making a Basic authenticated call is ridiculously easy:
```php
$options = array(
'auth' => new Requests_Auth_Basic(array('user', 'password'))
);
Requests::get('http://httpbin.org/basic-auth/user/password', array(), $options);
```
As Basic authentication is usually what you want when you specify a username
and password, you can also just pass in an array as a shorthand:
```php
$options = array(
'auth' => array('user', 'password')
);
Requests::get('http://httpbin.org/basic-auth/user/password', array(), $options);
```
Note that POST/PUT can also take a data parameter, so you also need that
before `$options`:
```php
Requests::post('http://httpbin.org/basic-auth/user/password', array(), null, $options);
```
+29
View File
@@ -0,0 +1,29 @@
Goals
=====
1. **Simple interface**
Requests is designed to provide a simple, unified interface to making
requests, regardless of what is available on the system. This means not worrying.
2. **Fully tested code**
Requests strives to have 90%+ code coverage from the unit tests, aiming for
the ideal 100%. Introducing new features always means introducing new tests
(Note: some parts of the code are not covered by design. These sections are
marked with `@codeCoverageIgnore` tags)
3. **Maximum compatibility**
No matter what you have installed on your system, you should be able to run
Requests. We use cURL if it's available, and fallback to sockets otherwise.
We require only a baseline of PHP 5.2, leaving the choice of PHP minimum
requirement fully in your hands, and giving you the ability to support many
more hosts.
4. **No dependencies**
Requests is designed to be entirely self-contained and doesn't require
anything else at all. You can run Requests on an entirely stock PHP build
without any additional extensions outside the standard library.
+92
View File
@@ -0,0 +1,92 @@
Hooks
=====
Requests has a hook system that you can use to manipulate parts of the request
process along with internal transport hooks.
Check out the [API documentation for `Requests_Hooks`][requests_hooks] for more
information on how to use the hook system.
Available Hooks
---------------
* `requests.before_request`
Alter the request before it's sent to the transport.
Parameters: `string &$url`, `array &$headers`, `array|string &$data`,
`string &$type`, `array &$options`
* `requests.before_parse`
Alter the raw HTTP response before parsing
Parameters: `string &$response`
* `requests.after_request`
Alter the response object before it's returned to the user
Parameters: `Requests_Response &$return`
* `curl.before_request`
Set cURL options before the transport sets any (note that Requests may
override these)
Parameters: `cURL resource &$fp`
* `curl.before_send`
Set cURL options just before the request is actually sent via `curl_exec`
Parameters: `cURL resource &$fp`
* `curl.after_request`
Alter the raw HTTP response before returning for parsing
Parameters: `string &$response`
* `fsockopen.before_request`
Run events before the transport does anything
* `fsockopen.after_headers`
Add extra headers before the body begins (i.e. before `\r\n\r\n`)
Parameters: `string &$out`
* `fsockopen.before_send`
Add body data before sending the request
Parameters: `string &$out`
* `fsockopen.after_send`
Run events after writing the data to the socket
* `fsockopen.after_request`
Alter the raw HTTP response before returning for parsing
Parameters: `string &$response`
Registering Hooks
-----------------
Note: if you're doing this in an authentication handler, see the [Custom
Authentication guide][authentication-custom] instead.
[authentication-custom]: authentication-custom.md
In order to register your own hooks, you need to instantiate `Requests_hooks`
and pass this in via the 'hooks' option.
```php
$hooks = new Requests_Hooks();
$hooks->register('requests.after_request', 'mycallback');
$request = Requests::get('http://httpbin.org/get', array(), array('hooks' => $hooks));
```
+23
View File
@@ -0,0 +1,23 @@
Proxy Support
=============
You can easily make requests through HTTP proxies.
To make requests through an open proxy, specify the following options:
```php
$options = array(
'proxy' => '127.0.0.1:3128'
);
Requests::get('http://httpbin.org/ip', array(), $options);
```
If your proxy needs you to authenticate, the option will become an array like
the following:
```php
$options = array(
'proxy' => array( '127.0.0.1:3128', 'my_username', 'my_password' )
);
Requests::get('http://httpbin.org/ip', array(), $options);
```
@@ -0,0 +1,74 @@
Advanced Usage
==============
Session Handling
----------------
Making multiple requests to the same site with similar options can be a pain,
since you end up repeating yourself. The Session object can be used to set
default parameters for these.
Let's simulate communicating with GitHub.
```php
$session = new Requests_Session('https://api.github.com/');
$session->headers['X-ContactAuthor'] = 'rmccue';
$session->useragent = 'My-Awesome-App';
$response = $session->get('/zen');
```
You can use the `url`, `headers`, `data` and `options` properties of the Session
object to set the defaults for this session, and the constructor also takes
parameters in the same order as `Requests::request()`. Accessing any other
properties will set the corresponding key in the options array; that is:
```php
// Setting the property...
$session->useragent = 'My-Awesome-App';
// ...is the same as setting the option
$session->options['useragent'] = 'My-Awesome-App';
```
Secure Requests with SSL
------------------------
By default, HTTPS requests will use the most secure options available:
```php
$response = Requests::get('https://httpbin.org/');
```
Requests bundles certificates from the [Mozilla certificate authority list][],
which is the same list of root certificates used in most browsers. If you're
accessing sites with certificates from other CAs, or self-signed certificates,
you can point Requests to a custom CA list in PEM form (the same format
accepted by cURL and OpenSSL):
```php
$options = array(
'verify' => '/path/to/cacert.pem'
);
$response = Requests::get('https://httpbin.org/', array(), $options);
```
Alternatively, if you want to disable verification completely, this is possible
with `'verify' => false`, but note that this is extremely insecure and should be
avoided.
### Security Note
Requests supports SSL across both cURL and fsockopen in a transparent manner.
Unlike other PHP HTTP libraries, support for verifying the certificate name is
built-in; that is, a request for `https://github.com/` will actually verify the
certificate's name even with the fsockopen transport. This makes Requests the
first and currently only PHP HTTP library that supports full SSL verification.
(Note that WordPress now also supports this verification, thanks to efforts by
the Requests development team.)
(See also the [related PHP][php-bug-47030] and [OpenSSL-related][php-bug-55820]
bugs in PHP for more information on Subject Alternate Name field.)
[Mozilla certificate authority list]: http://www.mozilla.org/projects/security/certs/
[php-bug-47030]: https://bugs.php.net/bug.php?id=47030
[php-bug-55820]:https://bugs.php.net/bug.php?id=55820
+154
View File
@@ -0,0 +1,154 @@
Usage
=====
Ready to go? Make sure you have Requests installed before attempting any of the
steps in this guide.
Loading Requests
----------------
Before we can load Requests up, we'll need to make sure it's loaded. This is a
simple two-step:
```php
// First, include Requests
include('/path/to/library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
```
If you'd like to bring along your own autoloader, you can forget about this
completely.
Make a GET Request
------------------
One of the most basic things you can do with HTTP is make a GET request.
Let's grab GitHub's public timeline:
```php
$response = Requests::get('https://github.com/timeline.json');
```
`$response` is now a **Requests_Response** object. Response objects are what
you'll be working with whenever you want to get data back from your request.
Using the Response Object
-------------------------
Now that we have the response from GitHub, let's get the body of the response.
```php
var_dump($response->body);
// string(42865) "[{"repository":{"url":"...
```
Custom Headers
--------------
If you want to add custom headers to the request, simply pass them in as an
associative array as the second parameter:
```php
$response = Requests::get('https://github.com/timeline.json', array('X-Requests' => 'Is Awesome!'));
```
Make a POST Request
-------------------
Making a POST request is very similar to making a GET:
```php
$response = Requests::post('http://httpbin.org/post');
```
You'll probably also want to pass in some data. You can pass in either a
string, an array or an object (Requests uses [`http_build_query`][build_query]
internally) as the third parameter (after the URL and headers):
[build_query]: http://php.net/http_build_query
```php
$data = array('key1' => 'value1', 'key2' => 'value2');
$response = Requests::post('http://httpbin.org/post', array(), $data);
var_dump($response->body);
```
This gives the output:
string(503) "{
"origin": "124.191.162.147",
"files": {},
"form": {
"key2": "value2",
"key1": "value1"
},
"headers": {
"Content-Length": "23",
"Accept-Encoding": "deflate;q=1.0, compress;q=0.5, gzip;q=0.5",
"X-Forwarded-Port": "80",
"Connection": "keep-alive",
"User-Agent": "php-requests/1.6-dev",
"Host": "httpbin.org",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
},
"url": "http://httpbin.org/post",
"args": {},
"data": ""
}"
To send raw data, simply pass in a string instead. You'll probably also want to
set the Content-Type header to ensure the remote server knows what you're
sending it:
```php
$url = 'https://api.github.com/some/endpoint';
$headers = array('Content-Type' => 'application/json');
$data = array('some' => 'data');
$response = Requests::post($url, $headers, json_encode($data));
```
Note that if you don't manually specify a Content-Type header, Requests has
undefined behaviour for the header. It may be set to various values depending
on the internal execution path, so it's recommended to set this explicitly if
you need to.
Status Codes
------------
The Response object also gives you access to the status code:
```php
var_dump($response->status_code);
// int(200)
```
You can also easily check if this status code is a success code, or if it's an
error:
```php
var_dump($response->success);
// bool(true)
```
Response Headers
----------------
We can also grab headers pretty easily:
```php
var_dump($response->headers['Date']);
// string(29) "Thu, 09 Feb 2012 15:22:06 GMT"
```
Note that this is case-insensitive, so the following are all equivalent:
* `$response->headers['Date']`
* `$response->headers['date']`
* `$response->headers['DATE']`
* `$response->headers['dAtE']`
If a header isn't set, this will give `null`. You can also check with
`isset($response->headers['date'])`
@@ -0,0 +1,192 @@
Why Requests Instead of X?
==========================
This is a quick look at why you should use Requests instead of another
solution. Keep in mind though that these are my point of view, and they may not
be issues for you.
As always with software, you should choose what you think is best.
Why should I use Requests?
--------------------------
1. **Designed for maximum compatibility**
The realities of working with widely deployable software mean that awesome
PHP features aren't always available. PHP 5.3, cURL, OpenSSL and more are not
necessarily going to be available on every system. While you're welcome to
require PHP 5.3, 5.4 or even 5.5, it's not our job to force you to use those.
(The WordPress project estimates [about 60%][wpstats] of hosts are running
PHP 5.2, so this is a serious issue for developers working on large
deployable projects.)
Don't worry though, Requests will automatically use better features where
possible, giving you an extra speed boost with cURL.
2. **Simple API**
Requests' API is designed to be able to be learnt in 10 minutes. Everything
from basic requests all the way up to advanced usage involving custom SSL
certificates and stored cookies is handled by a simple API.
Other HTTP libraries optimize for the library developer's time; **Requests
optimizes for your time**.
3. **Thoroughly tested**
Requests is [continuously integrated with Travis][travis] and test coverage
is [constantly monitored with Coveralls][coveralls] to give you confidence in
the library. We aim for test coverage **over 90%** at all times, and new
features require new tests to go along with them. This ensures that you can
be confident in the quality of the code, as well as being able to update to
the latest version of Requests without worrying about compatibility.
4. **Secure by default**
Unlike other HTTP libraries, Requests is secure by default. Requests is the
**first and currently only** standalone HTTP library to
**[fully verify][requests_ssl] all HTTPS requests** even without cURL. We
also bundle the latest root certificate authorities to ensure that your
secure requests are actually secure.
(Of note is that WordPress as of version 3.7 also supports full checking of
the certificates, thanks to [evangelism efforts on our behalf][wpssl].
Together, we are the only HTTP libraries in PHP to fully verify certificates
to the same level as browsers.)
5. **Extensible from the core**
If you need low-level access to Requests' internals, simply plug your
callbacks in via the built-in [hooking system][] and mess around as much as
you want. Requests' simple hooking system is so powerful that both
authentication handlers and cookie support is actually handled internally
with hooks.
[coveralls]: https://coveralls.io/r/rmccue/Requests
[hooking system]: hooks.md
[requests_ssl]: https://github.com/rmccue/Requests/blob/master/library/Requests/SSL.php
[travis]: https://travis-ci.org/rmccue/Requests
[wpssl]: http://core.trac.wordpress.org/ticket/25007
Why shouldn't I use...
----------------------
Requests isn't the first or only HTTP library in PHP and it's important to
acknowledge the other solutions out there. Here's why you should use Requests
instead of something else, in our opinion.
### cURL
1. **Not every host has cURL installed**
cURL is far from being ubiquitous, so you can't rely on it always being
available when distributing software. Anecdotal data collected from various
projects indicates that cURL is available on roughly 90% of hosts, but that
leaves 10% of hosts without it.
2. **cURL's interface sucks**
cURL's interface was designed for PHP 4, and hence uses resources with
horrible functions such as `curl_setopt()`. Combined with that, it uses 229
global constants, polluting the global namespace horribly.
Requests, on the other hand, exposes only a handful of classes to the
global namespace, most of which are for internal use. You can learn to use
the `Requests::request()` method and the `Requests_Response` object in the
space of 10 minutes and you already know how to use Requests.
### Guzzle
1. **Requires cURL and PHP 5.3+**
Guzzle is designed to be a client to fit a large number of installations, but
as a result of optimizing for Guzzle developer time, it uses cURL as an
underlying transport. As noted above, this is a majority of systems, but
far from all.
The same is true for PHP 5.3+. While we'd all love to rely on PHP's newer
features, the fact is that a huge percentage of hosts are still running on
PHP 5.2. (The WordPress project estimates [about 60%][wpstats] of hosts are
running PHP 5.2.)
2. **Not just a HTTP client**
Guzzle is not intended to just be a HTTP client, but rather to be a
full-featured REST client. Requests is just a HTTP client, intentionally. Our
development strategy is to act as a low-level library that REST clients can
easily be built on, not to provide the whole kitchen sink for you.
If you want to rapidly develop a web service client using a framework, Guzzle
will suit you perfectly. On the other hand, if you want a HTTP client without
all the rest, Requests is the way to go.
[wpstats]: http://wordpress.org/about/stats/
### Buzz
1. **Requires PHP 5.3+**
As with Guzzle, while PHP 5.3+ is awesome, you can't always rely on it being
on a host. With widely distributable software, this is a huge problem.
2. **Not transport-transparent**
For making certain types of requests, such as multi-requests, you can't rely
on a high-level abstraction and instead have to use the low-level transports.
This really gains nothing (other than a fancy interface) over just using the
methods directly and means that you can't rely on features to be available.
### fsockopen
1. **Very low-level**
fsockopen is used for working with sockets directly, so it only knows about
the transport layer (TCP in our case), not anything higher (i.e. HTTP on the
application layer). To be able to use fsockopen as a HTTP client, you need
to write all the HTTP code yourself, and once you're done, you'll end up
with something that is almost exactly like Requests.
### PEAR HTTP_Request2
1. **Requires PEAR**
PEAR is (in theory) a great distribution system (with a less than wonderful
implementation), however it is not ubiquitous, as many hosts disable it to
save on space that most people aren't going to use anyway.
PEAR is also a pain for users. Users want to be able to download a zip of
your project without needing to install anything else from PEAR.
(If you really want though, Requests is available via PEAR. Check the README
to see how to grab it.)
2. **Depends on other PEAR utilities**
HTTP\_Request2 requires Net_URL2 in order to function, locking you in to
using PEAR for your project.
Requests is entirely self-contained, and includes all the libraries it needs
(for example, Requests\_IRI is based on ComplexPie\_IRI by Geoffrey Sneddon).
### PECL HttpRequest
1. **Requires a PECL extension**
Similar to PEAR, users aren't big fans of installing extra libraries. Unlike
PEAR though, PECL extensions require compiling, which end users will be
unfamiliar with. In addition, on systems where users do not have full
control over PHP, they will be unable to install custom extensions.
### Zend Framework's Zend\_Http\_Client
1. **Requires other parts of the Zend Framework**
Similar to HTTP_Request2, Zend's client is not fully self-contained and
requires other components from the framework.
@@ -0,0 +1,16 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Now let's make a request!
$options = array(
'auth' => array('someuser', 'password')
);
$request = Requests::get('http://httpbin.org/basic-auth/someuser/password', array(), $options);
// Check what we received
var_dump($request);
@@ -0,0 +1,13 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Now let's make a request!
$request = Requests::get('http://httpbin.org/get', array('Accept' => 'application/json'));
// Check what we received
var_dump($request);
@@ -0,0 +1,45 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Setup what we want to request
$requests = array(
array(
'url' => 'http://httpbin.org/get',
'headers' => array('Accept' => 'application/javascript'),
),
'post' => array(
'url' => 'http://httpbin.org/post',
'data' => array('mydata' => 'something'),
),
'delayed' => array(
'url' => 'http://httpbin.org/delay/10',
'options' => array(
'timeout' => 20,
),
),
);
// Setup a callback
function my_callback(&$request, $id) {
var_dump($id, $request);
}
// Tell Requests to use the callback
$options = array(
'complete' => 'my_callback',
);
// Send the request!
$responses = Requests::request_multiple($requests, $options);
// Note: the response from the above call will be an associative array matching
// $requests with the response data, however we've already handled it in
// my_callback() anyway!
//
// If you don't believe me, uncomment this:
# var_dump($responses);
@@ -0,0 +1,13 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Now let's make a request!
$request = Requests::post('http://httpbin.org/post', array(), array('mydata' => 'something'));
// Check what we received
var_dump($request);
@@ -0,0 +1,18 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Now let's make a request via a proxy.
$options = array(
'proxy' => '127.0.0.1:8080', // syntax: host:port, eg 12.13.14.14:8080 or someproxy.com:3128
// If you need to authenticate, use the following syntax:
// 'proxy' => array( '127.0.0.1:8080', 'username', 'password' ),
);
$request = Requests::get('http://httpbin.org/ip', array(), $options );
// See result
var_dump($request->body);
@@ -0,0 +1,24 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Set up our session
$session = new Requests_Session('http://httpbin.org/');
$session->headers['Accept'] = 'application/json';
$session->useragent = 'Awesomesauce';
// Now let's make a request!
$request = $session->get('/get');
// Check what we received
var_dump($request);
// Let's check our user agent!
$request = $session->get('/user-agent');
// And check again
var_dump($request);
@@ -0,0 +1,17 @@
<?php
// First, include Requests
include('../library/Requests.php');
// Next, make sure Requests can load internal classes
Requests::register_autoloader();
// Define a timeout of 2.5 seconds
$options = array(
'timeout' => 2.5,
);
// Now let's make a request to a page that will delay its response by 3 seconds
$request = Requests::get('http://httpbin.org/delay/3', array(), $options);
// An exception will be thrown, stating a timeout of the request !
@@ -0,0 +1,869 @@
<?php
/**
* Requests for PHP
*
* Inspired by Requests for Python.
*
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
*
* @package Requests
*/
/**
* Requests for PHP
*
* Inspired by Requests for Python.
*
* Based on concepts from SimplePie_File, RequestCore and WP_Http.
*
* @package Requests
*/
class Requests {
/**
* POST method
*
* @var string
*/
const POST = 'POST';
/**
* PUT method
*
* @var string
*/
const PUT = 'PUT';
/**
* GET method
*
* @var string
*/
const GET = 'GET';
/**
* HEAD method
*
* @var string
*/
const HEAD = 'HEAD';
/**
* DELETE method
*
* @var string
*/
const DELETE = 'DELETE';
/**
* PATCH method
*
* @link http://tools.ietf.org/html/rfc5789
* @var string
*/
const PATCH = 'PATCH';
/**
* Current version of Requests
*
* @var string
*/
const VERSION = '1.6';
/**
* Registered transport classes
*
* @var array
*/
protected static $transports = array();
/**
* Selected transport name
*
* Use {@see get_transport()} instead
*
* @var array
*/
public static $transport = array();
/**
* This is a static class, do not instantiate it
*
* @codeCoverageIgnore
*/
private function __construct() {}
/**
* Autoloader for Requests
*
* Register this with {@see register_autoloader()} if you'd like to avoid
* having to create your own.
*
* (You can also use `spl_autoload_register` directly if you'd prefer.)
*
* @codeCoverageIgnore
*
* @param string $class Class name to load
*/
public static function autoloader($class) {
// Check that the class starts with "Requests"
if (strpos($class, 'Requests') !== 0) {
return;
}
$file = str_replace('_', '/', $class);
if (file_exists(dirname(__FILE__) . '/' . $file . '.php')) {
require_once(dirname(__FILE__) . '/' . $file . '.php');
}
}
/**
* Register the built-in autoloader
*
* @codeCoverageIgnore
*/
public static function register_autoloader() {
spl_autoload_register(array('Requests', 'autoloader'));
}
/**
* Register a transport
*
* @param string $transport Transport class to add, must support the Requests_Transport interface
*/
public static function add_transport($transport) {
if (empty(self::$transports)) {
self::$transports = array(
'Requests_Transport_cURL',
'Requests_Transport_fsockopen',
);
}
self::$transports = array_merge(self::$transports, array($transport));
}
/**
* Get a working transport
*
* @throws Requests_Exception If no valid transport is found (`notransport`)
* @return Requests_Transport
*/
protected static function get_transport($capabilities = array()) {
// Caching code, don't bother testing coverage
// @codeCoverageIgnoreStart
// array of capabilities as a string to be used as an array key
ksort($capabilities);
$cap_string = serialize($capabilities);
// Don't search for a transport if it's already been done for these $capabilities
if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {
return new self::$transport[$cap_string]();
}
// @codeCoverageIgnoreEnd
if (empty(self::$transports)) {
self::$transports = array(
'Requests_Transport_cURL',
'Requests_Transport_fsockopen',
);
}
// Find us a working transport
foreach (self::$transports as $class) {
if (!class_exists($class))
continue;
$result = call_user_func(array($class, 'test'), $capabilities);
if ($result) {
self::$transport[$cap_string] = $class;
break;
}
}
if (self::$transport[$cap_string] === null) {
throw new Requests_Exception('No working transports found', 'notransport', self::$transports);
}
return new self::$transport[$cap_string]();
}
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $options
* @return Requests_Response
*/
/**
* Send a GET request
*/
public static function get($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::GET, $options);
}
/**
* Send a HEAD request
*/
public static function head($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::HEAD, $options);
}
/**
* Send a DELETE request
*/
public static function delete($url, $headers = array(), $options = array()) {
return self::request($url, $headers, null, self::DELETE, $options);
}
/**#@-*/
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $data
* @param array $options
* @return Requests_Response
*/
/**
* Send a POST request
*/
public static function post($url, $headers = array(), $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::POST, $options);
}
/**
* Send a PUT request
*/
public static function put($url, $headers = array(), $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::PUT, $options);
}
/**
* Send a PATCH request
*
* Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
* specification recommends that should send an ETag
*
* @link http://tools.ietf.org/html/rfc5789
*/
public static function patch($url, $headers, $data = array(), $options = array()) {
return self::request($url, $headers, $data, self::PATCH, $options);
}
/**#@-*/
/**
* Main interface for HTTP requests
*
* This method initiates a request and sends it via a transport before
* parsing.
*
* The `$options` parameter takes an associative array with the following
* options:
*
* - `timeout`: How long should we wait for a response?
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
* - `connect_timeout`: How long should we wait while trying to connect?
* (float, seconds with a millisecond precision, default: 10, example: 0.01)
* - `useragent`: Useragent to send to the server
* (string, default: php-requests/$version)
* - `follow_redirects`: Should we follow 3xx redirects?
* (boolean, default: true)
* - `redirects`: How many times should we redirect before erroring?
* (integer, default: 10)
* - `blocking`: Should we block processing on this request?
* (boolean, default: true)
* - `filename`: File to stream the body to instead.
* (string|boolean, default: false)
* - `auth`: Authentication handler or array of user/password details to use
* for Basic authentication
* (Requests_Auth|array|boolean, default: false)
* - `proxy`: Proxy details to use for proxy by-passing and authentication
* (Requests_Proxy|array|boolean, default: false)
* - `idn`: Enable IDN parsing
* (boolean, default: true)
* - `transport`: Custom transport. Either a class name, or a
* transport object. Defaults to the first working transport from
* {@see getTransport()}
* (string|Requests_Transport, default: {@see getTransport()})
* - `hooks`: Hooks handler.
* (Requests_Hooker, default: new Requests_Hooks())
* - `verify`: Should we verify SSL certificates? Allows passing in a custom
* certificate file as a string. (Using true uses the system-wide root
* certificate store instead, but this may have different behaviour
* across transports.)
* (string|boolean, default: library/Requests/Transport/cacert.pem)
* - `verifyname`: Should we verify the common name in the SSL certificate?
* (boolean: default, true)
*
* @throws Requests_Exception On invalid URLs (`nonhttp`)
*
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type (use Requests constants)
* @param array $options Options for the request (see description for more information)
* @return Requests_Response
*/
public static function request($url, $headers = array(), $data = array(), $type = self::GET, $options = array()) {
if (empty($options['type'])) {
$options['type'] = $type;
}
$options = array_merge(self::get_default_options(), $options);
self::set_defaults($url, $headers, $data, $type, $options);
$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));
if (!empty($options['transport'])) {
$transport = $options['transport'];
if (is_string($options['transport'])) {
$transport = new $transport();
}
} else {
$need_ssl = (0 === stripos($url, 'https://'));
$capabilities = array('ssl' => $need_ssl);
$transport = self::get_transport($capabilities);
}
$response = $transport->request($url, $headers, $data, $options);
$options['hooks']->dispatch('requests.before_parse', array(&$response, $url, $headers, $data, $type, $options));
return self::parse_response($response, $url, $headers, $data, $options);
}
/**
* Send multiple HTTP requests simultaneously
*
* The `$requests` parameter takes an associative or indexed array of
* request fields. The key of each request can be used to match up the
* request with the returned data, or with the request passed into your
* `multiple.request.complete` callback.
*
* The request fields value is an associative array with the following keys:
*
* - `url`: Request URL Same as the `$url` parameter to
* {@see Requests::request}
* (string, required)
* - `headers`: Associative array of header fields. Same as the `$headers`
* parameter to {@see Requests::request}
* (array, default: `array()`)
* - `data`: Associative array of data fields or a string. Same as the
* `$data` parameter to {@see Requests::request}
* (array|string, default: `array()`)
* - `type`: HTTP request type (use Requests constants). Same as the `$type`
* parameter to {@see Requests::request}
* (string, default: `Requests::GET`)
* - `cookies`: Associative array of cookie name to value, or cookie jar.
* (array|Requests_Cookie_Jar)
*
* If the `$options` parameter is specified, individual requests will
* inherit options from it. This can be used to use a single hooking system,
* or set all the types to `Requests::POST`, for example.
*
* In addition, the `$options` parameter takes the following global options:
*
* - `complete`: A callback for when a request is complete. Takes two
* parameters, a Requests_Response/Requests_Exception reference, and the
* ID from the request array (Note: this can also be overridden on a
* per-request basis, although that's a little silly)
* (callback)
*
* @param array $requests Requests data (see description for more information)
* @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either Requests_Response or a Requests_Exception object)
*/
public static function request_multiple($requests, $options = array()) {
$options = array_merge(self::get_default_options(true), $options);
if (!empty($options['hooks'])) {
$options['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($options['complete'])) {
$options['hooks']->register('multiple.request.complete', $options['complete']);
}
}
foreach ($requests as $id => &$request) {
if (!isset($request['headers'])) {
$request['headers'] = array();
}
if (!isset($request['data'])) {
$request['data'] = array();
}
if (!isset($request['type'])) {
$request['type'] = self::GET;
}
if (!isset($request['options'])) {
$request['options'] = $options;
$request['options']['type'] = $request['type'];
}
else {
if (empty($request['options']['type'])) {
$request['options']['type'] = $request['type'];
}
$request['options'] = array_merge($options, $request['options']);
}
self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);
// Ensure we only hook in once
if ($request['options']['hooks'] !== $options['hooks']) {
$request['options']['hooks']->register('transport.internal.parse_response', array('Requests', 'parse_multiple'));
if (!empty($request['options']['complete'])) {
$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
}
}
}
unset($request);
if (!empty($options['transport'])) {
$transport = $options['transport'];
if (is_string($options['transport'])) {
$transport = new $transport();
}
}
else {
$transport = self::get_transport();
}
$responses = $transport->request_multiple($requests, $options);
foreach ($responses as $id => &$response) {
// If our hook got messed with somehow, ensure we end up with the
// correct response
if (is_string($response)) {
$request = $requests[$id];
self::parse_multiple($response, $request);
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response, $id));
}
}
return $responses;
}
/**
* Get the default options
*
* @see Requests::request() for values returned by this method
* @param boolean $multirequest Is this a multirequest?
* @return array Default option values
*/
protected static function get_default_options($multirequest = false) {
$defaults = array(
'timeout' => 10,
'connect_timeout' => 10,
'useragent' => 'php-requests/' . self::VERSION,
'redirected' => 0,
'redirects' => 10,
'follow_redirects' => true,
'blocking' => true,
'type' => self::GET,
'filename' => false,
'auth' => false,
'proxy' => false,
'cookies' => false,
'idn' => true,
'hooks' => null,
'transport' => null,
'verify' => dirname( __FILE__ ) . '/Requests/Transport/cacert.pem',
'verifyname' => true,
);
if ($multirequest !== false) {
$defaults['complete'] = null;
}
return $defaults;
}
/**
* Set the default values
*
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type
* @param array $options Options for the request
* @return array $options
*/
protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
throw new Requests_Exception('Only HTTP requests are handled.', 'nonhttp', $url);
}
if (empty($options['hooks'])) {
$options['hooks'] = new Requests_Hooks();
}
if (is_array($options['auth'])) {
$options['auth'] = new Requests_Auth_Basic($options['auth']);
}
if ($options['auth'] !== false) {
$options['auth']->register($options['hooks']);
}
if (!empty($options['proxy'])) {
$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
}
if ($options['proxy'] !== false) {
$options['proxy']->register($options['hooks']);
}
if (is_array($options['cookies'])) {
$options['cookies'] = new Requests_Cookie_Jar($options['cookies']);
}
elseif (empty($options['cookies'])) {
$options['cookies'] = new Requests_Cookie_Jar();
}
if ($options['cookies'] !== false) {
$options['cookies']->register($options['hooks']);
}
if ($options['idn'] !== false) {
$iri = new Requests_IRI($url);
$iri->host = Requests_IDNAEncoder::encode($iri->ihost);
$url = $iri->uri;
}
}
/**
* HTTP response parser
*
* @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)
* @throws Requests_Exception On missing head/body separator (`noversion`)
* @throws Requests_Exception On missing head/body separator (`toomanyredirects`)
*
* @param string $headers Full response text including headers and body
* @param string $url Original request URL
* @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
* @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
* @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
* @return Requests_Response
*/
protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
$return = new Requests_Response();
if (!$options['blocking']) {
return $return;
}
$return->raw = $headers;
$return->url = $url;
if (!$options['filename']) {
if (($pos = strpos($headers, "\r\n\r\n")) === false) {
// Crap!
throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
}
$headers = substr($return->raw, 0, $pos);
$return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));
}
else {
$return->body = '';
}
// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
$headers = str_replace("\r\n", "\n", $headers);
// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
$headers = preg_replace('/\n[ \t]/', ' ', $headers);
$headers = explode("\n", $headers);
preg_match('#^HTTP/1\.\d[ \t]+(\d+)#i', array_shift($headers), $matches);
if (empty($matches)) {
throw new Requests_Exception('Response could not be parsed', 'noversion', $headers);
}
$return->status_code = (int) $matches[1];
if ($return->status_code >= 200 && $return->status_code < 300) {
$return->success = true;
}
foreach ($headers as $header) {
list($key, $value) = explode(':', $header, 2);
$value = trim($value);
preg_replace('#(\s+)#i', ' ', $value);
$return->headers[$key] = $value;
}
if (isset($return->headers['transfer-encoding'])) {
$return->body = self::decode_chunked($return->body);
unset($return->headers['transfer-encoding']);
}
if (isset($return->headers['content-encoding'])) {
$return->body = self::decompress($return->body);
}
//fsockopen and cURL compatibility
if (isset($return->headers['connection'])) {
unset($return->headers['connection']);
}
$options['hooks']->dispatch('requests.before_redirect_check', array(&$return, $req_headers, $req_data, $options));
if ((in_array($return->status_code, array(300, 301, 302, 303, 307)) || $return->status_code > 307 && $return->status_code < 400) && $options['follow_redirects'] === true) {
if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
if ($return->status_code === 303) {
$options['type'] = Requests::GET;
}
$options['redirected']++;
$location = $return->headers['location'];
if (strpos ($location, 'http://') !== 0 && strpos ($location, 'https://') !== 0) {
// relative redirect, for compatibility make it absolute
$location = Requests_IRI::absolutize($url, $location);
$location = $location->uri;
}
$redirected = self::request($location, $req_headers, $req_data, false, $options);
$redirected->history[] = $return;
return $redirected;
}
elseif ($options['redirected'] >= $options['redirects']) {
throw new Requests_Exception('Too many redirects', 'toomanyredirects', $return);
}
}
$return->redirects = $options['redirected'];
$options['hooks']->dispatch('requests.after_request', array(&$return, $req_headers, $req_data, $options));
return $return;
}
/**
* Callback for `transport.internal.parse_response`
*
* Internal use only. Converts a raw HTTP response to a Requests_Response
* while still executing a multiple request.
*
* @param string $headers Full response text including headers and body
* @param array $request Request data as passed into {@see Requests::request_multiple()}
* @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
*/
public static function parse_multiple(&$response, $request) {
try {
$response = self::parse_response($response, $request['url'], $request['headers'], $request['data'], $request['options']);
}
catch (Requests_Exception $e) {
$response = $e;
}
}
/**
* Decoded a chunked body as per RFC 2616
*
* @see http://tools.ietf.org/html/rfc2616#section-3.6.1
* @param string $data Chunked body
* @return string Decoded body
*/
protected static function decode_chunked($data) {
if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($data))) {
return $data;
}
$decoded = '';
$encoded = $data;
while (true) {
$is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches );
if (!$is_chunked) {
// Looks like it's not chunked after all
return $data;
}
$length = hexdec(trim($matches[1]));
if ($length === 0) {
// Ignore trailer headers
return $decoded;
}
$chunk_length = strlen($matches[0]);
$decoded .= $part = substr($encoded, $chunk_length, $length);
$encoded = substr($encoded, $chunk_length + $length + 2);
if (trim($encoded) === '0' || empty($encoded)) {
return $decoded;
}
}
// We'll never actually get down here
// @codeCoverageIgnoreStart
}
// @codeCoverageIgnoreEnd
/**
* Convert a key => value array to a 'key: value' array for headers
*
* @param array $array Dictionary of header values
* @return array List of headers
*/
public static function flatten($array) {
$return = array();
foreach ($array as $key => $value) {
$return[] = "$key: $value";
}
return $return;
}
/**
* Convert a key => value array to a 'key: value' array for headers
*
* @deprecated Misspelling of {@see Requests::flatten}
* @param array $array Dictionary of header values
* @return array List of headers
*/
public static function flattern($array) {
return self::flatten($array);
}
/**
* Decompress an encoded body
*
* Implements gzip, compress and deflate. Guesses which it is by attempting
* to decode.
*
* @todo Make this smarter by defaulting to whatever the headers say first
* @param string $data Compressed data in one of the above formats
* @return string Decompressed string
*/
public static function decompress($data) {
if (substr($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
// Not actually compressed. Probably cURL ruining this for us.
return $data;
}
if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {
return $decoded;
}
elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
return $decoded;
}
elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
return $decoded;
}
elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== false) {
return $decoded;
}
return $data;
}
/**
* Decompression of deflated string while staying compatible with the majority of servers.
*
* Certain Servers will return deflated data with headers which PHP's gzinflate()
* function cannot handle out of the box. The following function has been created from
* various snippets on the gzinflate() PHP documentation.
*
* Warning: Magic numbers within. Due to the potential different formats that the compressed
* data may be returned in, some "magic offsets" are needed to ensure proper decompression
* takes place. For a simple progmatic way to determine the magic offset in use, see:
* http://core.trac.wordpress.org/ticket/18273
*
* @since 2.8.1
* @link http://core.trac.wordpress.org/ticket/18273
* @link http://au2.php.net/manual/en/function.gzinflate.php#70875
* @link http://au2.php.net/manual/en/function.gzinflate.php#77336
*
* @param string $gzData String to decompress.
* @return string|bool False on failure.
*/
public static function compatible_gzinflate($gzData) {
// Compressed data might contain a full zlib header, if so strip it for
// gzinflate()
if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
$i = 10;
$flg = ord( substr($gzData, 3, 1) );
if ( $flg > 0 ) {
if ( $flg & 4 ) {
list($xlen) = unpack('v', substr($gzData, $i, 2) );
$i = $i + 2 + $xlen;
}
if ( $flg & 8 )
$i = strpos($gzData, "\0", $i) + 1;
if ( $flg & 16 )
$i = strpos($gzData, "\0", $i) + 1;
if ( $flg & 2 )
$i = $i + 2;
}
$decompressed = self::compatible_gzinflate( substr( $gzData, $i ) );
if ( false !== $decompressed ) {
return $decompressed;
}
}
// If the data is Huffman Encoded, we must first strip the leading 2
// byte Huffman marker for gzinflate()
// The response is Huffman coded by many compressors such as
// java.util.zip.Deflater, Rubys Zlib::Deflate, and .NET's
// System.IO.Compression.DeflateStream.
//
// See http://decompres.blogspot.com/ for a quick explanation of this
// data type
$huffman_encoded = false;
// low nibble of first byte should be 0x08
list( , $first_nibble ) = unpack( 'h', $gzData );
// First 2 bytes should be divisible by 0x1F
list( , $first_two_bytes ) = unpack( 'n', $gzData );
if ( 0x08 == $first_nibble && 0 == ( $first_two_bytes % 0x1F ) )
$huffman_encoded = true;
if ( $huffman_encoded ) {
if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 2 ) ) ) )
return $decompressed;
}
if ( "\x50\x4b\x03\x04" == substr( $gzData, 0, 4 ) ) {
// ZIP file format header
// Offset 6: 2 bytes, General-purpose field
// Offset 26: 2 bytes, filename length
// Offset 28: 2 bytes, optional field length
// Offset 30: Filename field, followed by optional field, followed
// immediately by data
list( , $general_purpose_flag ) = unpack( 'v', substr( $gzData, 6, 2 ) );
// If the file has been compressed on the fly, 0x08 bit is set of
// the general purpose field. We can use this to differentiate
// between a compressed document, and a ZIP file
$zip_compressed_on_the_fly = ( 0x08 == (0x08 & $general_purpose_flag ) );
if ( ! $zip_compressed_on_the_fly ) {
// Don't attempt to decode a compressed zip file
return $gzData;
}
// Determine the first byte of data, based on the above ZIP header
// offsets:
$first_file_start = array_sum( unpack( 'v2', substr( $gzData, 26, 4 ) ) );
if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 30 + $first_file_start ) ) ) ) {
return $decompressed;
}
return false;
}
// Finally fall back to straight gzinflate
if ( false !== ( $decompressed = @gzinflate( $gzData ) ) ) {
return $decompressed;
}
// Fallback for all above failing, not expected, but included for
// debugging and preventing regressions and to track stats
if ( false !== ( $decompressed = @gzinflate( substr( $gzData, 2 ) ) ) ) {
return $decompressed;
}
return false;
}
public static function match_domain($host, $reference) {
// Check for a direct match
if ($host === $reference) {
return true;
}
// Calculate the valid wildcard match if the host is not an IP address
// Also validates that the host has 3 parts or more, as per Firefox's
// ruleset.
$parts = explode('.', $host);
if (ip2long($host) === false && count($parts) >= 3) {
$parts[0] = '*';
$wildcard = implode('.', $parts);
if ($wildcard === $reference) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Authentication provider interface
*
* @package Requests
* @subpackage Authentication
*/
/**
* Authentication provider interface
*
* Implement this interface to act as an authentication provider.
*
* Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider.
*
* @see Requests_Hooks
* @package Requests
* @subpackage Authentication
*/
interface Requests_Auth {
/**
* Register hooks as needed
*
* This method is called in {@see Requests::request} when the user has set
* an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need.
*
* @see Requests_Hooks::register
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks);
}
@@ -0,0 +1,88 @@
<?php
/**
* Basic Authentication provider
*
* @package Requests
* @subpackage Authentication
*/
/**
* Basic Authentication provider
*
* Provides a handler for Basic HTTP authentication via the Authorization
* header.
*
* @package Requests
* @subpackage Authentication
*/
class Requests_Auth_Basic implements Requests_Auth {
/**
* Username
*
* @var string
*/
public $user;
/**
* Password
*
* @var string
*/
public $pass;
/**
* Constructor
*
* @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|null $args Array of user and password. Must have exactly two elements
*/
public function __construct($args = null) {
if (is_array($args)) {
if (count($args) !== 2) {
throw new Requests_Exception('Invalid number of arguments', 'authbasicbadargs');
}
list($this->user, $this->pass) = $args;
}
}
/**
* Register the necessary callbacks
*
* @see curl_before_send
* @see fsockopen_header
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks) {
$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
}
/**
* Set cURL parameters before the data is sent
*
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
}
/**
* Add extra headers to the request before sending
*
* @param string $out HTTP header string
*/
public function fsockopen_header(&$out) {
$out .= "Authorization: Basic " . base64_encode($this->getAuthString()) . "\r\n";
}
/**
* Get the authentication string (user:pass)
*
* @return string
*/
public function getAuthString() {
return $this->user . ':' . $this->pass;
}
}
@@ -0,0 +1,374 @@
<?php
/**
* Cookie storage object
*
* @package Requests
* @subpackage Cookies
*/
/**
* Cookie storage object
*
* @package Requests
* @subpackage Cookies
*/
class Requests_Cookie {
/**
*
* @var string
*/
public $name;
/**
* @var string
*/
public $value;
/**
* Cookie attributes
*
* Valid keys are (currently) path, domain, expires, max-age, secure and
* httponly.
*
* @var array
*/
public $attributes = array();
/**
* Cookie flags
*
* Valid keys are (currently) creation, last-access, persistent and
* host-only.
*
* @var array
*/
public $flags = array();
/**
* Create a new cookie object
*
* @param string $name
* @param string $value
* @param array $attributes Associative array of attribute data
*/
public function __construct($name, $value, $attributes = array(), $flags = array()) {
$this->name = $name;
$this->value = $value;
$this->attributes = $attributes;
$default_flags = array(
'creation' => time(),
'last-access' => time(),
'persistent' => false,
'host-only' => true,
);
$this->flags = array_merge($default_flags, $flags);
$this->normalize();
}
/**
* Check if a cookie is valid for a given URI
*
* @param Requests_IRI $uri URI to check
* @return boolean Whether the cookie is valid for the given URI
*/
public function uriMatches(Requests_IRI $uri) {
if (!$this->domainMatches($uri->host)) {
return false;
}
if (!$this->pathMatches($uri->path)) {
return false;
}
if (!empty($this->attributes['secure']) && $uri->scheme !== 'https') {
return false;
}
return true;
}
/**
* Check if a cookie is valid for a given domain
*
* @param string $string Domain to check
* @return boolean Whether the cookie is valid for the given domain
*/
public function domainMatches($string) {
if (!isset($this->attributes['domain'])) {
// Cookies created manually; cookies created by Requests will set
// the domain to the requested domain
return true;
}
$domain_string = $this->attributes['domain'];
if ($domain_string === $string) {
// The domain string and the string are identical.
return true;
}
// If the cookie is marked as host-only and we don't have an exact
// match, reject the cookie
if ($this->flags['host-only'] === true) {
return false;
}
if (strlen($string) <= strlen($domain_string)) {
// For obvious reasons, the string cannot be a suffix if the domain
// is shorter than the domain string
return false;
}
if (substr($string, -1 * strlen($domain_string)) !== $domain_string) {
// The domain string should be a suffix of the string.
return false;
}
$prefix = substr($string, 0, strlen($string) - strlen($domain_string));
if (substr($prefix, -1) !== '.') {
// The last character of the string that is not included in the
// domain string should be a %x2E (".") character.
return false;
}
if (preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $string)) {
// The string should be a host name (i.e., not an IP address).
return false;
}
return true;
}
/**
* Check if a cookie is valid for a given path
*
* From the path-match check in RFC 6265 section 5.1.4
*
* @param string $request_path Path to check
* @return boolean Whether the cookie is valid for the given path
*/
public function pathMatches($request_path) {
if (empty($request_path)) {
// Normalize empty path to root
$request_path = '/';
}
if (!isset($this->attributes['path'])) {
// Cookies created manually; cookies created by Requests will set
// the path to the requested path
return true;
}
$cookie_path = $this->attributes['path'];
if ($cookie_path === $request_path) {
// The cookie-path and the request-path are identical.
return true;
}
if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) {
if (substr($cookie_path, -1) === '/') {
// The cookie-path is a prefix of the request-path, and the last
// character of the cookie-path is %x2F ("/").
return true;
}
if (substr($request_path, strlen($cookie_path), 1) === '/') {
// The cookie-path is a prefix of the request-path, and the
// first character of the request-path that is not included in
// the cookie-path is a %x2F ("/") character.
return true;
}
}
return false;
}
/**
* Normalize cookie and attributes
*
* @return boolean Whether the cookie was successfully normalized
*/
public function normalize() {
foreach ($this->attributes as $key => $value) {
$orig_value = $value;
switch ($key) {
case 'domain':
// Domain normalization, as per RFC 6265 section 5.2.3
if ($value[0] === '.') {
$value = substr($value, 1);
}
break;
}
if ($value !== $orig_value) {
$this->attributes[$key] = $value;
}
}
return true;
}
/**
* Format a cookie for a Cookie header
*
* This is used when sending cookies to a server.
*
* @return string Cookie formatted for Cookie header
*/
public function formatForHeader() {
return sprintf('%s=%s', $this->name, $this->value);
}
/**
* Format a cookie for a Set-Cookie header
*
* This is used when sending cookies to clients. This isn't really
* applicable to client-side usage, but might be handy for debugging.
*
* @return string Cookie formatted for Set-Cookie header
*/
public function formatForSetCookie() {
$header_value = $this->formatForHeader();
if (!empty($this->attributes)) {
$parts = array();
foreach ($this->attributes as $key => $value) {
// Ignore non-associative attributes
if (is_numeric($key)) {
$parts[] = $value;
}
else {
$parts[] = sprintf('%s=%s', $key, $value);
}
}
$header_value .= '; ' . implode('; ', $parts);
}
return $header_value;
}
/**
* Get the cookie value
*
* Attributes and other data can be accessed via methods.
*/
public function __toString() {
return $this->value;
}
/**
* Parse a cookie string into a cookie object
*
* Based on Mozilla's parsing code in Firefox and related projects, which
* is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
* specifies some of this handling, but not in a thorough manner.
*
* @param string Cookie header value (from a Set-Cookie header)
* @return Requests_Cookie Parsed cookie object
*/
public static function parse($string, $name = '') {
$parts = explode(';', $string);
$kvparts = array_shift($parts);
if (!empty($name)) {
$value = $string;
}
elseif (strpos($kvparts, '=') === false) {
// Some sites might only have a value without the equals separator.
// Deviate from RFC 6265 and pretend it was actually a blank name
// (`=foo`)
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=169091
$name = '';
$value = $kvparts;
}
else {
list($name, $value) = explode('=', $kvparts, 2);
}
$name = trim($name);
$value = trim($value);
// Attribute key are handled case-insensitively
$attributes = new Requests_Utility_CaseInsensitiveDictionary();
if (!empty($parts)) {
foreach ($parts as $part) {
if (strpos($part, '=') === false) {
$part_key = $part;
$part_value = true;
}
else {
list($part_key, $part_value) = explode('=', $part, 2);
$part_value = trim($part_value);
}
$part_key = trim($part_key);
$attributes[$part_key] = $part_value;
}
}
return new Requests_Cookie($name, $value, $attributes);
}
/**
* Parse all Set-Cookie headers from request headers
*
* @param Requests_Response_Headers $headers
* @return array
*/
public static function parseFromHeaders(Requests_Response_Headers $headers, Requests_IRI $origin = null) {
$cookie_headers = $headers->getValues('Set-Cookie');
if (empty($cookie_headers)) {
return array();
}
$cookies = array();
foreach ($cookie_headers as $header) {
$parsed = self::parse($header);
// Default domain/path attributes
if (empty($parsed->attributes['domain']) && !empty($origin)) {
$parsed->attributes['domain'] = $origin->host;
$parsed->flags['host-only'] = false;
}
else {
$parsed->flags['host-only'] = true;
}
$path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
if (!$path_is_valid && !empty($origin)) {
$path = $origin->path;
// Default path normalization as per RFC 6265 section 5.1.4
if (substr($path, 0, 1) !== '/') {
// If the uri-path is empty or if the first character of
// the uri-path is not a %x2F ("/") character, output
// %x2F ("/") and skip the remaining steps.
$path = '/';
}
elseif (substr_count($path, '/') === 1) {
// If the uri-path contains no more than one %x2F ("/")
// character, output %x2F ("/") and skip the remaining
// step.
$path = '/';
}
else {
// Output the characters of the uri-path from the first
// character up to, but not including, the right-most
// %x2F ("/").
$path = substr($path, 0, strrpos($path, '/'));
}
$parsed->attributes['path'] = $path;
}
// Reject invalid cookie domains
if (!$parsed->domainMatches($origin->host)) {
continue;
}
$cookies[$parsed->name] = $parsed;
}
return $cookies;
}
}
@@ -0,0 +1,158 @@
<?php
/**
* Cookie holder object
*
* @package Requests
* @subpackage Cookies
*/
/**
* Cookie holder object
*
* @package Requests
* @subpackage Cookies
*/
class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
/**
* Actual item data
*
* @var array
*/
protected $cookies = array();
/**
* Create a new jar
*
* @param array $cookies Existing cookie values
*/
public function __construct($cookies = array()) {
$this->cookies = $cookies;
}
/**
* Normalise cookie data into a Requests_Cookie
*
* @param string|Requests_Cookie $cookie
* @return Requests_Cookie
*/
public function normalizeCookie($cookie, $key = null) {
if ($cookie instanceof Requests_Cookie) {
return $cookie;
}
return Requests_Cookie::parse($cookie, $key);
}
/**
* Check if the given item exists
*
* @param string $key Item key
* @return boolean Does the item exist?
*/
public function offsetExists($key) {
return isset($this->cookies[$key]);
}
/**
* Get the value for the item
*
* @param string $key Item key
* @return string Item value
*/
public function offsetGet($key) {
if (!isset($this->cookies[$key]))
return null;
return $this->cookies[$key];
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$this->cookies[$key] = $value;
}
/**
* Unset the given header
*
* @param string $key
*/
public function offsetUnset($key) {
unset($this->cookies[$key]);
}
/**
* Get an iterator for the data
*
* @return ArrayIterator
*/
public function getIterator() {
return new ArrayIterator($this->cookies);
}
/**
* Register the cookie handler with the request's hooking system
*
* @param Requests_Hooker $hooks Hooking system
*/
public function register(Requests_Hooker $hooks) {
$hooks->register('requests.before_request', array($this, 'before_request'));
$hooks->register('requests.before_redirect_check', array($this, 'before_redirect_check'));
}
/**
* Add Cookie header to a request if we have any
*
* As per RFC 6265, cookies are separated by '; '
*
* @param string $url
* @param array $headers
* @param array $data
* @param string $type
* @param array $options
*/
public function before_request($url, &$headers, &$data, &$type, &$options) {
if ( ! $url instanceof Requests_IRI ) {
$url = new Requests_IRI($url);
}
if (!empty($this->cookies)) {
$cookies = array();
foreach ($this->cookies as $key => $cookie) {
$cookie = $this->normalizeCookie($cookie, $key);
if ( $cookie->domainMatches( $url->host ) ) {
$cookies[] = $cookie->formatForHeader();
}
}
$headers['Cookie'] = implode('; ', $cookies);
}
}
/**
* Parse all cookies from a response and attach them to the response
*
* @var Requests_Response $response
*/
public function before_redirect_check(Requests_Response &$return) {
$url = $return->url;
if ( ! $url instanceof Requests_IRI ) {
$url = new Requests_IRI($url);
}
$cookies = Requests_Cookie::parseFromHeaders($return->headers, $url);
$this->cookies = array_merge($this->cookies, $cookies);
$return->cookies = $this;
}
}
@@ -0,0 +1,62 @@
<?php
/**
* Exception for HTTP requests
*
* @package Requests
*/
/**
* Exception for HTTP requests
*
* @package Requests
*/
class Requests_Exception extends Exception {
/**
* Type of exception
*
* @var string
*/
protected $type;
/**
* Data associated with the exception
*
* @var mixed
*/
protected $data;
/**
* Create a new exception
*
* @param string $message Exception message
* @param string $type Exception type
* @param mixed $data Associated data
* @param integer $code Exception numerical code, if applicable
*/
public function __construct($message, $type, $data = null, $code = 0) {
parent::__construct($message, $code);
$this->type = $type;
$this->data = $data;
}
/**
* Like {@see getCode()}, but a string code.
*
* @codeCoverageIgnore
* @return string
*/
public function getType() {
return $this->type;
}
/**
* Gives any relevant data
*
* @codeCoverageIgnore
* @return mixed
*/
public function getData() {
return $this->data;
}
}
@@ -0,0 +1,67 @@
<?php
/**
* Exception based on HTTP response
*
* @package Requests
*/
/**
* Exception based on HTTP response
*
* @package Requests
*/
class Requests_Exception_HTTP extends Requests_Exception {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 0;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unknown';
/**
* Create a new exception
*
* There is no mechanism to pass in the status code, as this is set by the
* subclass used. Reason phrases can vary, however.
*
* @param string $reason Reason phrase
* @param mixed $data Associated data
*/
public function __construct($reason = null, $data = null) {
if ($reason !== null) {
$this->reason = $reason;
}
$message = sprintf('%d %s', $this->code, $this->reason);
parent::__construct($message, 'httpresponse', $data, $this->code);
}
/**
* Get the status message
*/
public function getReason() {
return $this->reason;
}
/**
* Get the correct exception class for a given error code
*
* @param int $code HTTP status code
* @return string Exception class name to use
*/
public static function get_class($code) {
$class = sprintf('Requests_Exception_HTTP_%d', $code);
if (class_exists($class)) {
return $class;
}
return 'Requests_Exception_HTTP_Unknown';
}
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 400 Bad Request responses
*
* @package Requests
*/
/**
* Exception for 400 Bad Request responses
*
* @package Requests
*/
class Requests_Exception_HTTP_400 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 400;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Bad Request';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 401 Unauthorized responses
*
* @package Requests
*/
/**
* Exception for 401 Unauthorized responses
*
* @package Requests
*/
class Requests_Exception_HTTP_401 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 401;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unauthorized';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 402 Payment Required responses
*
* @package Requests
*/
/**
* Exception for 402 Payment Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_402 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 402;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Payment Required';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 403 Forbidden responses
*
* @package Requests
*/
/**
* Exception for 403 Forbidden responses
*
* @package Requests
*/
class Requests_Exception_HTTP_403 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 403;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Forbidden';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 404 Not Found responses
*
* @package Requests
*/
/**
* Exception for 404 Not Found responses
*
* @package Requests
*/
class Requests_Exception_HTTP_404 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 404;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Found';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 405 Method Not Allowed responses
*
* @package Requests
*/
/**
* Exception for 405 Method Not Allowed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_405 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 405;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Method Not Allowed';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 406 Not Acceptable responses
*
* @package Requests
*/
/**
* Exception for 406 Not Acceptable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_406 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 406;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Acceptable';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 407 Proxy Authentication Required responses
*
* @package Requests
*/
/**
* Exception for 407 Proxy Authentication Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_407 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 407;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Proxy Authentication Required';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 408 Request Timeout responses
*
* @package Requests
*/
/**
* Exception for 408 Request Timeout responses
*
* @package Requests
*/
class Requests_Exception_HTTP_408 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 408;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Timeout';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 409 Conflict responses
*
* @package Requests
*/
/**
* Exception for 409 Conflict responses
*
* @package Requests
*/
class Requests_Exception_HTTP_409 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 409;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Conflict';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 410 Gone responses
*
* @package Requests
*/
/**
* Exception for 410 Gone responses
*
* @package Requests
*/
class Requests_Exception_HTTP_410 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 410;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Gone';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 411 Length Required responses
*
* @package Requests
*/
/**
* Exception for 411 Length Required responses
*
* @package Requests
*/
class Requests_Exception_HTTP_411 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 411;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Length Required';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 412 Precondition Failed responses
*
* @package Requests
*/
/**
* Exception for 412 Precondition Failed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_412 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 412;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Precondition Failed';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 413 Request Entity Too Large responses
*
* @package Requests
*/
/**
* Exception for 413 Request Entity Too Large responses
*
* @package Requests
*/
class Requests_Exception_HTTP_413 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 413;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Entity Too Large';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 414 Request-URI Too Large responses
*
* @package Requests
*/
/**
* Exception for 414 Request-URI Too Large responses
*
* @package Requests
*/
class Requests_Exception_HTTP_414 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 414;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request-URI Too Large';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 415 Unsupported Media Type responses
*
* @package Requests
*/
/**
* Exception for 415 Unsupported Media Type responses
*
* @package Requests
*/
class Requests_Exception_HTTP_415 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 415;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unsupported Media Type';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 416 Requested Range Not Satisfiable responses
*
* @package Requests
*/
/**
* Exception for 416 Requested Range Not Satisfiable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_416 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 416;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Requested Range Not Satisfiable';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 417 Expectation Failed responses
*
* @package Requests
*/
/**
* Exception for 417 Expectation Failed responses
*
* @package Requests
*/
class Requests_Exception_HTTP_417 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 417;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Expectation Failed';
}
@@ -0,0 +1,29 @@
<?php
/**
* Exception for 418 I'm A Teapot responses
*
* @see http://tools.ietf.org/html/rfc2324
* @package Requests
*/
/**
* Exception for 418 I'm A Teapot responses
*
* @see http://tools.ietf.org/html/rfc2324
* @package Requests
*/
class Requests_Exception_HTTP_418 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 418;
/**
* Reason phrase
*
* @var string
*/
protected $reason = "I'm A Teapot";
}
@@ -0,0 +1,29 @@
<?php
/**
* Exception for 428 Precondition Required responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 428 Precondition Required responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_428 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 428;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Precondition Required';
}
@@ -0,0 +1,29 @@
<?php
/**
* Exception for 429 Too Many Requests responses
*
* @see http://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
/**
* Exception for 429 Too Many Requests responses
*
* @see http://tools.ietf.org/html/draft-nottingham-http-new-status-04
* @package Requests
*/
class Requests_Exception_HTTP_429 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 429;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Too Many Requests';
}
@@ -0,0 +1,29 @@
<?php
/**
* Exception for 431 Request Header Fields Too Large responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 431 Request Header Fields Too Large responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_431 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 431;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Request Header Fields Too Large';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 500 Internal Server Error responses
*
* @package Requests
*/
/**
* Exception for 500 Internal Server Error responses
*
* @package Requests
*/
class Requests_Exception_HTTP_500 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 500;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Internal Server Error';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 501 Not Implemented responses
*
* @package Requests
*/
/**
* Exception for 501 Not Implemented responses
*
* @package Requests
*/
class Requests_Exception_HTTP_501 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 501;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Not Implemented';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 502 Bad Gateway responses
*
* @package Requests
*/
/**
* Exception for 502 Bad Gateway responses
*
* @package Requests
*/
class Requests_Exception_HTTP_502 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 502;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Bad Gateway';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 503 Service Unavailable responses
*
* @package Requests
*/
/**
* Exception for 503 Service Unavailable responses
*
* @package Requests
*/
class Requests_Exception_HTTP_503 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 503;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Service Unavailable';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 504 Gateway Timeout responses
*
* @package Requests
*/
/**
* Exception for 504 Gateway Timeout responses
*
* @package Requests
*/
class Requests_Exception_HTTP_504 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 504;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Gateway Timeout';
}
@@ -0,0 +1,27 @@
<?php
/**
* Exception for 505 HTTP Version Not Supported responses
*
* @package Requests
*/
/**
* Exception for 505 HTTP Version Not Supported responses
*
* @package Requests
*/
class Requests_Exception_HTTP_505 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 505;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'HTTP Version Not Supported';
}
@@ -0,0 +1,29 @@
<?php
/**
* Exception for 511 Network Authentication Required responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
/**
* Exception for 511 Network Authentication Required responses
*
* @see http://tools.ietf.org/html/rfc6585
* @package Requests
*/
class Requests_Exception_HTTP_511 extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 511;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Network Authentication Required';
}
@@ -0,0 +1,44 @@
<?php
/**
* Exception for unknown status responses
*
* @package Requests
*/
/**
* Exception for unknown status responses
*
* @package Requests
*/
class Requests_Exception_HTTP_Unknown extends Requests_Exception_HTTP {
/**
* HTTP status code
*
* @var integer
*/
protected $code = 0;
/**
* Reason phrase
*
* @var string
*/
protected $reason = 'Unknown';
/**
* Create a new exception
*
* If `$data` is an instance of {@see Requests_Response}, uses the status
* code from it. Otherwise, sets as 0
*
* @param string $reason Reason phrase
* @param mixed $data Associated data
*/
public function __construct($reason = null, $data = null) {
if ($data instanceof Requests_Response) {
$this->code = $data->status_code;
}
parent::__construct($reason, $data);
}
}
@@ -0,0 +1,33 @@
<?php
/**
* Event dispatcher
*
* @package Requests
* @subpackage Utilities
*/
/**
* Event dispatcher
*
* @package Requests
* @subpackage Utilities
*/
interface Requests_Hooker {
/**
* Register a callback for a hook
*
* @param string $hook Hook name
* @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
*/
public function register($hook, $callback, $priority = 0);
/**
* Dispatch a message
*
* @param string $hook Hook name
* @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness
*/
public function dispatch($hook, $parameters = array());
}
@@ -0,0 +1,61 @@
<?php
/**
* Handles adding and dispatching events
*
* @package Requests
* @subpackage Utilities
*/
/**
* Handles adding and dispatching events
*
* @package Requests
* @subpackage Utilities
*/
class Requests_Hooks implements Requests_Hooker {
/**
* Constructor
*/
public function __construct() {
// pass
}
/**
* Register a callback for a hook
*
* @param string $hook Hook name
* @param callback $callback Function/method to call on event
* @param int $priority Priority number. <0 is executed earlier, >0 is executed later
*/
public function register($hook, $callback, $priority = 0) {
if (!isset($this->hooks[$hook])) {
$this->hooks[$hook] = array();
}
if (!isset($this->hooks[$hook][$priority])) {
$this->hooks[$hook][$priority] = array();
}
$this->hooks[$hook][$priority][] = $callback;
}
/**
* Dispatch a message
*
* @param string $hook Hook name
* @param array $parameters Parameters to pass to callbacks
* @return boolean Successfulness
*/
public function dispatch($hook, $parameters = array()) {
if (empty($this->hooks[$hook])) {
return false;
}
foreach ($this->hooks[$hook] as $priority => $hooked) {
foreach ($hooked as $callback) {
call_user_func_array($callback, $parameters);
}
}
return true;
}
}
@@ -0,0 +1,390 @@
<?php
/**
* IDNA URL encoder
*
* Note: Not fully compliant, as nameprep does nothing yet.
*
* @package Requests
* @subpackage Utilities
* @see http://tools.ietf.org/html/rfc3490 IDNA specification
* @see http://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
*/
class Requests_IDNAEncoder {
/**
* ACE prefix used for IDNA
*
* @see http://tools.ietf.org/html/rfc3490#section-5
* @var string
*/
const ACE_PREFIX = 'xn--';
/**#@+
* Bootstrap constant for Punycode
*
* @see http://tools.ietf.org/html/rfc3492#section-5
* @var int
*/
const BOOTSTRAP_BASE = 36;
const BOOTSTRAP_TMIN = 1;
const BOOTSTRAP_TMAX = 26;
const BOOTSTRAP_SKEW = 38;
const BOOTSTRAP_DAMP = 700;
const BOOTSTRAP_INITIAL_BIAS = 72;
const BOOTSTRAP_INITIAL_N = 128;
/**#@-*/
/**
* Encode a hostname using Punycode
*
* @param string $string Hostname
* @return string Punycode-encoded hostname
*/
public static function encode($string) {
$parts = explode('.', $string);
foreach ($parts as &$part) {
$part = self::to_ascii($part);
}
return implode('.', $parts);
}
/**
* Convert a UTF-8 string to an ASCII string using Punycode
*
* @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
* @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
* @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
* @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
*
* @param string $string ASCII or UTF-8 string (max length 64 characters)
* @return string ASCII string
*/
public static function to_ascii($string) {
// Step 1: Check if the string is already ASCII
if (self::is_ascii($string)) {
// Skip to step 7
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Provided string is too long', 'idna.provided_too_long', $string);
}
// Step 2: nameprep
$string = self::nameprep($string);
// Step 3: UseSTD3ASCIIRules is false, continue
// Step 4: Check if it's ASCII now
if (self::is_ascii($string)) {
// Skip to step 7
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Prepared string is too long', 'idna.prepared_too_long', $string);
}
// Step 5: Check ACE prefix
if (strpos($string, self::ACE_PREFIX) === 0) {
throw new Requests_Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $string);
}
// Step 6: Encode with Punycode
$string = self::punycode_encode($string);
// Step 7: Prepend ACE prefix
$string = self::ACE_PREFIX . $string;
// Step 8: Check size
if (strlen($string) < 64) {
return $string;
}
throw new Requests_Exception('Encoded string is too long', 'idna.encoded_too_long', $string);
}
/**
* Check whether a given string contains only ASCII characters
*
* @internal (Testing found regex was the fastest implementation)
*
* @param string $string
* @return bool Is the string ASCII-only?
*/
protected static function is_ascii($string) {
return (preg_match('/(?:[^\x00-\x7F])/', $string) !== 1);
}
/**
* Prepare a string for use as an IDNA name
*
* @todo Implement this based on RFC 3491 and the newer 5891
* @param string $string
* @return string Prepared string
*/
protected static function nameprep($string) {
return $string;
}
/**
* Convert a UTF-8 string to a UCS-4 codepoint array
*
* Based on Requests_IRI::replace_invalid_with_pct_encoding()
*
* @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
* @param string $input
* @return array Unicode code points
*/
protected static function utf8_to_codepoints($input) {
$codepoints = array();
// Get number of bytes
$strlen = strlen($input);
for ($position = 0; $position < $strlen; $position++) {
$value = ord($input[$position]);
// One byte sequence:
if ((~$value & 0x80) === 0x80) {
$character = $value;
$length = 1;
$remaining = 0;
}
// Two byte sequence:
elseif (($value & 0xE0) === 0xC0) {
$character = ($value & 0x1F) << 6;
$length = 2;
$remaining = 1;
}
// Three byte sequence:
elseif (($value & 0xF0) === 0xE0) {
$character = ($value & 0x0F) << 12;
$length = 3;
$remaining = 2;
}
// Four byte sequence:
elseif (($value & 0xF8) === 0xF0) {
$character = ($value & 0x07) << 18;
$length = 4;
$remaining = 3;
}
// Invalid byte:
else {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
}
if ($remaining > 0) {
if ($position + $length > $strlen) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
for ($position++; $remaining > 0; $position++) {
$value = ord($input[$position]);
// If it is invalid, count the sequence as invalid and reprocess the current byte:
if (($value & 0xC0) !== 0x80) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
$character |= ($value & 0x3F) << (--$remaining * 6);
}
$position--;
}
if (
// Non-shortest form sequences are invalid
$length > 1 && $character <= 0x7F
|| $length > 2 && $character <= 0x7FF
|| $length > 3 && $character <= 0xFFFF
// Outside of range of ucschar codepoints
// Noncharacters
|| ($character & 0xFFFE) === 0xFFFE
|| $character >= 0xFDD0 && $character <= 0xFDEF
|| (
// Everything else not in ucschar
$character > 0xD7FF && $character < 0xF900
|| $character < 0x20
|| $character > 0x7E && $character < 0xA0
|| $character > 0xEFFFD
)
) {
throw new Requests_Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
}
$codepoints[] = $character;
}
return $codepoints;
}
/**
* RFC3492-compliant encoder
*
* @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
* @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
*
* @param string $input UTF-8 encoded string to encode
* @return string Punycode-encoded string
*/
public static function punycode_encode($input) {
$output = '';
# let n = initial_n
$n = self::BOOTSTRAP_INITIAL_N;
# let delta = 0
$delta = 0;
# let bias = initial_bias
$bias = self::BOOTSTRAP_INITIAL_BIAS;
# let h = b = the number of basic code points in the input
$h = $b = 0; // see loop
# copy them to the output in order
$codepoints = self::utf8_to_codepoints($input);
foreach ($codepoints as $char) {
if ($char < 128) {
// Character is valid ASCII
// TODO: this should also check if it's valid for a URL
$output .= chr($char);
$h++;
}
// Check if the character is non-ASCII, but below initial n
// This never occurs for Punycode, so ignore in coverage
// @codeCoverageIgnoreStart
elseif ($char < $n) {
throw new Requests_Exception('Invalid character', 'idna.character_outside_domain', $char);
}
// @codeCoverageIgnoreEnd
else {
$extended[$char] = true;
}
}
$extended = array_keys($extended);
sort($extended);
$b = $h;
# [copy them] followed by a delimiter if b > 0
if (strlen($output) > 0) {
$output .= '-';
}
# {if the input contains a non-basic code point < n then fail}
# while h < length(input) do begin
while ($h < count($codepoints)) {
# let m = the minimum code point >= n in the input
$m = array_shift($extended);
//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
# let delta = delta + (m - n) * (h + 1), fail on overflow
$delta += ($m - $n) * ($h + 1);
# let n = m
$n = $m;
# for each code point c in the input (in order) do begin
for ($num = 0; $num < count($codepoints); $num++) {
$c = $codepoints[$num];
# if c < n then increment delta, fail on overflow
if ($c < $n) {
$delta++;
}
# if c == n then begin
elseif ($c === $n) {
# let q = delta
$q = $delta;
# for k = base to infinity in steps of base do begin
for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
# let t = tmin if k <= bias {+ tmin}, or
# tmax if k >= bias + tmax, or k - bias otherwise
if ($k <= ($bias + self::BOOTSTRAP_TMIN)) {
$t = self::BOOTSTRAP_TMIN;
}
elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) {
$t = self::BOOTSTRAP_TMAX;
}
else {
$t = $k - $bias;
}
# if q < t then break
if ($q < $t) {
break;
}
# output the code point for digit t + ((q - t) mod (base - t))
$digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t));
//printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($digit));
$output .= self::digit_to_char($digit);
# let q = (q - t) div (base - t)
$q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
# end
}
# output the code point for digit q
$output .= self::digit_to_char($q);
//printf('needed delta is %d, encodes as "%s"' . PHP_EOL, $delta, self::digit_to_char($q));
# let bias = adapt(delta, h + 1, test h equals b?)
$bias = self::adapt($delta, $h + 1, $h === $b);
//printf('bias becomes %d' . PHP_EOL, $bias);
# let delta = 0
$delta = 0;
# increment h
$h++;
# end
}
# end
}
# increment delta and n
$delta++;
$n++;
# end
}
return $output;
}
/**
* Convert a digit to its respective character
*
* @see http://tools.ietf.org/html/rfc3492#section-5
* @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
*
* @param int $digit Digit in the range 0-35
* @return string Single character corresponding to digit
*/
protected static function digit_to_char($digit) {
// @codeCoverageIgnoreStart
// As far as I know, this never happens, but still good to be sure.
if ($digit < 0 || $digit > 35) {
throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
}
// @codeCoverageIgnoreEnd
$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
return substr($digits, $digit, 1);
}
/**
* Adapt the bias
*
* @see http://tools.ietf.org/html/rfc3492#section-6.1
* @param int $delta
* @param int $numpoints
* @param bool $firsttime
* @return int New bias
*/
protected static function adapt($delta, $numpoints, $firsttime) {
# function adapt(delta,numpoints,firsttime):
# if firsttime then let delta = delta div damp
if ($firsttime) {
$delta = floor($delta / self::BOOTSTRAP_DAMP);
}
# else let delta = delta div 2
else {
$delta = floor($delta / 2);
}
# let delta = delta + (delta div numpoints)
$delta += floor($delta / $numpoints);
# let k = 0
$k = 0;
# while delta > ((base - tmin) * tmax) div 2 do begin
$max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
while ($delta > $max) {
# let delta = delta div (base - tmin)
$delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
# let k = k + base
$k += self::BOOTSTRAP_BASE;
# end
}
# return k + (((base - tmin + 1) * delta) div (delta + skew))
return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW));
}
}
@@ -0,0 +1,221 @@
<?php
/**
* Class to validate and to work with IPv6 addresses
*
* @package Requests
* @subpackage Utilities
*/
/**
* Class to validate and to work with IPv6 addresses
*
* This was originally based on the PEAR class of the same name, but has been
* entirely rewritten.
*
* @package Requests
* @subpackage Utilities
*/
class Requests_IPv6
{
/**
* Uncompresses an IPv6 address
*
* RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and expands the '::' to
* the required number of zero pieces.
*
* Example: FF01::101 -> FF01:0:0:0:0:0:0:101
* ::1 -> 0:0:0:0:0:0:0:1
*
* @author Alexander Merz <alexander.merz@web.de>
* @author elfrink at introweb dot nl
* @author Josh Peck <jmp at joshpeck dot org>
* @copyright 2003-2005 The PHP Group
* @license http://www.opensource.org/licenses/bsd-license.php
* @param string $ip An IPv6 address
* @return string The uncompressed IPv6 address
*/
public static function uncompress($ip)
{
$c1 = -1;
$c2 = -1;
if (substr_count($ip, '::') === 1)
{
list($ip1, $ip2) = explode('::', $ip);
if ($ip1 === '')
{
$c1 = -1;
}
else
{
$c1 = substr_count($ip1, ':');
}
if ($ip2 === '')
{
$c2 = -1;
}
else
{
$c2 = substr_count($ip2, ':');
}
if (strpos($ip2, '.') !== false)
{
$c2++;
}
// ::
if ($c1 === -1 && $c2 === -1)
{
$ip = '0:0:0:0:0:0:0:0';
}
// ::xxx
else if ($c1 === -1)
{
$fill = str_repeat('0:', 7 - $c2);
$ip = str_replace('::', $fill, $ip);
}
// xxx::
else if ($c2 === -1)
{
$fill = str_repeat(':0', 7 - $c1);
$ip = str_replace('::', $fill, $ip);
}
// xxx::xxx
else
{
$fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
$ip = str_replace('::', $fill, $ip);
}
}
return $ip;
}
/**
* Compresses an IPv6 address
*
* RFC 4291 allows you to compress consecutive zero pieces in an address to
* '::'. This method expects a valid IPv6 address and compresses consecutive
* zero pieces to '::'.
*
* Example: FF01:0:0:0:0:0:0:101 -> FF01::101
* 0:0:0:0:0:0:0:1 -> ::1
*
* @see uncompress()
* @param string $ip An IPv6 address
* @return string The compressed IPv6 address
*/
public static function compress($ip)
{
// Prepare the IP to be compressed
$ip = self::uncompress($ip);
$ip_parts = self::split_v6_v4($ip);
// Replace all leading zeros
$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);
// Find bunches of zeros
if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE))
{
$max = 0;
$pos = null;
foreach ($matches[0] as $match)
{
if (strlen($match[0]) > $max)
{
$max = strlen($match[0]);
$pos = $match[1];
}
}
$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
}
if ($ip_parts[1] !== '')
{
return implode(':', $ip_parts);
}
else
{
return $ip_parts[0];
}
}
/**
* Splits an IPv6 address into the IPv6 and IPv4 representation parts
*
* RFC 4291 allows you to represent the last two parts of an IPv6 address
* using the standard IPv4 representation
*
* Example: 0:0:0:0:0:0:13.1.68.3
* 0:0:0:0:0:FFFF:129.144.52.38
*
* @param string $ip An IPv6 address
* @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part
*/
private static function split_v6_v4($ip)
{
if (strpos($ip, '.') !== false)
{
$pos = strrpos($ip, ':');
$ipv6_part = substr($ip, 0, $pos);
$ipv4_part = substr($ip, $pos + 1);
return array($ipv6_part, $ipv4_part);
}
else
{
return array($ip, '');
}
}
/**
* Checks an IPv6 address
*
* Checks if the given IP is a valid IPv6 address
*
* @param string $ip An IPv6 address
* @return bool true if $ip is a valid IPv6 address
*/
public static function check_ipv6($ip)
{
$ip = self::uncompress($ip);
list($ipv6, $ipv4) = self::split_v6_v4($ip);
$ipv6 = explode(':', $ipv6);
$ipv4 = explode('.', $ipv4);
if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4)
{
foreach ($ipv6 as $ipv6_part)
{
// The section can't be empty
if ($ipv6_part === '')
return false;
// Nor can it be over four characters
if (strlen($ipv6_part) > 4)
return false;
// Remove leading zeros (this is safe because of the above)
$ipv6_part = ltrim($ipv6_part, '0');
if ($ipv6_part === '')
$ipv6_part = '0';
// Check the value is valid
$value = hexdec($ipv6_part);
if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF)
return false;
}
if (count($ipv4) === 4)
{
foreach ($ipv4 as $ipv4_part)
{
$value = (int) $ipv4_part;
if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF)
return false;
}
}
return true;
}
else
{
return false;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
<?php
/**
* Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
/**
* Proxy connection interface
*
* Implement this interface to handle proxy settings and authentication
*
* Parameters should be passed via the constructor where possible, as this
* makes it much easier for users to use your provider.
*
* @see Requests_Hooks
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
interface Requests_Proxy {
/**
* Register hooks as needed
*
* This method is called in {@see Requests::request} when the user has set
* an instance as the 'auth' option. Use this callback to register all the
* hooks you'll need.
*
* @see Requests_Hooks::register
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks);
}
@@ -0,0 +1,150 @@
<?php
/**
* HTTP Proxy connection interface
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
/**
* HTTP Proxy connection interface
*
* Provides a handler for connection via an HTTP proxy
*
* @package Requests
* @subpackage Proxy
* @since 1.6
*/
class Requests_Proxy_HTTP implements Requests_Proxy {
/**
* Proxy host and port
*
* Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
*
* @var string
*/
public $proxy;
/**
* Username
*
* @var string
*/
public $user;
/**
* Password
*
* @var string
*/
public $pass;
/**
* Do we need to authenticate? (ie username & password have been provided)
*
* @var boolean
*/
public $use_authentication;
/**
* Constructor
*
* @since 1.6
* @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
* @param array|null $args Array of user and password. Must have exactly two elements
*/
public function __construct($args = null) {
if (is_string($args)) {
$this->proxy = $args;
}
elseif (is_array($args)) {
if (count($args) == 1) {
list($this->proxy) = $args;
}
elseif (count($args) == 3) {
list($this->proxy, $this->user, $this->pass) = $args;
$this->use_authentication = true;
}
else {
throw new Requests_Exception( 'Invalid number of arguments', 'proxyhttpbadargs');
}
}
}
/**
* Register the necessary callbacks
*
* @since 1.6
* @see curl_before_send
* @see fsockopen_remote_socket
* @see fsockopen_remote_host_path
* @see fsockopen_header
* @param Requests_Hooks $hooks Hook system
*/
public function register(Requests_Hooks &$hooks) {
$hooks->register('curl.before_send', array(&$this, 'curl_before_send'));
$hooks->register('fsockopen.remote_socket', array(&$this, 'fsockopen_remote_socket'));
$hooks->register('fsockopen.remote_host_path', array(&$this, 'fsockopen_remote_host_path'));
if( $this->use_authentication ) {
$hooks->register('fsockopen.after_headers', array(&$this, 'fsockopen_header'));
}
}
/**
* Set cURL parameters before the data is sent
*
* @since 1.6
* @param resource $handle cURL resource
*/
public function curl_before_send(&$handle) {
curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_setopt($handle, CURLOPT_PROXY, $this->proxy);
if ($this->use_authentication) {
curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string());
}
}
/**
* Alter remote socket information before opening socket connection
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_remote_socket( &$remote_socket ) {
$remote_socket = $this->proxy;
}
/**
* Alter remote path before getting stream data
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_remote_host_path( &$path, $url ) {
$path = $url;
}
/**
* Add extra headers to the request before sending
*
* @since 1.6
* @param string $out HTTP header string
*/
public function fsockopen_header( &$out ) {
$out .= "Proxy-Authorization: Basic " . base64_encode($this->get_auth_string()) . "\r\n";
}
/**
* Get the authentication string (user:pass)
*
* @since 1.6
* @return string
*/
public function get_auth_string() {
return $this->user . ':' . $this->pass;
}
}
@@ -0,0 +1,95 @@
<?php
/**
* HTTP response class
*
* Contains a response from Requests::request()
* @package Requests
*/
/**
* HTTP response class
*
* Contains a response from Requests::request()
* @package Requests
*/
class Requests_Response {
/**
* Constructor
*/
public function __construct() {
$this->headers = new Requests_Response_Headers();
}
/**
* Response body
* @var string
*/
public $body = '';
/**
* Raw HTTP data from the transport
* @var string
*/
public $raw = '';
/**
* Headers, as an associative array
* @var array
*/
public $headers = array();
/**
* Status code, false if non-blocking
* @var integer|boolean
*/
public $status_code = false;
/**
* Whether the request succeeded or not
* @var boolean
*/
public $success = false;
/**
* Number of redirects the request used
* @var integer
*/
public $redirects = 0;
/**
* URL requested
* @var string
*/
public $url = '';
/**
* Previous requests (from redirects)
* @var array Array of Requests_Response objects
*/
public $history = array();
/**
* Cookies from the request
*/
public $cookies = array();
/**
* Throws an exception if the request was not successful
*
* @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
* @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
* @param boolean $allow_redirects Set to false to throw on a 3xx as well
*/
public function throw_for_status($allow_redirects = true) {
if ($this->status_code >= 300 && $this->status_code < 400) {
if (!$allow_redirects) {
throw new Requests_Exception('Redirection not allowed', 'response.no_redirects', $this);
}
}
elseif (!$this->success) {
$exception = Requests_Exception_HTTP::get_class($this->status_code);
throw new $exception(null, $this);
}
}
}
@@ -0,0 +1,95 @@
<?php
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
*/
/**
* Case-insensitive dictionary, suitable for HTTP headers
*
* @package Requests
*/
class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictionary {
/**
* Get the given header
*
* Unlike {@see self::getValues()}, this returns a string. If there are
* multiple values, it concatenates them with a comma as per RFC2616.
*
* Avoid using this where commas may be used unquoted in values, such as
* Set-Cookie headers.
*
* @param string $key
* @return string Header value
*/
public function offsetGet($key) {
$key = strtolower($key);
if (!isset($this->data[$key]))
return null;
return $this->flatten($this->data[$key]);
}
/**
* Set the given item
*
* @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
*
* @param string $key Item name
* @param string $value Item value
*/
public function offsetSet($key, $value) {
if ($key === null) {
throw new Requests_Exception('Object is a dictionary, not a list', 'invalidset');
}
$key = strtolower($key);
if (!isset($this->data[$key])) {
$this->data[$key] = array();
}
$this->data[$key][] = $value;
}
/**
* Get all values for a given header
*
* @param string $key
* @return array Header values
*/
public function getValues($key) {
$key = strtolower($key);
if (!isset($this->data[$key]))
return null;
return $this->data[$key];
}
/**
* Flattens a value into a string
*
* Converts an array into a string by imploding values with a comma, as per
* RFC2616's rules for folding headers.
*
* @param string|array $value Value to flatten
* @return string Flattened value
*/
public function flatten($value) {
if (is_array($value))
$value = implode(',', $value);
return $value;
}
/**
* Get an iterator for the data
*
* Converts the internal
* @return ArrayIterator
*/
public function getIterator() {
return new Requests_Utility_FilteredIterator($this->data, array($this, 'flatten'));
}
}
@@ -0,0 +1,151 @@
<?php
/**
* SSL utilities for Requests
*
* @package Requests
* @subpackage Utilities
*/
/**
* SSL utilities for Requests
*
* Collection of utilities for working with and verifying SSL certificates.
*
* @package Requests
* @subpackage Utilities
*/
class Requests_SSL {
/**
* Verify the certificate against common name and subject alternative names
*
* Unfortunately, PHP doesn't check the certificate against the alternative
* names, leading things like 'https://www.github.com/' to be invalid.
* Instead
*
* @see http://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
*
* @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
* @param string $host Host name to verify against
* @param resource $context Stream context
* @return bool
*/
public static function verify_certificate($host, $cert) {
// Calculate the valid wildcard match if the host is not an IP address
$parts = explode('.', $host);
if (ip2long($host) === false) {
$parts[0] = '*';
}
$wildcard = implode('.', $parts);
$has_dns_alt = false;
// Check the subjectAltName
if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
$altnames = explode(',', $cert['extensions']['subjectAltName']);
foreach ($altnames as $altname) {
$altname = trim($altname);
if (strpos($altname, 'DNS:') !== 0)
continue;
$has_dns_alt = true;
// Strip the 'DNS:' prefix and trim whitespace
$altname = trim(substr($altname, 4));
// Check for a match
if (self::match_domain($host, $altname) === true) {
return true;
}
}
}
// Fall back to checking the common name if we didn't get any dNSName
// alt names, as per RFC2818
if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
// Check for a match
if (self::match_domain($host, $cert['subject']['CN']) === true) {
return true;
}
}
return false;
}
/**
* Verify that a reference name is valid
*
* Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
* - Wildcards can only occur in a name with more than 3 components
* - Wildcards can only occur as the last character in the first
* component
* - Wildcards may be preceded by additional characters
*
* We modify these rules to be a bit stricter and only allow the wildcard
* character to be the full first component; that is, with the exclusion of
* the third rule.
*
* @param string $reference Reference dNSName
* @return boolean Is the name valid?
*/
public static function verify_reference_name($reference) {
$parts = explode('.', $reference);
// Check the first part of the name
$first = array_shift($parts);
if (strpos($first, '*') !== false) {
// Check that the wildcard is the full part
if ($first !== '*') {
return false;
}
// Check that we have at least 3 components (including first)
if (count($parts) < 2) {
return false;
}
}
// Check the remaining parts
foreach ($parts as $part) {
if (strpos($part, '*') !== false) {
return false;
}
}
// Nothing found, verified!
return true;
}
/**
* Match a hostname against a dNSName reference
*
* @param string $host Requested host
* @param string $reference dNSName to match against
* @return boolean Does the domain match?
*/
public static function match_domain($host, $reference) {
// Check if the reference is blacklisted first
if (self::verify_reference_name($reference) !== true) {
return false;
}
// Check for a direct match
if ($host === $reference) {
return true;
}
// Calculate the valid wildcard match if the host is not an IP address
// Also validates that the host has 3 parts or more, as per Firefox's
// ruleset.
if (ip2long($host) === false) {
$parts = explode('.', $host);
$parts[0] = '*';
$wildcard = implode('.', $parts);
if ($wildcard === $reference) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,258 @@
<?php
/**
* Session handler for persistent requests and default parameters
*
* @package Requests
* @subpackage Session Handler
*/
/**
* Session handler for persistent requests and default parameters
*
* Allows various options to be set as default values, and merges both the
* options and URL properties together. A base URL can be set for all requests,
* with all subrequests resolved from this. Base options can be set (including
* a shared cookie jar), then overridden for individual requests.
*
* @package Requests
* @subpackage Session Handler
*/
class Requests_Session {
/**
* Base URL for requests
*
* URLs will be made absolute using this as the base
* @var string|null
*/
public $url = null;
/**
* Base headers for requests
* @var array
*/
public $headers = array();
/**
* Base data for requests
*
* If both the base data and the per-request data are arrays, the data will
* be merged before sending the request.
*
* @var array
*/
public $data = array();
/**
* Base options for requests
*
* The base options are merged with the per-request data for each request.
* The only default option is a shared cookie jar between requests.
*
* Values here can also be set directly via properties on the Session
* object, e.g. `$session->useragent = 'X';`
*
* @var array
*/
public $options = array();
/**
* Create a new session
*
* @param string|null $url Base URL for requests
* @param array $headers Default headers for requests
* @param array $data Default data for requests
* @param array $options Default options for requests
*/
public function __construct($url = null, $headers = array(), $data = array(), $options = array()) {
$this->url = $url;
$this->headers = $headers;
$this->data = $data;
$this->options = $options;
if (empty($this->options['cookies'])) {
$this->options['cookies'] = new Requests_Cookie_Jar();
}
}
/**
* Get a property's value
*
* @param string $key Property key
* @return mixed|null Property value, null if none found
*/
public function __get($key) {
if (isset($this->options[$key])) {
return $this->options[$key];
}
return null;
}
/**
* Set a property's value
*
* @param string $key Property key
* @param mixed $value Property value
*/
public function __set($key, $value) {
$this->options[$key] = $value;
}
/**
* Remove a property's value
*
* @param string $key Property key
*/
public function __isset($key) {
return isset($this->options[$key]);
}
/**
* Remove a property's value
*
* @param string $key Property key
*/
public function __unset($key) {
if (isset($this->options[$key])) {
unset($this->options[$key]);
}
}
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $options
* @return Requests_Response
*/
/**
* Send a GET request
*/
public function get($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::GET, $options);
}
/**
* Send a HEAD request
*/
public function head($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::HEAD, $options);
}
/**
* Send a DELETE request
*/
public function delete($url, $headers = array(), $options = array()) {
return $this->request($url, $headers, null, Requests::DELETE, $options);
}
/**#@-*/
/**#@+
* @see request()
* @param string $url
* @param array $headers
* @param array $data
* @param array $options
* @return Requests_Response
*/
/**
* Send a POST request
*/
public function post($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::POST, $options);
}
/**
* Send a PUT request
*/
public function put($url, $headers = array(), $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PUT, $options);
}
/**
* Send a PATCH request
*
* Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
* specification recommends that should send an ETag
*
* @link http://tools.ietf.org/html/rfc5789
*/
public function patch($url, $headers, $data = array(), $options = array()) {
return $this->request($url, $headers, $data, Requests::PATCH, $options);
}
/**#@-*/
/**
* Main interface for HTTP requests
*
* This method initiates a request and sends it via a transport before
* parsing.
*
* @see Requests::request()
*
* @throws Requests_Exception On invalid URLs (`nonhttp`)
*
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type (use Requests constants)
* @param array $options Options for the request (see {@see Requests::request})
* @return Requests_Response
*/
public function request($url, $headers = array(), $data = array(), $type = Requests::GET, $options = array()) {
$request = $this->merge_request(compact('url', 'headers', 'data', 'options'));
return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
}
/**
* Send multiple HTTP requests simultaneously
*
* @see Requests::request_multiple()
*
* @param array $requests Requests data (see {@see Requests::request_multiple})
* @param array $options Global and default options (see {@see Requests::request})
* @return array Responses (either Requests_Response or a Requests_Exception object)
*/
public function request_multiple($requests, $options = array()) {
foreach ($requests as $key => $request) {
$requests[$key] = $this->merge_request($request, false);
}
$options = array_merge($this->options, $options);
// Disallow forcing the type, as that's a per request setting
unset($options['type']);
return Requests::request_multiple($requests, $options);
}
/**
* Merge a request's data with the default data
*
* @param array $request Request data (same form as {@see request_multiple})
* @param boolean $merge_options Should we merge options as well?
* @return array Request data
*/
protected function merge_request($request, $merge_options = true) {
if ($this->url !== null) {
$request['url'] = Requests_IRI::absolutize($this->url, $request['url']);
$request['url'] = $request['url']->uri;
}
$request['headers'] = array_merge($this->headers, $request['headers']);
if (is_array($request['data']) && is_array($this->data)) {
$request['data'] = array_merge($this->data, $request['data']);
}
if ($merge_options !== false) {
$request['options'] = array_merge($this->options, $request['options']);
// Disallow forcing the type, as that's a per request setting
unset($request['options']['type']);
}
return $request;
}
}

Some files were not shown because too many files have changed in this diff Show More