forked from LiveCarta/LiveCartaWP
Changed source root directory
This commit is contained in:
2
html/wp-content/plugins/s3-uploads/.phpcsignore
Normal file
2
html/wp-content/plugins/s3-uploads/.phpcsignore
Normal file
@@ -0,0 +1,2 @@
|
||||
lib/
|
||||
psalm/
|
||||
7
html/wp-content/plugins/s3-uploads/CHANGELOG.md
Normal file
7
html/wp-content/plugins/s3-uploads/CHANGELOG.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## 2.1.0-RC2
|
||||
|
||||
- Remove `lib/aws-sdk` from repository [#305](https://github.com/humanmade/S3-Uploads/pull/305)
|
||||
- Fix delete files on delete attachment [#307](https://github.com/humanmade/S3-Uploads/pull/307)
|
||||
- Fix Imagick with s3 compatibility [#306](https://github.com/humanmade/S3-Uploads/pull/306)
|
||||
92
html/wp-content/plugins/s3-uploads/LICENSE
Normal file
92
html/wp-content/plugins/s3-uploads/LICENSE
Normal file
@@ -0,0 +1,92 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
276
html/wp-content/plugins/s3-uploads/README.md
Normal file
276
html/wp-content/plugins/s3-uploads/README.md
Normal file
@@ -0,0 +1,276 @@
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td align="left" width="70">
|
||||
<strong>S3 Uploads</strong><br />
|
||||
Lightweight "drop-in" for storing WordPress uploads on Amazon S3 instead of the local filesystem.
|
||||
</td>
|
||||
<td align="right" width="20%">
|
||||
<a href="https://shepherd.dev/github/humanmade/S3-Uploads">
|
||||
<img src="https://shepherd.dev/github/humanmade/S3-Uploads/coverage.svg" alt="Psalm coverage">
|
||||
</a>
|
||||
<a href="https://github.com/humanmade/S3-Uploads/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/humanmade/S3-Uploads/actions/workflows/ci.yml/badge.svg" alt="CI">
|
||||
</a>
|
||||
<a href="https://codecov.io/github/humanmade/S3-Uploads" >
|
||||
<img src="https://codecov.io/github/humanmade/S3-Uploads/graph/badge.svg?token=JmeqBWddkV"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
A <strong><a href="https://hmn.md/">Human Made</a></strong> project. Maintained by @joehoyle.
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="https://humanmade.com/content/themes/hmnmd/assets/images/hm-logo.svg" width="100" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
S3 Uploads is a WordPress plugin to store uploads on S3. S3 Uploads aims to be a lightweight "drop-in" for storing uploads on Amazon S3 instead of the local filesystem.
|
||||
|
||||
It's focused on providing a highly robust S3 interface with no "bells and whistles", WP-Admin UI or much otherwise. It comes with some helpful WP-CLI commands for generating IAM users, listing files on S3 and Migrating your existing library to S3.
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP >= 7.4
|
||||
- WordPress >= 5.3
|
||||
|
||||
## Getting Set Up
|
||||
|
||||
S3 Uploads requires installation via Composer:
|
||||
|
||||
```
|
||||
composer require humanmade/s3-uploads
|
||||
```
|
||||
|
||||
**Note:** [Composer's autoloader](https://getcomposer.org/doc/01-basic-usage.md#autoloading) must be loaded before S3 Uploads is loaded. We recommend loading it in your `wp-config.php` before `wp-settings.php` is loaded as shown below.
|
||||
|
||||
```php
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Once you've installed the plugin, add the following constants to your `wp-config.php`:
|
||||
|
||||
```PHP
|
||||
define( 'S3_UPLOADS_BUCKET', 'my-bucket' );
|
||||
define( 'S3_UPLOADS_REGION', '' ); // the s3 bucket region (excluding the rest of the URL)
|
||||
|
||||
// You can set access key and secret directly:
|
||||
define( 'S3_UPLOADS_KEY', '' );
|
||||
define( 'S3_UPLOADS_SECRET', '' );
|
||||
|
||||
// Or if using IAM instance profiles, you can use the instance's credentials:
|
||||
define( 'S3_UPLOADS_USE_INSTANCE_PROFILE', true );
|
||||
```
|
||||
Please refer to this [Region list](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region) for the S3_UPLOADS_REGION values.
|
||||
|
||||
Use of path prefix after the bucket name is allowed and is optional. For example, if you want to upload all files to 'my-folder' inside a bucket called 'my-bucket', you can use:
|
||||
|
||||
```PHP
|
||||
define( 'S3_UPLOADS_BUCKET', 'my-bucket/my-folder' );
|
||||
```
|
||||
|
||||
Please refer to this document outlining [Best Practices for managing AWS access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html#securing_access-keys)
|
||||
|
||||
You must then enable the plugin. To do this via WP-CLI use command:
|
||||
|
||||
```
|
||||
wp plugin activate S3-Uploads
|
||||
```
|
||||
|
||||
The plugin name must match the directory you have cloned S3 Uploads into;
|
||||
If you're using Composer, use
|
||||
```
|
||||
wp plugin activate s3-uploads
|
||||
```
|
||||
|
||||
|
||||
The next thing that you should do is to verify your setup. You can do this using the `verify` command
|
||||
like so:
|
||||
|
||||
```
|
||||
wp s3-uploads verify
|
||||
```
|
||||
|
||||
You will need to create your IAM user yourself, or attach the necessary permissions to an existing user, you can output the policy via `wp s3-uploads generate-iam-policy`
|
||||
|
||||
|
||||
## Listing files on S3
|
||||
|
||||
S3-Uploads comes with a WP-CLI command for listing files in the S3 bucket for debugging etc.
|
||||
|
||||
```
|
||||
wp s3-uploads ls [<path>]
|
||||
```
|
||||
|
||||
## Uploading files to S3
|
||||
|
||||
If you have an existing media library with attachment files, use the below command to copy them all to S3 from local disk.
|
||||
|
||||
```
|
||||
wp s3-uploads upload-directory <from> <to> [--verbose]
|
||||
```
|
||||
|
||||
For example, to migrate your whole uploads directory to S3, you'd run:
|
||||
|
||||
```
|
||||
wp s3-uploads upload-directory /path/to/uploads/ uploads
|
||||
```
|
||||
|
||||
There is also an all purpose `cp` command for arbitrary copying to and from S3.
|
||||
|
||||
```
|
||||
wp s3-uploads cp <from> <to>
|
||||
```
|
||||
|
||||
Note: as either `<from>` or `<to>` can be S3 or local locations, you must specify the full S3 location via `s3://mybucket/mydirectory` for example `cp ./test.txt s3://mybucket/test.txt`.
|
||||
|
||||
## Private Uploads
|
||||
|
||||
WordPress (and therefore S3 Uploads) default behaviour is that all uploaded media files are publicly accessible. In certain cases which may not be desireable. S3 Uploads supports setting S3 Objects to a `private` ACL and providing temporarily signed URLs for all files that are marked as private.
|
||||
|
||||
S3 Uploads does not make assumptions or provide UI for marking attachments as private, instead you should integrate the `s3_uploads_is_attachment_private` WordPress filter to control the behaviour. For example, to mark _all_ attachments as private:
|
||||
|
||||
```php
|
||||
add_filter( 's3_uploads_is_attachment_private', '__return_true' );
|
||||
```
|
||||
|
||||
Private uploads can be transitioned to public by calling `S3_Uploads::set_attachment_files_acl( $id, 'public-read' )` or vica-versa. For example:
|
||||
|
||||
```php
|
||||
S3_Uploads::get_instance()->set_attachment_files_acl( 15, 'public-read' );
|
||||
```
|
||||
|
||||
The default expiry for all private file URLs is 6 hours. You can modify this by using the `s3_uploads_private_attachment_url_expiry` WordPress filter. The value can be any string interpreted by `strtotime`. For example:
|
||||
|
||||
```php
|
||||
add_filter( 's3_uploads_private_attachment_url_expiry', function ( $expiry ) {
|
||||
return '+1 hour';
|
||||
} );
|
||||
```
|
||||
|
||||
If you're using [Stream](https://wordpress.org/plugins/stream/) for audit logs, [S3 Uploads Audit](https://github.com/humanmade/s3-uploads-audit) is an add-on plugin which supports logging some S3 Uploads actions e.g any setting of ACL for files of an attachment. So you can install it for such audit functionality.
|
||||
|
||||
## Cache Control
|
||||
|
||||
You can define the default HTTP `Cache-Control` header for uploaded media using the
|
||||
following constant:
|
||||
|
||||
```PHP
|
||||
define( 'S3_UPLOADS_HTTP_CACHE_CONTROL', 30 * 24 * 60 * 60 );
|
||||
// will expire in 30 days time
|
||||
```
|
||||
|
||||
You can also configure the `Expires` header using the `S3_UPLOADS_HTTP_EXPIRES` constant
|
||||
For instance if you wanted to set an asset to effectively not expire, you could
|
||||
set the Expires header way off in the future. For example:
|
||||
|
||||
```PHP
|
||||
define( 'S3_UPLOADS_HTTP_EXPIRES', gmdate( 'D, d M Y H:i:s', time() + (10 * 365 * 24 * 60 * 60) ) .' GMT' );
|
||||
// will expire in 10 years time
|
||||
```
|
||||
|
||||
## Default Behaviour
|
||||
|
||||
As S3 Uploads is a plug and play plugin, activating it will start rewriting image URLs to S3, and also put
|
||||
new uploads on S3. Sometimes this isn't required behaviour as a site owner may want to upload a large
|
||||
amount of media to S3 using the `wp-cli` commands before enabling S3 Uploads to direct all uploads requests
|
||||
to S3. In this case one can define the `S3_UPLOADS_AUTOENABLE` to `false`. For example, place the following
|
||||
in your `wp-config.php`:
|
||||
|
||||
```PHP
|
||||
define( 'S3_UPLOADS_AUTOENABLE', false );
|
||||
```
|
||||
|
||||
To then enable S3 Uploads rewriting, use the wp-cli command: `wp s3-uploads enable` / `wp s3-uploads disable`
|
||||
to toggle the behaviour.
|
||||
|
||||
## URL Rewrites
|
||||
|
||||
By default, S3 Uploads will use the canonical S3 URIs for referencing the uploads, i.e. `[bucket name].s3.amazonaws.com/uploads/[file path]`. If you want to use another URL to serve the images from (for instance, if you [wish to use S3 as an origin for CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200168926-How-do-I-use-CloudFlare-with-Amazon-s-S3-Service-)), you should define `S3_UPLOADS_BUCKET_URL` in your `wp-config.php`:
|
||||
|
||||
```PHP
|
||||
// Define the base bucket URL (without trailing slash)
|
||||
define( 'S3_UPLOADS_BUCKET_URL', 'https://your.origin.url.example/path' );
|
||||
```
|
||||
S3 Uploads' URL rewriting feature can be disabled if the current website does not require it, nginx proxy to s3 etc. In this case the plugin will only upload files to the S3 bucket.
|
||||
```PHP
|
||||
// disable URL rewriting alltogether
|
||||
define( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL', true );
|
||||
```
|
||||
|
||||
## S3 Object Permissions
|
||||
|
||||
The object permission of files uploaded to S3 by this plugin can be controlled by setting the `S3_UPLOADS_OBJECT_ACL`
|
||||
constant. The default setting if not specified is `public-read` to allow objects to be read by anyone. If you don't
|
||||
want the uploads to be publicly readable then you can define `S3_UPLOADS_OBJECT_ACL` as one of `private` or `authenticated-read`
|
||||
in you wp-config file:
|
||||
|
||||
```PHP
|
||||
// Set the S3 object permission to private
|
||||
define('S3_UPLOADS_OBJECT_ACL', 'private');
|
||||
```
|
||||
|
||||
For more information on S3 permissions please see the Amazon S3 permissions documentation.
|
||||
|
||||
## Custom Endpoints
|
||||
|
||||
Depending on your requirements you may wish to use an alternative S3 compatible object storage system such as Minio, Ceph,
|
||||
Digital Ocean Spaces, Scaleway and others.
|
||||
|
||||
You can configure the endpoint by adding the following code to a file in the `wp-content/mu-plugins/` directory, for example `wp-content/mu-plugins/s3-endpoint.php`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Filter S3 Uploads params.
|
||||
add_filter( 's3_uploads_s3_client_params', function ( $params ) {
|
||||
$params['endpoint'] = 'https://your.endpoint.com';
|
||||
$params['use_path_style_endpoint'] = true;
|
||||
$params['debug'] = false; // Set to true if uploads are failing.
|
||||
return $params;
|
||||
} );
|
||||
```
|
||||
|
||||
**Note:** As of AWS SDK 3.337, S3 uses a [new type of integrity protection](https://github.com/aws/aws-sdk-php/issues/3062) which is not supported by all third-party S3-compatible APIs. If you experience errors, you may need to disable the new checksum functionality:
|
||||
|
||||
```php
|
||||
add_filter( 's3_uploads_s3_client_params', function ( $params ) {
|
||||
// ...
|
||||
$params['request_checksum_calculation'] = 'when_required';
|
||||
$params['response_checksum_validation'] = 'when_required';
|
||||
return $params;
|
||||
} );
|
||||
```
|
||||
|
||||
## Temporary Session Tokens
|
||||
|
||||
If your S3 access is configured to require a temporary session token in addition to the access key and secret, you should configure the credentials using the following code:
|
||||
|
||||
```php
|
||||
// Filter S3 Uploads params.
|
||||
add_filter( 's3_uploads_s3_client_params', function ( $params ) {
|
||||
$params['credentials']['token'] = 'your session token here';
|
||||
return $params;
|
||||
} );
|
||||
```
|
||||
|
||||
## Offline Development
|
||||
|
||||
While it's possible to use S3 Uploads for local development (this is actually a nice way to not have to sync all uploads from production to development),
|
||||
if you want to develop offline you have a couple of options.
|
||||
|
||||
1. Just disable the S3 Uploads plugin in your development environment.
|
||||
2. Define the `S3_UPLOADS_USE_LOCAL` constant with the plugin active.
|
||||
|
||||
Option 2 will allow you to run the S3 Uploads plugin for production parity purposes, it will essentially mock
|
||||
Amazon S3 with a local stream wrapper and actually store the uploads in your WP Upload Dir `/s3/`.
|
||||
|
||||
## Credits
|
||||
|
||||
Created by Human Made for high volume and large-scale sites. We run S3 Uploads on sites with millions of monthly page views, and thousands of sites.
|
||||
|
||||
Written and maintained by [Joe Hoyle](https://github.com/joehoyle). Thanks to all our [contributors](https://github.com/humanmade/S3-Uploads/graphs/contributors).
|
||||
|
||||
Interested in joining in on the fun? [Join us, and become human!](https://hmn.md/is/hiring/)
|
||||
1
html/wp-content/plugins/s3-uploads/codecov.yml
Normal file
1
html/wp-content/plugins/s3-uploads/codecov.yml
Normal file
@@ -0,0 +1 @@
|
||||
comment: false
|
||||
44
html/wp-content/plugins/s3-uploads/composer.json
Normal file
44
html/wp-content/plugins/s3-uploads/composer.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "humanmade/s3-uploads",
|
||||
"description": "WordPress plugin to store uploads on S3",
|
||||
"homepage": "https://github.com/humanmade/S3-Uploads",
|
||||
"keywords": [
|
||||
"wordpress"
|
||||
],
|
||||
"license": "GPL-2.0+",
|
||||
"autoload": {
|
||||
"classmap": [ "inc/" ]
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Human Made",
|
||||
"homepage": "https://humanmade.com/"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/humanmade/s3-uploads/issues",
|
||||
"source": "https://github.com/humanmade/s3-uploads"
|
||||
},
|
||||
"type": "wordpress-plugin",
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"composer/installers": "~1.0 || ^2.0",
|
||||
"aws/aws-sdk-php": "^3.366"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"pcov/clobber": "^2.0",
|
||||
"vimeo/psalm": "^5.0",
|
||||
"humanmade/psalm-plugin-wordpress": "^3.1",
|
||||
"yoast/phpunit-polyfills": "^4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./tests/run-tests.sh",
|
||||
"check-types": "./vendor/bin/psalm"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/installers": true
|
||||
}
|
||||
}
|
||||
}
|
||||
5067
html/wp-content/plugins/s3-uploads/composer.lock
generated
Normal file
5067
html/wp-content/plugins/s3-uploads/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Imagick;
|
||||
use WP_Error;
|
||||
use WP_Image_Editor_Imagick;
|
||||
|
||||
class Image_Editor_Imagick extends WP_Image_Editor_Imagick {
|
||||
|
||||
/**
|
||||
* @var ?Imagick
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected $file;
|
||||
|
||||
/**
|
||||
* @var ?array{width: int, height: int}
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
protected $remote_filename = null;
|
||||
|
||||
/**
|
||||
* Hold on to a reference of all temp local files.
|
||||
*
|
||||
* These are cleaned up on __destruct.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $temp_files_to_cleanup = [];
|
||||
|
||||
/**
|
||||
* Loads image from $this->file into new Imagick Object.
|
||||
*
|
||||
* @return true|WP_Error True if loaded; WP_Error on failure.
|
||||
*/
|
||||
public function load() {
|
||||
if ( $this->image instanceof Imagick ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( $this->file !== null && $this->file !== '' && ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) {
|
||||
return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( $this->file === null || $this->file === '' || strpos( $this->file, $upload_dir['basedir'] ) !== 0 ) {
|
||||
return parent::load();
|
||||
}
|
||||
|
||||
$temp_filename = tempnam( get_temp_dir(), 's3-uploads' );
|
||||
$this->temp_files_to_cleanup[] = $temp_filename;
|
||||
|
||||
copy( $this->file, $temp_filename );
|
||||
$this->remote_filename = $this->file;
|
||||
$this->file = $temp_filename;
|
||||
|
||||
$result = parent::load();
|
||||
|
||||
$this->file = $this->remote_filename;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagick by default can't handle s3:// paths
|
||||
* for saving images. We have instead save it to a file file,
|
||||
* then copy it to the s3:// path as a workaround.
|
||||
*
|
||||
* @param Imagick $image
|
||||
* @param ?string $filename
|
||||
* @param ?string $mime_type
|
||||
* @return WP_Error|array{path: string, file: string, width: int, height: int, mime-type: string}
|
||||
*/
|
||||
protected function _save( $image, $filename = null, $mime_type = null ) {
|
||||
list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
|
||||
|
||||
if ( ! $filename ) {
|
||||
$filename = $this->generate_filename( null, null, $extension );
|
||||
}
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $filename, $upload_dir['basedir'] ) === 0 ) {
|
||||
$temp_filename = tempnam( get_temp_dir(), 's3-uploads' );
|
||||
} else {
|
||||
$temp_filename = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var WP_Error|array{path: string, file: string, width: int, height: int, mime-type: string}
|
||||
*/
|
||||
$parent_call = parent::_save( $image, $temp_filename !== false ? $temp_filename : $filename, $mime_type );
|
||||
|
||||
if ( is_wp_error( $parent_call ) ) {
|
||||
if ( $temp_filename !== false ) {
|
||||
unlink( $temp_filename );
|
||||
}
|
||||
|
||||
return $parent_call;
|
||||
} else {
|
||||
$save = $parent_call;
|
||||
}
|
||||
|
||||
$copy_result = copy( $save['path'], $filename );
|
||||
|
||||
unlink( $save['path'] );
|
||||
if ( $temp_filename !== false ) {
|
||||
unlink( $temp_filename );
|
||||
}
|
||||
|
||||
if ( ! $copy_result ) {
|
||||
return new WP_Error( 'unable-to-copy-to-s3', 'Unable to copy the temp image to S3' );
|
||||
}
|
||||
|
||||
$response = [
|
||||
'path' => $filename,
|
||||
'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
|
||||
'width' => $this->size['width'] ?? 0,
|
||||
'height' => $this->size['height'] ?? 0,
|
||||
'mime-type' => $mime_type,
|
||||
];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
array_map( 'unlink', $this->temp_files_to_cleanup );
|
||||
parent::__destruct();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.MemberNotSnakeCase
|
||||
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
|
||||
// phpcs:disable WordPress.NamingConventions.ValidHookName.NotLowercase
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.NotSnakeCase
|
||||
// phpcs:disable WordPress.WP.AlternativeFunctions
|
||||
|
||||
/**
|
||||
* Local stream wrapper that writes files to the upload dir
|
||||
*
|
||||
* This is for the most part taken from Drupal, with some modifications.
|
||||
*/
|
||||
class Local_Stream_Wrapper {
|
||||
/**
|
||||
* Stream context resource.
|
||||
*
|
||||
* @var ?resource
|
||||
*/
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* A generic resource handle.
|
||||
*
|
||||
* @var ?resource
|
||||
*/
|
||||
public $handle = null;
|
||||
|
||||
/**
|
||||
* Instance URI (stream).
|
||||
*
|
||||
* A stream is referenced as "scheme://target".
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* Gets the path that the wrapper is responsible for.
|
||||
*
|
||||
* @return string String specifying the path.
|
||||
*/
|
||||
static function getDirectoryPath() : string {
|
||||
$upload_dir = Plugin::get_instance()->get_original_upload_dir();
|
||||
return $upload_dir['basedir'] . '/s3';
|
||||
}
|
||||
|
||||
function setUri( string $uri ) : void {
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
function getUri() : string {
|
||||
return $this->uri ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local writable target of the resource within the stream.
|
||||
*
|
||||
* This function should be used in place of calls to realpath() or similar
|
||||
* functions when attempting to determine the location of a file. While
|
||||
* functions like realpath() may return the location of a read-only file, this
|
||||
* method may return a URI or path suitable for writing that is completely
|
||||
* separate from the URI used for reading.
|
||||
*
|
||||
* @param string $uri
|
||||
* Optional URI.
|
||||
*
|
||||
* @return string
|
||||
* Returns a string representing a location suitable for writing of a file,
|
||||
* or FALSE if unable to write to the file such as with read-only streams.
|
||||
*/
|
||||
protected function getTarget( $uri = null ) : string {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri ?? '';
|
||||
}
|
||||
|
||||
$parts = explode( '://', $uri, 2 );
|
||||
$target = $parts[1] ?? '';
|
||||
|
||||
// Remove erroneous leading or trailing, forward-slashes and backslashes.
|
||||
return trim( $target, '\/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mime type for URI
|
||||
*
|
||||
* @param string $uri
|
||||
* @param array{extensions?: string[], mimetypes: array<string,string>} $mapping
|
||||
* @return string
|
||||
*/
|
||||
static function getMimeType( string $uri, ?array $mapping = null ) : string {
|
||||
|
||||
$extension = '';
|
||||
$file_parts = explode( '.', basename( $uri ) );
|
||||
|
||||
// Remove the first part: a full filename should not match an extension.
|
||||
array_shift( $file_parts );
|
||||
|
||||
// Iterate over the file parts, trying to find a match.
|
||||
// For my.awesome.image.jpeg, we try:
|
||||
// - jpeg
|
||||
// - image.jpeg, and
|
||||
// - awesome.image.jpeg
|
||||
while ( $additional_part = array_pop( $file_parts ) ) {
|
||||
$extension = strtolower( $additional_part . ( $extension ? '.' . $extension : '' ) );
|
||||
if ( isset( $mapping['extensions'][ $extension ] ) ) {
|
||||
return $mapping['mimetypes'][ $mapping['extensions'][ $extension ] ];
|
||||
}
|
||||
}
|
||||
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
function chmod( int $mode ) : bool {
|
||||
$output = @chmod( $this->getLocalPath(), $mode ); // // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
// We are modifying the underlying file here, so we have to clear the stat
|
||||
// cache so that PHP understands that URI has changed too.
|
||||
clearstatcache( true, $this->getLocalPath() );
|
||||
return $output;
|
||||
}
|
||||
|
||||
function realpath() : string {
|
||||
return $this->getLocalPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical absolute path of the URI, if possible.
|
||||
*
|
||||
* @param string $uri
|
||||
* (optional) The stream wrapper URI to be converted to a canonical
|
||||
* absolute path. This may point to a directory or another type of file.
|
||||
*
|
||||
* @return string
|
||||
* If $uri is not set, returns the canonical absolute path of the URI
|
||||
* previously. If $uri is set and valid for this class, returns its canonical absolute
|
||||
* path, as determined by the realpath() function. If $uri is set but not
|
||||
* valid, returns empty string.
|
||||
*/
|
||||
protected function getLocalPath( $uri = null ) : string {
|
||||
if ( ! isset( $uri ) ) {
|
||||
$uri = $this->uri;
|
||||
}
|
||||
$path = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
$realpath = str_replace( '/', DIRECTORY_SEPARATOR, $path ); // ensure check against realpath passes on Windows machines
|
||||
|
||||
$directory = realpath( $this->getDirectoryPath() );
|
||||
|
||||
if ( $directory === false || strpos( $realpath, $directory ) !== 0 ) {
|
||||
return '';
|
||||
}
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fopen(), file_get_contents(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the file to open.
|
||||
* @param string $mode
|
||||
* The file mode ("r", "wb" etc.).
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
|
||||
* @param string $opened_path
|
||||
* A string containing the path actually opened.
|
||||
* @param-out string $opened_path
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if file was opened successfully.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-open.php
|
||||
*/
|
||||
public function stream_open( $uri, $mode, $options, &$opened_path ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
$this->handle = ( $options & STREAM_REPORT_ERRORS ) ? fopen( $path, $mode ) : @fopen( $path, $mode );
|
||||
|
||||
if ( (bool) $this->handle && $options & STREAM_USE_PATH ) {
|
||||
$opened_path = $path;
|
||||
}
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for chmod(), chown(), etc.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE on success or FALSE on failure.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-metadata.php
|
||||
*/
|
||||
public function stream_metadata() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for flock().
|
||||
*
|
||||
* @param int $operation
|
||||
* One of the following:
|
||||
* - LOCK_SH to acquire a shared lock (reader).
|
||||
* - LOCK_EX to acquire an exclusive lock (writer).
|
||||
* - LOCK_UN to release a lock (shared or exclusive).
|
||||
* - LOCK_NB if you don't want flock() to block while locking (not
|
||||
* supported on Windows).
|
||||
*
|
||||
* @return bool
|
||||
* Always returns TRUE at the present time.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-lock.php
|
||||
*/
|
||||
public function stream_lock( $operation ) {
|
||||
if ( in_array( $operation, [ LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB ] ) && $this->handle ) {
|
||||
return flock( $this->handle, $operation );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fread(), file_get_contents() etc.
|
||||
*
|
||||
* @param int $count
|
||||
* Maximum number of bytes to be read.
|
||||
*
|
||||
* @return string|bool
|
||||
* The string that was read, or FALSE in case of an error.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-read.php
|
||||
*/
|
||||
public function stream_read( $count ) {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return fread( $this->handle, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fwrite(), file_put_contents() etc.
|
||||
*
|
||||
* @param string $data
|
||||
* The string to be written.
|
||||
*
|
||||
* @return int
|
||||
* The number of bytes written.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-write.php
|
||||
*/
|
||||
public function stream_write( $data ) {
|
||||
if ( ! $this->handle ) {
|
||||
return 0;
|
||||
}
|
||||
return fwrite( $this->handle, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for feof().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if end-of-file has been reached.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-eof.php
|
||||
*/
|
||||
public function stream_eof() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return feof( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fseek().
|
||||
*
|
||||
* @param int $offset
|
||||
* The byte offset to got to.
|
||||
* @param int $whence
|
||||
* SEEK_SET, SEEK_CUR, or SEEK_END.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-seek.php
|
||||
*/
|
||||
public function stream_seek( $offset, $whence ) {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
// fseek returns 0 on success and -1 on a failure.
|
||||
// stream_seek 1 on success and 0 on a failure.
|
||||
return ! fseek( $this->handle, $offset, $whence );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fflush().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if data was successfully stored (or there was no data to store).
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-flush.php
|
||||
*/
|
||||
public function stream_flush() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
$result = fflush( $this->handle );
|
||||
|
||||
$params = [
|
||||
'Bucket' => S3_UPLOADS_BUCKET,
|
||||
'Key' => trim( str_replace( S3_UPLOADS_BUCKET, '', $this->getTarget() ), '/' ),
|
||||
];
|
||||
|
||||
/**
|
||||
* Action when a new object has been uploaded to s3.
|
||||
*
|
||||
* @param array $params S3Client::putObject parameters.
|
||||
*/
|
||||
do_action( 's3_uploads_putObject', $params );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for ftell().
|
||||
*
|
||||
* @return false|int
|
||||
* The current offset in bytes from the beginning of file.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-tell.php
|
||||
*/
|
||||
public function stream_tell() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return ftell( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fstat().
|
||||
*
|
||||
* @return array|false
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-stat.php
|
||||
*/
|
||||
public function stream_stat() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
return fstat( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for fclose().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if stream was successfully closed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-close.php
|
||||
*/
|
||||
public function stream_close() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
$resource = $this->handle;
|
||||
return fclose( $resource );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying stream resource for stream_select().
|
||||
*
|
||||
* @param int $cast_as
|
||||
* Can be STREAM_CAST_FOR_SELECT or STREAM_CAST_AS_STREAM.
|
||||
*
|
||||
* @return resource|false
|
||||
* The underlying stream resource or FALSE if stream_select() is not
|
||||
* supported.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.stream-cast.php
|
||||
*/
|
||||
public function stream_cast( $cast_as ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for unlink().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the resource to delete.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if resource was successfully deleted.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.unlink.php
|
||||
*/
|
||||
public function unlink( $uri ) {
|
||||
$this->uri = $uri;
|
||||
return unlink( $this->getLocalPath() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rename().
|
||||
*
|
||||
* @param string $from_uri,
|
||||
* The URI to the file to rename.
|
||||
* @param string $to_uri
|
||||
* The new URI for file.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if file was successfully renamed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rename.php
|
||||
*/
|
||||
public function rename( $from_uri, $to_uri ) {
|
||||
return rename( $this->getLocalPath( $from_uri ), $this->getLocalPath( $to_uri ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for mkdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to create.
|
||||
* @param int $mode
|
||||
* Permission flags - see mkdir().
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully created.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.mkdir.php
|
||||
*/
|
||||
public function mkdir( $uri, $mode, $options ) {
|
||||
$this->uri = $uri;
|
||||
$recursive = (bool) ( $options & STREAM_MKDIR_RECURSIVE );
|
||||
if ( $recursive ) {
|
||||
// $this->getLocalPath() fails if $uri has multiple levels of directories
|
||||
// that do not yet exist.
|
||||
$localpath = $this->getDirectoryPath() . '/' . $this->getTarget( $uri );
|
||||
} else {
|
||||
$localpath = $this->getLocalPath( $uri );
|
||||
}
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return mkdir( $localpath, $mode, $recursive );
|
||||
} else {
|
||||
return @mkdir( $localpath, $mode, $recursive );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rmdir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to delete.
|
||||
* @param int $options
|
||||
* A bit mask of STREAM_REPORT_ERRORS.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if directory was successfully removed.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.rmdir.php
|
||||
*/
|
||||
public function rmdir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
if ( $options & STREAM_REPORT_ERRORS ) {
|
||||
return rmdir( $this->getLocalPath() );
|
||||
} else {
|
||||
return @rmdir( $this->getLocalPath() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for stat().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to get information about.
|
||||
* @param int $flags
|
||||
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
|
||||
*
|
||||
* @return array
|
||||
* An array with file status, or FALSE in case of an error - see fstat()
|
||||
* for a description of this array.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.url-stat.php
|
||||
*/
|
||||
public function url_stat( $uri, $flags ) {
|
||||
$this->uri = $uri;
|
||||
$path = $this->getLocalPath();
|
||||
// Suppress warnings if requested or if the file or directory does not
|
||||
// exist. This is consistent with PHP's plain filesystem stream wrapper.
|
||||
if ( $flags & STREAM_URL_STAT_QUIET || ! file_exists( $path ) ) {
|
||||
return @stat( $path );
|
||||
} else {
|
||||
return stat( $path );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for opendir().
|
||||
*
|
||||
* @param string $uri
|
||||
* A string containing the URI to the directory to open.
|
||||
* @param int $options
|
||||
* Unknown (parameter is not documented in PHP Manual).
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-opendir.php
|
||||
*/
|
||||
public function dir_opendir( $uri, $options ) {
|
||||
$this->uri = $uri;
|
||||
$this->handle = opendir( $this->getLocalPath() );
|
||||
|
||||
return (bool) $this->handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for readdir().
|
||||
*
|
||||
* @return string
|
||||
* The next filename, or FALSE if there are no more files in the directory.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-readdir.php
|
||||
*/
|
||||
public function dir_readdir() {
|
||||
if ( ! $this->handle ) {
|
||||
return '';
|
||||
}
|
||||
return readdir( $this->handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for rewinddir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-rewinddir.php
|
||||
*/
|
||||
public function dir_rewinddir() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
rewinddir( $this->handle );
|
||||
// We do not really have a way to signal a failure as rewinddir() does not
|
||||
// have a return value and there is no way to read a directory handler
|
||||
// without advancing to the next file.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for closedir().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success.
|
||||
*
|
||||
* @see http://php.net/manual/streamwrapper.dir-closedir.php
|
||||
*/
|
||||
public function dir_closedir() {
|
||||
if ( ! $this->handle ) {
|
||||
return false;
|
||||
}
|
||||
closedir( $this->handle );
|
||||
// We do not really have a way to signal a failure as closedir() does not
|
||||
// have a return value.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
691
html/wp-content/plugins/s3-uploads/inc/class-plugin.php
Normal file
691
html/wp-content/plugins/s3-uploads/inc/class-plugin.php
Normal file
@@ -0,0 +1,691 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Aws;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* @psalm-consistent-constructor
|
||||
*/
|
||||
class Plugin {
|
||||
|
||||
/**
|
||||
* The S3 bucket with path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $bucket;
|
||||
|
||||
/**
|
||||
* The URL that resolves to the S3 bucket.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $bucket_url;
|
||||
|
||||
/**
|
||||
* AWS IAM access key used for S3 Access.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* AWS IAM access key secret used for S3 Access.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
private $secret;
|
||||
|
||||
/**
|
||||
* Original wp_upload_dir() before being replaced by S3 Uploads.
|
||||
*
|
||||
* @var ?array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public $original_upload_dir;
|
||||
|
||||
/**
|
||||
* @var ?string
|
||||
*/
|
||||
private $region = null;
|
||||
|
||||
/**
|
||||
* @var ?Aws\S3\S3Client
|
||||
*/
|
||||
private $s3 = null;
|
||||
|
||||
/**
|
||||
* @var ?static
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public static function get_instance() {
|
||||
|
||||
if ( ! self::$instance ) {
|
||||
self::$instance = new static(
|
||||
S3_UPLOADS_BUCKET,
|
||||
defined( 'S3_UPLOADS_KEY' ) ? S3_UPLOADS_KEY : null,
|
||||
defined( 'S3_UPLOADS_SECRET' ) ? S3_UPLOADS_SECRET : null,
|
||||
defined( 'S3_UPLOADS_BUCKET_URL' ) ? S3_UPLOADS_BUCKET_URL : null,
|
||||
S3_UPLOADS_REGION
|
||||
);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $bucket
|
||||
* @param ?string $key
|
||||
* @param ?string $secret
|
||||
* @param ?string $bucket_url
|
||||
* @param ?string $region
|
||||
*/
|
||||
public function __construct( $bucket, $key, $secret, $bucket_url = null, $region = null ) {
|
||||
$this->bucket = $bucket;
|
||||
$this->key = $key;
|
||||
$this->secret = $secret;
|
||||
$this->bucket_url = $bucket_url;
|
||||
$this->region = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the hooks, urls filtering etc for S3 Uploads
|
||||
*/
|
||||
public function setup() : void {
|
||||
$this->register_stream_wrapper();
|
||||
|
||||
add_filter( 'upload_dir', [ $this, 'filter_upload_dir' ] );
|
||||
add_filter( 'wp_image_editors', [ $this, 'filter_editors' ], 9 );
|
||||
add_action( 'delete_attachment', [ $this, 'delete_attachment_files' ] );
|
||||
add_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10, 2 );
|
||||
add_filter( 'wp_resource_hints', [ $this, 'wp_filter_resource_hints' ], 10, 2 );
|
||||
|
||||
add_filter( 'wp_handle_sideload_prefilter', [ $this, 'filter_sideload_move_temp_file_to_s3' ] );
|
||||
add_filter( 'wp_generate_attachment_metadata', [ $this, 'set_filesize_in_attachment_meta' ], 10, 2 );
|
||||
|
||||
add_filter( 'wp_get_attachment_url', [ $this, 'add_s3_signed_params_to_attachment_url' ], 10, 2 );
|
||||
add_filter( 'wp_get_attachment_image_src', [ $this, 'add_s3_signed_params_to_attachment_image_src' ], 10, 2 );
|
||||
add_filter( 'wp_calculate_image_srcset', [ $this, 'add_s3_signed_params_to_attachment_image_srcset' ], 10, 5 );
|
||||
|
||||
add_filter( 'wp_generate_attachment_metadata', [ $this, 'set_attachment_private_on_generate_attachment_metadata' ], 10, 2 );
|
||||
|
||||
add_filter( 'pre_wp_unique_filename_file_list', [ $this, 'get_files_for_unique_filename_file_list' ], 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Tear down the hooks, url filtering etc for S3 Uploads
|
||||
*/
|
||||
public function tear_down() : void {
|
||||
|
||||
stream_wrapper_unregister( 's3' );
|
||||
remove_filter( 'upload_dir', [ $this, 'filter_upload_dir' ] );
|
||||
remove_filter( 'wp_image_editors', [ $this, 'filter_editors' ], 9 );
|
||||
remove_filter( 'wp_handle_sideload_prefilter', [ $this, 'filter_sideload_move_temp_file_to_s3' ] );
|
||||
remove_filter( 'wp_generate_attachment_metadata', [ $this, 'set_filesize_in_attachment_meta' ] );
|
||||
|
||||
remove_filter( 'wp_get_attachment_url', [ $this, 'add_s3_signed_params_to_attachment_url' ] );
|
||||
remove_filter( 'wp_get_attachment_image_src', [ $this, 'add_s3_signed_params_to_attachment_image_src' ] );
|
||||
remove_filter( 'wp_calculate_image_srcset', [ $this, 'add_s3_signed_params_to_attachment_image_srcset' ] );
|
||||
|
||||
remove_filter( 'wp_generate_attachment_metadata', [ $this, 'set_attachment_private_on_generate_attachment_metadata' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the stream wrapper for s3
|
||||
*/
|
||||
public function register_stream_wrapper() : void {
|
||||
if ( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL ) {
|
||||
stream_wrapper_register( 's3', 'S3_Uploads\Local_Stream_Wrapper', STREAM_IS_URL );
|
||||
} else {
|
||||
Stream_Wrapper::register( $this );
|
||||
$acl = defined( 'S3_UPLOADS_OBJECT_ACL' ) ? S3_UPLOADS_OBJECT_ACL : 'public-read';
|
||||
stream_context_set_option( stream_context_get_default(), 's3', 'ACL', $acl );
|
||||
}
|
||||
|
||||
stream_context_set_option( stream_context_get_default(), 's3', 'seekable', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the s3:// path for the bucket.
|
||||
*/
|
||||
public function get_s3_path() : string {
|
||||
return 's3://' . $this->bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the default wp_upload_dir.
|
||||
*
|
||||
* @param array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false} $dirs
|
||||
* @return array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public function filter_upload_dir( array $dirs ) : array {
|
||||
|
||||
$this->original_upload_dir = $dirs;
|
||||
$s3_path = $this->get_s3_path();
|
||||
|
||||
$dirs['path'] = str_replace( WP_CONTENT_DIR, $s3_path, $dirs['path'] );
|
||||
$dirs['basedir'] = str_replace( WP_CONTENT_DIR, $s3_path, $dirs['basedir'] );
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL' ) || ! S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL ) {
|
||||
|
||||
if ( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL ) {
|
||||
$dirs['url'] = str_replace( $s3_path, $dirs['baseurl'] . '/s3/' . $this->bucket, $dirs['path'] );
|
||||
$dirs['baseurl'] = str_replace( $s3_path, $dirs['baseurl'] . '/s3/' . $this->bucket, $dirs['basedir'] );
|
||||
|
||||
} else {
|
||||
$dirs['url'] = str_replace( $s3_path, $this->get_s3_url(), $dirs['path'] );
|
||||
$dirs['baseurl'] = str_replace( $s3_path, $this->get_s3_url(), $dirs['basedir'] );
|
||||
}
|
||||
}
|
||||
|
||||
return $dirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all attachment files from S3 when an attachment is deleted.
|
||||
*
|
||||
* WordPress Core's handling of deleting files for attachments via
|
||||
* wp_delete_attachment_files is not compatible with remote streams, as
|
||||
* it makes many assumptions about local file paths. The hooks also do
|
||||
* not exist to be able to modify their behavior. As such, we just clean
|
||||
* up the s3 files when an attachment is removed, and leave WordPress to try
|
||||
* a failed attempt at mangling the s3:// urls.
|
||||
*
|
||||
* @param int $post_id
|
||||
*/
|
||||
public function delete_attachment_files( int $post_id ) : void {
|
||||
$meta = wp_get_attachment_metadata( $post_id );
|
||||
$file = get_attached_file( $post_id );
|
||||
if ( $file === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $meta['sizes'] ) ) {
|
||||
foreach ( $meta['sizes'] as $sizeinfo ) {
|
||||
$intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
|
||||
wp_delete_file( $intermediate_file );
|
||||
}
|
||||
}
|
||||
|
||||
wp_delete_file( $file );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the S3 URL base for uploads.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_url() : string {
|
||||
if ( $this->bucket_url !== null ) {
|
||||
return $this->bucket_url;
|
||||
}
|
||||
|
||||
$bucket = strtok( $this->bucket, '/' );
|
||||
$path = substr( $this->bucket, strlen( $bucket ) );
|
||||
|
||||
return apply_filters( 's3_uploads_bucket_url', 'https://' . $bucket . '.s3.amazonaws.com' . $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the S3 bucket name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_bucket() : string {
|
||||
return strtok( $this->bucket, '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region of the S3 bucket.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_s3_bucket_region() : ?string {
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original upload directory before it was replaced by S3 uploads.
|
||||
*
|
||||
* @return array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
public function get_original_upload_dir() : array {
|
||||
|
||||
if ( empty( $this->original_upload_dir ) ) {
|
||||
wp_upload_dir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array{path: string, basedir: string, baseurl: string, url: string, subdir: string, error: string|false}
|
||||
*/
|
||||
$upload_dir = $this->original_upload_dir;
|
||||
return $upload_dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a file url in the uploads directory to the params needed for S3.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array{bucket: string, key: string, query: string|null}|null
|
||||
*/
|
||||
public function get_s3_location_for_url( string $url ) : ?array {
|
||||
$s3_url = 'https://' . $this->get_s3_bucket() . '.s3.amazonaws.com/';
|
||||
if ( strpos( $url, $s3_url ) === 0 ) {
|
||||
$parsed = wp_parse_url( $url );
|
||||
return [
|
||||
'bucket' => $this->get_s3_bucket(),
|
||||
'key' => isset( $parsed['path'] ) ? ltrim( $parsed['path'], '/' ) : '',
|
||||
'query' => $parsed['query'] ?? null,
|
||||
];
|
||||
}
|
||||
$upload_dir = wp_upload_dir();
|
||||
|
||||
if ( strpos( $url, $upload_dir['baseurl'] ) === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $url );
|
||||
$parsed = wp_parse_url( $path );
|
||||
if ( ! isset( $parsed['host'] ) || ! isset( $parsed['path'] ) ) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'bucket' => $parsed['host'],
|
||||
'key' => ltrim( $parsed['path'], '/' ),
|
||||
'query' => $parsed['query'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse a file path in the uploads directory to the params needed for S3.
|
||||
*
|
||||
* @param string $url
|
||||
* @return array{key: string, bucket: string}
|
||||
*/
|
||||
public function get_s3_location_for_path( string $path ) : ?array {
|
||||
$parsed = wp_parse_url( $path );
|
||||
if ( ! isset( $parsed['path'] ) || ! isset( $parsed['host'] ) || ! isset( $parsed['scheme'] ) || $parsed['scheme'] !== 's3' ) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'bucket' => $parsed['host'],
|
||||
'key' => ltrim( $parsed['path'], '/' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Aws\S3\S3Client
|
||||
*/
|
||||
public function s3() : Aws\S3\S3Client {
|
||||
|
||||
if ( ! empty( $this->s3 ) ) {
|
||||
return $this->s3;
|
||||
}
|
||||
|
||||
$this->s3 = $this->get_aws_sdk()->createS3();
|
||||
return $this->s3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AWS Sdk.
|
||||
*
|
||||
* @return Aws\Sdk
|
||||
*/
|
||||
public function get_aws_sdk() : Aws\Sdk {
|
||||
/** @var null|Aws\Sdk */
|
||||
$sdk = apply_filters( 's3_uploads_aws_sdk', null, $this );
|
||||
if ( $sdk ) {
|
||||
return $sdk;
|
||||
}
|
||||
|
||||
$params = [ 'version' => 'latest' ];
|
||||
|
||||
if ( $this->key !== null && $this->secret !== null ) {
|
||||
$params['credentials']['key'] = $this->key;
|
||||
$params['credentials']['secret'] = $this->secret;
|
||||
}
|
||||
|
||||
if ( $this->region !== null ) {
|
||||
$params['signature'] = 'v4';
|
||||
$params['region'] = $this->region;
|
||||
}
|
||||
|
||||
if ( defined( 'WP_PROXY_HOST' ) && defined( 'WP_PROXY_PORT' ) ) {
|
||||
$proxy_auth = '';
|
||||
$proxy_address = WP_PROXY_HOST . ':' . WP_PROXY_PORT;
|
||||
|
||||
if ( defined( 'WP_PROXY_USERNAME' ) && defined( 'WP_PROXY_PASSWORD' ) ) {
|
||||
$proxy_auth = WP_PROXY_USERNAME . ':' . WP_PROXY_PASSWORD . '@';
|
||||
}
|
||||
|
||||
$params['request.options']['proxy'] = $proxy_auth . $proxy_address;
|
||||
}
|
||||
|
||||
$params = apply_filters( 's3_uploads_s3_client_params', $params );
|
||||
|
||||
$sdk = new Aws\Sdk( $params );
|
||||
return $sdk;
|
||||
}
|
||||
|
||||
public function filter_editors( array $editors ) : array {
|
||||
$position = array_search( 'WP_Image_Editor_Imagick', $editors );
|
||||
if ( $position !== false ) {
|
||||
unset( $editors[ $position ] );
|
||||
}
|
||||
|
||||
array_unshift( $editors, __NAMESPACE__ . '\\Image_Editor_Imagick' );
|
||||
|
||||
return $editors;
|
||||
}
|
||||
/**
|
||||
* Copy the file from /tmp to an s3 dir so handle_sideload doesn't fail due to
|
||||
* trying to do a rename() on the file cross streams. This is somewhat of a hack
|
||||
* to work around the core issue https://core.trac.wordpress.org/ticket/29257
|
||||
*
|
||||
* @param array{tmp_name: string, name: string, type: string, size: int, error: int} $file File array
|
||||
* @return array{tmp_name: string, name: string, type: string, size: int, error: int}
|
||||
*/
|
||||
public function filter_sideload_move_temp_file_to_s3( array $file ) {
|
||||
$upload_dir = wp_upload_dir();
|
||||
$new_path = $upload_dir['basedir'] . '/tmp/' . basename( $file['tmp_name'] );
|
||||
|
||||
copy( $file['tmp_name'], $new_path );
|
||||
unlink( $file['tmp_name'] );
|
||||
$file['tmp_name'] = $new_path;
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the attachment filesize in the attachment meta array.
|
||||
*
|
||||
* Getting the filesize of an image in S3 involves a remote HEAD request,
|
||||
* which is a bit slower than a local filesystem operation would be. As a
|
||||
* result, operations like `wp_prepare_attachments_for_js' take substantially
|
||||
* longer to complete against s3 uploads than if they were performed with a
|
||||
* local filesystem.i
|
||||
*
|
||||
* Saving the filesize in the attachment metadata when the image is
|
||||
* uploaded allows core to skip this stat when retrieving and formatting it.
|
||||
*
|
||||
* @param array<string, mixed> $metadata Attachment metadata.
|
||||
* @param int $attachment_id Attachment ID.
|
||||
* @return array<string, mixed> Attachment metadata array, with "filesize" value added.
|
||||
*/
|
||||
function set_filesize_in_attachment_meta( array $metadata, int $attachment_id ) : array {
|
||||
$file = get_attached_file( $attachment_id );
|
||||
if ( $file === false ) {
|
||||
return $metadata;
|
||||
}
|
||||
if ( ! isset( $metadata['filesize'] ) && file_exists( $file ) ) {
|
||||
$metadata['filesize'] = filesize( $file );
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters wp_read_image_metadata. exif_read_data() doesn't work on
|
||||
* file streams so we need to make a temporary local copy to extract
|
||||
* exif data from.
|
||||
*
|
||||
* @param array<string, mixed> $meta
|
||||
* @param string $file
|
||||
* @return array<string, mixed>|false
|
||||
*/
|
||||
public function wp_filter_read_image_metadata( array $meta, string $file ) {
|
||||
remove_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10 );
|
||||
$temp_file = $this->copy_image_from_s3( $file );
|
||||
$meta = wp_read_image_metadata( $temp_file );
|
||||
add_filter( 'wp_read_image_metadata', [ $this, 'wp_filter_read_image_metadata' ], 10, 2 );
|
||||
unlink( $temp_file );
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the DNS address for the S3 Bucket to list for DNS prefetch.
|
||||
*
|
||||
* @param array $hints
|
||||
* @param string $relation_type
|
||||
* @return array
|
||||
*/
|
||||
function wp_filter_resource_hints( array $hints, string $relation_type ) : array {
|
||||
if (
|
||||
( defined( 'S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL' ) && S3_UPLOADS_DISABLE_REPLACE_UPLOAD_URL ) ||
|
||||
( defined( 'S3_UPLOADS_USE_LOCAL' ) && S3_UPLOADS_USE_LOCAL )
|
||||
) {
|
||||
return $hints;
|
||||
}
|
||||
|
||||
if ( 'dns-prefetch' === $relation_type ) {
|
||||
$hints[] = $this->get_s3_url();
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local copy of the file.
|
||||
*
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
public function copy_image_from_s3( string $file ) : string {
|
||||
if ( ! function_exists( 'wp_tempnam' ) ) {
|
||||
require_once( ABSPATH . 'wp-admin/includes/file.php' );
|
||||
}
|
||||
$temp_filename = wp_tempnam( $file );
|
||||
copy( $file, $temp_filename );
|
||||
return $temp_filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the attachment is private.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_private_attachment( int $attachment_id ) : bool {
|
||||
/**
|
||||
* Filters whether an attachment should be private.
|
||||
*
|
||||
* @param bool Whether the attachment is private.
|
||||
* @param int The attachment ID.
|
||||
*/
|
||||
$private = apply_filters( 's3_uploads_is_attachment_private', false, $attachment_id );
|
||||
return $private;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ACL (Access Control List) for an attachments files.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @param 'public-read'|'private' $acl public-read|private
|
||||
* @return WP_Error|null
|
||||
*/
|
||||
public function set_attachment_files_acl( int $attachment_id, string $acl ) : ?WP_Error {
|
||||
$files = static::get_attachment_files( $attachment_id );
|
||||
$locations = array_map( [ $this, 'get_s3_location_for_path' ], $files );
|
||||
// Remove any null items in the array from get_s3_location_for_path().
|
||||
$locations = array_filter( $locations );
|
||||
$s3 = $this->s3();
|
||||
$commands = [];
|
||||
foreach ( $locations as $location ) {
|
||||
$commands[] = $s3->getCommand( 'putObjectAcl', [
|
||||
'Bucket' => $location['bucket'],
|
||||
'Key' => $location['key'],
|
||||
'ACL' => $acl,
|
||||
] );
|
||||
}
|
||||
|
||||
try {
|
||||
Aws\CommandPool::batch( $s3, $commands );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( $e->getCode(), $e->getMessage() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after ACL of files of an attachment is set.
|
||||
*
|
||||
* @param int $attachment_id Attachment whose ACL has been changed.
|
||||
* @param string $acl The new ACL that's been set.
|
||||
* @psalm-suppress TooManyArguments -- Currently do_action doesn't detect variable number of arguments.
|
||||
*/
|
||||
do_action( 's3_uploads_set_attachment_files_acl', $attachment_id, $acl );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the files stored for a given attachment.
|
||||
*
|
||||
* @param integer $attachment_id
|
||||
* @return list<string> Array of all full paths to the attachment's files.
|
||||
*/
|
||||
public static function get_attachment_files( int $attachment_id ) : array {
|
||||
/** @var string */
|
||||
$main_file = get_attached_file( $attachment_id );
|
||||
$main_file_directory = dirname( $main_file );
|
||||
$files = [ $main_file ];
|
||||
|
||||
$meta = wp_get_attachment_metadata( $attachment_id );
|
||||
if ( isset( $meta['sizes'] ) ) {
|
||||
foreach ( $meta['sizes'] as $size => $sizeinfo ) {
|
||||
$files[] = $main_file_directory . '/' . $sizeinfo['file'];
|
||||
}
|
||||
}
|
||||
|
||||
/** @var string|false */
|
||||
$original_image = get_post_meta( $attachment_id, 'original_image', true );
|
||||
if ( $original_image !== false && $original_image !== '' ) {
|
||||
$files[] = $main_file_directory . '/' . $original_image;
|
||||
}
|
||||
|
||||
/** @var array<string,array{file: string}> */
|
||||
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
|
||||
if ( $backup_sizes ) {
|
||||
foreach ( $backup_sizes as $size => $sizeinfo ) {
|
||||
// Backup sizes only store the backup filename, which is relative to the
|
||||
// main attached file, unlike the metadata sizes array.
|
||||
$files[] = $main_file_directory . '/' . $sizeinfo['file'];
|
||||
}
|
||||
}
|
||||
|
||||
$files = apply_filters( 's3_uploads_get_attachment_files', $files, $attachment_id );
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params onto an image for for a given attachment.
|
||||
*
|
||||
* This function determines whether the attachment needs a signed URL, so is safe to
|
||||
* pass any URL.
|
||||
*
|
||||
* @param string $url
|
||||
* @param integer $post_id
|
||||
* @return string
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_url( string $url, int $post_id ) : string {
|
||||
if ( ! $this->is_private_attachment( $post_id ) ) {
|
||||
return $url;
|
||||
}
|
||||
$path = $this->get_s3_location_for_url( $url );
|
||||
if ( ! $path ) {
|
||||
return $url;
|
||||
}
|
||||
$cmd = $this->s3()->getCommand(
|
||||
'GetObject',
|
||||
[
|
||||
'Bucket' => $path['bucket'],
|
||||
'Key' => $path['key'],
|
||||
]
|
||||
);
|
||||
|
||||
$presigned_url_expires = apply_filters( 's3_uploads_private_attachment_url_expiry', '+6 hours', $post_id );
|
||||
$query = $this->s3()->createPresignedRequest( $cmd, $presigned_url_expires )->getUri()->getQuery();
|
||||
|
||||
// The URL could have query params on it already (such as being an already signed URL),
|
||||
// but query params will mean the S3 signed URL will become corrupt. So, we have to
|
||||
// remove all query params.
|
||||
$url = strtok( $url, '?' ) . '?' . $query;
|
||||
$url = apply_filters( 's3_uploads_presigned_url', $url, $post_id );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params to an image src array.
|
||||
*
|
||||
* @param array{0: string, 1: int, 2: int}|false $image
|
||||
* @param integer|"" $post_id The post id, due to WordPress hook, this can be "", so can't just hint as int.
|
||||
* @return array{0: string, 1: int, 2: int}|false
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_image_src( $image, $post_id ) {
|
||||
if ( $image === false || $post_id === '' || $post_id === 0 ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
$image[0] = $this->add_s3_signed_params_to_attachment_url( $image[0], $post_id );
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the S3 signed params to the image srcset (response image) sizes.
|
||||
*
|
||||
* @param array{url: string, descriptor: string, value: int}[] $sources
|
||||
* @param array $sizes
|
||||
* @param string $src
|
||||
* @param array $meta
|
||||
* @param integer $post_id
|
||||
* @return array{url: string, descriptor: string, value: int}[]
|
||||
*/
|
||||
public function add_s3_signed_params_to_attachment_image_srcset( array $sources, array $sizes, string $src, array $meta, int $post_id ) : array {
|
||||
foreach ( $sources as &$source ) {
|
||||
$source['url'] = $this->add_s3_signed_params_to_attachment_url( $source['url'], $post_id );
|
||||
}
|
||||
return $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whenever attachment metadata is generated, set the attachment files to private if it's a private attachment.
|
||||
*
|
||||
* @param array $metadata The attachment metadata.
|
||||
* @param int $attachment_id The attachment ID
|
||||
* @return array
|
||||
*/
|
||||
public function set_attachment_private_on_generate_attachment_metadata( array $metadata, int $attachment_id ) : array {
|
||||
if ( $this->is_private_attachment( $attachment_id ) ) {
|
||||
$this->set_attachment_files_acl( $attachment_id, 'private' );
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the files used for wp_unique_filename() comparisons
|
||||
*
|
||||
* @param array|null $files
|
||||
* @param string $dir
|
||||
* @return array
|
||||
*/
|
||||
public function get_files_for_unique_filename_file_list( ?array $files, string $dir, string $filename ) : array {
|
||||
$name = pathinfo( $filename, PATHINFO_FILENAME );
|
||||
// The s3:// streamwrapper support listing by partial prefixes with wildcards.
|
||||
// For example, scandir( s3://bucket/2019/06/my-image* )
|
||||
$scandir = scandir( trailingslashit( $dir ) . $name . '*' );
|
||||
if ( $scandir === false ) {
|
||||
$scandir = []; // Set as empty array for return
|
||||
}
|
||||
return $scandir;
|
||||
}
|
||||
}
|
||||
1229
html/wp-content/plugins/s3-uploads/inc/class-stream-wrapper.php
Normal file
1229
html/wp-content/plugins/s3-uploads/inc/class-stream-wrapper.php
Normal file
File diff suppressed because it is too large
Load Diff
360
html/wp-content/plugins/s3-uploads/inc/class-wp-cli-command.php
Normal file
360
html/wp-content/plugins/s3-uploads/inc/class-wp-cli-command.php
Normal file
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
use Aws\Command;
|
||||
use Aws\S3\Transfer;
|
||||
use Exception;
|
||||
use WP_CLI;
|
||||
|
||||
class WP_CLI_Command extends \WP_CLI_Command {
|
||||
|
||||
/**
|
||||
* Verifies the API keys entered will work for writing and deleting from S3.
|
||||
*
|
||||
* @subcommand verify
|
||||
*/
|
||||
public function verify_api_keys() : void {
|
||||
// Verify first that we have the necessary access keys to connect to S3.
|
||||
if ( ! $this->verify_s3_access_constants() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get S3 Upload instance.
|
||||
Plugin::get_instance();
|
||||
|
||||
// Create a path in the base directory, with a random file name to avoid potentially overwriting existing data.
|
||||
$upload_dir = wp_upload_dir();
|
||||
$s3_path = $upload_dir['basedir'] . '/' . wp_rand() . '.txt';
|
||||
|
||||
// Attempt to copy the local Canola test file to the generated path on S3.
|
||||
WP_CLI::print_value( 'Attempting to upload file ' . $s3_path );
|
||||
|
||||
$copy = copy(
|
||||
dirname( dirname( __FILE__ ) ) . '/verify.txt',
|
||||
$s3_path
|
||||
);
|
||||
|
||||
// Check that the copy worked.
|
||||
if ( ! $copy ) {
|
||||
WP_CLI::error( 'Failed to copy / write to S3 - check your policy?' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File uploaded to S3 successfully.' );
|
||||
|
||||
// Delete the file off S3.
|
||||
WP_CLI::print_value( 'Attempting to delete file. ' . $s3_path );
|
||||
$delete = unlink( $s3_path );
|
||||
|
||||
// Check that the delete worked.
|
||||
if ( ! $delete ) {
|
||||
WP_CLI::error( 'Failed to delete ' . $s3_path );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WP_CLI::print_value( 'File deleted from S3 successfully.' );
|
||||
|
||||
WP_CLI::success( 'Looks like your configuration is correct.' );
|
||||
}
|
||||
|
||||
private function get_iam_policy() : string {
|
||||
|
||||
$bucket = strtok( S3_UPLOADS_BUCKET, '/' );
|
||||
|
||||
$path = null;
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$path = str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET );
|
||||
}
|
||||
|
||||
return '{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "Stmt1392016154000",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:AbortMultipartUpload",
|
||||
"s3:DeleteObject",
|
||||
"s3:GetBucketAcl",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:GetBucketPolicy",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:ListBucket",
|
||||
"s3:ListBucketMultipartUploads",
|
||||
"s3:ListMultipartUploadParts",
|
||||
"s3:PutObject",
|
||||
"s3:PutObjectAcl"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::' . S3_UPLOADS_BUCKET . '/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Sid": "AllowRootAndHomeListingOfBucket",
|
||||
"Action": ["s3:ListBucket"],
|
||||
"Effect": "Allow",
|
||||
"Resource": ["arn:aws:s3:::' . $bucket . '"],
|
||||
"Condition":{"StringLike":{"s3:prefix":["' . ( $path !== null ? $path . '/' : '' ) . '*"]}}
|
||||
}
|
||||
]
|
||||
}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AWS IAM Policy that S3 Uploads requires
|
||||
*
|
||||
* It's typically not a good idea to use access keys that have full access to your S3 account,
|
||||
* as if the keys are compromised through the WordPress site somehow, you don't
|
||||
* want to give full control via those keys.
|
||||
*
|
||||
* @subcommand generate-iam-policy
|
||||
*/
|
||||
public function generate_iam_policy() : void {
|
||||
|
||||
WP_Cli::print_value( $this->get_iam_policy() );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* List files in the S3 bucket
|
||||
*
|
||||
* @synopsis [<path>]
|
||||
*
|
||||
* @param array{0: string} $args
|
||||
*/
|
||||
public function ls( array $args ) : void {
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= trailingslashit( ltrim( $args[0], '/' ) );
|
||||
}
|
||||
|
||||
try {
|
||||
$objects = $s3->getIterator(
|
||||
'ListObjectsV2', [
|
||||
'Bucket' => strtok( S3_UPLOADS_BUCKET, '/' ),
|
||||
'Prefix' => $prefix,
|
||||
]
|
||||
);
|
||||
/** @var array{Key: string} $object */
|
||||
foreach ( $objects as $object ) {
|
||||
WP_CLI::line( str_replace( $prefix, '', $object['Key'] ) );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy files to / from the uploads directory. Use s3://bucket/location for S3
|
||||
*
|
||||
* @synopsis <from> <to>
|
||||
*
|
||||
* @param array{0: string, 1: string} $args
|
||||
*/
|
||||
public function cp( array $args ) : void {
|
||||
|
||||
$from = $args[0];
|
||||
$to = $args[1];
|
||||
|
||||
if ( is_dir( $from ) ) {
|
||||
$this->recurse_copy( $from, $to );
|
||||
} else {
|
||||
copy( $from, $to );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Completed copy from %s to %s', $from, $to ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a directory to S3
|
||||
*
|
||||
* @subcommand upload-directory
|
||||
* @synopsis <from> [<to>] [--concurrency=<concurrency>] [--verbose]
|
||||
*
|
||||
* @param array{0: string, 1: string} $args
|
||||
* @param array{concurrency?: int, verbose?: bool} $args_assoc
|
||||
*/
|
||||
public function upload_directory( array $args, array $args_assoc ) : void {
|
||||
|
||||
$from = $args[0];
|
||||
$to = '';
|
||||
if ( isset( $args[1] ) ) {
|
||||
$to = $args[1];
|
||||
}
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
$args_assoc = wp_parse_args(
|
||||
$args_assoc, [
|
||||
'concurrency' => 5,
|
||||
'verbose' => false,
|
||||
]
|
||||
);
|
||||
|
||||
$transfer_args = [
|
||||
'concurrency' => $args_assoc['concurrency'],
|
||||
'debug' => (bool) $args_assoc['verbose'],
|
||||
'before' => function ( Command $command ) : void {
|
||||
if ( in_array( $command->getName(), [ 'PutObject', 'CreateMultipartUpload' ], true ) ) {
|
||||
$acl = defined( 'S3_UPLOADS_OBJECT_ACL' ) ? S3_UPLOADS_OBJECT_ACL : 'public-read';
|
||||
$command['ACL'] = $acl;
|
||||
}
|
||||
},
|
||||
];
|
||||
try {
|
||||
$manager = new Transfer( $s3, $from, 's3://' . S3_UPLOADS_BUCKET . '/' . $to, $transfer_args );
|
||||
$manager->transfer();
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete files from S3
|
||||
*
|
||||
* @synopsis <path> [--regex=<regex>]
|
||||
*
|
||||
* @param array{0: string} $args
|
||||
* @param array{regex?: string} $args_assoc
|
||||
*/
|
||||
public function rm( array $args, array $args_assoc ) : void {
|
||||
|
||||
$s3 = Plugin::get_instance()->s3();
|
||||
|
||||
$prefix = '';
|
||||
$regex = isset( $args_assoc['regex'] ) ? $args_assoc['regex'] : '';
|
||||
|
||||
if ( strpos( S3_UPLOADS_BUCKET, '/' ) !== false ) {
|
||||
$prefix = trailingslashit( str_replace( strtok( S3_UPLOADS_BUCKET, '/' ) . '/', '', S3_UPLOADS_BUCKET ) );
|
||||
}
|
||||
|
||||
if ( isset( $args[0] ) ) {
|
||||
$prefix .= ltrim( $args[0], '/' );
|
||||
|
||||
if ( strpos( $args[0], '.' ) === false ) {
|
||||
$prefix = trailingslashit( $prefix );
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$s3->deleteMatchingObjects(
|
||||
strtok( S3_UPLOADS_BUCKET, '/' ),
|
||||
$prefix,
|
||||
$regex,
|
||||
[
|
||||
'before_delete',
|
||||
function() {
|
||||
WP_CLI::line( 'Deleting file' );
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
WP_CLI::error( $e->getMessage() );
|
||||
}
|
||||
|
||||
WP_CLI::success( sprintf( 'Successfully deleted %s', $prefix ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the auto-rewriting of media links to S3
|
||||
*/
|
||||
public function enable() : void {
|
||||
update_option( 's3_uploads_enabled', 'enabled' );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting enabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the auto-rewriting of media links to S3
|
||||
*/
|
||||
public function disable() : void {
|
||||
delete_option( 's3_uploads_enabled' );
|
||||
|
||||
WP_CLI::success( 'Media URL rewriting disabled.' );
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files for a given attachment.
|
||||
*
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @subcommand get-attachment-files
|
||||
* @synopsis <attachment-id>
|
||||
*
|
||||
* @param array{0: int} $args
|
||||
*/
|
||||
public function get_attachment_files( array $args ) : void {
|
||||
WP_CLI::print_value( Plugin::get_attachment_files( $args[0] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ACL of all files for an attachment.
|
||||
*
|
||||
* Useful for debugging.
|
||||
*
|
||||
* @subcommand set-attachment-acl
|
||||
* @synopsis <attachment-id> <acl>
|
||||
*
|
||||
* @param array{0: int, 1: 'public-read'|'private'} $args
|
||||
*/
|
||||
public function set_attachment_acl( array $args ) : void {
|
||||
$result = Plugin::get_instance()->set_attachment_files_acl( $args[0], $args[1] );
|
||||
WP_CLI::print_value( $result );
|
||||
}
|
||||
|
||||
private function recurse_copy( string $src, string $dst ) : void {
|
||||
$dir = opendir( $src );
|
||||
@mkdir( $dst );
|
||||
while ( false !== ( $file = readdir( $dir ) ) ) {
|
||||
if ( ( '.' !== $file ) && ( '..' !== $file ) ) {
|
||||
if ( is_dir( $src . '/' . $file ) ) {
|
||||
$this->recurse_copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
} else {
|
||||
WP_CLI::line( sprintf( 'Copying from %s to %s', $src . '/' . $file, $dst . '/' . $file ) );
|
||||
copy( $src . '/' . $file, $dst . '/' . $file );
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir( $dir );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the required constants for the S3 connections are set.
|
||||
*
|
||||
* @return bool true if all constants are set, else false.
|
||||
*/
|
||||
private function verify_s3_access_constants() {
|
||||
$required_constants = [
|
||||
'S3_UPLOADS_BUCKET',
|
||||
];
|
||||
|
||||
// Credentials do not need to be set when using AWS Instance Profiles.
|
||||
if ( ! defined( 'S3_UPLOADS_USE_INSTANCE_PROFILE' ) || ! S3_UPLOADS_USE_INSTANCE_PROFILE ) {
|
||||
array_push( $required_constants, 'S3_UPLOADS_KEY', 'S3_UPLOADS_SECRET' );
|
||||
}
|
||||
|
||||
$all_set = true;
|
||||
foreach ( $required_constants as $constant ) {
|
||||
if ( ! defined( $constant ) ) {
|
||||
WP_CLI::error( sprintf( 'The required constant %s is not defined.', $constant ), false );
|
||||
$all_set = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $all_set;
|
||||
}
|
||||
}
|
||||
192
html/wp-content/plugins/s3-uploads/inc/namespace.php
Normal file
192
html/wp-content/plugins/s3-uploads/inc/namespace.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace S3_Uploads;
|
||||
|
||||
function init() : void {
|
||||
// Ensure the AWS SDK can be loaded.
|
||||
if ( ! class_exists( '\\Aws\\S3\\S3Client' ) ) {
|
||||
trigger_error( 'S3 Uploads requires the AWS SDK. Ensure Composer dependencies have been loaded.', E_USER_WARNING );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! check_requirements() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_BUCKET' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ( ! defined( 'S3_UPLOADS_KEY' ) || ! defined( 'S3_UPLOADS_SECRET' ) ) && ! defined( 'S3_UPLOADS_USE_INSTANCE_PROFILE' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! defined( 'S3_UPLOADS_REGION' ) ) {
|
||||
wp_die( 'S3_UPLOADS_REGION constant is required. Please define it in your wp-config.php' );
|
||||
}
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
\WP_CLI::add_command( 's3-uploads', 'S3_Uploads\\WP_CLI_Command' );
|
||||
}
|
||||
|
||||
$instance = Plugin::get_instance();
|
||||
$instance->setup();
|
||||
|
||||
// Add filters to "wrap" the wp_privacy_personal_data_export_file function call as we need to
|
||||
// switch out the personal_data directory to a local temp folder, and then upload after it's
|
||||
// complete, as Core tries to write directly to the ZipArchive which won't work with the
|
||||
// S3 streamWrapper.
|
||||
add_action( 'wp_privacy_personal_data_export_file', __NAMESPACE__ . '\\before_export_personal_data', 9, 0 );
|
||||
add_action( 'wp_privacy_personal_data_export_file', __NAMESPACE__ . '\\after_export_personal_data', 11, 0 );
|
||||
add_action( 'wp_privacy_personal_data_export_file_created', __NAMESPACE__ . '\\move_temp_personal_data_to_s3', 1000 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the environment meets the plugin's requirements, like the minimum PHP version.
|
||||
*
|
||||
* @return bool True if the requirements are met, else false.
|
||||
*/
|
||||
function check_requirements() : bool {
|
||||
global $wp_version;
|
||||
|
||||
if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\outdated_php_version_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( version_compare( $wp_version, '5.3.0', '<' ) ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\outdated_wp_version_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ini_get( 'allow_url_fopen' ) === false || ini_get( 'allow_url_fopen' ) === '' ) {
|
||||
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
|
||||
add_action( 'admin_notices', __NAMESPACE__ . '\\url_fopen_disabled_notice', 10, 0 );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the PHP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function outdated_php_version_notice() : void {
|
||||
printf(
|
||||
'<div class="error"><p>The S3 Uploads plugin requires PHP version 7.4 or higher. Your server is running PHP version %s.</p></div>',
|
||||
PHP_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the PHP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function url_fopen_disabled_notice() : void {
|
||||
printf( '<div class="error"><p>The S3 Uploads plugin requires PHP option allow_url_fopen to be enabled. <a href="%s" target="_blank" rel="noopener noreferrer">Learn more</a>.</p></div>',
|
||||
'https://www.php.net/manual/en/filesystem.configuration.php#ini.allow-url-fopen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print an admin notice when the WP version is not high enough.
|
||||
*
|
||||
* This has to be a named function for compatibility with PHP 5.2.
|
||||
*/
|
||||
function outdated_wp_version_notice() : void {
|
||||
global $wp_version;
|
||||
|
||||
printf(
|
||||
'<div class="error"><p>The S3 Uploads plugin requires WordPress version 5.3 or higher. Your server is running WordPress version %s.</p></div>',
|
||||
esc_html( $wp_version )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL rewriting is enabled.
|
||||
*
|
||||
* Define S3_UPLOADS_AUTOENABLE to false in your wp-config to disable, or use the
|
||||
* s3_uploads_enabled option.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function enabled() : bool {
|
||||
// Make sure the plugin is enabled when autoenable is on
|
||||
$constant_autoenable_off = ( defined( 'S3_UPLOADS_AUTOENABLE' ) && false === S3_UPLOADS_AUTOENABLE );
|
||||
|
||||
if ( $constant_autoenable_off && 'enabled' !== get_option( 's3_uploads_enabled' ) ) { // If the plugin is not enabled, skip
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the filters for wp_privacy_exports_dir to use a temp folder location.
|
||||
*/
|
||||
function before_export_personal_data() : void {
|
||||
add_filter( 'wp_privacy_exports_dir', __NAMESPACE__ . '\\set_wp_privacy_exports_dir' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the filters for wp_privacy_exports_dir as we only want it added in some cases.
|
||||
*/
|
||||
function after_export_personal_data() : void {
|
||||
remove_filter( 'wp_privacy_exports_dir', __NAMESPACE__ . '\\set_wp_privacy_exports_dir' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the wp_privacy_exports_dir location
|
||||
*
|
||||
* We don't want to use the default uploads folder location, as with S3 Uploads this is
|
||||
* going to the a s3:// custom URL handler, which is going to fail with the use of ZipArchive.
|
||||
* Instead we set to to WP's get_temp_dir and move the fail in the wp_privacy_personal_data_export_file_created
|
||||
* hook.
|
||||
*
|
||||
* @param string $dir
|
||||
* @return string
|
||||
*/
|
||||
function set_wp_privacy_exports_dir( string $dir ) {
|
||||
if ( strpos( $dir, 's3://' ) !== 0 ) {
|
||||
return $dir;
|
||||
}
|
||||
$dir = get_temp_dir() . 'wp_privacy_exports_dir/';
|
||||
if ( ! is_dir( $dir ) ) {
|
||||
mkdir( $dir );
|
||||
file_put_contents( $dir . 'index.html', '' ); // @codingStandardsIgnoreLine FS write is ok.
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the tmp personal data file to the true uploads location
|
||||
*
|
||||
* Once a personal data file has been written, move it from the overridden "temp"
|
||||
* location to the S3 location where it should have been stored all along, and where
|
||||
* the "natural" Core URL is going to be pointing to.
|
||||
*/
|
||||
function move_temp_personal_data_to_s3( string $archive_pathname ) : void {
|
||||
if ( strpos( $archive_pathname, get_temp_dir() ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
$upload_dir = wp_upload_dir();
|
||||
$exports_dir = trailingslashit( $upload_dir['basedir'] ) . 'wp-personal-data-exports/';
|
||||
$destination = $exports_dir . pathinfo( $archive_pathname, PATHINFO_FILENAME ) . '.' . pathinfo( $archive_pathname, PATHINFO_EXTENSION );
|
||||
copy( $archive_pathname, $destination );
|
||||
unlink( $archive_pathname );
|
||||
}
|
||||
34
html/wp-content/plugins/s3-uploads/psalm.xml
Normal file
34
html/wp-content/plugins/s3-uploads/psalm.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
errorLevel="2"
|
||||
ensureArrayStringOffsetsExist="true"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="./"/>
|
||||
<ignoreFiles>
|
||||
<directory name="vendor"/>
|
||||
<directory name="psalm"/>
|
||||
<directory name="tests"/>
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
<stubs>
|
||||
<file name="psalm/stubs/imagick.php"/>
|
||||
<file name="psalm/stubs/constants.php"/>
|
||||
</stubs>
|
||||
<globals>
|
||||
<var name="wp_version" type="string"/>
|
||||
</globals>
|
||||
<issueHandlers>
|
||||
<MissingFile>
|
||||
<errorLevel type="suppress">
|
||||
<file name="inc/class-plugin.php"/>
|
||||
</errorLevel>
|
||||
</MissingFile>
|
||||
</issueHandlers>
|
||||
<plugins>
|
||||
<pluginClass class="PsalmWordPress\Plugin"/>
|
||||
</plugins>
|
||||
</psalm>
|
||||
14
html/wp-content/plugins/s3-uploads/psalm/stubs/constants.php
Normal file
14
html/wp-content/plugins/s3-uploads/psalm/stubs/constants.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const S3_UPLOADS_BUCKET = 'hmn-uploads';
|
||||
const S3_UPLOADS_KEY = 'key';
|
||||
const S3_UPLOADS_SECRET = 'secret';
|
||||
const S3_UPLOADS_REGION = 'us-east-1';
|
||||
const S3_UPLOADS_BUCKET_URL = 'https://localhost';
|
||||
const S3_UPLOADS_OBJECT_ACL = 'public';
|
||||
|
||||
const S3_UPLOADS_HTTP_EXPIRES = '2 days';
|
||||
const S3_UPLOADS_HTTP_CACHE_CONTROL = '300';
|
||||
23
html/wp-content/plugins/s3-uploads/psalm/stubs/imagick.php
Normal file
23
html/wp-content/plugins/s3-uploads/psalm/stubs/imagick.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @method Imagick clone() (PECL imagick 2.0.0)<br/>Makes an exact copy of the Imagick object
|
||||
* @link https://php.net/manual/en/class.imagick.php
|
||||
*/
|
||||
class Imagick implements Iterator, Countable {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace WP_CLI {
|
||||
/**
|
||||
*
|
||||
* @param string $command
|
||||
* @param callable|class-string $class
|
||||
* @return void
|
||||
*/
|
||||
function add_command( string $command, $class ) {
|
||||
|
||||
}
|
||||
}
|
||||
13
html/wp-content/plugins/s3-uploads/s3-uploads.php
Normal file
13
html/wp-content/plugins/s3-uploads/s3-uploads.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Plugin Name: S3 Uploads
|
||||
Description: Store uploads in S3
|
||||
Author: Human Made Limited
|
||||
Version: 3.0.11
|
||||
Author URI: https://hmn.md
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/inc/namespace.php';
|
||||
|
||||
add_action( 'plugins_loaded', 'S3_Uploads\\init', 0, 0 );
|
||||
1
html/wp-content/plugins/s3-uploads/verify.txt
Normal file
1
html/wp-content/plugins/s3-uploads/verify.txt
Normal file
@@ -0,0 +1 @@
|
||||
• Todo: migrate to Altis
|
||||
Reference in New Issue
Block a user