From 79dd93ce59e9e1cff3b0872a712f54d9b6a9eabd Mon Sep 17 00:00:00 2001 From: Greg Marshall Date: Mon, 6 Nov 2023 10:21:02 -0600 Subject: [PATCH 1/6] WIP: issue-14 From ffd3b457dda0715987b6d3b662e286b7a0036836 Mon Sep 17 00:00:00 2001 From: Greg Marshall Date: Mon, 6 Nov 2023 11:38:51 -0600 Subject: [PATCH 2/6] adding files --- src/class-settings.php | 176 ++++++++++ src/class-wp-newsletter-builder.php | 309 ++++++++++++++++++ .../class-campaign-monitor.php} | 139 +------- src/email-providers/class-omeda.php | 125 +++++++ .../interface-email-provider.php | 99 ++++++ 5 files changed, 711 insertions(+), 137 deletions(-) create mode 100644 src/class-settings.php create mode 100644 src/class-wp-newsletter-builder.php rename src/{class-campaign-monitor-client.php => email-providers/class-campaign-monitor.php} (63%) create mode 100644 src/email-providers/class-omeda.php create mode 100644 src/email-providers/interface-email-provider.php diff --git a/src/class-settings.php b/src/class-settings.php new file mode 100644 index 00000000..8992cbe7 --- /dev/null +++ b/src/class-settings.php @@ -0,0 +1,176 @@ + static::SETTINGS_KEY, + 'children' => [ + 'from_email' => new \Fieldmanager_TextField( __( 'From Email', 'wp-newsletter-builder' ) ), + 'reply_to_email' => new \Fieldmanager_TextField( __( 'Reply To Email', 'wp-newsletter-builder' ) ), + 'from_names' => new \Fieldmanager_TextField( + [ + 'label' => __( 'From Names', 'wp-newsletter-builder' ), + 'limit' => 0, + 'add_more_label' => __( 'Add From Name', 'wp-newsletter-builder' ), + 'one_label_per_item' => false, + ] + ), + 'footer_settings' => new \Fieldmanager_Group( + [ + 'label' => __( 'Footer Settings', 'wp-newsletter-builder' ), + 'collapsed' => true, + 'collapsible' => true, + 'children' => [ + 'facebook_url' => new \Fieldmanager_Link( + [ + 'label' => __( 'Facebook URL', 'wp-newsletter-builder' ), + ] + ), + 'twitter_url' => new \Fieldmanager_Link( + [ + 'label' => __( 'Twitter URL', 'wp-newsletter-builder' ), + ] + ), + 'instagram_url' => new \Fieldmanager_Link( + [ + 'label' => __( 'Instagram URL', 'wp-newsletter-builder' ), + ] + ), + 'youtube_url' => new \Fieldmanager_Link( + [ + 'label' => __( 'YouTube URL', 'wp-newsletter-builder' ), + ] + ), + 'image' => new \Fieldmanager_Media( + [ + 'label' => __( 'Footer Image', 'wp-newsletter-builder' ), + 'preview_size' => 'medium', + ] + ), + 'address' => new \Fieldmanager_TextField( + [ + 'label' => __( 'Company Address', 'wp-newsletter-builder' ), + ] + ), + ], + ] + ), + ], + ] + ); + + $settings->activate_submenu_page(); + } + + /** + * Get the API key and instantiate a client using the API key. + * + * @return \CS_REST_General + */ + public function get_client() { + $settings = get_option( static::SETTINGS_KEY ); + if ( empty( $settings ) || empty( $settings['api_key'] ) ) { + return false; + } + $auth = [ 'api_key' => $settings['api_key'] ]; + $wrap = new \CS_REST_General( $auth ); + + return $wrap; + } + + /** + * Gets the lists for the client. + * + * @TODO: Add caching that works on Pantheon and WordPress VIP. + * + * @return array|false + */ + public function get_lists() { + $settings = get_option( static::SETTINGS_KEY ); + if ( empty( $settings ) || empty( $settings['api_key'] ) || empty( $settings['client_id'] ) ) { + return false; + } + $auth = [ 'api_key' => $settings['api_key'] ]; + + $wrap = new \CS_REST_Clients( + $settings['client_id'], + $auth + ); + + return $wrap->get_lists()->response; + } + + /** + * Gets footer settings. + * + * @TODO: Add caching that works on Pantheon and WordPress VIP. + * + * @return array|false + */ + public function get_footer_settings() { + $settings = get_option( static::SETTINGS_KEY ); + if ( empty( $settings ) || empty( $settings['footer_settings'] ) ) { + return false; + } + + return $settings['footer_settings']; + } + + /** + * Gets From Names. + * + * @return array|false + */ + public function get_from_names() { + $settings = get_option( static::SETTINGS_KEY ); + if ( empty( $settings ) || empty( $settings['from_names'] ) ) { + return false; + } + + return $settings['from_names']; + } +} diff --git a/src/class-wp-newsletter-builder.php b/src/class-wp-newsletter-builder.php new file mode 100644 index 00000000..1e266fea --- /dev/null +++ b/src/class-wp-newsletter-builder.php @@ -0,0 +1,309 @@ + [ + 'name' => __( 'Newsletters', 'wp-newsletter-builder' ), + 'singular_name' => __( 'Newsletter', 'wp-newsletter-builder' ), + ], + 'public' => true, + 'has_archive' => true, + 'rewrite' => [ 'slug' => 'nb-newsletters' ], + 'supports' => [ 'title', 'editor', 'custom-fields' ], + 'show_in_rest' => true, + 'exclude_from_search' => true, + 'template' => [ + [ + 'wp-newsletter-builder/email-settings', + [ + 'lock' => [ + 'move' => true, + 'remove' => true, + ], + ], + ], + ], + 'menu_icon' => 'dashicons-email-alt2', + ], + ); + + register_post_type( + 'nb_template', + [ + 'labels' => [ + 'name' => __( 'Templates', 'wp-newsletter-builder' ), + 'singular_name' => __( 'Template', 'wp-newsletter-builder' ), + ], + 'public' => true, + 'has_archive' => true, + 'rewrite' => [ 'slug' => 'nb-templates' ], + 'supports' => [ 'title', 'editor', 'custom-fields' ], + 'show_in_rest' => true, + 'exclude_from_search' => true, + 'template' => [ + [ + 'wp-newsletter-builder/email-settings', + [ + 'lock' => [ + 'move' => true, + 'remove' => true, + ], + ], + ], + ], + 'menu_icon' => 'dashicons-admin-customizer', + ], + ); + } + + /** + * Adds the local template file to the template hierarchy. + * + * @param string $template The existing template. + * @return string + */ + public function include_template( $template ) { + global $post; + + $local_path = WP_NEWSLETTER_BUILDER_DIR . '/single-nb_newsletter.php'; + + if ( + $post instanceof \WP_Post + && is_singular( 'nb_newsletter' ) + && file_exists( $local_path ) + && 0 === validate_file( $local_path ) + ) { + $template = $local_path; + } + + return $template; + } + + /** + * Sends the newsletter when the newsletter post is published. + * + * @param int $post_id The post id. + * @param \WP_Post $post The post. + * @param bool $update Whether this is an update. + * @param \WP_Post $post_before The post before the update. + */ + public function on_newsletter_after_insert_post( $post_id, $post, $update, $post_before ): void { + if ( 'nb_newsletter' !== $post->post_type ) { + return; + } + $new_status = $post->post_status; + $old_status = $post_before->post_status; + if ( $new_status === $old_status || 'publish' !== $new_status ) { + return; + } + $this->do_send( $post->ID ); + } + + /** + * Sends the breaking newsletter when the post is published. + * + * @param int $post_id The post id. + */ + public function on_after_insert_post( $post_id ): void { + $post = get_post( $post_id ); + if ( 'post' !== $post->post_type ) { + return; + } + if ( 'publish' !== $post->post_status ) { + return; + } + $should_send = get_post_meta( $post->ID, 'nb_breaking_should_send', true ); + if ( ! $should_send ) { + return; + } + + $nb_newsletter_subject = get_post_meta( $post->ID, 'nb_breaking_subject', true ); + if ( empty( $nb_newsletter_subject ) ) { + $nb_newsletter_subject = $post->post_title; + } + + $nb_newsletter_preview = get_post_meta( $post->ID, 'nb_breaking_preview', true ); + if ( empty( $nb_newsletter_preview ) ) { + $nb_newsletter_preview = $post->post_excerpt; + } + + // Publish the post, which should kick off the other transition listener to send the email. + $breaking_post_id = wp_insert_post( + [ + 'post_title' => "Breaking News {$post->ID}", + 'post_content' => get_post_meta( $post->ID, 'nb_breaking_content', true ), + 'post_status' => 'publish', + 'post_type' => 'nb_newsletter', + 'meta_input' => [ + 'nb_newsletter_email_type' => get_post_meta( $post->ID, 'nb_breaking_email_type', true ), + 'nb_newsletter_template' => get_post_meta( $post->ID, 'nb_breaking_template', true ), + 'nb_newsletter_from_name' => get_post_meta( $post->ID, 'nb_breaking_from_name', true ), + 'nb_newsletter_subject' => $nb_newsletter_subject, + 'nb_newsletter_preview' => $nb_newsletter_preview, + 'nb_newsletter_list' => get_post_meta( $post->ID, 'nb_breaking_list', true ), + 'nb_newsletter_header_img' => get_post_meta( $post->ID, 'nb_breaking_header_img', true ), + ], + ] + ); + if ( is_wp_error( $breaking_post_id ) ) { + return; + } + + $sent_emails = get_post_meta( $post->ID, 'nb_newsletter_sent_breaking_post_id', true ) ?? []; + if ( ! is_array( $sent_emails ) ) { + $sent_emails = [ $sent_emails ]; + } + $sent_emails[] = $breaking_post_id; + update_post_meta( $post->ID, 'nb_newsletter_sent_breaking_post_id', $sent_emails ); + delete_post_meta( $post->ID, 'nb_breaking_subject' ); + delete_post_meta( $post->ID, 'nb_breaking_preview' ); + delete_post_meta( $post->ID, 'nb_breaking_list' ); + delete_post_meta( $post->ID, 'nb_breaking_should_send' ); + + \wp_update_post( + [ + 'ID' => $breaking_post_id, + 'post_status' => 'publish', + ] + ); + } + + /** + * Override the HTML URL for the newsletter - return a public url if local, add auth if staging. + * + * @param string $url The existing url. + * @return string + */ + public function modify_html_url( $url ): string { + $url = str_replace( + [ + 'https://www.', + 'http://www.', + ], + [ + 'https://', + 'http://', + ], + $url + ); + $settings = get_option( 'nb_campaign_monitor_settings' ); + if ( ! empty( $settings['dev_settings']['static_preview_url'] ) ) { + return $settings['dev_settings']['static_preview_url']; + } + if ( + ! empty( $settings['dev_settings']['basic_auth_username'] ) && ! empty( $settings['dev_settings']['basic_auth_password'] ) + ) { + return str_replace( + '://', + sprintf( '://%s:%s@', $settings['dev_settings']['basic_auth_username'], $settings['dev_settings']['basic_auth_password'] ), + $url + ); + } + return $url; + } + + /** + * Sends the newsletter. + * + * @param int $post_id The post id. + * @return void + */ + public function do_send( $post_id ) { + $lists = get_post_meta( $post_id, 'nb_newsletter_list', true ); + if ( ! is_array( $lists ) ) { + $lists = [ $lists ]; + } + if ( empty( $lists ) ) { + return; + } + $campaign_id = get_post_meta( $post_id, 'nb_newsletter_campaign_id', true ); + $result = Campaign_Monitor_Client::instance()->create_campaign( $post_id, $lists ); + if ( 201 === $result['http_status_code'] ) { + update_post_meta( $post_id, 'nb_newsletter_campaign_id', $result['response'] ); + $send_result = Campaign_Monitor_Client::instance()->send_campaign( $result['response'] ); + update_post_meta( $post_id, 'nb_newsletter_send_result', $send_result ); + } + update_post_meta( $post_id, 'nb_newsletter_campaign_result', $result ); + } + + /** + * Modifies the query so we can view draft newsletters as well as published ones. + * + * @param \WP_Query $query The query. + * @return \WP_Query + */ + public function modify_query( $query ) { + if ( + $query->is_main_query() + && isset( $query->query_vars['post_type'] ) + && 'nb_newsletter' === $query->query_vars['post_type'] + ) { + $query->query['post_status'] = [ 'publish', 'draft' ]; + $query->query_vars['post_status'] = [ 'publish', 'draft' ]; + } + return $query; + } + + /** + * Displays an error message if Fieldmanager is not installed. + * + * @return void + */ + public function fieldmanager_not_found_error() { + ?> +
+

+ ', + '', + ); + ?> +

+
+ -
-

- ', - '', - ); - ?> -

-
- new \Fieldmanager_TextField( __( 'API Key', 'wp-newsletter-builder' ) ), 'client_id' => new \Fieldmanager_TextField( __( 'Client ID', 'wp-newsletter-builder' ) ), 'confirmation_email' => new \Fieldmanager_TextField( __( 'Confirmation Email', 'wp-newsletter-builder' ) ), - 'google_api_key' => new \Fieldmanager_TextField( __( 'Google API Key', 'wp-newsletter-builder' ) ), - 'from_email' => new \Fieldmanager_TextField( __( 'From Email', 'wp-newsletter-builder' ) ), - 'reply_to_email' => new \Fieldmanager_TextField( __( 'Reply To Email', 'wp-newsletter-builder' ) ), - 'from_names' => new \Fieldmanager_TextField( - [ - 'label' => __( 'From Names', 'wp-newsletter-builder' ), - 'limit' => 0, - 'add_more_label' => __( 'Add From Name', 'wp-newsletter-builder' ), - 'one_label_per_item' => false, - ] - ), - 'dev_settings' => new \Fieldmanager_Group( - [ - 'label' => __( 'Development Settings', 'wp-newsletter-builder' ), - 'collapsed' => true, - 'collapsible' => true, - 'children' => [ - 'static_preview_url' => new \Fieldmanager_Link( - [ - 'label' => __( 'Static Preview URL', 'wp-newsletter-builder' ), - 'description' => __( 'For local development, provide an internet accessible file to use for the newsletter content.', 'wp-newsletter-builder' ), - ] - ), - 'basic_auth_username' => new \Fieldmanager_TextField( - [ - 'label' => __( 'Basic Auth Username', 'wp-newsletter-builder' ), - 'description' => __( 'For protected staging sites, provide a username for basic auth.', 'wp-newsletter-builder' ), - ] - ), - 'basic_auth_password' => new \Fieldmanager_TextField( - [ - 'label' => __( 'Basic Auth Password', 'wp-newsletter-builder' ), - 'description' => __( 'For protected staging sites, provide a password for basic auth.', 'wp-newsletter-builder' ), - ] - ), - ], - ] - ), - 'footer_settings' => new \Fieldmanager_Group( - [ - 'label' => __( 'Footer Settings', 'wp-newsletter-builder' ), - 'collapsed' => true, - 'collapsible' => true, - 'children' => [ - 'facebook_url' => new \Fieldmanager_Link( - [ - 'label' => __( 'Facebook URL', 'wp-newsletter-builder' ), - ] - ), - 'twitter_url' => new \Fieldmanager_Link( - [ - 'label' => __( 'Twitter URL', 'wp-newsletter-builder' ), - ] - ), - 'instagram_url' => new \Fieldmanager_Link( - [ - 'label' => __( 'Instagram URL', 'wp-newsletter-builder' ), - ] - ), - 'youtube_url' => new \Fieldmanager_Link( - [ - 'label' => __( 'YouTube URL', 'wp-newsletter-builder' ), - ] - ), - 'image' => new \Fieldmanager_Media( - [ - 'label' => __( 'Footer Image', 'wp-newsletter-builder' ), - 'preview_size' => 'medium', - ] - ), - 'address' => new \Fieldmanager_TextField( - [ - 'label' => __( 'Company Address', 'wp-newsletter-builder' ), - ] - ), - ], - ] - ), ], ] ); @@ -206,36 +101,6 @@ public function get_lists() { return $wrap->get_lists()->response; } - /** - * Gets footer settings. - * - * @TODO: Add caching that works on Pantheon and WordPress VIP. - * - * @return array|false - */ - public function get_footer_settings() { - $settings = get_option( static::SETTINGS_KEY ); - if ( empty( $settings ) || empty( $settings['footer_settings'] ) ) { - return false; - } - - return $settings['footer_settings']; - } - - /** - * Gets From Names. - * - * @return array|false - */ - public function get_from_names() { - $settings = get_option( static::SETTINGS_KEY ); - if ( empty( $settings ) || empty( $settings['from_names'] ) ) { - return false; - } - - return $settings['from_names']; - } - /** * Creates an email campaign. * diff --git a/src/email-providers/class-omeda.php b/src/email-providers/class-omeda.php new file mode 100644 index 00000000..a183b8b1 --- /dev/null +++ b/src/email-providers/class-omeda.php @@ -0,0 +1,125 @@ + Date: Mon, 6 Nov 2023 12:27:24 -0600 Subject: [PATCH 3/6] a few fixes --- plugin.php | 3 ++- src/class-settings.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin.php b/plugin.php index 10799900..fb89ae09 100644 --- a/plugin.php +++ b/plugin.php @@ -64,12 +64,13 @@ function () { function main() { new Ads(); new Breaking_Recipients(); - Campaign_Monitor_Client::instance()->setup(); new Email_Types(); + new Settings(); new Media(); new WP_Newsletter_Builder(); new Rest_API_Endpoints(); new Rest_API_Fields(); new Rest_API_Query(); + // TODO: Find selected email provider and instantiate it. } main(); diff --git a/src/class-settings.php b/src/class-settings.php index 8992cbe7..75b43cc7 100644 --- a/src/class-settings.php +++ b/src/class-settings.php @@ -23,7 +23,7 @@ class Settings { * * @return void */ - public function setup() { + public function __construct() { add_action( 'init', [ $this, 'maybe_register_settings_page' ] ); } From bf6967dcd45c9f0f7132c1b66779beb9bd0ae603 Mon Sep 17 00:00:00 2001 From: Greg Marshall Date: Tue, 7 Nov 2023 15:30:36 -0600 Subject: [PATCH 4/6] working with adapter --- .phpcs.xml | 1 + blocks/email-settings/index.php | 2 -- plugin.php | 36 ++++++++++++++++++- plugins/newsletter-from-post/index.php | 19 +++++++++- src/assets.php | 3 +- src/class-breaking-recipients.php | 3 +- src/class-email-types.php | 5 +-- src/class-rest-api-endpoints.php | 12 ++++--- src/class-settings.php | 2 +- src/class-wp-newsletter-builder.php | 5 +-- .../class-campaign-monitor.php | 2 +- 11 files changed, 74 insertions(+), 16 deletions(-) diff --git a/.phpcs.xml b/.phpcs.xml index f77fbf7f..b0425ef2 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -38,6 +38,7 @@ + diff --git a/blocks/email-settings/index.php b/blocks/email-settings/index.php index b447c18c..7871407b 100644 --- a/blocks/email-settings/index.php +++ b/blocks/email-settings/index.php @@ -5,8 +5,6 @@ * @package wp-newsletter-builder */ -use WP_Newsletter_Builder\Campaign_Monitor_Client; - /** * Registers the block using the metadata loaded from the `block.json` file. * Behind the scenes, it registers also all assets so they can be enqueued diff --git a/plugin.php b/plugin.php index f301c1b1..341f08b5 100644 --- a/plugin.php +++ b/plugin.php @@ -58,6 +58,8 @@ function () { /* class files get loaded by the autoloader */ +global $newsletter_builder_email_provider; + /** * Instantiate the plugin. */ @@ -71,6 +73,38 @@ function main() { new Rest_API_Endpoints(); new Rest_API_Fields(); new Rest_API_Query(); - // TODO: Find selected email provider and instantiate it. + // Find selected email provider and instantiate it. + $selected_email_provider = apply_filters( 'wp_newsletter_builder_selected_provider', '' ); + + // Check if provider has been selected and exists. + if ( empty( $selected_email_provider ) || ! class_exists( $selected_email_provider ) ) { + \add_action( + 'admin_notices', + function () { + ?> +
+

+ wp_newsletter_builder_selected_provider' + ); + ?> +

+
+ setup(); } main(); diff --git a/plugins/newsletter-from-post/index.php b/plugins/newsletter-from-post/index.php index 630ad1e5..9d7df23f 100644 --- a/plugins/newsletter-from-post/index.php +++ b/plugins/newsletter-from-post/index.php @@ -38,12 +38,29 @@ function action_enqueue_post_sidebar_assets() { if ( 'post' !== $post_type ) { return; } + $settings = new Settings(); + + $templates = get_posts( // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.get_posts_get_posts + [ + 'post_type' => 'nb_template', + 'posts_per_page' => -1, + 'orderby' => 'ID', + 'suppress_filters' => false, + ] + ); + $template_map = []; + + foreach ( $templates as $template ) { + $template_map[ $template->ID ] = $template->post_title; + } + wp_enqueue_script( 'plugin-newsletter-from-post' ); wp_localize_script( 'plugin-newsletter-from-post', 'newsletterBuilder', [ - 'fromNames' => Campaign_Monitor_Client::instance()->get_from_names(), + 'fromNames' => $settings->get_from_names(), + 'templates' => $template_map, 'breakingLists' => ( new Breaking_Recipients() )->get_breaking_recipients(), ] ); diff --git a/src/assets.php b/src/assets.php index 6094c5b3..47d69770 100644 --- a/src/assets.php +++ b/src/assets.php @@ -125,11 +125,12 @@ function action_enqueue_block_editor_assets() { get_asset_dependency_array( 'editor' ), get_asset_version( 'editor' ) ); + $settings = new Settings(); wp_localize_script( 'wp-newsletter-builder-email-settings-editor-script', 'newsletterBuilder', [ - 'fromNames' => Campaign_Monitor_Client::instance()->get_from_names(), + 'fromNames' => $settings->get_from_names(), 'templates' => $template_map, ] ); diff --git a/src/class-breaking-recipients.php b/src/class-breaking-recipients.php index ffb6e7a8..1636aa15 100644 --- a/src/class-breaking-recipients.php +++ b/src/class-breaking-recipients.php @@ -93,7 +93,8 @@ public function get_breaking_recipients() { * @return array */ private function get_options() { - $lists = Campaign_Monitor_Client::instance()->get_lists(); + global $newsletter_builder_email_provider; + $lists = $newsletter_builder_email_provider->get_lists(); $options = []; foreach ( $lists as $list ) { $options[ $list->ListID ] = $list->Name; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase diff --git a/src/class-email-types.php b/src/class-email-types.php index 161313c8..f9677f9a 100644 --- a/src/class-email-types.php +++ b/src/class-email-types.php @@ -46,8 +46,9 @@ public function maybe_register_settings_page() { * @return void */ public function register_fields() { - $from_names = Campaign_Monitor_Client::instance()->get_from_names(); - $settings = new \Fieldmanager_Group( + $plugin_settings = new Settings(); + $from_names = $plugin_settings->get_from_names(); + $settings = new \Fieldmanager_Group( [ 'name' => static::SETTINGS_KEY, 'children' => [ diff --git a/src/class-rest-api-endpoints.php b/src/class-rest-api-endpoints.php index 822c3117..f8fc5d17 100644 --- a/src/class-rest-api-endpoints.php +++ b/src/class-rest-api-endpoints.php @@ -90,7 +90,8 @@ public function get_lists() { if ( ! current_user_can( 'edit_posts' ) ) { return new \WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to access this endpoint.', 'wp-newsletter-builder' ), [ 'status' => 401 ] ); } - $lists = Campaign_Monitor_Client::instance()->get_lists(); + global $newsletter_builder_email_provider; + $lists = $newsletter_builder_email_provider->get_lists(); return $lists; } @@ -123,7 +124,8 @@ public function get_footer_settings() { if ( ! current_user_can( 'edit_posts' ) ) { return new \WP_Error( 'rest_forbidden', esc_html__( 'You do not have permission to access this endpoint.', 'wp-newsletter-builder' ), [ 'status' => 401 ] ); } - $footer_settings = Campaign_Monitor_Client::instance()->get_footer_settings(); + $settings = new Settings(); + $footer_settings = $settings->get_footer_settings(); return $footer_settings; } @@ -155,7 +157,8 @@ public function get_status( $request ) { 'Status' => __( 'Not sent', 'wp-newsletter-builder' ), ]; } - $status = Campaign_Monitor_Client::instance()->get_campaign_summary( $campaign_id ); + global $newsletter_builder_email_provider; + $status = $newsletter_builder_email_provider->get_campaign_summary( $campaign_id ); if ( ! empty( $status ) && 200 === $status['http_status_code'] ) { $status['response']->Status = __( 'Sent' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase wp_cache_set( $cache_key, $status['response'], null, 5 * MINUTE_IN_SECONDS ); @@ -209,8 +212,9 @@ public function subscribe( $request ) { ]; } $list_results = []; + global $newsletter_builder_email_provider; foreach ( $list_ids as $list_id ) { - $result = Campaign_Monitor_Client::instance()->add_subscriber( $list_id, $email ); + $result = $newsletter_builder_email_provider->add_subscriber( $list_id, $email ); if ( ! empty( $result ) && 200 === $result['http_status_code'] ) { $list_results[ $list_id ] = [ 'success' => true, diff --git a/src/class-settings.php b/src/class-settings.php index 75b43cc7..7cb91c50 100644 --- a/src/class-settings.php +++ b/src/class-settings.php @@ -34,7 +34,7 @@ public function __construct() { */ public function maybe_register_settings_page() { if ( function_exists( 'fm_register_submenu_page' ) && \current_user_can( 'manage_options' ) ) { - \fm_register_submenu_page( static::SETTINGS_KEY, 'edit.php?post_type=nb_newsletter', __( 'Settings', 'wp-newsletter-builder' ), __( 'Settings', 'wp-newsletter-builder' ) ); + \fm_register_submenu_page( static::SETTINGS_KEY, 'edit.php?post_type=nb_newsletter', __( 'General Settings', 'wp-newsletter-builder' ), __( 'General Settings', 'wp-newsletter-builder' ) ); \add_action( 'fm_submenu_' . static::SETTINGS_KEY, [ $this, 'register_fields' ] ); } } diff --git a/src/class-wp-newsletter-builder.php b/src/class-wp-newsletter-builder.php index 1e266fea..7c0f97b9 100644 --- a/src/class-wp-newsletter-builder.php +++ b/src/class-wp-newsletter-builder.php @@ -246,10 +246,11 @@ public function do_send( $post_id ) { return; } $campaign_id = get_post_meta( $post_id, 'nb_newsletter_campaign_id', true ); - $result = Campaign_Monitor_Client::instance()->create_campaign( $post_id, $lists ); + global $newsletter_builder_email_provider; + $result = $newsletter_builder_email_provider->create_campaign( $post_id, $lists ); if ( 201 === $result['http_status_code'] ) { update_post_meta( $post_id, 'nb_newsletter_campaign_id', $result['response'] ); - $send_result = Campaign_Monitor_Client::instance()->send_campaign( $result['response'] ); + $send_result = $newsletter_builder_email_provider->send_campaign( $result['response'] ); update_post_meta( $post_id, 'nb_newsletter_send_result', $send_result ); } update_post_meta( $post_id, 'nb_newsletter_campaign_result', $result ); diff --git a/src/email-providers/class-campaign-monitor.php b/src/email-providers/class-campaign-monitor.php index 8861dc89..4658a953 100644 --- a/src/email-providers/class-campaign-monitor.php +++ b/src/email-providers/class-campaign-monitor.php @@ -38,7 +38,7 @@ public function setup() { */ public function maybe_register_settings_page() { if ( function_exists( 'fm_register_submenu_page' ) && \current_user_can( 'manage_options' ) ) { - \fm_register_submenu_page( static::SETTINGS_KEY, 'edit.php?post_type=nb_newsletter', __( 'Settings', 'wp-newsletter-builder' ), __( 'Settings', 'wp-newsletter-builder' ) ); + \fm_register_submenu_page( static::SETTINGS_KEY, 'edit.php?post_type=nb_newsletter', __( 'Campaign Monitor Settings', 'wp-newsletter-builder' ), __( 'Campaign Monitor Settings', 'wp-newsletter-builder' ) ); \add_action( 'fm_submenu_' . static::SETTINGS_KEY, [ $this, 'register_fields' ] ); } } From 9816e3ee3e3e2289e7b6611b812049dd10b729af Mon Sep 17 00:00:00 2001 From: Greg Marshall Date: Tue, 7 Nov 2023 18:54:42 -0600 Subject: [PATCH 5/6] formatting --- plugin.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.php b/plugin.php index 341f08b5..e4f2a176 100644 --- a/plugin.php +++ b/plugin.php @@ -103,7 +103,8 @@ function () { return; } global $newsletter_builder_email_provider; - $provider = new $selected_email_provider(); + $provider = new $selected_email_provider(); + $newsletter_builder_email_provider = $provider::instance(); $newsletter_builder_email_provider->setup(); } From 659d39a7816697503336e6872fe735af613f30f7 Mon Sep 17 00:00:00 2001 From: Greg Marshall Date: Tue, 7 Nov 2023 19:01:26 -0600 Subject: [PATCH 6/6] version info and add composer install to build --- .github/workflows/built-release.yml | 5 +- CHANGELOG.md | 4 + configure.php | 763 ---------------------------- plugin.php | 2 +- 4 files changed, 6 insertions(+), 768 deletions(-) delete mode 100644 configure.php diff --git a/.github/workflows/built-release.yml b/.github/workflows/built-release.yml index 46b1ef21..089d63be 100644 --- a/.github/workflows/built-release.yml +++ b/.github/workflows/built-release.yml @@ -4,12 +4,9 @@ on: push: branches: - develop - - main - - production jobs: built-release: uses: alleyinteractive/.github/.github/workflows/built-release.yml@main - if: ${{ github.repository != 'alleyinteractive/create-wordpress-plugin' }} with: - node: 16 + composer_install: true diff --git a/CHANGELOG.md b/CHANGELOG.md index d53b026a..9ac23f7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `Newsletter Builder` will be documented in this file. +## 0.3.0 - 2023-11-07 + +- Set up email provider adapter system + ## 0.2.0 - 2023-11-07 - Move templates from static files to using a custom post type diff --git a/configure.php b/configure.php deleted file mode 100644 index 04b35f85..00000000 --- a/configure.php +++ /dev/null @@ -1,763 +0,0 @@ -#!/usr/bin/env php -] - * : The author name. - * - * [--author_email=] - * : The author email. - * - * phpcs:disable - */ - -namespace Create_WordPress_Plugin\Configure; - -if ( ! defined( 'STDIN' ) ) { - die( 'Not in CLI mode.' ); -} - -if ( 0 === strpos( strtoupper( PHP_OS ), 'WIN' ) ) { - die( 'Not supported in Windows. 🪟' ); -} - -if ( version_compare( PHP_VERSION, '8.0.0', '<' ) ) { - die( 'PHP 8.0.0 or greater is required.' ); -} - -// Parse the command line arguments from $argv. -$args = []; -$previous_key = null; - -foreach ( $argv as $value ) { - if ( str_starts_with( $value, '--' ) ) { - if ( false !== strpos( $value, '=' ) ) { - [ $arg, $value ] = explode( '=', substr( $value, 2 ), 2 ); - - $args[ $arg ] = trim( $value ); - - $previous_key = null; - } else { - $args[ substr( $value, 2 ) ] = true; - - $previous_key = substr( $value, 2 ); - } - } elseif ( ! empty( $previous_key ) ) { - $args[ $previous_key ] = trim( $value ); - } else { - $previous_key = trim( $value ); - } -} - -$terminal_width = (int) exec( 'tput cols' ); - -function write( string $text ): void { - global $terminal_width; - echo wordwrap( $text, $terminal_width - 1 ) . PHP_EOL; -} - -function ask( string $question, string $default = '', bool $allow_empty = true ): string { - while ( true ) { - write( $question . ( $default ? " [{$default}]" : '' ) . ': ' ); - $answer = readline( '> ' ); - - $value = $answer ?: $default; - - if ( ! $allow_empty && empty( $value ) ) { - echo "This value can't be empty." . PHP_EOL; - continue; - } - - return $value; - } -} - -function confirm( string $question, bool $default = false ): bool { - write( "{$question} (yes/no) [" . ( $default ? 'yes' : 'no' ) . ']: ' ); - - $answer = readline( '> ' ); - - if ( ! $answer ) { - return $default; - } - - return in_array( strtolower( trim( $answer ) ), [ 'y', 'yes', 'true', '1' ], true ); -} - -function run( string $command, string $dir = null ): string { - $command = $dir ? "cd {$dir} && {$command}" : $command; - - return trim( (string) shell_exec( $command ) ); -} - -function str_after( string $subject, string $search ): string { - $pos = strrpos( $subject, $search ); - - if ( $pos === false ) { - return $subject; - } - - return substr( $subject, $pos + strlen( $search ) ); -} - -function slugify( string $subject ): string { - return strtolower( trim( (string) preg_replace( '/[^A-Za-z0-9-]+/', '-', $subject ), '-' ) ); -} - -function title_case( string $subject ): string { - return ensure_capitalp( str_replace( ' ', '_', ucwords( str_replace( [ '-', '_' ], ' ', $subject ) ) ) ); -} - -function ensure_capitalp( string $text ): string { - return str_replace( 'Wordpress', 'WordPress', $text ); -} - -/** - * @param string $file - * @param array $replacements - */ -function replace_in_file( string $file, array $replacements ): void { - $contents = file_get_contents( $file ); - - if ( empty( $contents ) ) { - return; - } - - file_put_contents( - $file, - str_replace( - array_keys( $replacements ), - array_values( $replacements ), - $contents, - ) - ); -} - -function remove_readme_paragraphs( string $file ): void { - $contents = file_get_contents( $file ); - - if ( empty( $contents ) ) { - return; - } - - file_put_contents( - $file, - trim( (string) preg_replace( '/.*/s', '', $contents ) ?: $contents ), - ); -} - -function remove_composer_require(): void { - global $plugin_file; - - $contents = file_get_contents( $plugin_file ); - - if ( empty( $contents ) ) { - return; - } - - file_put_contents( - $plugin_file, - trim( (string) preg_replace( '/\n\/\* Start Composer Loader \*\/.*\/\* End Composer Loader \*\/\n/s', '', $contents ) ?: $contents ) . PHP_EOL, - ); - - echo "Removed Composer's vendor/autoload.php from {$plugin_file}" . PHP_EOL; -} - -function remove_composer_wrapper_comments(): void { - global $plugin_file; - - $contents = file_get_contents( $plugin_file ); - - if ( empty( $contents ) ) { - return; - } - - file_put_contents( - $plugin_file, - trim( preg_replace( '/\n\/\* (Start|End) Composer Loader \*\/\n/', '', $contents ) ?: $contents ) . PHP_EOL, - ); - - echo "Removed Composer's wrapper comments from {$plugin_file}" . PHP_EOL; -} - -function remove_composer_files(): void { - $file_list = [ - 'composer.json', - 'composer.lock', - 'vendor/', - ]; - - delete_files( $file_list ); - - write( sprintf( 'Removed %s files.', implode( ', ', $file_list ) ) ); -} - -function remove_project_files(): void { - $file_list = [ - '.buddy', - 'buddy.yml', - 'CHANGELOG.md', - '.deployignore', - '.editorconfig', - '.gitignore', - '.gitattributes', - '.github', - 'LICENSE', - ]; - - delete_files( $file_list ); - - write( sprintf( 'Removed %s files.', implode( ', ', $file_list ) ) ); -} - -function rollup_phpcs_to_parent( string $parent_file, string $local_file, string $plugin_name, string $plugin_domain ): void { - $config = ' - - PHP_CodeSniffer standard for ' . $plugin_name . ' - - - - - - - - - - - - - - - - - - -'; - - if ( file_put_contents( $local_file, $config ) ) { - delete_files( '.phpcs' ); - - echo "Updated {$local_file}.\n"; - } -} - -function remove_assets_readme( bool $keep_contents, string $file = 'README.md' ): void { - $contents = file_get_contents( $file ); - - if ( empty( $contents ) ) { - return; - } - - if ( $keep_contents ) { - $contents = str_replace( '', '', $contents ); - $contents = str_replace( '', '', $contents ); - - file_put_contents( $file, $contents ); - } else { - file_put_contents( - $file, - trim( (string) preg_replace( '/.*/s', '', $contents ) ?: $contents ), - ); - } -} - -function remove_assets_require(): void { - global $plugin_file; - - $contents = file_get_contents( $plugin_file ); - - if ( empty( $contents ) ) { - return; - } - - file_put_contents( - $plugin_file, - trim( (string) preg_replace( '/require_once __DIR__ \. \'\/src\/assets.php\';\\n/s', '', $contents ) ?: $contents ) . PHP_EOL, - ); -} - -function remove_assets_buddy( string $file = 'buddy.yml' ): void { - $contents = file_get_contents( $file ); - - if ( empty( $contents ) ) { - return; - } - - $contents = trim( preg_replace( '/(- action: "npm audit".*)variables:/s', 'variables:', $contents ) ?: $contents ); - $contents = str_replace( ' variables:', ' variables:', $contents ); - - file_put_contents( $file, $contents ); -} - -function determine_separator( string $path ): string { - return str_replace( '/', DIRECTORY_SEPARATOR, $path ); -} - -/** - * @return array - */ -function list_all_files_for_replacement(): array { - return explode( PHP_EOL, run( 'grep -R -l . --exclude LICENSE --exclude configure.php --exclude .phpunit.result.cache --exclude-dir .phpcs --exclude composer.lock --exclude-dir .git --exclude-dir .github --exclude-dir vendor --exclude-dir node_modules --exclude-dir modules --exclude-dir .phpcs' ) ); -} - -/** - * @param string|array $paths - */ -function delete_files( string|array $paths ): void { - if ( ! is_array( $paths ) ) { - $paths = [ $paths ]; - } - - foreach ( $paths as $path ) { - $path = determine_separator( $path ); - - if ( is_dir( $path ) ) { - run( "rm -rf {$path}" ); - } elseif ( file_exists( $path ) ) { - @unlink( $path ); - } - } -} - -function remove_phpstan(): void { - delete_files( 'phpstan.neon' ); - - // Manually patch the Composer.json file. - if ( file_exists( 'composer.json' ) ) { - $composer_json = (array) json_decode( (string) file_get_contents( 'composer.json' ), true ); - - if ( isset( $composer_json['scripts']['phpstan'] ) ) { // @phpstan-ignore-line - unset( $composer_json['scripts']['phpstan'] ); // @phpstan-ignore-line - - $composer_json['scripts']['test'] = [ // @phpstan-ignore-line - '@phpcs', - '@phpunit', - ]; - - file_put_contents( 'composer.json', json_encode( $composer_json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); - } - } -} - -function contributing_message( string $message ): void { - write( "\n{$message}\n" ); - echo "\t\e]8;;https://github.com/alleyinteractive/.github/blob/main/CONTRIBUTING.md#best-practices\e\\CONTRIBUTING.md\e]8;;\e\\\n\n"; -} - -echo "\nWelcome friend to alleyinteractive/create-wordpress-plugin! 😀\nLet's setup your WordPress Plugin 🚀\n\n"; - -// Always delete the 'merge-develop-to-scaffold.yml' file (this is never used in a scaffolded plugins). -delete_files( '.github/workflows/merge-develop-to-scaffold.yml' ); - -$current_dir = getcwd(); - -if ( ! $current_dir ) { - echo "Could not determine current directory.\n"; - exit( 1 ); -} - -$folder_name = ensure_capitalp( basename( $current_dir ) ); - -$author_email = ask( - question: 'Author email?', - default: (string) ( $args['author_email'] ?? run( 'git config user.email' ) ), - allow_empty: false, -); - -$username_guess = explode( ':', run( 'git config remote.origin.url' ) )[1] ?? ''; -$username_guess = dirname( $username_guess ); -$username_guess = basename( $username_guess ); -$author_username = ask( - question: 'Author username?', - default: $username_guess, - allow_empty: false, -); - -$author_name = ask( - question: 'Author name?', - default: (string) ( $args['author_name'] ?? run( 'git config user.name' ) ), - allow_empty: false, -); - -$vendor_name = ask( - question: 'Vendor name (usually the Github Organization)?', - default: $username_guess, - allow_empty: false, -); - -$vendor_slug = slugify( $vendor_name ); -$is_alley_plugin = 'alleyinteractive' === $vendor_slug; - -$plugin_name = ask( - question: 'Plugin name?', - default: (string) ( $args['plugin_name'] ?? str_replace( '_', ' ', title_case( $folder_name ) ) ), - allow_empty: false, -); - -while ( true ) { - $plugin_name_slug = slugify( ask( - question: 'Plugin slug?', - default: slugify( $plugin_name ), - allow_empty: false, - ) ); - - // Suggest a different plugin name if this is an Alley plugin. - if ( $is_alley_plugin && 0 !== strpos( $plugin_name_slug, 'wp-' ) ) { - $example_slug = "wp-{$plugin_name_slug}"; - - contributing_message( "Alley WordPress plugin slugs should be prefixed with \"wp-\". For example, {$example_slug} would be a great slug. If this plugin isn't meant to be published anywhere, this is fine to ignore. See our CONTRIBUTING.md for more details." ); - - if ( ! confirm( 'Do you wish to continue anyway?', false ) ) { - continue; - } - } - - break; -} - -while ( true ) { - $namespace = ask( - question: 'Plugin namespace?', - default: $is_alley_plugin ? 'Alley\\WP\\' . title_case( $plugin_name ) : title_case( $plugin_name ), - allow_empty: false, - ); - - // Check if the namespace is valid. - if ( ! preg_match( '/^[a-zA-Z0-9_\\\\]+$/', $namespace ) ) { - echo "Invalid namespace, please try again.\n"; - continue; - } - - // Offer to fix the namespace if this is an Alley plugin. - if ( $is_alley_plugin && 0 !== strpos( $namespace, 'Alley\\WP\\' ) ) { - $example_namespace = 'Alley\\WP\\' . title_case( $plugin_name ); - contributing_message( "Alley WordPress plugins should be prefixed with \"Alley\\WP\\\". A namespace such as \"{$example_namespace}\" would work well. If this plugin isn't meant to be published anywhere, this is fine to ignore. See our CONTRIBUTING.md for more details." ); - - if ( confirm( 'Do you wish to continue anyway?', false ) ) { - break; - } - } - - break; -} - -$class_name = ask( 'Base class name for plugin?', title_case( $plugin_name ) ); -$description = ask( 'Plugin description?', "This is my plugin {$plugin_name}" ); - -while ( true ) { - $plugin_file = ask( 'Main plugin file?', "{$plugin_name_slug}.php" ); - - // Validate that plugin file is a valid file name. - if ( ! preg_match( '/^[a-zA-Z0-9-_\.]+\.php$/', $plugin_file ) ) { - echo "Invalid plugin file name. Please try again.\n"; - continue; - } - - // Validate that plugin file does not already exist. - if ( file_exists( $plugin_file ) ) { - echo "Plugin file already exists. Please try again.\n"; - continue; - } - - break; -} - -write( '------' ); -write( "Plugin : {$plugin_name} <{$plugin_name_slug}>" ); -write( "Author : {$author_name} ({$author_email})" ); -write( "Vendor : {$vendor_name} ({$vendor_slug})" ); -write( "Description : {$description}" ); -write( "Namespace : {$namespace}" ); -write( "Main File : {$plugin_file}" ); -write( "Main Class : {$class_name}" ); -write( '------' ); - -write( 'This script will replace the above values in all relevant files in the project directory.' ); - -if ( ! confirm( 'Modify files?', true ) ) { - exit( 1 ); -} - -$search_and_replace = [ - 'author_name' => $author_name, - 'author_username' => $author_username, - 'email@domain.com' => $author_email, - - 'A skeleton WordPress plugin' => $description, - - // Escape the namespace used in composer.json. - '"Create_WordPress_Plugin\\"' => (string) json_encode( $namespace ), - '"Create_WordPress_Plugin\\Tests\\"' => (string) json_encode( $namespace . '\\Tests' ), - - 'Create_WordPress_Plugin' => $namespace, - 'Example_Plugin' => $class_name, - - 'create_wordpress_plugin' => str_replace( '-', '_', $plugin_name_slug ), - 'plugin_name' => $plugin_name, - - 'create-wordpress-plugin' => $plugin_name_slug, - 'Create WordPress Plugin' => $plugin_name, - - 'CREATE_WORDPRESS_PLUGIN' => strtoupper( str_replace( '-', '_', $plugin_name_slug ) ), - 'Skeleton' => $class_name, - 'vendor_name' => $vendor_name, - 'alleyinteractive' => $vendor_slug, - 'plugin.php' => $plugin_file, -]; - -// Patch the Composer.json namespace first before search and replace. -run( - 'composer config extra.wordpress-autoloader.autoload --json \'' . json_encode( [ - $namespace => 'src', - ] ) . '\'', -); - -run( - 'composer config extra.wordpress-autoloader.autoload-dev --json \'' . json_encode( [ - $namespace . '\\Tests' => 'tests', - ] ) . '\'', -); - -foreach ( list_all_files_for_replacement() as $path ) { - echo "Updating $path...\n"; - - replace_in_file( $path, $search_and_replace ); - - if ( str_contains( $path, determine_separator( 'src/class-example-plugin.php' ) ) ) { - rename( $path, determine_separator( './src/class-' . str_replace( '_', '-', strtolower( $class_name ) ) . '.php' ) ); - } - - if ( str_contains( $path, 'README.md' ) ) { - remove_readme_paragraphs( $path ); - } -} - -// Attempt to rename the main plugin file. -if ( 'plugin.php' !== $plugin_file ) { - rename( 'plugin.php', $plugin_file ); - - replace_in_file( './.github/workflows/upgrade-wordpress-plugin.yml', $search_and_replace ); - - echo "Renamed plugin.php to {$plugin_file}\n"; -} - -echo "Done!\n\n"; - -$needs_built_assets = false; -$uses_composer = false; - -if ( confirm( 'Will this plugin be compiling front-end assets (Node)?', true ) ) { - $needs_built_assets = true; - - if ( confirm( 'Do you want to run `npm install` and `npm run build`?', true ) ) { - echo run( 'npm install && npm run build' ); - echo "\n\n\n"; - } - - remove_assets_readme( true ); -} elseif ( confirm( 'Do you want to delete the front-end files? (Such as package.json, etc.)', true ) ) { - echo "Deleting...\n"; - - delete_files( - [ - '.github/workflows/node-tests.yml', - '.eslintignore', - '.eslintrc.json', - '.nvmrc', - '.stylelintrc.json', - 'babel.config.js', - 'jest.config.js', - 'jsconfig.json', - 'package.json', - 'package-lock.json', - 'tsconfig.json', - 'entries/', - 'blocks/', - 'build/', - 'bin/', - 'node_modules/', - 'scaffold', - 'src/assets.php', - ] - ); - - remove_assets_readme( false ); - remove_assets_require(); - remove_assets_buddy(); -} - -if ( confirm( 'Will this plugin be using Composer? (WordPress Composer Autoloader already included! phpcs and phpunit also rely on Composer being installed for testing.)', true ) ) { - $uses_composer = true; - $needs_built_assets = true; - - remove_composer_wrapper_comments(); - - if ( confirm( 'Do you want to run `composer install`?', true ) ) { - if ( file_exists( __DIR__ . '/composer.lock' ) ) { - echo run( 'composer update' ); - } else { - echo run( 'composer install' ); - } - - echo "\n\n"; - } -} elseif ( confirm( 'Do you want to remove the vendor/autoload.php dependency from your main plugin file and the composer.json file?' ) ) { - remove_composer_require(); - - // Prompt the user to delete the composer.json file. Plugins often still - // keep this around for development and Packagist. - if ( confirm( 'Do you want to delete the composer.json and composer.lock files? (This will prevent you from using PHPCS/PHPStan/Composer entirely).', false ) ) { - remove_composer_files(); - } -} - -if ( file_exists( 'composer.json') && ! confirm(' Using PHPStan? (PHPStan is a great static analyzer to help find bugs in your code.)', true) ) { - remove_phpstan(); -} - -$standalone = true; - -// Check if the plugin will be use standalone (as a single repository) or as a -// part of larger project (such as a wp-content-rooted project). Assumes that -// the parent project is located at /wp-content/ and this plugin is located at -// /wp-content/plugins/:plugin/. -if ( - file_exists( '../../.git/index' ) - && ! confirm( - 'Will this be a standalone plugin, not located within a larger project? For example, a standalone plugin will have a separate repository and will be distributed independently.', - false, - ) -) { - $standalone = false; - - $needs_built_assets = false; - - if ( confirm( 'Do you want to remove project-based files, such as GitHub actions? (If this is a standalone plugin, these are probably in the root directory.)', true ) ) { - remove_project_files(); - } - - // Offer to roll up this plugin's dependencies to the parent project's composer. - if ( $uses_composer && file_exists( '../../composer.json' ) ) { - $parent_composer = (string) realpath( '../../composer.json' ); - $parent_folder = dirname( $parent_composer ); - - if ( confirm( "Do you want to rollup the plugin's Composer dependencies to the parent project's composer.json file ({$parent_composer})? This will copy this plugin's dependencies to the parent project and delete the local composer.json file.", true ) ) { - $composer = (array) json_decode( (string) file_get_contents( $parent_composer ), true ); - $plugin_composer = (array) json_decode( (string) file_get_contents( 'composer.json' ), true ); - - $original = $composer; - - $composer['require'] = array_merge( - (array) ( $composer['require'] ?? [] ), - (array) ( $plugin_composer['require'] ?? [] ), - ); - - $composer['require-dev'] = array_merge( - (array) ( $composer['require-dev'] ?? [] ), - (array) ( $plugin_composer['require-dev'] ?? [] ), - ); - - $composer['config']['allow-plugins']['alleyinteractive/composer-wordpress-autoloader'] = true; - - ksort( $composer['require'] ); - ksort( $composer['require-dev'] ); - ksort( $composer['config']['allow-plugins'] ); - - if ( $composer !== $original ) { - file_put_contents( $parent_composer, json_encode( $composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) ); - echo "Updated {$parent_composer} with the plugin's composer dependencies.\n"; - - remove_composer_require(); - remove_composer_files(); - - echo "\n\n"; - - if ( confirm( "Do you want to run `composer update` in {$parent_folder}?", true ) ) { - echo run( 'composer update', $parent_folder ); - echo "\n\n"; - } - } - - $parent_files = [ - $parent_folder . '/phpcs.xml', - $parent_folder . '/phpcs.xml.dist', - $parent_folder . '/.phpcs.xml', - ]; - - if ( file_exists( __DIR__ . '/.phpcs.xml' ) ) { - foreach ( $parent_files as $parent_file ) { - if ( ! file_exists( $parent_file ) ) { - continue; - } - - if ( confirm( "Do you want to roll up the phpcs configuration to the parent? (This will change the plugin's phpcs configuration to inherit the parent configuration from {$parent_file}.)" ) ) { - rollup_phpcs_to_parent( - parent_file: '../../' . basename( $parent_file ), - local_file: __DIR__ . '/.phpcs.xml', - plugin_name: $plugin_name, - plugin_domain: $plugin_name_slug, - ); - - break; - } - } - } - } - } - - if ( confirm( 'Do you want to remove the git repository for the plugin?', true ) ) { - delete_files( '.git' ); - } -} - -// Offer to delete the built asset workflows if built assets aren't needed. -if ( ! $needs_built_assets && file_exists( '.github/workflows/built-release.yml' ) && confirm( 'Delete the Github actions for built assets?', true ) ) { - delete_files( - [ - '.github/workflows/built-branch.yml', - '.github/workflows/built-release.yml', - ] - ); -} - -if ( - $standalone && file_exists( __DIR__ . '/buddy.yml' ) && ! confirm( 'Do you need the Buddy CI configuration? (Alley devs only -- if the plugin is open-source it will not be needed)', false ) -) { - delete_files( [ '.buddy', 'buddy.yml' ] ); -} - -if ( confirm( 'Let this script delete itself?', true ) ) { - delete_files( - [ - 'Makefile', - __FILE__, - ] - ); -} - -echo "\n\nWe're done! 🎉\n\n"; - -// Offer some information about built releases if the workflow still exists. -if ( file_exists( '.github/workflows/built-release.yml' ) ) { - echo <<