diff --git a/.github/workflows/built-release.yml b/.github/workflows/built-release.yml index cf60ecfc..089d63be 100644 --- a/.github/workflows/built-release.yml +++ b/.github/workflows/built-release.yml @@ -8,3 +8,5 @@ on: jobs: built-release: uses: alleyinteractive/.github/.github/workflows/built-release.yml@main + with: + composer_install: true 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/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/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/configure.php b/configure.php deleted file mode 100644 index 9fc4bdf4..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 WP_Newsletter_Builder\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. - '"WP_Newsletter_Builder\\"' => (string) json_encode( $namespace ), - '"WP_Newsletter_Builder\\Tests\\"' => (string) json_encode( $namespace . '\\Tests' ), - - 'WP_Newsletter_Builder' => $namespace, - 'Example_Plugin' => $class_name, - - 'wp_newsletter_builder' => str_replace( '-', '_', $plugin_name_slug ), - 'plugin_name' => $plugin_name, - - 'create-wordpress-plugin' => $plugin_name_slug, - 'Create WordPress Plugin' => $plugin_name, - - 'WP_NEWSLETTER_BUILDER' => 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 <<setup(); new Email_Types(); + new Settings(); new Media(); new WP_Newsletter_Builder(); new Rest_API_Endpoints(); new Rest_API_Fields(); new Rest_API_Query(); + // 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 new file mode 100644 index 00000000..7cb91c50 --- /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 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/class-campaign-monitor-client.php b/src/email-providers/class-campaign-monitor.php similarity index 62% rename from src/class-campaign-monitor-client.php rename to src/email-providers/class-campaign-monitor.php index 0b838013..4658a953 100644 --- a/src/class-campaign-monitor-client.php +++ b/src/email-providers/class-campaign-monitor.php @@ -5,14 +5,14 @@ * @package wp-newsletter-builder */ -namespace WP_Newsletter_Builder; +namespace WP_Newsletter_Builder\Email_Providers; use WP_Newsletter_Builder\Singleton; /** * Campaign Monitor Client class */ -class Campaign_Monitor_Client { +class Campaign_Monitor implements Email_Provider { use Singleton; /** @@ -31,41 +31,14 @@ public function setup() { add_action( 'init', [ $this, 'maybe_register_settings_page' ] ); } - /** - * 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 @@ +