Skip to content

Commit d12ab9f

Browse files
committed
Add Media Resource Checker plugin
1 parent 42b61cc commit d12ab9f

11 files changed

Lines changed: 446 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
package-lock.json
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-blob', 'wp-block-editor', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-hooks', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'f13102762651a2180e59');

wordpress.org/public_html/wp-content/plugins/wporg-media-resource-checker/build/index.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wordpress.org/public_html/wp-content/plugins/wporg-media-resource-checker/build/style-index-rtl.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wordpress.org/public_html/wp-content/plugins/wporg-media-resource-checker/build/style-index.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "wporg-media-resource-checker",
3+
"version": "1.0.0",
4+
"description": "Displays warnings in the block editor when media resources are from domains other than the recommended ones.",
5+
"author": "WordPress.org",
6+
"license": "GPL-2.0-or-later",
7+
"scripts": {
8+
"start": "wp-scripts start",
9+
"build": "wp-scripts build",
10+
"format": "wp-scripts format src",
11+
"packages-update": "wp-scripts packages-update"
12+
},
13+
"devDependencies": {
14+
"@wordpress/icons": "^11.3.0",
15+
"@wordpress/scripts": "^31.1.0"
16+
},
17+
"dependencies": {
18+
"clsx": "^2.1.1"
19+
}
20+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { useSelect } from '@wordpress/data';
5+
import { store as coreStore } from '@wordpress/core-data';
6+
7+
/**
8+
* Internal dependencies
9+
*/
10+
import { getBlockMediaResourceToCheck, isInvalidResource } from './utils';
11+
12+
/**
13+
* Custom hook to check if a block has an invalid media resource.
14+
*
15+
* @param {string} name The block name.
16+
* @param {Object} attributes The block attributes.
17+
* @return {Object} Object containing hasInvalidResource, siteUrl, and mediaUrl.
18+
*/
19+
export const useHasInvalidSource = ( name, attributes ) => {
20+
const siteUrl = useSelect( ( select ) => {
21+
const siteData = select( coreStore ).getEntityRecord(
22+
'root',
23+
'__unstableBase'
24+
);
25+
return siteData?.home || siteData?.url || null;
26+
}, [] );
27+
28+
const mediaUrl = getBlockMediaResourceToCheck( name, attributes );
29+
30+
const hasInvalidResource =
31+
mediaUrl && isInvalidResource( mediaUrl, siteUrl );
32+
33+
return {
34+
hasInvalidResource,
35+
siteUrl,
36+
mediaUrl,
37+
};
38+
};
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import clsx from 'clsx';
5+
6+
/**
7+
* WordPress dependencies
8+
*/
9+
import { __ } from '@wordpress/i18n';
10+
import { addFilter } from '@wordpress/hooks';
11+
import { createHigherOrderComponent } from '@wordpress/compose';
12+
import { BlockControls } from '@wordpress/block-editor';
13+
import {
14+
Dropdown,
15+
ExternalLink,
16+
ToolbarButton,
17+
ToolbarGroup,
18+
} from '@wordpress/components';
19+
import { caution } from '@wordpress/icons';
20+
import { getAuthority } from '@wordpress/url';
21+
22+
/**
23+
* Internal dependencies
24+
*/
25+
import { ALLOWED_DOMAINS } from './utils';
26+
import { useHasInvalidSource } from './hooks';
27+
import './style.scss';
28+
29+
/**
30+
* Block edit component with warning.
31+
*
32+
* @param {Object} props Component props.
33+
* @param {Object} props.BlockEdit The block edit component.
34+
* @return {Function} The block edit with warning component.
35+
*/
36+
const BlockEditWithWarning = ( { BlockEdit, siteUrl, mediaUrl, ...props } ) => {
37+
const siteAuthority = getAuthority( siteUrl );
38+
const allowedDomainList = [
39+
siteAuthority,
40+
...ALLOWED_DOMAINS.map( ( domain ) => domain.authority ),
41+
];
42+
43+
return (
44+
<>
45+
<BlockEdit { ...props } />
46+
{ props.isSelected && (
47+
<BlockControls>
48+
<Dropdown
49+
contentClassName=""
50+
renderToggle={ ( { isOpen, onToggle } ) => {
51+
return (
52+
<ToolbarGroup>
53+
<ToolbarButton
54+
aria-expanded={ isOpen }
55+
aria-haspopup="true"
56+
onClick={ onToggle }
57+
label={ __( 'Media resource error' ) }
58+
icon={ caution }
59+
className="wporg-media-resource-checker-toolbar-button"
60+
/>
61+
</ToolbarGroup>
62+
);
63+
} }
64+
renderContent={ () => {
65+
return (
66+
<div className="wporg-media-resource-checker-popover-content">
67+
<p>
68+
{ __(
69+
'This media resource is from a domain other than the recommended ones.',
70+
'wporg-media-resource-checker'
71+
) }
72+
</p>
73+
<p>
74+
{ mediaUrl && (
75+
<ExternalLink href={ mediaUrl }>
76+
{ mediaUrl }
77+
</ExternalLink>
78+
) }
79+
</p>
80+
<p>
81+
{ __(
82+
'Please use a media resource from the following recommended domains:',
83+
'wporg-media-resource-checker'
84+
) }
85+
</p>
86+
<ul>
87+
{ allowedDomainList.map( ( domain ) => (
88+
<li key={ domain }>{ domain }</li>
89+
) ) }
90+
</ul>
91+
</div>
92+
);
93+
} }
94+
/>
95+
</BlockControls>
96+
) }
97+
</>
98+
);
99+
};
100+
101+
/**
102+
* Higher order component to check if the media resource is from a domain
103+
* other than the recommended ones.
104+
*
105+
* @param {Function} BlockEdit The block edit component.
106+
* @return {Function} The higher order component.
107+
*/
108+
const withMediaResourceChecker = createHigherOrderComponent( ( BlockEdit ) => {
109+
return ( props ) => {
110+
const { name, attributes } = props;
111+
112+
const { hasInvalidResource, siteUrl, mediaUrl } = useHasInvalidSource(
113+
name,
114+
attributes
115+
);
116+
117+
return hasInvalidResource ? (
118+
<BlockEditWithWarning
119+
BlockEdit={ BlockEdit }
120+
siteUrl={ siteUrl }
121+
mediaUrl={ mediaUrl }
122+
{ ...props }
123+
/>
124+
) : (
125+
<BlockEdit key="edit" { ...props } />
126+
);
127+
};
128+
}, 'withMediaResourceChecker' );
129+
130+
/**
131+
* Higher order component to add className to wrapperProps for blocks
132+
* with invalid resources.
133+
*
134+
* @param {Function} BlockListBlock The block list block component.
135+
* @return {Function} The higher order component.
136+
*/
137+
const withInvalidResourceClassName = createHigherOrderComponent(
138+
( BlockListBlock ) => {
139+
return ( props ) => {
140+
const { name, attributes, wrapperProps = {} } = props;
141+
142+
const { hasInvalidResource } = useHasInvalidSource(
143+
name,
144+
attributes
145+
);
146+
147+
const newWrapperProps = hasInvalidResource
148+
? {
149+
...wrapperProps,
150+
className: clsx(
151+
wrapperProps.className,
152+
'wporg-media-resource-checker-has-invalid-resource'
153+
),
154+
}
155+
: wrapperProps;
156+
157+
return (
158+
<BlockListBlock { ...props } wrapperProps={ newWrapperProps } />
159+
);
160+
};
161+
},
162+
'withInvalidResourceClassName'
163+
);
164+
165+
addFilter(
166+
'editor.BlockEdit',
167+
'wporg-media-resource-checker/with-media-resource-checker',
168+
withMediaResourceChecker
169+
);
170+
171+
addFilter(
172+
'editor.BlockListBlock',
173+
'wporg-media-resource-checker/with-invalid-resource-class-name',
174+
withInvalidResourceClassName
175+
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.wporg-media-resource-checker-has-invalid-resource::before {
2+
content: '';
3+
display: block;
4+
width: 100%;
5+
height: 100%;
6+
position: absolute;
7+
top: 0;
8+
left: 0;
9+
z-index: 2;
10+
border: 2px solid #cc1818;
11+
background: rgba(#cc1818, 0.2);
12+
box-sizing: border-box;
13+
}
14+
15+
.wporg-media-resource-checker-toolbar-button svg {
16+
fill: #cc1818;
17+
}
18+
19+
.wporg-media-resource-checker-popover-content {
20+
width: 240px;
21+
word-break: break-word;
22+
23+
p {
24+
margin: 0 0 1em;
25+
}
26+
27+
ul {
28+
margin: 0;
29+
list-style: disc outside;
30+
padding-left: 1.5em;
31+
}
32+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { isBlobURL } from '@wordpress/blob';
5+
import { getAuthority } from '@wordpress/url';
6+
7+
// List of blocks to check.
8+
const BLOCKS_TO_CHECK = [
9+
{
10+
name: 'core/image',
11+
idKey: 'id',
12+
urlKey: 'url',
13+
},
14+
{
15+
name: 'core/video',
16+
idKey: 'id',
17+
urlKey: 'src',
18+
},
19+
{
20+
name: 'core/cover',
21+
idKey: 'id',
22+
urlKey: 'url',
23+
},
24+
];
25+
26+
// List of allowed domain regexes.
27+
export const ALLOWED_DOMAINS = [
28+
{
29+
authority: 'wordpress.org',
30+
regex: /^(.*\.)?wordpress\.org$/,
31+
},
32+
{
33+
authority: 'wp.com',
34+
regex: /^(.*\.)?wp\.com$/,
35+
},
36+
];
37+
38+
/**
39+
* Gets the media resource to check for the block.
40+
*
41+
* @param {string} blockName The name of the block.
42+
* @param {Object} attributes The attributes of the block.
43+
* @return {string|null} The media resource to check, or null if the block is not in the list of blocks to check.
44+
*/
45+
export const getBlockMediaResourceToCheck = ( blockName, attributes ) => {
46+
const blockToCheck = BLOCKS_TO_CHECK.find(
47+
( block ) => block.name === blockName
48+
);
49+
if ( ! blockToCheck ) {
50+
return null;
51+
}
52+
return attributes[ blockToCheck.urlKey ];
53+
};
54+
55+
/**
56+
* Checks whether the block has an invalid resource.
57+
*
58+
* @param {string} mediaUrl The media URL to check.
59+
* @param {string} siteUrl The site URL.
60+
* @return {boolean} True if the resource is invalid.
61+
*/
62+
export const isInvalidResource = ( mediaUrl, siteUrl ) => {
63+
if ( ! siteUrl ) {
64+
return false;
65+
}
66+
67+
// If no URL, cannot determine.
68+
if ( ! mediaUrl || isBlobURL( mediaUrl ) ) {
69+
return false;
70+
}
71+
72+
const siteAuthority = getAuthority( siteUrl );
73+
const mediaAuthority = getAuthority( mediaUrl );
74+
75+
// If the authority is the same, it means the resource is from the site.
76+
if ( siteAuthority === mediaAuthority ) {
77+
return false;
78+
}
79+
80+
// Check if the authority is from an allowed domain.
81+
if (
82+
ALLOWED_DOMAINS.some( ( domain ) =>
83+
mediaAuthority.match( domain.regex )
84+
)
85+
) {
86+
return false;
87+
}
88+
89+
// The media is not from an allowed domain.
90+
return true;
91+
};

0 commit comments

Comments
 (0)