Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Extensions Page (1st pass) #3188

Open
wants to merge 47 commits into
base: develop
Choose a base branch
from

Conversation

josephfusco
Copy link
Member

@josephfusco josephfusco commented Aug 12, 2024

This is the 1st pass at #304

Features

This page is registered as a submenu of WPGraphQL: /wp-admin/admin.php?page=wpgraphql-extensions

UI Scenarios

State Button Label Description
Uninstalled Install & Activate Indicates the plugin is not installed and can be installed and activated.
Installed Activate Indicates the plugin is installed but not active.
Active Active (subdued) Indicates the plugin is currently active.

Demo

extensions.page.mov

Key Points

  • Extensions/plugins cannot be deactivated or deleted on this screen by design.
  • Plugin data is hardcoded now with the intent to fetch from WordPress.org

Possible Additions

  • Add plugin thumbnail to card (similar to WordPress.org)
  • Allow new platforms to be registered (Bitbucket, Gitlab, etc)

TODO

  • Setup @wordpress/data in order to use @wordpress/notice store

@josephfusco josephfusco mentioned this pull request Aug 12, 2024
12 tasks
build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
</div>
<div className="action-links">
<ul className="plugin-action-buttons">
{host.includes('wordpress.org') && (

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
wordpress.org
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix AI 19 days ago

To fix the problem, we need to ensure that the host of the URL is explicitly checked against a whitelist of allowed hosts. This involves parsing the URL to extract the host and then comparing it against a predefined list of allowed hosts. This approach prevents malicious URLs from bypassing the check by embedding the allowed host in unexpected locations.

  1. Parse the URL to extract the host.
  2. Define a whitelist of allowed hosts.
  3. Check if the parsed host is in the whitelist.
  4. Update the relevant code to use this new check.
Suggested changeset 1
packages/extensions/PluginCard.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/extensions/PluginCard.js b/packages/extensions/PluginCard.js
--- a/packages/extensions/PluginCard.js
+++ b/packages/extensions/PluginCard.js
@@ -41,3 +41,5 @@
 
-    const host = new URL(plugin.plugin_url).host;
+    const url = new URL(plugin.plugin_url);
+    const host = url.host;
+    const allowedHosts = ['wordpress.org', 'github.com'];
     const { buttonText, buttonDisabled } = getButtonDetails(host, plugin.plugin_url, isInstalled, isActive, installing, activating);
@@ -71,3 +73,3 @@
                     <ul className="plugin-action-buttons">
-                        {host.includes('wordpress.org') && (
+                        {allowedHosts.includes(host) && host === 'wordpress.org' && (
                             <li>
@@ -84,3 +86,3 @@
                         )}
-                        {host.includes('github.com') && (
+                        {allowedHosts.includes(host) && host === 'github.com' && (
                             <li>
EOF
@@ -41,3 +41,5 @@

const host = new URL(plugin.plugin_url).host;
const url = new URL(plugin.plugin_url);
const host = url.host;
const allowedHosts = ['wordpress.org', 'github.com'];
const { buttonText, buttonDisabled } = getButtonDetails(host, plugin.plugin_url, isInstalled, isActive, installing, activating);
@@ -71,3 +73,3 @@
<ul className="plugin-action-buttons">
{host.includes('wordpress.org') && (
{allowedHosts.includes(host) && host === 'wordpress.org' && (
<li>
@@ -84,3 +86,3 @@
)}
{host.includes('github.com') && (
{allowedHosts.includes(host) && host === 'github.com' && (
<li>
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
@coveralls
Copy link

coveralls commented Aug 12, 2024

Coverage Status

coverage: 82.885% (-1.1%) from 83.99%
when pulling e411f2b on feat/3049-extensions-page-fix
into 61f5316 on develop.

</a>
</li>
)}
{plugin.support_url && (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

</button>
</li>
)}
{host.includes('github.com') && (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

const useInstallPlugin = (pluginUrl, pluginPath) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function useInstallPlugin has 78 lines of code (exceeds 40 allowed). Consider refactoring.

import useInstallPlugin from './useInstallPlugin';
import { getButtonDetails } from './utils';

const PluginCard = ({ plugin }) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function PluginCard has a Cognitive Complexity of 13 (exceeds 5 allowed). Consider refactoring.

import { __ } from '@wordpress/i18n';
import apiFetch from '@wordpress/api-fetch';

const useInstallPlugin = (pluginUrl, pluginPath) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function useInstallPlugin has a Cognitive Complexity of 21 (exceeds 5 allowed). Consider refactoring.

build/extensions.js Fixed Show fixed Hide fixed
build/extensions.js Fixed Show fixed Hide fixed
* @param {Function} activatePlugin - Function to activate the plugin.
* @returns {{buttonText: string, buttonDisabled: boolean, buttonOnClick: Function|null}} The button details.
*/
export const getButtonDetails = (host, plugin_url, isInstalled, isActive, installing, activating, activatePlugin) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function getButtonDetails has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.

* @param {Function} activatePlugin - Function to activate the plugin.
* @returns {{buttonText: string, buttonDisabled: boolean, buttonOnClick: Function|null}} The button details.
*/
export const getButtonDetails = (host, plugin_url, isInstalled, isActive, installing, activating, activatePlugin) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function getButtonDetails has 41 lines of code (exceeds 40 allowed). Consider refactoring.

import useInstallPlugin from './useInstallPlugin';
import { getButtonDetails } from './utils';

const PluginCard = ({ plugin }) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function PluginCard has a Cognitive Complexity of 15 (exceeds 5 allowed). Consider refactoring.

@@ -0,0 +1,357 @@
<?php
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File Extensions.php has 277 lines of code (exceeds 250 allowed). Consider refactoring.

*
* @return array<string, array<string, mixed>> List of extensions.
*/
public function get_extensions(): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function get_extensions has a Cognitive Complexity of 12 (exceeds 5 allowed). Consider refactoring.

@josephfusco josephfusco self-assigned this Sep 3, 2024
josephfusco and others added 2 commits September 4, 2024 14:28
}
);

return apply_filters( 'graphql_get_extensions', $this->extensions );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this method.

*
* @return array<string, array<string, mixed>> List of extensions.
*/
public function get_extensions(): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function get_extensions has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

…WPGraphQL.php

- docblock updates
- change Extensions to support 1 author instead of multiple authors
- introduce upgrade routine that runs if the previous version of the plugin is older than the current version of the plugin
- cache the extensions.json and invalidate the cache during plugin update routine
if ( 'WPGraphQL' !== $a['author']['name'] && 'WPGraphQL' === $b['author']['name'] ) {
return 1;
}
return strcasecmp( $a['name'], $b['name'] );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this method.

// Cache the extensions data
wp_cache_set( $extensions_cache_key, $data, $cache_group );

return $data;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this method.

return -1;
}
if ( 'WPGraphQL' !== $a['author']['name'] && 'WPGraphQL' === $b['author']['name'] ) {
return 1;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid too many return statements within this method.

*
* @return array<string, array<string, mixed>> List of extensions.
*/
public function get_extensions(): array {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method get_extensions has 41 lines of code (exceeds 40 allowed). Consider refactoring.

@@ -0,0 +1 @@
(()=>{"use strict";var e={n:t=>{var n=t&&t.__esModule?()=>t.default:()=>t;return e.d(n,{a:n}),n},d:(t,n)=>{for(var a in n)e.o(n,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:n[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.element,n=window.React,a=window.wp.i18n,l=window.wp.components,i=window.wp.apiFetch;var r=e.n(i);const s=({plugin:e})=>{const{installing:i,activating:s,status:o,error:p,installPlugin:c,activatePlugin:u}=((e,t)=>{const[l,i]=(0,n.useState)(!1),[s,o]=(0,n.useState)(!1),[p,c]=(0,n.useState)(""),[u,w]=(0,n.useState)(""),g=(e,t="")=>{c(e),w(t)},m=async(n=t)=>{if(o(!0),g((0,a.__)("Activating...","wp-graphql")),!n){let t=new URL(e).pathname.split("/").filter(Boolean).pop();n=`${t}/${t}.php`}try{const t=await r()({path:`/wp/v2/plugins/${n}`,method:"PUT",data:{status:"active"},headers:{"X-WP-Nonce":wpgraphqlExtensions.nonce}});if("active"===t.status)return g((0,a.__)("Active","wp-graphql")),window.wpgraphqlExtensions.extensions=window.wpgraphqlExtensions.extensions.map((t=>t.plugin_url===e?{...t,installed:!0,active:!0}:t)),!0;throw t.message.includes("Plugin file does not exist")?new Error((0,a.__)("Plugin file does not exist","wp-graphql")):new Error((0,a.__)("Activation failed","wp-graphql"))}catch(e){throw g((0,a.__)("Activation failed","wp-graphql"),e.message||(0,a.__)("Activation failed","wp-graphql")),e}finally{i(!1),o(!1)}};return{installing:l,activating:s,status:p,error:u,installPlugin:async()=>{i(!0),g((0,a.__)("Installing...","wp-graphql"));let n=new URL(e).pathname.split("/").filter(Boolean).pop();try{if("inactive"!==(await r()({path:"/wp/v2/plugins",method:"POST",data:{slug:n,status:"inactive"},headers:{"X-WP-Nonce":wpgraphqlExtensions.nonce}})).status)throw new Error((0,a.__)("Installation failed","wp-graphql"));await m(t)}catch(e){if(!e.message.includes("destination folder already exists"))throw g((0,a.__)("Installation failed","wp-graphql"),e.message||(0,a.__)("Installation failed","wp-graphql")),i(!1),e;await m(t)}},activatePlugin:m}})(e.plugin_url,e.plugin_path),[w,g]=(0,t.useState)(e.installed),[m,d]=(0,t.useState)(e.active),[h,_]=(0,t.useState)(!0);(0,t.useEffect)((()=>{g(e.installed),d(e.active)}),[e]);const E=new URL(e.plugin_url).host,{buttonText:b,buttonDisabled:f}=((e,t,n,l,i,r,s)=>{let o,p=!1,c=null;const u=e=>()=>window.open(e,"_blank");if(i)o=(0,a.__)("Installing...","wp-graphql"),p=!0;else if(r)o=(0,a.__)("Activating...","wp-graphql"),p=!0;else if(l)o=(0,a.__)("Active","wp-graphql"),p=!0;else if(n)o=(0,a.__)("Activate","wp-graphql"),c=s;else{const e=new URL(t).hostname.toLowerCase();switch(!0){case/github\.com$/.test(e):o=(0,a.__)("View on GitHub","wp-graphql"),c=u(t);break;case/bitbucket\.org$/.test(e):o=(0,a.__)("View on Bitbucket","wp-graphql"),c=u(t);break;case/gitlab\.com$/.test(e):o=(0,a.__)("View on GitLab","wp-graphql"),c=u(t);break;case/wordpress\.org$/.test(e):o=(0,a.__)("Install & Activate","wp-graphql"),c=s;break;default:o=(0,a.__)("View Plugin","wp-graphql"),c=u(t)}}return{buttonText:o,buttonDisabled:p,buttonOnClick:c}})(0,e.plugin_url,w,m,i,s);return(0,n.createElement)("div",{className:"plugin-card"},(0,n.createElement)("div",{className:"plugin-card-top"},(0,n.createElement)("div",{className:"name column-name"},(0,n.createElement)("h2",null,e.name),(0,n.createElement)((({author:e})=>e&&e.name&&e.homepage?(0,n.createElement)(n.Fragment,null,(0,n.createElement)("em",null,"By "),(0,n.createElement)("cite",{key:e.homepage},(0,n.createElement)("a",{href:e.homepage,target:"_blank",rel:"noopener noreferrer"},e.name))):null),{author:e.author}),e.experiment&&(0,n.createElement)("em",{className:"plugin-experimental"},"(experimental)")),(0,n.createElement)("div",{className:"action-links"},(0,n.createElement)("ul",{className:"plugin-action-buttons"},E.includes("wordpress.org")&&(0,n.createElement)("li",null,(0,n.createElement)("button",{type:"button",className:"button "+(m?"button-disabled":"button-primary"),disabled:f,onClick:async()=>{const t=w,n=m;try{w?(await u(e.plugin_path),d(!0)):(await c(),g(!0),d(!0))}catch(e){g(t),d(n)}finally{window.wpgraphqlExtensions.extensions=window.wpgraphqlExtensions.extensions.map((t=>t.plugin_url===e.plugin_url?{...t,installed:w,active:m}:t))}}},b,(i||s)&&(0,n.createElement)(l.Spinner,null))),E.includes("github.com")&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.plugin_url,target:"_blank",rel:"noopener noreferrer",className:"button button-secondary"},(0,a.__)("View on GitHub","wp-graphql"))),e.support_url&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.support_url,target:"_blank",rel:"noopener noreferrer",className:"thickbox open-plugin-details-modal"},(0,a.__)("Get Support","wp-graphql"))),e.settings_url&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.settings_url},(0,a.__)("Settings","wp-graphql"))))),(0,n.createElement)("div",{className:"desc column-description"},(0,n.createElement)("p",null,e.description))),p&&h&&(0,n.createElement)(l.Notice,{status:"error",isDismissible:!0,onRemove:()=>_(!1)},p))},o=()=>{const[e,t]=(0,n.useState)([]);return(0,n.useEffect)((()=>{window.wpgraphqlExtensions&&window.wpgraphqlExtensions.extensions&&t(window.wpgraphqlExtensions.extensions)}),[]),(0,n.createElement)("div",{className:"wp-clearfix"},(0,n.createElement)("div",{className:"plugin-cards"},e.map((e=>(0,n.createElement)(s,{key:e.plugin_url,plugin:e})))))};document.addEventListener("DOMContentLoaded",(()=>{const e=document.getElementById("wpgraphql-extensions");e&&(0,t.createRoot)(e).render((0,t.createElement)(o))}))})();

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
wordpress.org
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix AI 19 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

@@ -0,0 +1 @@
(()=>{"use strict";var e={n:t=>{var n=t&&t.__esModule?()=>t.default:()=>t;return e.d(n,{a:n}),n},d:(t,n)=>{for(var a in n)e.o(n,a)&&!e.o(t,a)&&Object.defineProperty(t,a,{enumerable:!0,get:n[a]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.element,n=window.React,a=window.wp.i18n,l=window.wp.components,i=window.wp.apiFetch;var r=e.n(i);const s=({plugin:e})=>{const{installing:i,activating:s,status:o,error:p,installPlugin:c,activatePlugin:u}=((e,t)=>{const[l,i]=(0,n.useState)(!1),[s,o]=(0,n.useState)(!1),[p,c]=(0,n.useState)(""),[u,w]=(0,n.useState)(""),g=(e,t="")=>{c(e),w(t)},m=async(n=t)=>{if(o(!0),g((0,a.__)("Activating...","wp-graphql")),!n){let t=new URL(e).pathname.split("/").filter(Boolean).pop();n=`${t}/${t}.php`}try{const t=await r()({path:`/wp/v2/plugins/${n}`,method:"PUT",data:{status:"active"},headers:{"X-WP-Nonce":wpgraphqlExtensions.nonce}});if("active"===t.status)return g((0,a.__)("Active","wp-graphql")),window.wpgraphqlExtensions.extensions=window.wpgraphqlExtensions.extensions.map((t=>t.plugin_url===e?{...t,installed:!0,active:!0}:t)),!0;throw t.message.includes("Plugin file does not exist")?new Error((0,a.__)("Plugin file does not exist","wp-graphql")):new Error((0,a.__)("Activation failed","wp-graphql"))}catch(e){throw g((0,a.__)("Activation failed","wp-graphql"),e.message||(0,a.__)("Activation failed","wp-graphql")),e}finally{i(!1),o(!1)}};return{installing:l,activating:s,status:p,error:u,installPlugin:async()=>{i(!0),g((0,a.__)("Installing...","wp-graphql"));let n=new URL(e).pathname.split("/").filter(Boolean).pop();try{if("inactive"!==(await r()({path:"/wp/v2/plugins",method:"POST",data:{slug:n,status:"inactive"},headers:{"X-WP-Nonce":wpgraphqlExtensions.nonce}})).status)throw new Error((0,a.__)("Installation failed","wp-graphql"));await m(t)}catch(e){if(!e.message.includes("destination folder already exists"))throw g((0,a.__)("Installation failed","wp-graphql"),e.message||(0,a.__)("Installation failed","wp-graphql")),i(!1),e;await m(t)}},activatePlugin:m}})(e.plugin_url,e.plugin_path),[w,g]=(0,t.useState)(e.installed),[m,d]=(0,t.useState)(e.active),[h,_]=(0,t.useState)(!0);(0,t.useEffect)((()=>{g(e.installed),d(e.active)}),[e]);const E=new URL(e.plugin_url).host,{buttonText:b,buttonDisabled:f}=((e,t,n,l,i,r,s)=>{let o,p=!1,c=null;const u=e=>()=>window.open(e,"_blank");if(i)o=(0,a.__)("Installing...","wp-graphql"),p=!0;else if(r)o=(0,a.__)("Activating...","wp-graphql"),p=!0;else if(l)o=(0,a.__)("Active","wp-graphql"),p=!0;else if(n)o=(0,a.__)("Activate","wp-graphql"),c=s;else{const e=new URL(t).hostname.toLowerCase();switch(!0){case/github\.com$/.test(e):o=(0,a.__)("View on GitHub","wp-graphql"),c=u(t);break;case/bitbucket\.org$/.test(e):o=(0,a.__)("View on Bitbucket","wp-graphql"),c=u(t);break;case/gitlab\.com$/.test(e):o=(0,a.__)("View on GitLab","wp-graphql"),c=u(t);break;case/wordpress\.org$/.test(e):o=(0,a.__)("Install & Activate","wp-graphql"),c=s;break;default:o=(0,a.__)("View Plugin","wp-graphql"),c=u(t)}}return{buttonText:o,buttonDisabled:p,buttonOnClick:c}})(0,e.plugin_url,w,m,i,s);return(0,n.createElement)("div",{className:"plugin-card"},(0,n.createElement)("div",{className:"plugin-card-top"},(0,n.createElement)("div",{className:"name column-name"},(0,n.createElement)("h2",null,e.name),(0,n.createElement)((({author:e})=>e&&e.name&&e.homepage?(0,n.createElement)(n.Fragment,null,(0,n.createElement)("em",null,"By "),(0,n.createElement)("cite",{key:e.homepage},(0,n.createElement)("a",{href:e.homepage,target:"_blank",rel:"noopener noreferrer"},e.name))):null),{author:e.author}),e.experiment&&(0,n.createElement)("em",{className:"plugin-experimental"},"(experimental)")),(0,n.createElement)("div",{className:"action-links"},(0,n.createElement)("ul",{className:"plugin-action-buttons"},E.includes("wordpress.org")&&(0,n.createElement)("li",null,(0,n.createElement)("button",{type:"button",className:"button "+(m?"button-disabled":"button-primary"),disabled:f,onClick:async()=>{const t=w,n=m;try{w?(await u(e.plugin_path),d(!0)):(await c(),g(!0),d(!0))}catch(e){g(t),d(n)}finally{window.wpgraphqlExtensions.extensions=window.wpgraphqlExtensions.extensions.map((t=>t.plugin_url===e.plugin_url?{...t,installed:w,active:m}:t))}}},b,(i||s)&&(0,n.createElement)(l.Spinner,null))),E.includes("github.com")&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.plugin_url,target:"_blank",rel:"noopener noreferrer",className:"button button-secondary"},(0,a.__)("View on GitHub","wp-graphql"))),e.support_url&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.support_url,target:"_blank",rel:"noopener noreferrer",className:"thickbox open-plugin-details-modal"},(0,a.__)("Get Support","wp-graphql"))),e.settings_url&&(0,n.createElement)("li",null,(0,n.createElement)("a",{href:e.settings_url},(0,a.__)("Settings","wp-graphql"))))),(0,n.createElement)("div",{className:"desc column-description"},(0,n.createElement)("p",null,e.description))),p&&h&&(0,n.createElement)(l.Notice,{status:"error",isDismissible:!0,onRemove:()=>_(!1)},p))},o=()=>{const[e,t]=(0,n.useState)([]);return(0,n.useEffect)((()=>{window.wpgraphqlExtensions&&window.wpgraphqlExtensions.extensions&&t(window.wpgraphqlExtensions.extensions)}),[]),(0,n.createElement)("div",{className:"wp-clearfix"},(0,n.createElement)("div",{className:"plugin-cards"},e.map((e=>(0,n.createElement)(s,{key:e.plugin_url,plugin:e})))))};document.addEventListener("DOMContentLoaded",(()=>{const e=document.getElementById("wpgraphql-extensions");e&&(0,t.createRoot)(e).render((0,t.createElement)(o))}))})();

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
github.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix AI 19 days ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

jasonbahl and others added 17 commits November 7, 2024 19:46
…ons page works

- add /packages/extensions/README.md file explaining how the extensions react app works
- added a `/schemas` directory with schemas for `extensions.json` and `single-extension.json` and a README.md explaining the schemas
…on adding back more one at a time following the new processes.
…s better. Provided documentation for how to filter extensions (modify / override the list of extensions)
- re-built the react app
refactor: validate extensions with PHP
feat: submit an extension criteria and validation
# Conflicts:
#	composer.lock
#	package-lock.json
Copy link

@codeclimate codeclimate bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR diff size of 10733 lines exceeds the maximum allowed for the inline comments feature.

Copy link

codeclimate bot commented Dec 17, 2024

Code Climate has analyzed commit e411f2b and detected 11 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 9
Duplication 2

View more on Code Climate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants