Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions environments/plugin-directory/bin/after-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,37 @@ $WPENV cli sudo sh -c \
# Set up permalinks.
$WP wp rewrite structure '/%postname%/' --hard

# Create pages that exist on wordpress.org/plugins (if they don't already exist).
# Create pages that exist on wordpress.org/plugins.
#
# Idempotent: match by slug and update, otherwise create. after-start.sh runs on
# every `wp-env start`, and a plain `wp post create` would append -2, -3, … to the
# slug each time, leaving the canonical slug pointing at a stale, empty page.
echo "Creating pages..."

# ensure_page <title> <slug> <content> [parent_id]; prints the page ID.
ensure_page() {
local title="$1" slug="$2" content="$3" parent="$4" id
local args=( --post_type=page --post_status=publish --post_title="$title" --post_name="$slug" --post_content="$content" --porcelain )
[ -n "$parent" ] && args+=( --post_parent="$parent" )
id=$($WP wp post list --post_type=page --name="$slug" --field=ID 2>/dev/null | tr -d '\r\n ')
if [ -n "$id" ]; then
$WP wp post update "$id" --post_content="$content" > /dev/null 2>&1
else
id=$($WP wp post create "${args[@]}" 2>/dev/null | tr -d '\r\n ')
fi
printf '%s' "$id"
}

# Parent: /developers/
$WP wp post create --post_type=page --post_status=publish --post_title='Developer Information' --post_name='developers' --porcelain > /dev/null 2>&1 && echo " Created page: /developers/" || true
DEVELOPERS_ID=$($WP wp post list --post_type=page --name=developers --field=ID 2>/dev/null)
DEVELOPERS_ID=$(ensure_page 'Developer Information' 'developers' '' '')

if [ -n "$DEVELOPERS_ID" ]; then
# Children of /developers/
$WP wp post create --post_type=page --post_status=publish --post_title='Add your Plugin' --post_name='add' --post_parent=$DEVELOPERS_ID --porcelain > /dev/null 2>&1 && echo " Created page: /developers/add/" || true
$WP wp post create --post_type=page --post_status=publish --post_title='Readme Validator' --post_content='[readme-validator]' --post_name='readme-validator' --post_parent=$DEVELOPERS_ID --porcelain > /dev/null 2>&1 && echo " Created page: /developers/readme-validator/" || true
$WP wp post create --post_type=page --post_status=publish --post_title='Block Plugin Checker' --post_content='[block-validator]' --post_name='block-plugin-validator' --post_parent=$DEVELOPERS_ID --porcelain > /dev/null 2>&1 && echo " Created page: /developers/block-plugin-validator/" || true
$WP wp post create --post_type=page --post_status=publish --post_title='Release Management' --post_content='[release-confirmation]' --post_name='releases' --post_parent=$DEVELOPERS_ID --porcelain > /dev/null 2>&1 && echo " Created page: /developers/releases/" || true
ensure_page 'Add your Plugin' 'add' '' "$DEVELOPERS_ID" > /dev/null
ensure_page 'Readme Validator' 'readme-validator' '[readme-validator]' "$DEVELOPERS_ID" > /dev/null
ensure_page 'Block Plugin Checker' 'block-plugin-validator' '[block-validator]' "$DEVELOPERS_ID" > /dev/null
ensure_page 'Release Management' 'releases' '[release-confirmation]' "$DEVELOPERS_ID" > /dev/null
echo " Pages ready under /developers/"
fi

# Create stub database tables that exist outside WordPress on production.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public function get_browser_url( $file ) {
return str_replace( 'https://plugins.svn.wordpress.org', 'https://plugins.trac.wordpress.org/browser', $this->repo_url ) . '/' . $file;
}
}

// No repo to link to (e.g. a ZIP upload) — return an empty string rather than null.
return '';
}

/**
Expand Down Expand Up @@ -160,7 +163,7 @@ public function run_check_plugin_repo( $url ) {
$this->record_result(
__FUNCTION__,
'error',
sprintf( __( 'Invalid url: %s', 'wporg-plugins' ), $url ),
sprintf( __( 'Invalid url: %s', 'wporg-plugins' ), esc_html( $url ) ),
$url
);
return $this->results;
Expand All @@ -170,7 +173,7 @@ public function run_check_plugin_repo( $url ) {
$this->record_result(
__FUNCTION__,
'error',
sprintf( __( 'URL must be GitHub or plugins.svn.wordpress.org: %s', 'wporg-plugins' ), $url ),
sprintf( __( 'URL must be GitHub or plugins.svn.wordpress.org: %s', 'wporg-plugins' ), esc_html( $url ) ),
$url
);
return $this->results;
Expand All @@ -182,7 +185,7 @@ public function run_check_plugin_repo( $url ) {
$this->record_result(
__FUNCTION__,
'error',
sprintf( __( 'URL must be a plugin repository: %s', 'wporg-plugins' ), $url ),
sprintf( __( 'URL must be a plugin repository: %s', 'wporg-plugins' ), esc_html( $url ) ),
$url
);
return $this->results;
Expand Down Expand Up @@ -212,7 +215,7 @@ public function run_check_plugin_repo( $url ) {
$this->record_result(
__FUNCTION__,
'error',
sprintf( __( 'URL must be a plugin repository: %s', 'wporg-plugins' ), $url ),
sprintf( __( 'URL must be a plugin repository: %s', 'wporg-plugins' ), esc_html( $url ) ),
$url
);
return $this->results;
Expand Down Expand Up @@ -248,7 +251,7 @@ function export_plugin( $svn_url ) {
$this->record_result(
__FUNCTION__,
'error',
sprintf( __( 'Error fetching repository %s: %s', 'wporg-plugins' ), $svn_url, $export['errors'][0]['error_code'] ?? 'unknown error' ),
sprintf( __( 'Error fetching repository %s: %s', 'wporg-plugins' ), esc_html( $svn_url ), esc_html( $export['errors'][0]['error_code'] ?? 'unknown error' ) ),
$export['errors'] ?? array()
);
return false;
Expand Down Expand Up @@ -421,10 +424,10 @@ public function find_php_functions( $base_dir ) {
'error',
sprintf(
__( 'PHP error %s in %s', 'wporg-plugins' ),
$php_calls->get_error_message(),
esc_html( $php_calls->get_error_message() ),
sprintf( '<a href="%s">%s</a>',
$this->get_browser_url( $filename ) . '#L' . $php_calls->get_error_data(),
$this->relative_filename( $filename )
esc_url( $this->get_browser_url( $filename ) . '#L' . $php_calls->get_error_data() ),
esc_html( $this->relative_filename( $filename ) )
)
),
[ $filename, $php_calls->get_error_data() ]
Expand Down Expand Up @@ -514,15 +517,15 @@ function check_license() {
__FUNCTION__,
'info',
// translators: %s is the license.
sprintf( __( 'Found a license in readme.txt: %s.', 'wporg-plugins' ), $this->readme->license ),
sprintf( __( 'Found a license in readme.txt: %s.', 'wporg-plugins' ), esc_html( $this->readme->license ) ),
$this->readme->license
);
} elseif ( ! empty( $this->headers->License ) ) {
$this->record_result(
__FUNCTION__,
'info',
// translators: %s is the license.
sprintf( __( 'Found a license in plugin headers: %s.', 'wporg-plugins' ), $this->headers->License ),
sprintf( __( 'Found a license in plugin headers: %s.', 'wporg-plugins' ), esc_html( $this->headers->License ) ),
$this->headers->License
);
}
Expand Down Expand Up @@ -598,8 +601,8 @@ function check_for_duplicate_block_name() {
sprintf(
// translators: %1$s is the block slug, %2$s is the found plugin title.
__( 'Block name %1$s already exists in the plugin "%2$s."', 'wporg-plugins' ),
'<code>' . $block->name . '</code>',
$query->posts[0]->post_title
'<code>' . esc_html( $block->name ) . '</code>',
esc_html( $query->posts[0]->post_title )
),
[ 'block_name' => $block->name, 'slug' => $post->post_name ]
);
Expand All @@ -621,7 +624,7 @@ function check_for_standard_block_name() {
__FUNCTION__,
'error',
// translators: %s is the block name.
sprintf( __( 'Block name %s is invalid. Please use lowercase alphanumeric characters.', 'wporg-plugins' ), '<code>' . $block->name . '</code>' )
sprintf( __( 'Block name %s is invalid. Please use lowercase alphanumeric characters.', 'wporg-plugins' ), '<code>' . esc_html( $block->name ) . '</code>' )
);
} else {
$disallowed_ns = array( 'cgb/', 'create-block/', 'example/', 'block/', 'core/', 'gutenberg-examples/' );
Expand All @@ -633,8 +636,8 @@ function check_for_standard_block_name() {
sprintf(
// translators: %1$s is the block name, %2$s is the namespace.
__( 'Block %1$s uses namespace %2$s. Please use a unique namespace.', 'wporg-plugins' ),
'<code>' . $block->name . '</code>',
'<code>' . $ns . '</code>'
'<code>' . esc_html( $block->name ) . '</code>',
'<code>' . esc_html( $ns ) . '</code>'
)
);
break;
Expand Down Expand Up @@ -665,7 +668,7 @@ function check_for_multiple_namespaces() {
// translators: %1$d is the number of namespaces, %2$s is the list of namespaces.
__( 'Found blocks with %1$d different namespaces: %2$s.', 'wporg-plugins' ),
count( $namespaces ),
'<code>' . implode( ', ', $namespaces ) . '</code>'
'<code>' . esc_html( implode( ', ', $namespaces ) ) . '</code>'
),
$namespaces
);
Expand Down Expand Up @@ -732,7 +735,7 @@ function check_for_unique_namespace() {
'error',
// translators: %1$s is the namespace, %2$s is a link to the plugin.
sprintf( __( 'Please use a unique block namespace. Namespace %1$s is already used by %2$s.' ),
'<code>' . $this->get_namespace( $block ) . '</code>',
'<code>' . esc_html( $this->get_namespace( $block ) ) . '</code>',
'<a href="' . esc_url( get_permalink( $post ) ) . '">' . esc_html( $post->post_title ) . '</a>'
)
);
Expand All @@ -745,7 +748,7 @@ function check_for_unique_namespace() {
'warning',
// translators: %1$s is the namespace, %2$s is a link to the plugin.
sprintf( __( 'Please use a unique block namespace. Namespace %1$s is already used by %2$s.' ),
'<code>' . $this->get_namespace( $block ) . '</code>',
'<code>' . esc_html( $this->get_namespace( $block ) ) . '</code>',
'<a href="' . esc_url( get_permalink( $post ) ) . '">' . esc_html( $post->post_title ) . '</a>'
)
);
Expand Down Expand Up @@ -793,7 +796,7 @@ function check_for_block_json() {
__FUNCTION__,
'info',
// translators: %s is the block name.
sprintf( __( 'Found a block.json file for block %s.', 'wporg-plugins' ), '<code>' . $block_name . '</code>' ),
sprintf( __( 'Found a block.json file for block %s.', 'wporg-plugins' ), '<code>' . esc_html( $block_name ) . '</code>' ),
$this->block_json_files[ $block_name ]
);
}
Expand Down Expand Up @@ -834,7 +837,7 @@ function( $block ) {
sprintf(
/* translators: %s is a list of block names. */
__( 'More than one top-level block was found: %s', 'wporg-plugins' ),
implode( ', ', $list )
esc_html( implode( ', ', $list ) )
)
);
}
Expand All @@ -852,7 +855,7 @@ function check_for_block_script_files() {
__FUNCTION__,
'info',
// translators: %s is the block name.
sprintf( __( 'Found file %s.', 'wporg-plugins' ), '<code>' . $script . '</code>' ),
sprintf( __( 'Found file %s.', 'wporg-plugins' ), '<code>' . esc_html( $script ) . '</code>' ),
compact( 'kind', 'script' )
);
} else {
Expand All @@ -868,7 +871,7 @@ function check_for_block_script_files() {
$this->record_result(
__FUNCTION__,
'error',
sprintf( $message, '<code>' . $script . '</code>' ),
sprintf( $message, '<code>' . esc_html( $script ) . '</code>' ),
compact( 'kind', 'script' )
);
}
Expand Down Expand Up @@ -912,8 +915,8 @@ function check_block_json_is_valid_json() {
sprintf(
// translators: %1$s is the file name, %2$s is the json error message.
__( 'Error attempting to parse json in %1$s: %2$s', 'wporg-plugins' ),
'<code><a href="' . $this->get_browser_url( $block_json_file ) . '">' . $this->relative_filename( $block_json_file ) . '</a></code>',
$message
'<code><a href="' . esc_url( $this->get_browser_url( $block_json_file ) ) . '">' . esc_html( $this->relative_filename( $block_json_file ) ) . '</a></code>',
esc_html( $message )
),
$this->relative_filename( $block_json_file )
);
Expand All @@ -932,7 +935,7 @@ function check_block_json_is_valid() {
__FUNCTION__,
'info',
// translators: %s is the file name.
sprintf( __( 'JSON file %s is valid.', 'wporg-plugins' ), '<code>' . $this->relative_filename( $block_json_file ) . '</code>' ),
sprintf( __( 'JSON file %s is valid.', 'wporg-plugins' ), '<code>' . esc_html( $this->relative_filename( $block_json_file ) ) . '</code>' ),
$this->relative_filename( $block_json_file )
);
continue;
Expand All @@ -947,7 +950,7 @@ function check_block_json_is_valid() {
$this->record_result(
__FUNCTION__,
( 'error' === $code ? 'warning' : $code ), // TODO: be smarter about mapping these
'<code><a href="' . $this->get_browser_url( $block_json_file ) . '">' . $this->relative_filename( $block_json_file ) . '</a></code>: ' . $message,
'<code><a href="' . esc_url( $this->get_browser_url( $block_json_file ) ) . '">' . esc_html( $this->relative_filename( $block_json_file ) ) . '</a></code>: ' . wp_kses( $message, array( 'code' => array() ) ),
array(
$this->relative_filename( $block_json_file ),
$result->get_error_data( $code ),
Expand Down Expand Up @@ -1073,7 +1076,7 @@ function check_php_function_calls() {
sprintf(
// translators: %s is the function name.
__( 'Found PHP call %s. This may cause problems.', 'wporg-plugins' ),
'<a href="' . $this->get_browser_url( $call[2] ) . '#L' . $call[1] . '"><code>' . $call[0] . '()</code></a>'
'<a href="' . esc_url( $this->get_browser_url( $call[2] ) . '#L' . $call[1] ) . '"><code>' . esc_html( $call[0] ) . '()</code></a>'
),
$call
);
Expand All @@ -1084,7 +1087,7 @@ function check_php_function_calls() {
sprintf(
// translators: %s is the function name.
__( 'Found PHP call %s. This is likely to prevent your plugin from working as expected.', 'wporg-plugins' ),
'<a href="' . $this->get_browser_url( $call[2] ) . '#L' . $call[1] . '"><code>' . $call[0] . '()</code></a>'
'<a href="' . esc_url( $this->get_browser_url( $call[2] ) . '#L' . $call[1] ) . '"><code>' . esc_html( $call[0] ) . '()</code></a>'
),
$call
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ protected function parse_readme_contents( $contents ) {
$headers['license'] = trim( str_replace( $url[1], '', $headers['license'] ), " -*\t\n\r\n()<>" );
}

$this->license = $headers['license'];
$this->license = $this->sanitize_text( $headers['license'] );
}
if ( ! empty( $headers['license_uri'] ) ) {
$this->license_uri = $headers['license_uri'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public function translate_code_to_message( $error_code, $data = false ) {
return sprintf(
/* translators: %s: list of tags not supported */
__( 'One or more tags were ignored. The following tags are not permitted: %s', 'wporg-plugins' ),
'<code>' . implode( '</code>, <code>', $data ) . '</code>'
'<code>' . implode( '</code>, <code>', array_map( 'esc_html', $data ) ) . '</code>'
);
case 'low_usage_tags':
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,20 @@ protected static function display_results( $checker ) {
$output .= "<p class='small'>{$labels['description']}</p>\n";
}
$output .= "<div class='notice notice-{$type} notice-alt'>\n";
foreach ( (array) $results_by_type[ $type ] as $item ) {
foreach ( (array) ( $results_by_type[ $type ] ?? array() ) as $item ) {
// Only get details if this is a warning or error.
$details = ( 'info' === $type ) ? false : self::get_detailed_help( $item->check_name, $item );
$message = self::sanitize_message( $item->message );
if ( $details ) {
$details = '<p>' . implode( '</p><p>', (array) $details ) . '</p>';
$output .= "<details class='{$item->check_name}'><summary>{$item->message}</summary>{$details}</details>";
$output .= "<details class='{$item->check_name}'><summary>{$message}</summary>{$details}</details>";
} else {
$output .= "<p>{$item->message}</p>";
$output .= "<p>{$message}</p>";
}
}
// Collapse block.json warnings into one details at the end of warnings list.
if ( 'error' === $type && ! empty( $block_json_issues ) ) {
$messages = wp_list_pluck( $block_json_issues, 'message' );
$messages = array_map( array( __CLASS__, 'sanitize_message' ), wp_list_pluck( $block_json_issues, 'message' ) );
$details = '<p>' . implode( '</p><p>', (array) $messages ) . '</p>';
$output .= sprintf(
'<details class="check_block_json_is_valid"><summary>%1$s</summary>%2$s</details>',
Expand All @@ -368,6 +369,32 @@ protected static function display_results( $checker ) {
echo $output;
}

/**
* Restrict a checker result message to the markup the checks actually use.
*
* Messages recorded by Block_Plugin_Checker intentionally contain a little
* markup, but they also interpolate plugin-controlled values such as readme
* headers, block names and file paths. Those values are escaped where they
* are interpolated; this is the backstop for anything that is missed.
*
* @param string $message A message recorded by Block_Plugin_Checker.
*
* @return string The message, safe to output as HTML.
*/
protected static function sanitize_message( $message ) {
return wp_kses(
$message,
array(
'a' => array( 'href' => true ),
'br' => array(),
'code' => array(),
'em' => array(),
'strong' => array(),
),
array( 'http', 'https' )
);
}

/**
* Get a more detailed help message for a given check.
*
Expand Down
Loading
Loading