-
Notifications
You must be signed in to change notification settings - Fork 446
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
base: develop
Are you sure you want to change the base?
Conversation
This reverts commit 33fdf01.
</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
Show autofix suggestion
Hide autofix suggestion
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.
- Parse the URL to extract the host.
- Define a whitelist of allowed hosts.
- Check if the parsed host is in the whitelist.
- Update the relevant code to use this new check.
-
Copy modified lines R42-R44 -
Copy modified line R74 -
Copy modified line R87
@@ -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> |
</a> | ||
</li> | ||
)} | ||
{plugin.support_url && ( |
There was a problem hiding this comment.
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') && ( |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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 }) => { |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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.
* @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) => { |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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 }) => { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
src/Admin/Extensions/Extensions.php
Outdated
* | ||
* @return array<string, array<string, mixed>> List of extensions. | ||
*/ | ||
public function get_extensions(): array { |
There was a problem hiding this comment.
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.
- load extensions from .json file
src/Admin/Extensions/Extensions.php
Outdated
} | ||
); | ||
|
||
return apply_filters( 'graphql_get_extensions', $this->extensions ); |
There was a problem hiding this comment.
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.
src/Admin/Extensions/Extensions.php
Outdated
* | ||
* @return array<string, array<string, mixed>> List of extensions. | ||
*/ | ||
public function get_extensions(): array { |
There was a problem hiding this comment.
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'] ); |
There was a problem hiding this comment.
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.
src/Admin/Extensions/Extensions.php
Outdated
// Cache the extensions data | ||
wp_cache_set( $extensions_cache_key, $data, $cache_group ); | ||
|
||
return $data; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
src/Admin/Extensions/Extensions.php
Outdated
* | ||
* @return array<string, array<string, mixed>> List of extensions. | ||
*/ | ||
public function get_extensions(): array { |
There was a problem hiding this comment.
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
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
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.
…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
There was a problem hiding this 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.
Code Climate has analyzed commit e411f2b and detected 11 issues on this pull request. Here's the issue category breakdown:
View more on Code Climate. |
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
Demo
extensions.page.mov
Key Points
Possible Additions
TODO