diff --git a/.github/workflows/phpunit_on_pull_request.yml b/.github/workflows/phpunit_on_pull_request.yml
index 473d8f9c..d48ca906 100644
--- a/.github/workflows/phpunit_on_pull_request.yml
+++ b/.github/workflows/phpunit_on_pull_request.yml
@@ -1,12 +1,107 @@
on: pull_request
name: PHPUnit
jobs:
- runPHPCSInspection:
- name: Run PHPUnit test
+ unit: #-----------------------------------------------------------------------
+ name: Unit test / PHP ${{ matrix.php }}
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['7.3', '7.4', '8.0']
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Check out source code
+ uses: actions/checkout@v2
+
+ - name: Check existence of composer.json file
+ id: check_files
+ uses: andstor/file-existence-action@v1
+ with:
+ files: "composer.json, phpunit.xml.dist"
+
+ - name: Set up PHP environment
+ if: steps.check_files.outputs.files_exists == 'true'
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '${{ matrix.php }}'
+ coverage: xdebug
+ tools: composer,cs2pr
+
+ - name: Get Composer cache Directory
+ if: steps.check_files.outputs.files_exists == 'true'
+ id: composer-cache
+ run: |
+ echo "::set-output name=dir::$(composer config cache-files-dir)"
+ - name: Use Composer cache
+ if: steps.check_files.outputs.files_exists == 'true'
+ uses: actions/cache@master
+ with:
+ path: ${{ steps['composer-cache'].outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-composer-
+ - name: Install dependencies
+ if: steps.check_files.outputs.files_exists == 'true'
+ run: COMPOSER_ROOT_VERSION=dev-master composer install --prefer-dist --no-progress --no-suggest
+
+ - name: Setup problem matcher to provide annotations for PHPUnit
+ if: steps.check_files.outputs.files_exists == 'true'
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Run PHPUnit
+ if: steps.check_files.outputs.files_exists == 'true'
+ run: composer update && composer tests:unit
+
+ - name: Archive code coverage results
+ uses: actions/upload-artifact@v2
+ with:
+ name: code-coverage-report
+ path: /tmp/report/html
+
+ artifact-upload: #-----------------------------------------------------------------------
+ name: Push artifact to static site
runs-on: ubuntu-latest
+ needs: [unit]
+
steps:
- - uses: actions/checkout@v2
- with:
- ref: ${{ github.event.pull_request.head.sha }}
- - name: Run PHPUnit test
- uses: docker://rtcamp/action-run-phpunit:v1.0.0
+ - name: Check out gh-reports repository
+ uses: actions/checkout@v2
+ with:
+ repository: rtCamp/gh-reports.rt.gw
+ token: ${{ secrets.RTBOT_TOKEN }}
+
+ - name: Download artifact
+ uses: actions/download-artifact@v2
+ with:
+ name: code-coverage-report
+ path: /tmp/report/html
+
+ - name: Move report into correct location
+ run: |
+ repo=$(echo ${GITHUB_REPOSITORY#rtCamp/})
+ rm -rf "$repo/$GITHUB_REF"
+ mkdir -p "$repo/$GITHUB_REF"
+ rsync -avhP /tmp/report/html/ "$repo/$GITHUB_REF/"
+
+ - name: Commit files
+ run: |
+ repo=$(echo ${GITHUB_REPOSITORY#rtCamp/})
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add "$repo"
+ git commit -m "artifcat report build: $GITHUB_REPOSITORY@$GITHUB_SHA"
+
+ - name: Push changes
+ uses: ad-m/github-push-action@master
+ with:
+ github_token: ${{ secrets.RTBOT_TOKEN }}
+ branch: main
+ repository: rtCamp/gh-reports.rt.gw
+
+ - name: Comment URL on PR
+ uses: unsplash/comment-on-pr@master
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ msg: "Code coverage report: https://gh-reports.rt.gw/login-with-google/${{ github.ref }}/"
+ check_for_duplicate_msg: true
diff --git a/.github/workflows/release_plugin_on_tag.yml b/.github/workflows/release_plugin_on_tag.yml
index 29f857bb..d307d5bd 100644
--- a/.github/workflows/release_plugin_on_tag.yml
+++ b/.github/workflows/release_plugin_on_tag.yml
@@ -11,7 +11,6 @@ jobs:
- name: WordPress Plugin Deploy
uses: rtCamp/action-wordpress-org-plugin-deploy@master
env:
- CUSTOM_COMMAND: composer install --no-dev --optimize-autoloader && composer update
EXCLUDE_LIST: .github .gitignore .eslintignore phpunit.xml phpcs.xml tests README.md
SLUG: login-with-google
ASSETS_DIR: wp-assets
diff --git a/.gitignore b/.gitignore
index e647fd33..19b7c6ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/vendor/
.idea
node_modules/
.DS_Store
+.phpunit.result.cache
diff --git a/LICENSE b/LICENSE
index d159169d..50e720ec 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,82 +1,85 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+### GNU GENERAL PUBLIC LICENSE
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
+Version 2, June 1991
- Preamble
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+### Preamble
+
+The licenses for most software are designed to take away your freedom
+to share and change it. By contrast, the GNU General Public License is
+intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
- To protect your rights, we need to make restrictions that forbid
+To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the software, or if you modify it.
- For example, if you distribute copies of such a program, whether
+For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
rights.
- We protect your rights with two steps: (1) copyright the software, and
+We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
- Also, for each author's protection and ours, we want to make certain
+Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
+software. If the software is modified by someone else and passed on,
+we want its recipients to know that what they have is not the
+original, so that any problems introduced by others will not reflect
+on the original authors' reputations.
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
+Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at
+all.
- The precise terms and conditions for copying, distribution and
+The precise terms and conditions for copying, distribution and
modification follow.
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
+**0.** This License applies to any program or other work which
+contains a notice placed by the copyright holder saying it may be
+distributed under the terms of this General Public License. The
+"Program", below, refers to any such program or work, and a "work
+based on the Program" means either the Program or any derivative work
+under copyright law: that is to say, a work containing the Program or
+a portion of it, either verbatim or with modifications and/or
+translated into another language. (Hereinafter, translation is
+included without limitation in the term "modification".) Each licensee
+is addressed as "you".
Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
+covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
+is covered only if its contents constitute a work based on the Program
+(independent of having been made by running the Program). Whether that
+is true depends on what the Program does.
- 1. You may copy and distribute verbatim copies of the Program's
+**1.** You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
@@ -85,41 +88,46 @@ and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
+you may at your option offer warranty protection in exchange for a
+fee.
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
+**2.** You may modify your copy or copies of the Program or any
+portion of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
+
+**a)** You must cause the modified files to carry prominent notices
+stating that you changed the files and the date of any change.
+
+
+**b)** You must cause any work that you distribute or publish, that in
+whole or in part contains or is derived from the Program or any part
+thereof, to be licensed as a whole at no charge to all third parties
+under the terms of this License.
+
+
+**c)** If the modified program normally reads commands interactively
+when run, you must cause it, when started running for such interactive
+use in the most ordinary way, to print or display an announcement
+including an appropriate copyright notice and a notice that there is
+no warranty (or else, saying that you provide a warranty) and that
+users may redistribute the program under these conditions, and telling
+the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an
+announcement, your work based on the Program is not required to print
+an announcement.)
+
+These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
+sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
+entire whole, and thus to each and every part regardless of who wrote
+it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
@@ -131,32 +139,35 @@ with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
- 3. You may copy and distribute the Program (or a work based on it,
+**3.** You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
+**a)** Accompany it with the complete corresponding machine-readable
+source code, which must be distributed under the terms of Sections 1
+and 2 above on a medium customarily used for software interchange; or,
+
+
+**b)** Accompany it with a written offer, valid for at least three
+years, to give any third party, for a charge no more than your cost of
+physically performing source distribution, a complete machine-readable
+copy of the corresponding source code, to be distributed under the
+terms of Sections 1 and 2 above on a medium customarily used for
+software interchange; or,
+
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
+**c)** Accompany it with the information you received as to the offer
+to distribute corresponding source code. (This alternative is allowed
+only for noncommercial distribution and only if you received the
+program in object code or executable form with such an offer, in
+accord with Subsection b above.)
The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
+making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
+control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
@@ -169,43 +180,44 @@ access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
+**4.** You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt otherwise
+to copy, modify, sublicense or distribute the Program is void, and
+will automatically terminate your rights under this License. However,
+parties who have received copies, or rights, from you under this
+License will not have their licenses terminated so long as such
parties remain in full compliance.
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
+**5.** You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
+**6.** Each time you redistribute the Program (or any work based on
+the Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
+these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
+**7.** If, as a consequence of a court judgment or allegation of
+patent infringement or for any other reason (not limited to patent
+issues), conditions are imposed on you (whether by court order,
+agreement or otherwise) that contradict the conditions of this
+License, they do not excuse you from the conditions of this License.
+If you cannot distribute so as to satisfy simultaneously your
+obligations under this License and any other pertinent obligations,
+then as a consequence you may not distribute the Program at all. For
+example, if a patent license would not permit royalty-free
+redistribution of the Program by all those who receive copies directly
+or indirectly through you, then the only way you could satisfy both it
+and this License would be to refrain entirely from distribution of the
+Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
@@ -216,7 +228,7 @@ It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
+implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
@@ -226,114 +238,124 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
- 8. If the distribution and/or use of the Program is restricted in
+**8.** If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
+countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
+**9.** The Free Software Foundation may publish revised and/or new
+versions of the General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Program does not specify a
+version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+**10.** If you wish to incorporate parts of the Program into other
+free programs whose distribution conditions are different, write to
+the author to ask for permission. For software which is copyrighted by
+the Free Software Foundation, write to the Free Software Foundation;
+we sometimes make exceptions for this. Our decision will be guided by
+the two goals of preserving the free status of all derivatives of our
+free software and of promoting the sharing and reuse of software
+generally.
+
+**NO WARRANTY**
+
+**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+### END OF TERMS AND CONDITIONS
+
+### How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
+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
+To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
-
- Copyright (C)
+ one line to give the program's name and an idea of what it does.
+ Copyright (C) yyyy name of author
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-Also add information on how to contact you by electronic and paper mail.
+Also add information on how to contact you by electronic and paper
+mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- , 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
+ type `show w'. This is free software, and you are welcome
+ to redistribute it under certain conditions; type `show c'
+ for details.
+
+The hypothetical commands \`show w' and \`show c' should show the
+appropriate parts of the General Public License. Of course, the
+commands you use may be called something other than \`show w' and
+\`show c'; they could even be mouse-clicks or menu items--whatever
+suits your program.
+
+You should also get your employer (if you work as a programmer) or
+your school, if any, to sign a "copyright disclaimer" for the program,
+if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright
+ interest in the program `Gnomovision'
+ (which makes passes at compilers) written
+ by James Hacker.
+
+ signature of Ty Coon, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library,
+you may consider it more useful to permit linking proprietary
+applications with the library. If this is what you want to do, use the
+[GNU Lesser General Public
+License](https://www.gnu.org/licenses/lgpl.html) instead of this
+License.
diff --git a/README.md b/README.md
index ba928e2d..2bdedbed 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,98 @@
-
-
-
+# Login with Google
-# Log in with Google
+> WordPress plugin to login/register with google
-
+1. [Overview](#overview)
+2. [Installation](#installation)
+3. [Usage Instructions](#usage-instructions)
+
+ [Plugin Constants](#plugin-constants)
-Minimal plugin that allows WordPress users to log in using Google.
+4. [Shortcode](#shortcode)
+5. [Minimum Requirements](#minimum-requirements)
+6. [License](#license)
-**Author:** rtCamp
+## Overview
-**Tags:** Google login, sign in, sso, oauth, authentication, sign-in, single sign-on, log in
+Login with google provides seamless experience for users to login in to WordPress
+sites using their google account. No need to manually create accounts, no need to remember quirky
+passwords. Just one click and land into the site!
-**Requires at least:** 5.0
+## Installation
-**Tested up to:** 5.8.1
+1. Clone this repository.
+2. Run `composer install --no-dev` from inside the cloned directory.
+3. Upload the directory to `wp-content/plugins` directory.
+4. Activate the plugin from WordPress dashboard.
-**Requires PHP version:** 7.0
+## Browser support
+[These browsers are supported](https://developers.google.com/identity/gsi/web/guides/supported-browsers). Note, for example, that One Tap Login is not supported in Safari.
-**Stable tag:** 1.0.10
+## Usage Instructions
-**License:** GPLv2 or later (of course!)
+1. You will need to register a new application at https://console.cloud.google.com/apis/dashboard
-**License URI:** http://www.gnu.org/licenses/gpl-2.0.html
+2. `Authorization callback URL` should be like `https://yourdomain.com/wp-login.php`, where
+`https://yourdomain.com` will be replaced by your site URL.
-## Setup
+3. Once you create the app, you will receive the `Client ID` and `Client Secret`, add these credentials
+in `Settings > Login with google` settings page in their respective fields.
+
+4. `Create new user` enables new user registration irrespective of `Membership` settings in
+ `Settings > General`; as sometimes enabling user registration can lead to lots of spam users.
+ Plugin will take this setting as first priority and membership setting as second priority, so if
+ any one of them is enabled, new users will be registered by this plugin after successful authorization.
+
+5. `Whitelisted Domains` allows users from specific domains (domain in email) to get registered on site.
+This will prevent unwanted registration on website.
+**For Example:** If you want users only from your organization (`myorg.com`) to get registered on the
+website, you enter `myorg.com` in whitelisted domains. Users with google
+email like `abc@myorg.com` will be able to register on website. Contrary to this, users with emails like
+`something@gmail.com` would not be able to register here.
+
-1. If you're cloning repo, then after cloning run `composer install --no-dev` to install dependencies. GitHub release zip and WordPress.org download can skip this step.
-2. Create a project from [Google Developers Console](https://console.developers.google.com/apis/dashboard) if none exists.
-3. Go to **Credentials** tab, then create credential for OAuth client.
- * Application type will be **Web Application**
- * Add `YOUR_DOMAIN/wp-login.php` in **Authorized redirect URIs**
-4. This will give you **Client ID** and **Secret key**.
-5. Input these values either in `WP Admin > Settings > WP Google Login`, or in `wp-config.php` using the following code snippet:
+### Plugin Constants
-```php
-define( 'WP_GOOGLE_LOGIN_CLIENT_ID', 'YOUR_GOOGLE_CLIENT_ID' );
-define( 'WP_GOOGLE_LOGIN_SECRET', 'YOUR_SECRET_KEY' );
-```
+Above mentioned settings can also be configured via PHP constants by defining them in wp-config.php
+file.
-### How to enable automatic user registration?
+Refer following list of constants.
-You can enable user registration either by
-- Checking `Settings > WP Google Login > Enable Google Login Registration`
-OR
-- Adding `define( 'WP_GOOGLE_LOGIN_USER_REGISTRATION', 'true' );` in wp-config.php file.
+| | Type | Description |
+|-----------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| WP_GOOGLE_LOGIN_CLIENT_ID | String | Google client ID of your application. |
+| WP_GOOGLE_LOGIN_SECRET | String | Secret key of your application |
+| WP_GOOGLE_LOGIN_USER_REGISTRATION | Boolean | (Optional) Set True If you want to enable new user registration. By default, user registration defers to `Settings > General Settings > Membership` if constant is not set. |
+| WP_GOOGLE_LOGIN_WHITELIST_DOMAINS | String | (Optional) Domain name, if you want to restrict login with your custom domain. By default, It will allow all domains. You can whitelist multiple domains. |
-Note: If the checkbox is ON then, it will register valid Google users even when WordPress default setting, under `Settings > General Settings > Membership > Anyone can register` checkbox is OFF.
+These constants can also be configured
+via [wp-cli](https://developer.wordpress.org/cli/commands/config/).
-### How to restrict user registration to one or more domain(s)?
+**Note:** If you have defined the constant in wp-config.php file, corresponding settings field will be disable
+(locked for editing) on the settings page.
-By default, when you enable user registration via constant `WP_GOOGLE_LOGIN_USER_REGISTRATION` or enable `Settings > WP Google Login > Enable Google Login Registration`, it will create a user for any Google login (including gmail.com users). If you are planning to use this plugin on a private, internal site, then you may like to restrict user registration to users under a single Google Suite organization. This configuration variable does that.
+## Shortcode
-Add your domain name, without any schema prefix and `www,` as the value of `WP_GOOGLE_LOGIN_WHITELIST_DOMAINS` constant or in the settings `Settings > WP Google Login > Whitelisted Domains`. You can whitelist multiple domains. Please separate domains with commas. See the below example to know how to do it via constants:
+You can add the google login button to any page/post using shortcode: `google_login`
-```php
-define( 'WP_GOOGLE_LOGIN_WHITELIST_DOMAINS', 'example.com,sample.com' );
+**Example:**
+```php
+[google_login button_text="Google Login" force_display="yes" /]
```
-**Note:** If a user already exists, they **will be allowed to login with Google** regardless of whether their domain is whitelisted or not. Whitelisting will only prevent users from **registering** with email addresses from non-whitelisted domains.
-
-
-## Hooks
-
-### 1. Action `wp_google_login_token`
-
-This action provides access token received after Google login.
-**Parameters:**
-* `token` (Array): Converted token using `fetchAccessTokenWithAuthCode` method of `Google_Client` class.
-* `user_info` (Array): Details of user after login.
-* `client` (Object): `Google_Client` object in use.
-
-### 2. Filter `wp_google_login_scopes`
-
-This filter can be used to filter existing scope used in Google Sign in.
-You can ask for additional permission while user logs in.
-
-This filter will provide 1 parameter `scopes` in callback, which contains array of scopes.
-
-
-## wp-config.php parameters list
-
-| | Type | Description |
-|-----------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| WP_GOOGLE_LOGIN_CLIENT_ID | String | Google client ID of your application. |
-| WP_GOOGLE_LOGIN_SECRET | String | Secret key of your application |
-| WP_GOOGLE_LOGIN_USER_REGISTRATION | Boolean | (Optional) Set True If you want to enable new user registration. By default, user registration defers to `Settings > General Settings > Membership` if constant is not set. |
-| WP_GOOGLE_LOGIN_WHITELIST_DOMAINS | String | (Optional) Domain name, if you want to restrict login with your custom domain. By default, It will allow all domains. You can whitelist multiple domains. |
+**Supported attributes for shortcode**
+| Parameter | Description | Values | Default |
+| -------------- | --------------------------------------------------------------| -------| ------------------ |
+| button_text | Text to show for login button | string | Login with google |
+| force_display | Whether to display button when user is already logged in | yes/no | no |
+| redirect_to | URL where user should be redirected post login | URL | `wp-admin` |
## Contribute
### Reporting a bug 🐞
-Before creating a new issue, do browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for resolution or upcoming fixes.
+Before creating a new issue, do browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for resolution or upcoming fixes.
If you still need to [log an issue](https://github.com/rtCamp/login-with-google/issues/new), making sure to include as much detail as you can, including clear steps to reproduce your issue if possible.
@@ -101,7 +100,7 @@ If you still need to [log an issue](https://github.com/rtCamp/login-with-google/
Want to contribute a new feature? Start a conversation by logging an [issue](https://github.com/rtCamp/login-with-google/issues).
-Once you're ready to send a pull request, please run through the following checklist:
+Once you're ready to send a pull request, please run through the following checklist:
1. Browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for anything related to what you want to work on. If you don't find any related issues, open a new one.
@@ -115,17 +114,27 @@ Once you're ready to send a pull request, please run through the following check
1. Once your pull request has passed final code review and tests, it will be merged into `develop` and be in the pipeline for the next release. Props to you! 🎉
+
## Unit testing
-- Setup local unit test environment by running script from terminal
+Unit tests can be run with simple command `composer tests:unit`.
+Please note that you'll need to do `composer install` (need to install dev dependencies) for running
+unit tests.
+
+You should have PHP CLI > 7.1 installed. If you have Xdebug enabled with php, code coverage report will be
+generated at `/tmp/report/html`
+
+## Minimum Requirements
-```./bin/install-wp-tests.sh [db-host] [wp-version] [skip-database-creation]```
+WordPress >= 5.4.0
-- Execute `phpunit` in terminal from repository to run all test cases.
+PHP >= 7.3
-- Execute `phpunit ./tests/inc/test-class.php` in terminal with file path to run specific tests.
+## License
+This library is released under
+["GPL 2.0 or later" License](LICENSE).
## BTW, We're Hiring!
-
+
diff --git a/assets/build/js/login.js b/assets/build/js/login.js
index fa7972a2..54ec6bf2 100644
--- a/assets/build/js/login.js
+++ b/assets/build/js/login.js
@@ -1 +1 @@
-!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=0)}([function(e,t,n){n(1),e.exports=n(2)},function(e,t){({init:function(){document.addEventListener("DOMContentLoaded",this.onContentLoaded)},onContentLoaded:function(){this.form=document.getElementById("loginform")||document.getElementById("registerform"),null!==this.form&&(this.googleLoginButton=this.form.querySelector(".wp_google_login").cloneNode(!0),this.googleLoginButton.classList.remove("hidden"),this.form.append(this.googleLoginButton))}}).init()},function(e,t){}]);
\ No newline at end of file
+!function(e){var t={};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,n){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(n,r,function(t){return e[t]}.bind(null,r));return n},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="/",o(o.s=0)}([function(e,t,o){o(1),e.exports=o(2)},function(e,t){({init:function(){document.addEventListener("DOMContentLoaded",this.onContentLoaded)},onContentLoaded:function(){this.form=document.getElementById("loginform")||document.getElementById("registerform"),document.querySelector(".wp_google_login")&&null===this.form&&(document.cookie="wp-login-with-google=1;path="+window.location.pathname+";"),null!==this.form&&(this.googleLoginButton=this.form.querySelector(".wp_google_login"),this.googleLoginButton.classList.remove("hidden"),this.form.append(this.googleLoginButton))}}).init()},function(e,t){}]);
\ No newline at end of file
diff --git a/assets/build/js/onetap.js b/assets/build/js/onetap.js
new file mode 100644
index 00000000..2fa41276
--- /dev/null
+++ b/assets/build/js/onetap.js
@@ -0,0 +1,35 @@
+window.LoginWithGoogleDataCallBack = function( response ) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', TempAccessOneTap.ajaxurl, true);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.onreadystatechange = function() {
+ if(xhr.readyState === XMLHttpRequest.DONE) {
+ var status = xhr.status;
+ if ( status === 200 ) {
+ var response = JSON.parse( xhr.responseText );
+
+ if ( ! response.success ) {
+ alert( response.data );
+ return;
+ }
+
+ try {
+
+ var redirect_to = new URL( response.data.redirect );
+ var homeurl = new URL( TempAccessOneTap.homeurl );
+
+ if ( redirect_to.host !== homeurl.host ) {
+ throw new URIError( wp.i18n.__( 'Invalid URL for Redirection', 'login-with-google' ) );
+ }
+
+ } catch ( e ) {
+ alert( e.message );
+ return;
+ }
+
+ window.location = response.data.redirect;
+ }
+ }
+ };
+ xhr.send( 'action=validate_id_token&token=' + response.credential + '&state=' + TempAccessOneTap.state );
+};
diff --git a/assets/build/js/onetap.min.js b/assets/build/js/onetap.min.js
new file mode 100644
index 00000000..a7eef91b
--- /dev/null
+++ b/assets/build/js/onetap.min.js
@@ -0,0 +1 @@
+window.LoginWithGoogleDataCallBack=function(e){var t=new XMLHttpRequest;t.open("POST",TempAccessOneTap.ajaxurl,!0),t.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),t.onreadystatechange=function(){if(t.readyState===XMLHttpRequest.DONE&&200===t.status){var e=JSON.parse(t.responseText);if(!e.success)return void alert(e.data);try{var a=new URL(e.data.redirect),n=new URL(TempAccessOneTap.homeurl);if(a.host!==n.host)throw new URIError(wp.i18n.__("Invalid URL for Redirection","login-with-google"))}catch(e){return void alert(e.message)}window.location=e.data.redirect}},t.send("action=validate_id_token&token="+e.credential+"&state="+TempAccessOneTap.state)};
diff --git a/assets/mix-manifest.json b/assets/mix-manifest.json
index c444db49..2d8efe2e 100644
--- a/assets/mix-manifest.json
+++ b/assets/mix-manifest.json
@@ -1,4 +1,6 @@
{
"/build/js/login.js": "/build/js/login.js",
- "/build/css/login.css": "/build/css/login.css"
+ "/build/css/login.css": "/build/css/login.css",
+ "/build/js/onetap.js": "/build/js/onetap.js",
+ "/build/js/onetap.min.js": "/build/js/onetap.min.js"
}
diff --git a/assets/package.json b/assets/package.json
index 720154f1..cd3e94ef 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -8,11 +8,11 @@
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
- "language": "wp i18n make-pot ../ --exclude=\"assets\",\"tests\",\"vendor\" ../languages/login-with-google.po"
+ "language": "wp i18n make-pot ../ --exclude=\"assets\",\"tests\",\"vendor\" ../languages/login-with-google.pot"
},
"devDependencies": {
"cross-env": "^5.1",
- "laravel-mix": "^4.0.15",
+ "laravel-mix": "^4.1.0",
"lodash": "^4.17.15",
"sass": "^1.26.5",
"sass-loader": "^7.1.0",
diff --git a/assets/src/js/login.js b/assets/src/js/login.js
index 6bd85beb..7dc7df80 100644
--- a/assets/src/js/login.js
+++ b/assets/src/js/login.js
@@ -19,6 +19,9 @@ const wpGoogleLogin = {
* Callback function when content is load.
* To render the google login button at after login form.
*
+ * Set cookie if "Login with Google" button displayed to bypass page cache
+ * Do not set on wp login or registration page.
+ *
* @return void
*/
onContentLoaded() {
@@ -26,13 +29,18 @@ const wpGoogleLogin = {
// Form either can be login or register form.
this.form = document.getElementById( 'loginform' ) || document.getElementById( 'registerform' );
+ // Set cookie if "Login with Google" button displayed to bypass page cache
+ // Do not set on wp login or registration page.
+ if ( document.querySelector( '.wp_google_login' ) && null === this.form ) {
+ document.cookie = 'wp-login-with-google=1;path=' + window.location.pathname + ';';
+ }
+
if ( null === this.form ) {
return;
}
- this.googleLoginButton = this.form.querySelector( '.wp_google_login' ).cloneNode( true );
+ this.googleLoginButton = this.form.querySelector( '.wp_google_login' );
this.googleLoginButton.classList.remove( 'hidden' );
-
// HTML is cloned from existing HTML node.
this.form.append( this.googleLoginButton );
}
diff --git a/assets/src/js/onetap.js b/assets/src/js/onetap.js
new file mode 100644
index 00000000..2fa41276
--- /dev/null
+++ b/assets/src/js/onetap.js
@@ -0,0 +1,35 @@
+window.LoginWithGoogleDataCallBack = function( response ) {
+ var xhr = new XMLHttpRequest();
+ xhr.open('POST', TempAccessOneTap.ajaxurl, true);
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ xhr.onreadystatechange = function() {
+ if(xhr.readyState === XMLHttpRequest.DONE) {
+ var status = xhr.status;
+ if ( status === 200 ) {
+ var response = JSON.parse( xhr.responseText );
+
+ if ( ! response.success ) {
+ alert( response.data );
+ return;
+ }
+
+ try {
+
+ var redirect_to = new URL( response.data.redirect );
+ var homeurl = new URL( TempAccessOneTap.homeurl );
+
+ if ( redirect_to.host !== homeurl.host ) {
+ throw new URIError( wp.i18n.__( 'Invalid URL for Redirection', 'login-with-google' ) );
+ }
+
+ } catch ( e ) {
+ alert( e.message );
+ return;
+ }
+
+ window.location = response.data.redirect;
+ }
+ }
+ };
+ xhr.send( 'action=validate_id_token&token=' + response.credential + '&state=' + TempAccessOneTap.state );
+};
diff --git a/assets/webpack.mix.js b/assets/webpack.mix.js
index 7cf589cf..8a423f03 100644
--- a/assets/webpack.mix.js
+++ b/assets/webpack.mix.js
@@ -11,5 +11,7 @@ mix.options( {
} );
mix.copy( 'src/images', 'build/images' )
+ .copy( 'src/js/onetap.js', 'build/js' )
+ .minify( 'build/js/onetap.js' )
.js( 'src/js/login.js', 'build/js' )
- .sass( 'src/scss/login.scss', 'build/css' );
\ No newline at end of file
+ .sass( 'src/scss/login.scss', 'build/css' );
diff --git a/autoloader.php b/autoloader.php
deleted file mode 100644
index dca7fb9a..00000000
--- a/autoloader.php
+++ /dev/null
@@ -1,72 +0,0 @@
- [db-host] [wp-version] [skip-database-creation]"
- exit 1
-fi
-
-DB_NAME=$1
-DB_USER=$2
-DB_PASS=$3
-DB_HOST=${4-localhost}
-WP_VERSION=${5-latest}
-SKIP_DB_CREATE=${6-false}
-
-TMPDIR=${TMPDIR-/tmp}
-TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
-WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
-WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
-
-download() {
- if [ `which curl` ]; then
- curl -s "$1" > "$2";
- elif [ `which wget` ]; then
- wget -nv -O "$2" "$1"
- fi
-}
-
-if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
- WP_BRANCH=${WP_VERSION%\-*}
- WP_TESTS_TAG="branches/$WP_BRANCH"
-
-elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
- WP_TESTS_TAG="branches/$WP_VERSION"
-elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
- if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
- # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
- WP_TESTS_TAG="tags/${WP_VERSION%??}"
- else
- WP_TESTS_TAG="tags/$WP_VERSION"
- fi
-elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
- WP_TESTS_TAG="trunk"
-else
- # http serves a single offer, whereas https serves multiple. we only want one
- download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
- grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
- LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
- if [[ -z "$LATEST_VERSION" ]]; then
- echo "Latest WordPress version could not be found"
- exit 1
- fi
- WP_TESTS_TAG="tags/$LATEST_VERSION"
-fi
-set -ex
-
-install_wp() {
-
- if [ -d $WP_CORE_DIR ]; then
- return;
- fi
-
- mkdir -p $WP_CORE_DIR
-
- if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
- mkdir -p $TMPDIR/wordpress-nightly
- download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip
- unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/
- mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR
- else
- if [ $WP_VERSION == 'latest' ]; then
- local ARCHIVE_NAME='latest'
- elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
- # https serves multiple offers, whereas http serves single.
- download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
- if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
- # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
- LATEST_VERSION=${WP_VERSION%??}
- else
- # otherwise, scan the releases and get the most up to date minor version of the major release
- local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
- LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
- fi
- if [[ -z "$LATEST_VERSION" ]]; then
- local ARCHIVE_NAME="wordpress-$WP_VERSION"
- else
- local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
- fi
- else
- local ARCHIVE_NAME="wordpress-$WP_VERSION"
- fi
- download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
- tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
- fi
-
- download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
-}
-
-install_test_suite() {
- # portable in-place argument for both GNU sed and Mac OSX sed
- if [[ $(uname -s) == 'Darwin' ]]; then
- local ioption='-i.bak'
- else
- local ioption='-i'
- fi
-
- # set up testing suite if it doesn't yet exist
- if [ ! -d $WP_TESTS_DIR ]; then
- # set up testing suite
- mkdir -p $WP_TESTS_DIR
- svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
- svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
- fi
-
- if [ ! -f wp-tests-config.php ]; then
- download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
- # remove all forward slashes in the end
- WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
- sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
- sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
- fi
-
-}
-
-install_db() {
-
- if [ ${SKIP_DB_CREATE} = "true" ]; then
- return 0
- fi
-
- # parse DB_HOST for port or socket references
- local PARTS=(${DB_HOST//\:/ })
- local DB_HOSTNAME=${PARTS[0]};
- local DB_SOCK_OR_PORT=${PARTS[1]};
- local EXTRA=""
-
- if ! [ -z $DB_HOSTNAME ] ; then
- if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
- EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
- elif ! [ -z $DB_SOCK_OR_PORT ] ; then
- EXTRA=" --socket=$DB_SOCK_OR_PORT"
- elif ! [ -z $DB_HOSTNAME ] ; then
- EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
- fi
- fi
-
- # create database
- mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
-}
-
-install_wp
-install_test_suite
-install_db
diff --git a/composer.json b/composer.json
index f0a10346..262b81a0 100644
--- a/composer.json
+++ b/composer.json
@@ -1,22 +1,61 @@
{
"name": "rtcamp/login-with-google",
- "description": "Minimal plugin which allows WP user to login with google.",
- "license": "GPLv2",
+ "description": "WordPress plugin to let users login with google.",
+ "license": "GPL 2.0",
"authors": [
+ {
+ "name": "rtCamp",
+ "email": "contact@rtcamp.com",
+ "homepage": "https://rtcamp.com/",
+ "role": "Developer"
+ },
{
"name": "Utkarsh Patel",
"email": "itismeutkarsh@gmail.com"
+ },
+ {
+ "name": "Paul Clark",
+ "role": "Developer"
+ },
+ {
+ "name": "Ankit Gade",
+ "email": "ankit.gade@rtcamp.com",
+ "homepage": "https://iamank.it/",
+ "role": "Developer"
}
],
+ "minimum-stability": "stable",
"require": {
- "google/apiclient": "^2.0"
+ "php": ">=7.1",
+ "pimple/pimple": "3.*"
+ },
+ "require-dev": {
+ "squizlabs/php_codesniffer": "^3.5",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
+ "wp-coding-standards/wpcs": "^2.3",
+ "sirbrillig/phpcs-variable-analysis": "^2.10",
+ "automattic/vipwpcs": "^2.2",
+ "phpcompatibility/phpcompatibility-wp": "^2.1",
+ "phpunit/phpunit": "9.5",
+ "10up/wp_mock": "0.4.2"
},
- "scripts": {
- "post-update-cmd": "Google_Task_Composer::cleanup"
+ "autoload": {
+ "psr-4": {
+ "RtCamp\\GoogleLogin\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "RtCamp\\GoogleLogin\\Tests\\": "tests/php"
+ }
},
- "extra": {
- "google/apiclient-services": [
- "Oauth2"
+ "scripts": {
+ "cs": "@php ./vendor/bin/phpcs",
+ "cs:fix": "@php ./vendor/bin/phpcbf",
+ "tests:unit": "@php ./vendor/bin/phpunit tests/php/Unit/",
+ "qa": [
+ "@cs",
+ "@tests"
]
}
}
diff --git a/composer.lock b/composer.lock
index d6aaaa26..b106c288 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,627 +4,2493 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "43275de4c7e7779e3156ca4886d5d1a6",
+ "content-hash": "68a3dc0fe0a42aa8319c92c284fb3843",
"packages": [
{
- "name": "firebase/php-jwt",
- "version": "v5.0.0",
+ "name": "pimple/pimple",
+ "version": "v3.4.0",
"source": {
"type": "git",
- "url": "https://github.com/firebase/php-jwt.git",
- "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e"
+ "url": "https://github.com/silexphp/Pimple.git",
+ "reference": "86406047271859ffc13424a048541f4531f53601"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/9984a4d3a32ae7673d6971ea00bae9d0a1abba0e",
- "reference": "9984a4d3a32ae7673d6971ea00bae9d0a1abba0e",
+ "url": "https://api.github.com/repos/silexphp/Pimple/zipball/86406047271859ffc13424a048541f4531f53601",
+ "reference": "86406047271859ffc13424a048541f4531f53601",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=7.2.5",
+ "psr/container": "^1.1"
},
"require-dev": {
- "phpunit/phpunit": " 4.8.35"
+ "symfony/phpunit-bridge": "^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Pimple": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Pimple, a simple Dependency Injection Container",
+ "homepage": "https://pimple.symfony.com",
+ "keywords": [
+ "container",
+ "dependency injection"
+ ],
+ "time": "2021-03-06T08:28:00+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "time": "2021-03-05T17:36:06+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "10up/wp_mock",
+ "version": "0.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/10up/wp_mock.git",
+ "reference": "9019226eb50df275aa86bb15bfc98a619601ee49"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/10up/wp_mock/zipball/9019226eb50df275aa86bb15bfc98a619601ee49",
+ "reference": "9019226eb50df275aa86bb15bfc98a619601ee49",
+ "shasum": ""
+ },
+ "require": {
+ "antecedent/patchwork": "^2.1",
+ "mockery/mockery": "^1.0",
+ "php": ">=7.1",
+ "phpunit/phpunit": ">=7.0"
+ },
+ "require-dev": {
+ "behat/behat": "^3.0",
+ "php-coveralls/php-coveralls": "^2.1",
+ "sebastian/comparator": ">=1.2.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "WP_Mock\\": "./php/WP_Mock"
+ },
+ "classmap": [
+ "php/WP_Mock.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "A mocking library to take the pain out of unit testing for WordPress",
+ "time": "2019-03-16T03:44:39+00:00"
+ },
+ {
+ "name": "antecedent/patchwork",
+ "version": "2.1.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/antecedent/patchwork.git",
+ "reference": "b98e046dd4c0acc34a0846604f06f6111654d9ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/antecedent/patchwork/zipball/b98e046dd4c0acc34a0846604f06f6111654d9ea",
+ "reference": "b98e046dd4c0acc34a0846604f06f6111654d9ea",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=4"
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignas Rudaitis",
+ "email": "ignas.rudaitis@gmail.com"
+ }
+ ],
+ "description": "Method redefinition (monkey-patching) functionality for PHP.",
+ "homepage": "http://patchwork2.org/",
+ "keywords": [
+ "aop",
+ "aspect",
+ "interception",
+ "monkeypatching",
+ "redefinition",
+ "runkit",
+ "testing"
+ ],
+ "time": "2019-12-22T17:52:09+00:00"
+ },
+ {
+ "name": "automattic/vipwpcs",
+ "version": "2.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Automattic/VIP-Coding-Standards.git",
+ "reference": "efacebef421334d54b99afa92fb8fa645336a8a7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Automattic/VIP-Coding-Standards/zipball/efacebef421334d54b99afa92fb8fa645336a8a7",
+ "reference": "efacebef421334d54b99afa92fb8fa645336a8a7",
+ "shasum": ""
+ },
+ "require": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7",
+ "php": ">=5.4",
+ "sirbrillig/phpcs-variable-analysis": "^2.8.3",
+ "squizlabs/php_codesniffer": "^3.5.5",
+ "wp-coding-standards/wpcs": "^2.3"
+ },
+ "require-dev": {
+ "php-parallel-lint/php-console-highlighter": "^0.5",
+ "php-parallel-lint/php-parallel-lint": "^1.0",
+ "phpcompatibility/php-compatibility": "^9",
+ "phpcsstandards/phpcsdevtools": "^1.0",
+ "phpunit/phpunit": "^4 || ^5 || ^6 || ^7"
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/Automattic/VIP-Coding-Standards/graphs/contributors"
+ }
+ ],
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress VIP minimum coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2021-04-28T16:41:50+00:00"
+ },
+ {
+ "name": "dealerdirect/phpcodesniffer-composer-installer",
+ "version": "v0.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/fe390591e0241955f22eb9ba327d137e501c771c",
+ "reference": "fe390591e0241955f22eb9ba327d137e501c771c",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0 || ^2.0",
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.0 || ^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "composer/composer": "*",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "sensiolabs/security-checker": "^4.1.0"
+ },
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
+ },
+ "autoload": {
+ "psr-4": {
+ "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Franck Nijhof",
+ "email": "franck.nijhof@dealerdirect.com",
+ "homepage": "http://www.frenck.nl",
+ "role": "Developer / IT Manager"
+ }
+ ],
+ "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
+ "homepage": "http://www.dealerdirect.com",
+ "keywords": [
+ "PHPCodeSniffer",
+ "PHP_CodeSniffer",
+ "code quality",
+ "codesniffer",
+ "composer",
+ "installer",
+ "phpcs",
+ "plugin",
+ "qa",
+ "quality",
+ "standard",
+ "standards",
+ "style guide",
+ "stylecheck",
+ "tests"
+ ],
+ "time": "2020-12-07T18:04:37+00:00"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^8.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
- "Firebase\\JWT\\": "src"
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-10T18:47:58+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3|^7.0|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "time": "2020-07-09T08:09:16+00:00"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "1.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mockery/mockery.git",
+ "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea",
+ "reference": "d1339f64479af1bee0e82a0413813fe5345a54ea",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "^2.0.1",
+ "lib-pcre": ">=7.0",
+ "php": "^7.3 || ^8.0"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5 || ^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework",
+ "homepage": "https://github.com/mockery/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "time": "2021-02-24T09:51:49+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "replace": {
+ "myclabs/deep-copy": "self.version"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ },
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-13T09:40:50+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.10.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4432ba399e47c66624bc73c8c0f811e5c109576f",
+ "reference": "4432ba399e47c66624bc73c8c0f811e5c109576f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "time": "2021-05-03T19:11:20+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
+ "reference": "85265efd3af7ba3ca4b2a2c34dbfc5788dd29133",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "time": "2020-06-27T14:33:11+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "bae7c545bef187884426f042434e561ab1ddb182"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182",
+ "reference": "bae7c545bef187884426f042434e561ab1ddb182",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "time": "2021-02-23T14:00:09+00:00"
+ },
+ {
+ "name": "phpcompatibility/php-compatibility",
+ "version": "9.3.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
+ "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3",
+ "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
+ },
+ "conflict": {
+ "squizlabs/php_codesniffer": "2.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "homepage": "https://github.com/wimg",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "homepage": "https://github.com/jrfnl",
+ "role": "lead"
+ },
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
+ }
+ ],
+ "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
+ "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards"
+ ],
+ "time": "2019-12-27T09:44:58+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-paragonie",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/ddabec839cc003651f2ce695c938686d1086cf43",
+ "reference": "ddabec839cc003651f2ce695c938686d1086cf43",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
+ "paragonie/random_compat": "dev-master",
+ "paragonie/sodium_compat": "dev-master"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "paragonie",
+ "phpcs",
+ "polyfill",
+ "standards"
+ ],
+ "time": "2021-02-15T10:24:51+00:00"
+ },
+ {
+ "name": "phpcompatibility/phpcompatibility-wp",
+ "version": "2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
+ "reference": "b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e",
+ "reference": "b7dc0cd7a8f767ccac5e7637550ea1c50a67b09e",
+ "shasum": ""
+ },
+ "require": {
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcompatibility/phpcompatibility-paragonie": "^1.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7"
+ },
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
+ "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
+ },
+ "type": "phpcodesniffer-standard",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-or-later"
+ ],
+ "authors": [
+ {
+ "name": "Wim Godden",
+ "role": "lead"
+ },
+ {
+ "name": "Juliette Reinders Folmer",
+ "role": "lead"
+ }
+ ],
+ "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
+ "homepage": "http://phpcompatibility.com/",
+ "keywords": [
+ "compatibility",
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2021-02-15T12:58:46+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "time": "2020-09-03T19:13:55+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "time": "2020-09-17T18:55:26+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "1.13.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea",
+ "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.2",
+ "php": "^7.2 || ~8.0, <8.1",
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "sebastian/comparator": "^3.0 || ^4.0",
+ "sebastian/recursion-context": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^6.0",
+ "phpunit/phpunit": "^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2021-03-17T13:42:18+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "f6293e1b30a2354e8428e004689671b83871edde"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f6293e1b30a2354e8428e004689671b83871edde",
+ "reference": "f6293e1b30a2354e8428e004689671b83871edde",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.10.2",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.3",
+ "phpunit/php-text-template": "^2.0.2",
+ "sebastian/code-unit-reverse-lookup": "^2.0.2",
+ "sebastian/complexity": "^2.0",
+ "sebastian/environment": "^5.1.2",
+ "sebastian/lines-of-code": "^1.0.3",
+ "sebastian/version": "^3.0.1",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcov": "*",
+ "ext-xdebug": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-03-28T07:26:59+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/aa4be8575f26070b100fccb67faabb28f21f66f8",
+ "reference": "aa4be8575f26070b100fccb67faabb28f21f66f8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:57:25+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "8e16c225d57c3d6808014df6b1dd7598d0a5bbbe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e16c225d57c3d6808014df6b1dd7598d0a5bbbe",
+ "reference": "8e16c225d57c3d6808014df6b1dd7598d0a5bbbe",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.3.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.1",
+ "phar-io/version": "^3.0.2",
+ "php": ">=7.3",
+ "phpspec/prophecy": "^1.12.1",
+ "phpunit/php-code-coverage": "^9.2.3",
+ "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.3",
+ "phpunit/php-timer": "^5.0.2",
+ "sebastian/cli-parser": "^1.0.1",
+ "sebastian/code-unit": "^1.0.6",
+ "sebastian/comparator": "^4.0.5",
+ "sebastian/diff": "^4.0.3",
+ "sebastian/environment": "^5.1.3",
+ "sebastian/exporter": "^4.0.3",
+ "sebastian/global-state": "^5.0.1",
+ "sebastian/object-enumerator": "^4.0.3",
+ "sebastian/resource-operations": "^3.0.3",
+ "sebastian/type": "^2.3",
+ "sebastian/version": "^3.0.2"
+ },
+ "require-dev": {
+ "ext-pdo": "*",
+ "phpspec/prophecy-phpunit": "^2.0.1"
+ },
+ "suggest": {
+ "ext-soap": "*",
+ "ext-xdebug": "*"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.5-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ],
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "funding": [
+ {
+ "url": "https://phpunit.de/donate.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-12-04T05:05:53+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:08:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:49:45+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.7",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:52:27+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:10:38+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:52:38+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d89cc98761b8cb5a1a235a6b703ae50d34080e65",
+ "reference": "d89cc98761b8cb5a1a235a6b703ae50d34080e65",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
- "name": "Neuman Vong",
- "email": "neuman+pear@twilio.com",
- "role": "Developer"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
},
{
- "name": "Anant Narayanan",
- "email": "anant@php.net",
- "role": "Developer"
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
}
],
- "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
- "homepage": "https://github.com/firebase/php-jwt",
- "time": "2017-06-27T22:17:23+00:00"
+ "time": "2020-09-28T05:24:23+00:00"
},
{
- "name": "google/apiclient",
- "version": "v2.2.2",
+ "name": "sebastian/global-state",
+ "version": "5.0.2",
"source": {
"type": "git",
- "url": "https://github.com/googleapis/google-api-php-client.git",
- "reference": "4e0fd83510e579043e10e565528b323b7c2b3c81"
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "a90ccbddffa067b51f574dea6eb25d5680839455"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/4e0fd83510e579043e10e565528b323b7c2b3c81",
- "reference": "4e0fd83510e579043e10e565528b323b7c2b3c81",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/a90ccbddffa067b51f574dea6eb25d5680839455",
+ "reference": "a90ccbddffa067b51f574dea6eb25d5680839455",
"shasum": ""
},
"require": {
- "firebase/php-jwt": "~2.0|~3.0|~4.0|~5.0",
- "google/apiclient-services": "~0.13",
- "google/auth": "^1.0",
- "guzzlehttp/guzzle": "~5.3.1|~6.0",
- "guzzlehttp/psr7": "^1.2",
- "monolog/monolog": "^1.17",
- "php": ">=5.4",
- "phpseclib/phpseclib": "~0.3.10|~2.0"
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
},
"require-dev": {
- "cache/filesystem-adapter": "^0.3.2",
- "phpunit/phpunit": "~4.8.36",
- "squizlabs/php_codesniffer": "~2.3",
- "symfony/css-selector": "~2.1",
- "symfony/dom-crawler": "~2.1"
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
},
"suggest": {
- "cache/filesystem-adapter": "For caching certs and tokens (using Google_Client::setCache)"
+ "ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.x-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Google_": "src/"
- },
"classmap": [
- "src/Google/Service/"
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "Apache-2.0"
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
],
- "description": "Client library for Google APIs",
- "homepage": "http://developers.google.com/api-client-library/php",
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
- "google"
+ "global state"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2018-06-20T15:52:20+00:00"
+ "time": "2020-10-26T15:55:19+00:00"
},
{
- "name": "google/apiclient-services",
- "version": "v0.94",
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.3",
"source": {
"type": "git",
- "url": "https://github.com/googleapis/google-api-php-client-services.git",
- "reference": "9686fc7dfd5b92dd9ff075bdcd674cd5c15294be"
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/9686fc7dfd5b92dd9ff075bdcd674cd5c15294be",
- "reference": "9686fc7dfd5b92dd9ff075bdcd674cd5c15294be",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"shasum": ""
},
"require": {
- "php": ">=5.4"
+ "nikic/php-parser": "^4.6",
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "~4.8"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
- "autoload": {
- "psr-0": {
- "Google_Service_": "src"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
- "Apache-2.0"
+ "BSD-3-Clause"
],
- "description": "Client library for Google APIs",
- "homepage": "http://developers.google.com/api-client-library/php",
- "keywords": [
- "google"
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2019-04-13T00:23:08+00:00"
+ "time": "2020-11-28T06:42:11+00:00"
},
{
- "name": "google/auth",
- "version": "v1.5.1",
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
"source": {
"type": "git",
- "url": "https://github.com/googleapis/google-auth-library-php.git",
- "reference": "0f75e20e7392e863f5550ed2c2d3d50af21710fb"
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/0f75e20e7392e863f5550ed2c2d3d50af21710fb",
- "reference": "0f75e20e7392e863f5550ed2c2d3d50af21710fb",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
"shasum": ""
},
"require": {
- "firebase/php-jwt": "~2.0|~3.0|~4.0|~5.0",
- "guzzlehttp/guzzle": "~5.3.1|~6.0",
- "guzzlehttp/psr7": "^1.2",
- "php": ">=5.4",
- "psr/cache": "^1.0",
- "psr/http-message": "^1.0"
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^1.11",
- "guzzlehttp/promises": "0.1.1|^1.3",
- "phpseclib/phpseclib": "^2",
- "phpunit/phpunit": "^4.8.36|^5.7",
- "sebastian/comparator": ">=1.2.3"
- },
- "suggest": {
- "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings. Please require version ^2."
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
- "autoload": {
- "psr-4": {
- "Google\\Auth\\": "src"
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
- "Apache-2.0"
+ "BSD-3-Clause"
],
- "description": "Google Auth Library for PHP",
- "homepage": "http://github.com/google/google-auth-library-php",
- "keywords": [
- "Authentication",
- "google",
- "oauth2"
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2019-04-16T18:48:28+00:00"
+ "time": "2020-10-26T13:12:34+00:00"
},
{
- "name": "guzzlehttp/guzzle",
- "version": "6.3.3",
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/guzzle.git",
- "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
- "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
"shasum": ""
},
"require": {
- "guzzlehttp/promises": "^1.0",
- "guzzlehttp/psr7": "^1.4",
- "php": ">=5.5"
+ "php": ">=7.3"
},
"require-dev": {
- "ext-curl": "*",
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
- "psr/log": "^1.0"
- },
- "suggest": {
- "psr/log": "Required for using the Log middleware"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "6.3-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
- "files": [
- "src/functions_include.php"
- ],
- "psr-4": {
- "GuzzleHttp\\": "src/"
- }
+ "classmap": [
+ "src/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
}
],
- "description": "Guzzle is a PHP HTTP client library",
- "homepage": "http://guzzlephp.org/",
- "keywords": [
- "client",
- "curl",
- "framework",
- "http",
- "http client",
- "rest",
- "web service"
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2018-04-22T15:46:56+00:00"
+ "time": "2020-10-26T13:14:26+00:00"
},
{
- "name": "guzzlehttp/promises",
- "version": "v1.3.1",
+ "name": "sebastian/recursion-context",
+ "version": "4.0.4",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/promises.git",
- "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
- "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
"shasum": ""
},
"require": {
- "php": ">=5.5.0"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "^4.0"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
- "psr-4": {
- "GuzzleHttp\\Promise\\": "src/"
- },
- "files": [
- "src/functions_include.php"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
}
],
- "description": "Guzzle promises library",
- "keywords": [
- "promise"
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2016-12-20T10:07:11+00:00"
+ "time": "2020-10-26T13:17:30+00:00"
},
{
- "name": "guzzlehttp/psr7",
- "version": "1.5.2",
+ "name": "sebastian/resource-operations",
+ "version": "3.0.3",
"source": {
"type": "git",
- "url": "https://github.com/guzzle/psr7.git",
- "reference": "9f83dded91781a01c63574e387eaa769be769115"
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
- "reference": "9f83dded91781a01c63574e387eaa769be769115",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"shasum": ""
},
"require": {
- "php": ">=5.4.0",
- "psr/http-message": "~1.0",
- "ralouphie/getallheaders": "^2.0.5"
- },
- "provide": {
- "psr/http-message-implementation": "1.0"
+ "php": ">=7.3"
},
"require-dev": {
- "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
+ "phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.5-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
- "psr-4": {
- "GuzzleHttp\\Psr7\\": "src/"
- },
- "files": [
- "src/functions_include.php"
+ "classmap": [
+ "src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
- },
- {
- "name": "Tobias Schultze",
- "homepage": "https://github.com/Tobion"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
}
],
- "description": "PSR-7 message implementation that also provides common utility methods",
- "keywords": [
- "http",
- "message",
- "psr-7",
- "request",
- "response",
- "stream",
- "uri",
- "url"
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2018-12-04T20:46:45+00:00"
+ "time": "2020-09-28T06:45:17+00:00"
},
{
- "name": "monolog/monolog",
- "version": "1.24.0",
+ "name": "sebastian/type",
+ "version": "2.3.1",
"source": {
"type": "git",
- "url": "https://github.com/Seldaek/monolog.git",
- "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266"
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
- "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/81cd61ab7bbf2de744aba0ea61fae32f721df3d2",
+ "reference": "81cd61ab7bbf2de744aba0ea61fae32f721df3d2",
"shasum": ""
},
"require": {
- "php": ">=5.3.0",
- "psr/log": "~1.0"
- },
- "provide": {
- "psr/log-implementation": "1.0.0"
+ "php": ">=7.3"
},
"require-dev": {
- "aws/aws-sdk-php": "^2.4.9 || ^3.0",
- "doctrine/couchdb": "~1.0@dev",
- "graylog2/gelf-php": "~1.0",
- "jakub-onderka/php-parallel-lint": "0.9",
- "php-amqplib/php-amqplib": "~2.4",
- "php-console/php-console": "^3.1.3",
- "phpunit/phpunit": "~4.5",
- "phpunit/phpunit-mock-objects": "2.3.0",
- "ruflin/elastica": ">=0.90 <3.0",
- "sentry/sentry": "^0.13",
- "swiftmailer/swiftmailer": "^5.3|^6.0"
- },
- "suggest": {
- "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
- "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
- "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
- "ext-mongo": "Allow sending log messages to a MongoDB server",
- "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
- "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
- "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
- "php-console/php-console": "Allow sending log messages to Google Chrome",
- "rollbar/rollbar": "Allow sending log messages to Rollbar",
- "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
- "sentry/sentry": "Allow sending log messages to a Sentry server"
+ "phpunit/phpunit": "^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "2.3-dev"
}
},
"autoload": {
- "psr-4": {
- "Monolog\\": "src/Monolog"
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:18:59+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
}
},
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "Jordi Boggiano",
- "email": "j.boggiano@seld.be",
- "homepage": "http://seld.be"
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
}
],
- "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
- "homepage": "http://github.com/Seldaek/monolog",
- "keywords": [
- "log",
- "logging",
- "psr-3"
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
],
- "time": "2018-11-05T09:00:11+00:00"
+ "time": "2020-09-28T06:39:44+00:00"
},
{
- "name": "phpseclib/phpseclib",
- "version": "2.0.15",
+ "name": "sirbrillig/phpcs-variable-analysis",
+ "version": "v2.11.0",
"source": {
"type": "git",
- "url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0"
+ "url": "https://github.com/sirbrillig/phpcs-variable-analysis.git",
+ "reference": "e76e816236f401458dd8e16beecab905861b5867"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/11cf67cf78dc4acb18dc9149a57be4aee5036ce0",
- "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0",
+ "url": "https://api.github.com/repos/sirbrillig/phpcs-variable-analysis/zipball/e76e816236f401458dd8e16beecab905861b5867",
+ "reference": "e76e816236f401458dd8e16beecab905861b5867",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=5.4.0",
+ "squizlabs/php_codesniffer": "^3.5"
},
"require-dev": {
- "phing/phing": "~2.7",
- "phpunit/phpunit": "^4.8.35|^5.7|^6.0",
- "sami/sami": "~2.0",
- "squizlabs/php_codesniffer": "~2.0"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+ "limedeck/phpunit-detailed-printer": "^3.1 || ^4.0 || ^5.0",
+ "phpstan/phpstan": "^0.11.8",
+ "phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0",
+ "sirbrillig/phpcs-import-detection": "^1.1"
},
- "suggest": {
- "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
- "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
- "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
- "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
- },
- "type": "library",
+ "type": "phpcodesniffer-standard",
"autoload": {
- "files": [
- "phpseclib/bootstrap.php"
- ],
"psr-4": {
- "phpseclib\\": "phpseclib/"
+ "VariableAnalysis\\": "VariableAnalysis/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-2-Clause"
],
"authors": [
{
- "name": "Jim Wigginton",
- "email": "terrafrost@php.net",
- "role": "Lead Developer"
- },
- {
- "name": "Patrick Monnerat",
- "email": "pm@datasphere.ch",
- "role": "Developer"
- },
- {
- "name": "Andreas Fischer",
- "email": "bantu@phpbb.com",
- "role": "Developer"
+ "name": "Sam Graham",
+ "email": "php-codesniffer-variableanalysis@illusori.co.uk"
},
{
- "name": "Hans-Jürgen Petrich",
- "email": "petrich@tronic-media.com",
- "role": "Developer"
- },
- {
- "name": "Graham Campbell",
- "email": "graham@alt-three.com",
- "role": "Developer"
+ "name": "Payton Swick",
+ "email": "payton@foolord.com"
}
],
- "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
- "homepage": "http://phpseclib.sourceforge.net",
- "keywords": [
- "BigInteger",
- "aes",
- "asn.1",
- "asn1",
- "blowfish",
- "crypto",
- "cryptography",
- "encryption",
- "rsa",
- "security",
- "sftp",
- "signature",
- "signing",
- "ssh",
- "twofish",
- "x.509",
- "x509"
- ],
- "time": "2019-03-10T16:53:45+00:00"
- },
- {
- "name": "psr/cache",
- "version": "1.0.1",
+ "description": "A PHPCS sniff to detect problems with variables.",
+ "time": "2021-03-09T22:32:14+00:00"
+ },
+ {
+ "name": "squizlabs/php_codesniffer",
+ "version": "3.6.0",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/cache.git",
- "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8"
+ "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+ "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8",
- "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
+ "reference": "ffced0d2c8fa8e6cdc4d695a743271fab6c38625",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "ext-simplexml": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": ">=5.4.0"
},
+ "require-dev": {
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+ },
+ "bin": [
+ "bin/phpcs",
+ "bin/phpcbf"
+ ],
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Cache\\": "src/"
+ "dev-master": "3.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "MIT"
+ "BSD-3-Clause"
],
"authors": [
{
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "name": "Greg Sherwood",
+ "role": "lead"
}
],
- "description": "Common interface for caching libraries",
+ "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
- "cache",
- "psr",
- "psr-6"
+ "phpcs",
+ "standards"
],
- "time": "2016-08-06T20:24:11+00:00"
+ "time": "2021-04-09T00:54:41+00:00"
},
{
- "name": "psr/http-message",
- "version": "1.0.1",
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.23.0",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/http-message.git",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce",
+ "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
- "Psr\\Http\\Message\\": "src/"
- }
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -632,48 +2498,118 @@
],
"authors": [
{
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
}
],
- "description": "Common interface for HTTP messages",
- "homepage": "https://github.com/php-fig/http-message",
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
"keywords": [
- "http",
- "http-message",
- "psr",
- "psr-7",
- "request",
- "response"
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-02-19T12:13:01+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/75a63c33a8577608444246075ea0af0d052e452a",
+ "reference": "75a63c33a8577608444246075ea0af0d052e452a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
],
- "time": "2016-08-06T14:39:51+00:00"
+ "time": "2020-07-12T23:59:07+00:00"
},
{
- "name": "psr/log",
- "version": "1.1.0",
+ "name": "webmozart/assert",
+ "version": "1.10.0",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
+ "url": "https://github.com/webmozarts/assert.git",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
+ "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
+ "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": "^7.2 || ^8.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<0.12.20",
+ "vimeo/psalm": "<4.6.1 || 4.6.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.10-dev"
}
},
"autoload": {
"psr-4": {
- "Psr\\Log\\": "Psr/Log/"
+ "Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -682,66 +2618,73 @@
],
"authors": [
{
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
}
],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
+ "description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
- "log",
- "psr",
- "psr-3"
+ "assert",
+ "check",
+ "validate"
],
- "time": "2018-11-20T15:27:04+00:00"
+ "time": "2021-03-09T10:59:23+00:00"
},
{
- "name": "ralouphie/getallheaders",
- "version": "2.0.5",
+ "name": "wp-coding-standards/wpcs",
+ "version": "2.3.0",
"source": {
"type": "git",
- "url": "https://github.com/ralouphie/getallheaders.git",
- "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
+ "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
- "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
+ "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/7da1894633f168fe244afc6de00d141f27517b62",
+ "reference": "7da1894633f168fe244afc6de00d141f27517b62",
"shasum": ""
},
"require": {
- "php": ">=5.3"
+ "php": ">=5.4",
+ "squizlabs/php_codesniffer": "^3.3.1"
},
"require-dev": {
- "phpunit/phpunit": "~3.7.0",
- "satooshi/php-coveralls": ">=1.0"
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || ^0.6",
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpcsstandards/phpcsdevtools": "^1.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
},
- "type": "library",
- "autoload": {
- "files": [
- "src/getallheaders.php"
- ]
+ "suggest": {
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.6 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically."
},
+ "type": "phpcodesniffer-standard",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
- "name": "Ralph Khattar",
- "email": "ralph.khattar@gmail.com"
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
}
],
- "description": "A polyfill for getallheaders.",
- "time": "2016-02-11T07:05:27+00:00"
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "wordpress"
+ ],
+ "time": "2020-05-13T23:57:56+00:00"
}
],
- "packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
- "platform": [],
- "platform-dev": []
+ "platform": {
+ "php": ">=7.1"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "1.1.0"
}
diff --git a/inc/classes/class-google-auth.php b/inc/classes/class-google-auth.php
deleted file mode 100644
index 9e45ee3a..00000000
--- a/inc/classes/class-google-auth.php
+++ /dev/null
@@ -1,475 +0,0 @@
-
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Inc;
-
-use WP_Google_Login\Inc\Traits\Singleton;
-
-/**
- * Class Google_Auth
- */
-class Google_Auth {
-
- use Singleton;
-
- /**
- * Google client instance.
- *
- * @var \Google_Client
- */
- protected $_client = false;
-
- /**
- * To store after login redirect URL.
- *
- * @var string
- */
- protected $_redirect_to = '';
-
- /**
- * Google_Auth constructor.
- */
- protected function __construct() {
-
- $this->_include_vendor();
-
- $this->_client = $this->_get_client();
-
- add_filter( 'authenticate', [ $this, 'authenticate_user' ] );
- add_filter( 'login_redirect', [ $this, 'get_login_redirect' ] );
- add_filter( 'registration_redirect', [ $this, 'get_login_redirect' ] );
- add_filter( 'allowed_redirect_hosts', [ $this, 'maybe_whitelist_subdomain' ] );
-
- }
-
- /**
- * To include vendor file.
- *
- * @return void
- */
- protected function _include_vendor() {
-
- $vendor_autoload = sprintf( '%s/vendor/autoload.php', WP_GOOGLE_LOGIN_PATH );
-
- $validate_file = validate_file( $vendor_autoload );
- // Function validate_file returns 2 for Windows drive path, so we check that as well.
- if ( ! empty( $vendor_autoload ) && file_exists( $vendor_autoload ) && ( 0 === $validate_file || 2 === $validate_file ) ) {
- require_once( $vendor_autoload ); // phpcs:ignore
- }
-
- }
-
- /**
- * To get instance of Google Client.
- *
- * @return \Google_Client
- */
- protected function _get_client() {
- $client_id = wp_google_login_get_client_id();
- $client_secret = wp_google_login_get_client_secret();
-
- // If we don't have client id and secret then bail out, plugin won't work.
- if ( empty( $client_id ) || empty( $client_secret ) ) {
- return;
- }
-
- $client = new \Google_Client();
- $client->setApplicationName( 'WP Google Login' );
-
- $client->setClientId( $client_id );
- $client->setClientSecret( $client_secret );
-
- $redirect_to = filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_URL );
- $redirect_to = ( ! empty( $redirect_to ) ) ? $redirect_to : admin_url();
-
- // If redirect_to url don't have host name then add that.
- $redirect_to = ( ! wp_parse_url( $redirect_to, PHP_URL_HOST ) ) ? home_url( $redirect_to ) : $redirect_to;
-
- $state = [
- 'redirect_to' => $redirect_to,
- 'blog_id' => get_current_blog_id(),
- ];
- $state = urlencode_deep( implode( '|', $state ) );
-
- $client->setState( $state );
-
- $login_url = $this->_get_login_url();
-
- $client->setRedirectUri( $login_url );
-
- return $client;
-
- }
-
- /**
- * Get login URL, on which user will redirect after authenticated from google.
- *
- * @return string Redirect URL.
- */
- protected function _get_login_url() {
-
- // By default we will use current site's login URL.
- $login_url = wp_login_url();
-
- // If it's multisite setup.
- // Then check if plugin is activate on network wide or if plugin is activate on main site
- // Then use main site login url.
- if ( is_multisite() && defined( 'BLOG_ID_CURRENT_SITE' ) ) {
-
- $mu_plugins = get_site_option( 'active_sitewide_plugins', [] );
-
- $plugins_activate_on_main_site = get_blog_option( BLOG_ID_CURRENT_SITE, 'active_plugins' );
-
- if ( ! empty( $mu_plugins[ WP_GOOGLE_LOGIN_PLUGIN_NAME ] ) || in_array( WP_GOOGLE_LOGIN_PLUGIN_NAME, $plugins_activate_on_main_site, true ) ) {
- $login_url = network_site_url( 'wp-login.php' ); // @codeCoverageIgnore
- }
- }
-
- return $login_url;
- }
-
- /**
- * To get user info from google auth token.
- *
- * @param string $token Auth token.
- *
- * @return array|\Exception|\Google_Service_Exception User info
- */
- protected function _get_user_from_token( $token ) {
-
- if ( empty( $token ) ) {
- return [];
- }
-
- $token = urldecode( $token );
-
- try {
-
- // @codeCoverageIgnoreStart
- // Ignoring because we cannot mock token and associate it with a user in test cases.
- $token = $this->_client->fetchAccessTokenWithAuthCode( $token );
-
- $oauthservice = new \Google_Service_Oauth2( $this->_client );
-
- $google_userinfo = $oauthservice->userinfo->get();
-
- $user_info = [
- 'user_email' => $google_userinfo->getEmail(),
- 'display_name' => $google_userinfo->getName(),
- 'first_name' => $google_userinfo->getGivenName(),
- 'last_name' => $google_userinfo->getFamilyName(),
- 'picture' => $google_userinfo->getPicture(),
- ];
-
- /**
- * This hook provides access token fetched by google sign-in.
- *
- * @since 1.0
- *
- * @param array $token Converted access token.
- * @param array $user_info User details fetched from this token.
- * @param object $client Google_Client object.
- */
- do_action( 'wp_google_login_token', $token, $user_info, $this->_client );
-
- return $user_info;
-
- // @codeCoverageIgnoreEnd
- } catch ( \Google_Service_Exception $exception ) {
- return $exception;
- }
-
- }
-
- /**
- * To get scopes.
- *
- * @return string
- */
- protected function _get_scopes() {
-
- $scopes = [
- 'email',
- 'profile',
- 'openid',
- ];
-
- /**
- * This hook can be used to add/change google API scope.
- * By setting different scopes, you can ask different permissions.
- *
- * @since 1.0
- *
- * @param array $scopes Scopes array.
- *
- * @return array Modified scopes.
- */
- $scopes = apply_filters( 'wp_google_login_scopes', $scopes );
-
- return implode( ' ', $scopes );
-
- }
-
- /**
- * Create user base on provided data.
- *
- * @param array $user_info User info,
- * user_email : User email address
- * display_name : Display name
- * first_name : First name
- * last_name : Last name.
- *
- * @return int
- */
- protected function _create_user( $user_info = [] ) {
-
- if ( empty( $user_info['user_email'] ) || ! is_email( $user_info['user_email'] ) ) {
- return 0;
- }
-
- $email = $user_info['user_email'];
-
- $user_login = sanitize_user( current( explode( '@', $email ) ), true );
-
- // Ensure username is unique.
- $append = 1;
- $o_user_login = $user_login;
-
- while ( username_exists( $user_login ) ) {
- $user_login = $o_user_login . $append;
- $append++;
- }
-
- $user_info['user_login'] = $user_login;
- $user_info['user_pass'] = wp_generate_password( 18 );
-
- $user_id = wp_insert_user( $user_info );
-
- return ( ! empty( $user_id ) && ! is_wp_error( $user_id ) ) ? $user_id : 0;
-
- }
-
- /**
- * To check if user can register or not.
- *
- * @return bool
- */
- protected function _can_users_register() {
- $options = get_option( 'wp_google_login_settings' );
-
- if ( defined( 'WP_GOOGLE_LOGIN_USER_REGISTRATION' ) ) {
- return (bool) WP_GOOGLE_LOGIN_USER_REGISTRATION;
- }
-
- $registration_enabled = ! empty( $options['registration_enabled'] ) ? (bool) $options['registration_enabled'] : false;
-
- if ( $registration_enabled ) {
- return true;
- }
-
- $can_user_register = get_option( 'users_can_register' );
-
- return ( ! empty( $can_user_register ) ) ? true : false;
- }
-
- /**
- * To check if given email address can be register or not.
- *
- * @param string $email Email address.
- *
- * @return bool True if it can register, Otherwise False.
- */
- protected function _can_register_with_email( $email ) {
-
- if ( empty( $email ) ) {
- return false;
- }
-
- $whitelisted_domains = wp_google_login_get_whitelisted_domains();
-
- /**
- * If Const is not defined or empty,
- * then allow all domain.
- */
- if ( empty( $whitelisted_domains ) ) {
- return true;
- }
-
- $email_parts = explode( '@', $email );
- $email_domain = ( ! empty( $email_parts[1] ) ) ? strtolower( trim( $email_parts[1] ) ) : '';
-
- $whitelisted_domains = explode( ',', $whitelisted_domains );
- $whitelisted_domains = array_map( 'trim', $whitelisted_domains );
-
- $count = ( ! empty( $whitelisted_domains ) ) && is_array( $whitelisted_domains ) ? count( $whitelisted_domains ) : 1;
-
- for ( $i = 0; $i < ( $count - 1 ); $i++ ) {
-
- $whitelisted_domains[ $i ] = strtolower( trim( $whitelisted_domains[ $i ] ) );
- $whitelisted_domains[ $i ] = str_replace( 'www.', '', $whitelisted_domains[ $i ] );
-
- }
-
- $whitelisted_domains = array_unique( $whitelisted_domains );
-
- return ( ! empty( $email_domain ) && in_array( $email_domain, $whitelisted_domains, true ) ) ? true : false;
- }
-
- /**
- * To get google authentication URL.
- *
- * @return string
- */
- public function get_login_url() {
-
- $scopes = $this->_get_scopes();
- if ( ! is_null( $this->_client ) ) {
- $url = $this->_client->createAuthUrl( $scopes );
- }
-
- return $url;
- }
-
- /**
- * To authenticate user.
- *
- * @param null|\WP_User|\WP_Error $user WP_User if the user is authenticated.
- * WP_Error or null otherwise.
- *
- * @return null|\WP_User|\WP_Error WP_User if the user is authenticated.
- * WP_Error or null otherwise.
- */
- public function authenticate_user( $user = null ) {
-
- $is_mu_site = is_multisite();
-
- $token = Helper::filter_input( INPUT_GET, 'code', FILTER_SANITIZE_STRING );
- $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
- $state = urldecode( $state );
- $state = explode( '|', $state );
-
- $redirect_to = ( ! empty( $state[0] ) ) ? esc_url_raw( $state[0] ) : '';
- $blog_id = ( ! empty( $state[1] ) && 0 < intval( $state[1] ) ) ? intval( $state[1] ) : 0;
-
- if ( empty( $token ) ) {
- return $user;
- }
-
- // Set redirect URL. so we can redirect after login.
- $this->_redirect_to = $redirect_to;
-
- /**
- * If blog_id in state does not match current blog ID.
- * Then redirect to login page of request blog.
- * So that can take care of authentication.
- */
- if ( $is_mu_site && $blog_id !== get_current_blog_id() ) {
-
- $query_string = filter_input( INPUT_SERVER, 'QUERY_STRING', FILTER_SANITIZE_STRING );
-
- $blog_url = get_blog_option( $blog_id, 'siteurl' );
- $blog_login_url = sprintf( '%s/wp-login.php?%s', $blog_url, $query_string );
-
- wp_safe_redirect( $blog_login_url );
- // @codeCoverageIgnoreStart
- // Ignoring because cannot test exit.
- exit();
- // @codeCoverageIgnoreEnd
- }
-
- $user_info = $this->_get_user_from_token( $token );
-
- if ( ! is_array( $user_info ) || empty( $user_info['user_email'] ) || ! is_email( $user_info['user_email'] ) ) {
- return $user;
- }
-
- // @codeCoverageIgnoreStart
- // Ignoring because we cannot mock token and associate it with a user in test cases.
- $user = get_user_by( 'email', $user_info['user_email'] );
-
- // We found the user.
- if ( ! empty( $user ) && $user instanceof \WP_User ) {
-
- if ( ! $is_mu_site ) {
- return $user;
- }
-
- // Check for MU site.
- if ( ! empty( $blog_id ) && is_user_member_of_blog( $user->ID, $blog_id ) ) {
- return $user;
- }
- }
-
- // Check if user registration is allow or not.
- if ( ! $this->_can_users_register() ) {
- return new \WP_Error(
- 'wp_google_login_error',
- // translators: %s: User email.
- sprintf( __( 'User %s not registered in WordPress.', 'login-with-google' ), $user_info['user_email'] )
- );
- }
-
- // Check if email address is allowed or not.
- if ( ! $this->_can_register_with_email( $user_info['user_email'] ) ) {
- return new \WP_Error(
- 'wp_google_login_error',
- // translators: %s: User email.
- sprintf( __( 'User can not register with %s email address.', 'login-with-google' ), $user_info['user_email'] )
- );
- }
-
- // Let's create WP user first.
- if ( empty( $user ) || ! $user instanceof \WP_User ) {
- $user_id = $this->_create_user( $user_info );
- $user = get_user_by( 'id', $user_id );
- }
-
- if ( $is_mu_site ) {
- $default_user_role = get_blog_option( $blog_id, 'default_role', 'subscriber' );
- add_user_to_blog( $blog_id, $user->ID, $default_user_role );
- }
-
- return $user;
- // @codeCoverageIgnoreEnd
- }
-
- /**
- * To redirect to appropriate URL after auth with google.
- *
- * @param string $redirect_to Redirect to URL.
- *
- * @return string Redirect to URL.
- */
- public function get_login_redirect( $redirect_to ) {
- return ( ! empty( $this->_redirect_to ) ) ? $this->_redirect_to : $redirect_to;
- }
-
- /**
- * To whitelist domain where we going to redirect after authentication user with google.
- *
- * @param array $hosts Whitelisted domains.
- *
- * @return array Whitelisted domains.
- */
- public function maybe_whitelist_subdomain( $hosts = [] ) {
-
- $hosts = ( ! empty( $hosts ) && is_array( $hosts ) ) ? $hosts : [];
-
- if ( ! empty( $this->_redirect_to ) ) {
- $subdomain = wp_parse_url( $this->_redirect_to, PHP_URL_HOST );
-
- $hosts[] = $subdomain;
- }
-
- $hosts = array_unique( $hosts );
-
- return $hosts;
- }
-
-}
diff --git a/inc/classes/class-plugin.php b/inc/classes/class-plugin.php
deleted file mode 100644
index 0fa8db21..00000000
--- a/inc/classes/class-plugin.php
+++ /dev/null
@@ -1,82 +0,0 @@
-
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Inc;
-
-use WP_Google_Login\Inc\Traits\Singleton;
-
-/**
- * Class Plugin
- */
-class Plugin {
-
- use Singleton;
-
- /**
- * Instance of Google_Auth class.
- *
- * @var \WP_Google_Login\Inc\Google_Auth
- */
- protected $_google_auth = false;
-
- /**
- * Plugin constructor.
- */
- protected function __construct() {
- \WP_Google_Login\Inc\Settings::get_instance();
-
- $this->_google_auth = Google_Auth::get_instance();
-
- $this->_setup_hooks();
- }
-
- /**
- * To setup actions/filters.
- *
- * @return void
- */
- protected function _setup_hooks() {
-
- /**
- * Actions
- */
- add_action( 'login_enqueue_scripts', [ $this, 'login_enqueue_scripts' ] );
- add_action( 'login_form', [ $this, 'add_google_login_button' ] );
- add_action( 'register_form', [ $this, 'add_google_login_button' ] );
-
- }
-
- /**
- * To enqueue style and script for login page.
- *
- * @return void
- */
- public function login_enqueue_scripts() {
-
- wp_enqueue_script( 'wp_google_login_script', sprintf( '%s/assets/build/js/login.js', WP_GOOGLE_LOGIN_URL ), [], WP_GOOGLE_LOGIN_VERSION );
- wp_enqueue_style( 'wp_google_login_style', sprintf( '%s/assets/build/css/login.css', WP_GOOGLE_LOGIN_URL ), [], WP_GOOGLE_LOGIN_VERSION );
-
- }
-
- /**
- * To render google login button.
- *
- * @return void
- */
- public function add_google_login_button() {
-
- $template_path = sprintf( '%s/template/google-login-button.php', WP_GOOGLE_LOGIN_PATH );
- $login_url = $this->_google_auth->get_login_url();
-
- Helper::render_template( $template_path, [
- 'login_url' => $login_url,
- ] );
- }
-
-}
\ No newline at end of file
diff --git a/inc/classes/class-settings.php b/inc/classes/class-settings.php
deleted file mode 100644
index 64ae06e3..00000000
--- a/inc/classes/class-settings.php
+++ /dev/null
@@ -1,237 +0,0 @@
-setup_hooks();
- }
-
- /**
- * To setup actions/filters.
- *
- * @return void
- */
- public function setup_hooks() {
-
- /**
- * Actions
- */
- add_action( 'admin_menu', [ $this, 'add_admin_menu' ] );
- add_action( 'admin_init', [ $this, 'settings_init' ] );
- }
-
- /**
- * Add admin menu.
- *
- * @return void
- */
- public function add_admin_menu() {
- if ( current_user_can( 'manage_options' ) ) {
- add_filter( 'plugin_action_links', [ $this, 'plugin_action_links' ], 10, 2 );
- }
- add_options_page( __( 'Log in with Google', 'login-with-google' ), __( 'Log in with Google', 'login-with-google' ), 'manage_options', 'login-with-google', [
- $this,
- 'options_page'
- ] );
- }
-
- /**
- * Adds a "Settings" link to this plugin's entry on the plugin list.
- *
- * @param array $links Array of links for plugin actions.
- * @param string $file Path to the plugin file relative to the plugins directory.
- *
- * @return array $links Array of links for plugin actions.
- */
- public function plugin_action_links( $links, $file ) {
- if ( 'login-with-google/login-with-google.php' === $file ) {
- $links[] = "" . __( 'Settings', 'login-with-google' ) . ' ';
- }
-
- return $links;
- }
-
- /**
- * Add admin menu.
- *
- * @return void
- */
- public function settings_init() {
- register_setting( 'wp_google_login', 'wp_google_login_settings' );
-
- add_settings_section(
- 'wp_google_login_section',
- __( 'Log in with Google Settings', 'login-with-google' ),
- [ $this, 'settings_section_callback' ],
- 'wp_google_login'
- );
-
- add_settings_field(
- 'wp_google_login_client_id',
- __( 'Client ID', 'login-with-google' ),
- [ $this, 'wp_google_login_client_id_render' ],
- 'wp_google_login',
- 'wp_google_login_section',
- [ 'label_for' => 'client-id' ]
- );
-
- add_settings_field(
- 'wp_google_login_client_secret',
- __( 'Client Secret', 'login-with-google' ),
- [ $this, 'wp_google_login_client_secret_render' ],
- 'wp_google_login',
- 'wp_google_login_section',
- [ 'label_for' => 'client-secret' ]
- );
-
- add_settings_field(
- 'wp_google_login_whitelisted_domains',
- __( 'Whitelisted Domains', 'login-with-google' ),
- [ $this, 'wp_google_login_whitelisted_domains_render' ],
- 'wp_google_login',
- 'wp_google_login_section',
- [ 'label_for' => 'whitelisted-domains' ]
- );
-
- add_settings_field(
- 'wp_google_login_enable_registration',
- __( 'Create new user', 'login-with-google' ),
- [ $this, 'wp_google_login_enable_registrationr' ],
- 'wp_google_login',
- 'wp_google_login_section',
- [ 'label_for' => 'enable-registration' ]
- );
- }
-
- /**
- * Render Client ID settings field.
- *
- * @return void
- */
- public function wp_google_login_client_id_render() {
- $client_id = wp_google_login_get_client_id();
- $disabled = '';
- if ( defined( 'WP_GOOGLE_LOGIN_CLIENT_ID' ) ) {
- $disabled = 'disabled';
- }
- ?>
- value=''>
-
- %1s %3s .
',
- esc_html__( 'Create oAuth Client ID and Client Secret at', 'login-with-google' ),
- 'https://console.developers.google.com/apis/dashboard',
- 'console.developers.google.com'
- ) );
- ?>
-
-
- value=''>
-
- value=''>
-
-
- >
- value='1'>
-
-
- membership setting is off.', 'login-with-google' ),
- is_multisite() ? 'network/settings.php' : 'options-general.php'
- ) );
- ?>
-
-
-
-
-
- value pair for each `classname => instance` in self::$_instance
- * for each sub-class.
- */
- $called_class = get_called_class();
-
- if ( ! isset( $instance[ $called_class ] ) ) {
-
- $instance[ $called_class ] = new $called_class();
-
- /**
- * Dependent items can use the `wp_google_login_extend_singleton_init_{$called_class}` hook to execute code
- */
- do_action( sprintf( 'wp_google_login_extend_singleton_init_%s', $called_class ) );
-
- }
-
- return $instance[ $called_class ];
-
- }
-
-} // End trait
diff --git a/languages/login-with-google.pot b/languages/login-with-google.pot
new file mode 100644
index 00000000..40f3f62f
--- /dev/null
+++ b/languages/login-with-google.pot
@@ -0,0 +1,115 @@
+# Copyright (C) 2021 rtCamp
+# This file is distributed under the same license as the Login with Google plugin.
+msgid ""
+msgstr ""
+"Project-Id-Version: Login with Google 1.0.15\n"
+"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/login-with-google\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"POT-Creation-Date: 2021-06-29T16:19:14+05:30\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"X-Generator: WP-CLI 2.4.0\n"
+"X-Domain: login-with-google\n"
+
+#. Plugin Name of the plugin
+#: src/Modules/Settings.php:228
+msgid "Login with Google"
+msgstr ""
+
+#. Description of the plugin
+msgid "Allow users to login/register via Google."
+msgstr ""
+
+#. Author of the plugin
+msgid "rtCamp"
+msgstr ""
+
+#. Author URI of the plugin
+msgid "https://rtcamp.com"
+msgstr ""
+
+#: login-with-google.php:40
+msgid "Login with google Plugin requires PHP version 7.3 or higher. Please ask your server administrator to update your environment to latest PHP version"
+msgstr ""
+
+#: login-with-google.php:47
+msgid "The plugin Login with google has been deactivated"
+msgstr ""
+
+#. translators: %$s is replaced with requested service name.
+#: src/Container.php:61
+msgid "Invalid Service %s Passed to the container"
+msgstr ""
+
+#: src/Modules/Login.php:172
+msgid "Registration is not allowed."
+msgstr ""
+
+#. translators: %s is replaced with email ID of user trying to register
+#: src/Modules/Login.php:199
+msgid "Cannot register with this email: %s"
+msgstr ""
+
+#: src/Modules/Settings.php:93
+msgid "Log in with Google Settings"
+msgstr ""
+
+#: src/Modules/Settings.php:101
+msgid "Client ID"
+msgstr ""
+
+#: src/Modules/Settings.php:110
+msgid "Client Secret"
+msgstr ""
+
+#: src/Modules/Settings.php:119
+msgid "Create new user"
+msgstr ""
+
+#: src/Modules/Settings.php:128
+msgid "Whitelisted Domains"
+msgstr ""
+
+#: src/Modules/Settings.php:148
+msgid "Create oAuth Client ID and Client Secret at"
+msgstr ""
+
+#: src/Modules/Settings.php:185
+msgid "Create a new user account if it does not exist already"
+msgstr ""
+
+#. translators: %1s will be replaced by page link
+#: src/Modules/Settings.php:192
+msgid "If this setting is checked, a new user will be created even if membership setting is off."
+msgstr ""
+
+#: src/Modules/Settings.php:215
+msgid "Add each domain comma separated"
+msgstr ""
+
+#: src/Modules/Settings.php:227
+msgid "Login with Google settings"
+msgstr ""
+
+#: src/Modules/Shortcode.php:91
+msgid "Login with google"
+msgstr ""
+
+#: src/Utils/GoogleClient.php:98
+msgid "Access token must be set to make this API call"
+msgstr ""
+
+#: src/Utils/GoogleClient.php:173
+msgid "Could not retrieve the access token, please try again."
+msgstr ""
+
+#: src/Utils/GoogleClient.php:199
+msgid "Could not retrieve the user information, please try again."
+msgstr ""
+
+#: templates/google-login-button.php:9
+msgid "Log in with Google"
+msgstr ""
diff --git a/languages/wp-google-login.po b/languages/wp-google-login.po
deleted file mode 100644
index 8b9f83e9..00000000
--- a/languages/wp-google-login.po
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2020 rtCamp
-# This file is distributed under the same license as the WP Google Login plugin.
-msgid ""
-msgstr ""
-"Project-Id-Version: WP Google Login 1.0\n"
-"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/login-with-google\n"
-"Last-Translator: FULL NAME \n"
-"Language-Team: LANGUAGE \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2020-04-29T09:17:01+00:00\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"X-Generator: WP-CLI 2.4.0\n"
-"X-Domain: login-with-google\n"
-
-#. Plugin Name of the plugin
-msgid "Log in with Google"
-msgstr ""
-
-#. Plugin URI of the plugin
-msgid "https://github.com/rtCamp/login-with-google"
-msgstr ""
-
-#. Description of the plugin
-msgid "Minimal plugin which allows WP user to login with google."
-msgstr ""
-
-#. Author of the plugin
-msgid "rtCamp"
-msgstr ""
-
-#. Author URI of the plugin
-msgid "https://rtcamp.com"
-msgstr ""
-
-#. translators: %s: User email.
-#: inc/classes/class-google-auth.php:398
-msgid "User %s not registered in WordPress."
-msgstr ""
-
-#. translators: %s: User email.
-#: inc/classes/class-google-auth.php:407
-msgid "User can not register with %s email address."
-msgstr ""
-
-#: template/google-login-button.php:10
-msgid "Log in with Google"
-msgstr ""
diff --git a/login-with-google.php b/login-with-google.php
index 2433016a..851d8496 100644
--- a/login-with-google.php
+++ b/login-with-google.php
@@ -1,35 +1,133 @@
Please ask your server administrator to update your environment to latest PHP version',
+ 'login-with-google'
+ );
+
+ printf(
+ '',
+ esc_html__(
+ 'The plugin Login with google has been deactivated',
+ 'login-with-google'
+ ),
+ wp_kses( $message, [ 'br' => true ] )
+ );
+
+ deactivate_plugins( plugin_basename( __FILE__ ) );
+ }
+ );
+ }
-// Missing vendor autoload file or invalid file path.
-$validate_file = validate_file( $vendor_autoload );
-// Function validate_file returns 2 for Windows drive path, so we check that as well.
-if ( empty( $vendor_autoload ) || ! file_exists( $vendor_autoload ) || ( 0 !== $validate_file && 2 !== $validate_file ) ) {
return;
}
+/**
+ * Autoload the dependencies.
+ *
+ * @return bool
+ */
+function autoload(): bool {
+ static $done;
+ if ( is_bool( $done ) ) {
+ return $done;
+ }
+
+ if ( is_readable( __DIR__ . '/vendor/autoload.php' ) ) {
+ require_once __DIR__ . '/vendor/autoload.php';
+ $done = true;
-// We already making sure that file is exists and valid.
-require_once plugin_dir_path( __FILE__ ) . 'autoloader.php';
-require_once plugin_dir_path( __FILE__ ) . 'inc/functions.php';
+ return true;
+ }
+ $done = false;
+
+ return false;
+}
-\WP_Google_Login\Inc\Plugin::get_instance();
+/**
+ * Do not do anything if composer install
+ * is not run.
+ */
+if ( ! autoload() ) {
+ return;
+}
+
+/**
+ * Return the container instance.
+ */
+function container(): Container {
+ static $container;
+
+ if ( null !== $container ) {
+ return $container;
+ }
+
+ $container = new Container( new PimpleContainer() );
+
+ return $container;
+}
+
+/**
+ * Return the Plugin instance.
+ *
+ * @return Plugin
+ */
+function plugin(): Plugin {
+ static $plugin;
+
+ if ( null !== $plugin ) {
+ return $plugin;
+ }
+
+ $plugin = new Plugin( container() );
+ return $plugin;
+}
+
+/**
+ * Let the magic happen by
+ * running the plugin.
+ */
+add_action(
+ 'plugins_loaded',
+ function() {
+ plugin()->run();
+ },
+ 100
+);
diff --git a/phpcs.xml b/phpcs.xml
index 0fa987fe..9fea8e34 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -1,12 +1,60 @@
-
-
-
-
-
- warning
+
+
+
+
+
+
+
+ src
+ login-with-google.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tests/*
+
+
+
+
+
+
- */node_modules/*
- */vendor/*
- */assets/build/*
- */tests/*
+
+
+ tests/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */vendor/*
+ */node_modules/*
+ /lib/*
+ */tests/*
+ */.github/*
+ */.scripts/*
diff --git a/phpunit.xml b/phpunit.xml
deleted file mode 100644
index badcdd32..00000000
--- a/phpunit.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
- ./tests/
-
-
-
-
- ./inc/classes/
-
-
-
-
-
-
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 00000000..1e843f61
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,22 @@
+
+
+
+
+ src
+
+
+ vendor/
+ templates/
+ src/Utils/Helper.php
+
+
+
+
+
+
+
+ tests/php/Unit
+
+
+
+
diff --git a/readme.txt b/readme.txt
index 155ee684..6bda3c07 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,95 +4,116 @@ Donate link: https://rtcamp.com/
Tags: Google login, sign in, sso, oauth, authentication, sign-in, single sign-on, log in
Requires at least: 5.0
Tested up to: 5.8.1
-Requires PHP: 7.0
-Stable tag: 1.0.10
+Requires PHP: 7.3
+Stable tag: 1.2.2
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
-
+
Minimal plugin that allows WordPress users to log in using Google.
-
+
== Description ==
-Minimal plugin that allows WordPress users to log in using Google.
+Ultra minimal plugin to let your users login to WordPress applications using their Google accounts. No more remembering hefty passwords!
-= Setup =
+### Initial Setup
1. Create a project from [Google Developers Console](https://console.developers.google.com/apis/dashboard) if none exists.
+
+
2. Go to **Credentials** tab, then create credential for OAuth client.
* Application type will be **Web Application**
* Add `YOUR_DOMAIN/wp-login.php` in **Authorized redirect URIs**
+
+
3. This will give you **Client ID** and **Secret key**.
+
+
4. Input these values either in `WP Admin > Settings > WP Google Login`, or in `wp-config.php` using the following code snippet:
-`
+```
define( 'WP_GOOGLE_LOGIN_CLIENT_ID', 'YOUR_GOOGLE_CLIENT_ID' );
define( 'WP_GOOGLE_LOGIN_SECRET', 'YOUR_SECRET_KEY' );
-`
+````
-= How to enable automatic user registration =
+### Browser support
+[These browsers are supported](https://developers.google.com/identity/gsi/web/guides/supported-browsers). Note, for example, that One Tap Login is not supported in Safari.
+
+### How to enable automatic user registration
You can enable user registration either by
-- Checking `Settings > WP Google Login > Enable Google Login Registration`
+- Enabling *Settings > WP Google Login > Enable Google Login Registration*
+
+
OR
-- Adding `define( 'WP_GOOGLE_LOGIN_USER_REGISTRATION', 'true' );` in wp-config.php file.
-Note: If the checkbox is ON then, it will register valid Google users even when WordPress default setting, under `Settings > General Settings > Membership > Anyone can register` checkbox is OFF.
-= How to restrict user registration to one or more domain(s) =
+- Adding
+```
+define( 'WP_GOOGLE_LOGIN_USER_REGISTRATION', 'true' );
+```
+in wp-config.php file.
-By default, when you enable user registration via constant `WP_GOOGLE_LOGIN_USER_REGISTRATION` or enable `Settings > WP Google Login > Enable Google Login Registration`, it will create a user for any Google login (including gmail.com users). If you are planning to use this plugin on a private, internal site, then you may like to restrict user registration to users under a single Google Suite organization. This configuration variable does that.
+**Note:** If the checkbox is ON then, it will register valid Google users even when WordPress default setting, under
-Add your domain name, without any schema prefix and `www,` as the value of `WP_GOOGLE_LOGIN_WHITELIST_DOMAINS` constant or in the settings `Settings > WP Google Login > Whitelisted Domains`. You can whitelist multiple domains. Please separate domains with commas. See the below example to know how to do it via constants:
+*Settings > General Settings > Membership > Anyone can register* checkbox
-`define( 'WP_GOOGLE_LOGIN_WHITELIST_DOMAINS', 'example.com,sample.com' );`
+is OFF.
-**Note:** If a user already exists, they **will be allowed to login with Google** regardless of whether their domain is whitelisted or not. Whitelisting will only prevent users from **registering** with email addresses from non-whitelisted domains.
+### Restrict user registration to one or more domain(s)
-= Hooks =
+By default, when you enable user registration via constant `WP_GOOGLE_LOGIN_USER_REGISTRATION` or enable *Settings > WP Google Login > Enable Google Login Registration*, it will create a user for any Google login (including gmail.com users). If you are planning to use this plugin on a private, internal site, then you may like to restrict user registration to users under a single Google Suite organization. This configuration variable does that.
-Action `wp_google_login_token`
-This action provides access token received after Google login.
-**Parameters:**
+Add your domain name, without any schema prefix and `www,` as the value of `WP_GOOGLE_LOGIN_WHITELIST_DOMAINS` constant or in the settings `Settings > WP Google Login > Whitelisted Domains`. You can whitelist multiple domains. Please separate domains with commas. See the below example to know how to do it via constants:
+
+```
+define( 'WP_GOOGLE_LOGIN_WHITELIST_DOMAINS', 'example.com,sample.com' );
+```
-* `token` (Array): Converted token using `fetchAccessTokenWithAuthCode` method of `Google_Client` class.
-* `user_info` (Array): Details of user after login.
-* `client` (Object): `Google_Client` object in use.
+**Note:** If a user already exists, they **will be allowed to login with Google** regardless of whether their domain is whitelisted or not. Whitelisting will only prevent users from **registering** with email addresses from non-whitelisted domains.
+
+### Hooks
Filter `wp_google_login_scopes`
This filter can be used to filter existing scope used in Google Sign in.
You can ask for additional permission while user logs in.
This filter will provide 1 parameter `scopes` in callback, which contains array of scopes.
-= wp-config.php parameters list =
+#### wp-config.php parameters list
* `WP_GOOGLE_LOGIN_CLIENT_ID` (string): Google client ID of your application.
+
+
* `WP_GOOGLE_LOGIN_SECRET` (string): Secret key of your application
+
+
* `WP_GOOGLE_LOGIN_USER_REGISTRATION` (boolean) (optional): Set `true` If you want to enable new user registration. By default, user registration defers to `Settings > General Settings > Membership` if constant is not set.
+
+
* `WP_GOOGLE_LOGIN_WHITELIST_DOMAINS` (string) (optional): Domain names, if you want to restrict login with your custom domain. By default, it will allow all domains. You can whitelist multiple domains.
-= BTW, We're Hiring! =
+### BTW, We're Hiring!
-[Join us at rtCamp, we specialize in providing high performance enterprise WordPress solutions](https://rtcamp.com/)
+[ ](https://rtcamp.com/careers/)
== Installation ==
-
+
1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Follow "Setup" instructions in ReadMe to configure credentials from Google Developers Console.
-
+
== Frequently Asked Questions ==
-
+
= Reporting a bug 🐞 =
-
-Before creating a new issue, do browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for resolution or upcoming fixes.
+
+Before creating a new issue, do browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for resolution or upcoming fixes.
If you still need to [log an issue](https://github.com/rtCamp/login-with-google/issues/new), making sure to include as much detail as you can, including clear steps to reproduce the issue, if possible.
-
+
= Creating a pull request =
-
+
Want to contribute a new feature? Start a conversation by [logging an issue](https://github.com/rtCamp/login-with-google/issues).
-Once you're ready to send a pull request, please run through the following checklist:
+Once you're ready to send a pull request, please run through the following checklist:
1. Browse through the [existing issues](https://github.com/rtCamp/login-with-google/issues) for anything related to what you want to work on. If you don't find any related issues, open a new one.
@@ -108,29 +129,52 @@ Once you're ready to send a pull request, please run through the following check
= Unit testing =
-- Setup local unit test environment by running script from terminal
-
-`./bin/install-wp-tests.sh [db-host] [wp-version] [skip-database-creation]`
+- Clone the plugin from [repository](https://github.com/rtCamp/login-with-google).
-- Execute `phpunit` in terminal from repository to run all test cases.
+- Run `composer install && composer tests:unit` to run unit tests.
-- Execute `phpunit ./tests/inc/test-class.php` in terminal with file path to run specific tests.
-
== Screenshots ==
-
+
1. Login screen with Google option added.
2. Plugin settings screen.
3. Settings within Google Developer Console.
-
+
== Changelog ==
+= 1.2.2 =
+* Maintenance release.
+
+= 1.2.1 =
+* Feature: Provide filter for client arguments: rtcamp.google_client_args
+
+= 1.2 =
+* Feature: One-Tap Login setting for supported browsers.
+
+= 1.1 =
+* Feature: Add shortcode `[google_login]` with optional attributes: `[google_login button_text="Login with Google" force_display="no" redirect_to="https://example.url/page"]`.
+* Feature: Replace third-party oAuth client with custom GoogleClient class.
+* Fix: Identification of state value, whether a given oAuth login is relevant to this plugin.
+* Remove: Google oAuth library from composer.
+
+= 1.0.14 =
+* Revert Login with GitHub state fix.
+
+= 1.0.13 =
+* Fix login issue related to oAuth state.
+
+= 1.0.12 =
+* Fix conflict with Login with GitHub plugin.
+
+= 1.0.11 =
+* Add 'login_with_google/client_arguments' filter for Google_Client arguments.
+
= 1.0.10 =
-* Fix issue where JS/CSS were not loding.
+* Fix issue where JS/CSS were not loading.
= 1.0.9 =
* Initial release.
-
+
== Upgrade Notice ==
-= 1.0.10 =
-* Fix issue where JS/CSS were not loding.
+= 1.2.1 =
+* Feature: Provide filter for client arguments: rtcamp.google_client_args
diff --git a/src/Container.php b/src/Container.php
new file mode 100644
index 00000000..0eaf5bbd
--- /dev/null
+++ b/src/Container.php
@@ -0,0 +1,188 @@
+container = $container;
+ }
+
+ /**
+ * Get the service object.
+ *
+ * @param string $service Service object in need.
+ *
+ * @return object
+ *
+ * @throws InvalidArgumentException Exception for invalid service.
+ */
+ public function get( string $service ) {
+ if ( ! in_array( $service, $this->container->keys() ) ) {
+ /* translators: %$s is replaced with requested service name. */
+ throw new InvalidArgumentException( sprintf( __( 'Invalid Service %s Passed to the container', 'login-with-google' ), $service ) );
+ }
+
+ return $this->container[ $service ];
+ }
+
+ /**
+ * Define common services in container.
+ *
+ * All the module specific services will be defined inside
+ * respective module's container.
+ *
+ * @codeCoverageIgnore
+ *
+ * @return void
+ */
+ public function define_services(): void {
+ /**
+ * Define Settings service to add settings page and retrieve setting values.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return Settings
+ */
+ $this->container['settings'] = function( PimpleContainer $c ) {
+ return new Settings();
+ };
+
+ /**
+ * Define the login flow service.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return Login
+ */
+ $this->container['login_flow'] = function( PimpleContainer $c ) {
+ return new Login( $c['gh_client'], $c['authenticator'] );
+ };
+
+ /**
+ * Define a service for Google OAuth client.
+ *
+ * @param PimpleContainer $c Pimple container instance.
+ *
+ * @return GoogleClient
+ */
+ $this->container['gh_client'] = function ( PimpleContainer $c ) {
+ $settings = $c['settings'];
+
+ return new GoogleClient(
+ [
+ 'client_id' => $settings->client_id,
+ 'client_secret' => $settings->client_secret,
+ 'redirect_uri' => wp_login_url(),
+ ]
+ );
+ };
+
+ /**
+ * Define Assets service to add styles or script.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return Assets
+ */
+ $this->container['assets'] = function ( PimpleContainer $c ) {
+ return new Assets();
+ };
+
+ /**
+ * Define Shortcode service to register shortcode for google login.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return Shortcode
+ */
+ $this->container['shortcode'] = function ( PimpleContainer $c ) {
+ return new Shortcode( $c['gh_client'], $c['assets'] );
+ };
+
+ /**
+ * Define Token Verifier Service.
+ *
+ * Useful in verifying JWT Auth token.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return TokenVerifier
+ */
+ $this->container['token_verifier'] = function ( PimpleContainer $c ) {
+ return new TokenVerifier( $c['settings'] );
+ };
+
+ /**
+ * One Tap Login Service.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return OneTapLogin
+ */
+ $this->container['one_tap_login'] = function ( PimpleContainer $c ) {
+ return new OneTapLogin( $c['settings'], $c['token_verifier'], $c['gh_client'], $c['authenticator'] );
+ };
+
+ /**
+ * Authenticator utility.
+ *
+ * @param PimpleContainer $c Pimple container object.
+ *
+ * @return Authenticator
+ */
+ $this->container['authenticator'] = function ( PimpleContainer $c ) {
+ return new Authenticator( $c['settings'] );
+ };
+
+ /**
+ * Define any additional services.
+ *
+ * @param ContainerInterface $container Container object.
+ *
+ * @since 1.0.0
+ */
+ do_action( 'rtcamp.google_login_services', $this );
+ }
+}
diff --git a/src/Interfaces/Container.php b/src/Interfaces/Container.php
new file mode 100644
index 00000000..b83e9613
--- /dev/null
+++ b/src/Interfaces/Container.php
@@ -0,0 +1,25 @@
+register_style( 'login-with-google', 'build/css/login.css' );
+ }
+
+ /**
+ * Enqueue the login stylesheet.
+ *
+ * @return void
+ */
+ public function enqueue_login_styles(): void {
+ /**
+ * If style is not registered, register it.
+ */
+ if ( ! wp_style_is( 'login-with-google', 'registered' ) ) {
+ $this->register_login_styles();
+ }
+
+ if ( ! wp_script_is( 'login-with-google-script', 'registered' ) ) {
+ $this->register_script( 'login-with-google-script', 'build/js/login.js' );
+ }
+
+ wp_enqueue_script( 'login-with-google-script' );
+ wp_enqueue_style( 'login-with-google' );
+ }
+
+ /**
+ * Register a new script.
+ *
+ * @param string $handle Name of the script. Should be unique.
+ * @param string|bool $file script file, path of the script relative to the assets/build/ directory.
+ * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
+ * @param string|bool|null $ver Optional. String specifying script version number, if not set, filetime will be used as version number.
+ * @param bool $in_footer Optional. Whether to enqueue the script before instead of in the .
+ * Default 'false'.
+ * @return bool Whether the script has been registered. True on success, false on failure.
+ */
+ public function register_script( $handle, $file, $deps = [], $ver = false, $in_footer = true ) {
+ $src = sprintf( '%1$sassets/%2$s', plugin()->url, $file );
+ $version = $this->get_file_version( $file, $ver );
+
+ return wp_register_script( $handle, $src, $deps, $version, $in_footer );
+ }
+
+ /**
+ * Register a CSS stylesheet.
+ *
+ * @param string $handle Name of the stylesheet. Should be unique.
+ * @param string|bool $file style file, path of the script relative to the assets/build/ directory.
+ * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
+ * @param string|bool|null $ver Optional. String specifying script version number, if not set, filetime will be used as version number.
+ * @param string $media Optional. The media for which this stylesheet has been defined.
+ * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
+ * '(orientation: portrait)' and '(max-width: 640px)'.
+ *
+ * @return bool Whether the style has been registered. True on success, false on failure.
+ */
+ public function register_style( $handle, $file, $deps = [], $ver = false, $media = 'all' ) {
+ $src = sprintf( '%1$sassets/%2$s', plugin()->url, $file );
+ $version = $this->get_file_version( $file, $ver );
+
+ return wp_register_style( $handle, $src, $deps, $version, $media );
+ }
+
+ /**
+ * Get file version.
+ *
+ * @param string $file File path.
+ * @param int|string|boolean $ver File version.
+ *
+ * @return bool|false|int
+ */
+ private function get_file_version( $file, $ver = false ) {
+ if ( ! empty( $ver ) ) {
+ return $ver;
+ }
+
+ $file_path = sprintf( '%s/%s', plugin()->assets_dir, $file );
+
+ return file_exists( $file_path ) ? filemtime( $file_path ) : false;
+ }
+}
diff --git a/src/Modules/Login.php b/src/Modules/Login.php
new file mode 100644
index 00000000..f62e5c4c
--- /dev/null
+++ b/src/Modules/Login.php
@@ -0,0 +1,223 @@
+gh_client = $client;
+ $this->authenticator = $authenticator;
+ }
+
+ /**
+ * Module name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return 'login_flow';
+ }
+
+ /**
+ * Initialize login flow.
+ *
+ * @return void
+ */
+ public function init(): void {
+ add_action( 'login_form', [ $this, 'login_button' ] );
+ add_action( 'authenticate', [ $this, 'authenticate' ] );
+ add_action( 'rtcamp.google_register_user', [ $this->authenticator, 'register' ] );
+ add_action( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
+ add_action( 'rtcamp.google_user_created', [ $this, 'user_meta' ] );
+ add_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
+ add_action( 'wp_login', [ $this, 'login_redirect' ] );
+ }
+
+ /**
+ * Add the login button to login form.
+ *
+ * @return void
+ */
+ public function login_button(): void {
+ $template = trailingslashit( plugin()->template_dir ) . 'google-login-button.php';
+ $login_url = plugin()->container()->get( 'gh_client' )->authorization_url();
+
+ Helper::render_template(
+ $template,
+ [
+ 'login_url' => $login_url,
+ ]
+ );
+ }
+
+ /**
+ * Authenticate the user.
+ *
+ * @param WP_User|null $user User object. Default is null.
+ *
+ * @return WP_User|WP_Error
+ * @throws Exception During authentication.
+ */
+ public function authenticate( $user = null ) {
+ if ( $user instanceof WP_User ) {
+ return $user;
+ }
+
+ $code = Helper::filter_input( INPUT_GET, 'code', FILTER_SANITIZE_STRING );
+
+ if ( ! $code ) {
+ return $user;
+ }
+
+ $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
+ $decoded_state = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;
+
+ if ( ! is_array( $decoded_state ) || empty( $decoded_state['provider'] ) || 'google' !== $decoded_state['provider'] ) {
+ return $user;
+ }
+
+ if ( empty( $decoded_state['nonce'] ) || ! wp_verify_nonce( $decoded_state['nonce'], 'login_with_google' ) ) {
+ return $user;
+ }
+
+ try {
+ $this->gh_client->set_access_token( $code );
+ $user = $this->gh_client->user();
+ $user = $this->authenticator->authenticate( $user );
+
+ if ( $user instanceof WP_User ) {
+ $this->authenticated = true;
+
+ return $user;
+ }
+
+ throw new Exception( __( 'Could not authenticate the user, please try again.', 'login-with-google' ) );
+
+ } catch ( Throwable $e ) {
+ return new WP_Error( 'google_login_failed', $e->getMessage() );
+ }
+ }
+
+ /**
+ * Add extra meta information about user.
+ *
+ * @param int $uid User ID.
+ *
+ * @return void
+ */
+ public function user_meta( int $uid ) {
+ add_user_meta( $uid, 'oauth_user', 1, true );
+ add_user_meta( $uid, 'oauth_provider', 'google', true );
+ }
+
+ /**
+ * Redirect URL.
+ *
+ * This is useful when redirect URL is present when
+ * trying to login to wp-admin.
+ *
+ * @param string $url Redirect URL address.
+ *
+ * @return string
+ */
+ public function redirect_url( string $url ): string {
+
+ return remove_query_arg( 'redirect_to', $url );
+ }
+
+ /**
+ * Add redirect_to location in state.
+ *
+ * @param array $state State data.
+ *
+ * @return array
+ */
+ public function state_redirect( array $state ): array {
+ $redirect_to = Helper::filter_input( INPUT_GET, 'redirect_to', FILTER_SANITIZE_STRING );
+ /**
+ * Filter the default redirect URL in case redirect_to param is not available.
+ * Default to admin URL.
+ *
+ * @param string $admin_url Admin URL address.
+ */
+ $state['redirect_to'] = $redirect_to ?? apply_filters( 'rtcamp.google_default_redirect', admin_url() );
+
+ return $state;
+ }
+
+ /**
+ * Add a redirect once user has been authenticated successfully.
+ *
+ * @return void
+ */
+ public function login_redirect(): void {
+ $state = Helper::filter_input( INPUT_GET, 'state', FILTER_SANITIZE_STRING );
+
+ if ( ! $state || ! $this->authenticated ) {
+ return;
+ }
+
+ $state = base64_decode( $state );
+ $state = $state ? json_decode( $state ) : null;
+
+ if ( ( $state instanceof stdClass ) && ! empty( $state->provider ) && 'google' === $state->provider && ! empty( $state->redirect_to ) ) {
+ wp_safe_redirect( $state->redirect_to );
+ exit;
+ }
+ }
+}
diff --git a/src/Modules/OneTapLogin.php b/src/Modules/OneTapLogin.php
new file mode 100644
index 00000000..a17fe428
--- /dev/null
+++ b/src/Modules/OneTapLogin.php
@@ -0,0 +1,221 @@
+settings = $settings;
+ $this->token_verifier = $verifier;
+ $this->google_client = $client;
+ $this->authenticator = $authenticator;
+ }
+
+ /**
+ * Module name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return 'one_tap_login';
+ }
+
+ /**
+ * Module Initialization activity.
+ *
+ * Everything will happen if and only if one tap is active in settings.
+ */
+ public function init(): void {
+ if ( $this->settings->one_tap_login ) {
+ add_action( 'login_enqueue_scripts', [ $this, 'one_tap_scripts' ] );
+ add_action( 'login_footer', [ $this, 'one_tap_prompt' ] );
+ add_action( 'wp_ajax_nopriv_validate_id_token', [ $this, 'validate_token' ] );
+ add_action( 'rtcamp.id_token_verified', [ $this, 'authenticate' ] );
+ add_action(
+ 'init',
+ function () {
+ if ( ! is_user_logged_in() ) {
+ $hook_prefix = ( 'sitewide' === $this->settings->one_tap_login_screen ) ? 'wp' : 'login';
+ add_action( $hook_prefix . '_enqueue_scripts', [ $this, 'one_tap_scripts' ] );
+ add_action( $hook_prefix . '_footer', [ $this, 'one_tap_prompt' ], 10000 );
+ }
+ }
+ );
+ }
+ }
+
+ /**
+ * Show one tap prompt markup.
+ *
+ * @return void
+ */
+ public function one_tap_prompt(): void { ?>
+
+ path ) . 'assets/build/js/onetap.js' ),
+ true
+ );
+
+ $data = [
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
+ 'state' => $this->google_client->state(),
+ 'homeurl' => get_option( 'home', '' ),
+ ];
+
+ wp_register_script(
+ 'login-with-google-one-tap-js',
+ trailingslashit( plugin()->url ) . 'assets/build/js/' . $filename,
+ [
+ 'wp-i18n',
+ ],
+ filemtime( trailingslashit( plugin()->path ) . 'assets/build/js/onetap.js' ),
+ true
+ );
+
+ wp_add_inline_script(
+ 'login-with-google-one-tap-js',
+ 'var TempAccessOneTap=' . json_encode( $data ), //phpcs:disable WordPress.WP.AlternativeFunctions.json_encode_json_encode
+ 'before'
+ );
+
+ wp_enqueue_script( 'login-with-google-one-tap-js' );
+ }
+
+ /**
+ * Validate the ID token.
+ *
+ * @return void
+ * @throws Exception Credential verification failure exception.
+ */
+ public function validate_token(): void {
+ try {
+ $token = Helper::filter_input( INPUT_POST, 'token', FILTER_SANITIZE_STRING );
+ $verified = $this->token_verifier->verify_token( $token );
+
+ if ( ! $verified ) {
+ throw new Exception( __( 'Cannot verify the credentials', 'login-with-google' ) );
+ }
+
+ /**
+ * Do something when token has been verified successfully.
+ *
+ * If we are here that means ID token has been verified.
+ *
+ * @since 1.0.16
+ */
+ do_action( 'rtcamp.id_token_verified' );
+
+ $redirect_to = apply_filters( 'rtcamp.google_default_redirect', admin_url() );
+ $state = Helper::filter_input( INPUT_POST, 'state', FILTER_SANITIZE_STRING );
+ $decoded_state = $state ? (array) ( json_decode( base64_decode( $state ) ) ) : null;
+
+ if ( is_array( $decoded_state ) && ! empty( $decoded_state['provider'] ) && 'google' === $decoded_state['provider'] ) {
+ $redirect_to = $decoded_state['redirect_to'] ?? $redirect_to;
+ }
+
+ wp_send_json_success(
+ [
+ 'redirect' => $redirect_to,
+ ]
+ );
+ die;
+
+ } catch ( Exception $e ) {
+ wp_send_json_error( $e->getMessage() );
+ }
+ }
+
+ /**
+ * Authenticate the user in WordPress.
+ *
+ * @return void
+ * @throws Exception Authentication exception.
+ */
+ public function authenticate(): void {
+ $user = $this->token_verifier->current_user();
+
+ if ( is_null( $user ) ) {
+ throw new Exception( __( 'User not found to authenticate', 'login-with-google' ) );
+ }
+
+ $wp_user = $this->authenticator->authenticate( $user );
+ $this->authenticator->set_auth_cookies( $wp_user );
+ }
+}
diff --git a/src/Modules/Settings.php b/src/Modules/Settings.php
new file mode 100644
index 00000000..f63e1637
--- /dev/null
+++ b/src/Modules/Settings.php
@@ -0,0 +1,361 @@
+
+ */
+
+declare(strict_types=1);
+
+namespace RtCamp\GoogleLogin\Modules;
+
+use RtCamp\GoogleLogin\Interfaces\Module as ModuleInterface;
+
+/**
+ * Class Settings.
+ *
+ * @property string|null whitelisted_domains
+ * @property string|null client_id
+ * @property string|null client_secret
+ * @property bool|null registration_enabled
+ * @property bool|null one_tap_login
+ * @property string one_tap_login_screen
+ *
+ * @package RtCamp\GoogleLogin\Modules
+ */
+class Settings implements ModuleInterface {
+
+ /**
+ * Settings values.
+ *
+ * @var array
+ */
+ public $options;
+
+ /**
+ * Getters for settings values.
+ *
+ * @var string[]
+ */
+ private $getters = [
+ 'WP_GOOGLE_LOGIN_CLIENT_ID' => 'client_id',
+ 'WP_GOOGLE_LOGIN_SECRET' => 'client_secret',
+ 'WP_GOOGLE_LOGIN_USER_REGISTRATION' => 'registration_enabled',
+ 'WP_GOOGLE_LOGIN_WHITELIST_DOMAINS' => 'whitelisted_domains',
+ 'WP_GOOGLE_ONE_TAP_LOGIN' => 'one_tap_login',
+ 'WP_GOOGLE_ONE_TAP_LOGIN_SCREEN' => 'one_tap_login_screen',
+ ];
+
+ /**
+ * Getter method.
+ *
+ * @param string $name Name of option to fetch.
+ */
+ public function __get( string $name ) {
+ if ( in_array( $name, $this->getters, true ) ) {
+ $constant_name = array_search( $name, $this->getters, true );
+
+ return defined( $constant_name ) ? constant( $constant_name ) : ( $this->options[ $name ] ?? '' );
+ }
+
+ return null;
+ }
+
+ /**
+ * Return module name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return 'settings';
+ }
+
+ /**
+ * Initialization of module.
+ *
+ * @return void
+ */
+ public function init(): void {
+ $this->options = get_option( 'wp_google_login_settings', [] );
+ add_action( 'admin_init', [ $this, 'register_settings' ] );
+ add_action( 'admin_menu', [ $this, 'settings_page' ] );
+ }
+
+ /**
+ * Register the settings, section and fields.
+ *
+ * @return void
+ */
+ public function register_settings(): void {
+ register_setting( 'wp_google_login', 'wp_google_login_settings' );
+
+ add_settings_section(
+ 'wp_google_login_section',
+ __( 'Log in with Google Settings', 'login-with-google' ),
+ function () {
+ },
+ 'login-with-google'
+ );
+
+ add_settings_field(
+ 'wp_google_login_client_id',
+ __( 'Client ID', 'login-with-google' ),
+ [ $this, 'client_id_field' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'client-id' ]
+ );
+
+ add_settings_field(
+ 'wp_google_login_client_secret',
+ __( 'Client Secret', 'login-with-google' ),
+ [ $this, 'client_secret_field' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'client-secret' ]
+ );
+
+ add_settings_field(
+ 'wp_google_allow_registration',
+ __( 'Create New User', 'login-with-google' ),
+ [ $this, 'user_registration' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'user-registration' ]
+ );
+
+ add_settings_field(
+ 'wp_google_one_tap_login',
+ __( 'Enable One Tap Login', 'login-with-google' ),
+ [ $this, 'one_tap_login' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'one-tap-login' ]
+ );
+
+ add_settings_field(
+ 'wp_google_one_tap_login_screen',
+ __( 'One Tap Login Locations', 'login-with-google' ),
+ [ $this, 'one_tap_login_screens' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'one-tap-login-screen' ]
+ );
+
+ add_settings_field(
+ 'wp_google_whitelisted_domain',
+ __( 'Whitelisted Domains', 'login-with-google' ),
+ [ $this, 'whitelisted_domains' ],
+ 'login-with-google',
+ 'wp_google_login_section',
+ [ 'label_for' => 'whitelisted-domains' ]
+ );
+ }
+
+ /**
+ * Render client ID field.
+ *
+ * @return void
+ */
+ public function client_id_field(): void { ?>
+ disabled( 'client_id' ); ?> />
+
+ %1s %3s .
',
+ esc_html__( 'Create oAuth Client ID and Client Secret at', 'login-with-google' ),
+ 'https://console.developers.google.com/apis/dashboard',
+ 'console.developers.google.com'
+ )
+ );
+ ?>
+
+
+ disabled( 'client_secret' ); ?> />
+ General
+ *
+ * @return void
+ */
+ public function user_registration(): void {
+ ?>
+ disabled( 'registration_enabled' ); ?> type='checkbox'
+ name='wp_google_login_settings[registration_enabled]'
+ id="user-registration" registration_enabled ) ); ?>
+ value='1'>
+
+
+
+ membership setting is off.', 'login-with-google' ),
+ is_multisite() ? 'network/settings.php' : 'options-general.php'
+ )
+ );
+ ?>
+
+
+ disabled( 'one_tap_login' ); ?>
+ type='checkbox'
+ name='wp_google_login_settings[one_tap_login]'
+ id="one-tap-login" one_tap_login ) ); ?>
+ value='1'>
+
+
+ one_tap_login_screen ?? '';
+ ?>
+ disabled( 'one_tap_login' ); ?>
+ type='radio'
+ name='wp_google_login_settings[one_tap_login_screen]'
+ id="one-tap-login-screen-login" one_tap_login_screen, $default ) ); ?>
+ value='login'>
+
+
+ disabled( 'one_tap_login' ); ?>
+ type='radio'
+ name='wp_google_login_settings[one_tap_login_screen]'
+ id="one-tap-login-screen-sitewide" one_tap_login_screen, 'sitewide' ) ); ?>
+ value='sitewide'>
+
+
+
+
+
+ disabled( 'whitelisted_domains' ); ?> type='text' name='wp_google_login_settings[whitelisted_domains]' id="whitelisted-domains" value='whitelisted_domains ); ?>' autocomplete="off" />
+
+
+
+
+
+
+
+ getters, true );
+
+ if ( false !== $constant_name ) {
+ if ( defined( $constant_name ) ) {
+ echo esc_attr( 'disabled="disabled"' );
+ }
+ }
+ }
+}
diff --git a/src/Modules/Shortcode.php b/src/Modules/Shortcode.php
new file mode 100644
index 00000000..0d9c87a5
--- /dev/null
+++ b/src/Modules/Shortcode.php
@@ -0,0 +1,181 @@
+gh_client = $client;
+ $this->assets = $assets;
+ }
+
+ /**
+ * Module name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return 'shortcode';
+ }
+
+ /**
+ * Initialization actions.
+ */
+ public function init(): void {
+ add_shortcode( self::TAG, [ $this, 'callback' ] );
+ add_filter( 'do_shortcode_tag', [ $this, 'scan_shortcode' ], 10, 3 );
+ }
+
+ /**
+ * Callback function for shortcode rendering.
+ *
+ * @param array $attrs Shortcode attributes.
+ *
+ * @return string
+ */
+ public function callback( $attrs = [] ): string {
+ $attrs = shortcode_atts(
+ [
+ 'button_text' => __( 'Login with google', 'login-with-google' ),
+ 'force_display' => 'no',
+ 'redirect_to' => get_permalink(),
+ ],
+ $attrs,
+ self::TAG
+ );
+
+ if ( ! $this->should_display( $attrs ) ) {
+ return '';
+ }
+
+ $this->redirect_uri = $attrs['redirect_to'];
+
+ add_filter( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
+ add_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
+
+ $attrs['login_url'] = $this->gh_client->authorization_url();
+
+ remove_filter( 'rtcamp.google_login_state', [ $this, 'state_redirect' ] );
+ remove_filter( 'rtcamp.google_redirect_url', [ $this, 'redirect_url' ] );
+ $template = trailingslashit( plugin()->template_dir ) . 'google-login-button.php';
+
+ return Helper::render_template( $template, $attrs, false );
+ }
+
+ /**
+ * Check if the current single post or page contains
+ * shortcode. If it does, enqueue the relevant style.
+ *
+ * @param string $output Shortcode output.
+ * @param string $tag Shortcode tag being processed.
+ * @param array|string $attrs Shortcode attributes.
+ *
+ * @return string
+ */
+ public function scan_shortcode( string $output, string $tag, $attrs ): string {
+ if ( ( ! is_single() && ! is_page() ) || self::TAG !== $tag || ! $this->should_display( (array) $attrs ) ) {
+ return $output;
+ }
+
+ $this->assets->enqueue_login_styles();
+
+ return $output;
+ }
+
+
+ /**
+ * Filter redirect URL as per shortcode param.
+ *
+ * @param string $url Login URL.
+ *
+ * @return string
+ */
+ public function redirect_url( string $url ): string {
+
+ return remove_query_arg( 'redirect_to', $url );
+ }
+
+ /**
+ * Add redirect_to location in state.
+ *
+ * @param array $state State data.
+ *
+ * @return array
+ */
+ public function state_redirect( array $state ): array {
+ if ( is_null( $this->redirect_uri ) ) {
+ return $state;
+ }
+
+ $state['redirect_to'] = $this->redirect_uri;
+
+ return $state;
+ }
+
+ /**
+ * Determines whether to process the shortcode.
+ *
+ * @param array $attrs Shortcode attributes.
+ *
+ * @return bool
+ */
+ private function should_display( array $attrs ): bool {
+ if ( ! is_user_logged_in() || ( ! empty( $attrs['force_display'] ) && 'yes' === (string) $attrs['force_display'] ) ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Plugin.php b/src/Plugin.php
new file mode 100644
index 00000000..34208f7f
--- /dev/null
+++ b/src/Plugin.php
@@ -0,0 +1,143 @@
+container = $container;
+ }
+
+ /**
+ * Run the plugin
+ *
+ * @return void
+ */
+ public function run(): void {
+ $this->path = dirname( __FILE__, 2 );
+ $this->url = plugin_dir_url( trailingslashit( dirname( __FILE__, 2 ) ) . 'login-with-google.php' );
+ $this->template_dir = trailingslashit( $this->path ) . 'templates/';
+ $this->assets_dir = trailingslashit( $this->path ) . 'assets/';
+
+ /**
+ * Filter out active modules before modules are initialized.
+ *
+ * @param array $active_modules Active modules list.
+ *
+ * @since 1.0.0
+ */
+ $this->active_modules = apply_filters( 'rtcamp.google_login_modules', $this->active_modules );
+
+ $this->container()->define_services();
+ $this->activate_modules();
+
+ add_action( 'init', [ $this, 'load_translations' ] );
+ }
+
+ /**
+ * Load the plugin translation if available.
+ *
+ * @return void
+ */
+ public function load_translations(): void {
+ load_plugin_textdomain( 'login-with-google', false, basename( plugin()->path ) . '/languages/' . get_locale() );
+ }
+
+ /**
+ * Return container object
+ *
+ * @return ContainerInterface
+ */
+ public function container(): ContainerInterface {
+ return $this->container;
+ }
+
+ /**
+ * Activate individual modules.
+ *
+ * @return void
+ */
+ private function activate_modules(): void {
+ foreach ( $this->active_modules as $module ) {
+ $module_instance = $this->container()->get( $module );
+ $module_instance->init();
+ }
+ }
+}
diff --git a/src/Utils/Authenticator.php b/src/Utils/Authenticator.php
new file mode 100644
index 00000000..b7d0abfa
--- /dev/null
+++ b/src/Utils/Authenticator.php
@@ -0,0 +1,169 @@
+settings = $settings;
+ }
+
+ /**
+ * Authenticate the user.
+ *
+ * If registration setting is on, user will be created if
+ * that user does not exist in the application.
+ *
+ * @param stdClass $user User data object returned by Google.
+ *
+ * @return WP_User
+ * @throws InvalidArgumentException For invalid registrations.
+ */
+ public function authenticate( stdClass $user ): WP_User {
+ if ( ! property_exists( $user, 'email' ) ) {
+ throw new InvalidArgumentException( __( 'Email needs to be present for the user.', 'login-with-google' ) );
+ }
+
+ if ( email_exists( $user->email ) ) {
+ return get_user_by( 'email', $user->email );
+ }
+
+ /**
+ * Check if we need to register the user.
+ *
+ * @param stdClass $user User object from google.
+ * @since 1.0.0
+ */
+ return apply_filters( 'rtcamp.google_register_user', $this->maybe_create_username( $user ) );
+ }
+
+ /**
+ * Register the new user if setting is on for registration.
+ *
+ * @param stdClass $user User object from google.
+ *
+ * @return WP_User|null
+ * @throws Throwable Invalid email registration.
+ * @throws Exception Registration is off.
+ */
+ public function register( stdClass $user ): ?WP_User {
+ $register = true === (bool) $this->settings->registration_enabled || (bool) get_option( 'users_can_register', false );
+
+ if ( ! $register ) {
+ throw new Exception( __( 'Registration is not allowed.', 'login-with-google' ) );
+ }
+
+ try {
+ $whitelisted_domains = $this->settings->whitelisted_domains;
+ if ( empty( $whitelisted_domains ) || $this->can_register_with_email( $user->email ) ) {
+ $uid = wp_insert_user(
+ [
+ 'user_login' => Helper::unique_username( $user->login ),
+ 'user_pass' => wp_generate_password( 18 ),
+ 'user_email' => $user->email,
+ ]
+ );
+
+ /**
+ * Fires once the user has been registered successfully.
+ */
+ do_action( 'rtcamp.google_user_created', $uid, $user );
+
+ return get_user_by( 'id', $uid );
+ }
+
+ /* translators: %s is replaced with email ID of user trying to register */
+ throw new Exception( sprintf( __( 'Cannot register with this email: %s', 'login-with-google' ), $user->email ) );
+
+ } catch ( Throwable $e ) {
+
+ throw $e;
+ }
+
+ }
+
+ /**
+ * Set auth cookies for WordPress login.
+ *
+ * @param WP_User $user WP User object.
+ *
+ * @return void
+ */
+ public function set_auth_cookies( WP_User $user ) {
+ wp_clear_auth_cookie();
+ wp_set_current_user( $user->ID, $user->user_login );
+ wp_set_auth_cookie( $user->ID );
+ }
+
+ /**
+ * Assign the `login` property to user object
+ * if it doesn't exists.
+ *
+ * @param stdClass $user User object.
+ *
+ * @return stdClass
+ */
+ private function maybe_create_username( stdClass $user ): stdClass {
+ if ( property_exists( $user, 'login' ) || ! property_exists( $user, 'email' ) ) {
+ return $user;
+ }
+
+ $email = $user->email;
+ $user_login = sanitize_user( current( explode( '@', $email ) ), true );
+ $user_login = Helper::unique_username( $user_login );
+ $user->login = $user_login;
+
+ return $user;
+ }
+
+ /**
+ * Check if given email can be used for registration.
+ *
+ * @param string $email Email ID.
+ *
+ * @return bool
+ */
+ private function can_register_with_email( string $email ): bool {
+ $whitelisted_domains = explode( ',', $this->settings->whitelisted_domains );
+ $whitelisted_domains = array_map( 'strtolower', $whitelisted_domains );
+ $whitelisted_domains = array_map( 'trim', $whitelisted_domains );
+ $email_parts = explode( '@', $email );
+ $email_parts = array_map( 'strtolower', $email_parts );
+
+ return in_array( $email_parts[1], $whitelisted_domains, true );
+ }
+}
diff --git a/src/Utils/GoogleClient.php b/src/Utils/GoogleClient.php
new file mode 100644
index 00000000..ea78f887
--- /dev/null
+++ b/src/Utils/GoogleClient.php
@@ -0,0 +1,254 @@
+client_id = $config['client_id'] ?? '';
+ $this->client_secret = $config['client_secret'] ?? '';
+ $this->redirect_uri = $config['redirect_uri'] ?? '';
+ }
+
+ /**
+ * Check if access token is set before calling API methods.
+ *
+ * @param string $name Name of method called.
+ * @param mixed $args Arguments for method.
+ *
+ * @throws Exception Empty access token.
+ */
+ public function __call( string $name, $args ) {
+ $methods = [
+ 'user',
+ 'emails',
+ ];
+
+ if ( in_array( $name, $methods, true ) && empty( $this->access_token ) ) {
+ throw new Exception( __( 'Access token must be set to make this API call', 'login-with-google' ) );
+ }
+ }
+
+ /**
+ * Set access token.
+ *
+ * @param string $code Token.
+ *
+ * @return self
+ * @throws \Throwable Exception for fetching access token.
+ */
+ public function set_access_token( string $code ): self {
+ try {
+ $this->access_token = $this->access_token( $code )->access_token;
+
+ return $this;
+ } catch ( \Throwable $e ) {
+ throw $e;
+ }
+ }
+
+ /**
+ * Return redirect url.
+ *
+ * @return string
+ */
+ public function gt_redirect_url(): string {
+ return apply_filters( 'rtcamp.google_redirect_url', $this->redirect_uri );
+ }
+
+ /**
+ * Get the authorize URL
+ *
+ * @return string
+ */
+ public function authorization_url(): string {
+ $plugin_scope = [
+ 'email',
+ 'profile',
+ 'openid',
+ ];
+
+ $scope = apply_filters_deprecated(
+ 'wp_google_login_scopes',
+ [
+ $plugin_scope,
+ ],
+ '1.0.15',
+ 'rtcamp.google_scope'
+ );
+
+ /**
+ * Filter the scopes.
+ *
+ * @param array $scope List of scopes.
+ */
+ $scope = apply_filters( 'rtcamp.google_scope', $scope );
+
+ $client_args = [
+ 'client_id' => $this->client_id,
+ 'redirect_uri' => $this->gt_redirect_url(),
+ 'state' => $this->state(),
+ 'scope' => implode( ' ', $scope ),
+ 'access_type' => 'online',
+ 'response_type' => 'code',
+ ];
+
+ /**
+ * Filter the arguments for sending in query.
+ *
+ * This is useful in cases for example: choosing the correct prompt.
+ *
+ * @param array $client_args List of query arguments to send to Google OAuth.
+ */
+ $client_args = apply_filters( 'rtcamp.google_client_args', $client_args );
+
+ return self::AUTHORIZE_URL . '?' . http_build_query( $client_args );
+ }
+
+ /**
+ * Get the access token.
+ *
+ * @param string $code Response code received during authorization.
+ *
+ * @return \stdClass
+ * @throws Exception For access token errors.
+ */
+ public function access_token( string $code ): \stdClass {
+ $response = wp_remote_post(
+ self::TOKEN_URL,
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'body' => [
+ 'client_id' => $this->client_id,
+ 'client_secret' => $this->client_secret,
+ 'redirect_uri' => $this->gt_redirect_url(),
+ 'code' => $code,
+ 'grant_type' => 'authorization_code',
+ ],
+ ]
+ );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ throw new Exception( __( 'Could not retrieve the access token, please try again.', 'login-with-google' ) );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $response ) );
+ }
+
+ /**
+ * Make an API request.
+ *
+ * @return \stdClass
+ * @throws \Throwable Exception during API.
+ * @throws Exception API Exception.
+ */
+ public function user(): \stdClass {
+ try {
+ //phpcs:disable WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
+ $user = wp_remote_get(
+ trailingslashit( self::API_BASE ) . 'oauth2/v2/userinfo?access_token=' . $this->access_token,
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $user ) ) {
+ throw new Exception( __( 'Could not retrieve the user information, please try again.', 'login-with-google' ) );
+ }
+
+ return json_decode( wp_remote_retrieve_body( $user ) );
+
+ } catch ( \Throwable $e ) {
+
+ throw $e;
+ }
+ }
+
+ /**
+ * State to pass in GH API.
+ *
+ * @return string
+ */
+ public function state(): string {
+ $state_data['nonce'] = wp_create_nonce( 'login_with_google' );
+ $state_data = apply_filters( 'rtcamp.google_login_state', $state_data );
+ $state_data['provider'] = 'google';
+
+ return base64_encode( wp_json_encode( $state_data ) );
+ }
+
+}
diff --git a/inc/classes/class-helper.php b/src/Utils/Helper.php
similarity index 88%
rename from inc/classes/class-helper.php
rename to src/Utils/Helper.php
index 8ebbc031..3961660b 100644
--- a/inc/classes/class-helper.php
+++ b/src/Utils/Helper.php
@@ -2,12 +2,15 @@
/**
* Helper class for all helper function.
*
- * @author Dhaval Parekh
+ * This class has been taken from Login with Google plugin.
*
- * @package login-with-google
+ * @package RtCamp\GoogleLogin
+ * @since 1.0.0
*/
-namespace WP_Google_Login\Inc;
+declare(strict_types=1);
+
+namespace RtCamp\GoogleLogin\Utils;
/**
* Class Helper
@@ -172,4 +175,22 @@ public static function filter_input( $type, $variable_name, $filter = FILTER_DEF
}
-}
\ No newline at end of file
+ /**
+ * Checks if username exists, if it does, creates a
+ * unique username by appending digits.
+ *
+ * @param string $username Username.
+ *
+ * @return string
+ */
+ public static function unique_username( string $username ): string {
+ $uname = $username;
+ $count = 1;
+
+ while ( username_exists( $uname ) ) {
+ $uname = $uname . '' . $count;
+ }
+
+ return $uname;
+ }
+}
diff --git a/src/Utils/TokenVerifier.php b/src/Utils/TokenVerifier.php
new file mode 100644
index 00000000..b9fdddc2
--- /dev/null
+++ b/src/Utils/TokenVerifier.php
@@ -0,0 +1,334 @@
+ OPENSSL_ALGO_SHA256,
+ 'RS384' => OPENSSL_ALGO_SHA384,
+ 'RS512' => OPENSSL_ALGO_SHA512,
+ 'ES384' => OPENSSL_ALGO_SHA384,
+ 'ES256' => OPENSSL_ALGO_SHA512,
+ ];
+
+ /**
+ * ID Token Sent via Google.
+ *
+ * @var string
+ */
+ private $token = '';
+
+ /**
+ * User who needs to be authenticated.
+ *
+ * @var stdClass
+ */
+ private $current_user;
+
+ /**
+ * Settings instance.
+ *
+ * @var Settings
+ */
+ private $settings;
+
+ /**
+ * TokenVerifier constructor.
+ *
+ * @param Settings $settings Settings instance.
+ */
+ public function __construct( Settings $settings ) {
+ $this->settings = $settings;
+ }
+
+ /**
+ * Get supported algorithms value.
+ *
+ * @param string $algo Algorithm.
+ */
+ public static function get_supported_algorithm( string $algo = '' ) {
+ $find_algo = array_key_exists( $algo, self::SUPPORTED_ALGORITHMS );
+
+ if ( ! $find_algo ) {
+ return apply_filters( 'rtcamp.default_algorithm', OPENSSL_ALGO_SHA256, $algo );
+ }
+
+ return self::SUPPORTED_ALGORITHMS[ $algo ];
+ }
+
+ /**
+ * Verify if a token is valid or not.
+ *
+ * @param string $token Received ID token from Google.
+ *
+ * @return bool
+ * @throws Exception Token verification failure exception.
+ */
+ public function verify_token( string $token ): bool {
+ $this->token = $token;
+
+ try {
+ $this->is_valid_jwt();
+ $this->is_valid_signature();
+ $this->valid_data();
+
+ return true;
+ } catch ( Exception $e ) {
+
+ do_action( 'rtcamp.login_with_google_exception', $e );
+
+ throw $e;
+ }
+ }
+
+ /**
+ * Base64 URL Encode a string.
+ *
+ * @param string $string Input string to encode.
+ *
+ * @return array|string|string[]
+ */
+ public function base64_encode_url( $string ) {
+ return str_replace( [ '+', '/', '=' ], [ '-', '_', '' ], base64_encode( $string ) );
+ }
+
+ /**
+ * Base64 URL Encode a string.
+ *
+ * @param string $string Input string to decode.
+ *
+ * @return false|string
+ */
+ public function base64_decode_url( string $string ) {
+ return base64_decode( str_replace( [ '-', '_' ], [ '+', '/' ], $string ) );
+ }
+
+ /**
+ * Retrieve current user's data.
+ *
+ * Current user is Google user, not WP user.
+ *
+ * @return stdClass|null
+ */
+ public function current_user(): ?stdClass {
+
+ return $this->current_user;
+ }
+
+ /**
+ * Get public key based on key ID.
+ *
+ * @param string|null $key_id Key ID.
+ *
+ * @return string|null
+ */
+ public function get_public_key( $key_id = null ): ?string {
+ if ( ! $key_id ) {
+ return null;
+ }
+
+ $transient_key = 'lwg_pk_' . $key_id;
+ $cached_pk = $this->get_transient( $transient_key );
+
+ if ( ! empty( $cached_pk ) ) {
+ return (string) $cached_pk;
+ }
+
+ //phpcs:disable WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get
+ $certs = wp_remote_get( self::CERTS_URL );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $certs ) ) {
+ return null;
+ }
+
+ $headers = wp_remote_retrieve_headers( $certs );
+ $keys = wp_remote_retrieve_body( $certs );
+ $keys = json_decode( $keys );
+
+ if ( property_exists( $keys, $key_id ) ) {
+ $max_age = is_object( $headers ) && is_a( $headers, Requests_Utility_CaseInsensitiveDictionary::class ) ? $this->get_max_age( $headers ) : 0;
+
+ /**
+ * Cache public key in transient.
+ *
+ * We will cache it for 5 mins less than the actual expiration time,
+ * so that it should be cleared on time.
+ */
+ if ( $max_age ) {
+ $max_age = $max_age - 300;
+ $this->set_transient( $transient_key, $keys->{$key_id}, max( 5, $max_age ) );
+ }
+
+ return $keys->{$key_id};
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether received token is valid JWT token or not.
+ *
+ * @return array|null Decoded informational array with Header|Payload|Signature form.
+ * @throws Exception ID token invalid.
+ */
+ private function is_valid_jwt(): ?array {
+ $parts = explode( '.', $this->token );
+
+ if ( ! is_array( $parts ) || 3 !== count( $parts ) ) {
+ throw new Exception( __( 'ID token is invalid', 'login-with-google' ) );
+ }
+
+ list( $header, $payload, $obtained_signature ) = $parts;
+ $header = $this->base64_decode_url( $header );
+ $payload = $this->base64_decode_url( $payload );
+
+ if ( ! $header || ! $payload ) {
+ throw new Exception( __( 'ID token is invalid', 'login-with-google' ) );
+ }
+
+ return [
+ $header,
+ $payload,
+ $obtained_signature,
+ ];
+ }
+
+ /**
+ * Verifies the signature in token.
+ *
+ * @return void
+ * @throws Exception Failed signature verification.
+ */
+ private function is_valid_signature(): void {
+ list( $header, $payload, $obtained_signature ) = $this->is_valid_jwt();
+ $parsed_header = json_decode( $header );
+ $parsed_header = wp_parse_args(
+ (array) $parsed_header,
+ [
+ 'kid' => null,
+ 'alg' => null,
+ 'typ' => 'JWT',
+ ]
+ );
+
+ if ( ! $parsed_header['kid'] || ! $parsed_header['alg'] ) {
+ throw new Exception( __( 'Cannot verify the ID token signature. Please try again.', 'login-with-google' ) );
+ }
+
+ $pubkey_pem = $this->get_public_key( $parsed_header['kid'] );
+ $decryption_key = openssl_pkey_get_public( $pubkey_pem );
+ $data = $this->base64_encode_url( $header ) . '.' . $this->base64_encode_url( $payload );
+ $calculated_signature = openssl_verify( $data, $this->base64_decode_url( $obtained_signature ), $decryption_key, self::get_supported_algorithm( $parsed_header['alg'] ) );
+
+ if ( 1 === (int) $calculated_signature ) {
+ $this->current_user = json_decode( $payload );
+
+ return;
+ }
+
+ throw new Exception( __( 'Cannot verify the ID token signature. Please try again.', 'login-with-google' ) );
+ }
+
+ /**
+ * Check the validity of data.
+ *
+ * @throws Exception If user is not set.
+ */
+ private function valid_data(): void {
+ if ( is_null( $this->current_user ) ) {
+ throw new Exception( __( 'No user present to validate', 'login-with-google' ) );
+ }
+
+ if ( $this->settings->client_id !== $this->current_user->aud ) {
+ throw new Exception( __( 'Invalid data found for authentication', 'login-with-google' ) );
+ }
+
+ if ( ! in_array( $this->current_user->iss, [ 'accounts.google.com', 'https://accounts.google.com' ], true ) ) {
+ throw new Exception( __( 'Invalid source found for authentication', 'login-with-google' ) );
+ }
+
+ if ( $this->current_user->exp < strtotime( 'now' ) ) {
+ throw new Exception( __( 'User data is stale! Please try again.', 'login-with-google' ) );
+ }
+ }
+
+ /**
+ * Get max age to cache the response from Cache-Control header.
+ *
+ * @param Requests_Utility_CaseInsensitiveDictionary $headers List of response headers.
+ *
+ * @return int
+ */
+ private function get_max_age( Requests_Utility_CaseInsensitiveDictionary $headers ): int {
+ if ( ! $headers->offsetExists( 'cache-control' ) ) {
+ return 0;
+ }
+
+ $cache_control = $headers->offsetGet( 'cache-control' );
+ $cache_control = explode( ',', $cache_control );
+ $cache_control = array_map( 'trim', $cache_control );
+ $cache_control = preg_grep( '/max-age=(\d+)?/', $cache_control );
+
+ if ( is_array( $cache_control ) && 1 === count( $cache_control ) ) {
+ $max_age = array_pop( $cache_control );
+ $max_age = explode( '=', $max_age );
+ $max_age = $max_age[1];
+
+ return intval( $max_age );
+ }
+
+ return 0;
+ }
+
+ /**
+ * Set the public key in transient.
+ *
+ * @param string $key Transient key.
+ * @param string $value Transient value.
+ * @param int $expire Transient expiration time in seconds.
+ *
+ * @return void
+ */
+ private function set_transient( string $key, string $value, int $expire = 0 ): void {
+ set_transient( $key, $value, $expire );
+ }
+
+ /**
+ * Retrieve the transient.
+ *
+ * @param string $key Transient key.
+ *
+ * @return mixed
+ */
+ private function get_transient( string $key ) {
+ return get_transient( $key );
+ }
+}
diff --git a/template/google-login-button.php b/templates/google-login-button.php
similarity index 71%
rename from template/google-login-button.php
rename to templates/google-login-button.php
index ea775f08..30c86c73 100644
--- a/template/google-login-button.php
+++ b/templates/google-login-button.php
@@ -2,9 +2,8 @@
/**
* Template for google login button.
*
- * @author Dhaval Parekh
- *
- * @package login-with-google
+ * @package RtCamp\GithubLogin
+ * @since 1.0.0
*/
$button_text = ( ! empty( $button_text ) ) ? $button_text : __( 'Log in with Google', 'login-with-google' );
@@ -14,8 +13,7 @@
}
?>
-
-
or
+
diff --git a/tests/.gitkeep b/tests/.gitkeep
new file mode 100644
index 00000000..11a84b10
--- /dev/null
+++ b/tests/.gitkeep
@@ -0,0 +1 @@
+//Silence is golden.
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
deleted file mode 100644
index 46ad3ffc..00000000
--- a/tests/bootstrap.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Tests;
-
-/**
- * Class Utility
- */
-class Utility {
-
- /**
- * Utility method to call private/protected method of a class and return method result as returned by the said
- * method. This is a generic wrapper function to align with relfection class and not to be use directly.
- *
- * @param mixed $object_or_class_name The object/class whose method is to be called.
- * @param string $method_name The Name of the method to call.
- * @param array $parameters The Parameters to be passed to the hidden method being called.
- *
- * @return mixed Result returned by the hidden method being called.
- */
- public static function invoke_method( $object_or_class_name, string $method_name, array $parameters = [] ) {
-
- $object = null;
-
- if ( is_object( $object_or_class_name ) ) {
- $object = $object_or_class_name;
- $class_name = get_class( $object );
- } else {
- $class_name = $object_or_class_name;
- }
-
- $o_reflection = new \ReflectionClass( $class_name );
-
- $method = $o_reflection->getMethod( $method_name );
- $method->setAccessible( true );
-
- return $method->invokeArgs( $object, $parameters );
-
- }
-
- /**
- * Utility method to get private/protected property of a class/object.
- * This is a generic wrapper function to align with relfection class and not to be use directly.
- *
- * @param mixed $object_or_class_name The object/class whose property is to be accessed.
- * @param string $property_name The name of the property to access.
- *
- * @return mixed Value of the hidden property being accessed.
- */
- public static function get_property( $object_or_class_name, $property_name ) {
-
- $object = null;
-
- if ( is_object( $object_or_class_name ) ) {
- $object = $object_or_class_name;
- $class_name = get_class( $object );
- } else {
- $class_name = $object_or_class_name;
- }
-
- $o_reflection = new \ReflectionClass( $class_name );
- $property = $o_reflection->getProperty( $property_name );
- $property->setAccessible( true );
-
- return $property->getValue( $object );
-
- }
-
- /**
- * Utility method to set private/protected property of an object/class.
- * This is a generic wrapper function to align with relfection class and not to be use directly.
- *
- * @param mixed $object_or_class_name The object/class whose property is to be accessed.
- * @param string $property_name The name of the property to access.
- * @param mixed $property_value The value to be set for the hidden property.
- *
- * @return mixed Value of the hidden property being accessed.
- */
- public static function set_and_get_property( $object_or_class_name, string $property_name, $property_value ) {
-
- $object = null;
-
- if ( is_object( $object_or_class_name ) ) {
- $object = $object_or_class_name;
- $class_name = get_class( $object );
- } else {
- $class_name = $object_or_class_name;
- }
-
- $o_reflection = new \ReflectionClass( $class_name );
- $property = $o_reflection->getProperty( $property_name );
- $property->setAccessible( true );
- $property->setValue( $object, $property_value );
-
- return $property->getValue( $object );
-
- }
-
-}
diff --git a/tests/php/PrivateAccess.php b/tests/php/PrivateAccess.php
new file mode 100644
index 00000000..3638af0c
--- /dev/null
+++ b/tests/php/PrivateAccess.php
@@ -0,0 +1,92 @@
+getMethod( $method_name );
+ $method->setAccessible( true );
+ return $method->invokeArgs( $object, $args );
+ }
+
+ /**
+ * Call a private static method as if it was public.
+ *
+ * @param string $class Class string to call the method of.
+ * @param string $method_name Name of the method to call.
+ * @param array $args Optional. Array of arguments to pass to the method.
+ * @return mixed Return value of the method call.
+ * @throws ReflectionException If the class could not be reflected upon.
+ */
+ protected function call_private_static_method( $class, $method_name, $args = [] ) {
+ $method = ( new ReflectionClass( $class ) )->getMethod( $method_name );
+ $method->setAccessible( true );
+ return $method->invokeArgs( null, $args );
+ }
+
+ /**
+ * Set a private property as if it was public.
+ *
+ * @param object|string $object Object instance or class string to set the property of.
+ * @param string $property_name Name of the property to set.
+ * @param mixed $value Value to set the property to.
+ * @throws ReflectionException If the object could not be reflected upon.
+ */
+ protected function set_private_property( $object, $property_name, $value ) {
+ $property = ( new ReflectionClass( $object ) )->getProperty( $property_name );
+ $property->setAccessible( true );
+ $property->setValue( $object, $value );
+ }
+
+ /**
+ * Get a private property as if it was public.
+ *
+ * @param object|string $object Object instance or class string to get the property of.
+ * @param string $property_name Name of the property to get.
+ * @return mixed Return value of the property.
+ * @throws ReflectionException If the object could not be reflected upon.
+ */
+ protected function get_private_property( $object, $property_name ) {
+ $property = ( new ReflectionClass( $object ) )->getProperty( $property_name );
+ $property->setAccessible( true );
+ return $property->getValue( $object );
+ }
+
+ /**
+ * Get a static private property as if it was public.
+ *
+ * @param string $class Class string to get the property of.
+ * @param string $property_name Name of the property to get.
+ * @return mixed Return value of the property.
+ * @throws ReflectionException If the class could not be reflected upon.
+ */
+ protected function get_static_private_property( $class, $property_name ) {
+ $properties = ( new ReflectionClass( $class ) )->getStaticProperties();
+ return array_key_exists( $property_name, $properties ) ? $properties[ $property_name ] : null;
+ }
+}
diff --git a/tests/php/TestCase.php b/tests/php/TestCase.php
new file mode 100644
index 00000000..94c6e820
--- /dev/null
+++ b/tests/php/TestCase.php
@@ -0,0 +1,175 @@
+
+ */
+class TestCase extends WPMockTestCase
+{
+ /**
+ * Sets up the fixture, for example, open a network connection.
+ * This method is called before a test is executed.
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ \WP_Mock::setUp();
+ }
+
+ /**
+ * Tears down the fixture, for example, close a network connection.
+ * This method is called after a test is executed.
+ */
+ public function tearDown(): void
+ {
+ parent::tearDown();
+ \WP_Mock::tearDown();
+ }
+
+ /**
+ * Build the Testee Mock Object
+ *
+ * Basic configuration available for all of the testee objects, call `getMock` to get the mock.
+ *
+ * @param string $className
+ * @param array $constructorArguments
+ * @param array $methods
+ * @param string $sutMethod
+ *
+ * @return PHPUnit_Framework_MockObject_MockBuilder
+ */
+ protected function buildTesteeMock(
+ string $className,
+ array $constructorArguments,
+ array $methods,
+ string $sutMethod
+ ): object {
+
+ $testee = $this->getMockBuilder($className);
+ $constructorArguments
+ ? $testee->setConstructorArgs($constructorArguments)
+ : $testee->disableOriginalConstructor();
+
+ $methods and $testee->setMethods($methods);
+ $sutMethod and $testee->setMethodsExcept([$sutMethod]);
+
+ return $testee;
+ }
+
+ /**
+ * Retrieve a Testee Mock to Test Protected Methods
+ *
+ * return MockBuilder
+ * @param string $className
+ * @param array $constructorArguments
+ * @param string $method
+ * @param array $methods
+ * @return array
+ * @throws ReflectionException
+ */
+ protected function buildTesteeMethodMock(
+ string $className,
+ array $constructorArguments,
+ string $method,
+ array $methods
+ ): array {
+
+ $testee = $this->buildTesteeMock(
+ $className,
+ $constructorArguments,
+ $methods,
+ ''
+ )->getMock();
+ $reflectionMethod = new ReflectionMethod($className, $method);
+ $reflectionMethod->setAccessible(true);
+ return [
+ $testee,
+ $reflectionMethod,
+ ];
+ }
+
+ /**
+ * Retrieve a Testee protected or private property.
+ *
+ * @param $property
+ * @param $object
+ */
+ //phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
+ protected function getTesteeProperty(
+ string $property,
+ object $object
+ ) {
+
+ $reflection = new \ReflectionClass($object);
+ $reflectionProperty = $reflection->getProperty($property);
+ $reflectionProperty->setAccessible(true);
+ return $reflectionProperty->getValue($object);
+ }
+
+ /**
+ * Set private/protected property.
+ *
+ * @param $object
+ * @param $property
+ * @param $value
+ *
+ * @throws ReflectionException
+ */
+ // phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
+ protected function setTesteeProperty(object $object, string $property, $value): void
+ {
+ $reflection = new \ReflectionClass($object);
+ $reflectionProperty = $reflection->getProperty($property);
+ $reflectionProperty->setAccessible(true);
+ $reflectionProperty->setValue($object, $value);
+ }
+
+ /**
+ * Use wp-mock to mock user defined and
+ * build-in wp functions.
+ *
+ * @param string $function_name
+ * @param mixed $args
+ * @param int $times
+ * @param mixed $return
+ */
+ protected function wpMockFunction(
+ string $functionName,
+ $args = [],
+ int $times = 1,
+ $return = null
+ ) {
+
+ $funcArgs = [
+ 'times' => $times,
+ ];
+
+ if (!empty($args)) {
+ $funcArgs['args'] = $args;
+ }
+
+ if (!empty($return)) {
+ $funcArgs['return'] = $return;
+ }
+
+ \WP_Mock::userFunction(
+ $functionName,
+ $funcArgs
+ );
+ }
+}
diff --git a/tests/php/Unit/ContainerTest.php b/tests/php/Unit/ContainerTest.php
new file mode 100644
index 00000000..be42f480
--- /dev/null
+++ b/tests/php/Unit/ContainerTest.php
@@ -0,0 +1,85 @@
+pimpleMock = $this->createMock( PimpleContainer::class );
+ $this->testee = new Testee( $this->pimpleMock );
+ }
+
+ public function testContainerImplementsInterface() {
+ $this->assertInstanceOf( ContainerInterface::class, $this->testee );
+ }
+
+ /**
+ * @covers ::get
+ */
+ public function testGetThrowsExceptionForNonExistentService() {
+ $this->pimpleMock->expects( $this->once() )
+ ->method( 'keys' )
+ ->willReturn( [ 'example_service' ] );
+
+ $this->expectException( InvalidArgumentException::class );
+ $this->testee->get( 'non_existent_service' );
+ }
+
+ /**
+ * @covers ::get
+ */
+ public function testGetReturnsServiceObject() {
+ $dummyService = (object) [
+ 'some_key' => 'some_value',
+ 'some_other_key' => 'some_other_value',
+ ];
+
+ $this->testee->container['test_service'] = $dummyService;
+
+ $this->pimpleMock->expects( $this->once() )
+ ->method( 'keys' )
+ ->willReturn( [ 'test_service' ] );
+
+ $this->pimpleMock->expects( $this->once() )
+ ->method( 'offsetGet' )
+ ->with( 'test_service' )
+ ->willReturn( $dummyService );
+
+ $this->testee->get( 'test_service' );
+ }
+}
diff --git a/tests/php/Unit/Modules/AssetsTest.php b/tests/php/Unit/Modules/AssetsTest.php
new file mode 100644
index 00000000..4342eaca
--- /dev/null
+++ b/tests/php/Unit/Modules/AssetsTest.php
@@ -0,0 +1,268 @@
+testee = new Testee();
+ }
+
+ /**
+ * @covers ::name
+ */
+ public function testName() {
+ $this->assertSame( 'assets', $this->testee->name() );
+ }
+
+ /**
+ * @covers ::init
+ */
+ public function testInit() {
+ WP_Mock::expectActionAdded(
+ 'login_enqueue_scripts',
+ [
+ $this->testee,
+ 'enqueue_login_styles'
+ ]
+ );
+
+ $this->testee->init();
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::register_login_styles
+ * @covers ::register_style
+ * @covers ::get_file_version
+ */
+ public function testRegisterLoginStyles() {
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 2,
+ function () {
+ return (object) [
+ 'url' => 'https://example.com/',
+ 'assets_dir' => 'https://example.com/assets',
+ ];
+ }
+ );
+
+ $this->wpMockFunction(
+ 'wp_register_style',
+ [
+ 'login-with-google',
+ 'https://example.com/assets/build/css/login.css',
+ [],
+ false,
+ true,
+ ],
+ 1,
+ true
+ );
+
+ $this->testee->register_login_styles();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::register_script
+ */
+ public function testRegisterLoginScript() {
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 2,
+ function () {
+ return (object) [
+ 'url' => 'https://example.com/',
+ 'assets_dir' => 'https://example.com/assets',
+ ];
+ }
+ );
+
+ $this->wpMockFunction(
+ 'wp_register_script',
+ [
+ 'login-with-google',
+ 'https://example.com/assets/js/login.js',
+ [
+ 'some-other-script'
+ ],
+ false,
+ true,
+ ],
+ 1,
+ true
+ );
+
+ $this->testee->register_script(
+ 'login-with-google',
+ 'js/login.js',
+ [
+ 'some-other-script'
+ ]
+ );
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * Test enqueuing style when it is already registered.
+ *
+ * @covers ::enqueue_login_styles
+ */
+ public function testEnqueueLoginStyleWithStyleRegistered() {
+ $this->wpMockFunction(
+ 'wp_style_is',
+ [
+ 'login-with-google',
+ 'registered',
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_script_is',
+ [
+ 'login-with-google-script',
+ 'registered',
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_register_style',
+ [
+ 'login-with-google',
+ 'https://example.com/assets/build/css/login.css',
+ [],
+ false,
+ true,
+ ],
+ 0,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_enqueue_style',
+ [
+ 'login-with-google',
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_enqueue_script',
+ [
+ 'login-with-google-script',
+ ],
+ 1,
+ true
+ );
+
+ $this->testee->enqueue_login_styles();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * Test enqueuing style when it is already registered.
+ *
+ * @covers ::enqueue_login_styles
+ * @covers ::get_file_version
+ */
+ public function testEnqueueLoginStyleWithStyleNotRegistered() {
+ $this->wpMockFunction(
+ 'wp_style_is',
+ [
+ 'login-with-google',
+ 'registered',
+ ],
+ 1,
+ false
+ );
+
+ $this->wpMockFunction(
+ 'wp_script_is',
+ [
+ 'login-with-google-script',
+ 'registered',
+ ],
+ 1,
+ false
+ );
+
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 4,
+ function () {
+ return (object) [
+ 'url' => 'https://example.com/',
+ 'assets_dir' => 'https://example.com/assets',
+ ];
+ }
+ );
+
+ $this->wpMockFunction(
+ 'wp_register_style',
+ [
+ 'login-with-google',
+ 'https://example.com/assets/build/css/login.css',
+ [],
+ false,
+ true,
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_enqueue_style',
+ [
+ 'login-with-google',
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'wp_enqueue_script',
+ [
+ 'login-with-google-script',
+ ],
+ 1,
+ true
+ );
+
+ $this->testee->enqueue_login_styles();
+ $this->assertConditionsMet();
+ }
+}
diff --git a/tests/php/Unit/Modules/LoginTest.php b/tests/php/Unit/Modules/LoginTest.php
new file mode 100644
index 00000000..86c7abda
--- /dev/null
+++ b/tests/php/Unit/Modules/LoginTest.php
@@ -0,0 +1,467 @@
+ghClientMock = $this->createMock( GoogleClient::class );
+ $this->authenticatorMock = $this->createMock( Authenticator::class );
+
+ $this->testee = new Testee( $this->ghClientMock, $this->authenticatorMock );
+ }
+
+ /**
+ * @covers ::name
+ */
+ public function testName() {
+ $this->assertSame( 'login_flow', $this->testee->name() );
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testImplementsModuleInterface() {
+ $this->assertTrue( $this->testee instanceof ModuleInterface );
+ }
+
+ /**
+ * @covers ::init
+ */
+ public function testInit() {
+ WP_Mock::expectActionAdded( 'login_form', [ $this->testee, 'login_button' ] );
+ WP_Mock::expectActionAdded( 'authenticate', [ $this->testee, 'authenticate' ] );
+ WP_Mock::expectActionAdded( 'rtcamp.google_register_user', [ $this->authenticatorMock, 'register' ] );
+ WP_Mock::expectActionAdded( 'rtcamp.google_redirect_url', [ $this->testee, 'redirect_url' ] );
+ WP_Mock::expectActionAdded( 'rtcamp.google_user_created', [ $this->testee, 'user_meta' ] );
+ WP_Mock::expectFilterAdded( 'rtcamp.google_login_state', [ $this->testee, 'state_redirect' ] );
+ WP_Mock::expectActionAdded( 'wp_login', [ $this->testee, 'login_redirect' ] );
+
+ $this->testee->init();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::login_button
+ */
+ public function testLoginButton() {
+ $pluginMock = $this->createMock( Plugin::class );
+ $pluginMock->template_dir = 'https://example.com/templates/';
+
+ $containerMock = $this->createMock( Container::class );
+ $containerMock->expects( $this->once() )
+ ->method( 'get' )
+ ->willReturn( $this->ghClientMock );
+
+ $pluginMock->expects( $this->once() )
+ ->method( 'container' )
+ ->willReturn( $containerMock );
+
+ $this->ghClientMock->expects( $this->once() )
+ ->method( 'authorization_url' )
+ ->willReturn( 'https://google.com/auth/' );
+
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 2,
+ $pluginMock
+ );
+
+ WP_Mock::userFunction(
+ 'trailingslashit',
+ [
+ 'times' => 1,
+ 'args' => [
+ 'https://example.com/templates/'
+ ],
+ 'return_arg' => 0
+ ]
+ );
+
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'render_template' )->once()->withArgs(
+ [
+ 'https://example.com/templates/google-login-button.php',
+ [
+ 'login_url' => 'https://google.com/auth/',
+ ]
+ ]
+ );
+
+ $this->testee->login_button();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationForNoCode() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( null );
+
+ $wp_user_mock = new \stdClass();
+ $wp_user_mock->login = 'test';
+ $wp_user_mock->email = 'test@unit.com';
+
+ $returned = $this->testee->authenticate( $wp_user_mock );
+
+ $this->assertSame( $returned, $wp_user_mock );
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationForAlreadyAuthenticatedUser() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->never()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( null );
+
+ $wp_user_mock = Mockery::mock( 'WP_User' );
+ $returned = $this->testee->authenticate( $wp_user_mock );
+
+ $this->assertSame( $returned, $wp_user_mock );
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationForDifferentProvider() {
+ $state = [
+ 'nonce' => '1234',
+ 'provider' => 'some_other',
+ ];
+
+ $state = base64_encode( json_encode( $state ) );
+
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'test_code' );
+
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'state',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( $state );
+
+ $wp_user_mock = new \stdClass();
+ $wp_user_mock->login = 'test';
+ $wp_user_mock->email = 'test@unit.com';
+
+ $returned = $this->testee->authenticate( $wp_user_mock );
+
+ $this->assertSame( $returned, $wp_user_mock );
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationWithForgedState() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'abc' );
+
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'state',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'eyJwcm92aWRlciI6ImdpdGh1YiJ9' );
+
+ $returned = $this->testee->authenticate();
+
+ $this->assertSame( null, $returned );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationWhenUserExists() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'abc' );
+
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'state',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'eyJwcm92aWRlciI6Imdvb2dsZSIsIm5vbmNlIjoidGVzdG5vbmNlIn0=' );
+
+ $this->ghClientMock->expects( $this->never() )
+ ->method( 'state' )
+ ->willReturn( 'eyJwcm92aWRlciI6Imdvb2dsZSIsIm5vbmNlIjoidGVzdG5vbmNlIn0=' );
+
+ $this->wpMockFunction(
+ 'wp_verify_nonce',
+ [
+ 'testnonce',
+ 'login_with_google',
+ ],
+ 1,
+ true
+ );
+
+ $this->ghClientMock->expects( $this->once() )
+ ->method( 'set_access_token' )
+ ->with( 'abc' );
+
+ $user = (object) [
+ 'email' => 'fakeemail@domain.com',
+ ];
+
+ $this->ghClientMock->expects( $this->once() )
+ ->method( 'user' )
+ ->willReturn( $user );
+
+
+ $userMock = Mockery::mock( 'WP_User' );
+ $this->authenticatorMock->expects( $this->once() )
+ ->method( 'authenticate' )
+ ->willReturn( $userMock );
+
+ $returned = $this->testee->authenticate();
+ $this->assertSame( $returned, $userMock );
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticationCapturesExceptions() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'code',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'abc' );
+
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'state',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'eyJwcm92aWRlciI6Imdvb2dsZSIsIm5vbmNlIjoidGVzdG5vbmNlIn0=' );
+
+ $this->ghClientMock->expects( $this->never() )
+ ->method( 'state' )
+ ->willReturn( 'eyJwcm92aWRlciI6Imdvb2dsZSIsIm5vbmNlIjoidGVzdG5vbmNlIn0=' );
+
+ $this->wpMockFunction(
+ 'wp_verify_nonce',
+ [
+ 'testnonce',
+ 'login_with_google',
+ ],
+ 1,
+ true
+ );
+
+ $this->ghClientMock->expects( $this->once() )
+ ->method( 'set_access_token' )
+ ->with( 'abc' )
+ ->willThrowException( new Exception( 'Exception for test' ) );
+
+ Mockery::mock( 'WP_Error' );
+ $returned = $this->testee->authenticate();
+
+ $this->assertInstanceOf( 'WP_Error', $returned );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::user_meta
+ */
+ public function testUserMeta() {
+ $user = new \stdClass();
+ $user->login = 'login';
+
+ $this->wpMockFunction(
+ 'add_user_meta',
+ [
+ 20,
+ 'oauth_user',
+ 1,
+ true,
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'add_user_meta',
+ [
+ 20,
+ 'oauth_provider',
+ 'google',
+ true,
+ ],
+ 1,
+ true
+ );
+
+ $this->testee->user_meta( 20 );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::redirect_url
+ */
+ public function testRedirectURLRetuensWithQueryParam() {
+ $url = 'https://example.com/?redirect_to=https://example.com/wp-admin';
+
+ $this->wpMockFunction(
+ 'remove_query_arg',
+ [
+ 'redirect_to',
+ $url,
+ ],
+ 1,
+ 'https://example.com/'
+ );
+
+ $redirect = $this->testee->redirect_url( $url );
+ $this->assertSame( 'https://example.com/', $redirect );
+ }
+
+ /**
+ * @covers ::state_redirect
+ */
+ public function testStateRedirectWithRedirectTo() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'redirect_to',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( 'https://example.com/state-page' );
+
+ $state_data = $this->testee->state_redirect( [] );
+
+ $this->assertIsArray( $state_data );
+ $this->assertContains( 'https://example.com/state-page', $state_data );
+ }
+
+ /**
+ * @covers ::state_redirect
+ */
+ public function testStateRedirectWithoutRedirectTo() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'redirect_to',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( null );
+
+ $this->wpMockFunction(
+ 'admin_url',
+ [],
+ 1,
+ 'https://example.com/login'
+ );
+
+ WP_Mock::expectFilter( 'rtcamp.google_default_redirect', 'https://example.com/login' );
+ $state_data = $this->testee->state_redirect( [] );
+ $this->assertIsArray( $state_data );
+ $this->assertContains( 'https://example.com/login', $state_data );
+ }
+
+ /**
+ * @covers ::login_redirect
+ */
+ public function testLoginRedirectWithNotStateAuthenticated() {
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'filter_input' )->once()->withArgs(
+ [
+ INPUT_GET,
+ 'state',
+ FILTER_SANITIZE_STRING
+ ]
+ )->andReturn( [] );
+
+ $data = $this->testee->login_redirect();
+ $this->assertNull( $data );
+ }
+}
diff --git a/tests/php/Unit/Modules/SettingsTest.php b/tests/php/Unit/Modules/SettingsTest.php
new file mode 100644
index 00000000..bc2616e1
--- /dev/null
+++ b/tests/php/Unit/Modules/SettingsTest.php
@@ -0,0 +1,318 @@
+testee = new Testee();
+ }
+
+ /**
+ * @covers ::name
+ */
+ public function testName() {
+ $this->assertSame( 'settings', $this->testee->name() );
+ }
+
+ public function testImplementsModuleInterface() {
+ $this->assertTrue( $this->testee instanceof ModuleInterface );
+ }
+
+ /**
+ * @covers ::__get
+ */
+ public function testGetWithNull() {
+ $value = $this->testee->__get( 'some_test_property' );
+ $this->assertEquals( null, $value );
+ }
+
+ /**
+ * @covers ::__get
+ */
+ public function testGetWithProper() {
+ $this->wpMockFunction(
+ 'get_option',
+ [
+ 'wp_google_login_settings',
+ []
+ ],
+ 1,
+ [
+ 'client_id' => 'cid'
+ ]
+ );
+
+ $this->testee->init();
+ $value = $this->testee->__get( 'client_id' );
+ $this->assertEquals( 'cid', $value );
+ }
+
+ /**
+ * @covers ::init
+ */
+ public function testInit() {
+ $this->wpMockFunction(
+ 'get_option',
+ [
+ 'wp_google_login_settings',
+ []
+ ],
+ 1,
+ []
+ );
+
+ WP_Mock::expectActionAdded( 'admin_init', [ $this->testee, 'register_settings' ] );
+ WP_Mock::expectActionAdded( 'admin_menu', [ $this->testee, 'settings_page' ] );
+
+ $this->testee->init();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::register_settings
+ */
+ public function testRegisterSettings() {
+ $this->wpMockFunction(
+ 'register_setting',
+ [
+ 'wp_google_login',
+ 'wp_google_login_settings'
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'add_settings_section',
+ [
+ 'wp_google_login_section',
+ 'Log in with Google Settings',
+ \Closure::class,
+ 'login-with-google'
+ ],
+ 1
+ );
+
+ WP_Mock::userFunction(
+ 'add_settings_field',
+ [
+ 'args' => [
+ \WP_Mock\Functions::type( 'string' ),
+ \WP_Mock\Functions::type( 'string' ),
+ \WP_Mock\Functions::type( 'callable' ),
+ \WP_Mock\Functions::type( 'string' ),
+ \WP_Mock\Functions::type( 'string' ),
+ \WP_Mock\Functions::type( 'array' ),
+ ],
+ 'times' => 6
+ ]
+ );
+
+ $this->testee->register_settings();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::settings_page
+ */
+ public function testSettingsPage() {
+ $this->wpMockFunction(
+ 'add_options_page',
+ [
+ 'Login with Google settings',
+ 'Login with Google',
+ 'manage_options',
+ 'login-with-google',
+ [
+ $this->testee,
+ 'output'
+ ],
+ ]
+ );
+
+ $this->testee->settings_page();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::output
+ */
+ public function testOutput() {
+ $this->wpMockFunction(
+ 'settings_fields',
+ [
+ 'wp_google_login',
+ ],
+ 1
+ );
+
+ $this->wpMockFunction(
+ 'do_settings_sections',
+ [
+ 'login-with-google',
+ ],
+ 1
+ );
+
+ $this->wpMockFunction(
+ 'submit_button',
+ [],
+ 1,
+ ''
+ );
+
+ $this->setOutputCallback(function() {});
+ $this->testee->output();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::client_id_field
+ */
+ public function testClientIdField() {
+ $this->wpMockFunction(
+ 'esc_html__',
+ [
+ 'Create oAuth Client ID and Client Secret at',
+ 'login-with-google'
+ ],
+ 2,
+ );
+
+ $this->wpMockFunction(
+ 'wp_kses_post',
+ [
+ sprintf(
+ '%1s %3s .
',
+ esc_html__( 'Create oAuth Client ID and Client Secret at', 'login-with-google' ),
+ 'https://console.developers.google.com/apis/dashboard',
+ 'console.developers.google.com'
+ )
+ ],
+ 1,
+ );
+
+ $this->setOutputCallback(function() {});
+ $this->testee->client_id_field();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::user_registration
+ */
+ public function testUserRegistration() {
+ $this->testee->registration_enabled = 'yes';
+
+ $this->wpMockFunction(
+ 'checked',
+ [
+ 'yes'
+ ],
+ 1,
+ );
+
+ $this->wpMockFunction(
+ 'esc_html_e',
+ [
+ 'Create a new user account if it does not exist already',
+ 'login-with-google'
+ ],
+ 1,
+ );
+
+ $this->wpMockFunction(
+ 'is_multisite',
+ [],
+ 1,
+ 'network/settings.php'
+ );
+
+ $this->wpMockFunction(
+ 'wp_kses_post',
+ [
+ /* translators: %1s will be replaced by page link */
+ __( 'If this setting is checked, a new user will be created even if membership setting is off.', 'login-with-google' ),
+ ],
+ 1,
+ );
+
+ $this->setOutputCallback(function() {});
+ $this->testee->user_registration();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::whitelisted_domains
+ */
+ public function testWhitelistedDomains() {
+ $this->testee->whitelisted_domains = 'https://example1.com,https://example2.com';
+
+ $this->wpMockFunction(
+ 'esc_attr',
+ [
+ 'https://example1.com,https://example2.com',
+ ],
+ 1,
+ );
+
+ $this->wpMockFunction(
+ 'esc_html',
+ [
+ __( 'Add each domain comma separated', 'login-with-google' )
+ ],
+ 1,
+ );
+
+ $this->setOutputCallback(function() {});
+ $this->testee->whitelisted_domains();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::client_secret_field
+ */
+ public function testClientSecretField() {
+ $this->testee->client_secret = 'cis';
+ $this->wpMockFunction(
+ 'esc_attr',
+ [
+ 'cis'
+ ],
+ 1
+ );
+
+ $this->setOutputCallback(function() {});
+ $this->testee->client_secret_field();
+ $this->assertConditionsMet();
+ }
+
+}
diff --git a/tests/php/Unit/Modules/ShortCodeTest.php b/tests/php/Unit/Modules/ShortCodeTest.php
new file mode 100644
index 00000000..0d1f270a
--- /dev/null
+++ b/tests/php/Unit/Modules/ShortCodeTest.php
@@ -0,0 +1,307 @@
+ghClientMock = $this->createMock( GoogleClient::class );
+ $this->assetMock = $this->createMock( Assets::class );
+
+ $this->testee = new Testee( $this->ghClientMock, $this->assetMock );
+ }
+
+ /**
+ * @covers ::name
+ */
+ public function testName() {
+ $this->assertSame( 'shortcode', $this->testee->name() );
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testImplementsModuleInterface() {
+ $this->assertTrue( $this->testee instanceof ModuleInterface );
+ }
+
+ /**
+ * @covers ::init
+ */
+ public function testInit() {
+ $this->wpMockFunction(
+ 'add_shortcode',
+ [
+ 'google_login',
+ [
+ $this->testee,
+ 'callback',
+ ]
+ ]
+ );
+
+ WP_Mock::expectFilterAdded( 'do_shortcode_tag', [ $this->testee, 'scan_shortcode' ], 10, 3 );
+
+ $this->testee->init();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::callback
+ * @covers ::should_display
+ */
+ public function testCallbackWhenUserIsLoggedIn() {
+ $this->wpMockFunction(
+ 'get_permalink',
+ [],
+ 1,
+ 'https://example.com/'
+ );
+
+ WP_Mock::userFunction(
+ 'shortcode_atts',
+ [
+ 'args' => [
+ [
+ 'button_text' => __( 'Login with google', 'login-with-google' ),
+ 'force_display' => 'no',
+ 'redirect_to' => 'https://example.com/',
+ ],
+ [],
+ 'google_login',
+ ],
+ 'times' => 1,
+ 'return_arg' => 0
+ ]
+ );
+
+ $this->wpMockFunction(
+ 'is_user_logged_in',
+ [],
+ 1,
+ true
+ );
+
+ $shortcode = $this->testee->callback();
+
+ $this->assertSame( '', $shortcode );
+ }
+
+ /**
+ * @covers ::callback
+ * @covers ::should_display
+ */
+ public function testCallbackWhenUserIsLoggedOut() {
+ WP_Mock::userFunction(
+ 'shortcode_atts',
+ [
+ 'args' => [
+ [
+ 'button_text' => __( 'Login with google', 'login-with-google' ),
+ 'force_display' => 'no',
+ 'redirect_to' => null,
+ ],
+ [],
+ 'google_login',
+ ],
+ 'times' => 1,
+ 'return_arg' => 0
+ ]
+ );
+
+ $this->wpMockFunction(
+ 'is_user_logged_in',
+ [],
+ 1,
+ false
+ );
+
+ WP_Mock::expectFilterAdded( 'rtcamp.google_redirect_url', [ $this->testee, 'redirect_url' ] );
+
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 1,
+ (object) [
+ 'template_dir' => '/some/path/templates',
+ ]
+ );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [],
+ 1,
+ '/some/path/templates/'
+ );
+
+ $this->ghClientMock->expects( $this->once() )
+ ->method( 'authorization_url' )
+ ->willReturn( 'https://google.com/auth/' );
+
+
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'render_template' )->once()->withArgs(
+ [
+ '/some/path/templates/google-login-button.php',
+ [
+ 'button_text' => 'Login with google',
+ 'force_display' => 'no',
+ 'redirect_to' => null,
+ 'login_url' => 'https://google.com/auth/',
+ ],
+ false
+ ]
+ )->andReturn( '' );
+
+ $this->testee->callback();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::scan_shortcode
+ * @covers ::should_display
+ */
+ public function testScanShortcodeForSinglePage() {
+ $this->wpMockFunction(
+ 'is_single',
+ [],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'is_page',
+ [],
+ 0,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'is_user_logged_in',
+ [],
+ 1,
+ false
+ );
+
+ $this->assetMock->expects( $this->once() )->method( 'enqueue_login_styles' );
+
+ $output = $this->testee->scan_shortcode( 'Hello', 'google_login', [] );
+ $this->assertSame( 'Hello', $output );
+ }
+
+ /**
+ * @covers ::scan_shortcode
+ * @covers ::should_display
+ */
+ public function testScanShortcodeForDifferentTag() {
+ $this->wpMockFunction(
+ 'is_single',
+ [],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'is_user_logged_in',
+ [],
+ 0,
+ false
+ );
+
+
+ $this->assetMock->expects( $this->never() )->method( 'enqueue_login_styles' );
+
+ $output = $this->testee->scan_shortcode( 'Hello', 'other_tag', [] );
+ $this->assertSame( 'Hello', $output );
+ }
+
+ /**
+ * @covers ::redirect_url
+ */
+ public function testRedirectURL() {
+ $url = 'https://example.com/?redirect_to=https://example.com/wp-admin';
+ $this->testee->redirect_uri = 'https://example.com/some-page';
+
+ $this->wpMockFunction(
+ 'remove_query_arg',
+ [
+ 'redirect_to',
+ $url
+ ],
+ 1,
+ 'https://example.com/'
+ );
+
+ $r_url = $this->testee->redirect_url( $url );
+ $this->assertSame( $r_url, 'https://example.com/' );
+ }
+
+ /**
+ * @covers ::state_redirect
+ */
+ public function testStateRedirectWithRedirectUrl() {
+ $this->testee->redirect_uri = 'https://example.com';
+
+ $state = [
+ 'provider' => 'google',
+ 'redirect_to' => 'https://example.com'
+ ];
+
+ $expected = $this->testee->state_redirect( $state );
+ $this->assertSame( $expected, $state );
+ }
+
+ /**
+ * @covers ::state_redirect
+ */
+ public function testStateRedirectWithoutRedirectUrl() {
+ $this->testee->redirect_uri = null;
+
+ $state = [
+ 'provider' => 'google'
+ ];
+
+ $expected = $this->testee->state_redirect( $state );
+ $this->assertSame( $expected, $state );
+ }
+}
diff --git a/tests/php/Unit/PluginTest.php b/tests/php/Unit/PluginTest.php
new file mode 100644
index 00000000..3f11116f
--- /dev/null
+++ b/tests/php/Unit/PluginTest.php
@@ -0,0 +1,321 @@
+moduleMock = $this->createMock( ModuleInterface::class );
+ $this->containerMock = $this->createMock( Container::class );
+ $this->testee = new Testee( $this->containerMock );
+ }
+
+ /**
+ * Test run method of Plugin class.
+ *
+ * @covers ::run
+ * @covers ::activate_modules
+ */
+ public function testRun() {
+ $this->moduleMock->expects( $this->exactly( 5 ) )
+ ->method( 'init' );
+
+ $this->containerMock->expects( $this->once() )
+ ->method( 'define_services' );
+
+
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ $this->wpMockFunction(
+ 'plugin_dir_url',
+ [
+ 'slashedstring/login-with-google.php'
+ ]
+ );
+
+ $this->testee->run();
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * Test the path for the plugin.
+ *
+ * @covers ::run
+ */
+ public function testPath() {
+ $this->moduleMock->expects( $this->exactly( 5 ) )
+ ->method( 'init' );
+
+ $this->containerMock->expects( $this->once() )
+ ->method( 'define_services' );
+
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ $this->wpMockFunction(
+ 'plugin_dir_url',
+ [
+ 'slashedstring/login-with-google.php'
+ ]
+ );
+
+ $this->testee->run();
+
+ $this->assertSame( GH_PLUGIN_DIR, $this->testee->path );
+ }
+
+ /**
+ * Test the path template directory in plugin.
+ *
+ * @covers ::run
+ */
+ public function testTemplateDirPath() {
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ $this->wpMockFunction(
+ 'plugin_dir_url',
+ [
+ 'slashedstring/login-with-google.php'
+ ]
+ );
+
+ $this->testee->run();
+
+ $this->assertSame( 'slashedstring/templates/', $this->testee->template_dir );
+ }
+
+ /**
+ * Test the plugin url.
+ *
+ * @covers ::run
+ */
+ public function testPluginURL() {
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ WP_Mock::userFunction(
+ 'plugin_dir_url',
+ [
+ 'args' => [
+ 'slashedstring/login-with-google.php'
+ ],
+ 'return_arg' => 0
+ ]
+ );
+
+ $this->testee->run();
+
+ $this->assertSame( 'slashedstring/login-with-google.php', $this->testee->url );
+ }
+
+ /**
+ * Test assets directory path.
+ *
+ * @covers ::run
+ */
+ public function testAssetsDirPath() {
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ $this->wpMockFunction(
+ 'plugin_dir_url',
+ [
+ 'slashedstring/login-with-google.php'
+ ]
+ );
+
+ $this->testee->run();
+
+ $this->assertSame( 'slashedstring/assets/', $this->testee->assets_dir );
+ }
+
+ /**
+ * Test that hooks are added on plugin run.
+ *
+ * @covers ::run
+ */
+ public function testHooksAddedOnRun() {
+ $this->containerMock->expects( $this->exactly( 5 ) )
+ ->method( 'get' )
+ ->withAnyParameters()
+ ->willReturn( $this->moduleMock );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ WP_Mock\Functions::type( 'string' )
+ ],
+ 3,
+ 'slashedstring/'
+ );
+
+ $this->wpMockFunction(
+ 'plugin_dir_url',
+ [
+ 'slashedstring/login-with-google.php'
+ ]
+ );
+
+ WP_Mock::expectActionAdded( 'init', [ $this->testee, 'load_translations' ] );
+ WP_Mock::expectFilter( 'rtcamp.google_login_modules', $this->testee->active_modules );
+
+ $this->testee->run();
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * Test load_translations method.
+ *
+ * @covers ::load_translations
+ */
+ public function testLoadTranslation() {
+
+ $this->moduleMock->expects( $this->never() )
+ ->method( 'init' );
+
+ $this->containerMock->expects( $this->never() )
+ ->method( 'define_services' );
+
+ $this->wpMockFunction(
+ 'get_locale',
+ [],
+ 1,
+ 'en_US'
+ );
+
+ $this->wpMockFunction(
+ 'RtCamp\GoogleLogin\plugin',
+ [],
+ 1,
+ function () {
+ return (object) [
+ 'path' => '/some/utterly/fake/path-to-test/',
+ ];
+ }
+ );
+
+ $this->wpMockFunction(
+ 'load_plugin_textdomain',
+ [
+ 'login-with-google',
+ false,
+ 'path-to-test/languages/en_US'
+ ]
+ );
+
+ $this->testee->load_translations();
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::container
+ */
+ public function testContainer() {
+ $container = $this->testee->container();
+
+ $this->assertSame( $container, $this->containerMock );
+ }
+}
diff --git a/tests/php/Unit/Utils/AuthenticatorTest.php b/tests/php/Unit/Utils/AuthenticatorTest.php
new file mode 100644
index 00000000..8e7eae8f
--- /dev/null
+++ b/tests/php/Unit/Utils/AuthenticatorTest.php
@@ -0,0 +1,276 @@
+settingsMock = $this->createMock( Settings::class );
+ $this->testee = new Testee( $this->settingsMock );
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testInstance() {
+ $this->assertInstanceOf( Testee::class, $this->testee );
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthenticateException() {
+ $user = [
+ 'name' => 'Test'
+ ];
+
+ $user = (object) $user;
+
+ $this->expectException( \InvalidArgumentException::class );
+ $this->testee->authenticate( $user );
+ }
+
+ /**
+ * @covers ::authenticate
+ */
+ public function testAuthentiCateReturnsUserObject() {
+ $user = (object) [
+ 'name' => 'Test',
+ 'email' => 'test@example.com',
+ ];
+
+ $wp_user = Mockery::mock( \WP_User::class );
+
+ $this->wpMockFunction(
+ 'email_exists',
+ [
+ 'test@example.com',
+ ],
+ 1,
+ true
+ );
+
+ $this->wpMockFunction(
+ 'get_user_by',
+ [
+ 'email',
+ 'test@example.com',
+ ],
+ 1,
+ $wp_user
+ );
+
+ $result = $this->testee->authenticate( $user );
+ $this->assertSame( $result, $wp_user );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::authenticate
+ * @covers ::maybe_create_username
+ */
+ public function testAuthenticateFilterApplied() {
+ $user = (object) [
+ 'name' => 'Test',
+ 'email' => 'test@example.com',
+ ];
+
+ $this->wpMockFunction(
+ 'email_exists',
+ [
+ 'test@example.com',
+ ],
+ 1,
+ false
+ );
+
+ $wp_user = Mockery::mock( \WP_User::class );
+
+ $this->wpMockFunction(
+ 'sanitize_user',
+ [
+ 'test',
+ true,
+ ],
+ 1,
+ 'test'
+ );
+
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+
+ $helperMock->expects( 'unique_username' )
+ ->once()
+ ->withArgs( ['test'] );
+
+ WP_Mock::onFilter( 'rtcamp.google_register_user' )->with( $user )->reply( $wp_user );
+
+ $this->testee->authenticate( $user );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::register
+ */
+ public function testRegisterThrowsExceptionForRegistrationDisabled() {
+ $this->settingsMock->registration_enabled = false;
+
+ $this->wpMockFunction(
+ 'get_option',
+ [
+ 'users_can_register',
+ false,
+ ],
+ 1,
+ false
+ );
+
+ $this->expectException( \Exception::class );
+
+ $this->testee->register( new \stdClass() );
+ }
+
+ /**
+ * @covers ::register
+ */
+ public function testRegisterThrowsExceptionForInvalidEmailDomain() {
+ $user = (object) [
+ 'email' => 'test@example.com',
+ 'login' => 'test',
+ ];
+ $this->settingsMock->registration_enabled = true;
+ $this->settingsMock->whitelisted_domains = 'somedomain.com';
+
+ $this->expectException( \Exception::class );
+ $this->testee->register( $user );
+ }
+
+ /**
+ * @covers ::register
+ * @covers ::can_register_with_email
+ */
+ public function testRegisterReturnsUserObject() {
+ $user = (object) [
+ 'email' => 'test@example.com',
+ 'login' => 'test',
+ ];
+
+ $this->settingsMock->registration_enabled = true;
+ $this->settingsMock->whitelisted_domains = 'example.com,somedomain.com';
+
+ $helperMock = Mockery::mock( 'alias:' . Helper::class );
+ $helperMock->expects( 'unique_username' )
+ ->once()
+ ->withArgs( ['test'] )
+ ->andReturn( 'test' );
+
+ $this->wpMockFunction(
+ 'wp_generate_password',
+ [ 18 ],
+ 1,
+ 'thisisrandompass'
+ );
+
+ $this->wpMockFunction(
+ 'wp_insert_user',
+ [
+ [
+ 'user_login' => 'test',
+ 'user_pass' => 'thisisrandompass',
+ 'user_email' => 'test@example.com',
+ ]
+ ],
+ 1,
+ 100
+ );
+
+ WP_Mock::expectAction( 'rtcamp.google_user_created', 100, $user );
+
+ $wp_user = Mockery::mock( \WP_User::class );
+
+ $this->wpMockFunction(
+ 'get_user_by',
+ [
+ 'id',
+ 100,
+ ],
+ 1,
+ $wp_user
+ );
+
+ $received = $this->testee->register( $user );
+
+ $this->assertSame( $wp_user, $received );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::set_auth_cookies
+ */
+ public function testSetAuthCookies() {
+ $wp_user = Mockery::mock( \WP_User::class );
+ $wp_user->ID = 100;
+ $wp_user->user_login = 'test';
+
+ $this->wpMockFunction(
+ 'wp_clear_auth_cookie',
+ [],
+ 1
+ );
+
+ $this->wpMockFunction(
+ 'wp_set_current_user',
+ [
+ 100,
+ 'test',
+ ],
+ 1
+ );
+
+ $this->wpMockFunction(
+ 'wp_set_auth_cookie',
+ [
+ 100
+ ],
+ 1
+ );
+
+ $this->testee->set_auth_cookies( $wp_user );
+ $this->assertConditionsMet();
+ }
+}
diff --git a/tests/php/Unit/Utils/GoogleClientTest.php b/tests/php/Unit/Utils/GoogleClientTest.php
new file mode 100644
index 00000000..8a3b24bb
--- /dev/null
+++ b/tests/php/Unit/Utils/GoogleClientTest.php
@@ -0,0 +1,350 @@
+testee = new Testee( [
+ 'client_id' => 'cid',
+ 'client_secret' => 'csc',
+ ] );
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testConstruct() {
+ $this->assertSame( 'cid', $this->getTesteeProperty( 'client_id', $this->testee ) );
+ $this->assertSame( 'csc', $this->getTesteeProperty( 'client_secret', $this->testee ) );
+ $this->assertSame( '', $this->getTesteeProperty( 'redirect_uri', $this->testee ) );
+ }
+
+ /**
+ * @covers ::__call
+ */
+ public function testCallWithUser() {
+ $this->expectException( Exception::class );
+ $this->testee->__call( 'user', null );
+ }
+
+ /**
+ * @covers ::__call
+ */
+ public function testCallWithEmails() {
+ $this->expectException( Exception::class );
+ $this->testee->__call( 'emails', null );
+ }
+
+ /**
+ * @covers ::__call
+ * @covers ::gt_redirect_url
+ */
+ public function testCallWithOtherMethods() {
+ WP_Mock::expectFilterNotAdded( 'rtcamp.github_redirect_url', '' );
+ $this->testee->__call( 'some_other_method', null );
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::set_access_token
+ * @covers ::state
+ * @covers ::gt_redirect_url
+ * @covers ::access_token
+ */
+ public function testSetAccessToken() {
+ WP_Mock::expectFilter( 'rtcamp.google_redirect_url', '' );
+
+ $this->wpMockFunction(
+ 'wp_remote_post',
+ [
+ 'https://oauth2.googleapis.com/token',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'body' => [
+ 'client_id' => 'cid',
+ 'client_secret' => 'csc',
+ 'redirect_uri' => '',
+ 'code' => 'abc',
+ 'grant_type' => 'authorization_code',
+ ],
+ ]
+ ],
+ 1,
+ 'response'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'response'
+ ],
+ 1,
+ 200
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_body',
+ [
+ 'response',
+ ],
+ 1,
+ function() {
+ $token = (object) [
+ 'access_token' => 'AccessToken'
+ ];
+ return json_encode( $token );
+ }
+ );
+
+ $obj = $this->testee->set_access_token( 'abc' );
+ $token = $this->getTesteeProperty( 'access_token', $this->testee );
+
+ $this->assertSame( $this->testee, $obj );
+ $this->assertSame( 'AccessToken', $token );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::set_access_token
+ */
+ public function testSetAccessTokenThrowsException() {
+ WP_Mock::expectFilter( 'rtcamp.google_redirect_url', '' );
+
+ $this->wpMockFunction(
+ 'wp_remote_post',
+ [
+ 'https://oauth2.googleapis.com/token',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'body' => [
+ 'client_id' => 'cid',
+ 'client_secret' => 'csc',
+ 'redirect_uri' => '',
+ 'code' => 'abc',
+ 'grant_type' => 'authorization_code',
+ ],
+ ]
+ ],
+ 1,
+ 'response'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'response'
+ ],
+ 1,
+ 400
+ );
+
+ $this->expectException( Exception::class );
+ $this->testee->set_access_token( 'abc' );
+ }
+
+ /**
+ * @covers ::access_token
+ */
+ public function testAccessTokenThrowsExceptionForNon200Code() {
+ $ghClient = $this->createPartialMock( Testee::class, [ 'gt_redirect_url' ] );
+ $ghClient->expects( $this->once() )->method( 'gt_redirect_url' )->willReturn( '' );
+
+ $ghClient->client_id = 'cid';
+ $ghClient->client_secret = 'csc';
+
+ $this->wpMockFunction(
+ 'wp_remote_post',
+ [
+ 'https://oauth2.googleapis.com/token',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ 'body' => [
+ 'client_id' => 'cid',
+ 'client_secret' => 'csc',
+ 'redirect_uri' => '',
+ 'code' => 'abc',
+ 'grant_type' => 'authorization_code',
+ ],
+ ]
+ ],
+ 1,
+ 'response'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'response'
+ ],
+ 1,
+ 400
+ );
+
+ $this->expectException( Exception::class );
+ $ghClient->access_token( 'abc' );
+ }
+
+ /**
+ * @covers ::user
+ */
+ public function testUserReturnsObject() {
+ $this->setTesteeProperty( $this->testee, 'access_token', 'someToken' );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ 'https://www.googleapis.com'
+ ],
+ 1,
+ 'https://www.googleapis.com/'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_get',
+ [
+ 'https://www.googleapis.com/oauth2/v2/userinfo?access_token=someToken',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ ],
+ 1,
+ 'response'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'response'
+ ],
+ 1,
+ 200
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_body',
+ [
+ 'response',
+ ],
+ 1,
+ function() {
+ $token = (object) [
+ 'email' => 'user@domain.com',
+ 'login' => 'login',
+ ];
+ return json_encode( $token );
+ }
+ );
+
+ $user = $this->testee->user();
+ $this->assertInstanceOf( \stdClass::class, $user );
+ $this->assertSame( $user->email, 'user@domain.com' );
+ $this->assertSame( $user->login, 'login' );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::user
+ */
+ public function testUserThrowsException() {
+ $this->setTesteeProperty( $this->testee, 'access_token', 'someToken' );
+
+ $this->wpMockFunction(
+ 'trailingslashit',
+ [
+ 'https://www.googleapis.com'
+ ],
+ 1,
+ 'https://www.googleapis.com/'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_get',
+ [
+ 'https://www.googleapis.com/oauth2/v2/userinfo?access_token=someToken',
+ [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]
+ ],
+ 1,
+ 'response'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'response'
+ ],
+ 1,
+ 404
+ );
+
+ $this->expectException( Exception::class );
+ $this->testee->user();
+ }
+
+ /**
+ * @covers ::authorization_url
+ */
+ public function testAuthorizationURL() {
+ $scope = [ 'email', 'profile', 'openid' ];
+ WP_Mock::onFilter( 'rtcamp.google_scope' )->with( $scope )->reply( $scope );
+ $ghClient = $this->createPartialMock( Testee::class, [ 'gt_redirect_url', 'state' ] );
+ $ghClient->expects( $this->once() )->method( 'gt_redirect_url' )->willReturn( '' );
+ $ghClient->expects( $this->once() )->method( 'state' )->willReturn( 'abcd' );
+ $ghClient->client_id = 'cid';
+
+ $client_args = [
+ 'client_id' => 'cid',
+ 'redirect_uri' => '',
+ 'state' => 'abcd',
+ 'scope' => implode( ' ', $scope ),
+ 'access_type' => 'online',
+ 'response_type' => 'code',
+ ];
+
+ WP_Mock::expectFilter( 'rtcamp.google_client_args', $client_args );
+
+ $expected = 'https://accounts.google.com/o/oauth2/auth?client_id=cid&redirect_uri=&state=abcd&scope=email+profile+openid&access_type=online&response_type=code';
+
+ $this->assertSame( $expected, $ghClient->authorization_url() );
+ }
+}
diff --git a/tests/php/Unit/Utils/TokenVerifierTest.php b/tests/php/Unit/Utils/TokenVerifierTest.php
new file mode 100644
index 00000000..1ed359b8
--- /dev/null
+++ b/tests/php/Unit/Utils/TokenVerifierTest.php
@@ -0,0 +1,288 @@
+settingsMock = $this->createMock( Settings::class );
+ $this->testee = new Testee( $this->settingsMock );
+ }
+
+ /**
+ * @covers ::__construct
+ */
+ public function testInstance() {
+ $this->assertInstanceOf( Testee::class, $this->testee );
+ }
+
+ public function testCertsURL() {
+ $this->assertSame( 'https://www.googleapis.com/oauth2/v1/certs', $this->testee::CERTS_URL );
+ }
+
+ /**
+ * @covers ::get_supported_algorithm
+ */
+ public function testGetSupportedAlgorithmDefault() {
+ \WP_Mock::expectFilter( 'rtcamp.default_algorithm', OPENSSL_ALGO_SHA256, '' );
+ $expected = OPENSSL_ALGO_SHA256;
+ $algo = $this->testee::get_supported_algorithm();
+
+ $this->assertSame( $expected, $algo );
+ }
+
+ /**
+ * @covers ::get_supported_algorithm
+ */
+ public function testGetSHA256Algo() {
+ $expected = OPENSSL_ALGO_SHA256;
+ $algo = $this->testee::get_supported_algorithm( 'RS256' );
+
+ $this->assertSame( $expected, $algo );
+ }
+
+ /**
+ * @covers ::base64_encode_url
+ */
+ public function testBase64EncodeURL() {
+ $str = 'some+random/string=';
+ $result = $this->testee->base64_encode_url( $str );
+
+ $this->assertSame( 'c29tZStyYW5kb20vc3RyaW5nPQ', $result );
+ }
+
+ /**
+ * @covers ::base64_decode_url
+ */
+ public function testBase64DecodeURL() {
+ $str = 'c29tZStyYW5kb20vc3RyaW5nPQ';
+ $result = $this->testee->base64_decode_url( $str );
+
+ $this->assertSame( 'some+random/string=', $result );
+ }
+
+ /**
+ * @covers ::current_user
+ */
+ public function testCurrentUser() {
+ $wp_user = (object) [
+ 'name' => 'Test',
+ ];
+ $this->setTesteeProperty( $this->testee, 'current_user', $wp_user );
+ $result = $this->testee->current_user();
+
+ $this->assertSame( $wp_user, $result );
+ }
+
+ /**
+ * @covers ::get_public_key
+ */
+ public function testPublicKeyIsNull() {
+ $pk = $this->testee->get_public_key( null );
+
+ $this->assertNull( $pk );
+ }
+
+ /**
+ * @covers ::get_public_key
+ */
+ public function testPublicKeyCachedValue() {
+ $this->wpMockFunction(
+ 'get_transient',
+ [
+ 'lwg_pk_my_public_key'
+ ],
+ 1,
+ 'abcd'
+ );
+
+ $pk = $this->testee->get_public_key( 'my_public_key' );
+
+ $this->assertSame( 'abcd', $pk );
+ }
+
+ /**
+ * @covers ::get_public_key
+ */
+ public function testPublicKeyIsNullForNon200Response() {
+ $this->wpMockFunction(
+ 'get_transient',
+ [
+ 'lwg_pk_my_public_key'
+ ],
+ 1,
+ null
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_get',
+ [
+ $this->testee::CERTS_URL
+ ],
+ 1,
+ 'certificate'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'certificate',
+ ],
+ 1,
+ 400
+ );
+
+ $pk = $this->testee->get_public_key( 'my_public_key' );
+
+ $this->assertNull( $pk );
+ }
+
+ /**
+ * @covers ::get_public_key
+ * @covers ::get_max_age
+ */
+ public function testPublicKeyRetrievalFromResponse() {
+ $this->wpMockFunction(
+ 'get_transient',
+ [
+ 'lwg_pk_my_public_key'
+ ],
+ 1,
+ null
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_get',
+ [
+ $this->testee::CERTS_URL
+ ],
+ 1,
+ 'certificate'
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_response_code',
+ [
+ 'certificate',
+ ],
+ 1,
+ 200
+ );
+
+ $headers = \Mockery::mock( \Requests_Utility_CaseInsensitiveDictionary::class );
+ $headers->expects( 'offsetExists' )->withArgs( [ 'cache-control' ] )->andReturn( true );
+ $headers->expects( 'offsetGet' )->withArgs( [ 'cache-control' ] )->andReturn( 'public, max-age=600' );
+
+ $body = [
+ 'my_public_key' => 'thisissomerandomkey',
+ ];
+
+ $body = json_encode( $body );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_headers',
+ [
+ 'certificate',
+ ],
+ 1,
+ $headers
+ );
+
+ $this->wpMockFunction(
+ 'wp_remote_retrieve_body',
+ [
+ 'certificate',
+ ],
+ 1,
+ $body
+ );
+
+ $this->wpMockFunction(
+ 'set_transient',
+ [
+ 'lwg_pk_my_public_key',
+ 'thisissomerandomkey',
+ 300
+ ],
+ 1,
+ true
+ );
+
+ $pk = $this->testee->get_public_key( 'my_public_key' );
+ $this->assertSame( 'thisissomerandomkey', $pk );
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::set_transient
+ */
+ public function testSetTransient() {
+ $this->wpMockFunction(
+ 'set_transient',
+ [
+ 'key',
+ 'val',
+ 200
+ ]
+ );
+
+ $this->call_private_method( $this->testee, 'set_transient', [ 'key', 'val', 200 ] );
+
+ $this->assertConditionsMet();
+ }
+
+ /**
+ * @covers ::get_transient
+ */
+ public function testGetTransient() {
+ $this->wpMockFunction(
+ 'get_transient',
+ [
+ 'key',
+ ],
+ 1,
+ 'val'
+ );
+
+ $val = $this->call_private_method( $this->testee, 'get_transient', [ 'key' ] );
+
+ $this->assertSame( 'val', $val );
+ $this->assertConditionsMet();
+ }
+}
diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php
new file mode 100644
index 00000000..112893e5
--- /dev/null
+++ b/tests/php/bootstrap.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+declare(strict_types=1);
+
+$vendor = dirname( __DIR__, 2 ) . '/vendor/';
+
+if (!file_exists($vendor . 'autoload.php')) {
+ die('Please install via Composer before running tests.');
+}
+
+require_once __DIR__ . '/stubs/hooks.php';
+require_once $vendor . 'autoload.php';
+require_once __DIR__ . '/TestCase.php';
+WP_Mock::setUsePatchwork(true);
+WP_Mock::bootstrap();
+unset($vendor);
+
+if ( ! defined( 'GH_PLUGIN_DIR' ) ) {
+ define( 'GH_PLUGIN_DIR', dirname( __DIR__, 2 ) );
+}
diff --git a/tests/php/stubs/hooks.php b/tests/php/stubs/hooks.php
new file mode 100644
index 00000000..c530401d
--- /dev/null
+++ b/tests/php/stubs/hooks.php
@@ -0,0 +1,15 @@
+
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Tests;
-
-use Exception;
-use WP_Google_Login\Inc\Google_Auth;
-
-/**
- * Class Test_Google_Auth
- *
- * @coversDefaultClass \WP_Google_Login\Inc\Google_Auth
- */
-class Test_Google_Auth extends \WP_UnitTestCase {
-
- /**
- * This google_auth data member will contain google_auth object.
- *
- * @var \WP_Google_Login\Inc\Google_Auth
- */
- protected $_instance = false;
-
- /**
- * This function set the instance for class google-auth.
- */
- public function setUp(): void {
-
- $this->_instance = Google_Auth::get_instance();
-
- }
-
- /**
- * Test the filters and Google_Client instance.
- *
- * @covers ::__construct
- * @covers ::_include_vendor
- */
- public function test_construct() {
-
- Utility::invoke_method( $this->_instance, '__construct' );
-
- $client = Utility::get_property( $this->_instance, '_client' );
-
- $this->assertInstanceOf( 'Google_Client', $client );
-
- $this->assertEquals( 10, has_filter( 'authenticate', [ $this->_instance, 'authenticate_user' ] ) );
- $this->assertEquals( 10, has_filter( 'registration_redirect', [ $this->_instance, 'get_login_redirect' ] ) );
- $this->assertEquals( 10, has_filter( 'login_redirect', [ $this->_instance, 'get_login_redirect' ] ) );
- $this->assertEquals( 10, has_filter( 'allowed_redirect_hosts', [ $this->_instance, 'maybe_whitelist_subdomain' ] ) );
- }
-
- /**
- * Test Google client instance and attributes.
- *
- * @covers ::_get_client
- */
- public function test_get_client() {
-
- $google_client = Utility::invoke_method( $this->_instance, '_get_client' );
- $client_config = Utility::get_property( $google_client, 'config' );
-
- $this->assertInstanceOf( 'Google_Client', $google_client );
-
- $this->assertEquals( $client_config['application_name'], 'WP Google Login' );
- $this->assertEquals( $client_config['client_id'], WP_GOOGLE_LOGIN_CLIENT_ID );
- $this->assertEquals( $client_config['client_secret'], WP_GOOGLE_LOGIN_SECRET );
-
- $state = $client_config['state'];
- $state = explode( '|', urldecode_deep( $state ) );
-
- if ( empty( $_redirect_to ) ) {
- $this->assertEquals( $state[0], admin_url() );
- } else {
- $this->assertEquals( $state[0], $_redirect_to );
- }
-
- if ( ! empty( get_current_blog_id() ) ) {
- $this->assertEquals( $state[1], get_current_blog_id() );
- }
-
- if ( is_multisite() ) {
- $this->assertEquals( $client_config['redirect_uri'], network_site_url( 'wp-login.php' ) );
- } else {
- $this->assertEquals( $client_config['redirect_uri'], wp_login_url() );
- }
-
- }
-
- /**
- * Test user can register or not.
- *
- * @covers ::_can_users_register
- */
- public function test_can_users_register() {
-
- $this->assertFalse( Utility::invoke_method( $this->_instance, '_can_users_register' ) );
-
- define( 'WP_GOOGLE_LOGIN_USER_REGISTRATION', false );
- $this->assertFalse( Utility::invoke_method( $this->_instance, '_can_users_register' ) );
-
- }
-
- /**
- * @covers ::_get_login_url
- */
- public function test__get_login_url() {
-
- /**
- * Test 1: For single site.
- */
- $this->assertEquals( Utility::invoke_method( $this->_instance, '_get_login_url' ), wp_login_url() );
-
- /**
- * Test 2:
- */
- define( 'BLOG_ID_CURRENT_SITE', 1 );
-
- $this->assertEquals( Utility::invoke_method( $this->_instance, '_get_login_url' ), wp_login_url() );
-
- }
-
- /**
- * Test login url.
- *
- * @covers ::get_login_url
- */
- public function test_get_login_url() {
- $this->assertContains( 'https://accounts.google.com/o/oauth2/auth', $this->_instance->get_login_url() );
- }
-
- /**
- * Test the user info from google auth token.
- *
- * @covers ::_get_user_from_token
- */
- public function test_get_user_from_token() {
-
- $output = Utility::invoke_method( $this->_instance, '_get_user_from_token', [ '' ] );
- $this->assertEmpty( $output );
-
- $output = Utility::invoke_method( $this->_instance, '_get_user_from_token', [ 'sadjhsfjf64das2d4s' ] );
- $this->assertInstanceOf( 'Google_Service_Exception', $output );
-
- }
-
- /**
- * @covers ::_get_scopes
- */
- public function test_get_scopes() {
-
- $output = Utility::invoke_method( $this->_instance, '_get_scopes' );
-
- $this->assertEquals( 'email profile openid', $output );
-
- }
-
- /**
- * Test create user base on provided data.
- *
- * @covers ::_create_user
- */
- public function test_create_user() {
-
- /**
- * Test 1: User detail without email.
- */
- $this->assertEquals( 0, Utility::invoke_method( $this->_instance, '_create_user', [ [ 'display_name' => 'User Name' ] ] ) );
-
- /**
- * Test 2: Pass user email address.
- */
- $user_1_id = Utility::invoke_method( $this->_instance, '_create_user', [ [ 'user_email' => 'user@example.com' ] ] );
- $user_1_data = get_userdata( $user_1_id );
- $this->assertGreaterThan( 0, $user_1_id );
-
- /**
- * Test 3: With identical email address.
- */
- $user_2_id = Utility::invoke_method( $this->_instance, '_create_user', [ [ 'user_email' => 'user@example2.com' ] ] );
- $user_2_data = get_userdata( $user_2_id );
-
- $this->assertNotEquals( $user_1_id, $user_2_id );
- $this->assertNotEquals( $user_1_data->data->user_login, $user_2_data->data->user_login );
- }
-
- /**
- * Test for given email address can be register or not.
- *
- * @covers ::_can_register_with_email
- */
- public function test_can_register_with_email() {
-
- /**
- * Test 1: Don't allow empty email
- */
- $this->assertFalse( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ '' ] ) );
-
- /**
- * Test 2: Allow email with any domain.
- */
- $this->assertTrue( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@gmail.com' ] ) );
- $this->assertTrue( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@sample.com' ] ) );
- $this->assertTrue( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@example.com' ] ) );
-
- /**
- * Test 3: Allow selected domains.
- */
- define( 'WP_GOOGLE_LOGIN_WHITELIST_DOMAINS', 'example.com, sample.com' );
-
- $this->assertFalse( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@gmail.com' ] ) );
- $this->assertTrue( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@sample.com' ] ) );
- $this->assertTrue( Utility::invoke_method( $this->_instance, '_can_register_with_email', [ 'user@example.com' ] ) );
- }
-
- /**
- * To authenticate user.
- *
- * @covers ::authenticate_user
- */
- public function test_authenticate_user() {
-
- /**
- * Adding helper hook on wp_redirect which will throw exception
- * which have message as redirected URL and Code as status.
- * This is one way of escaping from exit in the code.
- */
- add_filter( 'wp_redirect', [ $this, 'catch_redirect_destination' ], 99, 2 );
-
- /**
- * Test 1: No User passed, No token provided.
- */
- $this->assertEmpty( $this->_instance->authenticate_user( null ) );
-
- /**
- * Test 2: After passing token and state.
- */
- $state = [
- 'redirect_to' => home_url( 'wp-admin/edit.php' ),
- 'blog_id' => 1,
- ];
-
- $_GET['code'] = 'token_code';
- $_GET['state'] = urlencode_deep( implode( '|', $state ) );
-
- $output = $this->_instance->authenticate_user( 'custom_user' );
- $this->assertEquals( 'custom_user', $output );
-
- /**
- * Test 3: blog id not equals to current blog
- */
- $blog_id = self::factory()->blog->create();
- $current_blog = get_current_blog_id();
-
- switch_to_blog( $blog_id ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog
-
- $this->expectException(Exception::class);
-
- $output = $this->_instance->authenticate_user( 'custom_user' );
-
- switch_to_blog( $current_blog ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.switch_to_blog_switch_to_blog
-
- remove_filter( 'wp_redirect', [ $this, 'catch_redirect_destination' ], 99 );
- }
-
- /**
- * Test login url.
- *
- * @covers ::get_login_redirect
- */
- public function test_get_login_redirect() {
-
- $_redirect_to = Utility::get_property( $this->_instance, '_redirect_to' );
- $this->assertEquals( $_redirect_to, $this->_instance->get_login_redirect( $_redirect_to ) );
- }
-
- /**
- * Test whitelisted sub domain and redirect_to.
- *
- * @covers ::maybe_whitelist_subdomain
- */
- public function test_maybe_whitelist_subdomain() {
-
- Utility::set_and_get_property( $this->_instance, '_redirect_to', 'https://externalurl.com' );
-
- $this->assertContains( 'externalurl.com', $this->_instance->maybe_whitelist_subdomain( [] ) );
-
- }
-
- /**
- * To catch any redirection and throw location and status in Exception.
- * Note : Destination location can be get from Exception Message and
- * status can be get from Exception code.
- *
- * @param string $location Redirected location.
- * @param int $status Status.
- *
- * @throws \Exception Redirection data.
- *
- * @return void
- */
- public function catch_redirect_destination( $location, $status ) { // phpcs:ignore WordPressVIPMinimum.Filters.AlwaysReturn.missingReturnStatement
- throw new \Exception( $location, $status );
- }
-}
-
diff --git a/tests/test-class-helper.php b/tests/test-class-helper.php
deleted file mode 100644
index 59ed7713..00000000
--- a/tests/test-class-helper.php
+++ /dev/null
@@ -1,87 +0,0 @@
-
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Tests;
-
-use WP_Google_Login\Inc\Helper;
-
-/**
- * Class Test_Helper
- *
- * @coversDefaultClass \WP_Google_Login\Inc\Helper
- */
-class Test_Helper extends \WP_UnitTestCase {
-
- /**
- * @covers ::render_template
- */
- public function test_render_template() {
-
- $template_path = sprintf( '%s/template/google-login-button.php', WP_GOOGLE_LOGIN_PATH );
- $this->assertFileExists( $template_path );
-
- $login_url = 'http://google.com';
-
- /**
- * Test 1: Without passing third args.
- */
- ob_start();
- Helper::render_template( $template_path, [ 'login_url' => $login_url ] );
- $rendered_contents = ob_get_clean();
-
- $this->assertContains( $login_url, $rendered_contents );
-
- /**
- * Test 2: By passing third args $echo as false.
- */
- $output = Helper::render_template( $template_path, [ 'login_url' => $login_url ], false );
-
- $this->assertContains( $login_url, $output );
-
- /**
- * Test 3: By passing invalid file.
- */
- $output = Helper::render_template( 'invalid/file/path.php', [], false );
- $this->assertEquals( '', $output );
- }
-
- /**
- * @covers ::filter_input
- */
- public function test_filter_input() {
-
- /**
- * Test 1: Check with custom values.
- */
- $_GET['custom_key'] = 'Values on GET variable.';
- $_POST['custom_key'] = 'Values on POST variable.';
- $_COOKIE['custom_key'] = 'Values on COOKIE variable.';
- $_ENV['custom_key'] = 'Values on ENV variable.';
-
- $this->assertEquals( $_GET['custom_key'], Helper::filter_input( INPUT_GET, 'custom_key' ) );
- $this->assertEquals( $_POST['custom_key'], Helper::filter_input( INPUT_POST, 'custom_key' ) );
- $this->assertEquals( $_COOKIE['custom_key'], Helper::filter_input( INPUT_COOKIE, 'custom_key' ) );
- $this->assertEquals( $_ENV['custom_key'], Helper::filter_input( INPUT_ENV, 'custom_key' ) );
- $this->assertEquals( $_SERVER['HTTP_HOST'], Helper::filter_input( INPUT_SERVER, 'HTTP_HOST' ) );
-
- unset( $_GET['custom_key'], $_POST['custom_key'], $_COOKIE['custom_key'], $_ENV['custom_key'] );
-
- /**
- * Test 2: Check with keys those are not set.
- */
- $this->assertNull( Helper::filter_input( INPUT_GET, 'custom_key' ) );
- $this->assertNull( Helper::filter_input( INPUT_POST, 'custom_key' ) );
- $this->assertNull( Helper::filter_input( INPUT_COOKIE, 'custom_key' ) );
- $this->assertNull( Helper::filter_input( INPUT_ENV, 'custom_key' ) );
- $this->assertNull( Helper::filter_input( INPUT_SERVER, 'custom_key' ) );
- $this->assertNull( Helper::filter_input( INPUT_REQUEST, 'custom_key' ) );
- }
-
-}
-
diff --git a/tests/test-class-plugin.php b/tests/test-class-plugin.php
deleted file mode 100644
index 23445a8b..00000000
--- a/tests/test-class-plugin.php
+++ /dev/null
@@ -1,80 +0,0 @@
-
- *
- * @package login-with-google
- */
-
-namespace WP_Google_Login\Tests;
-
-use WP_Google_Login\Inc\Plugin;
-use WP_Google_Login\Inc\Google_Auth;
-
-/**
- * Class Test_Plugin
- *
- * @coversDefaultClass \WP_Google_Login\Inc\Plugin
- */
-class Test_Plugin extends \WP_UnitTestCase {
-
- /**
- * @var \WP_Google_Login\Inc\Plugin
- */
- protected $_instance = false;
-
- /**
- * Setup method.
- *
- * @return void
- */
- public function setUp() {
-
- $this->_instance = Plugin::get_instance();
-
- parent::setUp();
- }
-
- /**
- * @covers ::__construct
- * @covers ::_setup_hooks
- */
- public function test__construct() {
-
- Utility::invoke_method( $this->_instance, '__construct' );
-
- $this->assertEquals( 10, has_action( 'login_enqueue_scripts', [ $this->_instance, 'login_enqueue_scripts' ] ) );
- $this->assertEquals( 10, has_action( 'login_form', [ $this->_instance, 'add_google_login_button' ] ) );
- $this->assertEquals( 10, has_action( 'register_form', [ $this->_instance, 'add_google_login_button' ] ) );
-
- }
-
- /**
- * @covers ::login_enqueue_scripts
- */
- public function test_login_enqueue_scripts() {
-
- $this->_instance->login_enqueue_scripts();
-
- $this->assertTrue( wp_style_is( 'wp_google_login_style', 'registered' ) );
- $this->assertTrue( wp_script_is( 'wp_google_login_script', 'registered' ) );
-
- }
-
- /**
- * @covers ::add_google_login_button
- */
- public function test_add_google_login_button() {
-
- $google_auth = Google_Auth::get_instance();
- $login_url = esc_url( $google_auth->get_login_url() );
-
- ob_start();
- $this->_instance->add_google_login_button();
- $login_button_html = ob_get_clean();
-
- $this->assertContains( $login_url, $login_button_html );
- }
-
-}
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 00000000..2a197b7d
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see http://www.php-fig.org/psr/psr-0/
+ * @see http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+ private $classMapAuthoritative = false;
+ private $missingClasses = array();
+ private $apcuPrefix;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * 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, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 00000000..62ecfd8d
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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.
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 00000000..7a91153b
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,9 @@
+ array($vendorDir . '/pimple/pimple/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 00000000..8cc4e05a
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,11 @@
+ array($baseDir . '/src'),
+ 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 00000000..721aff6d
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,55 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require_once __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticIniteae54bb1498c1e8cc3f4af6a43a932c6::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 00000000..a36f2b1e
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,50 @@
+
+ array (
+ 'RtCamp\\GoogleLogin\\' => 19,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\Container\\' => 14,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'RtCamp\\GoogleLogin\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/src',
+ ),
+ 'Psr\\Container\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/container/src',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'P' =>
+ array (
+ 'Pimple' =>
+ array (
+ 0 => __DIR__ . '/..' . '/pimple/pimple/src',
+ ),
+ ),
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticIniteae54bb1498c1e8cc3f4af6a43a932c6::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticIniteae54bb1498c1e8cc3f4af6a43a932c6::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticIniteae54bb1498c1e8cc3f4af6a43a932c6::$prefixesPsr0;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 00000000..8ef9b668
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,100 @@
+[
+ {
+ "name": "pimple/pimple",
+ "version": "v3.4.0",
+ "version_normalized": "3.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/silexphp/Pimple.git",
+ "reference": "86406047271859ffc13424a048541f4531f53601"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/silexphp/Pimple/zipball/86406047271859ffc13424a048541f4531f53601",
+ "reference": "86406047271859ffc13424a048541f4531f53601",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^5.0"
+ },
+ "time": "2021-03-06T08:28:00+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Pimple": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Pimple, a simple Dependency Injection Container",
+ "homepage": "https://pimple.symfony.com",
+ "keywords": [
+ "container",
+ "dependency injection"
+ ]
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "time": "2021-03-05T17:36:06+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ]
+ }
+]
diff --git a/vendor/pimple/pimple/.gitignore b/vendor/pimple/pimple/.gitignore
new file mode 100644
index 00000000..a5c7ed69
--- /dev/null
+++ b/vendor/pimple/pimple/.gitignore
@@ -0,0 +1,4 @@
+phpunit.xml
+.phpunit.result.cache
+composer.lock
+/vendor/
diff --git a/vendor/pimple/pimple/.php_cs.dist b/vendor/pimple/pimple/.php_cs.dist
new file mode 100644
index 00000000..2787bb40
--- /dev/null
+++ b/vendor/pimple/pimple/.php_cs.dist
@@ -0,0 +1,20 @@
+setRules([
+ '@Symfony' => true,
+ '@Symfony:risky' => true,
+ '@PHPUnit75Migration:risky' => true,
+ 'php_unit_dedicate_assert' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'php_unit_fqcn_annotation' => true,
+ 'no_unreachable_default_argument_value' => false,
+ 'braces' => ['allow_single_line_closure' => true],
+ 'heredoc_to_nowdoc' => false,
+ 'ordered_imports' => true,
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'],
+ ])
+ ->setRiskyAllowed(true)
+ ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__.'/src'))
+;
diff --git a/vendor/pimple/pimple/.travis.yml b/vendor/pimple/pimple/.travis.yml
new file mode 100644
index 00000000..046ec308
--- /dev/null
+++ b/vendor/pimple/pimple/.travis.yml
@@ -0,0 +1,19 @@
+language: php
+
+env:
+ global:
+ - REPORT_EXIT_STATUS=1
+
+php:
+ - 7.2
+ - 7.3
+ - 7.4
+ - 8.0
+ - nightly
+
+before_script:
+ - composer self-update
+ - COMPOSER_ROOT_VERSION=dev-master composer install
+
+script:
+ - ./vendor/bin/simple-phpunit
diff --git a/vendor/pimple/pimple/CHANGELOG b/vendor/pimple/pimple/CHANGELOG
new file mode 100644
index 00000000..08059e5e
--- /dev/null
+++ b/vendor/pimple/pimple/CHANGELOG
@@ -0,0 +1,72 @@
+* 3.4.0 (2021-03-06)
+
+ * Implement version 1.1 of PSR-11
+
+* 3.3.1 (2020-11-24)
+
+ * Add support for PHP 8
+
+* 3.3.0 (2020-03-03)
+
+ * Drop PHP extension
+ * Bump min PHP version to 7.2.5
+
+* 3.2.3 (2018-01-21)
+
+ * prefixed all function calls with \ for extra speed
+
+* 3.2.2 (2017-07-23)
+
+ * reverted extending a protected closure throws an exception (deprecated it instead)
+
+* 3.2.1 (2017-07-17)
+
+ * fixed PHP error
+
+* 3.2.0 (2017-07-17)
+
+ * added a PSR-11 service locator
+ * added a PSR-11 wrapper
+ * added ServiceIterator
+ * fixed extending a protected closure (now throws InvalidServiceIdentifierException)
+
+* 3.1.0 (2017-07-03)
+
+ * deprecated the C extension
+ * added support for PSR-11 exceptions
+
+* 3.0.2 (2015-09-11)
+
+ * refactored the C extension
+ * minor non-significant changes
+
+* 3.0.1 (2015-07-30)
+
+ * simplified some code
+ * fixed a segfault in the C extension
+
+* 3.0.0 (2014-07-24)
+
+ * removed the Pimple class alias (use Pimple\Container instead)
+
+* 2.1.1 (2014-07-24)
+
+ * fixed compiler warnings for the C extension
+ * fixed code when dealing with circular references
+
+* 2.1.0 (2014-06-24)
+
+ * moved the Pimple to Pimple\Container (with a BC layer -- Pimple is now a
+ deprecated alias which will be removed in Pimple 3.0)
+ * added Pimple\ServiceProviderInterface (and Pimple::register())
+
+* 2.0.0 (2014-02-10)
+
+ * changed extend to automatically re-assign the extended service and keep it as shared or factory
+ (to keep BC, extend still returns the extended service)
+ * changed services to be shared by default (use factory() for factory
+ services)
+
+* 1.0.0
+
+ * initial version
diff --git a/vendor/pimple/pimple/LICENSE b/vendor/pimple/pimple/LICENSE
new file mode 100644
index 00000000..3e2a9e1e
--- /dev/null
+++ b/vendor/pimple/pimple/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2009-2020 Fabien Potencier
+
+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.
diff --git a/vendor/pimple/pimple/README.rst b/vendor/pimple/pimple/README.rst
new file mode 100644
index 00000000..70818395
--- /dev/null
+++ b/vendor/pimple/pimple/README.rst
@@ -0,0 +1,332 @@
+Pimple
+======
+
+.. caution::
+
+ Pimple is now closed for changes. No new features will be added and no
+ cosmetic changes will be accepted either. The only accepted changes are
+ compatibility with newer PHP versions and security issue fixes.
+
+.. caution::
+
+ This is the documentation for Pimple 3.x. If you are using Pimple 1.x, read
+ the `Pimple 1.x documentation`_. Reading the Pimple 1.x code is also a good
+ way to learn more about how to create a simple Dependency Injection
+ Container (recent versions of Pimple are more focused on performance).
+
+Pimple is a small Dependency Injection Container for PHP.
+
+Installation
+------------
+
+Before using Pimple in your project, add it to your ``composer.json`` file:
+
+.. code-block:: bash
+
+ $ ./composer.phar require pimple/pimple "^3.0"
+
+Usage
+-----
+
+Creating a container is a matter of creating a ``Container`` instance:
+
+.. code-block:: php
+
+ use Pimple\Container;
+
+ $container = new Container();
+
+As many other dependency injection containers, Pimple manages two different
+kind of data: **services** and **parameters**.
+
+Defining Services
+~~~~~~~~~~~~~~~~~
+
+A service is an object that does something as part of a larger system. Examples
+of services: a database connection, a templating engine, or a mailer. Almost
+any **global** object can be a service.
+
+Services are defined by **anonymous functions** that return an instance of an
+object:
+
+.. code-block:: php
+
+ // define some services
+ $container['session_storage'] = function ($c) {
+ return new SessionStorage('SESSION_ID');
+ };
+
+ $container['session'] = function ($c) {
+ return new Session($c['session_storage']);
+ };
+
+Notice that the anonymous function has access to the current container
+instance, allowing references to other services or parameters.
+
+As objects are only created when you get them, the order of the definitions
+does not matter.
+
+Using the defined services is also very easy:
+
+.. code-block:: php
+
+ // get the session object
+ $session = $container['session'];
+
+ // the above call is roughly equivalent to the following code:
+ // $storage = new SessionStorage('SESSION_ID');
+ // $session = new Session($storage);
+
+Defining Factory Services
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, each time you get a service, Pimple returns the **same instance**
+of it. If you want a different instance to be returned for all calls, wrap your
+anonymous function with the ``factory()`` method
+
+.. code-block:: php
+
+ $container['session'] = $container->factory(function ($c) {
+ return new Session($c['session_storage']);
+ });
+
+Now, each call to ``$container['session']`` returns a new instance of the
+session.
+
+Defining Parameters
+~~~~~~~~~~~~~~~~~~~
+
+Defining a parameter allows to ease the configuration of your container from
+the outside and to store global values:
+
+.. code-block:: php
+
+ // define some parameters
+ $container['cookie_name'] = 'SESSION_ID';
+ $container['session_storage_class'] = 'SessionStorage';
+
+If you change the ``session_storage`` service definition like below:
+
+.. code-block:: php
+
+ $container['session_storage'] = function ($c) {
+ return new $c['session_storage_class']($c['cookie_name']);
+ };
+
+You can now easily change the cookie name by overriding the
+``cookie_name`` parameter instead of redefining the service
+definition.
+
+Protecting Parameters
+~~~~~~~~~~~~~~~~~~~~~
+
+Because Pimple sees anonymous functions as service definitions, you need to
+wrap anonymous functions with the ``protect()`` method to store them as
+parameters:
+
+.. code-block:: php
+
+ $container['random_func'] = $container->protect(function () {
+ return rand();
+ });
+
+Modifying Services after Definition
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In some cases you may want to modify a service definition after it has been
+defined. You can use the ``extend()`` method to define additional code to be
+run on your service just after it is created:
+
+.. code-block:: php
+
+ $container['session_storage'] = function ($c) {
+ return new $c['session_storage_class']($c['cookie_name']);
+ };
+
+ $container->extend('session_storage', function ($storage, $c) {
+ $storage->...();
+
+ return $storage;
+ });
+
+The first argument is the name of the service to extend, the second a function
+that gets access to the object instance and the container.
+
+Extending a Container
+~~~~~~~~~~~~~~~~~~~~~
+
+If you use the same libraries over and over, you might want to reuse some
+services from one project to the next one; package your services into a
+**provider** by implementing ``Pimple\ServiceProviderInterface``:
+
+.. code-block:: php
+
+ use Pimple\Container;
+
+ class FooProvider implements Pimple\ServiceProviderInterface
+ {
+ public function register(Container $pimple)
+ {
+ // register some services and parameters
+ // on $pimple
+ }
+ }
+
+Then, register the provider on a Container:
+
+.. code-block:: php
+
+ $pimple->register(new FooProvider());
+
+Fetching the Service Creation Function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you access an object, Pimple automatically calls the anonymous function
+that you defined, which creates the service object for you. If you want to get
+raw access to this function, you can use the ``raw()`` method:
+
+.. code-block:: php
+
+ $container['session'] = function ($c) {
+ return new Session($c['session_storage']);
+ };
+
+ $sessionFunction = $container->raw('session');
+
+PSR-11 compatibility
+--------------------
+
+For historical reasons, the ``Container`` class does not implement the PSR-11
+``ContainerInterface``. However, Pimple provides a helper class that will let
+you decouple your code from the Pimple container class.
+
+The PSR-11 container class
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``Pimple\Psr11\Container`` class lets you access the content of an
+underlying Pimple container using ``Psr\Container\ContainerInterface``
+methods:
+
+.. code-block:: php
+
+ use Pimple\Container;
+ use Pimple\Psr11\Container as PsrContainer;
+
+ $container = new Container();
+ $container['service'] = function ($c) {
+ return new Service();
+ };
+ $psr11 = new PsrContainer($container);
+
+ $controller = function (PsrContainer $container) {
+ $service = $container->get('service');
+ };
+ $controller($psr11);
+
+Using the PSR-11 ServiceLocator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, a service needs access to several other services without being sure
+that all of them will actually be used. In those cases, you may want the
+instantiation of the services to be lazy.
+
+The traditional solution is to inject the entire service container to get only
+the services really needed. However, this is not recommended because it gives
+services a too broad access to the rest of the application and it hides their
+actual dependencies.
+
+The ``ServiceLocator`` is intended to solve this problem by giving access to a
+set of predefined services while instantiating them only when actually needed.
+
+It also allows you to make your services available under a different name than
+the one used to register them. For instance, you may want to use an object
+that expects an instance of ``EventDispatcherInterface`` to be available under
+the name ``event_dispatcher`` while your event dispatcher has been
+registered under the name ``dispatcher``:
+
+.. code-block:: php
+
+ use Monolog\Logger;
+ use Pimple\Psr11\ServiceLocator;
+ use Psr\Container\ContainerInterface;
+ use Symfony\Component\EventDispatcher\EventDispatcher;
+
+ class MyService
+ {
+ /**
+ * "logger" must be an instance of Psr\Log\LoggerInterface
+ * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface
+ */
+ private $services;
+
+ public function __construct(ContainerInterface $services)
+ {
+ $this->services = $services;
+ }
+ }
+
+ $container['logger'] = function ($c) {
+ return new Monolog\Logger();
+ };
+ $container['dispatcher'] = function () {
+ return new EventDispatcher();
+ };
+
+ $container['service'] = function ($c) {
+ $locator = new ServiceLocator($c, array('logger', 'event_dispatcher' => 'dispatcher'));
+
+ return new MyService($locator);
+ };
+
+Referencing a Collection of Services Lazily
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Passing a collection of services instances in an array may prove inefficient
+if the class that consumes the collection only needs to iterate over it at a
+later stage, when one of its method is called. It can also lead to problems
+if there is a circular dependency between one of the services stored in the
+collection and the class that consumes it.
+
+The ``ServiceIterator`` class helps you solve these issues. It receives a
+list of service names during instantiation and will retrieve the services
+when iterated over:
+
+.. code-block:: php
+
+ use Pimple\Container;
+ use Pimple\ServiceIterator;
+
+ class AuthorizationService
+ {
+ private $voters;
+
+ public function __construct($voters)
+ {
+ $this->voters = $voters;
+ }
+
+ public function canAccess($resource)
+ {
+ foreach ($this->voters as $voter) {
+ if (true === $voter->canAccess($resource)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ $container = new Container();
+
+ $container['voter1'] = function ($c) {
+ return new SomeVoter();
+ }
+ $container['voter2'] = function ($c) {
+ return new SomeOtherVoter($c['auth']);
+ }
+ $container['auth'] = function ($c) {
+ return new AuthorizationService(new ServiceIterator($c, array('voter1', 'voter2'));
+ }
+
+.. _Pimple 1.x documentation: https://github.com/silexphp/Pimple/tree/1.1
diff --git a/vendor/pimple/pimple/composer.json b/vendor/pimple/pimple/composer.json
new file mode 100644
index 00000000..fd319eb8
--- /dev/null
+++ b/vendor/pimple/pimple/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "pimple/pimple",
+ "type": "library",
+ "description": "Pimple, a simple Dependency Injection Container",
+ "keywords": ["dependency injection", "container"],
+ "homepage": "https://pimple.symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^5.0"
+ },
+ "autoload": {
+ "psr-0": { "Pimple": "src/" }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.4.x-dev"
+ }
+ }
+}
diff --git a/vendor/pimple/pimple/phpunit.xml.dist b/vendor/pimple/pimple/phpunit.xml.dist
new file mode 100644
index 00000000..89902022
--- /dev/null
+++ b/vendor/pimple/pimple/phpunit.xml.dist
@@ -0,0 +1,18 @@
+
+
+
+
+
+ ./src/Pimple/Tests
+
+
+
+
+
+
+
diff --git a/vendor/pimple/pimple/src/Pimple/Container.php b/vendor/pimple/pimple/src/Pimple/Container.php
new file mode 100644
index 00000000..715de10e
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Container.php
@@ -0,0 +1,298 @@
+factories = new \SplObjectStorage();
+ $this->protected = new \SplObjectStorage();
+
+ foreach ($values as $key => $value) {
+ $this->offsetSet($key, $value);
+ }
+ }
+
+ /**
+ * Sets a parameter or an object.
+ *
+ * Objects must be defined as Closures.
+ *
+ * Allowing any PHP callable leads to difficult to debug problems
+ * as function names (strings) are callable (creating a function with
+ * the same name as an existing parameter would break your container).
+ *
+ * @param string $id The unique identifier for the parameter or object
+ * @param mixed $value The value of the parameter or a closure to define an object
+ *
+ * @throws FrozenServiceException Prevent override of a frozen service
+ */
+ public function offsetSet($id, $value)
+ {
+ if (isset($this->frozen[$id])) {
+ throw new FrozenServiceException($id);
+ }
+
+ $this->values[$id] = $value;
+ $this->keys[$id] = true;
+ }
+
+ /**
+ * Gets a parameter or an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return mixed The value of the parameter or an object
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ */
+ public function offsetGet($id)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (
+ isset($this->raw[$id])
+ || !\is_object($this->values[$id])
+ || isset($this->protected[$this->values[$id]])
+ || !\method_exists($this->values[$id], '__invoke')
+ ) {
+ return $this->values[$id];
+ }
+
+ if (isset($this->factories[$this->values[$id]])) {
+ return $this->values[$id]($this);
+ }
+
+ $raw = $this->values[$id];
+ $val = $this->values[$id] = $raw($this);
+ $this->raw[$id] = $raw;
+
+ $this->frozen[$id] = true;
+
+ return $val;
+ }
+
+ /**
+ * Checks if a parameter or an object is set.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return bool
+ */
+ public function offsetExists($id)
+ {
+ return isset($this->keys[$id]);
+ }
+
+ /**
+ * Unsets a parameter or an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ */
+ public function offsetUnset($id)
+ {
+ if (isset($this->keys[$id])) {
+ if (\is_object($this->values[$id])) {
+ unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
+ }
+
+ unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
+ }
+ }
+
+ /**
+ * Marks a callable as being a factory service.
+ *
+ * @param callable $callable A service definition to be used as a factory
+ *
+ * @return callable The passed callable
+ *
+ * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
+ */
+ public function factory($callable)
+ {
+ if (!\is_object($callable) || !\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.');
+ }
+
+ $this->factories->attach($callable);
+
+ return $callable;
+ }
+
+ /**
+ * Protects a callable from being interpreted as a service.
+ *
+ * This is useful when you want to store a callable as a parameter.
+ *
+ * @param callable $callable A callable to protect from being evaluated
+ *
+ * @return callable The passed callable
+ *
+ * @throws ExpectedInvokableException Service definition has to be a closure or an invokable object
+ */
+ public function protect($callable)
+ {
+ if (!\is_object($callable) || !\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Callable is not a Closure or invokable object.');
+ }
+
+ $this->protected->attach($callable);
+
+ return $callable;
+ }
+
+ /**
+ * Gets a parameter or the closure defining an object.
+ *
+ * @param string $id The unique identifier for the parameter or object
+ *
+ * @return mixed The value of the parameter or the closure defining an object
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ */
+ public function raw($id)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (isset($this->raw[$id])) {
+ return $this->raw[$id];
+ }
+
+ return $this->values[$id];
+ }
+
+ /**
+ * Extends an object definition.
+ *
+ * Useful when you want to extend an existing object definition,
+ * without necessarily loading that object.
+ *
+ * @param string $id The unique identifier for the object
+ * @param callable $callable A service definition to extend the original
+ *
+ * @return callable The wrapped callable
+ *
+ * @throws UnknownIdentifierException If the identifier is not defined
+ * @throws FrozenServiceException If the service is frozen
+ * @throws InvalidServiceIdentifierException If the identifier belongs to a parameter
+ * @throws ExpectedInvokableException If the extension callable is not a closure or an invokable object
+ */
+ public function extend($id, $callable)
+ {
+ if (!isset($this->keys[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ if (isset($this->frozen[$id])) {
+ throw new FrozenServiceException($id);
+ }
+
+ if (!\is_object($this->values[$id]) || !\method_exists($this->values[$id], '__invoke')) {
+ throw new InvalidServiceIdentifierException($id);
+ }
+
+ if (isset($this->protected[$this->values[$id]])) {
+ @\trigger_error(\sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), E_USER_DEPRECATED);
+ }
+
+ if (!\is_object($callable) || !\method_exists($callable, '__invoke')) {
+ throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.');
+ }
+
+ $factory = $this->values[$id];
+
+ $extended = function ($c) use ($callable, $factory) {
+ return $callable($factory($c), $c);
+ };
+
+ if (isset($this->factories[$factory])) {
+ $this->factories->detach($factory);
+ $this->factories->attach($extended);
+ }
+
+ return $this[$id] = $extended;
+ }
+
+ /**
+ * Returns all defined value names.
+ *
+ * @return array An array of value names
+ */
+ public function keys()
+ {
+ return \array_keys($this->values);
+ }
+
+ /**
+ * Registers a service provider.
+ *
+ * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
+ * @param array $values An array of values that customizes the provider
+ *
+ * @return static
+ */
+ public function register(ServiceProviderInterface $provider, array $values = [])
+ {
+ $provider->register($this);
+
+ foreach ($values as $key => $value) {
+ $this[$key] = $value;
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php
new file mode 100644
index 00000000..7228421b
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/ExpectedInvokableException.php
@@ -0,0 +1,38 @@
+
+ */
+class ExpectedInvokableException extends \InvalidArgumentException implements ContainerExceptionInterface
+{
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php
new file mode 100644
index 00000000..e4d2f6d3
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php
@@ -0,0 +1,45 @@
+
+ */
+class FrozenServiceException extends \RuntimeException implements ContainerExceptionInterface
+{
+ /**
+ * @param string $id Identifier of the frozen service
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Cannot override frozen service "%s".', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php
new file mode 100644
index 00000000..91e82f98
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php
@@ -0,0 +1,45 @@
+
+ */
+class InvalidServiceIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface
+{
+ /**
+ * @param string $id The invalid identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Identifier "%s" does not contain an object definition.', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php
new file mode 100644
index 00000000..fb6b626e
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php
@@ -0,0 +1,45 @@
+
+ */
+class UnknownIdentifierException extends \InvalidArgumentException implements NotFoundExceptionInterface
+{
+ /**
+ * @param string $id The unknown identifier
+ */
+ public function __construct($id)
+ {
+ parent::__construct(\sprintf('Identifier "%s" is not defined.', $id));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/Container.php b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php
new file mode 100644
index 00000000..e18592eb
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Psr11/Container.php
@@ -0,0 +1,55 @@
+
+ */
+final class Container implements ContainerInterface
+{
+ private $pimple;
+
+ public function __construct(PimpleContainer $pimple)
+ {
+ $this->pimple = $pimple;
+ }
+
+ public function get(string $id)
+ {
+ return $this->pimple[$id];
+ }
+
+ public function has(string $id): bool
+ {
+ return isset($this->pimple[$id]);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php
new file mode 100644
index 00000000..c173d7c0
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php
@@ -0,0 +1,75 @@
+
+ */
+class ServiceLocator implements ContainerInterface
+{
+ private $container;
+ private $aliases = [];
+
+ /**
+ * @param PimpleContainer $container The Container instance used to locate services
+ * @param array $ids Array of service ids that can be located. String keys can be used to define aliases
+ */
+ public function __construct(PimpleContainer $container, array $ids)
+ {
+ $this->container = $container;
+
+ foreach ($ids as $key => $id) {
+ $this->aliases[\is_int($key) ? $id : $key] = $id;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $id)
+ {
+ if (!isset($this->aliases[$id])) {
+ throw new UnknownIdentifierException($id);
+ }
+
+ return $this->container[$this->aliases[$id]];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has(string $id)
+ {
+ return isset($this->aliases[$id]) && isset($this->container[$this->aliases[$id]]);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/ServiceIterator.php b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php
new file mode 100644
index 00000000..5cde5188
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/ServiceIterator.php
@@ -0,0 +1,69 @@
+
+ */
+final class ServiceIterator implements \Iterator
+{
+ private $container;
+ private $ids;
+
+ public function __construct(Container $container, array $ids)
+ {
+ $this->container = $container;
+ $this->ids = $ids;
+ }
+
+ public function rewind()
+ {
+ \reset($this->ids);
+ }
+
+ public function current()
+ {
+ return $this->container[\current($this->ids)];
+ }
+
+ public function key()
+ {
+ return \current($this->ids);
+ }
+
+ public function next()
+ {
+ \next($this->ids);
+ }
+
+ public function valid()
+ {
+ return null !== \key($this->ids);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php
new file mode 100644
index 00000000..c004594b
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/ServiceProviderInterface.php
@@ -0,0 +1,46 @@
+value = $value;
+
+ return $service;
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php
new file mode 100644
index 00000000..33cd4e54
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/NonInvokable.php
@@ -0,0 +1,34 @@
+factory(function () {
+ return new Service();
+ });
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php
new file mode 100644
index 00000000..d71b184d
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Fixtures/Service.php
@@ -0,0 +1,35 @@
+
+ */
+class Service
+{
+ public $value;
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php
new file mode 100644
index 00000000..097a7fd9
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleServiceProviderInterfaceTest.php
@@ -0,0 +1,77 @@
+
+ */
+class PimpleServiceProviderInterfaceTest extends TestCase
+{
+ public function testProvider()
+ {
+ $pimple = new Container();
+
+ $pimpleServiceProvider = new Fixtures\PimpleServiceProvider();
+ $pimpleServiceProvider->register($pimple);
+
+ $this->assertEquals('value', $pimple['param']);
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+
+ $serviceOne = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+
+ public function testProviderWithRegisterMethod()
+ {
+ $pimple = new Container();
+
+ $pimple->register(new Fixtures\PimpleServiceProvider(), [
+ 'anotherParameter' => 'anotherValue',
+ ]);
+
+ $this->assertEquals('value', $pimple['param']);
+ $this->assertEquals('anotherValue', $pimple['anotherParameter']);
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+
+ $serviceOne = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['factory'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php
new file mode 100644
index 00000000..ffa50a6a
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/PimpleTest.php
@@ -0,0 +1,610 @@
+
+ */
+class PimpleTest extends TestCase
+{
+ public function testWithString()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+
+ $this->assertEquals('value', $pimple['param']);
+ }
+
+ public function testWithClosure()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['service']);
+ }
+
+ public function testServicesShouldBeDifferent()
+ {
+ $pimple = new Container();
+ $pimple['service'] = $pimple->factory(function () {
+ return new Fixtures\Service();
+ });
+
+ $serviceOne = $pimple['service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ }
+
+ public function testShouldPassContainerAsParameter()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['container'] = function ($container) {
+ return $container;
+ };
+
+ $this->assertNotSame($pimple, $pimple['service']);
+ $this->assertSame($pimple, $pimple['container']);
+ }
+
+ public function testIsset()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ $pimple['null'] = null;
+
+ $this->assertTrue(isset($pimple['param']));
+ $this->assertTrue(isset($pimple['service']));
+ $this->assertTrue(isset($pimple['null']));
+ $this->assertFalse(isset($pimple['non_existent']));
+ }
+
+ public function testConstructorInjection()
+ {
+ $params = ['param' => 'value'];
+ $pimple = new Container($params);
+
+ $this->assertSame($params['param'], $pimple['param']);
+ }
+
+ public function testOffsetGetValidatesKeyIsPresent()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ echo $pimple['foo'];
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyOffsetGetValidatesKeyIsPresent()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ echo $pimple['foo'];
+ }
+
+ public function testOffsetGetHonorsNullValues()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = null;
+ $this->assertNull($pimple['foo']);
+ }
+
+ public function testUnset()
+ {
+ $pimple = new Container();
+ $pimple['param'] = 'value';
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+
+ unset($pimple['param'], $pimple['service']);
+ $this->assertFalse(isset($pimple['param']));
+ $this->assertFalse(isset($pimple['service']));
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testShare($service)
+ {
+ $pimple = new Container();
+ $pimple['shared_service'] = $service;
+
+ $serviceOne = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+
+ $serviceTwo = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+
+ $this->assertSame($serviceOne, $serviceTwo);
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testProtect($service)
+ {
+ $pimple = new Container();
+ $pimple['protected'] = $pimple->protect($service);
+
+ $this->assertSame($service, $pimple['protected']);
+ }
+
+ public function testGlobalFunctionNameAsParameterValue()
+ {
+ $pimple = new Container();
+ $pimple['global_function'] = 'strlen';
+ $this->assertSame('strlen', $pimple['global_function']);
+ }
+
+ public function testRaw()
+ {
+ $pimple = new Container();
+ $pimple['service'] = $definition = $pimple->factory(function () {
+ return 'foo';
+ });
+ $this->assertSame($definition, $pimple->raw('service'));
+ }
+
+ public function testRawHonorsNullValues()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = null;
+ $this->assertNull($pimple->raw('foo'));
+ }
+
+ public function testFluentRegister()
+ {
+ $pimple = new Container();
+ $this->assertSame($pimple, $pimple->register($this->getMockBuilder('Pimple\ServiceProviderInterface')->getMock()));
+ }
+
+ public function testRawValidatesKeyIsPresent()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ $pimple->raw('foo');
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyRawValidatesKeyIsPresent()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ $pimple->raw('foo');
+ }
+
+ /**
+ * @dataProvider serviceDefinitionProvider
+ */
+ public function testExtend($service)
+ {
+ $pimple = new Container();
+ $pimple['shared_service'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['factory_service'] = $pimple->factory(function () {
+ return new Fixtures\Service();
+ });
+
+ $pimple->extend('shared_service', $service);
+ $serviceOne = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+ $serviceTwo = $pimple['shared_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+ $this->assertSame($serviceOne, $serviceTwo);
+ $this->assertSame($serviceOne->value, $serviceTwo->value);
+
+ $pimple->extend('factory_service', $service);
+ $serviceOne = $pimple['factory_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceOne);
+ $serviceTwo = $pimple['factory_service'];
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $serviceTwo);
+ $this->assertNotSame($serviceOne, $serviceTwo);
+ $this->assertNotSame($serviceOne->value, $serviceTwo->value);
+ }
+
+ public function testExtendDoesNotLeakWithFactories()
+ {
+ if (\extension_loaded('pimple')) {
+ $this->markTestSkipped('Pimple extension does not support this test');
+ }
+ $pimple = new Container();
+
+ $pimple['foo'] = $pimple->factory(function () {
+ return;
+ });
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $pimple) {
+ return;
+ });
+ unset($pimple['foo']);
+
+ $p = new \ReflectionProperty($pimple, 'values');
+ $p->setAccessible(true);
+ $this->assertEmpty($p->getValue($pimple));
+
+ $p = new \ReflectionProperty($pimple, 'factories');
+ $p->setAccessible(true);
+ $this->assertCount(0, $p->getValue($pimple));
+ }
+
+ public function testExtendValidatesKeyIsPresent()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyExtendValidatesKeyIsPresent()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ public function testKeys()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = 123;
+ $pimple['bar'] = 123;
+
+ $this->assertEquals(['foo', 'bar'], $pimple->keys());
+ }
+
+ /** @test */
+ public function settingAnInvokableObjectShouldTreatItAsFactory()
+ {
+ $pimple = new Container();
+ $pimple['invokable'] = new Fixtures\Invokable();
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\Service', $pimple['invokable']);
+ }
+
+ /** @test */
+ public function settingNonInvokableObjectShouldTreatItAsParameter()
+ {
+ $pimple = new Container();
+ $pimple['non_invokable'] = new Fixtures\NonInvokable();
+
+ $this->assertInstanceOf('Pimple\Tests\Fixtures\NonInvokable', $pimple['non_invokable']);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\Pimple\Exception\ExpectedInvokableException::class);
+ $this->expectExceptionMessage('Service definition is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple->factory($service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyFactoryFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Service definition is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple->factory($service);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\Pimple\Exception\ExpectedInvokableException::class);
+ $this->expectExceptionMessage('Callable is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple->protect($service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyProtectFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Callable is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple->protect($service);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testExtendFailsForKeysNotContainingServiceDefinitions($service)
+ {
+ $this->expectException(\Pimple\Exception\InvalidServiceIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.');
+
+ $pimple = new Container();
+ $pimple['foo'] = $service;
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyExtendFailsForKeysNotContainingServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Identifier "foo" does not contain an object definition.');
+
+ $pimple = new Container();
+ $pimple['foo'] = $service;
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ /**
+ * @group legacy
+ * @expectedDeprecation How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "foo" should be protected?
+ */
+ public function testExtendingProtectedClosureDeprecation()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = $pimple->protect(function () {
+ return 'bar';
+ });
+
+ $pimple->extend('foo', function ($value) {
+ return $value.'-baz';
+ });
+
+ $this->assertSame('bar-baz', $pimple['foo']);
+ }
+
+ /**
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testExtendFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\Pimple\Exception\ExpectedInvokableException::class);
+ $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ };
+ $pimple->extend('foo', $service);
+ }
+
+ /**
+ * @group legacy
+ * @dataProvider badServiceDefinitionProvider
+ */
+ public function testLegacyExtendFailsForInvalidServiceDefinitions($service)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Extension service definition is not a Closure or invokable object.');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ };
+ $pimple->extend('foo', $service);
+ }
+
+ public function testExtendFailsIfFrozenServiceIsNonInvokable()
+ {
+ $this->expectException(\Pimple\Exception\FrozenServiceException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return new Fixtures\NonInvokable();
+ };
+ $foo = $pimple['foo'];
+
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ public function testExtendFailsIfFrozenServiceIsInvokable()
+ {
+ $this->expectException(\Pimple\Exception\FrozenServiceException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return new Fixtures\Invokable();
+ };
+ $foo = $pimple['foo'];
+
+ $pimple->extend('foo', function () {
+ });
+ }
+
+ /**
+ * Provider for invalid service definitions.
+ */
+ public function badServiceDefinitionProvider()
+ {
+ return [
+ [123],
+ [new Fixtures\NonInvokable()],
+ ];
+ }
+
+ /**
+ * Provider for service definitions.
+ */
+ public function serviceDefinitionProvider()
+ {
+ return [
+ [function ($value) {
+ $service = new Fixtures\Service();
+ $service->value = $value;
+
+ return $service;
+ }],
+ [new Fixtures\Invokable()],
+ ];
+ }
+
+ public function testDefiningNewServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['bar'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $pimple['bar']);
+ }
+
+ public function testOverridingServiceAfterFreeze()
+ {
+ $this->expectException(\Pimple\Exception\FrozenServiceException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testLegacyOverridingServiceAfterFreeze()
+ {
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Cannot override frozen service "foo".');
+
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ }
+
+ public function testRemovingServiceAfterFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $foo = $pimple['foo'];
+
+ unset($pimple['foo']);
+ $pimple['foo'] = function () {
+ return 'bar';
+ };
+ $this->assertSame('bar', $pimple['foo']);
+ }
+
+ public function testExtendingService()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
+ return "$foo.bar";
+ });
+ $pimple['foo'] = $pimple->extend('foo', function ($foo, $app) {
+ return "$foo.baz";
+ });
+ $this->assertSame('foo.bar.baz', $pimple['foo']);
+ }
+
+ public function testExtendingServiceAfterOtherServiceFreeze()
+ {
+ $pimple = new Container();
+ $pimple['foo'] = function () {
+ return 'foo';
+ };
+ $pimple['bar'] = function () {
+ return 'bar';
+ };
+ $foo = $pimple['foo'];
+
+ $pimple['bar'] = $pimple->extend('bar', function ($bar, $app) {
+ return "$bar.baz";
+ });
+ $this->assertSame('bar.baz', $pimple['bar']);
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php
new file mode 100644
index 00000000..d47b9c3f
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ContainerTest.php
@@ -0,0 +1,76 @@
+assertSame($pimple['service'], $psr->get('service'));
+ }
+
+ public function testGetThrowsExceptionIfServiceIsNotFound()
+ {
+ $this->expectException(\Psr\Container\NotFoundExceptionInterface::class);
+ $this->expectExceptionMessage('Identifier "service" is not defined.');
+
+ $pimple = new Container();
+ $psr = new PsrContainer($pimple);
+
+ $psr->get('service');
+ }
+
+ public function testHasReturnsTrueIfServiceExists()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Service();
+ };
+ $psr = new PsrContainer($pimple);
+
+ $this->assertTrue($psr->has('service'));
+ }
+
+ public function testHasReturnsFalseIfServiceDoesNotExist()
+ {
+ $pimple = new Container();
+ $psr = new PsrContainer($pimple);
+
+ $this->assertFalse($psr->has('service'));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php
new file mode 100644
index 00000000..bd2d335b
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/Psr11/ServiceLocatorTest.php
@@ -0,0 +1,131 @@
+
+ */
+class ServiceLocatorTest extends TestCase
+{
+ public function testCanAccessServices()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['service']);
+
+ $this->assertSame($pimple['service'], $locator->get('service'));
+ }
+
+ public function testCanAccessAliasedServices()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['alias' => 'service']);
+
+ $this->assertSame($pimple['service'], $locator->get('alias'));
+ }
+
+ public function testCannotAccessAliasedServiceUsingRealIdentifier()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "service" is not defined.');
+
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['alias' => 'service']);
+
+ $service = $locator->get('service');
+ }
+
+ public function testGetValidatesServiceCanBeLocated()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "foo" is not defined.');
+
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['alias' => 'service']);
+
+ $service = $locator->get('foo');
+ }
+
+ public function testGetValidatesTargetServiceExists()
+ {
+ $this->expectException(\Pimple\Exception\UnknownIdentifierException::class);
+ $this->expectExceptionMessage('Identifier "invalid" is not defined.');
+
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['alias' => 'invalid']);
+
+ $service = $locator->get('alias');
+ }
+
+ public function testHasValidatesServiceCanBeLocated()
+ {
+ $pimple = new Container();
+ $pimple['service1'] = function () {
+ return new Fixtures\Service();
+ };
+ $pimple['service2'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['service1']);
+
+ $this->assertTrue($locator->has('service1'));
+ $this->assertFalse($locator->has('service2'));
+ }
+
+ public function testHasChecksIfTargetServiceExists()
+ {
+ $pimple = new Container();
+ $pimple['service'] = function () {
+ return new Fixtures\Service();
+ };
+ $locator = new ServiceLocator($pimple, ['foo' => 'service', 'bar' => 'invalid']);
+
+ $this->assertTrue($locator->has('foo'));
+ $this->assertFalse($locator->has('bar'));
+ }
+}
diff --git a/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php
new file mode 100644
index 00000000..2bb935f3
--- /dev/null
+++ b/vendor/pimple/pimple/src/Pimple/Tests/ServiceIteratorTest.php
@@ -0,0 +1,52 @@
+assertSame(['service1' => $pimple['service1'], 'service2' => $pimple['service2']], iterator_to_array($iterator));
+ }
+}
diff --git a/vendor/psr/container/.gitignore b/vendor/psr/container/.gitignore
new file mode 100644
index 00000000..b2395aa0
--- /dev/null
+++ b/vendor/psr/container/.gitignore
@@ -0,0 +1,3 @@
+composer.lock
+composer.phar
+/vendor/
diff --git a/vendor/psr/container/LICENSE b/vendor/psr/container/LICENSE
new file mode 100644
index 00000000..2877a489
--- /dev/null
+++ b/vendor/psr/container/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2016 container-interop
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+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.
diff --git a/vendor/psr/container/README.md b/vendor/psr/container/README.md
new file mode 100644
index 00000000..1b9d9e57
--- /dev/null
+++ b/vendor/psr/container/README.md
@@ -0,0 +1,13 @@
+Container interface
+==============
+
+This repository holds all interfaces related to [PSR-11 (Container Interface)][psr-url].
+
+Note that this is not a Container implementation of its own. It is merely abstractions that describe the components of a Dependency Injection Container.
+
+The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist.
+
+[psr-url]: https://www.php-fig.org/psr/psr-11/
+[package-url]: https://packagist.org/packages/psr/container
+[implementation-url]: https://packagist.org/providers/psr/container-implementation
+
diff --git a/vendor/psr/container/composer.json b/vendor/psr/container/composer.json
new file mode 100644
index 00000000..3797a253
--- /dev/null
+++ b/vendor/psr/container/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "psr/container",
+ "type": "library",
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "keywords": ["psr", "psr-11", "container", "container-interop", "container-interface"],
+ "homepage": "https://github.com/php-fig/container",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ }
+}
diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php
new file mode 100644
index 00000000..cf10b8b4
--- /dev/null
+++ b/vendor/psr/container/src/ContainerExceptionInterface.php
@@ -0,0 +1,10 @@
+