brianhenryie/bh-wp-private-uploads
Composer 安装命令:
composer require brianhenryie/bh-wp-private-uploads
包简介
WordPress library for serving private/protected file uploads.
README 文档
README
BH WP Private Uploads
A library to easily create a WordPress uploads subdirectory whose contents cannot be publicly downloaded. Based on Chris Dennis 's brilliant Private Uploads plugin. Adds convenience functions for uploading files to the protected directory, CLI and REST API commands, and displays an admin notice if the directory is public.
Intro
I've needed this in various plugins and libraries:
e.g.
- BH WP Logger needs the "logs" directory to be private
- BH WP Mailboxes needs the "attachments" directory to be private
- BH WC Auto Print Shipping Labels & Receipts needs its PDF directory to be private
The main feature is that it regularly runs a HTTP request to confirm the directory is protected. If it's not, it displays an admin notice.
Then, it allows admins to download all files from that directory and has a filter to allow other users to access the files.
It duplicates the Media/attachment UI, and metaboxes can be added to custom post types for uploading files.
It's far from polished, but there's a lot going on that's not mentioned in this README.
NB: Expect breaking changes with every release until v1.0.0.
If you decide to use this, I'm happy to jump on a call to talk about the direction of the library and how it can be improved.
The main feature in-progress is to allow files to be tied to a specific user, and to allow broader permissions based on the parent post. Specifically to enable GDPR-compliant auto-deletion.
Install
composer require brianhenryie/bh-wp-private-uploads
The following code expects you're prefixing your libraries' namespaces with a tool such as brianhenryie/strauss.
Instantiate
The following code will create a folder, wp-content/uploads/my-plugin, with a .htaccess protecting it (via WordPress rewrite rules), and creates a cron job to verify the URL is protected, otherwise it displays an admin notice warning the site admin.
$settings = new class() implements \BrianHenryIE\WP_Private_Uploads\Private_Uploads_Settings_Interface { use \BrianHenryIE\WP_Private_Uploads\Private_Uploads_Settings_Trait; public function get_plugin_slug(): string { return 'my-plugin'; } }; $private_uploads = \BrianHenryIE\WP_Private_Uploads\Private_Uploads::instance( $settings );
The trait provides some sensible defaults based off the plugin slug, which can be easily overridden. It also allows forward-compatability, i.e. methods can be added to the settings interface and defaults provided by the trait.
Use
That $private_uploads instance can be passed around, or the singleton can be accessed anywhere in the code without requiring the settings again.
$private_uploads = \BrianHenryIE\WP_Private_Uploads\Private_Uploads::instance();
The \BrianHenryIE\WP_Private_Uploads\API\API class (which Private_Uploads extends) contains convenience functions for downloading and moving files to the private uploads folder. These methods use wp_handle_upload behind the scenes and return result objects with file information.
// Download `https://example.org/doc.pdf` to `wp-content/uploads/my-plugin/2022/02/target-filename.pdf`. $result = $private_uploads->download_remote_file_to_private_uploads( 'https://example.org/doc.pdf', 'target-filename.pdf' ); if ( $result->is_success() ) { $file_path = $result->get_file(); $file_url = $result->get_url(); } // Move `'/local/path/to/doc.pdf` to `wp-content/uploads/my-plugin/2022/02/target-filename.pdf`. $result = $private_uploads->move_file_to_private_uploads( '/local/path/to/doc.pdf', 'target-filename.pdf' ); if ( $result->is_success() ) { $file_path = $result->get_file(); }
Important
The API class performs no user-capability checks. This is deliberate: uploads frequently happen on cron and WP-CLI where there is no logged-in user (user id 0), and requiring upload_files would break those flows. Authorization is enforced at each request boundary instead (the REST controller's create_item_permissions_check(), admin-ajax capability checks, and WP-CLI being trusted). If you expose these methods to a web request, check capabilities yourself first. Consumer plugins that want an additional guard can hook the bh_wp_private_uploads_can_upload filter (return false to reject an upload).
The filter is passed the plugin slug and post type name of the instance the file is being uploaded to, so a single handler can distinguish between instances:
add_filter( 'bh_wp_private_uploads_can_upload', 'reject_large_uploads', 10, 5 ); /** * @param bool $can_upload * @param string $tmp_file Source filepath. * @param string $filename Destination filename. * @param string $plugin_slug * @param string $post_type_name */ function reject_large_uploads( bool $can_upload, string $tmp_file, string $filename, string $plugin_slug, string $post_type_name ): bool { if ( 'my-plugin' !== $plugin_slug ) { return $can_upload; } return $can_upload && filesize( $tmp_file ) < 10_000_000; }
Returning false causes the API method to throw a Private_Uploads_Exception.
The ..._and_create_post variants additionally create a post of the registered custom post type recording the file – so it appears in the private media library UI – and assign it an owner (post_author) and optionally a parent post:
// Move the file and record it with a post owned by the user, attached to e.g. a WooCommerce order. $result = $private_uploads->move_file_to_private_uploads_and_create_post( tmp_file: '/local/path/to/doc.pdf', filename: 'target-filename.pdf', post_author_id: $user_id, // Omit for no owner (`post_author` = `0`). post_parent_id: $order_id, ); $post_id = $result->post_id; // Download a remote file and record it with a post. $result = $private_uploads->download_remote_file_to_private_uploads_and_create_post( file_url: 'https://example.org/doc.pdf', filename: 'target-filename.pdf', post_author_id: $user_id, );
By default, administrators can access the files via their URL. This can be widened to more users with the filter:
/** * Allow filtering for other users. * * @param bool $should_serve_file * @param string $file * @param string $plugin_slug * @param string $post_type_name */ $should_serve_file = apply_filters( 'bh_wp_private_uploads_allow', $should_serve_file, $file, $plugin_slug, $post_type_name );
e.g. WooCommerce plugins probably always want shop-managers to be able to access files:
add_filter( 'bh_wp_private_uploads_allow', 'add_shop_manager_to_allow', 10, 3 ); function add_shop_manager_to_allow( bool $should_serve_file, string $file, string $plugin_slug ): bool { if ( 'my-plugin' !== $plugin_slug ) { return $should_serve_file; } return $should_serve_file || current_user_can( 'manage_woocommerce' ); }
Hooks
Every hook is passed the $plugin_slug and $post_type_name of the instance it fired for as its final two arguments, so one callback can serve several instances (or ignore the ones it does not own). Remember WordPress passes only the first argument unless you declare the argument count in add_filter() / add_action().
| Hook | Type | Arguments |
|---|---|---|
bh_wp_private_uploads_can_upload |
filter | $can_upload, $tmp_file, $filename, $plugin_slug, $post_type_name |
bh_wp_private_uploads_allow |
filter | $should_serve_file, $file, $plugin_slug, $post_type_name |
bh_wp_private_uploads_url_is_public_warning |
filter | $content, $url, $plugin_slug, $post_type_name |
bh_wp_private_uploads_rest_upload |
action | $file, $request, $plugin_slug, $post_type_name |
The earlier per-post-type hook names – bh_wp_private_uploads_{$post_type_name}_allow, bh_wp_private_uploads_url_is_public_warning_{$post_type_name} and rest_private_uploads_upload – still fire, but are deprecated as of 0.4.0 and will be removed in a future release.
Advanced
Folder Name
The folder name can easily be changed, e.g. wp-content/uploads/email-attachments:
$settings = new class() implements \BrianHenryIE\WP_Private_Uploads\API\Private_Uploads_Settings_Interface { use \BrianHenryIE\WP_Private_Uploads\API\Private_Uploads_Settings_Trait; public function get_plugin_slug(): string { return 'my-plugin'; } /** * Defaults to the plugin slug when using Private_Uploads_Settings_Trait. */ public function get_uploads_subdirectory_name(): string { return 'email-attachments'; } }; $private_uploads = \BrianHenryIE\WP_Private_Uploads\Private_Uploads::instance( $settings );
CLI Command
A CLI command is easily added during configuration:
$settings = new class() implements \BrianHenryIE\WP_Private_Uploads\API\Private_Uploads_Settings_Interface { use \BrianHenryIE\WP_Private_Uploads\API\Private_Uploads_Settings_Trait; public function get_plugin_slug(): string { return 'my-plugin'; } /** * Defaults to no CLI commands when using Private_Uploads_Settings_Trait. */ public function get_cli_base(): ?string { return 'my-plugin'; } }; $private_uploads = \BrianHenryIE\WP_Private_Uploads\Private_Uploads::instance( $settings );
wp my-plugin download https://example.org/doc.pdf
!Singleton
There's no need to use the singleton.
$private_uploads = new Private_Uploads( $private_uploads_settings, $logger ); // Add the hooks: new BH_WP_Private_Uploads_Hooks( $private_uploads, $private_uploads_settings, $logger );
Quick Test
To quickly test the URL is private with cURL:
curl -o /dev/null --silent --head --write-out '%{http_code}\n' http://localhost:8080/bh-wp-private-uploads/wp-content/uploads/private/private.txt
Contributing
See CONTRIBUTING.md.
Status
TODO:
- Test API and serve private files classes
Focus settings on the post type, not the plugin slug(maybe rename the settings interface to reflect this)- Instantiate the hooks with API class as the parameter, not the Settings (i.e. avoid situation where wires could be crossed)
- Add documentation & screenshots for the media upload UI
- Update this documentation to include post type object filter.
- Verify all test steps in this README
- Test with bh-wp-logger
Some amount of PHPUnit, WPCS, PhpStan done, but lots to doNow thoroughly PHPCS + PHPStan and lots of PHPUnit + Playwright- User level permissions per file. (custom post type with filepath/url as GUID)
- Acceptance tests: https://github.com/gamajo/codeception-redirects
- Unit test REST endpoint
- Does the rewrite rule work when WordPress is installed in a subdir?
- Add Nginx instructions
- Detect the user's hosting provider
- GDPR deletion
- REST API file upload -> webhook.
- When viewing an individual post edit screen, it should display information about auto-deleting
- Zip development plugin and add PLayground link on PRs & releases
Permissions:
We already have registered a post type for registering the REST endpoint. For files that need to be tied to a specific user, make them the author of the post For broader permissions, use the parent of the post--- e.g. set the parent post to be the WooCommerce order and anyone who is allowed to view the order can view the file. Also ties into privacy (GDPR deletion etc.)
brianhenryie/bh-wp-private-uploads 适用场景与选型建议
brianhenryie/bh-wp-private-uploads 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 24.83k 次下载、GitHub Stars 达 3, 最近一次更新时间为 2024 年 04 月 20 日, 在 PHP 生态内属于活跃度较高的组件。
我们在过去多个企业项目中使用过 brianhenryie/bh-wp-private-uploads 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 brianhenryie/bh-wp-private-uploads 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
统计信息
- 总下载量: 24.83k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 3
- 点击次数: 13
- 依赖项目数: 2
- 推荐数: 1
其他信息
- 授权协议: GPL-2.0-or-later
- 更新时间: 2024-04-20