Add support for appending a unique string to the filename for the wp/v2/media endpoint (#42702)

* Extract 'downloadable product' class and include it for API endpoint calls as well

* Re-add mediauploader component

* Restore UploadFilesMenuItem

* Provide additionalData type

* Restore MediaUploader component

* Lint PHP

* Add changelogs

* Update pnpm-lock

* Revert "Update pnpm-lock"

This reverts commit b61ee5813aa0b7b8b1ea8e71423bedbb6f876139.

* Revert pnpm-lock.yaml

* Fix unit tests

* Use WC_ABSPATH
This commit is contained in:
Nathan Silveira 2023-12-18 14:12:44 -03:00 committed by GitHub
parent bbe2a6f2d7
commit 09bcb3fcac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 132 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add additionalData prop to MediaUploader component

View File

@ -39,6 +39,7 @@ type MediaUploaderProps = {
onUpload?: ( files: MediaItem | MediaItem[] ) => void;
onFileUploadChange?: ( files: MediaItem | MediaItem[] ) => void;
uploadMedia?: ( options: UploadMediaOptions ) => Promise< void >;
additionalData?: Record< string, unknown >;
};
export const MediaUploader = ( {
@ -56,6 +57,7 @@ export const MediaUploader = ( {
onUpload = () => null,
onSelect = () => null,
uploadMedia = wpUploadMedia,
additionalData,
}: MediaUploaderProps ) => {
const multiple = Boolean( multipleSelect );
@ -72,6 +74,7 @@ export const MediaUploader = ( {
onFileChange( files ) {
onFileUploadChange( multiple ? files : files[ 0 ] );
},
additionalData,
} );
} }
render={ ( { openFileDialog } ) => (
@ -133,6 +136,7 @@ export const MediaUploader = ( {
multiple ? files : files[ 0 ]
);
},
additionalData,
} )
}
/>

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Allow uploading downloadable products by drag & drop and without the Media Library component

View File

@ -12,9 +12,11 @@ import { chevronDown, chevronUp } from '@wordpress/icons';
import { DownloadsMenuProps } from './types';
import { MediaLibraryMenuItem } from '../media-library-menu-item';
import { InsertUrlMenuItem } from '../insert-url-menu-item';
import { UploadFilesMenuItem } from '../upload-files-menu-item';
export function DownloadsMenu( {
allowedTypes,
maxUploadFileSize,
onUploadSuccess,
onUploadError,
}: DownloadsMenuProps ) {
@ -39,6 +41,15 @@ export function DownloadsMenu( {
renderContent={ ( { onClose } ) => (
<div className="components-dropdown-menu__menu">
<MenuGroup>
<UploadFilesMenuItem
allowedTypes={ allowedTypes }
maxUploadFileSize={ maxUploadFileSize }
onUploadSuccess={ ( files ) => {
onUploadSuccess( files );
onClose();
} }
onUploadError={ onUploadError }
/>
<MediaLibraryMenuItem
allowedTypes={ allowedTypes }
onUploadSuccess={ ( files ) => {

View File

@ -13,7 +13,7 @@ import {
import { closeSmall } from '@wordpress/icons';
import { MediaItem } from '@wordpress/media-utils';
import { useWooBlockProps } from '@woocommerce/block-templates';
import { ListItem, Sortable } from '@woocommerce/components';
import { ListItem, MediaUploader, Sortable } from '@woocommerce/components';
import { Product, ProductDownload } from '@woocommerce/data';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore No types for this exist yet.
@ -31,6 +31,7 @@ import {
ManageDownloadLimitsModalProps,
} from '../../../components/manage-download-limits-modal';
import { EditDownloadsModal } from './edit-downloads-modal';
import { UploadImage } from './upload-image';
function getFileName( url?: string ) {
const [ name ] = url?.split( '/' ).reverse() ?? [];
@ -249,36 +250,53 @@ export function Edit( {
</div>
<div className="wp-block-woocommerce-product-downloads-field__body">
{ ! Boolean( downloads.length ) && (
<div className="wp-block-woocommerce-product-downloads-field__drop-zone-content">
<p className="wp-block-woocommerce-product-downloads-field__drop-zone-label">
{ createInterpolateElement(
__(
'Supported file types: <Types /> and more. <link>View all</link>',
'woocommerce'
),
{
Types: (
<Fragment>
PNG, JPG, PDF, PPT, DOC, MP3, MP4
</Fragment>
),
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href="https://codex.wordpress.org/Uploading_Files"
target="_blank"
rel="noreferrer"
onClick={ ( event ) =>
event.stopPropagation()
}
/>
),
}
) }
</p>
</div>
) }
<MediaUploader
label={
! Boolean( downloads.length ) ? (
<div className="wp-block-woocommerce-product-downloads-field__drop-zone-content">
<UploadImage />
<p className="wp-block-woocommerce-product-downloads-field__drop-zone-label">
{ createInterpolateElement(
__(
'Supported file types: <Types /> and more. <link>View all</link>',
'woocommerce'
),
{
Types: (
<Fragment>
PNG, JPG, PDF, PPT, DOC,
MP3, MP4
</Fragment>
),
link: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href="https://codex.wordpress.org/Uploading_Files"
target="_blank"
rel="noreferrer"
onClick={ ( event ) =>
event.stopPropagation()
}
/>
),
}
) }
</p>
</div>
) : (
''
)
}
buttonText=""
allowedMediaTypes={ allowedTypes }
multipleSelect={ 'add' }
onUpload={ handleFileUpload }
onFileUploadChange={ handleFileUpload }
onError={ handleUploadError }
additionalData={ {
type: 'downloadable_product',
} }
/>
{ Boolean( downloads.length ) && (
<Sortable className="wp-block-woocommerce-product-downloads-field__table">

View File

@ -30,6 +30,9 @@ export function UploadFilesMenuItem( {
maxUploadFileSize,
onFileChange: onUploadSuccess,
onError: onUploadError,
additionalData: {
type: 'downloadable_product',
},
} );
}

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Use downloadable products configuration for uploads through wp/v2/media endpoint

View File

@ -54,10 +54,7 @@ class WC_Admin_Post_Types {
add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 );
add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) );
// Uploads.
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
add_filter( 'wp_unique_filename', array( $this, 'update_filename' ), 10, 3 );
add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
include_once __DIR__ . '/class-wc-admin-upload-downloadable-product.php';
// Hide template for CPT archive.
add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 );
@ -742,100 +739,6 @@ class WC_Admin_Post_Types {
<?php
}
/**
* Change upload dir for downloadable files.
*
* @param array $pathdata Array of paths.
* @return array
*/
public function upload_dir( $pathdata ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( isset( $_POST['type'] ) && 'downloadable_product' === $_POST['type'] ) {
if ( empty( $pathdata['subdir'] ) ) {
$pathdata['path'] = $pathdata['path'] . '/woocommerce_uploads';
$pathdata['url'] = $pathdata['url'] . '/woocommerce_uploads';
$pathdata['subdir'] = '/woocommerce_uploads';
} else {
$new_subdir = '/woocommerce_uploads' . $pathdata['subdir'];
$pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
$pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
$pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
}
}
return $pathdata;
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Change filename for WooCommerce uploads and prepend unique chars for security.
*
* @param string $full_filename Original filename.
* @param string $ext Extension of file.
* @param string $dir Directory path.
*
* @return string New filename with unique hash.
* @since 4.0
*/
public function update_filename( $full_filename, $ext, $dir ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( ! isset( $_POST['type'] ) || ! 'downloadable_product' === $_POST['type'] ) {
return $full_filename;
}
if ( ! strpos( $dir, 'woocommerce_uploads' ) ) {
return $full_filename;
}
if ( 'no' === get_option( 'woocommerce_downloads_add_hash_to_filename' ) ) {
return $full_filename;
}
return $this->unique_filename( $full_filename, $ext );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Change filename to append random text.
*
* @param string $full_filename Original filename with extension.
* @param string $ext Extension.
*
* @return string Modified filename.
*/
public function unique_filename( $full_filename, $ext ) {
$ideal_random_char_length = 6; // Not going with a larger length because then downloaded filename will not be pretty.
$max_filename_length = 255; // Max file name length for most file systems.
$length_to_prepend = min( $ideal_random_char_length, $max_filename_length - strlen( $full_filename ) - 1 );
if ( 1 > $length_to_prepend ) {
return $full_filename;
}
$suffix = strtolower( wp_generate_password( $length_to_prepend, false, false ) );
$filename = $full_filename;
if ( strlen( $ext ) > 0 ) {
$filename = substr( $filename, 0, strlen( $filename ) - strlen( $ext ) );
}
$full_filename = str_replace(
$filename,
"$filename-$suffix",
$full_filename
);
return $full_filename;
}
/**
* Run a filter when uploading a downloadable product.
*/
public function woocommerce_media_upload_downloadable_product() {
do_action( 'media_upload_file' );
}
/**
* Grant downloadable file access to any newly added files on any existing.
* orders for this product that have previously been granted downloadable file access.

View File

@ -0,0 +1,123 @@
<?php
/**
* Add hooks related to uploading downloadable products.
*
* @package WooCommerce\Admin
* @version 8.5.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( class_exists( 'WC_Admin_Upload_Downloadable_Product', false ) ) {
return new WC_Admin_Upload_Downloadable_Product();
}
/**
* WC_Admin_Upload_Downloadable_Product Class.
*/
class WC_Admin_Upload_Downloadable_Product {
/**
* Add hooks.
*/
public function __construct() {
add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
add_filter( 'wp_unique_filename', array( $this, 'update_filename' ), 10, 3 );
add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
}
/**
* Change upload dir for downloadable files.
*
* @param array $pathdata Array of paths.
* @return array
*/
public function upload_dir( $pathdata ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( isset( $_POST['type'] ) && 'downloadable_product' === $_POST['type'] ) {
if ( empty( $pathdata['subdir'] ) ) {
$pathdata['path'] = $pathdata['path'] . '/woocommerce_uploads';
$pathdata['url'] = $pathdata['url'] . '/woocommerce_uploads';
$pathdata['subdir'] = '/woocommerce_uploads';
} else {
$new_subdir = '/woocommerce_uploads' . $pathdata['subdir'];
$pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
$pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
$pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
}
}
return $pathdata;
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Change filename for WooCommerce uploads and prepend unique chars for security.
*
* @param string $full_filename Original filename.
* @param string $ext Extension of file.
* @param string $dir Directory path.
*
* @return string New filename with unique hash.
* @since 4.0
*/
public function update_filename( $full_filename, $ext, $dir ) {
// phpcs:disable WordPress.Security.NonceVerification.Missing
if ( ! isset( $_POST['type'] ) || ! 'downloadable_product' === $_POST['type'] ) {
return $full_filename;
}
if ( ! strpos( $dir, 'woocommerce_uploads' ) ) {
return $full_filename;
}
if ( 'no' === get_option( 'woocommerce_downloads_add_hash_to_filename' ) ) {
return $full_filename;
}
return $this->unique_filename( $full_filename, $ext );
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
/**
* Change filename to append random text.
*
* @param string $full_filename Original filename with extension.
* @param string $ext Extension.
*
* @return string Modified filename.
*/
public function unique_filename( $full_filename, $ext ) {
$ideal_random_char_length = 6; // Not going with a larger length because then downloaded filename will not be pretty.
$max_filename_length = 255; // Max file name length for most file systems.
$length_to_prepend = min( $ideal_random_char_length, $max_filename_length - strlen( $full_filename ) - 1 );
if ( 1 > $length_to_prepend ) {
return $full_filename;
}
$suffix = strtolower( wp_generate_password( $length_to_prepend, false, false ) );
$filename = $full_filename;
if ( strlen( $ext ) > 0 ) {
$filename = substr( $filename, 0, strlen( $filename ) - strlen( $ext ) );
}
$full_filename = str_replace(
$filename,
"$filename-$suffix",
$full_filename
);
return $full_filename;
}
/**
* Run a filter when uploading a downloadable product.
*/
public function woocommerce_media_upload_downloadable_product() {
// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
do_action( 'media_upload_file' );
}
}

View File

@ -49,6 +49,9 @@ class Init {
// Add currency symbol to orders endpoint response.
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
include_once WC_ABSPATH . 'includes/admin/class-wc-admin-upload-downloadable-product.php';
}
/**

View File

@ -11,18 +11,19 @@
class WC_Test_Admin_Post_Types extends WC_Unit_Test_Case {
/**
* Instance of WC_Admin_Post_Types.
* Instance of WC_Admin_Upload_Downloadable_Product.
*
* @var \WC_Admin_Post_Types
* @var \WC_Admin_Upload_Downloadable_Product
*/
protected $wc_cpt;
/**
* Setup. Create a instance to use throughout.
*/
public function setUp(): void {
parent::setUp();
$this->wc_cpt = new WC_Admin_Post_Types();
$this->wc_cpt = new WC_Admin_Upload_Downloadable_Product();
}
/**