From d330784b7ba21e3fd30ce1b729cef3267b4f5e7a Mon Sep 17 00:00:00 2001 From: madhusudhand Date: Wed, 9 Aug 2023 14:04:04 +0530 Subject: [PATCH 1/3] Add wp-admin menu for git repository configuration --- admin/class-git-integration.php | 142 ++++++++++++++++++ create-block-theme.php | 21 ++- .../class-create-block-theme-settings.php | 57 +++++++ includes/class-create-block-theme.php | 2 + 4 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 admin/class-git-integration.php create mode 100644 includes/class-create-block-theme-settings.php diff --git a/admin/class-git-integration.php b/admin/class-git-integration.php new file mode 100644 index 00000000..fa57aaee --- /dev/null +++ b/admin/class-git-integration.php @@ -0,0 +1,142 @@ +plugin_settings = new Create_Block_Theme_Settings(); + add_action( 'admin_menu', array( $this, 'create_admin_menu' ) ); + } + + public function create_admin_menu() { + if ( ! wp_is_block_theme() ) { + return; + } + $menu_title = _x( 'Create Block Theme: Git Utilities', 'UI String', 'create-block-theme' ); + $page_title = $menu_title; + add_menu_page( $page_title, $menu_title, 'edit_theme_options', 'themes-git-integration', array( $this, 'git_settings_page' ) ); + } + + public function git_settings_page() { + // Add your settings page content here + $git_url_format = 'https://github.com/username/repository'; + $page_title = _x( 'Create Block Theme: Git Utilities', 'UI String', 'create-block-theme' ); + + if ( isset( $_POST['delete_git_config'] ) ) { + $this->delete_settings(); + } elseif ( isset( $_POST['save_git_config'] ) ) { + $this->save_settings( $_POST ); + } + $settings = $this->get_settings(); + $repository_url = $settings['repository_url']; + $default_branch = $settings['default_branch']; + $access_token = $settings['access_token']; + $author_name = $settings['author_name']; + $author_email = $settings['author_email']; + ?> + +
+

+

+ +

+
+
+ + +

' . __( 'Your WordPress site is connected to the git repository.', 'create-block-theme' ) . '✅

'; + echo "

+ " . __( 'Repository URL', 'create-block-theme' ) . ": + $repository_url + " . __( 'Default Branch', 'create-block-theme' ) . ": + $default_branch +

"; + + echo "

"; + + if ( ! empty( $access_token ) ) { + echo '

' . __( 'Access token configured', 'create-block-theme' ) . ' ✅
' . __( 'You can still update with a new access token.', 'create-block-theme' ) . '

'; + } + } else { + ?> +

+
+
+ +
+ +
+
+ +
+ +

+ +
+

+ + +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + +
+
+
+ plugin_settings->get_settings(); + } + + private function save_settings( $settings ) { + $repository_url = sanitize_text_field( $settings['repository_url'] ); + $default_branch = sanitize_text_field( $settings['default_branch'] ); + $access_token = sanitize_text_field( $settings['access_token'] ); + $author_name = sanitize_text_field( $settings['author_name'] ); + $author_email = sanitize_text_field( $settings['author_email'] ); + + // TODO: try cloning the git repository here. + // Show error if it fails + // save the options only if git clone is successful. + + $this->plugin_settings->update_settings( + array( + 'repository_url' => $repository_url, + 'default_branch' => $default_branch, + 'access_token' => $access_token, + 'author_name' => $author_name, + 'author_email' => $author_email, + ) + ); + + echo '

Settings saved!

'; + } + + public function delete_settings() { + $this->plugin_settings->delete_settings(); + // TODO: delete the local clone of the repository + } +} diff --git a/create-block-theme.php b/create-block-theme.php index c723fb79..03f77e88 100644 --- a/create-block-theme.php +++ b/create-block-theme.php @@ -26,7 +26,8 @@ /** * The core plugin class. */ -require plugin_dir_path( __FILE__ ) . 'includes/class-create-block-theme.php'; +require_once plugin_dir_path( __FILE__ ) . 'includes/class-create-block-theme.php'; +require_once plugin_dir_path( __FILE__ ) . 'includes/class-create-block-theme-settings.php'; /** * Begins execution of the plugin. @@ -44,3 +45,21 @@ function run_create_block_theme() { } run_create_block_theme(); + +function create_block_theme_activated() { + // none +} + +function create_block_theme_deactivated() { + // none +} + +function create_block_theme_uninstalled() { + $plugin_settings = new Create_Block_Theme_Settings(); + $plugin_settings->delete_settings(); +} + +// may be some clean up required ??? +register_activation_hook( __FILE__, 'create_block_theme_activated' ); +register_deactivation_hook( __FILE__, 'create_block_theme_deactivated' ); +register_uninstall_hook( __FILE__, 'create_block_theme_uninstalled' ); diff --git a/includes/class-create-block-theme-settings.php b/includes/class-create-block-theme-settings.php new file mode 100644 index 00000000..93ad9a93 --- /dev/null +++ b/includes/class-create-block-theme-settings.php @@ -0,0 +1,57 @@ + get_option( 'repository_url', '' ), + 'default_branch' => get_option( 'default_branch', '' ), + 'access_token' => get_option( 'access_token', '' ), + 'author_name' => get_option( 'author_name', '' ), + 'author_email' => get_option( 'author_email', '' ), + ); + } + + function update_settings( $settings ) { + if ( ! empty( $settings['repository_url'] ) ) { + update_option( 'repository_url', $settings['repository_url'] ); + } + if ( ! empty( $settings['default_branch'] ) ) { + update_option( 'default_branch', $settings['default_branch'] ); + } + if ( ! empty( $settings['access_token'] ) ) { + update_option( 'access_token', $settings['access_token'] ); + } + if ( ! empty( $settings['author_name'] ) ) { + update_option( 'author_name', $settings['author_name'] ); + } + if ( ! empty( $settings['author_email'] ) ) { + update_option( 'author_email', $settings['author_email'] ); + } + } + + function delete_settings() { + delete_option( 'repository_url' ); + delete_option( 'default_branch' ); + delete_option( 'access_token' ); + delete_option( 'author_name' ); + delete_option( 'author_email' ); + } +} diff --git a/includes/class-create-block-theme.php b/includes/class-create-block-theme.php index 1829a515..e0906fbf 100644 --- a/includes/class-create-block-theme.php +++ b/includes/class-create-block-theme.php @@ -42,6 +42,7 @@ private function load_dependencies() { */ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-create-theme.php'; require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-manage-fonts.php'; + require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-git-integration.php'; require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/wp-org-theme-directory.php'; require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-create-block-theme-api.php'; @@ -61,6 +62,7 @@ private function define_admin_hooks() { $plugin_admin = new Create_Block_Theme_Admin(); $manage_fonts_admin = new Manage_Fonts_Admin(); + $git_admin = new Git_Integration_Admin(); $wp_theme_directory = new WP_Theme_Directory(); $plugin_api = new Create_Block_Theme_API(); } From e9e5df9a2556dbd0fa348272b433b0136148847c Mon Sep 17 00:00:00 2001 From: madhusudhand Date: Wed, 9 Aug 2023 14:27:01 +0530 Subject: [PATCH 2/3] update text content --- admin/class-git-integration.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/admin/class-git-integration.php b/admin/class-git-integration.php index fa57aaee..293abda3 100644 --- a/admin/class-git-integration.php +++ b/admin/class-git-integration.php @@ -22,7 +22,6 @@ public function create_admin_menu() { public function git_settings_page() { // Add your settings page content here $git_url_format = 'https://github.com/username/repository'; - $page_title = _x( 'Create Block Theme: Git Utilities', 'UI String', 'create-block-theme' ); if ( isset( $_POST['delete_git_config'] ) ) { $this->delete_settings(); @@ -38,9 +37,9 @@ public function git_settings_page() { ?>
-

-

- +

+

+

@@ -48,7 +47,7 @@ public function git_settings_page() {

' . __( 'Your WordPress site is connected to the git repository.', 'create-block-theme' ) . '✅

'; + echo '

' . __( 'Your WordPress site is connected to the git repository.', 'create-block-theme' ) . ' ✅

'; echo "

" . __( 'Repository URL', 'create-block-theme' ) . ": $repository_url @@ -78,8 +77,8 @@ public function git_settings_page() { if ( empty( $repository_url ) ) { ?> -

- +

+

From e0f81a9bec4938e1ac0c6744181306d71d8feef0 Mon Sep 17 00:00:00 2001 From: madhusudhand Date: Mon, 14 Aug 2023 20:20:49 +0530 Subject: [PATCH 3/3] update admin menu with additonal configuration options --- admin/class-git-integration.php | 13 +- includes/class-create-block-theme-api.php | 29 ++ .../class-create-block-theme-settings.php | 77 ++- includes/class-themes-git-api.php | 69 +++ src/git-integration/admin/index.js | 437 ++++++++++++++++++ src/git-integration/admin/styles.module.css | 57 +++ src/index.js | 4 + 7 files changed, 641 insertions(+), 45 deletions(-) create mode 100644 includes/class-themes-git-api.php create mode 100644 src/git-integration/admin/index.js create mode 100644 src/git-integration/admin/styles.module.css diff --git a/admin/class-git-integration.php b/admin/class-git-integration.php index 293abda3..e2c0158d 100644 --- a/admin/class-git-integration.php +++ b/admin/class-git-integration.php @@ -1,5 +1,6 @@ + +
+ git_api = new Themes_Git_API(); } /** @@ -97,6 +101,31 @@ public function register_rest_routes() { }, ) ); + + // GIT Integration routes + register_rest_route( + 'create-block-theme/v1', + '/settings', + array( + 'methods' => 'GET', + 'callback' => array( $this->git_api, 'get_settings' ), + 'permission_callback' => function () { + return current_user_can( 'edit_theme_options' ); + }, + ) + ); + + register_rest_route( + 'create-block-theme/v1', + '/update-git-connection', + array( + 'methods' => 'POST', + 'callback' => array( $this->git_api, 'update_git_repo' ), + 'permission_callback' => function () { + return current_user_can( 'edit_theme_options' ); + }, + ) + ); } function rest_get_readme_data( $request ) { diff --git a/includes/class-create-block-theme-settings.php b/includes/class-create-block-theme-settings.php index 93ad9a93..d9491bbe 100644 --- a/includes/class-create-block-theme-settings.php +++ b/includes/class-create-block-theme-settings.php @@ -1,57 +1,46 @@ get_option( 'repository_url', '' ), - 'default_branch' => get_option( 'default_branch', '' ), - 'access_token' => get_option( 'access_token', '' ), - 'author_name' => get_option( 'author_name', '' ), - 'author_email' => get_option( 'author_email', '' ), + 'connected_repos' => get_option( 'connected_repos', array() ), + ); + } + + function add_git_connection( $connection ) { + $repos = get_option( 'connected_repos', array() ); + array_push( $repos, $connection ); + update_option( 'connected_repos', $repos ); + } + + function update_git_connection( $connection, $theme_slug ) { + // TODO: update only fields with values. keep the old values if value is not set + $repos = get_option( 'connected_repos', array() ); + $repos = array_map( + function( $repo ) use ( $connection, $theme_slug ) { + if ( $repo['themeSlug'] === $theme_slug ) { + return array_merge( $repo, $connection ); + } + return $repo; + }, + $repos ); + update_option( 'connected_repos', $repos ); } - function update_settings( $settings ) { - if ( ! empty( $settings['repository_url'] ) ) { - update_option( 'repository_url', $settings['repository_url'] ); - } - if ( ! empty( $settings['default_branch'] ) ) { - update_option( 'default_branch', $settings['default_branch'] ); - } - if ( ! empty( $settings['access_token'] ) ) { - update_option( 'access_token', $settings['access_token'] ); - } - if ( ! empty( $settings['author_name'] ) ) { - update_option( 'author_name', $settings['author_name'] ); - } - if ( ! empty( $settings['author_email'] ) ) { - update_option( 'author_email', $settings['author_email'] ); - } + function delete_git_connection( $theme_slug ) { + $repos = get_option( 'connected_repos', array() ); + $repos = array_filter( + $repos, + function( $repo ) use ( $theme_slug ) { + return $repo['themeSlug'] !== $theme_slug; + } + ); + update_option( 'connected_repos', $repos ); } function delete_settings() { - delete_option( 'repository_url' ); - delete_option( 'default_branch' ); - delete_option( 'access_token' ); - delete_option( 'author_name' ); - delete_option( 'author_email' ); + delete_option( 'connected_repos' ); } } diff --git a/includes/class-themes-git-api.php b/includes/class-themes-git-api.php new file mode 100644 index 00000000..975ed5b3 --- /dev/null +++ b/includes/class-themes-git-api.php @@ -0,0 +1,69 @@ +plugin_settings = new Create_Block_Theme_Settings(); + } + + public function get_settings() { + try { + $settings = $this->plugin_settings->get_settings(); + // TODO: mask accessToken + + return array( 'settings' => $settings ); + } catch ( \Throwable $th ) { + return array( 'status' => 'error' ); + } + } + + public function update_git_repo( $request ) { + $req_params = $request->get_params(); + $action = $req_params['action']; + $repository = $req_params['repository']; + $theme_slug = $req_params['themeSlug']; + $theme_name = $req_params['themeName']; + + // do not save any other params + $repository = array( + 'repositoryUrl' => $repository['repositoryUrl'], + 'defaultBranch' => $repository['defaultBranch'], + 'accessToken' => $repository['accessToken'], + 'authorName' => $repository['authorName'], + 'authorEmail' => $repository['authorEmail'], + ); + + try { + if ( 'create' === $action ) { + $this->plugin_settings->add_git_connection( + array_merge( + $repository, + array( + 'themeSlug' => $theme_slug, + 'themeName' => $theme_name, + ) + ) + ); + } elseif ( 'update' === $action ) { + $this->plugin_settings->update_git_connection( $repository, $theme_slug ); + } elseif ( 'delete' === $action ) { + $this->plugin_settings->delete_git_connection( $theme_slug ); + } else { + return array( + 'status' => 'error', + 'message' => 'Invalid action.', + ); + } + + return array( 'status' => 'ok' ); + } catch ( \Throwable $th ) { + return array( + 'status' => 'error', + 'message' => $th . __toString(), + ); + } + } +} diff --git a/src/git-integration/admin/index.js b/src/git-integration/admin/index.js new file mode 100644 index 00000000..3ff46be5 --- /dev/null +++ b/src/git-integration/admin/index.js @@ -0,0 +1,437 @@ +import { __ } from '@wordpress/i18n'; +import styles from './styles.module.css'; +import { + Button, + // eslint-disable-next-line + __experimentalInputControl as InputControl, + // eslint-disable-next-line + __experimentalText as Text, + RadioControl, + Notice, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { Fragment, useEffect, useState } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; + +export default function GitIntegrationAdminPage() { + const [ view, setView ] = useState( 'all-connections' ); + const [ repos, setRepos ] = useState( [] ); + const [ selectedRepo, setSelectedRepo ] = useState(); + const [ allThemesConnected, setAllThemesConnected ] = useState( false ); + const [ activeThemeConnected, setActiveThemeConnected ] = useState( false ); + const [ theme, setTheme ] = useState( { + name: '', + } ); + useSelect( ( select ) => { + const themeData = select( 'core' ).getCurrentTheme(); + if ( ! themeData ) return; + setTheme( { + name: themeData.name.raw, + slug: themeData.textdomain, + } ); + }, [] ); + + useEffect( () => { + fetchRepos(); + }, [] ); + + useEffect( () => { + ( repos || [] ) + .filter( + ( repo ) => ! repo.themeSlug || repo.themeSlug === theme.slug + ) + .forEach( ( repo ) => { + if ( ! repo.themeSlug ) { + setAllThemesConnected( true ); + } else if ( repo.themeSlug === theme.slug ) { + setActiveThemeConnected( true ); + } + } ); + }, [ repos, theme.slug ] ); + + const fetchRepos = () => { + apiFetch( { + path: '/create-block-theme/v1/settings', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } ).then( ( response ) => { + // TODO: sort the result + setRepos( response.settings.connected_repos ); + } ); + }; + + const onChange = ( action, data ) => { + if ( [ 'canceled', 'success' ].includes( action ) ) { + setView( 'connections' ); + fetchRepos(); + } else if ( action === 'update' ) { + setSelectedRepo( data ); + setView( 'new-connection' ); + } else { + setSelectedRepo(); + setView( 'new-connection' ); + } + }; + + return ( +
+
+

+ { __( + 'Create Block Theme: Git Utilities', + 'create-block-theme' + ) } +

+

+ { __( + 'Connect your WordPress site themes with Git repositories. Pull theme changes from the connected repository and commit theme changes to the repository.', + 'create-block-theme' + ) } +

+
+
+ { view === 'new-connection' ? ( +
+ +
+ ) : ( +
+ +
+ ) } +
+
+ ); +} + +function GitConfigurationForm( { + repository, + theme, + nonce, + allThemesConnected, + activeThemeConnected, + onChange, +} ) { + repository = repository || {}; + const editMode = repository.repositoryUrl; + const [ error, setError ] = useState( '' ); + const [ config, setConfig ] = useState( { + connectionType: ! allThemesConnected ? 'all-themes' : 'active-theme', + repositoryUrl: repository.repositoryUrl || '', + defaultBranch: repository.defaultBranch || '', + accessToken: '', + authorName: repository.authorName || '', + authorEmail: repository.authorEmail || '', + } ); + + const validateForm = () => { + // TODO: add validations + if ( ! config.repositoryUrl ) { + return 'Repository Url is required.'; + } + + if ( allThemesConnected && config.connectionType === 'all-themes' ) { + return 'A repository is already connected with all themes.'; + } else if ( + activeThemeConnected && + config.connectionType === 'active-theme' + ) { + return `A repository is already connected with the active theme ${ theme.name }`; + } + }; + + const onSubmit = ( action ) => { + let postData = {}; + if ( action === 'create' ) { + const formError = validateForm(); + setError( formError ); + if ( formError ) { + return; + } + postData = { + action, + repository: config, + themeSlug: + config.connectionType === 'all-themes' ? '' : theme.slug, + themeName: + config.connectionType === 'all-themes' ? '' : theme.name, + }; + } else if ( action === 'update' || action === 'delete' ) { + postData = { + action, + repository: config, + themeSlug: repository.themeSlug, + }; + } + + apiFetch( { + path: '/create-block-theme/v1/update-git-connection', + method: 'POST', + data: postData, + headers: { + 'Content-Type': 'application/json', + }, + } ) + .then( ( response ) => { + if ( response.status === 'error' ) { + setError( 'Failed to connect repository.' ); + return; + } + onChange( 'success' ); + } ) + .catch( () => { + setError( 'Failed to connect repository.' ); + } ); + }; + + return ( +
+

+ { editMode + ? __( 'Edit repository connection', 'create-block-theme' ) + : __( 'Connect a repository', 'create-block-theme' ) } +

+ + { editMode ? ( + <> +
+ Repository Url + : { repository.repositoryUrl } + Default Branch + : { repository.defaultBranch } + Connected Theme + + { ': ' + + ( config.connectionType === 'all-themes' + ? __( 'All Themes', 'create-block-theme' ) + : `${ theme.name }` ) } + +
+
+ + + ) : ( + <> + + setConfig( { ...config, repositoryUrl: value } ) + } + /> +
+ + setConfig( { ...config, defaultBranch: value } ) + } + /> +
+ + setConfig( { + ...config, + connectionType: value, + } ) + } + /> + + ) } +

+ { __( + 'Following options are required if the repository is private or if you want to commit theme changes to the repository.', + 'create-block-theme' + ) } +

+ + setConfig( { ...config, accessToken: value } ) + } + /> +
+ + setConfig( { ...config, authorName: value } ) + } + /> +
+ + setConfig( { ...config, authorEmail: value } ) + } + /> +
+ { error && ( + <> + + { error } + +
+ + ) } +
+ + +
+
+
+ ); +} + +function ConnectedRepositories( { repos, onChange } ) { + return ( +
+
+

+ { __( 'Connected repositories', 'create-block-theme' ) } +

+ +
+ { ! Array.isArray( repos ) || ! repos.length ? ( + <> +

+ { __( + 'Site is not connected to any repository.', + 'create-block-theme' + ) } +

+ + ) : ( + <> +
+
+
+ Repository Url +
+
+ Default Branch +
+
+ Access Token +
+
+ Author +
+
+ Connected Theme +
+ { repos.map( ( repo, index ) => ( + +
+
+ onChange( 'update', repo ) + } + onKeyUp={ () => + onChange( 'update', repo ) + } + tabIndex="0" + role="link" + > + Edit +
+
+
+ { repo.repositoryUrl } +
+
+ { repo.defaultBranch } +
+
+ { repo.accessToken ? '✅' : '' } +
+
+ { repo.authorName } + { repo.authorEmail + ? ` <${ repo.authorEmail }>` + : '' } +
+
+ { repo.themeName || 'All Themes' } +
+
+ ) ) } +
+ + ) } +
+ ); +} diff --git a/src/git-integration/admin/styles.module.css b/src/git-integration/admin/styles.module.css new file mode 100644 index 00000000..748dfd07 --- /dev/null +++ b/src/git-integration/admin/styles.module.css @@ -0,0 +1,57 @@ +.pageLayout { + padding: 1rem 2rem; + padding-bottom: 0; + height: calc(100% - 2rem); + display: flex; + flex-direction: column; +} +.pageHeader { + border-bottom: 1px solid #e2e2e2; + margin-bottom: 0.5rem; +} +.pageContainer { + flex-grow: 1; +} +.repoForm { + max-width: 400px; +} +.notice { + margin: 0; +} +.connectionDetails { + display: grid; + grid-template-columns: 140px auto; +} + +.sectionHeader { + display: flex; + justify-content: space-between; + align-items: center; +} +.repoTable { + width: 100%; + margin-top: 0.5rem; + display: grid; + grid-template-columns: 60px 5fr 1.5fr 1.5fr 2fr 1.5fr; + border-left: 1px solid #c3c4c7; + border-top: 1px solid #c3c4c7; +} +.connectedRepoHeader { + background-color: #f6f7f7; + padding: 1rem; + border-right: 1px solid #c3c4c7; + border-bottom: 1px solid #c3c4c7; + color: #2c3338; + font-weight: 700; +} +.connectedRepoItem { + padding: 0.5rem 1rem; + border-right: 1px solid #c3c4c7; + border-bottom: 1px solid #c3c4c7; +} +.editLink { + cursor: pointer; +} +.editLink:hover { + text-decoration: underline; +} diff --git a/src/index.js b/src/index.js index a886c620..fd81bb19 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import { render, createRoot } from '@wordpress/element'; import ManageFonts from './manage-fonts'; import GoogleFonts from './google-fonts'; import LocalFonts from './local-fonts'; +import GitIntegrationAdminPage from './git-integration/admin'; import { ManageFontsProvider } from './fonts-context'; import './index.scss'; @@ -20,6 +21,9 @@ function App() { case 'add-local-font-to-theme-json': PageComponent = LocalFonts; break; + case 'themes-git-integration': + PageComponent = GitIntegrationAdminPage; + break; default: PageComponent = () =>

This page is not implemented yet.

; break;