From b1bc85e6d6f858f1059ebde1b993de861c1b676d Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 10:59:13 +0530 Subject: [PATCH 01/18] feature: pull requests integration --- assets/src/admin/pull-requests/index.js | 589 ++++++++++++++++++ assets/src/components/icons/Close.js | 7 + assets/src/components/icons/Merge.js | 5 + assets/src/components/icons/View.js | 7 + assets/src/css/admin.scss | 1 + composer.json | 2 +- inc/classes/class-assets.php | 39 +- inc/classes/class-cache.php | 2 +- inc/classes/class-hooks.php | 10 +- inc/classes/class-rest.php | 3 +- inc/classes/class-s3-upload.php | 10 +- inc/classes/class-utils.php | 116 ++++ .../plugin-configs/class-constants.php | 126 ++++ inc/classes/plugin-configs/class-db.php | 2 +- .../plugin-configs/class-secret-key.php | 10 +- .../class-vip-plugin-activation.php | 2 +- inc/classes/rest/class-basic-options.php | 21 +- .../rest/class-github-pull-requests.php | 461 ++++++++++++++ inc/classes/rest/class-s3.php | 65 +- inc/classes/rest/class-workflow.php | 23 +- inc/classes/settings/class-shared-sites.php | 35 +- inc/helpers/custom-functions.php | 7 +- oneupdate.php | 2 +- package.json | 2 +- uninstall.php | 2 - webpack.config.js | 1 + 26 files changed, 1442 insertions(+), 108 deletions(-) create mode 100644 assets/src/admin/pull-requests/index.js create mode 100644 assets/src/components/icons/Close.js create mode 100644 assets/src/components/icons/Merge.js create mode 100644 assets/src/components/icons/View.js create mode 100644 inc/classes/class-utils.php create mode 100644 inc/classes/plugin-configs/class-constants.php create mode 100644 inc/classes/rest/class-github-pull-requests.php diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js new file mode 100644 index 0000000..21a22be --- /dev/null +++ b/assets/src/admin/pull-requests/index.js @@ -0,0 +1,589 @@ +import { useState, useEffect, useCallback, createRoot } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { + Card, + CardHeader, + CardBody, + Button, + SelectControl, + TextControl, + Modal, + Spinner, + DropdownMenu, + __experimentalGrid as Grid, // eslint-disable-line @wordpress/no-unsafe-wp-apis + Snackbar, + MenuGroup, + MenuItem, +} from '@wordpress/components'; +import { decodeEntities } from '@wordpress/html-entities'; +import { moreVertical } from '@wordpress/icons'; +import CloseIcon from '../../components/icons/Close'; +import MergeIcon from '../../components/icons/Merge'; +import ViewIcon from '../../components/icons/View'; + +const API_NAMESPACE = OneUpdatePullRequests.restUrl + '/oneupdate/v1/github'; +const NONCE = OneUpdatePullRequests.restNonce; +const REPOS = OneUpdatePullRequests.repos; + +const PER_PAGE = 25; + +const GitHubPullRequests = () => { + const [ pullRequests, setPullRequests ] = useState( [] ); + const [ loading, setLoading ] = useState( false ); + const [ notice, setNotice ] = useState( null ); + const [ selectedRepo, setSelectedRepo ] = useState( Object.keys( REPOS )?.[ 0 ] || '' ); + const [ statusFilter, setStatusFilter ] = useState( 'all' ); + const [ searchQuery, setSearchQuery ] = useState( '' ); + const [ page, setPage ] = useState( 1 ); + const [ totalPages, setTotalPages ] = useState( 1 ); + const [ selectedPR, setSelectedPR ] = useState( null ); + const [ isDetailModalOpen, setIsDetailModalOpen ] = useState( false ); + const [ prDetails, setPrDetails ] = useState( null ); + const [ detailsLoading, setDetailsLoading ] = useState( false ); + const [ currentPage, setCurrentPage ] = useState( 1 ); + + // Repo options for SelectControl + const repoOptions = Object.entries( REPOS ).map( ( [ key, value ] ) => ( { + label: `${ value } (${ key })`, + value: key, + } ) ); + + // Status filter options + const statusOptions = [ + { label: __( 'All Status', 'oneupdate' ), value: 'all' }, + { label: __( 'Open', 'oneupdate' ), value: 'open' }, + { label: __( 'Merged/Closed', 'oneupdate' ), value: 'closed' }, + ]; + + const fetchPullRequests = useCallback( async () => { + if ( ! selectedRepo ) { + return; + } + + setLoading( true ); + setNotice( null ); + + try { + const params = new URLSearchParams( { + per_page: PER_PAGE.toString(), + page: page.toString(), + state: statusFilter, + } ); + + if ( searchQuery.trim() ) { + params.append( 'search_query', searchQuery.trim() ); + } + + const response = await fetch( + `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?${ params.toString() }`, + { + headers: { + 'Content-Type': 'application/json', + 'X-WP-NONCE': NONCE, + }, + }, + ); + + if ( ! response.ok ) { + throw new Error( 'Failed to fetch pull requests' ); + } + + const data = await response.json(); + + if ( data.success ) { + setPullRequests( data.pull_requests || [] ); + // Calculate total pages based on response (you might need to adjust based on your API) + const totalCount = response.headers.get( 'X-WP-Total' ) || data.pull_requests.length; + setTotalPages( Math.ceil( totalCount / PER_PAGE ) ); + setCurrentPage( data?.pagination?.current_page || 1 ); + } else { + throw new Error( data.message || 'Failed to fetch pull requests' ); + } + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message || __( 'Error fetching pull requests.', 'oneupdate' ), + } ); + setPullRequests( [] ); + } finally { + setLoading( false ); + } + }, [ selectedRepo, statusFilter, searchQuery, page ] ); + + const fetchPRDetails = useCallback( async ( prNumber ) => { + if ( ! selectedRepo || ! prNumber ) { + return; + } + + setDetailsLoading( true ); + try { + const response = await fetch( + `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?pr_number=${ prNumber }`, + { + headers: { + 'Content-Type': 'application/json', + 'X-WP-NONCE': NONCE, + }, + }, + ); + + if ( ! response.ok ) { + throw new Error( 'Failed to fetch PR details' ); + } + + const data = await response.json(); + + if ( data.success && data.pull_request?.[ 0 ] ) { + setPrDetails( data.pull_request[ 0 ] ); + } else { + throw new Error( 'Failed to fetch PR details' ); + } + } catch ( error ) { + setNotice( { + type: 'error', + message: error.message || __( 'Error fetching PR details.', 'oneupdate' ), + } ); + } finally { + setDetailsLoading( false ); + } + }, [ selectedRepo ] ); + + const openDetailModal = ( pr ) => { + setSelectedPR( pr ); + setIsDetailModalOpen( true ); + if ( pr.state !== 'open' ) { + fetchPRDetails( pr.number ); + } + }; + + const closeModals = () => { + setIsDetailModalOpen( false ); + setSelectedPR( null ); + setPrDetails( null ); + }; + + const formatDate = ( dateString ) => { + if ( ! dateString ) { + return __( 'N/A', 'oneupdate' ); + } + return new Date( dateString ).toLocaleDateString( undefined, { + year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', + } ); + }; + + const getPRStatusBadge = ( pr ) => { + let status = pr.state; + let color = '#6b7280'; + + if ( pr.merged_at ) { + status = 'merged'; + color = '#7c3aed'; + } else if ( pr.state === 'open' ) { + color = '#059669'; + } else if ( pr.state === 'closed' ) { + color = '#dc2626'; + } + + return ( + + { status } + + ); + }; + + const renderPRActions = ( pr ) => { + return ( + + { ( { onClose } ) => { + return ( + <> + + { pr.state === 'open' && ( + <> + } + onClick={ () => { + openDetailModal( pr ); + onClose(); + } } + > + { __( 'View PR Details', 'oneupdate' ) } + + } + onClick={ () => { + // take user to github to merge + window.open( `${ pr.html_url }#:~:text=Merge%20pull%20request`, '_blank' ); + onClose(); + } } + > + { __( 'Merge PR', 'oneupdate' ) } + + } + onClick={ () => { + // take user to github to close + window.open( `${ pr.html_url }#:~:text=Close%20pull%20request`, '_blank' ); + onClose(); + } } + > + { __( 'Close PR', 'oneupdate' ) } + + + ) } + + { pr.state !== 'open' && ( + + } + onClick={ () => { + openDetailModal( pr ); + onClose(); + } } + > + { __( 'View PR Details', 'oneupdate' ) } + + + ) } + + ); + } } + + ); + }; + + // Reset page when filters change + useEffect( () => { + setPage( 1 ); + }, [ selectedRepo, statusFilter, searchQuery ] ); + + // Fetch PRs when dependencies change + useEffect( () => { + fetchPullRequests(); + }, [ fetchPullRequests ] ); + + return ( + <> + + +

{ __( 'GitHub Pull Requests', 'oneupdate' ) }

+
+ + { /* Filters */ } + + + <> + + + + + + { /* PR Table */ } + + + + + + + + + + + + + + { loading && ( + + + + ) } + { ! loading && pullRequests.length === 0 && ( + + + + ) } + { pullRequests.map( ( pr ) => ( + + + + + + + + + + ) ) } + +
{ __( 'PR #', 'oneupdate' ) }{ __( 'Title', 'oneupdate' ) }{ __( 'Author', 'oneupdate' ) }{ __( 'Status', 'oneupdate' ) }{ __( 'Created at', 'oneupdate' ) }{ __( 'Labels', 'oneupdate' ) }{ __( 'Actions', 'oneupdate' ) }
+ +
+ { __( 'No pull requests found.', 'oneupdate' ) } +
+ + #{ pr.number } + + + { decodeEntities( pr.title ) } + +
+ { + { pr.user.login } +
+
{ getPRStatusBadge( pr ) }{ formatDate( pr.created_at ) } + { pr.labels.length > 0 ? ( +
+ { pr.labels.slice( 0, 2 ).map( ( label ) => ( + + { label.name } + + ) ) } + { pr.labels.length > 2 && ( + + +{ pr.labels.length - 2 } { __( 'more', 'oneupdate' ) } + + ) } +
+ ) : ( + + { __( 'No labels', 'oneupdate' ) } + + ) } +
{ renderPRActions( pr ) }
+ + { /* Pagination */ } + { ( +
+ + + { __( 'Page', 'oneupdate' ) } { page } { __( 'of', 'oneupdate' ) } { totalPages === 0 ? currentPage : totalPages } + + +
+ ) } +
+
+ + { /* PR Details Modal */ } + { isDetailModalOpen && selectedPR && ( + +
+
+
+

{ __( 'PR Number:', 'oneupdate' ) } #{ selectedPR.number }

+

{ __( 'Title:', 'oneupdate' ) } { decodeEntities( selectedPR.title ) }

+

{ __( 'Author:', 'oneupdate' ) } { selectedPR.user.login }

+

{ __( 'Status:', 'oneupdate' ) } { getPRStatusBadge( selectedPR ) }

+
+
+

{ __( 'Created:', 'oneupdate' ) } { formatDate( selectedPR.created_at ) }

+

{ __( 'Updated:', 'oneupdate' ) } { formatDate( selectedPR.updated_at ) }

+

{ __( 'Branch:', 'oneupdate' ) } { selectedPR.pr_branch } → { selectedPR.base_branch }

+

+ { __( 'GitHub URL:', 'oneupdate' ) }{ ' ' } + + { __( 'View on GitHub', 'oneupdate' ) } + +

+
+
+ + { /* Labels */ } + { selectedPR.labels.length > 0 && ( +
+ { __( 'Labels:', 'oneupdate' ) } +
+ { selectedPR.labels.map( ( label ) => ( + + { label.name } + + ) ) } +
+
+ ) } + + { /* Description */ } + { selectedPR.body && ( +
+ { __( 'Description:', 'oneupdate' ) } +
+
+										{ decodeEntities( selectedPR.body ) }
+									
+
+
+ ) } +
+ + { /* Detailed PR Info */ } + { detailsLoading && ( +
+ +

{ __( 'Loading extra details…', 'oneupdate' ) }

+
+ ) } + + { prDetails && ( +
+ + { prDetails.merged_by && ( +
+ { __( 'Merged By:', 'oneupdate' ) } + { + { prDetails.merged_by.login } +
+ ) } +
+ ) } + + { /* Action Buttons */ } + { selectedPR.state === 'open' && ( +
+ + +
+ ) } +
+ ) } + + { /* Notice Snackbar */ } + { notice?.message && ( + setNotice( null ) } + className={ notice?.type === 'error' ? 'oneupdate-error-notice' : 'oneupdate-success-notice' } + > + { notice.message } + + ) } + + ); +}; + +// Render to Gutenberg admin page with ID: oneupdate-pull-requests +const target = document.getElementById( 'oneupdate-pull-requests' ); +if ( target ) { + const root = createRoot( target ); + root.render( ); +} diff --git a/assets/src/components/icons/Close.js b/assets/src/components/icons/Close.js new file mode 100644 index 0000000..41402e0 --- /dev/null +++ b/assets/src/components/icons/Close.js @@ -0,0 +1,7 @@ +export default function CloseIcon() { + return ( + + + + ); +} diff --git a/assets/src/components/icons/Merge.js b/assets/src/components/icons/Merge.js new file mode 100644 index 0000000..92295b0 --- /dev/null +++ b/assets/src/components/icons/Merge.js @@ -0,0 +1,5 @@ +export default function MergeIcon() { + return ( + + ); +} diff --git a/assets/src/components/icons/View.js b/assets/src/components/icons/View.js new file mode 100644 index 0000000..e4515de --- /dev/null +++ b/assets/src/components/icons/View.js @@ -0,0 +1,7 @@ +export default function ViewIcon() { + return ( + + + + ); +} diff --git a/assets/src/css/admin.scss b/assets/src/css/admin.scss index 48532fd..9f8c696 100644 --- a/assets/src/css/admin.scss +++ b/assets/src/css/admin.scss @@ -22,6 +22,7 @@ } } +#oneupdate-pull-requests, #oneupdate-settings-page, #oneupdate-plugin-manager { diff --git a/composer.json b/composer.json index e505e20..5643836 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "rtcamp/oneupdate", - "version": "1.0.1", + "version": "1.0.2", "description": "OneUpdate - Enterprise WordPress Plugin Manager Automate plugin updates across multiple WordPress sites with CI/CD integration. Creates pull requests for seamless development-to-production workflows.", "type": "wordpress-plugin", "autoload": { diff --git a/inc/classes/class-assets.php b/inc/classes/class-assets.php index a31b12d..4c7b491 100644 --- a/inc/classes/class-assets.php +++ b/inc/classes/class-assets.php @@ -7,6 +7,7 @@ namespace OneUpdate; +use OneUpdate\Plugin_Configs\Constants; use OneUpdate\Traits\Singleton; /** @@ -56,7 +57,7 @@ public function enqueue_admin_scripts( $hook_suffix ) { array( 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), - 'apiKey' => get_option( 'oneupdate_child_site_api_key', 'default_api_key' ), + 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'setupUrl' => admin_url( 'admin.php?page=oneupdate-settings' ), @@ -83,7 +84,7 @@ public function enqueue_admin_scripts( $hook_suffix ) { array( 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), - 'apiKey' => get_option( 'oneupdate_child_site_api_key', 'default_api_key' ), + 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), ) ); @@ -111,7 +112,7 @@ public function enqueue_admin_scripts( $hook_suffix ) { array( 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), - 'apiKey' => get_option( 'oneupdate_child_site_api_key', 'default_api_key' ), + 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'setupUrl' => admin_url( 'admin.php?page=oneupdate-settings' ), @@ -122,6 +123,38 @@ public function enqueue_admin_scripts( $hook_suffix ) { } + if ( strpos( $hook_suffix, 'oneupdate-pull-requests' ) !== false ) { + remove_all_actions( 'admin_notices' ); + $this->register_script( + 'oneupdate-pull-requests-script', + 'js/pull-requests.js', + ); + + $site_name_gh_repo = array(); + $oneupdate_sites = $GLOBALS['oneupdate_sites'] ?? array(); + if ( ! empty( $oneupdate_sites ) && is_array( $oneupdate_sites ) ) { + foreach ( $oneupdate_sites as $site ) { + if ( ! empty( $site['siteName'] ) && ! empty( $site['gh_repo'] ) && in_array( $site['gh_repo'], $site_name_gh_repo, true ) === false ) { + $site_name_gh_repo[ $site['gh_repo'] ] = $site['siteName']; + } + } + } + + wp_localize_script( + 'oneupdate-pull-requests-script', + 'OneUpdatePullRequests', + array( + 'restUrl' => esc_url( home_url( '/wp-json' ) ), + 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), + 'restNonce' => wp_create_nonce( 'wp_rest' ), + 'repos' => $site_name_gh_repo, + ) + ); + + wp_enqueue_script( 'oneupdate-pull-requests-script' ); + + } + // load admin styles. $this->register_style( 'oneupdate-admin-style', 'css/admin.css' ); wp_enqueue_style( 'oneupdate-admin-style' ); diff --git a/inc/classes/class-cache.php b/inc/classes/class-cache.php index 10343fd..a955aa0 100644 --- a/inc/classes/class-cache.php +++ b/inc/classes/class-cache.php @@ -34,7 +34,7 @@ protected function __construct() { public function setup_hooks(): void { // if current site type is governing site then do not run the cache hooks. - if ( 'governing-site' === get_option( 'oneupdate_site_type', '' ) ) { + if ( Utils::is_governing_site() ) { return; } diff --git a/inc/classes/class-hooks.php b/inc/classes/class-hooks.php index da5b768..3e592d3 100644 --- a/inc/classes/class-hooks.php +++ b/inc/classes/class-hooks.php @@ -7,6 +7,7 @@ namespace OneUpdate; +use OneUpdate\Plugin_Configs\Constants; use OneUpdate\Traits\Singleton; /** @@ -59,7 +60,7 @@ public function add_body_class_for_modal( $classes ): string { } // get oneupdate_site_type_transient transient to check if site type is set. - $site_type_transient = get_transient( 'oneupdate_site_type_transient' ); + $site_type_transient = get_transient( Constants::ONEUPDATE_SITE_TYPE_TRANSIENT ); if ( $site_type_transient ) { // If site type is already set, do not show the modal. return $classes; @@ -85,7 +86,7 @@ public function add_site_selection_modal(): void { } // get oneupdate_site_type_transient transient to check if site type is set. - $site_type_transient = get_transient( 'oneupdate_site_type_transient' ); + $site_type_transient = get_transient( Constants::ONEUPDATE_SITE_TYPE_TRANSIENT ); if ( $site_type_transient ) { // If site type is already set, do not show the modal. return; @@ -108,7 +109,7 @@ public function create_global_oneupdate_sites(): void { return; } - $sites = get_option( 'oneupdate_shared_sites', array() ); + $sites = get_option( Constants::ONEUPDATE_SHARED_SITES, array() ); if ( ! empty( $sites ) && is_array( $sites ) ) { $oneupdate_sites = array(); @@ -157,7 +158,7 @@ public function add_body_class_for_missing_sites( $classes ): string { } // get oneupdate_shared_sites option. - $shared_sites = get_option( 'oneupdate_shared_sites', array() ); + $shared_sites = get_option( Constants::ONEUPDATE_SHARED_SITES, array() ); // if shared_sites is empty or not an array, return the classes. if ( empty( $shared_sites ) || ! is_array( $shared_sites ) ) { @@ -165,6 +166,7 @@ public function add_body_class_for_missing_sites( $classes ): string { // remove plugin manager submenu. remove_submenu_page( 'oneupdate', 'oneupdate' ); + remove_submenu_page( 'oneupdate', 'oneupdate-pull-requests' ); return $classes; } diff --git a/inc/classes/class-rest.php b/inc/classes/class-rest.php index f07ef50..c398376 100644 --- a/inc/classes/class-rest.php +++ b/inc/classes/class-rest.php @@ -7,7 +7,7 @@ namespace OneUpdate; -use OneUpdate\REST\{ Basic_Options, S3, Workflow }; +use OneUpdate\REST\{ Basic_Options, S3, Workflow, GitHub_Pull_Requests }; use OneUpdate\Traits\Singleton; /** @@ -36,6 +36,7 @@ public function setup_hooks(): void { Basic_Options::get_instance(); Workflow::get_instance(); S3::get_instance(); + GitHub_Pull_Requests::get_instance(); // fix cors headers for REST API requests. add_filter( 'rest_pre_serve_request', array( $this, 'add_cors_headers' ), PHP_INT_MAX - 20, 4 ); diff --git a/inc/classes/class-s3-upload.php b/inc/classes/class-s3-upload.php index c2c9518..3df85ea 100644 --- a/inc/classes/class-s3-upload.php +++ b/inc/classes/class-s3-upload.php @@ -9,7 +9,7 @@ use OneUpdate\Traits\Singleton; use Aws\Exception\AwsException; - +use OneUpdate\Plugin_Configs\Constants; use OneUpdate\REST\S3; /** @@ -46,11 +46,11 @@ public function setup_hooks(): void { */ // phpcs:disable -- its custom query to cleanup s3 bucket. public function oneupdate_s3_zip_cleanup_event(): void { - $s3_credentials = get_option( 'oneupdate_s3_credentials' ); + $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); global $wpdb; - $table_name = $wpdb->prefix . 'oneupdate_s3_zip_history'; - $s3 = S3::get_s3_instance(); + $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; + $s3 = Utils::get_s3_instance(); $one_hour_ago = gmdate( 'Y-m-d H:i:s', current_time( 'timestamp', 1 ) - 3600 ); $expired_files = $wpdb->get_results( @@ -96,7 +96,7 @@ public function oneupdate_s3_zip_cleanup_event(): void { // phpcs:disable -- its custom query to cleanup s3 zip history. public function oneupdate_s3_zip_history_cleanup_event(): void { global $wpdb; - $table_name = $wpdb->prefix . 'oneupdate_s3_zip_history'; + $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; $one_week_ago = date( 'Y-m-d H:i:s', strtotime( '-1 week' ) ); $batch_size = 1000; $sleep_seconds = 2; diff --git a/inc/classes/class-utils.php b/inc/classes/class-utils.php new file mode 100644 index 0000000..78b311e --- /dev/null +++ b/inc/classes/class-utils.php @@ -0,0 +1,116 @@ +setup_hooks(); + } + + /** + * Setup WordPress hooks + */ + public function setup_hooks(): void { + } + + /** + * Get current site type. + * + * @return string + */ + public static function get_site_type(): string { + $site_type = get_option( Constants::ONEUPDATE_SITE_TYPE, '' ); + return $site_type; + } + + /** + * Is brand site. + * + * @return bool + */ + public static function is_brand_site(): bool { + return 'brand-site' === self::get_site_type(); + } + + /** + * Is governing site. + * + * @return bool + */ + public static function is_governing_site(): bool { + return 'governing-site' === self::get_site_type(); + } + + /** + * Get S3 instance. + * + * @return S3Client + */ + public static function get_s3_instance(): S3Client { + $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); + if ( empty( $s3_credentials ) || ! is_array( $s3_credentials ) ) { + return new S3Client( array() ); // Return an empty S3Client. + } + $s3 = new S3Client( + array( + 'version' => 'latest', + 'region' => $s3_credentials['region'] ?? '', + 'credentials' => array( + 'key' => $s3_credentials['accessKey'] ?? '', + 'secret' => $s3_credentials['secretKey'] ?? '', + ), + 'use_accelerate_endpoint' => true, + ) + ); + + // first check if the bucket has getBucketAccelerateConfiguration. + + try { + $accelerate_config = $s3->getBucketAccelerateConfiguration( + array( + 'Bucket' => $s3_credentials['bucketName'] ?? '', + ) + ); + if ( ! empty( $accelerate_config['Status'] ) && 'Enabled' === $accelerate_config['Status'] ) { + return $s3; + } + } catch ( AwsException $e ) { + $s3 = new S3Client( + array( + 'version' => 'latest', + 'region' => $s3_credentials['region'] ?? '', + 'endpoint' => $s3_credentials['endpoint'] ?? '', + 'credentials' => array( + 'key' => $s3_credentials['accessKey'] ?? '', + 'secret' => $s3_credentials['secretKey'] ?? '', + ), + 'use_path_style_endpoint' => true, // use path style endpoint. + ) + ); + } + + return $s3; + } +} diff --git a/inc/classes/plugin-configs/class-constants.php b/inc/classes/plugin-configs/class-constants.php new file mode 100644 index 0000000..a03c61b --- /dev/null +++ b/inc/classes/plugin-configs/class-constants.php @@ -0,0 +1,126 @@ +define_constants(); + } + + /** + * Define plugin constants + */ + private function define_constants(): void { + // future constants can be defined here. + } +} diff --git a/inc/classes/plugin-configs/class-db.php b/inc/classes/plugin-configs/class-db.php index 8cfd774..ca33fe3 100644 --- a/inc/classes/plugin-configs/class-db.php +++ b/inc/classes/plugin-configs/class-db.php @@ -32,7 +32,7 @@ protected function __construct() { */ public static function create_oneupdate_s3_zip_history_table(): void { global $wpdb; - $table_name = $wpdb->prefix . 'oneupdate_s3_zip_history'; + $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS $table_name ( diff --git a/inc/classes/plugin-configs/class-secret-key.php b/inc/classes/plugin-configs/class-secret-key.php index 4ae2c50..5011864 100644 --- a/inc/classes/plugin-configs/class-secret-key.php +++ b/inc/classes/plugin-configs/class-secret-key.php @@ -38,11 +38,11 @@ public function setup_hooks(): void { * @return void */ public function generate_secret_key(): void { - $secret_key = get_option( 'oneupdate_child_site_api_key' ); + $secret_key = get_option( Constants::ONEUPDATE_API_KEY ); if ( empty( $secret_key ) ) { $secret_key = wp_generate_password( 128, false, false ); // Store the secret key in the database. - update_option( 'oneupdate_child_site_api_key', $secret_key ); + update_option( Constants::ONEUPDATE_API_KEY, $secret_key ); } } @@ -52,10 +52,10 @@ public function generate_secret_key(): void { * @return \WP_REST_Response| \WP_Error */ public static function get_secret_key(): \WP_REST_Response|\WP_Error { - $secret_key = get_option( 'oneupdate_child_site_api_key' ); + $secret_key = get_option( Constants::ONEUPDATE_API_KEY ); if ( empty( $secret_key ) ) { self::regenerate_secret_key(); - $secret_key = get_option( 'oneupdate_child_site_api_key' ); + $secret_key = get_option( Constants::ONEUPDATE_API_KEY ); } return rest_ensure_response( array( @@ -72,7 +72,7 @@ public static function get_secret_key(): \WP_REST_Response|\WP_Error { public static function regenerate_secret_key(): \WP_REST_Response|\WP_Error { $regenerated_key = wp_generate_password( 128, false, false ); // Update the option with the new key. - update_option( 'oneupdate_child_site_api_key', $regenerated_key ); + update_option( Constants::ONEUPDATE_API_KEY, $regenerated_key ); return rest_ensure_response( array( diff --git a/inc/classes/plugin-configs/class-vip-plugin-activation.php b/inc/classes/plugin-configs/class-vip-plugin-activation.php index 8c9cfe8..c4e3ead 100644 --- a/inc/classes/plugin-configs/class-vip-plugin-activation.php +++ b/inc/classes/plugin-configs/class-vip-plugin-activation.php @@ -45,7 +45,7 @@ public function activate_vip_plugins(): void { } // get oneupdate_plugins_options option. - $oneupdate_plugins_options = get_option( 'oneupdate_plugins_options', array() ); + $oneupdate_plugins_options = get_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, array() ); // activate all plugins that are in the oneupdate_plugins_options. if ( ! empty( $oneupdate_plugins_options ) ) { diff --git a/inc/classes/rest/class-basic-options.php b/inc/classes/rest/class-basic-options.php index ab3f757..05b8bed 100644 --- a/inc/classes/rest/class-basic-options.php +++ b/inc/classes/rest/class-basic-options.php @@ -7,6 +7,7 @@ namespace OneUpdate\REST; +use OneUpdate\Plugin_Configs\Constants; use OneUpdate\Traits\Singleton; use OneUpdate\Plugin_Configs\Secret_Key; use WP_REST_Server; @@ -248,7 +249,7 @@ public function health_check(): \WP_REST_Response|\WP_Error { */ public function get_github_repos(): \WP_REST_Response|\WP_Error { - $github_token = get_option( 'oneupdate_gh_token', '' ); + $github_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); if ( empty( $github_token ) ) { return new \WP_Error( 'no_github_token', __( 'GitHub token not found.', 'oneupdate' ), array( 'status' => 404 ) ); @@ -328,7 +329,7 @@ public function get_github_repos(): \WP_REST_Response|\WP_Error { */ public function get_site_type(): \WP_REST_Response|\WP_Error { - $site_type = get_option( 'oneupdate_site_type', '' ); + $site_type = get_option( Constants::ONEUPDATE_SITE_TYPE, '' ); return rest_ensure_response( array( @@ -348,10 +349,10 @@ public function set_site_type( \WP_REST_Request $request ): \WP_REST_Response|\W $site_type = sanitize_text_field( $request->get_param( 'site_type' ) ); - update_option( 'oneupdate_site_type', $site_type ); + update_option( Constants::ONEUPDATE_SITE_TYPE, $site_type ); // set transient to indicating that site type has been set for infinite time. - set_transient( 'oneupdate_site_type_transient', true, 0 ); + set_transient( Constants::ONEUPDATE_SITE_TYPE_TRANSIENT, true, 0 ); return rest_ensure_response( array( @@ -399,7 +400,7 @@ public function set_github_token( \WP_REST_Request $request ): \WP_REST_Response ); } - update_option( 'oneupdate_gh_token', $github_token ); + update_option( Constants::ONEUPDATE_GH_TOKEN, $github_token ); return rest_ensure_response( array( @@ -415,7 +416,7 @@ public function set_github_token( \WP_REST_Request $request ): \WP_REST_Response * @return \WP_REST_Response|\WP_Error */ public function get_s3_credentials(): \WP_REST_Response|\WP_Error { - $s3_credentials = get_option( 'oneupdate_s3_credentials' ); + $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); return rest_ensure_response( array( @@ -450,7 +451,7 @@ public function set_s3_credentials( \WP_REST_Request $request ): \WP_REST_Respon } // Update S3 credentials in options. - update_option( 'oneupdate_s3_credentials', $s3_credentials ); + update_option( Constants::ONEUPDATE_S3_CREDENTIALS, $s3_credentials ); return rest_ensure_response( array( @@ -466,7 +467,7 @@ public function set_s3_credentials( \WP_REST_Request $request ): \WP_REST_Respon * @return \WP_REST_Response|\WP_Error */ public function get_shared_sites(): \WP_REST_Response|\WP_Error { - $shared_sites = get_option( 'oneupdate_shared_sites', array() ); + $shared_sites = get_option( Constants::ONEUPDATE_SHARED_SITES, array() ); return rest_ensure_response( array( 'success' => true, @@ -506,7 +507,7 @@ public function set_shared_sites( \WP_REST_Request $request ): \WP_REST_Response $gtihub_repos[] = $site['githubRepo'] ?? ''; } - update_option( 'oneupdate_shared_sites', $sites_data ); + update_option( Constants::ONEUPDATE_SHARED_SITES, $sites_data ); return rest_ensure_response( array( @@ -522,7 +523,7 @@ public function set_shared_sites( \WP_REST_Request $request ): \WP_REST_Response * @return \WP_REST_Response|\WP_Error */ public function get_github_token(): \WP_REST_Response|\WP_Error { - $github_token = get_option( 'oneupdate_gh_token', '' ); + $github_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); return rest_ensure_response( array( diff --git a/inc/classes/rest/class-github-pull-requests.php b/inc/classes/rest/class-github-pull-requests.php new file mode 100644 index 0000000..1fcd559 --- /dev/null +++ b/inc/classes/rest/class-github-pull-requests.php @@ -0,0 +1,461 @@ +setup_hooks(); + } + + /** + * Setup WordPress hooks + * + * @return void + */ + public function setup_hooks(): void { + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + /** + * Register REST API routes. + * + * @return void + */ + public function register_routes(): void { + /** + * Register a route to get pull requests by pagination. + */ + register_rest_route( + self::NAMESPACE, + '/pull-requests/(?P[a-zA-Z0-9._-]+)/(?P[a-zA-Z0-9._-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_pull_requests' ), + 'permission_callback' => array( self::class, 'permission_callback' ), + 'args' => array( + 'owner' => array( + 'required' => true, + 'type' => 'string', + ), + 'repo' => array( + 'required' => true, + 'type' => 'string', + ), + 'pr_number' => array( + 'required' => false, + 'type' => 'integer', + ), + 'state' => array( + 'required' => false, + 'type' => 'string', + 'default' => 'all', + 'enum' => array( 'open', 'closed', 'all', 'merged' ), + ), + 'page' => array( + 'required' => false, + 'type' => 'integer', + 'default' => 1, + ), + 'per_page' => array( + 'required' => false, + 'type' => 'integer', + 'default' => 25, + 'maximum' => 100, + 'minimum' => 1, + ), + 'search_query' => array( + 'required' => false, + 'type' => 'string', + ), + ), + ), + ) + ); + } + + /** + * Permission callback for the routes. + * + * @return bool + */ + public static function permission_callback() { + return current_user_can( 'manage_options' ); + } + + /** + * Get pull requests by pagination. + * + * @param \WP_REST_Request $request The REST request. + * + * @return \WP_REST_Response + */ + public function get_pull_requests( \WP_REST_Request $request ): \WP_REST_Response { + $gh_owner = sanitize_text_field( $request['owner'] ); + $gh_repo = sanitize_text_field( $request['repo'] ); + $pr_number = filter_var( $request->get_param( 'pr_number' ), FILTER_VALIDATE_INT ) ?? 0; + $page = filter_var( $request->get_param( 'page' ), FILTER_VALIDATE_INT ) ?? 1; + $pr_state = sanitize_text_field( $request->get_param( 'state' ) ) ?? 'all'; + $per_page = filter_var( $request->get_param( 'per_page' ), FILTER_VALIDATE_INT ) ?? 25; + $search_query = sanitize_text_field( $request->get_param( 'search_query' ) ) ?? ''; + + $gh_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); + + if ( empty( $gh_token ) ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => 'GitHub token is not set. Please set it in the OneUpdate settings.', + ), + 400 + ); + } + + // if pr_number is not provided, get all pull requests. + if ( empty( $pr_number ) && empty( $search_query ) ) { + return self::get_all_pull_requests( $gh_owner, $gh_repo, $pr_state, $gh_token, $per_page, $page ); + } + + // if pr_number is not provided but search_query is provided, search pull requests. + if ( ! empty( $search_query ) ) { + return self::search_pull_requests( $gh_owner, $gh_repo, $search_query, $gh_token, $per_page, $page, $pr_state ); + } + + // if pr_number is provided, get specific pull request. + return self::get_specific_pull_request( $gh_owner, $gh_repo, $pr_number, $gh_token ); + } + + /** + * Get all pull requests for a given repo. + * + * @param string $gh_owner GitHub owner. + * @param string $gh_repo GitHub repo. + * @param string $pr_state State of pull requests to fetch. Default is 'open'. + * @param string $gh_token GitHub token. + * @param int $per_page Number of pull requests per page. Default is 25. + * @param int $page Page number. Default is 1. + * + * @return \WP_REST_Response + */ + private static function get_all_pull_requests( string $gh_owner, string $gh_repo, string $pr_state = 'open', string $gh_token, int $per_page = 25, int $page = 1 ): \WP_REST_Response { + + // gh api endpoint to get pull requests. + $gh_api_endpoint = "https://api.github.com/repos/{$gh_owner}/{$gh_repo}/pulls?state={$pr_state}&per_page={$per_page}&page={$page}"; + + $response = wp_safe_remote_get( + $gh_api_endpoint, + array( + 'headers' => array( + 'Authorization' => "Bearer {$gh_token}", + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'OneUpdate Plugin Loader', + ), + 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. + ), + ); + + if ( is_wp_error( $response ) ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => $response->get_error_message(), + ), + 500 + ); + } + + $status_code = wp_remote_retrieve_response_code( $response ); + $body = wp_remote_retrieve_body( $response ); + $headers = wp_remote_retrieve_headers( $response ); + + if ( 200 !== $status_code ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => "GitHub API returned status code {$status_code}.", + ), + $status_code + ); + } + + $total_count = 0; + $total_pages = 1; + + if ( isset( $headers['link'] ) ) { + $link_header = $headers['link']; + + // Parse the Link header to get last page. + if ( preg_match( '/page=(\d+)>; rel="last"/', $link_header, $matches ) ) { + $total_pages = (int) $matches[1] ?? 1; + $total_count = $total_pages * $per_page; // Approximate. + } + } + + $pull_requests = json_decode( $body, true ); + + $pull_requests = self::format_github_pull_requests_info( $pull_requests ); + + $pull_requests_response = new \WP_REST_Response( + array( + 'success' => true, + 'pull_requests' => $pull_requests, + 'pagination' => array( + 'current_page' => $page, + 'per_page' => $per_page, + 'total_pages' => $total_pages, + 'total_count' => $total_count, + ), + ), + 200 + ); + + $pull_requests_response->header( 'X-WP-Total', $total_count ); + $pull_requests_response->header( 'X-WP-TotalPages', $total_pages ); + + return $pull_requests_response; + } + + /** + * Search pull requests in a given repo. + * + * @param string $gh_owner GitHub owner. + * @param string $gh_repo GitHub repo. + * @param string $search_query Search query. + * @param string $gh_token GitHub token. + * @param int $per_page Number of pull requests per page. Default is 25. + * @param int $page Page number. Default is 1. + * @param string $pr_state State of pull requests to fetch. Default is 'all'. + * + * @return \WP_REST_Response + */ + private static function search_pull_requests( string $gh_owner, string $gh_repo, string $search_query, string $gh_token, int $per_page = 25, int $page = 1, string $pr_state = 'all' ): \WP_REST_Response { + + // gh api endpoint to search pull requests. + $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr&per_page={$per_page}&page={$page}"; + + $response = wp_safe_remote_get( + $gh_api_endpoint, + array( + 'headers' => array( + 'Authorization' => "Bearer {$gh_token}", + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'OneUpdate Plugin Loader', + ), + 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. + ), + ); + + if ( is_wp_error( $response ) ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => $response->get_error_message(), + ), + 500 + ); + } + + $status_code = wp_remote_retrieve_response_code( $response ); + $body = wp_remote_retrieve_body( $response ); + $headers = wp_remote_retrieve_headers( $response ); + + if ( 200 !== $status_code ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => "GitHub API returned status code {$status_code}.", + 'response_body' => $body, + 'response' => $response, + ), + $status_code + ); + } + + $search_results = json_decode( $body, true ); + $pull_requests = isset( $search_results['items'] ) ? self::format_github_pull_requests_info( $search_results['items'] ) : array(); + + $total_count = 0; + $total_pages = 1; + + if ( isset( $headers['link'] ) ) { + $link_header = $headers['link']; + + // Parse the Link header to get last page. + if ( preg_match( '/page=(\d+)>; rel="last"/', $link_header, $matches ) ) { + $total_pages = (int) $matches[1] ?? 1; + $total_count = $total_pages * $per_page; // Approximate. + } + } + + foreach ( $pull_requests as $key => $pr ) { + if ( 'all' !== $pr_state && $pr['state'] !== $pr_state ) { + unset( $pull_requests[ $key ] ); + } + } + $pull_requests = array_values( $pull_requests ); // reindex array after unset. + + $search_response = new \WP_REST_Response( + array( + 'success' => true, + 'pull_requests' => $pull_requests, + 'pagination' => array( + 'current_page' => $page, + 'per_page' => $per_page, + 'total_pages' => $total_pages, + 'total_count' => $total_count, + ), + ), + 200 + ); + + $search_response->header( 'X-WP-Total', $total_count ); + $search_response->header( 'X-WP-TotalPages', $total_pages ); + + return $search_response; + } + + /** + * Get a specific pull request by its number. + * + * @param string $gh_owner GitHub owner. + * @param string $gh_repo GitHub repo. + * @param int $pr_number Pull request number. + * @param string $gh_token GitHub token. + * + * @return \WP_REST_Response + */ + private static function get_specific_pull_request( string $gh_owner, string $gh_repo, int $pr_number, string $gh_token ): \WP_REST_Response { + + // gh api endpoint to get a specific pull request. + $gh_api_endpoint = "https://api.github.com/repos/{$gh_owner}/{$gh_repo}/pulls/{$pr_number}"; + + $response = wp_safe_remote_get( + $gh_api_endpoint, + array( + 'headers' => array( + 'Authorization' => "Bearer {$gh_token}", + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'OneUpdate Plugin Loader', + ), + 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. + ), + ); + + if ( is_wp_error( $response ) ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => $response->get_error_message(), + ), + 500 + ); + } + + $status_code = wp_remote_retrieve_response_code( $response ); + $body = wp_remote_retrieve_body( $response ); + + if ( 200 !== $status_code ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => "GitHub API returned status code {$status_code}.", + 'response_body' => $body, + ), + $status_code + ); + } + + $pull_request = json_decode( $body, true ); + + $pull_request = self::format_github_pull_requests_info( array( $pull_request ) ); + + return new \WP_REST_Response( + array( + 'success' => true, + 'pull_request' => $pull_request, + ), + 200 + ); + } + + /** + * Format GitHub pull requests info to return only necessary fields. + * + * @param array $pull_requests Array of pull requests from GitHub API. + * + * @return array Formatted array of pull requests. + */ + private static function format_github_pull_requests_info( array $pull_requests ): array { + $formatted_prs = array(); + foreach ( $pull_requests as $pr ) { + $formatted_prs[] = array( + 'id' => $pr['id'] ?? '', + 'url' => $pr['url'] ?? '', + 'number' => $pr['number'] ?? '', + 'title' => $pr['title'] ?? '', + 'user' => array( + 'login' => $pr['user']['login'] ?? '', + 'avatar_url' => $pr['user']['avatar_url'] ?? '', + 'html_url' => $pr['user']['html_url'] ?? '', + ), + 'labels' => $pr['labels'] ?? '', + 'state' => $pr['state'] ?? '', + 'created_at' => $pr['created_at'] ?? '', + 'updated_at' => $pr['updated_at'] ?? '', + 'closed_at' => $pr['closed_at'] ?? '', + 'html_url' => $pr['html_url'] ?? '', + 'body' => $pr['body'] ?? '', + 'pr_branch' => $pr['head']['ref'] ?? '', + 'base_branch' => $pr['base']['ref'] ?? '', + 'merged_at' => $pr['merged_at'] ?? null, + 'merged' => $pr['merged'] ?? null, + 'merged_by' => isset( $pr['merged_by'] ) ? array( + 'login' => $pr['merged_by']['login'] ?? '', + 'avatar_url' => $pr['merged_by']['avatar_url'] ?? '', + 'html_url' => $pr['merged_by']['html_url'] ?? '', + ) : null, + + 'comments' => $pr['comments'] ?? null, + 'commits' => $pr['commits'] ?? null, + 'additions' => $pr['additions'] ?? null, + 'deletions' => $pr['deletions'] ?? null, + 'changed_files' => $pr['changed_files'] ?? null, + 'rebaseable' => $pr['rebaseable'] ?? null, + 'draft' => $pr['draft'] ?? null, + 'auto_merge' => $pr['auto_merge'] ?? null, + + ); + } + return $formatted_prs; + } +} diff --git a/inc/classes/rest/class-s3.php b/inc/classes/rest/class-s3.php index d9efd64..59041b7 100644 --- a/inc/classes/rest/class-s3.php +++ b/inc/classes/rest/class-s3.php @@ -11,6 +11,8 @@ use WP_REST_Server; use Aws\S3\S3Client; use Aws\Exception\AwsException; +use OneUpdate\Plugin_Configs\Constants; +use OneUpdate\Utils; /** * Class S3 @@ -100,11 +102,11 @@ public function register_routes(): void { * @return \WP_REST_Response| \WP_Error */ public function s3_health_check(): \WP_REST_Response|\WP_Error { - $s3_credentials = get_option( 'oneupdate_s3_credentials' ); + $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); if ( empty( $s3_credentials ) || ! is_array( $s3_credentials ) ) { return new \WP_REST_Response( array( 'message' => 'S3 credentials not set' ), 400 ); } - $s3 = self::get_s3_instance(); + $s3 = Utils::get_s3_instance(); try { // Attempt to list buckets to check connectivity. $result = $s3->listBuckets(); @@ -149,8 +151,8 @@ public function handle_s3_upload( \WP_REST_Request $request ): \WP_REST_Response if ( pathinfo( $file['name'], PATHINFO_EXTENSION ) !== 'zip' ) { return new \WP_REST_Response( array( 'message' => 'Only ZIP files are allowed' ), 400 ); } - $s3_credentials = get_option( 'oneupdate_s3_credentials' ); - $s3 = self::get_s3_instance(); + $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); + $s3 = Utils::get_s3_instance(); $s3_key = 'Uploads/' . uniqid() . '_' . basename( $file['name'] ); try { @@ -175,7 +177,7 @@ public function handle_s3_upload( \WP_REST_Request $request ): \WP_REST_Response )->getUri()->__toString(); global $wpdb; - $table_name = $wpdb->prefix . 'oneupdate_s3_zip_history'; + $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; $insert_result = $wpdb->insert( // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- inserting private plugin data. $table_name, array( @@ -210,61 +212,10 @@ public function handle_s3_upload( \WP_REST_Request $request ): \WP_REST_Response */ public function get_s3_upload_history(): \WP_REST_Response|\WP_Error { global $wpdb; - $table_name = $wpdb->prefix . 'oneupdate_s3_zip_history'; + $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; $query = "SELECT * FROM $table_name ORDER BY upload_time DESC"; $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.NoCaching -- Static query with no variables, caching not suitable for dynamic upload history. return new \WP_REST_Response( $results ? $results : array(), 200 ); } - - /** - * Get S3 instance. - * - * @return S3Client - */ - public static function get_s3_instance(): S3Client { - $s3_credentials = get_option( 'oneupdate_s3_credentials' ); - if ( empty( $s3_credentials ) || ! is_array( $s3_credentials ) ) { - return new S3Client( array() ); // Return an empty S3Client. - } - $s3 = new S3Client( - array( - 'version' => 'latest', - 'region' => $s3_credentials['region'] ?? '', - 'credentials' => array( - 'key' => $s3_credentials['accessKey'] ?? '', - 'secret' => $s3_credentials['secretKey'] ?? '', - ), - 'use_accelerate_endpoint' => true, - ) - ); - - // first check if the bucket has getBucketAccelerateConfiguration. - - try { - $accelerate_config = $s3->getBucketAccelerateConfiguration( - array( - 'Bucket' => $s3_credentials['bucketName'] ?? '', - ) - ); - if ( ! empty( $accelerate_config['Status'] ) && 'Enabled' === $accelerate_config['Status'] ) { - return $s3; - } - } catch ( AwsException $e ) { - $s3 = new S3Client( - array( - 'version' => 'latest', - 'region' => $s3_credentials['region'] ?? '', - 'endpoint' => $s3_credentials['endpoint'] ?? '', - 'credentials' => array( - 'key' => $s3_credentials['accessKey'] ?? '', - 'secret' => $s3_credentials['secretKey'] ?? '', - ), - 'use_path_style_endpoint' => true, // use path style endpoint. - ) - ); - } - - return $s3; - } } diff --git a/inc/classes/rest/class-workflow.php b/inc/classes/rest/class-workflow.php index 6f3b5f1..79cf272 100644 --- a/inc/classes/rest/class-workflow.php +++ b/inc/classes/rest/class-workflow.php @@ -9,6 +9,7 @@ use OneUpdate\Traits\Singleton; use OneUpdate\Cache; +use OneUpdate\Plugin_Configs\Constants; /** * Class Workflow @@ -257,7 +258,7 @@ public function register_rest_routes(): void { */ public function webhook_permission_callback(): bool { $secret = isset( $_GET['secret'] ) ? sanitize_text_field( wp_unslash( $_GET['secret'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no need for nonce as its called from webhook like vip or github. - $valid_secret = get_option( 'oneupdate_child_site_api_key', 'default_api_key' ); + $valid_secret = get_option( Constants::ONEUPDATE_API_KEY, '' ); return hash_equals( $secret, $valid_secret ); } @@ -636,7 +637,7 @@ public function apply_private_plugins_to_selected_sites( \WP_REST_Request $reque * @return array|\WP_Error */ private function trigger_github_action_for_private_plugin( string $repo, string $private_plugin, string $branch, string $site_name ): array|\WP_Error { - $github_token = get_option( 'oneupdate_gh_token', '' ); + $github_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); if ( empty( $github_token ) ) { return new \WP_Error( 'no_github_token', __( 'GitHub token not found.', 'oneupdate' ), array( 'status' => 404 ) ); @@ -714,7 +715,7 @@ private function trigger_github_action_for_private_plugin( string $repo, string * @return WP_REST_Response|\WP_Error */ public function get_oneupdate_plugins_options(): \WP_REST_Response|\WP_Error { - $options = get_option( 'oneupdate_plugins_options', array() ); + $options = get_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, array() ); return rest_ensure_response( array( @@ -745,12 +746,12 @@ public function update_oneupdate_plugins_options( \WP_REST_Request $request ): \ $plugin_type = $request_options['plugin_type'] ?? 'add_update'; // oneupdate_plugin_activate options. - $oneupdate_plugin_activate = get_option( 'oneupdate_plugins_options', array() ); + $oneupdate_plugin_activate = get_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, array() ); // if plugin type is deactivate/remove then remove the plugin from options. if ( 'deactivate' === $plugin_type || 'remove' === $plugin_type ) { // get active plugins options. - $active_plugins = get_option( 'active_plugins', array() ); + $active_plugins = get_option( Constants::ONEUPDATE_ACTIVE_PLUGINS, array() ); // remove the plugins from active plugins options. foreach ( $plugins as $plugin ) { if ( in_array( $plugin, $active_plugins, true ) ) { @@ -762,12 +763,12 @@ public function update_oneupdate_plugins_options( \WP_REST_Request $request ): \ } } // update the active plugins options. - update_option( 'active_plugins', $active_plugins ); + update_option( Constants::ONEUPDATE_ACTIVE_PLUGINS, $active_plugins ); } if ( 'activate' === $plugin_type ) { // if plugin type is activate then activate the plugins. - $active_plugins = get_option( 'active_plugins', array() ); + $active_plugins = get_option( Constants::ONEUPDATE_ACTIVE_PLUGINS, array() ); foreach ( $plugins as $plugin ) { if ( ! in_array( $plugin, $active_plugins, true ) ) { activate_plugin( $plugin, '', false, true ); @@ -779,7 +780,7 @@ public function update_oneupdate_plugins_options( \WP_REST_Request $request ): \ } } - update_option( 'oneupdate_plugins_options', $oneupdate_plugin_activate ); + update_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, $oneupdate_plugin_activate ); if ( ! empty( $plugins ) ) { Cache::rebuild_transient_for_single_plugin( @@ -863,7 +864,7 @@ public function apply_plugins_to_selected_sites( \WP_REST_Request $request ): \W // if current site is same as site_url then use current site token. if ( empty( $token ) ) { - $token = get_option( 'oneupdate_child_site_api_key', 'default_api_key' ); + $token = get_option( Constants::ONEUPDATE_API_KEY, '' ); } // create comma separated string array of plugins. @@ -922,7 +923,7 @@ public function apply_plugins_to_selected_sites( \WP_REST_Request $request ): \W * @return array|\WP_Error */ private function trigger_github_action_for_pr_creation( string $repo, string $branch, string $plugin_slug, string $version, string $plugin_type, string $site_name ): array|\WP_Error { - $github_token = get_option( 'oneupdate_gh_token' ); + $github_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); if ( empty( $github_token ) ) { return new \WP_Error( 'no_github_token', __( 'GitHub token not found.', 'oneupdate' ), array( 'status' => 404 ) ); @@ -1023,7 +1024,7 @@ private function trigger_github_action_for_pr_creation( string $repo, string $br * @return string|null The latest workflow run ID or null if not found. */ private function get_latest_workflow_run_id( string $repo, string $workflow_filename ): string|null { - $github_token = get_option( 'oneupdate_gh_token' ); + $github_token = get_option( Constants::ONEUPDATE_GH_TOKEN, '' ); if ( empty( $github_token ) ) { return null; diff --git a/inc/classes/settings/class-shared-sites.php b/inc/classes/settings/class-shared-sites.php index 1635147..f1f61b9 100644 --- a/inc/classes/settings/class-shared-sites.php +++ b/inc/classes/settings/class-shared-sites.php @@ -8,6 +8,7 @@ namespace OneUpdate\Settings; use OneUpdate\Traits\Singleton; +use OneUpdate\Utils; /** * Class Shared_Sites @@ -60,6 +61,18 @@ public function add_admin_menu(): void { array( $this, 'render_oneupdate_plugin_manager' ) ); + // if governing site then add pull requests menu. + if ( Utils::is_governing_site() ) { + add_submenu_page( + 'oneupdate', + __( 'Pull Requests', 'oneupdate' ), + __( 'Pull Requests', 'oneupdate' ), + 'manage_options', + 'oneupdate-pull-requests', + array( $this, 'render_oneupdate_pull_requests_page' ) + ); + } + // Add your other submenu page. add_submenu_page( 'oneupdate', @@ -71,7 +84,7 @@ public function add_admin_menu(): void { ); // if site type is brand then remove the governing site menu. - if ( 'governing-site' !== get_option( 'oneupdate_site_type', '' ) ) { + if ( ! Utils::is_governing_site() ) { remove_submenu_page( 'oneupdate', 'oneupdate' ); } } @@ -83,7 +96,7 @@ public function add_admin_menu(): void { */ public function render_oneupdate_plugin_manager(): void { // Check if the user has permission to manage options. - if ( ! current_user_can( 'manage_options' ) || 'governing-site' !== get_option( 'oneupdate_site_type', '' ) ) { + if ( ! current_user_can( 'manage_options' ) || ! Utils::is_governing_site() ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'oneupdate' ) ); } ?> @@ -112,4 +125,22 @@ public function render_oneupdate_settings_page(): void { +
+

+
+
+ Date: Mon, 8 Sep 2025 11:00:22 +0530 Subject: [PATCH 02/18] chore: cleanup unnessary cron job --- assets/src/admin/plugin/index.js | 2 +- assets/src/admin/pull-requests/index.js | 10 +- inc/classes/class-assets.php | 7 - inc/classes/class-plugin.php | 1 + inc/classes/class-s3-upload.php | 50 +--- inc/classes/rest/class-s3.php | 1 - languages/oneupdate.pot | 339 +++++++++++++++++++----- oneupdate.php | 12 +- 8 files changed, 284 insertions(+), 138 deletions(-) diff --git a/assets/src/admin/plugin/index.js b/assets/src/admin/plugin/index.js index 23c4ae5..24e1b86 100644 --- a/assets/src/admin/plugin/index.js +++ b/assets/src/admin/plugin/index.js @@ -16,7 +16,7 @@ const SiteTypeSelector = ( { value, setSiteType } ) => ( options={ [ { label: __( 'Select…', 'oneupdate' ), value: '' }, { label: __( 'Brand Site', 'oneupdate' ), value: 'brand-site' }, - { label: __( 'Governing site', 'oneupdate' ), value: 'governing-site' }, + { label: __( 'Governing Site', 'oneupdate' ), value: 'governing-site' }, ] } /> ); diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index 21a22be..bd24246 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -166,9 +166,7 @@ const GitHubPullRequests = () => { if ( ! dateString ) { return __( 'N/A', 'oneupdate' ); } - return new Date( dateString ).toLocaleDateString( undefined, { - year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', - } ); + return new Date( dateString ).toLocaleString(); }; const getPRStatusBadge = ( pr ) => { @@ -316,9 +314,9 @@ const GitHubPullRequests = () => { { __( 'PR #', 'oneupdate' ) } { __( 'Title', 'oneupdate' ) } - { __( 'Author', 'oneupdate' ) } - { __( 'Status', 'oneupdate' ) } - { __( 'Created at', 'oneupdate' ) } + { __( 'Author', 'oneupdate' ) } + { __( 'Status', 'oneupdate' ) } + { __( 'Created at', 'oneupdate' ) } { __( 'Labels', 'oneupdate' ) } { __( 'Actions', 'oneupdate' ) } diff --git a/inc/classes/class-assets.php b/inc/classes/class-assets.php index 4c7b491..2727a7d 100644 --- a/inc/classes/class-assets.php +++ b/inc/classes/class-assets.php @@ -55,12 +55,9 @@ public function enqueue_admin_scripts( $hook_suffix ) { 'oneupdate-settings-script', 'OneUpdateSettings', array( - 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'setupUrl' => admin_url( 'admin.php?page=oneupdate-settings' ), ) ); @@ -71,7 +68,6 @@ public function enqueue_admin_scripts( $hook_suffix ) { if ( strpos( $hook_suffix, 'toplevel_page_oneupdate' ) !== false ) { // remove all admin notices. remove_all_actions( 'admin_notices' ); - setcookie( 'vip-go-cb', '1', time() + ( 86400 * 30 ), '/' ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie -- this is to avoid caching issue in vip environment. $this->register_script( 'oneupdate-plugins-manager-script', @@ -82,7 +78,6 @@ public function enqueue_admin_scripts( $hook_suffix ) { 'oneupdate-plugins-manager-script', 'OneUpdatePlugins', array( - 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), @@ -110,11 +105,9 @@ public function enqueue_admin_scripts( $hook_suffix ) { 'oneupdate-setup-script', 'OneUpdateSettings', array( - 'nonce' => wp_create_nonce( 'wp_rest' ), 'restUrl' => esc_url( home_url( '/wp-json' ) ), 'apiKey' => get_option( Constants::ONEUPDATE_API_KEY, '' ), 'restNonce' => wp_create_nonce( 'wp_rest' ), - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'setupUrl' => admin_url( 'admin.php?page=oneupdate-settings' ), ) ); diff --git a/inc/classes/class-plugin.php b/inc/classes/class-plugin.php index 14af920..12a56b1 100644 --- a/inc/classes/class-plugin.php +++ b/inc/classes/class-plugin.php @@ -38,6 +38,7 @@ public function load_plugin_classes(): void { Settings::get_instance(); REST::get_instance(); Cache::get_instance(); + S3_Upload::get_instance(); } /** diff --git a/inc/classes/class-s3-upload.php b/inc/classes/class-s3-upload.php index 3df85ea..ccb9bea 100644 --- a/inc/classes/class-s3-upload.php +++ b/inc/classes/class-s3-upload.php @@ -10,7 +10,6 @@ use OneUpdate\Traits\Singleton; use Aws\Exception\AwsException; use OneUpdate\Plugin_Configs\Constants; -use OneUpdate\REST\S3; /** * Class S3_Upload @@ -34,7 +33,6 @@ protected function __construct() { */ public function setup_hooks(): void { add_action( 'oneupdate_s3_zip_cleanup_event', array( $this, 'oneupdate_s3_zip_cleanup_event' ) ); - add_action( 'oneupdate_s3_zip_history_cleanup_event', array( $this, 'oneupdate_s3_zip_history_cleanup_event' ) ); } /** @@ -44,7 +42,7 @@ public function setup_hooks(): void { * * @throws \Exception If there is an error deleting files from S3. */ - // phpcs:disable -- its custom query to cleanup s3 bucket. + // phpcs:disable -- its custom query to cleanup s3 bucket & history table. public function oneupdate_s3_zip_cleanup_event(): void { $s3_credentials = get_option( Constants::ONEUPDATE_S3_CREDENTIALS, array() ); @@ -84,49 +82,5 @@ public function oneupdate_s3_zip_cleanup_event(): void { ) ); } - // phpcs:enable. - - /** - * Handle S3 zip history cleanup event. - * - * @return void - * - * @throws \Exception If there is an error deleting files from S3. - */ - // phpcs:disable -- its custom query to cleanup s3 zip history. - public function oneupdate_s3_zip_history_cleanup_event(): void { - global $wpdb; - $table_name = $wpdb->prefix . Constants::ONEUPDATE_S3_ZIP_HISTORY_TABLE; - $one_week_ago = date( 'Y-m-d H:i:s', strtotime( '-1 week' ) ); - $batch_size = 1000; - $sleep_seconds = 2; - - // Count total records to delete. - $total_records = $wpdb->get_var( - $wpdb->prepare( "SELECT COUNT(*) FROM $table_name WHERE upload_time <= %s", $one_week_ago ) - ); - - if ( $total_records > 0 ) { - $offset = 0; - while ( $offset < $total_records ) { - $query_template = sprintf( - 'DELETE FROM `%s` WHERE upload_time <= %%s LIMIT %%d', - esc_sql( $table_name ) - ); - - $wpdb->query( - $wpdb->prepare( - $query_template, - $one_week_ago, - $batch_size - ) - ); - $offset += $batch_size; - if ( $offset < $total_records ) { - sleep( $sleep_seconds ); - } - } - } - } - // phpcs:enable. + // phpcs:enable.-- its custom query to cleanup s3 bucket & history table. } diff --git a/inc/classes/rest/class-s3.php b/inc/classes/rest/class-s3.php index 59041b7..537fdea 100644 --- a/inc/classes/rest/class-s3.php +++ b/inc/classes/rest/class-s3.php @@ -9,7 +9,6 @@ use OneUpdate\Traits\Singleton; use WP_REST_Server; -use Aws\S3\S3Client; use Aws\Exception\AwsException; use OneUpdate\Plugin_Configs\Constants; use OneUpdate\Utils; diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index 1372fce..2c45279 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -1,28 +1,33 @@ # Copyright (C) 2025 Utsav Patel, rtCamp -# This file is distributed under the same license as the OneUpdate plugin. +# This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: OneUpdate 1.0.1\n" +"Project-Id-Version: OneUpdate 1.0.2\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/OneUpdate\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-08-29T11:41:39+00:00\n" +"POT-Creation-Date: 2025-09-08T05:27:49+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" #. Plugin Name of the plugin #: oneupdate.php -#: inc/classes/settings/class-shared-sites.php:45 -#: oneupdate.php:44 +#: inc/classes/settings/class-shared-sites.php:46 +#: oneupdate.php:47 #: assets/build/js/plugin.js:1 #: assets/src/admin/plugin/index.js:111 msgid "OneUpdate" msgstr "" +#. Plugin URI of the plugin +#: oneupdate.php +msgid "https://github.com/rtCamp/OneUpdate/" +msgstr "" + #. Description of the plugin #: oneupdate.php msgid "OneUpdate - Enterprise WordPress Plugin Manager Automate plugin updates across multiple WordPress sites with CI/CD integration. Creates pull requests for seamless development-to-production workflows." @@ -42,15 +47,15 @@ msgstr "" msgid "No plugins found." msgstr "" -#: inc/classes/class-hooks.php:139 -#: inc/classes/settings/class-shared-sites.php:66 -#: inc/classes/settings/class-shared-sites.php:67 -#: inc/classes/settings/class-shared-sites.php:110 +#: inc/classes/class-hooks.php:140 +#: inc/classes/settings/class-shared-sites.php:79 +#: inc/classes/settings/class-shared-sites.php:80 +#: inc/classes/settings/class-shared-sites.php:123 msgid "Settings" msgstr "" #. translators: %s is the error message from AWS S3 -#: inc/classes/class-s3-upload.php:73 +#: inc/classes/class-s3-upload.php:71 #, php-format msgid "Error deleting file from S3: %s" msgstr "" @@ -59,157 +64,167 @@ msgstr "" msgid "Secret key regenerated successfully." msgstr "" -#: inc/classes/rest/class-basic-options.php:239 +#: inc/classes/rest/class-basic-options.php:240 msgid "Health check passed successfully." msgstr "" -#: inc/classes/rest/class-basic-options.php:254 -#: inc/classes/rest/class-workflow.php:642 -#: inc/classes/rest/class-workflow.php:928 +#: inc/classes/rest/class-basic-options.php:255 +#: inc/classes/rest/class-workflow.php:643 +#: inc/classes/rest/class-workflow.php:929 msgid "GitHub token not found." msgstr "" -#: inc/classes/rest/class-basic-options.php:280 +#: inc/classes/rest/class-basic-options.php:281 msgid "Failed to fetch GitHub repositories." msgstr "" -#: inc/classes/rest/class-basic-options.php:312 +#: inc/classes/rest/class-basic-options.php:313 msgid "No repositories found for rtCamp or wpcomvip." msgstr "" -#: inc/classes/rest/class-basic-options.php:375 +#: inc/classes/rest/class-basic-options.php:376 msgid "GitHub token is required." msgstr "" -#: inc/classes/rest/class-basic-options.php:394 +#: inc/classes/rest/class-basic-options.php:395 msgid "Invalid GitHub token provided." msgstr "" -#: inc/classes/rest/class-basic-options.php:441 -#: inc/classes/rest/class-basic-options.php:448 +#: inc/classes/rest/class-basic-options.php:442 +#: inc/classes/rest/class-basic-options.php:449 msgid "Invalid S3 credentials provided." msgstr "" -#: inc/classes/rest/class-basic-options.php:495 +#: inc/classes/rest/class-basic-options.php:496 msgid "Brand Site already exists." msgstr "" -#: inc/classes/rest/class-basic-options.php:504 +#: inc/classes/rest/class-basic-options.php:505 msgid "GitHub repository already exists in one of Brand sites." msgstr "" -#: inc/classes/rest/class-workflow.php:280 +#: inc/classes/rest/class-workflow.php:281 msgid "Transient rebuilt successfully." msgstr "" -#: inc/classes/rest/class-workflow.php:298 +#: inc/classes/rest/class-workflow.php:299 msgid "Invalid plugins provided." msgstr "" -#: inc/classes/rest/class-workflow.php:307 +#: inc/classes/rest/class-workflow.php:308 msgid "Invalid site provided." msgstr "" -#: inc/classes/rest/class-workflow.php:337 +#: inc/classes/rest/class-workflow.php:338 msgid "Bulk plugin update initiated successfully." msgstr "" -#: inc/classes/rest/class-workflow.php:361 +#: inc/classes/rest/class-workflow.php:362 msgid "Invalid action provided." msgstr "" -#: inc/classes/rest/class-workflow.php:365 +#: inc/classes/rest/class-workflow.php:366 msgid "Invalid plugin slug provided." msgstr "" -#: inc/classes/rest/class-workflow.php:369 +#: inc/classes/rest/class-workflow.php:370 msgid "Invalid sites provided." msgstr "" #. translators: %s is the site URL -#: inc/classes/rest/class-workflow.php:407 -#: inc/classes/rest/class-workflow.php:434 +#: inc/classes/rest/class-workflow.php:408 +#: inc/classes/rest/class-workflow.php:435 #, php-format msgid "Failed to execute plugin action on site %s." msgstr "" -#: inc/classes/rest/class-workflow.php:440 +#: inc/classes/rest/class-workflow.php:441 msgid "Unknown error occurred." msgstr "" #. translators: %s is the site URL -#: inc/classes/rest/class-workflow.php:458 -#: inc/classes/rest/class-workflow.php:498 -#: inc/classes/rest/class-workflow.php:538 +#: inc/classes/rest/class-workflow.php:459 +#: inc/classes/rest/class-workflow.php:499 +#: inc/classes/rest/class-workflow.php:539 #, php-format msgid "GitHub repository not found for site %s." msgstr "" -#: inc/classes/rest/class-workflow.php:572 +#: inc/classes/rest/class-workflow.php:573 msgid "Plugin action executed successfully." msgstr "" -#: inc/classes/rest/class-workflow.php:592 +#: inc/classes/rest/class-workflow.php:593 msgid "Invalid data provided." msgstr "" -#: inc/classes/rest/class-workflow.php:606 -#: inc/classes/rest/class-workflow.php:847 +#: inc/classes/rest/class-workflow.php:607 +#: inc/classes/rest/class-workflow.php:848 msgid "Invalid site data provided." msgstr "" -#: inc/classes/rest/class-workflow.php:671 -#: inc/classes/rest/class-workflow.php:685 -#: inc/classes/rest/class-workflow.php:963 -#: inc/classes/rest/class-workflow.php:984 +#: inc/classes/rest/class-workflow.php:672 +#: inc/classes/rest/class-workflow.php:686 +#: inc/classes/rest/class-workflow.php:964 +#: inc/classes/rest/class-workflow.php:985 msgid "Failed to trigger GitHub action for PR creation." msgstr "" -#: inc/classes/rest/class-workflow.php:702 -#: inc/classes/rest/class-workflow.php:1008 +#: inc/classes/rest/class-workflow.php:703 +#: inc/classes/rest/class-workflow.php:1009 msgid "GitHub Action workflow dispatched successfully" msgstr "" -#: inc/classes/rest/class-workflow.php:740 +#: inc/classes/rest/class-workflow.php:741 msgid "Invalid options provided." msgstr "" -#: inc/classes/settings/class-shared-sites.php:44 -#: inc/classes/settings/class-shared-sites.php:56 +#: inc/classes/settings/class-shared-sites.php:45 #: inc/classes/settings/class-shared-sites.php:57 +#: inc/classes/settings/class-shared-sites.php:58 msgid "Plugin Manager" msgstr "" -#: inc/classes/settings/class-shared-sites.php:87 -#: inc/classes/settings/class-shared-sites.php:105 +#: inc/classes/settings/class-shared-sites.php:68 +#: inc/classes/settings/class-shared-sites.php:69 +msgid "Pull Requests" +msgstr "" + +#: inc/classes/settings/class-shared-sites.php:100 +#: inc/classes/settings/class-shared-sites.php:118 +#: inc/classes/settings/class-shared-sites.php:137 msgid "You do not have sufficient permissions to access this page." msgstr "" -#: inc/classes/settings/class-shared-sites.php:91 +#: inc/classes/settings/class-shared-sites.php:104 #: assets/build/js/plugin-manager.js:3 #: assets/src/admin/plugin-manager/index.js:1189 msgid "OneUpdate - Plugin Manager" msgstr "" +#: inc/classes/settings/class-shared-sites.php:141 +msgid "OneUpdate - Pull Requests" +msgstr "" + #. translators: %s is the plugin name. -#: oneupdate.php:43 +#: oneupdate.php:46 #, php-format msgid "You are running the %s plugin from the GitHub repository. Please build the assets and install composer dependencies to use the plugin." msgstr "" #. translators: %s is the command to run. -#: oneupdate.php:52 +#: oneupdate.php:55 #, php-format msgid "Run the following commands in the plugin directory: %s" msgstr "" #. translators: %s is the plugin name. -#: oneupdate.php:60 +#: oneupdate.php:63 #, php-format msgid "Please refer to the %s for more information." msgstr "" -#: oneupdate.php:64 +#: oneupdate.php:67 msgid "OneUpdate GitHub repository" msgstr "" @@ -356,24 +371,32 @@ msgid "Install Selected Plugins" msgstr "" #: assets/build/js/plugin-manager.js:1 +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:415 #: assets/src/components/PluginGrid.js:249 #: assets/src/components/PluginsSharing.js:467 msgid "Previous" msgstr "" #: assets/build/js/plugin-manager.js:1 +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:418 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "Page" msgstr "" #: assets/build/js/plugin-manager.js:1 +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:418 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "of" msgstr "" #: assets/build/js/plugin-manager.js:1 +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:426 #: assets/src/components/PluginGrid.js:259 #: assets/src/components/PluginsSharing.js:477 #: assets/src/components/PluginsSharing.js:523 @@ -655,7 +678,10 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:3 +#: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:1293 +#: assets/src/admin/pull-requests/index.js:303 +#: assets/src/admin/pull-requests/index.js:318 #: assets/src/components/S3ZipUploader.js:1000 msgid "Status" msgstr "" @@ -909,7 +935,9 @@ msgid "Search by name or description…" msgstr "" #: assets/build/js/plugin-manager.js:3 +#: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:1297 +#: assets/src/admin/pull-requests/index.js:53 msgid "All Status" msgstr "" @@ -1150,7 +1178,9 @@ msgid "Description" msgstr "" #: assets/build/js/plugin-manager.js:7 +#: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:2041 +#: assets/src/admin/pull-requests/index.js:317 msgid "Author" msgstr "" @@ -1250,7 +1280,7 @@ msgstr "" #: assets/build/js/plugin.js:1 #: assets/src/admin/plugin/index.js:19 -msgid "Governing site" +msgid "Governing Site" msgstr "" #: assets/build/js/plugin.js:1 @@ -1271,6 +1301,189 @@ msgstr "" msgid "Select current site type" msgstr "" +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:54 +msgid "Open" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:55 +msgid "Merged/Closed" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:105 +msgid "Error fetching pull requests." +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:144 +msgid "Error fetching PR details." +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:167 +msgid "N/A" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:284 +msgid "GitHub Pull Requests" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:290 +#: assets/src/components/PluginGrid.js:149 +msgid "Search" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:291 +msgid "Search by title, number…" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/build/js/settings.js:1 +#: assets/src/admin/pull-requests/index.js:297 +#: assets/src/components/SiteTable.js:28 +msgid "Brand Sites" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:315 +msgid "PR #" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:316 +msgid "Title" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:319 +msgid "Created at" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:320 +msgid "Labels" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/build/js/settings.js:1 +#: assets/src/admin/pull-requests/index.js:321 +#: assets/src/components/SiteTable.js:45 +msgid "Actions" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:335 +msgid "No pull requests found." +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:390 +msgid "more" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:396 +msgid "No labels" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:207 +msgid "PR Actions" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:224 +#: assets/src/admin/pull-requests/index.js:259 +msgid "View PR Details" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:234 +#: assets/src/admin/pull-requests/index.js:560 +msgid "Merge PR" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:245 +#: assets/src/admin/pull-requests/index.js:551 +msgid "Close PR" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:436 +msgid "Pull Request Details" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:444 +msgid "PR Number:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:445 +msgid "Title:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:446 +msgid "Author:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:447 +msgid "Status:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:450 +msgid "Created:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:451 +msgid "Updated:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:452 +msgid "Branch:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:454 +msgid "GitHub URL:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:456 +msgid "View on GitHub" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:465 +msgid "Labels:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:489 +msgid "Description:" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:513 +msgid "Loading extra details…" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:522 +msgid "Merged By:" +msgstr "" + #: assets/build/js/settings.js:1 #: assets/src/components/SiteTable.js:101 msgid "Delete Brand Site" @@ -1287,11 +1500,6 @@ msgstr "" msgid "Delete" msgstr "" -#: assets/build/js/settings.js:1 -#: assets/src/components/SiteTable.js:28 -msgid "Brand Sites" -msgstr "" - #: assets/build/js/settings.js:1 #: assets/src/components/SiteModal.js:113 #: assets/src/components/SiteTable.js:34 @@ -1319,11 +1527,6 @@ msgstr "" msgid "API Key" msgstr "" -#: assets/build/js/settings.js:1 -#: assets/src/components/SiteTable.js:45 -msgid "Actions" -msgstr "" - #: assets/build/js/settings.js:1 #: assets/src/components/SiteTable.js:52 msgid "No Brand Sites found." @@ -1665,10 +1868,6 @@ msgstr "" msgid "Search plugins…" msgstr "" -#: assets/src/components/PluginGrid.js:149 -msgid "Search" -msgstr "" - #: assets/src/components/PluginGrid.js:182 msgid "Unable to find any plugins to display." msgstr "" diff --git a/oneupdate.php b/oneupdate.php index 48ab98a..f54cfd3 100644 --- a/oneupdate.php +++ b/oneupdate.php @@ -1,6 +1,7 @@ Date: Mon, 8 Sep 2025 11:03:06 +0530 Subject: [PATCH 03/18] remove: release title prefix of OneUpdate --- .github/workflows/release_on_tag.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_on_tag.yml b/.github/workflows/release_on_tag.yml index 0cacbd4..9410e36 100644 --- a/.github/workflows/release_on_tag.yml +++ b/.github/workflows/release_on_tag.yml @@ -62,7 +62,7 @@ jobs: run: | gh release view "${{ github.ref_name }}" || \ gh release create "${{ github.ref_name }}" \ - --title "OneUpdate ${{ github.ref_name }}" \ + --title "${{ github.ref_name }}" \ --generate-notes \ --draft # Upload the artifact From afd8b48ab36d29e0658bbea1706247a0935951d1 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 11:53:29 +0530 Subject: [PATCH 04/18] update: pr details modal for closed pr's --- assets/src/admin/pull-requests/index.js | 2 +- languages/oneupdate.pot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index bd24246..d98f67e 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -514,7 +514,7 @@ const GitHubPullRequests = () => { ) } - { prDetails && ( + { prDetails && prDetails.merged_by && (
{ prDetails.merged_by && ( diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index 2c45279..9848c3c 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-09-08T05:27:49+00:00\n" +"POT-Creation-Date: 2025-09-08T06:19:14+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" From 959742c5f23518849537f2d41f2200df1cc7ffdd Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 12:09:59 +0530 Subject: [PATCH 05/18] chore: cleanup event of weekly history cron job --- oneupdate.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/oneupdate.php b/oneupdate.php index f54cfd3..bc9c3c6 100644 --- a/oneupdate.php +++ b/oneupdate.php @@ -123,5 +123,8 @@ function () { ONEUPDATE_PLUGIN_LOADER_PLUGIN_BASENAME, function () { wp_clear_scheduled_hook( 'oneupdate_s3_zip_cleanup_event' ); + + // remove oneupdate_s3_zip_history_cleanup_event event even though its removed but to make sure its completely removed from cron jobs. + wp_clear_scheduled_hook( 'oneupdate_s3_zip_history_cleanup_event' ); } ); From 5ddf3afc08cbc2c297d84742389c6bfe908479f5 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 12:29:45 +0530 Subject: [PATCH 06/18] update: error notice if github is giving 422 error --- assets/src/admin/pull-requests/index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index d98f67e..f703b80 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -85,7 +85,18 @@ const GitHubPullRequests = () => { ); if ( ! response.ok ) { - throw new Error( 'Failed to fetch pull requests' ); + if ( response?.statusText === 'Unprocessable Entity' ) { + setNotice( { + type: 'error', + message: __( 'Please enter valid character to search pull requests.' ), + } ); + } else { + setNotice( { + type: 'error', + message: __( 'Failed to fetch pull requests.', 'oneupdate' ), + } ); + } + return; } const data = await response.json(); @@ -97,7 +108,10 @@ const GitHubPullRequests = () => { setTotalPages( Math.ceil( totalCount / PER_PAGE ) ); setCurrentPage( data?.pagination?.current_page || 1 ); } else { - throw new Error( data.message || 'Failed to fetch pull requests' ); + setNotice( { + type: 'error', + message: __( 'Error fetching pull requests.', 'oneupdate' ), + } ); } } catch ( error ) { setNotice( { From c960e8d047ea8218f883df321f4ef0bac1cf03a7 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 15:48:30 +0530 Subject: [PATCH 07/18] chore: update PR component & unify spinner styles --- assets/src/admin/pull-requests/index.js | 241 ++++++++++++------------ assets/src/components/PluginGrid.js | 2 +- assets/src/components/SiteSettings.js | 2 +- languages/oneupdate.pot | 102 +++++----- 4 files changed, 180 insertions(+), 167 deletions(-) diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index f703b80..5434cdb 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -33,7 +33,7 @@ const GitHubPullRequests = () => { const [ notice, setNotice ] = useState( null ); const [ selectedRepo, setSelectedRepo ] = useState( Object.keys( REPOS )?.[ 0 ] || '' ); const [ statusFilter, setStatusFilter ] = useState( 'all' ); - const [ searchQuery, setSearchQuery ] = useState( '' ); + const [ searchQuery, setSearchQuery ] = useState( '[OneUpdate]' ); const [ page, setPage ] = useState( 1 ); const [ totalPages, setTotalPages ] = useState( 1 ); const [ selectedPR, setSelectedPR ] = useState( null ); @@ -165,9 +165,7 @@ const GitHubPullRequests = () => { const openDetailModal = ( pr ) => { setSelectedPR( pr ); setIsDetailModalOpen( true ); - if ( pr.state !== 'open' ) { - fetchPRDetails( pr.number ); - } + fetchPRDetails( pr.number ); }; const closeModals = () => { @@ -352,19 +350,22 @@ const GitHubPullRequests = () => { ) } { pullRequests.map( ( pr ) => ( + + + #{ pr.number } + + - #{ pr.number } + { decodeEntities( pr.title ) } - - { decodeEntities( pr.title ) } -
{ size="medium" shouldCloseOnClickOutside={ true } > -
-
-
-

{ __( 'PR Number:', 'oneupdate' ) } #{ selectedPR.number }

-

{ __( 'Title:', 'oneupdate' ) } { decodeEntities( selectedPR.title ) }

-

{ __( 'Author:', 'oneupdate' ) } { selectedPR.user.login }

-

{ __( 'Status:', 'oneupdate' ) } { getPRStatusBadge( selectedPR ) }

-
-
-

{ __( 'Created:', 'oneupdate' ) } { formatDate( selectedPR.created_at ) }

-

{ __( 'Updated:', 'oneupdate' ) } { formatDate( selectedPR.updated_at ) }

-

{ __( 'Branch:', 'oneupdate' ) } { selectedPR.pr_branch } → { selectedPR.base_branch }

-

- { __( 'GitHub URL:', 'oneupdate' ) }{ ' ' } - - { __( 'View on GitHub', 'oneupdate' ) } - -

-
+ { /* Detailed PR Info */ } + { detailsLoading && ( +
+ +

{ __( 'Loading PR details…', 'oneupdate' ) }

+ ) } - { /* Labels */ } - { selectedPR.labels.length > 0 && ( -
- { __( 'Labels:', 'oneupdate' ) } -
- { selectedPR.labels.map( ( label ) => ( - +
+
+
+

{ __( 'PR Number:', 'oneupdate' ) } #{ prDetails.number }

+

{ __( 'Title:', 'oneupdate' ) } { decodeEntities( prDetails.title ) }

+

{ __( 'Author:', 'oneupdate' ) } { prDetails.user.login }

+

{ __( 'Status:', 'oneupdate' ) } { getPRStatusBadge( prDetails ) }

+
+
+

{ __( 'Created:', 'oneupdate' ) } { formatDate( prDetails.created_at ) }

+

{ __( 'Updated:', 'oneupdate' ) } { formatDate( prDetails.updated_at ) }

+

{ __( 'Branch:', 'oneupdate' ) } { prDetails.pr_branch } → { prDetails.base_branch }

+

+ { __( 'GitHub URL:', 'oneupdate' ) }{ ' ' } + + { __( 'View on GitHub', 'oneupdate' ) } + +

+
+
+ + { /* Labels */ } + { prDetails.labels.length > 0 && ( +
+ { __( 'Labels:', 'oneupdate' ) } +
+ { prDetails.labels.map( ( label ) => ( + + { label.name } + + ) ) } +
+
+ ) } + + { /* Description */ } + { prDetails.body && ( +
+ { __( 'Description:', 'oneupdate' ) } +
- { label.name } - - ) ) } -
-
- ) } - - { /* Description */ } - { selectedPR.body && ( -
- { __( 'Description:', 'oneupdate' ) } -
-
-										{ decodeEntities( selectedPR.body ) }
-									
-
+
+												{ decodeEntities( prDetails.body ) }
+											
+
+
+ ) }
- ) } -
- { /* Detailed PR Info */ } - { detailsLoading && ( -
- -

{ __( 'Loading extra details…', 'oneupdate' ) }

-
- ) } + { /* Merged By Info */ } + { prDetails.merged_by && ( +
- { prDetails && prDetails.merged_by && ( -
+ { prDetails.merged_by && ( +
+ { __( 'Merged By:', 'oneupdate' ) } + { + { prDetails.merged_by.login } +
+ ) } +
+ ) } - { prDetails.merged_by && ( -
- { __( 'Merged By:', 'oneupdate' ) } - { + +
) } -
+ ) } - { /* Action Buttons */ } - { selectedPR.state === 'open' && ( -
- - -
- ) } ) } diff --git a/assets/src/components/PluginGrid.js b/assets/src/components/PluginGrid.js index f619b82..31b9ef3 100644 --- a/assets/src/components/PluginGrid.js +++ b/assets/src/components/PluginGrid.js @@ -153,7 +153,7 @@ const PluginGrid = () => { { /* Loading State */ } { loading && (
- +

{ __( 'Loading plugins…', 'oneupdate' ) }

) } diff --git a/assets/src/components/SiteSettings.js b/assets/src/components/SiteSettings.js index e6cfa02..5b94788 100644 --- a/assets/src/components/SiteSettings.js +++ b/assets/src/components/SiteSettings.js @@ -73,7 +73,7 @@ const SiteSettings = () => { }, [ fetchApiKey ] ); if ( isLoading ) { - return ; + return ; } return ( diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index 9848c3c..5031886 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-09-08T06:19:14+00:00\n" +"POT-Creation-Date: 2025-09-08T10:15:19+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" @@ -372,7 +372,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:415 +#: assets/src/admin/pull-requests/index.js:430 #: assets/src/components/PluginGrid.js:249 #: assets/src/components/PluginsSharing.js:467 msgid "Previous" @@ -380,7 +380,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:418 +#: assets/src/admin/pull-requests/index.js:433 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "Page" @@ -388,7 +388,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:418 +#: assets/src/admin/pull-requests/index.js:433 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "of" @@ -396,7 +396,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:426 +#: assets/src/admin/pull-requests/index.js:441 #: assets/src/components/PluginGrid.js:259 #: assets/src/components/PluginsSharing.js:477 #: assets/src/components/PluginsSharing.js:523 @@ -680,8 +680,8 @@ msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:1293 -#: assets/src/admin/pull-requests/index.js:303 -#: assets/src/admin/pull-requests/index.js:318 +#: assets/src/admin/pull-requests/index.js:315 +#: assets/src/admin/pull-requests/index.js:330 #: assets/src/components/S3ZipUploader.js:1000 msgid "Status" msgstr "" @@ -1180,7 +1180,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:7 #: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:2041 -#: assets/src/admin/pull-requests/index.js:317 +#: assets/src/admin/pull-requests/index.js:329 msgid "Author" msgstr "" @@ -1312,175 +1312,181 @@ msgid "Merged/Closed" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:105 +#: assets/src/admin/pull-requests/index.js:96 +msgid "Failed to fetch pull requests." +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:113 +#: assets/src/admin/pull-requests/index.js:119 msgid "Error fetching pull requests." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:144 +#: assets/src/admin/pull-requests/index.js:158 msgid "Error fetching PR details." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:167 +#: assets/src/admin/pull-requests/index.js:179 msgid "N/A" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:284 +#: assets/src/admin/pull-requests/index.js:296 msgid "GitHub Pull Requests" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:290 +#: assets/src/admin/pull-requests/index.js:302 #: assets/src/components/PluginGrid.js:149 msgid "Search" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:291 +#: assets/src/admin/pull-requests/index.js:303 msgid "Search by title, number…" msgstr "" #: assets/build/js/pull-requests.js:1 #: assets/build/js/settings.js:1 -#: assets/src/admin/pull-requests/index.js:297 +#: assets/src/admin/pull-requests/index.js:309 #: assets/src/components/SiteTable.js:28 msgid "Brand Sites" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:315 +#: assets/src/admin/pull-requests/index.js:327 msgid "PR #" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:316 +#: assets/src/admin/pull-requests/index.js:328 msgid "Title" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:319 +#: assets/src/admin/pull-requests/index.js:331 msgid "Created at" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:320 +#: assets/src/admin/pull-requests/index.js:332 msgid "Labels" msgstr "" #: assets/build/js/pull-requests.js:1 #: assets/build/js/settings.js:1 -#: assets/src/admin/pull-requests/index.js:321 +#: assets/src/admin/pull-requests/index.js:333 #: assets/src/components/SiteTable.js:45 msgid "Actions" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:335 +#: assets/src/admin/pull-requests/index.js:347 msgid "No pull requests found." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:390 +#: assets/src/admin/pull-requests/index.js:405 msgid "more" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:396 +#: assets/src/admin/pull-requests/index.js:411 msgid "No labels" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:207 +#: assets/src/admin/pull-requests/index.js:219 msgid "PR Actions" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:224 -#: assets/src/admin/pull-requests/index.js:259 +#: assets/src/admin/pull-requests/index.js:236 +#: assets/src/admin/pull-requests/index.js:271 msgid "View PR Details" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:234 -#: assets/src/admin/pull-requests/index.js:560 +#: assets/src/admin/pull-requests/index.js:246 +#: assets/src/admin/pull-requests/index.js:578 msgid "Merge PR" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:245 -#: assets/src/admin/pull-requests/index.js:551 +#: assets/src/admin/pull-requests/index.js:257 +#: assets/src/admin/pull-requests/index.js:569 msgid "Close PR" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:436 +#: assets/src/admin/pull-requests/index.js:451 msgid "Pull Request Details" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:444 +#: assets/src/admin/pull-requests/index.js:460 +msgid "Loading PR details…" +msgstr "" + +#: assets/build/js/pull-requests.js:1 +#: assets/src/admin/pull-requests/index.js:469 msgid "PR Number:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:445 +#: assets/src/admin/pull-requests/index.js:470 msgid "Title:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:446 +#: assets/src/admin/pull-requests/index.js:471 msgid "Author:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:447 +#: assets/src/admin/pull-requests/index.js:472 msgid "Status:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:450 +#: assets/src/admin/pull-requests/index.js:475 msgid "Created:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:451 +#: assets/src/admin/pull-requests/index.js:476 msgid "Updated:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:452 +#: assets/src/admin/pull-requests/index.js:477 msgid "Branch:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:454 +#: assets/src/admin/pull-requests/index.js:479 msgid "GitHub URL:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:456 +#: assets/src/admin/pull-requests/index.js:481 msgid "View on GitHub" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:465 +#: assets/src/admin/pull-requests/index.js:490 msgid "Labels:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:489 +#: assets/src/admin/pull-requests/index.js:514 msgid "Description:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:513 -msgid "Loading extra details…" -msgstr "" - -#: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:522 +#: assets/src/admin/pull-requests/index.js:540 msgid "Merged By:" msgstr "" From 2f43bdeaf0227ed736f09b0e86ae51b66bd8b0ba Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 17:40:19 +0530 Subject: [PATCH 08/18] update: search & pr status pull requests --- .../rest/class-github-pull-requests.php | 148 +++++++++++++++--- 1 file changed, 125 insertions(+), 23 deletions(-) diff --git a/inc/classes/rest/class-github-pull-requests.php b/inc/classes/rest/class-github-pull-requests.php index 1fcd559..1cf0836 100644 --- a/inc/classes/rest/class-github-pull-requests.php +++ b/inc/classes/rest/class-github-pull-requests.php @@ -260,8 +260,25 @@ private static function get_all_pull_requests( string $gh_owner, string $gh_repo */ private static function search_pull_requests( string $gh_owner, string $gh_repo, string $search_query, string $gh_token, int $per_page = 25, int $page = 1, string $pr_state = 'all' ): \WP_REST_Response { - // gh api endpoint to search pull requests. - $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr&per_page={$per_page}&page={$page}"; + // If we have a specific search query, use search API with state filter. + if ( ! empty( $search_query ) && 'all' !== $pr_state ) { + return self::search_pull_requests_with_query_and_state( $gh_owner, $gh_repo, $search_query, $gh_token, $per_page, $page, $pr_state ); + } + + // If no search query or state is 'all', use the original search approach. + if ( ! empty( $search_query ) ) { + $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr"; + + // Add state to search query if not 'all'. + if ( 'all' !== $pr_state ) { + $gh_api_endpoint .= "+state:{$pr_state}"; + } + + $gh_api_endpoint .= "&per_page={$per_page}&page={$page}"; + } else { + // Use pulls API for better state filtering when no search query. + $gh_api_endpoint = "https://api.github.com/repos/{$gh_owner}/{$gh_repo}/pulls?state={$pr_state}&per_page={$per_page}&page={$page}"; + } $response = wp_safe_remote_get( $gh_api_endpoint, @@ -295,36 +312,101 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, 'success' => false, 'message' => "GitHub API returned status code {$status_code}.", 'response_body' => $body, - 'response' => $response, ), $status_code ); } - $search_results = json_decode( $body, true ); - $pull_requests = isset( $search_results['items'] ) ? self::format_github_pull_requests_info( $search_results['items'] ) : array(); + $results = json_decode( $body, true ); - $total_count = 0; - $total_pages = 1; + // Handle different response formats. + if ( ! empty( $search_query ) ) { + // Search API response. + $pull_requests = isset( $results['items'] ) ? self::format_github_pull_requests_info( $results['items'] ) : array(); + $total_count = $results['total_count'] ?? 0; + } else { + // Pulls API response. + $pull_requests = self::format_github_pull_requests_info( $results ); + $total_count = self::get_total_count_from_headers( $headers, count( $pull_requests ) ); + } - if ( isset( $headers['link'] ) ) { - $link_header = $headers['link']; + $total_pages = ceil( $total_count / $per_page ); - // Parse the Link header to get last page. - if ( preg_match( '/page=(\d+)>; rel="last"/', $link_header, $matches ) ) { - $total_pages = (int) $matches[1] ?? 1; - $total_count = $total_pages * $per_page; // Approximate. - } + return new \WP_REST_Response( + array( + 'success' => true, + 'pull_requests' => $pull_requests, + 'pagination' => array( + 'current_page' => $page, + 'per_page' => $per_page, + 'total_pages' => $total_pages, + 'total_count' => $total_count, + ), + ), + 200 + ); + } + + /** + * Handle search with both query and state filters using multiple API calls if needed + * + * @param string $gh_owner GitHub owner. + * @param string $gh_repo GitHub repo. + * @param string $search_query Search query. + * @param string $gh_token GitHub token. + * @param int $per_page Number of pull requests per page. + * @param int $page Page number. + * @param string $pr_state State of pull requests to fetch. + * + * @return \WP_REST_Response + */ + private static function search_pull_requests_with_query_and_state( string $gh_owner, string $gh_repo, string $search_query, string $gh_token, int $per_page, int $page, string $pr_state ): \WP_REST_Response { + + // Use search API with state filter in query. + $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr+state:{$pr_state}&per_page={$per_page}&page={$page}"; + + $response = wp_safe_remote_get( + $gh_api_endpoint, + array( + 'headers' => array( + 'Authorization' => "Bearer {$gh_token}", + 'Accept' => 'application/vnd.github.v3+json', + 'User-Agent' => 'OneUpdate Plugin Loader', + ), + 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. + ), + ); + + if ( is_wp_error( $response ) ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => $response->get_error_message(), + ), + 500 + ); } - foreach ( $pull_requests as $key => $pr ) { - if ( 'all' !== $pr_state && $pr['state'] !== $pr_state ) { - unset( $pull_requests[ $key ] ); - } + $status_code = wp_remote_retrieve_response_code( $response ); + $body = wp_remote_retrieve_body( $response ); + + if ( 200 !== $status_code ) { + return new \WP_REST_Response( + array( + 'success' => false, + 'message' => "GitHub API returned status code {$status_code}.", + 'response_body' => $body, + ), + $status_code + ); } - $pull_requests = array_values( $pull_requests ); // reindex array after unset. - $search_response = new \WP_REST_Response( + $search_results = json_decode( $body, true ); + $pull_requests = isset( $search_results['items'] ) ? self::format_github_pull_requests_info( $search_results['items'] ) : array(); + $total_count = $search_results['total_count'] ?? 0; + $total_pages = ceil( $total_count / $per_page ); + + return new \WP_REST_Response( array( 'success' => true, 'pull_requests' => $pull_requests, @@ -337,11 +419,31 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, ), 200 ); + } - $search_response->header( 'X-WP-Total', $total_count ); - $search_response->header( 'X-WP-TotalPages', $total_pages ); + /** + * Extract total count from Link headers when using pulls API + * + * @param array $headers Response headers. + * @param int $current_count Current count of items fetched. + * + * @return int Total count of items. + */ + private static function get_total_count_from_headers( array $headers, int $current_count ): int { + if ( ! isset( $headers['link'] ) ) { + return $current_count; + } + + $link_header = $headers['link']; + + // Parse the Link header to get last page. + if ( preg_match( '/page=(\d+)>; rel="last"/', $link_header, $matches ) ) { + $last_page = (int) $matches[1]; + // This is an approximation. + return $last_page * 25; + } - return $search_response; + return $current_count; } /** From 4030230fc00a84fd4b710adaab321e56af6bdd8d Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 8 Sep 2025 17:58:12 +0530 Subject: [PATCH 09/18] update: added total page & total header to response --- inc/classes/rest/class-github-pull-requests.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/inc/classes/rest/class-github-pull-requests.php b/inc/classes/rest/class-github-pull-requests.php index 1cf0836..ae9b40c 100644 --- a/inc/classes/rest/class-github-pull-requests.php +++ b/inc/classes/rest/class-github-pull-requests.php @@ -332,7 +332,7 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, $total_pages = ceil( $total_count / $per_page ); - return new \WP_REST_Response( + $response_data = new \WP_REST_Response( array( 'success' => true, 'pull_requests' => $pull_requests, @@ -345,6 +345,11 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, ), 200 ); + + $response_data->header( 'X-WP-Total', $total_count ); + $response_data->header( 'X-WP-TotalPages', $total_pages ); + + return $response_data; } /** @@ -406,7 +411,7 @@ private static function search_pull_requests_with_query_and_state( string $gh_ow $total_count = $search_results['total_count'] ?? 0; $total_pages = ceil( $total_count / $per_page ); - return new \WP_REST_Response( + $response_data = new \WP_REST_Response( array( 'success' => true, 'pull_requests' => $pull_requests, @@ -419,6 +424,11 @@ private static function search_pull_requests_with_query_and_state( string $gh_ow ), 200 ); + + $response_data->header( 'X-WP-Total', $total_count ); + $response_data->header( 'X-WP-TotalPages', $total_pages ); + + return $response_data; } /** From 6a0e3c24595fe0c47b4a0af008915737419d8377 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Tue, 9 Sep 2025 10:22:23 +0530 Subject: [PATCH 10/18] remove: merge & close pr buttons --- assets/src/admin/pull-requests/index.js | 52 +------------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index 5434cdb..324709c 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -17,8 +17,6 @@ import { } from '@wordpress/components'; import { decodeEntities } from '@wordpress/html-entities'; import { moreVertical } from '@wordpress/icons'; -import CloseIcon from '../../components/icons/Close'; -import MergeIcon from '../../components/icons/Merge'; import ViewIcon from '../../components/icons/View'; const API_NAMESPACE = OneUpdatePullRequests.restUrl + '/oneupdate/v1/github'; @@ -233,28 +231,7 @@ const GitHubPullRequests = () => { onClose(); } } > - { __( 'View PR Details', 'oneupdate' ) } - - } - onClick={ () => { - // take user to github to merge - window.open( `${ pr.html_url }#:~:text=Merge%20pull%20request`, '_blank' ); - onClose(); - } } - > - { __( 'Merge PR', 'oneupdate' ) } - - } - onClick={ () => { - // take user to github to close - window.open( `${ pr.html_url }#:~:text=Close%20pull%20request`, '_blank' ); - onClose(); - } } - > - { __( 'Close PR', 'oneupdate' ) } + { __( 'View Details', 'oneupdate' ) } ) } @@ -268,7 +245,7 @@ const GitHubPullRequests = () => { onClose(); } } > - { __( 'View PR Details', 'oneupdate' ) } + { __( 'View Details', 'oneupdate' ) } ) } @@ -554,31 +531,6 @@ const GitHubPullRequests = () => { ) }
) } - - { /* Action Buttons */ } - { prDetails.state === 'open' && ( -
- - -
- ) } ) } From 98ab3860dc5c4d8aa7f3aac6b0524736747cdfce Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Tue, 9 Sep 2025 12:41:20 +0530 Subject: [PATCH 11/18] added translation to user facing error messages --- .../rest/class-github-pull-requests.php | 36 ++++-- languages/oneupdate.pot | 115 +++++++++--------- 2 files changed, 84 insertions(+), 67 deletions(-) diff --git a/inc/classes/rest/class-github-pull-requests.php b/inc/classes/rest/class-github-pull-requests.php index ae9b40c..17ee846 100644 --- a/inc/classes/rest/class-github-pull-requests.php +++ b/inc/classes/rest/class-github-pull-requests.php @@ -135,7 +135,7 @@ public function get_pull_requests( \WP_REST_Request $request ): \WP_REST_Respons return new \WP_REST_Response( array( 'success' => false, - 'message' => 'GitHub token is not set. Please set it in the OneUpdate settings.', + 'message' => __( 'GitHub token is not set. Please set it in the OneUpdate settings.', 'oneupdate' ), ), 400 ); @@ -178,7 +178,7 @@ private static function get_all_pull_requests( string $gh_owner, string $gh_repo 'headers' => array( 'Authorization' => "Bearer {$gh_token}", 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'OneUpdate Plugin Loader', + 'User-Agent' => __( 'OneUpdate Plugin Loader', 'oneupdate' ), ), 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. ), @@ -202,7 +202,12 @@ private static function get_all_pull_requests( string $gh_owner, string $gh_repo return new \WP_REST_Response( array( 'success' => false, - 'message' => "GitHub API returned status code {$status_code}.", + 'message' => + sprintf( + /* translation: %s github response code */ + 'GitHub API returned status code %d.', + $status_code, + ), ), $status_code ); @@ -286,7 +291,7 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, 'headers' => array( 'Authorization' => "Bearer {$gh_token}", 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'OneUpdate Plugin Loader', + 'User-Agent' => __( 'OneUpdate Plugin Loader', 'oneupdate' ), ), 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. ), @@ -310,7 +315,12 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, return new \WP_REST_Response( array( 'success' => false, - 'message' => "GitHub API returned status code {$status_code}.", + 'message' => + sprintf( + /* translation: %s github response code */ + 'GitHub API returned status code %d.', + $status_code, + ), 'response_body' => $body, ), $status_code @@ -376,7 +386,7 @@ private static function search_pull_requests_with_query_and_state( string $gh_ow 'headers' => array( 'Authorization' => "Bearer {$gh_token}", 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'OneUpdate Plugin Loader', + 'User-Agent' => __( 'OneUpdate Plugin Loader', 'oneupdate' ), ), 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. ), @@ -399,7 +409,11 @@ private static function search_pull_requests_with_query_and_state( string $gh_ow return new \WP_REST_Response( array( 'success' => false, - 'message' => "GitHub API returned status code {$status_code}.", + 'message' => sprintf( + /* translation: %s github response code */ + 'GitHub API returned status code %d.', + $status_code, + ), 'response_body' => $body, ), $status_code @@ -477,7 +491,7 @@ private static function get_specific_pull_request( string $gh_owner, string $gh_ 'headers' => array( 'Authorization' => "Bearer {$gh_token}", 'Accept' => 'application/vnd.github.v3+json', - 'User-Agent' => 'OneUpdate Plugin Loader', + 'User-Agent' => __( 'OneUpdate Plugin Loader', 'oneupdate' ), ), 'timeout' => 15, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- this is to avoid timeout issues. ), @@ -500,7 +514,11 @@ private static function get_specific_pull_request( string $gh_owner, string $gh_ return new \WP_REST_Response( array( 'success' => false, - 'message' => "GitHub API returned status code {$status_code}.", + 'message' => sprintf( + /* translation: %s github response code */ + 'GitHub API returned status code %d.', + $status_code, + ), 'response_body' => $body, ), $status_code diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index 5031886..fbc278c 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-09-08T10:15:19+00:00\n" +"POT-Creation-Date: 2025-09-09T07:07:24+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" @@ -103,6 +103,17 @@ msgstr "" msgid "GitHub repository already exists in one of Brand sites." msgstr "" +#: inc/classes/rest/class-github-pull-requests.php:138 +msgid "GitHub token is not set. Please set it in the OneUpdate settings." +msgstr "" + +#: inc/classes/rest/class-github-pull-requests.php:181 +#: inc/classes/rest/class-github-pull-requests.php:294 +#: inc/classes/rest/class-github-pull-requests.php:389 +#: inc/classes/rest/class-github-pull-requests.php:494 +msgid "OneUpdate Plugin Loader" +msgstr "" + #: inc/classes/rest/class-workflow.php:281 msgid "Transient rebuilt successfully." msgstr "" @@ -372,7 +383,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:430 +#: assets/src/admin/pull-requests/index.js:407 #: assets/src/components/PluginGrid.js:249 #: assets/src/components/PluginsSharing.js:467 msgid "Previous" @@ -380,7 +391,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:433 +#: assets/src/admin/pull-requests/index.js:410 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "Page" @@ -388,7 +399,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:433 +#: assets/src/admin/pull-requests/index.js:410 #: assets/src/components/PluginGrid.js:252 #: assets/src/components/PluginsSharing.js:470 msgid "of" @@ -396,7 +407,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:441 +#: assets/src/admin/pull-requests/index.js:418 #: assets/src/components/PluginGrid.js:259 #: assets/src/components/PluginsSharing.js:477 #: assets/src/components/PluginsSharing.js:523 @@ -680,8 +691,8 @@ msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:1293 -#: assets/src/admin/pull-requests/index.js:315 -#: assets/src/admin/pull-requests/index.js:330 +#: assets/src/admin/pull-requests/index.js:292 +#: assets/src/admin/pull-requests/index.js:307 #: assets/src/components/S3ZipUploader.js:1000 msgid "Status" msgstr "" @@ -937,7 +948,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:1297 -#: assets/src/admin/pull-requests/index.js:53 +#: assets/src/admin/pull-requests/index.js:51 msgid "All Status" msgstr "" @@ -1180,7 +1191,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:7 #: assets/build/js/pull-requests.js:1 #: assets/src/admin/plugin-manager/index.js:2041 -#: assets/src/admin/pull-requests/index.js:329 +#: assets/src/admin/pull-requests/index.js:306 msgid "Author" msgstr "" @@ -1302,191 +1313,179 @@ msgid "Select current site type" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:54 +#: assets/src/admin/pull-requests/index.js:52 msgid "Open" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:55 +#: assets/src/admin/pull-requests/index.js:53 msgid "Merged/Closed" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:96 +#: assets/src/admin/pull-requests/index.js:94 msgid "Failed to fetch pull requests." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:113 -#: assets/src/admin/pull-requests/index.js:119 +#: assets/src/admin/pull-requests/index.js:111 +#: assets/src/admin/pull-requests/index.js:117 msgid "Error fetching pull requests." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:158 +#: assets/src/admin/pull-requests/index.js:156 msgid "Error fetching PR details." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:179 +#: assets/src/admin/pull-requests/index.js:177 msgid "N/A" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:296 +#: assets/src/admin/pull-requests/index.js:273 msgid "GitHub Pull Requests" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:302 +#: assets/src/admin/pull-requests/index.js:279 #: assets/src/components/PluginGrid.js:149 msgid "Search" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:303 +#: assets/src/admin/pull-requests/index.js:280 msgid "Search by title, number…" msgstr "" #: assets/build/js/pull-requests.js:1 #: assets/build/js/settings.js:1 -#: assets/src/admin/pull-requests/index.js:309 +#: assets/src/admin/pull-requests/index.js:286 #: assets/src/components/SiteTable.js:28 msgid "Brand Sites" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:327 +#: assets/src/admin/pull-requests/index.js:304 msgid "PR #" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:328 +#: assets/src/admin/pull-requests/index.js:305 msgid "Title" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:331 +#: assets/src/admin/pull-requests/index.js:308 msgid "Created at" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:332 +#: assets/src/admin/pull-requests/index.js:309 msgid "Labels" msgstr "" #: assets/build/js/pull-requests.js:1 #: assets/build/js/settings.js:1 -#: assets/src/admin/pull-requests/index.js:333 +#: assets/src/admin/pull-requests/index.js:310 #: assets/src/components/SiteTable.js:45 msgid "Actions" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:347 +#: assets/src/admin/pull-requests/index.js:324 msgid "No pull requests found." msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:405 +#: assets/src/admin/pull-requests/index.js:382 msgid "more" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:411 +#: assets/src/admin/pull-requests/index.js:388 msgid "No labels" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:219 +#: assets/src/admin/pull-requests/index.js:217 msgid "PR Actions" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:236 -#: assets/src/admin/pull-requests/index.js:271 -msgid "View PR Details" -msgstr "" - -#: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:246 -#: assets/src/admin/pull-requests/index.js:578 -msgid "Merge PR" -msgstr "" - -#: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:257 -#: assets/src/admin/pull-requests/index.js:569 -msgid "Close PR" +#: assets/src/admin/pull-requests/index.js:234 +#: assets/src/admin/pull-requests/index.js:248 +msgid "View Details" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:451 +#: assets/src/admin/pull-requests/index.js:428 msgid "Pull Request Details" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:460 +#: assets/src/admin/pull-requests/index.js:437 msgid "Loading PR details…" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:469 +#: assets/src/admin/pull-requests/index.js:446 msgid "PR Number:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:470 +#: assets/src/admin/pull-requests/index.js:447 msgid "Title:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:471 +#: assets/src/admin/pull-requests/index.js:448 msgid "Author:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:472 +#: assets/src/admin/pull-requests/index.js:449 msgid "Status:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:475 +#: assets/src/admin/pull-requests/index.js:452 msgid "Created:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:476 +#: assets/src/admin/pull-requests/index.js:453 msgid "Updated:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:477 +#: assets/src/admin/pull-requests/index.js:454 msgid "Branch:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:479 +#: assets/src/admin/pull-requests/index.js:456 msgid "GitHub URL:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:481 +#: assets/src/admin/pull-requests/index.js:458 msgid "View on GitHub" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:490 +#: assets/src/admin/pull-requests/index.js:467 msgid "Labels:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:514 +#: assets/src/admin/pull-requests/index.js:491 msgid "Description:" msgstr "" #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/pull-requests/index.js:540 +#: assets/src/admin/pull-requests/index.js:517 msgid "Merged By:" msgstr "" From dbb2056107d716951e9fb266d94af3d4f85752b7 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 15 Sep 2025 13:04:16 +0530 Subject: [PATCH 12/18] chore: update PR endpoint and GH code scanner issues --- assets/src/admin/plugin-manager/index.js | 5 +- assets/src/admin/pull-requests/index.js | 4 +- assets/src/js/utils.js | 21 +- .../plugin-configs/class-secret-key.php | 4 +- inc/classes/rest/class-basic-options.php | 8 +- .../rest/class-github-pull-requests.php | 10 +- inc/classes/rest/class-workflow.php | 4 +- languages/oneupdate.pot | 256 +++++++++--------- 8 files changed, 160 insertions(+), 152 deletions(-) diff --git a/assets/src/admin/plugin-manager/index.js b/assets/src/admin/plugin-manager/index.js index 6f3ce2c..a902243 100644 --- a/assets/src/admin/plugin-manager/index.js +++ b/assets/src/admin/plugin-manager/index.js @@ -33,6 +33,7 @@ import { decodeEntities } from '@wordpress/html-entities'; import { arrowLeft, plus, loop } from '@wordpress/icons'; import PluginsSharing from '../../components/PluginsSharing'; import S3ZipUploader from '../../components/S3ZipUploader'; +import { PurifyElement } from '../../js/utils'; // Declare the OneUpdatePlugins variable const OneUpdatePlugins = window.OneUpdatePlugins || {}; @@ -289,11 +290,11 @@ const PluginManager = () => { name: decodeEntities( sampleSitePlugin?.Name || pluginInfo.name || slug ), description: decodeEntities( sampleSitePlugin?.Description || - pluginInfo.sections?.description?.replace( /<[^>]*>/g, '' ).substring( 0, 200 ) + '...' || + PurifyElement( pluginInfo.sections?.description )?.substring( 0, 200 ) + '...' || pluginInfo.short_description || 'No description available.', ), - author: decodeEntities( sampleSitePlugin?.Author || pluginInfo.author?.replace( /<[^>]*>/g, '' ) || 'Unknown' ), + author: decodeEntities( sampleSitePlugin?.Author || PurifyElement( pluginInfo.author ) || 'Unknown' ), version: sharedPluginData.version || sampleSitePlugin?.Version || pluginInfo.version || '0.0.0', plugin_uri: sampleSitePlugin?.PluginURI || pluginInfo.homepage || '', is_public: sampleSitePlugin?.is_public !== undefined ? sampleSitePlugin.is_public : Boolean( pluginInfo.download_link ), diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index 324709c..49b19d0 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -73,7 +73,7 @@ const GitHubPullRequests = () => { } const response = await fetch( - `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?${ params.toString() }`, + `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?${ params.toString() }&_=${ new Date().getTime() }`, { headers: { 'Content-Type': 'application/json', @@ -130,7 +130,7 @@ const GitHubPullRequests = () => { setDetailsLoading( true ); try { const response = await fetch( - `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?pr_number=${ prNumber }`, + `${ API_NAMESPACE }/pull-requests/${ selectedRepo }?pr_number=${ prNumber }&_=${ new Date().getTime() }`, { headers: { 'Content-Type': 'application/json', diff --git a/assets/src/js/utils.js b/assets/src/js/utils.js index 9d24970..85fc254 100644 --- a/assets/src/js/utils.js +++ b/assets/src/js/utils.js @@ -1,12 +1,14 @@ +import DOMPurify from 'dompurify'; + const isURL = ( str ) => { const pattern = new RegExp( - '^(https?:\\/\\/)?' + - '(([a-z\\d]([a-z\\d-]*[a-z\\d])*):([a-z\\d-]*[a-z\\d])*@)?' + - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + - '((\\d{1,3}\\.){3}\\d{1,3}))' + - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + - '(\\?[;&a-z\\d%_.~+=-]*)?' + - '(\\#[-a-z\\d_]*)?$', 'i', + '^https?:\\/\\/' + + '(?:[a-z\\d](?:[a-z\\d-]*[a-z\\d])?\\.)?' + + '[a-z\\d](?:[a-z\\d-]*[a-z\\d])?\\.' + + '[a-z]{2,}' + + '(?::\\d+)?' + + '(?:\\/[^\\s]*)?' + + '$', 'i', ); return pattern.test( str ); }; @@ -20,7 +22,12 @@ const isValidUrl = ( url ) => { } }; +const PurifyElement = ( item ) => { + return DOMPurify.sanitize( item, { ALLOWED_TAGS: [] } ); +}; + export { isURL, isValidUrl, + PurifyElement, }; diff --git a/inc/classes/plugin-configs/class-secret-key.php b/inc/classes/plugin-configs/class-secret-key.php index 5011864..672867e 100644 --- a/inc/classes/plugin-configs/class-secret-key.php +++ b/inc/classes/plugin-configs/class-secret-key.php @@ -42,7 +42,7 @@ public function generate_secret_key(): void { if ( empty( $secret_key ) ) { $secret_key = wp_generate_password( 128, false, false ); // Store the secret key in the database. - update_option( Constants::ONEUPDATE_API_KEY, $secret_key ); + update_option( Constants::ONEUPDATE_API_KEY, $secret_key, false ); } } @@ -72,7 +72,7 @@ public static function get_secret_key(): \WP_REST_Response|\WP_Error { public static function regenerate_secret_key(): \WP_REST_Response|\WP_Error { $regenerated_key = wp_generate_password( 128, false, false ); // Update the option with the new key. - update_option( Constants::ONEUPDATE_API_KEY, $regenerated_key ); + update_option( Constants::ONEUPDATE_API_KEY, $regenerated_key, false ); return rest_ensure_response( array( diff --git a/inc/classes/rest/class-basic-options.php b/inc/classes/rest/class-basic-options.php index 05b8bed..fc1a57f 100644 --- a/inc/classes/rest/class-basic-options.php +++ b/inc/classes/rest/class-basic-options.php @@ -349,7 +349,7 @@ public function set_site_type( \WP_REST_Request $request ): \WP_REST_Response|\W $site_type = sanitize_text_field( $request->get_param( 'site_type' ) ); - update_option( Constants::ONEUPDATE_SITE_TYPE, $site_type ); + update_option( Constants::ONEUPDATE_SITE_TYPE, $site_type, false ); // set transient to indicating that site type has been set for infinite time. set_transient( Constants::ONEUPDATE_SITE_TYPE_TRANSIENT, true, 0 ); @@ -400,7 +400,7 @@ public function set_github_token( \WP_REST_Request $request ): \WP_REST_Response ); } - update_option( Constants::ONEUPDATE_GH_TOKEN, $github_token ); + update_option( Constants::ONEUPDATE_GH_TOKEN, $github_token, false ); return rest_ensure_response( array( @@ -451,7 +451,7 @@ public function set_s3_credentials( \WP_REST_Request $request ): \WP_REST_Respon } // Update S3 credentials in options. - update_option( Constants::ONEUPDATE_S3_CREDENTIALS, $s3_credentials ); + update_option( Constants::ONEUPDATE_S3_CREDENTIALS, $s3_credentials, false ); return rest_ensure_response( array( @@ -507,7 +507,7 @@ public function set_shared_sites( \WP_REST_Request $request ): \WP_REST_Response $gtihub_repos[] = $site['githubRepo'] ?? ''; } - update_option( Constants::ONEUPDATE_SHARED_SITES, $sites_data ); + update_option( Constants::ONEUPDATE_SHARED_SITES, $sites_data, false ); return rest_ensure_response( array( diff --git a/inc/classes/rest/class-github-pull-requests.php b/inc/classes/rest/class-github-pull-requests.php index 17ee846..bab7dcb 100644 --- a/inc/classes/rest/class-github-pull-requests.php +++ b/inc/classes/rest/class-github-pull-requests.php @@ -109,7 +109,7 @@ public function register_routes(): void { * * @return bool */ - public static function permission_callback() { + public static function permission_callback(): bool { return current_user_can( 'manage_options' ); } @@ -141,7 +141,7 @@ public function get_pull_requests( \WP_REST_Request $request ): \WP_REST_Respons ); } - // if pr_number is not provided, get all pull requests. + // if pr_number & search query is not provided, get all pull requests. if ( empty( $pr_number ) && empty( $search_query ) ) { return self::get_all_pull_requests( $gh_owner, $gh_repo, $pr_state, $gh_token, $per_page, $page ); } @@ -170,7 +170,7 @@ public function get_pull_requests( \WP_REST_Request $request ): \WP_REST_Respons private static function get_all_pull_requests( string $gh_owner, string $gh_repo, string $pr_state = 'open', string $gh_token, int $per_page = 25, int $page = 1 ): \WP_REST_Response { // gh api endpoint to get pull requests. - $gh_api_endpoint = "https://api.github.com/repos/{$gh_owner}/{$gh_repo}/pulls?state={$pr_state}&per_page={$per_page}&page={$page}"; + $gh_api_endpoint = "https://api.github.com/repos/{$gh_owner}/{$gh_repo}/pulls?state={$pr_state}&per_page={$per_page}&page={$page}&order=desc&sort=committer-date"; $response = wp_safe_remote_get( $gh_api_endpoint, @@ -286,7 +286,7 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, } $response = wp_safe_remote_get( - $gh_api_endpoint, + $gh_api_endpoint . '&order=desc&sort=committer-date', array( 'headers' => array( 'Authorization' => "Bearer {$gh_token}", @@ -378,7 +378,7 @@ private static function search_pull_requests( string $gh_owner, string $gh_repo, private static function search_pull_requests_with_query_and_state( string $gh_owner, string $gh_repo, string $search_query, string $gh_token, int $per_page, int $page, string $pr_state ): \WP_REST_Response { // Use search API with state filter in query. - $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr+state:{$pr_state}&per_page={$per_page}&page={$page}"; + $gh_api_endpoint = "https://api.github.com/search/issues?q={$search_query}+repo:{$gh_owner}/{$gh_repo}+type:pr+state:{$pr_state}&per_page={$per_page}&page={$page}&order=desc&sort=committer-date"; $response = wp_safe_remote_get( $gh_api_endpoint, diff --git a/inc/classes/rest/class-workflow.php b/inc/classes/rest/class-workflow.php index 79cf272..56a43a6 100644 --- a/inc/classes/rest/class-workflow.php +++ b/inc/classes/rest/class-workflow.php @@ -763,7 +763,7 @@ public function update_oneupdate_plugins_options( \WP_REST_Request $request ): \ } } // update the active plugins options. - update_option( Constants::ONEUPDATE_ACTIVE_PLUGINS, $active_plugins ); + update_option( Constants::ONEUPDATE_ACTIVE_PLUGINS, $active_plugins, false ); } if ( 'activate' === $plugin_type ) { @@ -780,7 +780,7 @@ public function update_oneupdate_plugins_options( \WP_REST_Request $request ): \ } } - update_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, $oneupdate_plugin_activate ); + update_option( Constants::ONEUPDATE_PLUGINS_OPTIONS, $oneupdate_plugin_activate, false ); if ( ! empty( $plugins ) ) { Cache::rebuild_transient_for_single_plugin( diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index fbc278c..ce13f39 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-09-09T07:07:24+00:00\n" +"POT-Creation-Date: 2025-09-15T07:27:08+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" @@ -209,7 +209,7 @@ msgstr "" #: inc/classes/settings/class-shared-sites.php:104 #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1189 +#: assets/src/admin/plugin-manager/index.js:1190 msgid "OneUpdate - Plugin Manager" msgstr "" @@ -246,7 +246,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:569 +#: assets/src/admin/plugin-manager/index.js:570 #: assets/src/components/PluginsSharing.js:72 msgid "Latest" msgstr "" @@ -270,7 +270,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2047 +#: assets/src/admin/plugin-manager/index.js:2048 #: assets/src/components/PluginCard.js:118 #: assets/src/components/PluginsSharing.js:144 msgid "Version" @@ -293,7 +293,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1282 +#: assets/src/admin/plugin-manager/index.js:1283 #: assets/src/components/PluginsSharing.js:198 msgid "Search Plugins" msgstr "" @@ -328,7 +328,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1727 +#: assets/src/admin/plugin-manager/index.js:1728 #: assets/src/components/PluginGrid.js:181 #: assets/src/components/PluginsSharing.js:376 msgid "No plugins found" @@ -442,7 +442,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:5 #: assets/build/js/settings.js:1 -#: assets/src/admin/plugin-manager/index.js:1897 +#: assets/src/admin/plugin-manager/index.js:1898 #: assets/src/components/PluginsSharing.js:517 #: assets/src/components/PluginsSharing.js:749 #: assets/src/components/S3ZipUploader.js:156 @@ -459,7 +459,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1789 +#: assets/src/admin/plugin-manager/index.js:1790 #: assets/src/components/PluginsSharing.js:646 #: assets/src/components/S3ZipUploader.js:221 #: assets/src/components/S3ZipUploader.js:819 @@ -468,7 +468,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1801 +#: assets/src/admin/plugin-manager/index.js:1802 #: assets/src/components/PluginsSharing.js:664 #: assets/src/components/S3ZipUploader.js:77 #: assets/src/components/S3ZipUploader.js:233 @@ -562,9 +562,9 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:977 -#: assets/src/admin/plugin-manager/index.js:1055 -#: assets/src/admin/plugin-manager/index.js:1082 +#: assets/src/admin/plugin-manager/index.js:978 +#: assets/src/admin/plugin-manager/index.js:1056 +#: assets/src/admin/plugin-manager/index.js:1083 #: assets/src/components/S3ZipUploader.js:342 msgid "Back" msgstr "" @@ -690,7 +690,7 @@ msgstr "" #: assets/build/js/plugin-manager.js:1 #: assets/build/js/plugin-manager.js:3 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/plugin-manager/index.js:1293 +#: assets/src/admin/plugin-manager/index.js:1294 #: assets/src/admin/pull-requests/index.js:292 #: assets/src/admin/pull-requests/index.js:307 #: assets/src/components/S3ZipUploader.js:1000 @@ -716,556 +716,556 @@ msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:5 #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:1298 -#: assets/src/admin/plugin-manager/index.js:1869 -#: assets/src/admin/plugin-manager/index.js:2280 +#: assets/src/admin/plugin-manager/index.js:1299 +#: assets/src/admin/plugin-manager/index.js:1870 +#: assets/src/admin/plugin-manager/index.js:2281 #: assets/src/components/S3ZipUploader.js:1055 msgid "Active" msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:88 +#: assets/src/admin/plugin-manager/index.js:89 msgid "Failed to perform S3 health check." msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:116 +#: assets/src/admin/plugin-manager/index.js:117 msgid "Failed to fetch sites data." msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:349 +#: assets/src/admin/plugin-manager/index.js:350 msgid "Fetching sites…" msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:355 +#: assets/src/admin/plugin-manager/index.js:356 msgid "No sites found." msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:361 +#: assets/src/admin/plugin-manager/index.js:362 msgid "Fetching shared plugins data…" msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:371 +#: assets/src/admin/plugin-manager/index.js:372 msgid "Fetching real-time plugin status…" msgstr "" #: assets/build/js/plugin-manager.js:1 -#: assets/src/admin/plugin-manager/index.js:382 +#: assets/src/admin/plugin-manager/index.js:383 msgid "Fetching plugins info from" msgstr "" #. translators: %s is the error message #: assets/build/js/plugin-manager.js:2 -#: assets/src/admin/plugin-manager/index.js:399 +#: assets/src/admin/plugin-manager/index.js:400 #, js-format msgid "Error fetching plugins data: %s" msgstr "" #: assets/build/js/plugin-manager.js:2 -#: assets/src/admin/plugin-manager/index.js:400 +#: assets/src/admin/plugin-manager/index.js:401 msgid "Unknown error" msgstr "" #. translators: %s is the list of sites with updates available #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:448 +#: assets/src/admin/plugin-manager/index.js:449 #, js-format msgid "Updates available on: %s" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:588 +#: assets/src/admin/plugin-manager/index.js:589 msgid "No sites available for installation." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:912 +#: assets/src/admin/plugin-manager/index.js:913 msgid "Activate Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:913 +#: assets/src/admin/plugin-manager/index.js:914 msgid "Select sites where you want to activate this plugin." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:914 +#: assets/src/admin/plugin-manager/index.js:915 msgid "Activate on Selected Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:919 +#: assets/src/admin/plugin-manager/index.js:920 msgid "Deactivate Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:920 +#: assets/src/admin/plugin-manager/index.js:921 msgid "Select sites where you want to deactivate this plugin." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:921 +#: assets/src/admin/plugin-manager/index.js:922 msgid "Deactivate on Selected Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:926 +#: assets/src/admin/plugin-manager/index.js:927 msgid "Update Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:927 +#: assets/src/admin/plugin-manager/index.js:928 msgid "Select sites where you want to update this plugin." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:928 +#: assets/src/admin/plugin-manager/index.js:929 msgid "Update on Selected Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:933 +#: assets/src/admin/plugin-manager/index.js:934 msgid "Change Plugin Version" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:934 +#: assets/src/admin/plugin-manager/index.js:935 msgid "Choose plugin version and select sites where you want to change/update version." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:935 +#: assets/src/admin/plugin-manager/index.js:936 msgid "Change Version on Selected Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:940 +#: assets/src/admin/plugin-manager/index.js:941 msgid "Remove Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:941 +#: assets/src/admin/plugin-manager/index.js:942 msgid "Select sites where you want to remove this plugin." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:942 +#: assets/src/admin/plugin-manager/index.js:943 msgid "Remove from Selected Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:947 +#: assets/src/admin/plugin-manager/index.js:948 msgid "Plugin Action" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:948 +#: assets/src/admin/plugin-manager/index.js:949 msgid "Select sites for this action." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:949 +#: assets/src/admin/plugin-manager/index.js:950 msgid "Execute Action" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1104 +#: assets/src/admin/plugin-manager/index.js:1105 #: assets/src/components/PluginGrid.js:157 msgid "Loading plugins…" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:982 -#: assets/src/admin/plugin-manager/index.js:1215 +#: assets/src/admin/plugin-manager/index.js:983 +#: assets/src/admin/plugin-manager/index.js:1216 msgid "Add Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:985 +#: assets/src/admin/plugin-manager/index.js:986 msgid "Choose how you want to add a plugin to your sites." msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:1001 -#: assets/src/admin/plugin-manager/index.js:1995 +#: assets/src/admin/plugin-manager/index.js:1002 +#: assets/src/admin/plugin-manager/index.js:1996 msgid "Public Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1002 +#: assets/src/admin/plugin-manager/index.js:1003 msgid "Add a plugin from the WordPress.org repository." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1010 +#: assets/src/admin/plugin-manager/index.js:1011 msgid "Invalid S3 credentials. Please check your settings." msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:1026 -#: assets/src/admin/plugin-manager/index.js:1996 +#: assets/src/admin/plugin-manager/index.js:1027 +#: assets/src/admin/plugin-manager/index.js:1997 msgid "Private Plugin" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1027 +#: assets/src/admin/plugin-manager/index.js:1028 msgid "Upload a custom plugin from your computer." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1192 +#: assets/src/admin/plugin-manager/index.js:1193 msgid "Manage plugins across all your WordPress sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:818 -#: assets/src/admin/plugin-manager/index.js:826 -#: assets/src/admin/plugin-manager/index.js:882 +#: assets/src/admin/plugin-manager/index.js:819 +#: assets/src/admin/plugin-manager/index.js:827 +#: assets/src/admin/plugin-manager/index.js:883 msgid "Failed to update plugins." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:852 +#: assets/src/admin/plugin-manager/index.js:853 msgid "Plugins update's PR raised successfully." msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1205 +#: assets/src/admin/plugin-manager/index.js:1206 msgid "Update All" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1285 +#: assets/src/admin/plugin-manager/index.js:1286 msgid "Search by name or description…" msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/plugin-manager/index.js:1297 +#: assets/src/admin/plugin-manager/index.js:1298 #: assets/src/admin/pull-requests/index.js:51 msgid "All Status" msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:1299 -#: assets/src/admin/plugin-manager/index.js:2281 +#: assets/src/admin/plugin-manager/index.js:1300 +#: assets/src/admin/plugin-manager/index.js:2282 msgid "Inactive" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1304 +#: assets/src/admin/plugin-manager/index.js:1305 msgid "Type" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1308 +#: assets/src/admin/plugin-manager/index.js:1309 msgid "All Types" msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1309 -#: assets/src/admin/plugin-manager/index.js:1483 +#: assets/src/admin/plugin-manager/index.js:1310 +#: assets/src/admin/plugin-manager/index.js:1484 msgid "Public" msgstr "" #: assets/build/js/plugin-manager.js:3 #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1310 -#: assets/src/admin/plugin-manager/index.js:1484 +#: assets/src/admin/plugin-manager/index.js:1311 +#: assets/src/admin/plugin-manager/index.js:1485 msgid "Private" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1315 +#: assets/src/admin/plugin-manager/index.js:1316 msgid "Updates" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1319 +#: assets/src/admin/plugin-manager/index.js:1320 msgid "All Updates" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1320 +#: assets/src/admin/plugin-manager/index.js:1321 msgid "Updates Available" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1326 +#: assets/src/admin/plugin-manager/index.js:1327 msgid "Filter By Site" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1330 +#: assets/src/admin/plugin-manager/index.js:1331 msgid "All Sites" msgstr "" #: assets/build/js/plugin-manager.js:3 -#: assets/src/admin/plugin-manager/index.js:1331 +#: assets/src/admin/plugin-manager/index.js:1332 msgid "Common plugins" msgstr "" #. translators: %1$d is the number of filtered plugins, %2$d is the total number of plugins #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1348 +#: assets/src/admin/plugin-manager/index.js:1349 #, js-format msgid "Showing %1$d of %2$d plugins" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1492 +#: assets/src/admin/plugin-manager/index.js:1493 msgid "Plugin Actions" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1516 +#: assets/src/admin/plugin-manager/index.js:1517 msgid "Change version/update" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1529 +#: assets/src/admin/plugin-manager/index.js:1530 msgid "Activate on sites" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1542 +#: assets/src/admin/plugin-manager/index.js:1543 msgid "Deactivate on sites" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1558 +#: assets/src/admin/plugin-manager/index.js:1559 msgid "Install on sites" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1569 +#: assets/src/admin/plugin-manager/index.js:1570 msgid "Uninstall from sites" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1581 +#: assets/src/admin/plugin-manager/index.js:1582 msgid "Plugin Details" msgstr "" #: assets/build/js/plugin-manager.js:4 -#: assets/src/admin/plugin-manager/index.js:1694 +#: assets/src/admin/plugin-manager/index.js:1695 msgid "1 site needs update" msgstr "" #. translators: %d is the number of sites needing updates #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1697 +#: assets/src/admin/plugin-manager/index.js:1698 #, js-format msgid "%d sites need updates" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1730 +#: assets/src/admin/plugin-manager/index.js:1731 msgid "No plugins found matching your filters. Try adjusting your search criteria." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1763 +#: assets/src/admin/plugin-manager/index.js:1764 msgid "Select Version" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1767 +#: assets/src/admin/plugin-manager/index.js:1768 msgid "Select a version…" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1770 +#: assets/src/admin/plugin-manager/index.js:1771 msgid "Choose from the latest 5 stable versions available" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1776 +#: assets/src/admin/plugin-manager/index.js:1777 msgid "No stable versions available for this plugin." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1818 +#: assets/src/admin/plugin-manager/index.js:1819 msgid "No sites available for activation. Plugin is already active on all sites." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1823 +#: assets/src/admin/plugin-manager/index.js:1824 msgid "No sites available for deactivation. Plugin is not active on any sites." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:1828 +#: assets/src/admin/plugin-manager/index.js:1829 msgid "No sites have updates available for this plugin." msgstr "" #: assets/build/js/plugin-manager.js:5 #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:1874 -#: assets/src/admin/plugin-manager/index.js:2295 +#: assets/src/admin/plugin-manager/index.js:1875 +#: assets/src/admin/plugin-manager/index.js:2296 msgid "Update Available" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:648 +#: assets/src/admin/plugin-manager/index.js:649 msgid "Failed to execute action." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:652 +#: assets/src/admin/plugin-manager/index.js:653 msgid "Action failed." msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:685 +#: assets/src/admin/plugin-manager/index.js:686 msgid "Activated" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:688 +#: assets/src/admin/plugin-manager/index.js:689 msgid "Deactivated" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:691 +#: assets/src/admin/plugin-manager/index.js:692 msgid "Updated" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:694 +#: assets/src/admin/plugin-manager/index.js:695 msgid "Installed" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:697 +#: assets/src/admin/plugin-manager/index.js:698 msgid "Removed" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:700 +#: assets/src/admin/plugin-manager/index.js:701 msgid "Version change" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:703 +#: assets/src/admin/plugin-manager/index.js:704 msgid "Executed" msgstr "" #: assets/build/js/plugin-manager.js:5 -#: assets/src/admin/plugin-manager/index.js:709 +#: assets/src/admin/plugin-manager/index.js:710 msgid "selected sites" msgstr "" #. translators: %s is the plugin name, %s is the action verb, %s is the site names #: assets/build/js/plugin-manager.js:6 -#: assets/src/admin/plugin-manager/index.js:715 +#: assets/src/admin/plugin-manager/index.js:716 #, js-format msgid "%1$s %2$s PR raised successfully." msgstr "" #. translators: %s is the plugin name, %s is the action verb, %s is the site names #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:723 +#: assets/src/admin/plugin-manager/index.js:724 #, js-format msgid "%1$s %2$s successfully on %3$s." msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:1912 +#: assets/src/admin/plugin-manager/index.js:1913 msgid "Processing…" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2023 +#: assets/src/admin/plugin-manager/index.js:2024 msgid "Description" msgstr "" #: assets/build/js/plugin-manager.js:7 #: assets/build/js/pull-requests.js:1 -#: assets/src/admin/plugin-manager/index.js:2041 +#: assets/src/admin/plugin-manager/index.js:2042 #: assets/src/admin/pull-requests/index.js:306 msgid "Author" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2053 +#: assets/src/admin/plugin-manager/index.js:2054 msgid "Sites" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2062 +#: assets/src/admin/plugin-manager/index.js:2063 msgid "Downloads" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2072 +#: assets/src/admin/plugin-manager/index.js:2073 msgid "Requires WordPress" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2080 +#: assets/src/admin/plugin-manager/index.js:2081 msgid "Tested up to" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2088 +#: assets/src/admin/plugin-manager/index.js:2089 msgid "Requires PHP" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2096 +#: assets/src/admin/plugin-manager/index.js:2097 msgid "Last Updated" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2109 +#: assets/src/admin/plugin-manager/index.js:2110 msgid "Tags" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2136 +#: assets/src/admin/plugin-manager/index.js:2137 msgid "Plugin URI" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2153 +#: assets/src/admin/plugin-manager/index.js:2154 msgid "Installation" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2170 +#: assets/src/admin/plugin-manager/index.js:2171 msgid "FAQ" msgstr "" #: assets/build/js/plugin-manager.js:7 -#: assets/src/admin/plugin-manager/index.js:2187 +#: assets/src/admin/plugin-manager/index.js:2188 msgid "Changelog" msgstr "" #: assets/build/js/plugin-manager.js:7 #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:2211 -#: assets/src/admin/plugin-manager/index.js:2312 +#: assets/src/admin/plugin-manager/index.js:2212 +#: assets/src/admin/plugin-manager/index.js:2313 msgid "Close" msgstr "" #. translators: %s is the plugin name #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:2223 +#: assets/src/admin/plugin-manager/index.js:2224 #, js-format msgid "%s - Sites" msgstr "" #: assets/build/js/plugin-manager.js:8 -#: assets/src/admin/plugin-manager/index.js:2232 +#: assets/src/admin/plugin-manager/index.js:2233 msgid "Plugin status across all sites" msgstr "" From 6f8877e3f54f03a8ec9cf3b40ff3c07f4fc639f7 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Mon, 15 Sep 2025 16:48:09 +0530 Subject: [PATCH 13/18] fix: typo --- inc/classes/class-s3-upload.php | 2 +- inc/classes/plugin-configs/class-constants.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/classes/class-s3-upload.php b/inc/classes/class-s3-upload.php index ccb9bea..eaac125 100644 --- a/inc/classes/class-s3-upload.php +++ b/inc/classes/class-s3-upload.php @@ -82,5 +82,5 @@ public function oneupdate_s3_zip_cleanup_event(): void { ) ); } - // phpcs:enable.-- its custom query to cleanup s3 bucket & history table. + // phpcs:enable -- its custom query to cleanup s3 bucket & history table. } diff --git a/inc/classes/plugin-configs/class-constants.php b/inc/classes/plugin-configs/class-constants.php index a03c61b..2f03039 100644 --- a/inc/classes/plugin-configs/class-constants.php +++ b/inc/classes/plugin-configs/class-constants.php @@ -36,7 +36,7 @@ class Constants { public const ONEUPDATE_SHARED_SITES = 'oneupdate_shared_sites'; /** - * S3 creadentials. + * S3 credentials. * * @var string */ From 5e653f7b52856c35071df9ebf7c4f2a5ead2f92b Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Tue, 16 Sep 2025 10:10:22 +0530 Subject: [PATCH 14/18] chore: added pot generation script --- composer.json | 5 +++++ languages/oneupdate.pot | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5643836..d882971 100644 --- a/composer.json +++ b/composer.json @@ -32,5 +32,10 @@ }, "require": { "aws/aws-sdk-php": "^3.343" + }, + "scripts": { + "pot": "wp i18n make-pot . languages/oneupdate.pot --domain=oneupdate --exclude=node_modules,vendor", + "phpcs": "vendor/bin/phpcs --standard=./phpcs.xml.dist", + "phpcs:fix": "vendor/bin/phpcbf --standard=./phpcs.xml.dist" } } diff --git a/languages/oneupdate.pot b/languages/oneupdate.pot index ce13f39..148cce1 100644 --- a/languages/oneupdate.pot +++ b/languages/oneupdate.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-09-15T07:27:08+00:00\n" +"POT-Creation-Date: 2025-09-16T04:32:57+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: oneupdate\n" From 87c8b9b111fa51473e87a34199e0ddc6647ae9a7 Mon Sep 17 00:00:00 2001 From: Utsav Patel Date: Fri, 19 Sep 2025 12:25:47 +0530 Subject: [PATCH 15/18] address PR feedback --- assets/src/admin/pull-requests/index.js | 4 +- inc/classes/class-utils.php | 9 + .../rest/class-github-pull-requests.php | 169 ++++++++---------- languages/oneupdate.pot | 16 +- 4 files changed, 90 insertions(+), 108 deletions(-) diff --git a/assets/src/admin/pull-requests/index.js b/assets/src/admin/pull-requests/index.js index 49b19d0..635dd74 100644 --- a/assets/src/admin/pull-requests/index.js +++ b/assets/src/admin/pull-requests/index.js @@ -86,7 +86,7 @@ const GitHubPullRequests = () => { if ( response?.statusText === 'Unprocessable Entity' ) { setNotice( { type: 'error', - message: __( 'Please enter valid character to search pull requests.' ), + message: __( 'Please enter valid character to search pull requests.', 'oneupdate' ), } ); } else { setNotice( { @@ -407,7 +407,7 @@ const GitHubPullRequests = () => { { __( 'Previous', 'oneupdate' ) } - { __( 'Page', 'oneupdate' ) } { page } { __( 'of', 'oneupdate' ) } { totalPages === 0 ? currentPage : totalPages } + { __( 'Page', 'oneupdate' ) } { page } { __( 'of', 'oneupdate' ) } { totalPages === 0 || totalPages < currentPage ? currentPage : totalPages }